").append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+ // Otherwise use the full result
+ responseText );
+
+ }).complete( callback && function( jqXHR, status ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ });
+ }
+
+ return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
+ jQuery.fn[ type ] = function( fn ){
+ return this.on( type, fn );
+ };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ url: url,
+ type: method,
+ dataType: type,
+ data: data,
+ success: callback
+ });
+ };
+});
+
+jQuery.extend({
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {},
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ type: "GET",
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ processData: true,
+ async: true,
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ "*": allTypes,
+ text: "text/plain",
+ html: "text/html",
+ xml: "application/xml, text/xml",
+ json: "application/json, text/javascript"
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // Data converters
+ // Keys separate source (or catchall "*") and destination types with a single space
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ url: true,
+ context: true
+ }
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ return settings ?
+
+ // Building a settings object
+ ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+ // Extending ajaxSettings
+ ajaxExtend( jQuery.ajaxSettings, target );
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // Cross-domain detection vars
+ parts,
+ // Loop variable
+ i,
+ // URL without anti-cache param
+ cacheURL,
+ // Response headers as string
+ responseHeadersString,
+ // timeout handle
+ timeoutTimer,
+
+ // To know if global events are to be dispatched
+ fireGlobals,
+
+ transport,
+ // Response headers
+ responseHeaders,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events is callbackContext if it is a DOM node or jQuery collection
+ globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+ jQuery( callbackContext ) :
+ jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks("once memory"),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+ readyState: 0,
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while ( (match = rheaders.exec( responseHeadersString )) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match == null ? null : match;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ var lname = name.toLowerCase();
+ if ( !state ) {
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Status-dependent callbacks
+ statusCode: function( map ) {
+ var code;
+ if ( map ) {
+ if ( state < 2 ) {
+ for ( code in map ) {
+ // Lazy-add the new callback in a way that preserves old ones
+ statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+ }
+ } else {
+ // Execute the appropriate callbacks
+ jqXHR.always( map[ jqXHR.status ] );
+ }
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ var finalText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( finalText );
+ }
+ done( 0, finalText );
+ return this;
+ }
+ };
+
+ // Attach deferreds
+ deferred.promise( jqXHR ).complete = completeDeferred.add;
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // Handle falsy url in the settings object (#10093: consistency with old signature)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Alias method option to type as per ticket #12004
+ s.type = options.method || options.type || s.method || s.type;
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
+
+ // A cross-domain request is in order when we have a protocol:host:port mismatch
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() );
+ s.crossDomain = !!( parts &&
+ ( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+ ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
+ ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
+ );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger("ajaxStart");
+ }
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Save the URL in case we're toying with the If-Modified-Since
+ // and/or If-None-Match header later on
+ cacheURL = s.url;
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+ s.url = rts.test( cacheURL ) ?
+
+ // If there is already a '_' parameter, set its value
+ cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
+
+ // Otherwise add one to the end
+ cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
+ }
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ if ( jQuery.lastModified[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+ }
+ if ( jQuery.etag[ cacheURL ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+ }
+
+ // aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout(function() {
+ jqXHR.abort("timeout");
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch ( e ) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ // Callback for when everything is done
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ cacheURL ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("etag");
+ if ( modified ) {
+ jQuery.etag[ cacheURL ] = modified;
+ }
+ }
+
+ // if no content
+ if ( status === 204 ) {
+ isSuccess = true;
+ statusText = "nocontent";
+
+ // if not modified
+ } else if ( status === 304 ) {
+ isSuccess = true;
+ statusText = "notmodified";
+
+ // If we have data, let's convert it
+ } else {
+ isSuccess = ajaxConvert( s, response );
+ statusText = isSuccess.state;
+ success = isSuccess.data;
+ error = isSuccess.error;
+ isSuccess = !error;
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( status || !statusText ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger("ajaxStop");
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ }
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+ var firstDataType, ct, finalDataType, type,
+ contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields;
+
+ // Fill responseXXX fields
+ for ( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+ var conv2, current, conv, tmp,
+ converters = {},
+ i = 0,
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice(),
+ prev = dataTypes[ 0 ];
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ // Convert to each sequential dataType, tolerating list modification
+ for ( ; (current = dataTypes[++i]); ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current !== "*" ) {
+
+ // Convert response if prev dataType is non-auto and differs from current
+ if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split(" ");
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.splice( i--, 0, current );
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s["throws"] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+
+ // Update prev for next iteration
+ prev = current;
+ }
+ }
+
+ return { state: "success", data: response };
+}
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /(?:java|ecma)script/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || jQuery("head")[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement("script");
+
+ script.async = true;
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( script.parentNode ) {
+ script.parentNode.removeChild( script );
+ }
+
+ // Dereference the script
+ script = null;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+
+ // Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
+ // Use native DOM manipulation to avoid our domManip AJAX trickery
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( undefined, true );
+ }
+ }
+ };
+ }
+});
+var oldCallbacks = [],
+ rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
+ this[ callback ] = true;
+ return callback;
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var callbackName, overwritten, responseContainer,
+ jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+ "url" :
+ typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
+ );
+
+ // Handle iff the expected data type is "jsonp" or we have a parameter to set
+ if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+ // Get callback name, remembering preexisting value associated with it
+ callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+ s.jsonpCallback() :
+ s.jsonpCallback;
+
+ // Insert callback into url or form data
+ if ( jsonProp ) {
+ s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+ } else if ( s.jsonp !== false ) {
+ s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+ }
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( callbackName + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Install callback
+ overwritten = window[ callbackName ];
+ window[ callbackName ] = function() {
+ responseContainer = arguments;
+ };
+
+ // Clean-up function (fires after converters)
+ jqXHR.always(function() {
+ // Restore preexisting value
+ window[ callbackName ] = overwritten;
+
+ // Save back as free
+ if ( s[ callbackName ] ) {
+ // make sure that re-using the options doesn't screw things around
+ s.jsonpCallback = originalSettings.jsonpCallback;
+
+ // save the callback name for future use
+ oldCallbacks.push( callbackName );
+ }
+
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+ overwritten( responseContainer[ 0 ] );
+ }
+
+ responseContainer = overwritten = undefined;
+ });
+
+ // Delegate to script
+ return "script";
+ }
+});
+var xhrCallbacks, xhrSupported,
+ xhrId = 0,
+ // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+ xhrOnUnloadAbort = window.ActiveXObject && function() {
+ // Abort all pending requests
+ var key;
+ for ( key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( undefined, true );
+ }
+ };
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject("Microsoft.XMLHTTP");
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Determine support properties
+xhrSupported = jQuery.ajaxSettings.xhr();
+jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+xhrSupported = jQuery.support.ajax = !!xhrSupported;
+
+// Create transport if the browser can provide an xhr
+if ( xhrSupported ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var handle, i,
+ xhr = s.xhr();
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers["X-Requested-With"] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( err ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+ var status, responseHeaders, statusText, responses;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occurred
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ responses = {};
+ status = xhr.status;
+ responseHeaders = xhr.getAllResponseHeaders();
+
+ // When requesting binary data, IE6-9 will throw an exception
+ // on any attempt to access responseText (#11426)
+ if ( typeof xhr.responseText === "string" ) {
+ responses.text = xhr.responseText;
+ }
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ if ( !s.async ) {
+ // if we're in sync mode we fire the callback
+ callback();
+ } else if ( xhr.readyState === 4 ) {
+ // (IE6 & IE7) if it's in cache and has been
+ // retrieved directly we need to fire the callback
+ setTimeout( callback );
+ } else {
+ handle = ++xhrId;
+ if ( xhrOnUnloadAbort ) {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ jQuery( window ).unload( xhrOnUnloadAbort );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback( undefined, true );
+ }
+ }
+ };
+ }
+ });
+}
+var fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+ rrun = /queueHooks$/,
+ animationPrefilters = [ defaultPrefilter ],
+ tweeners = {
+ "*": [function( prop, value ) {
+ var end, unit,
+ tween = this.createTween( prop, value ),
+ parts = rfxnum.exec( value ),
+ target = tween.cur(),
+ start = +target || 0,
+ scale = 1,
+ maxIterations = 20;
+
+ if ( parts ) {
+ end = +parts[2];
+ unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" && start ) {
+ // Iteratively approximate from a nonzero starting point
+ // Prefer the current property, because this process will be trivial if it uses the same units
+ // Fallback to end or a simple constant
+ start = jQuery.css( tween.elem, prop, true ) || end || 1;
+
+ do {
+ // If previous iteration zeroed out, double until we get *something*
+ // Use a string for doubling factor so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
+
+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
+
+ // Update scale, tolerating zero or NaN from tween.cur()
+ // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+ } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+ }
+
+ tween.unit = unit;
+ tween.start = start;
+ // If a +=/-= token was provided, we're doing a relative animation
+ tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
+ }
+ return tween;
+ }]
+ };
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ });
+ return ( fxNow = jQuery.now() );
+}
+
+function createTweens( animation, props ) {
+ jQuery.each( props, function( prop, value ) {
+ var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( collection[ index ].call( animation, prop, value ) ) {
+
+ // we're done with this property
+ return;
+ }
+ }
+ });
+}
+
+function Animation( elem, properties, options ) {
+ var result,
+ stopped,
+ index = 0,
+ length = animationPrefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+ // don't match elem in the :animated selector
+ delete tick.elem;
+ }),
+ tick = function() {
+ if ( stopped ) {
+ return false;
+ }
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+ // archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
+ temp = remaining / animation.duration || 0,
+ percent = 1 - temp,
+ index = 0,
+ length = animation.tweens.length;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+ if ( percent < 1 && length ) {
+ return remaining;
+ } else {
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ }
+ },
+ animation = deferred.promise({
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, { specialEasing: {} }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+ // if we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+ if ( stopped ) {
+ return this;
+ }
+ stopped = true;
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
+
+ // resolve when we played the last frame
+ // otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ }),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length ; index++ ) {
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ return result;
+ }
+ }
+
+ createTweens( animation, props );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ elem: elem,
+ anim: animation,
+ queue: animation.opts.queue
+ })
+ );
+
+ // attach callbacks from options
+ return animation.progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+}
+
+function propFilter( props, specialEasing ) {
+ var value, name, index, easing, hooks;
+
+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = jQuery.camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( jQuery.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'index' from above because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
+ }
+});
+
+function defaultPrefilter( elem, props, opts ) {
+ /*jshint validthis:true */
+ var prop, index, length,
+ value, dataShow, toggle,
+ tween, hooks, oldfire,
+ anim = this,
+ style = elem.style,
+ orig = {},
+ handled = [],
+ hidden = elem.nodeType && isHidden( elem );
+
+ // handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+
+ anim.always(function() {
+ // doing this makes sure that the complete handler will be called
+ // before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ });
+ }
+
+ // height/width overflow pass
+ if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ if ( jQuery.css( elem, "display" ) === "inline" &&
+ jQuery.css( elem, "float" ) === "none" ) {
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
+ style.display = "inline-block";
+
+ } else {
+ style.zoom = 1;
+ }
+ }
+ }
+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ if ( !jQuery.support.shrinkWrapBlocks ) {
+ anim.always(function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ });
+ }
+ }
+
+
+ // show/hide pass
+ for ( index in props ) {
+ value = props[ index ];
+ if ( rfxtypes.exec( value ) ) {
+ delete props[ index ];
+ toggle = toggle || value === "toggle";
+ if ( value === ( hidden ? "hide" : "show" ) ) {
+ continue;
+ }
+ handled.push( index );
+ }
+ }
+
+ length = handled.length;
+ if ( length ) {
+ dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
+ if ( "hidden" in dataShow ) {
+ hidden = dataShow.hidden;
+ }
+
+ // store state if its toggle - enables .stop().toggle() to "reverse"
+ if ( toggle ) {
+ dataShow.hidden = !hidden;
+ }
+ if ( hidden ) {
+ jQuery( elem ).show();
+ } else {
+ anim.done(function() {
+ jQuery( elem ).hide();
+ });
+ }
+ anim.done(function() {
+ var prop;
+ jQuery._removeData( elem, "fxshow" );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ });
+ for ( index = 0 ; index < length ; index++ ) {
+ prop = handled[ index ];
+ tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
+ orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
+
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+ }
+}
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || "swing";
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ if ( this.options.duration ) {
+ this.pos = eased = jQuery.easing[ this.easing ](
+ percent, this.options.duration * percent, 0, 1, this.options.duration
+ );
+ } else {
+ this.pos = eased = percent;
+ }
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // passing an empty string as a 3rd parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails
+ // so, simple values such as "10px" are parsed to Float.
+ // complex values such as "rotate(1rad)" are returned as is.
+ result = jQuery.css( tween.elem, tween.prop, "" );
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+ // use step hook for back compat - use cssHook if its there - use .style if its
+ // available and use plain properties where available
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+ }
+};
+
+// Remove in 2.0 - this supports IE8's panic based approach
+// to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
+});
+
+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+ // animate to the value specified
+ .end().animate({ opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+ doAnimation.finish = function() {
+ anim.stop( true );
+ };
+ // Empty animations, or finishing resolves immediately
+ if ( empty || jQuery._data( this, "finish" ) ) {
+ anim.stop( true );
+ }
+ };
+ doAnimation.finish = doAnimation;
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ finish: function( type ) {
+ if ( type !== false ) {
+ type = type || "fx";
+ }
+ return this.each(function() {
+ var index,
+ data = jQuery._data( this ),
+ queue = data[ type + "queue" ],
+ hooks = data[ type + "queueHooks" ],
+ timers = jQuery.timers,
+ length = queue ? queue.length : 0;
+
+ // enable finishing flag on private data
+ data.finish = true;
+
+ // empty the queue first
+ jQuery.queue( this, type, [] );
+
+ if ( hooks && hooks.cur && hooks.cur.finish ) {
+ hooks.cur.finish.call( this );
+ }
+
+ // look for any active animations, and finish them
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+ timers[ index ].anim.stop( true );
+ timers.splice( index, 1 );
+ }
+ }
+
+ // look for any animations in the old queue and finish them
+ for ( index = 0; index < length; index++ ) {
+ if ( queue[ index ] && queue[ index ].finish ) {
+ queue[ index ].finish.call( this );
+ }
+ }
+
+ // turn off finishing flag
+ delete data.finish;
+ });
+ }
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ attrs = { height: type },
+ i = 0;
+
+ // if we include width, step value is 1 to do all cssExpand values,
+ // if we don't include width, step value is 2 to skip over Left and Right
+ includeWidth = includeWidth? 1 : 0;
+ for( ; i < 4 ; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show"),
+ slideUp: genFx("hide"),
+ slideToggle: genFx("toggle"),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function() {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
+
+ return opt;
+};
+
+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p*Math.PI ) / 2;
+ }
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ fxNow = jQuery.now();
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+ fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+ if ( timer() && jQuery.timers.push( timer ) ) {
+ jQuery.fx.start();
+ }
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+ if ( !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+ }
+};
+
+jQuery.fx.stop = function() {
+ clearInterval( timerId );
+ timerId = null;
+};
+
+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+};
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+jQuery.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var docElem, win,
+ box = { top: 0, left: 0 },
+ elem = this[ 0 ],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return;
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure it's not a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return box;
+ }
+
+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if ( typeof elem.getBoundingClientRect !== core_strundefined ) {
+ box = elem.getBoundingClientRect();
+ }
+ win = getWindow( doc );
+ return {
+ top: box.top + ( win.pageYOffset || docElem.scrollTop ) - ( docElem.clientTop || 0 ),
+ left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
+ };
+};
+
+jQuery.offset = {
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[ 0 ] ) {
+ return;
+ }
+
+ var offsetParent, offset,
+ parentOffset = { top: 0, left: 0 },
+ elem = this[ 0 ];
+
+ // fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
+ if ( jQuery.css( elem, "position" ) === "fixed" ) {
+ // we assume that getBoundingClientRect is available when computed position is fixed
+ offset = elem.getBoundingClientRect();
+ } else {
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent();
+
+ // Get correct offsets
+ offset = this.offset();
+ if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+ parentOffset = offsetParent.offset();
+ }
+
+ // Add offsetParent borders
+ parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+ parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+ }
+
+ // Subtract parent offsets and element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ return {
+ top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+ left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.documentElement;
+ while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || document.documentElement;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ win.document.documentElement[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return jQuery.access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+ // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable, null );
+ };
+ });
+});
+// Limit scope pollution from any deprecated API
+// (function() {
+
+// })();
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+ define( "jquery", [], function () { return jQuery; } );
+}
+
+})( window );
diff --git a/framework/yii/assets/jquery.maskedinput.js b/framework/yii/assets/jquery.maskedinput.js
new file mode 100644
index 0000000..49a5a72
--- /dev/null
+++ b/framework/yii/assets/jquery.maskedinput.js
@@ -0,0 +1,338 @@
+/*
+ Masked Input plugin for jQuery
+ Copyright (c) 2007-2013 Josh Bush (digitalbush.com)
+ Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
+ Version: 1.3.1
+*/
+(function($) {
+ function getPasteEvent() {
+ var el = document.createElement('input'),
+ name = 'onpaste';
+ el.setAttribute(name, '');
+ return (typeof el[name] === 'function')?'paste':'input';
+}
+
+var pasteEventName = getPasteEvent() + ".mask",
+ ua = navigator.userAgent,
+ iPhone = /iphone/i.test(ua),
+ android=/android/i.test(ua),
+ caretTimeoutId;
+
+$.mask = {
+ //Predefined character definitions
+ definitions: {
+ '9': "[0-9]",
+ 'a': "[A-Za-z]",
+ '*': "[A-Za-z0-9]"
+ },
+ dataName: "rawMaskFn",
+ placeholder: '_',
+};
+
+$.fn.extend({
+ //Helper Function for Caret positioning
+ caret: function(begin, end) {
+ var range;
+
+ if (this.length === 0 || this.is(":hidden")) {
+ return;
+ }
+
+ if (typeof begin == 'number') {
+ end = (typeof end === 'number') ? end : begin;
+ return this.each(function() {
+ if (this.setSelectionRange) {
+ this.setSelectionRange(begin, end);
+ } else if (this.createTextRange) {
+ range = this.createTextRange();
+ range.collapse(true);
+ range.moveEnd('character', end);
+ range.moveStart('character', begin);
+ range.select();
+ }
+ });
+ } else {
+ if (this[0].setSelectionRange) {
+ begin = this[0].selectionStart;
+ end = this[0].selectionEnd;
+ } else if (document.selection && document.selection.createRange) {
+ range = document.selection.createRange();
+ begin = 0 - range.duplicate().moveStart('character', -100000);
+ end = begin + range.text.length;
+ }
+ return { begin: begin, end: end };
+ }
+ },
+ unmask: function() {
+ return this.trigger("unmask");
+ },
+ mask: function(mask, settings) {
+ var input,
+ defs,
+ tests,
+ partialPosition,
+ firstNonMaskPos,
+ len;
+
+ if (!mask && this.length > 0) {
+ input = $(this[0]);
+ return input.data($.mask.dataName)();
+ }
+ settings = $.extend({
+ placeholder: $.mask.placeholder, // Load default placeholder
+ completed: null
+ }, settings);
+
+
+ defs = $.mask.definitions;
+ tests = [];
+ partialPosition = len = mask.length;
+ firstNonMaskPos = null;
+
+ $.each(mask.split(""), function(i, c) {
+ if (c == '?') {
+ len--;
+ partialPosition = i;
+ } else if (defs[c]) {
+ tests.push(new RegExp(defs[c]));
+ if (firstNonMaskPos === null) {
+ firstNonMaskPos = tests.length - 1;
+ }
+ } else {
+ tests.push(null);
+ }
+ });
+
+ return this.trigger("unmask").each(function() {
+ var input = $(this),
+ buffer = $.map(
+ mask.split(""),
+ function(c, i) {
+ if (c != '?') {
+ return defs[c] ? settings.placeholder : c;
+ }
+ }),
+ focusText = input.val();
+
+ function seekNext(pos) {
+ while (++pos < len && !tests[pos]);
+ return pos;
+ }
+
+ function seekPrev(pos) {
+ while (--pos >= 0 && !tests[pos]);
+ return pos;
+ }
+
+ function shiftL(begin,end) {
+ var i,
+ j;
+
+ if (begin<0) {
+ return;
+ }
+
+ for (i = begin, j = seekNext(end); i < len; i++) {
+ if (tests[i]) {
+ if (j < len && tests[i].test(buffer[j])) {
+ buffer[i] = buffer[j];
+ buffer[j] = settings.placeholder;
+ } else {
+ break;
+ }
+
+ j = seekNext(j);
+ }
+ }
+ writeBuffer();
+ input.caret(Math.max(firstNonMaskPos, begin));
+ }
+
+ function shiftR(pos) {
+ var i,
+ c,
+ j,
+ t;
+
+ for (i = pos, c = settings.placeholder; i < len; i++) {
+ if (tests[i]) {
+ j = seekNext(i);
+ t = buffer[i];
+ buffer[i] = c;
+ if (j < len && tests[j].test(t)) {
+ c = t;
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ function keydownEvent(e) {
+ var k = e.which,
+ pos,
+ begin,
+ end;
+
+ //backspace, delete, and escape get special treatment
+ if (k === 8 || k === 46 || (iPhone && k === 127)) {
+ pos = input.caret();
+ begin = pos.begin;
+ end = pos.end;
+
+ if (end - begin === 0) {
+ begin=k!==46?seekPrev(begin):(end=seekNext(begin-1));
+ end=k===46?seekNext(end):end;
+ }
+ clearBuffer(begin, end);
+ shiftL(begin, end - 1);
+
+ e.preventDefault();
+ } else if (k == 27) {//escape
+ input.val(focusText);
+ input.caret(0, checkVal());
+ e.preventDefault();
+ }
+ }
+
+ function keypressEvent(e) {
+ var k = e.which,
+ pos = input.caret(),
+ p,
+ c,
+ next;
+
+ if (e.ctrlKey || e.altKey || e.metaKey || k < 32) {//Ignore
+ return;
+ } else if (k) {
+ if (pos.end - pos.begin !== 0){
+ clearBuffer(pos.begin, pos.end);
+ shiftL(pos.begin, pos.end-1);
+ }
+
+ p = seekNext(pos.begin - 1);
+ if (p < len) {
+ c = String.fromCharCode(k);
+ if (tests[p].test(c)) {
+ shiftR(p);
+
+ buffer[p] = c;
+ writeBuffer();
+ next = seekNext(p);
+
+ if(android){
+ setTimeout($.proxy($.fn.caret,input,next),0);
+ }else{
+ input.caret(next);
+ }
+
+ if (settings.completed && next >= len) {
+ settings.completed.call(input);
+ }
+ }
+ }
+ e.preventDefault();
+ }
+ }
+
+ function clearBuffer(start, end) {
+ var i;
+ for (i = start; i < end && i < len; i++) {
+ if (tests[i]) {
+ buffer[i] = settings.placeholder;
+ }
+ }
+ }
+
+ function writeBuffer() { input.val(buffer.join('')); }
+
+ function checkVal(allow) {
+ //try to place characters where they belong
+ var test = input.val(),
+ lastMatch = -1,
+ i,
+ c;
+
+ for (i = 0, pos = 0; i < len; i++) {
+ if (tests[i]) {
+ buffer[i] = settings.placeholder;
+ while (pos++ < test.length) {
+ c = test.charAt(pos - 1);
+ if (tests[i].test(c)) {
+ buffer[i] = c;
+ lastMatch = i;
+ break;
+ }
+ }
+ if (pos > test.length) {
+ break;
+ }
+ } else if (buffer[i] === test.charAt(pos) && i !== partialPosition) {
+ pos++;
+ lastMatch = i;
+ }
+ }
+ if (allow) {
+ writeBuffer();
+ } else if (lastMatch + 1 < partialPosition) {
+ input.val("");
+ clearBuffer(0, len);
+ } else {
+ writeBuffer();
+ input.val(input.val().substring(0, lastMatch + 1));
+ }
+ return (partialPosition ? i : firstNonMaskPos);
+ }
+
+ input.data($.mask.dataName,function(){
+ return $.map(buffer, function(c, i) {
+ return tests[i]&&c!=settings.placeholder ? c : null;
+ }).join('');
+ });
+
+ if (!input.attr("readonly"))
+ input
+ .one("unmask", function() {
+ input
+ .unbind(".mask")
+ .removeData($.mask.dataName);
+ })
+ .bind("focus.mask", function() {
+ clearTimeout(caretTimeoutId);
+ var pos,
+ moveCaret;
+
+ focusText = input.val();
+ pos = checkVal();
+
+ caretTimeoutId = setTimeout(function(){
+ writeBuffer();
+ if (pos == mask.length) {
+ input.caret(0, pos);
+ } else {
+ input.caret(pos);
+ }
+ }, 10);
+ })
+ .bind("blur.mask", function() {
+ checkVal();
+ if (input.val() != focusText)
+ input.change();
+ })
+ .bind("keydown.mask", keydownEvent)
+ .bind("keypress.mask", keypressEvent)
+ .bind(pasteEventName, function() {
+ setTimeout(function() {
+ var pos=checkVal(true);
+ input.caret(pos);
+ if (settings.completed && pos == input.val().length)
+ settings.completed.call(input);
+ }, 0);
+ });
+ checkVal(); //Perform initial check for existing values
+ });
+ }
+});
+
+
+})(jQuery);
\ No newline at end of file
diff --git a/yii/assets/jquery.min.js b/framework/yii/assets/jquery.min.js
similarity index 100%
rename from yii/assets/jquery.min.js
rename to framework/yii/assets/jquery.min.js
diff --git a/framework/yii/assets/punycode/LICENSE-GPL.txt b/framework/yii/assets/punycode/LICENSE-GPL.txt
new file mode 100644
index 0000000..11dddd0
--- /dev/null
+++ b/framework/yii/assets/punycode/LICENSE-GPL.txt
@@ -0,0 +1,278 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
diff --git a/framework/yii/assets/punycode/LICENSE-MIT.txt b/framework/yii/assets/punycode/LICENSE-MIT.txt
new file mode 100644
index 0000000..97067e5
--- /dev/null
+++ b/framework/yii/assets/punycode/LICENSE-MIT.txt
@@ -0,0 +1,20 @@
+Copyright Mathias Bynens
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/framework/yii/assets/punycode/punycode.js b/framework/yii/assets/punycode/punycode.js
new file mode 100644
index 0000000..6242382
--- /dev/null
+++ b/framework/yii/assets/punycode/punycode.js
@@ -0,0 +1,502 @@
+/*! http://mths.be/punycode v1.2.1 by @mathias */
+;(function(root) {
+
+ /** Detect free variables */
+ var freeExports = typeof exports == 'object' && exports;
+ var freeModule = typeof module == 'object' && module &&
+ module.exports == freeExports && module;
+ var freeGlobal = typeof global == 'object' && global;
+ if (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal) {
+ root = freeGlobal;
+ }
+
+ /**
+ * The `punycode` object.
+ * @name punycode
+ * @type Object
+ */
+ var punycode,
+
+ /** Highest positive signed 32-bit float value */
+ maxInt = 2147483647, // aka. 0x7FFFFFFF or 2^31-1
+
+ /** Bootstring parameters */
+ base = 36,
+ tMin = 1,
+ tMax = 26,
+ skew = 38,
+ damp = 700,
+ initialBias = 72,
+ initialN = 128, // 0x80
+ delimiter = '-', // '\x2D'
+
+ /** Regular expressions */
+ regexPunycode = /^xn--/,
+ regexNonASCII = /[^ -~]/, // unprintable ASCII chars + non-ASCII chars
+ regexSeparators = /\x2E|\u3002|\uFF0E|\uFF61/g, // RFC 3490 separators
+
+ /** Error messages */
+ errors = {
+ 'overflow': 'Overflow: input needs wider integers to process',
+ 'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
+ 'invalid-input': 'Invalid input'
+ },
+
+ /** Convenience shortcuts */
+ baseMinusTMin = base - tMin,
+ floor = Math.floor,
+ stringFromCharCode = String.fromCharCode,
+
+ /** Temporary variable */
+ key;
+
+ /*--------------------------------------------------------------------------*/
+
+ /**
+ * A generic error utility function.
+ * @private
+ * @param {String} type The error type.
+ * @returns {Error} Throws a `RangeError` with the applicable error message.
+ */
+ function error(type) {
+ throw RangeError(errors[type]);
+ }
+
+ /**
+ * A generic `Array#map` utility function.
+ * @private
+ * @param {Array} array The array to iterate over.
+ * @param {Function} callback The function that gets called for every array
+ * item.
+ * @returns {Array} A new array of values returned by the callback function.
+ */
+ function map(array, fn) {
+ var length = array.length;
+ while (length--) {
+ array[length] = fn(array[length]);
+ }
+ return array;
+ }
+
+ /**
+ * A simple `Array#map`-like wrapper to work with domain name strings.
+ * @private
+ * @param {String} domain The domain name.
+ * @param {Function} callback The function that gets called for every
+ * character.
+ * @returns {Array} A new string of characters returned by the callback
+ * function.
+ */
+ function mapDomain(string, fn) {
+ return map(string.split(regexSeparators), fn).join('.');
+ }
+
+ /**
+ * Creates an array containing the decimal code points of each Unicode
+ * character in the string. While JavaScript uses UCS-2 internally,
+ * this function will convert a pair of surrogate halves (each of which
+ * UCS-2 exposes as separate characters) into a single code point,
+ * matching UTF-16.
+ * @see `punycode.ucs2.encode`
+ * @see
+ * @memberOf punycode.ucs2
+ * @name decode
+ * @param {String} string The Unicode input string (UCS-2).
+ * @returns {Array} The new array of code points.
+ */
+ function ucs2decode(string) {
+ var output = [],
+ counter = 0,
+ length = string.length,
+ value,
+ extra;
+ while (counter < length) {
+ value = string.charCodeAt(counter++);
+ if ((value & 0xF800) == 0xD800 && counter < length) {
+ // high surrogate, and there is a next character
+ extra = string.charCodeAt(counter++);
+ if ((extra & 0xFC00) == 0xDC00) { // low surrogate
+ output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
+ } else {
+ output.push(value, extra);
+ }
+ } else {
+ output.push(value);
+ }
+ }
+ return output;
+ }
+
+ /**
+ * Creates a string based on an array of decimal code points.
+ * @see `punycode.ucs2.decode`
+ * @memberOf punycode.ucs2
+ * @name encode
+ * @param {Array} codePoints The array of decimal code points.
+ * @returns {String} The new Unicode string (UCS-2).
+ */
+ function ucs2encode(array) {
+ return map(array, function(value) {
+ var output = '';
+ if (value > 0xFFFF) {
+ value -= 0x10000;
+ output += stringFromCharCode(value >>> 10 & 0x3FF | 0xD800);
+ value = 0xDC00 | value & 0x3FF;
+ }
+ output += stringFromCharCode(value);
+ return output;
+ }).join('');
+ }
+
+ /**
+ * Converts a basic code point into a digit/integer.
+ * @see `digitToBasic()`
+ * @private
+ * @param {Number} codePoint The basic (decimal) code point.
+ * @returns {Number} The numeric value of a basic code point (for use in
+ * representing integers) in the range `0` to `base - 1`, or `base` if
+ * the code point does not represent a value.
+ */
+ function basicToDigit(codePoint) {
+ return codePoint - 48 < 10
+ ? codePoint - 22
+ : codePoint - 65 < 26
+ ? codePoint - 65
+ : codePoint - 97 < 26
+ ? codePoint - 97
+ : base;
+ }
+
+ /**
+ * Converts a digit/integer into a basic code point.
+ * @see `basicToDigit()`
+ * @private
+ * @param {Number} digit The numeric value of a basic code point.
+ * @returns {Number} The basic code point whose value (when used for
+ * representing integers) is `digit`, which needs to be in the range
+ * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
+ * used; else, the lowercase form is used. The behavior is undefined
+ * if flag is non-zero and `digit` has no uppercase form.
+ */
+ function digitToBasic(digit, flag) {
+ // 0..25 map to ASCII a..z or A..Z
+ // 26..35 map to ASCII 0..9
+ return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
+ }
+
+ /**
+ * Bias adaptation function as per section 3.4 of RFC 3492.
+ * http://tools.ietf.org/html/rfc3492#section-3.4
+ * @private
+ */
+ function adapt(delta, numPoints, firstTime) {
+ var k = 0;
+ delta = firstTime ? floor(delta / damp) : delta >> 1;
+ delta += floor(delta / numPoints);
+ for (/* no initialization */; delta > baseMinusTMin * tMax >> 1; k += base) {
+ delta = floor(delta / baseMinusTMin);
+ }
+ return floor(k + (baseMinusTMin + 1) * delta / (delta + skew));
+ }
+
+ /**
+ * Converts a Punycode string of ASCII code points to a string of Unicode
+ * code points.
+ * @memberOf punycode
+ * @param {String} input The Punycode string of ASCII code points.
+ * @returns {String} The resulting string of Unicode code points.
+ */
+ function decode(input) {
+ // Don't use UCS-2
+ var output = [],
+ inputLength = input.length,
+ out,
+ i = 0,
+ n = initialN,
+ bias = initialBias,
+ basic,
+ j,
+ index,
+ oldi,
+ w,
+ k,
+ digit,
+ t,
+ length,
+ /** Cached calculation results */
+ baseMinusT;
+
+ // Handle the basic code points: let `basic` be the number of input code
+ // points before the last delimiter, or `0` if there is none, then copy
+ // the first basic code points to the output.
+
+ basic = input.lastIndexOf(delimiter);
+ if (basic < 0) {
+ basic = 0;
+ }
+
+ for (j = 0; j < basic; ++j) {
+ // if it's not a basic code point
+ if (input.charCodeAt(j) >= 0x80) {
+ error('not-basic');
+ }
+ output.push(input.charCodeAt(j));
+ }
+
+ // Main decoding loop: start just after the last delimiter if any basic code
+ // points were copied; start at the beginning otherwise.
+
+ for (index = basic > 0 ? basic + 1 : 0; index < inputLength; /* no final expression */) {
+
+ // `index` is the index of the next character to be consumed.
+ // Decode a generalized variable-length integer into `delta`,
+ // which gets added to `i`. The overflow checking is easier
+ // if we increase `i` as we go, then subtract off its starting
+ // value at the end to obtain `delta`.
+ for (oldi = i, w = 1, k = base; /* no condition */; k += base) {
+
+ if (index >= inputLength) {
+ error('invalid-input');
+ }
+
+ digit = basicToDigit(input.charCodeAt(index++));
+
+ if (digit >= base || digit > floor((maxInt - i) / w)) {
+ error('overflow');
+ }
+
+ i += digit * w;
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+
+ if (digit < t) {
+ break;
+ }
+
+ baseMinusT = base - t;
+ if (w > floor(maxInt / baseMinusT)) {
+ error('overflow');
+ }
+
+ w *= baseMinusT;
+
+ }
+
+ out = output.length + 1;
+ bias = adapt(i - oldi, out, oldi == 0);
+
+ // `i` was supposed to wrap around from `out` to `0`,
+ // incrementing `n` each time, so we'll fix that now:
+ if (floor(i / out) > maxInt - n) {
+ error('overflow');
+ }
+
+ n += floor(i / out);
+ i %= out;
+
+ // Insert `n` at position `i` of the output
+ output.splice(i++, 0, n);
+
+ }
+
+ return ucs2encode(output);
+ }
+
+ /**
+ * Converts a string of Unicode code points to a Punycode string of ASCII
+ * code points.
+ * @memberOf punycode
+ * @param {String} input The string of Unicode code points.
+ * @returns {String} The resulting Punycode string of ASCII code points.
+ */
+ function encode(input) {
+ var n,
+ delta,
+ handledCPCount,
+ basicLength,
+ bias,
+ j,
+ m,
+ q,
+ k,
+ t,
+ currentValue,
+ output = [],
+ /** `inputLength` will hold the number of code points in `input`. */
+ inputLength,
+ /** Cached calculation results */
+ handledCPCountPlusOne,
+ baseMinusT,
+ qMinusT;
+
+ // Convert the input in UCS-2 to Unicode
+ input = ucs2decode(input);
+
+ // Cache the length
+ inputLength = input.length;
+
+ // Initialize the state
+ n = initialN;
+ delta = 0;
+ bias = initialBias;
+
+ // Handle the basic code points
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue < 0x80) {
+ output.push(stringFromCharCode(currentValue));
+ }
+ }
+
+ handledCPCount = basicLength = output.length;
+
+ // `handledCPCount` is the number of code points that have been handled;
+ // `basicLength` is the number of basic code points.
+
+ // Finish the basic string - if it is not empty - with a delimiter
+ if (basicLength) {
+ output.push(delimiter);
+ }
+
+ // Main encoding loop:
+ while (handledCPCount < inputLength) {
+
+ // All non-basic code points < n have been handled already. Find the next
+ // larger one:
+ for (m = maxInt, j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+ if (currentValue >= n && currentValue < m) {
+ m = currentValue;
+ }
+ }
+
+ // Increase `delta` enough to advance the decoder's state to ,
+ // but guard against overflow
+ handledCPCountPlusOne = handledCPCount + 1;
+ if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
+ error('overflow');
+ }
+
+ delta += (m - n) * handledCPCountPlusOne;
+ n = m;
+
+ for (j = 0; j < inputLength; ++j) {
+ currentValue = input[j];
+
+ if (currentValue < n && ++delta > maxInt) {
+ error('overflow');
+ }
+
+ if (currentValue == n) {
+ // Represent delta as a generalized variable-length integer
+ for (q = delta, k = base; /* no condition */; k += base) {
+ t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias);
+ if (q < t) {
+ break;
+ }
+ qMinusT = q - t;
+ baseMinusT = base - t;
+ output.push(
+ stringFromCharCode(digitToBasic(t + qMinusT % baseMinusT, 0))
+ );
+ q = floor(qMinusT / baseMinusT);
+ }
+
+ output.push(stringFromCharCode(digitToBasic(q, 0)));
+ bias = adapt(delta, handledCPCountPlusOne, handledCPCount == basicLength);
+ delta = 0;
+ ++handledCPCount;
+ }
+ }
+
+ ++delta;
+ ++n;
+
+ }
+ return output.join('');
+ }
+
+ /**
+ * Converts a Punycode string representing a domain name to Unicode. Only the
+ * Punycoded parts of the domain name will be converted, i.e. it doesn't
+ * matter if you call it on a string that has already been converted to
+ * Unicode.
+ * @memberOf punycode
+ * @param {String} domain The Punycode domain name to convert to Unicode.
+ * @returns {String} The Unicode representation of the given Punycode
+ * string.
+ */
+ function toUnicode(domain) {
+ return mapDomain(domain, function(string) {
+ return regexPunycode.test(string)
+ ? decode(string.slice(4).toLowerCase())
+ : string;
+ });
+ }
+
+ /**
+ * Converts a Unicode string representing a domain name to Punycode. Only the
+ * non-ASCII parts of the domain name will be converted, i.e. it doesn't
+ * matter if you call it with a domain that's already in ASCII.
+ * @memberOf punycode
+ * @param {String} domain The domain name to convert, as a Unicode string.
+ * @returns {String} The Punycode representation of the given domain name.
+ */
+ function toASCII(domain) {
+ return mapDomain(domain, function(string) {
+ return regexNonASCII.test(string)
+ ? 'xn--' + encode(string)
+ : string;
+ });
+ }
+
+ /*--------------------------------------------------------------------------*/
+
+ /** Define the public API */
+ punycode = {
+ /**
+ * A string representing the current Punycode.js version number.
+ * @memberOf punycode
+ * @type String
+ */
+ 'version': '1.2.1',
+ /**
+ * An object of methods to convert from JavaScript's internal character
+ * representation (UCS-2) to decimal Unicode code points, and back.
+ * @see
+ * @memberOf punycode
+ * @type Object
+ */
+ 'ucs2': {
+ 'decode': ucs2decode,
+ 'encode': ucs2encode
+ },
+ 'decode': decode,
+ 'encode': encode,
+ 'toASCII': toASCII,
+ 'toUnicode': toUnicode
+ };
+
+ /** Expose `punycode` */
+ // Some AMD build optimizers, like r.js, check for specific condition patterns
+ // like the following:
+ if (
+ typeof define == 'function' &&
+ typeof define.amd == 'object' &&
+ define.amd
+ ) {
+ define(function() {
+ return punycode;
+ });
+ } else if (freeExports && !freeExports.nodeType) {
+ if (freeModule) { // in Node.js or RingoJS v0.8.0+
+ freeModule.exports = punycode;
+ } else { // in Narwhal or RingoJS v0.7.0-
+ for (key in punycode) {
+ punycode.hasOwnProperty(key) && (freeExports[key] = punycode[key]);
+ }
+ }
+ } else { // in Rhino or a web browser
+ root.punycode = punycode;
+ }
+
+}(this));
diff --git a/framework/yii/assets/punycode/punycode.min.js b/framework/yii/assets/punycode/punycode.min.js
new file mode 100644
index 0000000..a61badf
--- /dev/null
+++ b/framework/yii/assets/punycode/punycode.min.js
@@ -0,0 +1,2 @@
+/*! http://mths.be/punycode v1.2.1 by @mathias */
+(function(o){function e(o){throw RangeError(L[o])}function n(o,e){for(var n=o.length;n--;)o[n]=e(o[n]);return o}function t(o,e){return n(o.split(S),e).join(".")}function r(o){for(var e,n,t=[],r=0,u=o.length;u>r;)e=o.charCodeAt(r++),55296==(63488&e)&&u>r?(n=o.charCodeAt(r++),56320==(64512&n)?t.push(((1023&e)<<10)+(1023&n)+65536):t.push(e,n)):t.push(e);return t}function u(o){return n(o,function(o){var e="";return o>65535&&(o-=65536,e+=R(55296|1023&o>>>10),o=56320|1023&o),e+=R(o)}).join("")}function i(o){return 10>o-48?o-22:26>o-65?o-65:26>o-97?o-97:x}function f(o,e){return o+22+75*(26>o)-((0!=e)<<5)}function c(o,e,n){var t=0;for(o=n?P(o/m):o>>1,o+=P(o/e);o>M*y>>1;t+=x)o=P(o/M);return P(t+(M+1)*o/(o+j))}function l(o){var n,t,r,f,l,d,s,a,p,h,v=[],g=o.length,w=0,j=I,m=A;for(t=o.lastIndexOf(F),0>t&&(t=0),r=0;t>r;++r)o.charCodeAt(r)>=128&&e("not-basic"),v.push(o.charCodeAt(r));for(f=t>0?t+1:0;g>f;){for(l=w,d=1,s=x;f>=g&&e("invalid-input"),a=i(o.charCodeAt(f++)),(a>=x||a>P((b-w)/d))&&e("overflow"),w+=a*d,p=m>=s?C:s>=m+y?y:s-m,!(p>a);s+=x)h=x-p,d>P(b/h)&&e("overflow"),d*=h;n=v.length+1,m=c(w-l,n,0==l),P(w/n)>b-j&&e("overflow"),j+=P(w/n),w%=n,v.splice(w++,0,j)}return u(v)}function d(o){var n,t,u,i,l,d,s,a,p,h,v,g,w,j,m,E=[];for(o=r(o),g=o.length,n=I,t=0,l=A,d=0;g>d;++d)v=o[d],128>v&&E.push(R(v));for(u=i=E.length,i&&E.push(F);g>u;){for(s=b,d=0;g>d;++d)v=o[d],v>=n&&s>v&&(s=v);for(w=u+1,s-n>P((b-t)/w)&&e("overflow"),t+=(s-n)*w,n=s,d=0;g>d;++d)if(v=o[d],n>v&&++t>b&&e("overflow"),v==n){for(a=t,p=x;h=l>=p?C:p>=l+y?y:p-l,!(h>a);p+=x)m=a-h,j=x-h,E.push(R(f(h+m%j,0))),a=P(m/j);E.push(R(f(a,0))),l=c(t,w,u==i),t=0,++u}++t,++n}return E.join("")}function s(o){return t(o,function(o){return E.test(o)?l(o.slice(4).toLowerCase()):o})}function a(o){return t(o,function(o){return O.test(o)?"xn--"+d(o):o})}var p="object"==typeof exports&&exports,h="object"==typeof module&&module&&module.exports==p&&module,v="object"==typeof global&&global;(v.global===v||v.window===v)&&(o=v);var g,w,b=2147483647,x=36,C=1,y=26,j=38,m=700,A=72,I=128,F="-",E=/^xn--/,O=/[^ -~]/,S=/\x2E|\u3002|\uFF0E|\uFF61/g,L={overflow:"Overflow: input needs wider integers to process","not-basic":"Illegal input >= 0x80 (not a basic code point)","invalid-input":"Invalid input"},M=x-C,P=Math.floor,R=String.fromCharCode;if(g={version:"1.2.1",ucs2:{decode:r,encode:u},decode:l,encode:d,toASCII:a,toUnicode:s},"function"==typeof define&&"object"==typeof define.amd&&define.amd)define(function(){return g});else if(p&&!p.nodeType)if(h)h.exports=g;else for(w in g)g.hasOwnProperty(w)&&(p[w]=g[w]);else o.punycode=g})(this);
\ No newline at end of file
diff --git a/framework/yii/assets/yii.activeForm.js b/framework/yii/assets/yii.activeForm.js
new file mode 100644
index 0000000..2cb3b90
--- /dev/null
+++ b/framework/yii/assets/yii.activeForm.js
@@ -0,0 +1,401 @@
+/**
+ * Yii form widget.
+ *
+ * This is the JavaScript widget used by the yii\widgets\ActiveForm widget.
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @author Qiang Xue
+ * @since 2.0
+ */
+(function ($) {
+
+ $.fn.yiiActiveForm = function (method) {
+ if (methods[method]) {
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else if (typeof method === 'object' || !method) {
+ return methods.init.apply(this, arguments);
+ } else {
+ $.error('Method ' + method + ' does not exist on jQuery.yiiActiveForm');
+ return false;
+ }
+ };
+
+ var defaults = {
+ // the jQuery selector for the error summary
+ errorSummary: undefined,
+ // whether to perform validation before submitting the form.
+ validateOnSubmit: true,
+ // the container CSS class representing the corresponding attribute has validation error
+ errorCssClass: 'error',
+ // the container CSS class representing the corresponding attribute passes validation
+ successCssClass: 'success',
+ // the container CSS class representing the corresponding attribute is being validated
+ validatingCssClass: 'validating',
+ // the URL for performing AJAX-based validation. If not set, it will use the the form's action
+ validationUrl: undefined,
+ // a callback that is called before submitting the form. The signature of the callback should be:
+ // function ($form) { ...return false to cancel submission...}
+ beforeSubmit: undefined,
+ // a callback that is called before validating each attribute. The signature of the callback should be:
+ // function ($form, attribute, messages) { ...return false to cancel the validation...}
+ beforeValidate: undefined,
+ // a callback that is called after an attribute is validated. The signature of the callback should be:
+ // function ($form, attribute, messages)
+ afterValidate: undefined,
+ // the GET parameter name indicating an AJAX-based validation
+ ajaxVar: 'ajax'
+ };
+
+ var attributeDefaults = {
+ // attribute name or expression (e.g. "[0]content" for tabular input)
+ name: undefined,
+ // the jQuery selector of the container of the input field
+ container: undefined,
+ // the jQuery selector of the input field
+ input: undefined,
+ // the jQuery selector of the error tag
+ error: undefined,
+ // whether to perform validation when a change is detected on the input
+ validateOnChange: false,
+ // whether to perform validation when the user is typing.
+ validateOnType: false,
+ // number of milliseconds that the validation should be delayed when a user is typing in the input field.
+ validationDelay: 200,
+ // whether to enable AJAX-based validation.
+ enableAjaxValidation: false,
+ // function (attribute, value, messages), the client-side validation function.
+ validate: undefined,
+ // status of the input field, 0: empty, not entered before, 1: validated, 2: pending validation, 3: validating
+ status: 0,
+ // the value of the input
+ value: undefined
+ };
+
+ var methods = {
+ init: function (attributes, options) {
+ return this.each(function () {
+ var $form = $(this);
+ if ($form.data('yiiActiveForm')) {
+ return;
+ }
+
+ var settings = $.extend({}, defaults, options || {});
+ if (settings.validationUrl === undefined) {
+ settings.validationUrl = $form.prop('action');
+ }
+ $.each(attributes, function (i) {
+ attributes[i] = $.extend({value: getValue($form, this)}, attributeDefaults, this);
+ });
+ $form.data('yiiActiveForm', {
+ settings: settings,
+ attributes: attributes,
+ submitting: false,
+ validated: false
+ });
+
+ watchAttributes($form, attributes);
+
+ /**
+ * Clean up error status when the form is reset.
+ * Note that $form.on('reset', ...) does work because the "reset" event does not bubble on IE.
+ */
+ $form.bind('reset.yiiActiveForm', methods.resetForm);
+
+ if (settings.validateOnSubmit) {
+ $form.on('mouseup.yiiActiveForm keyup.yiiActiveForm', ':submit', function () {
+ $form.data('yiiActiveForm').submitObject = $(this);
+ });
+ $form.on('submit', methods.submitForm);
+ }
+ });
+ },
+
+ destroy: function () {
+ return this.each(function () {
+ $(window).unbind('.yiiActiveForm');
+ $(this).removeData('yiiActiveForm');
+ });
+ },
+
+ data: function() {
+ return this.data('yiiActiveForm');
+ },
+
+ submitForm: function () {
+ var $form = $(this),
+ data = $form.data('yiiActiveForm');
+ if (data.validated) {
+ // continue submitting the form since validation passes
+ data.validated = false;
+ return true;
+ }
+
+ if (data.settings.timer !== undefined) {
+ clearTimeout(data.settings.timer);
+ }
+ data.submitting = true;
+ if (!data.settings.beforeSubmit || data.settings.beforeSubmit($form)) {
+ validate($form, function (messages) {
+ var errors = [];
+ $.each(data.attributes, function () {
+ if (updateInput($form, this, messages)) {
+ errors.push(this.input);
+ }
+ });
+ updateSummary($form, messages);
+ if (errors.length) {
+ var top = $form.find(errors.join(',')).first().offset().top;
+ var wtop = $(window).scrollTop();
+ if (top < wtop || top > wtop + $(window).height) {
+ $(window).scrollTop(top);
+ }
+ } else {
+ data.validated = true;
+ var $button = data.submitObject || $form.find(':submit:first');
+ // TODO: if the submission is caused by "change" event, it will not work
+ if ($button.length) {
+ $button.click();
+ } else {
+ // no submit button in the form
+ $form.submit();
+ }
+ return;
+ }
+ data.submitting = false;
+ }, function () {
+ data.submitting = false;
+ });
+ } else {
+ data.submitting = false;
+ }
+ return false;
+ },
+
+ resetForm: function () {
+ var $form = $(this);
+ var data = $form.data('yiiActiveForm');
+ // Because we bind directly to a form reset event instead of a reset button (that may not exist),
+ // when this function is executed form input values have not been reset yet.
+ // Therefore we do the actual reset work through setTimeout.
+ setTimeout(function () {
+ $.each(data.attributes, function () {
+ // Without setTimeout() we would get the input values that are not reset yet.
+ this.value = getValue($form, this);
+ this.status = 0;
+ var $container = $form.find(this.container);
+ $container.removeClass(
+ data.settings.validatingCssClass + ' ' +
+ data.settings.errorCssClass + ' ' +
+ data.settings.successCssClass
+ );
+ $container.find(this.error).html('');
+ });
+ $form.find(data.settings.summary).hide().find('ul').html('');
+ }, 1);
+ }
+ };
+
+ var watchAttributes = function ($form, attributes) {
+ $.each(attributes, function (i, attribute) {
+ var $input = findInput($form, attribute);
+ if (attribute.validateOnChange) {
+ $input.on('change.yiiActiveForm', function () {
+ validateAttribute($form, attribute, false);
+ }).on('blur.yiiActiveForm', function () {
+ if (attribute.status == 0 || attribute.status == 1) {
+ validateAttribute($form, attribute, !attribute.status);
+ }
+ });
+ }
+ if (attribute.validateOnType) {
+ $input.on('keyup.yiiActiveForm', function () {
+ if (attribute.value !== getValue($form, attribute)) {
+ validateAttribute($form, attribute, false);
+ }
+ });
+ }
+ });
+ };
+
+ var validateAttribute = function ($form, attribute, forceValidate) {
+ var data = $form.data('yiiActiveForm');
+
+ if (forceValidate) {
+ attribute.status = 2;
+ }
+ $.each(data.attributes, function () {
+ if (this.value !== getValue($form, this)) {
+ this.status = 2;
+ forceValidate = true;
+ }
+ });
+ if (!forceValidate) {
+ return;
+ }
+
+ if (data.settings.timer !== undefined) {
+ clearTimeout(data.settings.timer);
+ }
+ data.settings.timer = setTimeout(function () {
+ if (data.submitting || $form.is(':hidden')) {
+ return;
+ }
+ $.each(data.attributes, function () {
+ if (this.status === 2) {
+ this.status = 3;
+ $form.find(this.container).addClass(data.settings.validatingCssClass);
+ }
+ });
+ validate($form, function (messages) {
+ var hasError = false;
+ $.each(data.attributes, function () {
+ if (this.status === 2 || this.status === 3) {
+ hasError = updateInput($form, this, messages) || hasError;
+ }
+ });
+ });
+ }, data.settings.validationDelay);
+ };
+
+ /**
+ * Performs validation.
+ * @param $form jQuery the jquery representation of the form
+ * @param successCallback function the function to be invoked if the validation completes
+ * @param errorCallback function the function to be invoked if the ajax validation request fails
+ */
+ var validate = function ($form, successCallback, errorCallback) {
+ var data = $form.data('yiiActiveForm'),
+ needAjaxValidation = false,
+ messages = {};
+
+ $.each(data.attributes, function () {
+ if (data.submitting || this.status === 2 || this.status === 3) {
+ var msg = [];
+ if (!data.settings.beforeValidate || data.settings.beforeValidate($form, this, msg)) {
+ if (this.validate) {
+ this.validate(this, getValue($form, this), msg);
+ }
+ if (msg.length) {
+ messages[this.name] = msg;
+ } else if (this.enableAjaxValidation) {
+ needAjaxValidation = true;
+ }
+ }
+ }
+ });
+
+ if (needAjaxValidation && (!data.submitting || $.isEmptyObject(messages))) {
+ // Perform ajax validation when at least one input needs it.
+ // If the validation is triggered by form submission, ajax validation
+ // should be done only when all inputs pass client validation
+ var $button = data.submitObject,
+ extData = '&' + data.settings.ajaxVar + '=' + $form.prop('id');
+ if ($button && $button.length && $button.prop('name')) {
+ extData += '&' + $button.prop('name') + '=' + $button.prop('value');
+ }
+ $.ajax({
+ url: data.settings.validationUrl,
+ type: $form.prop('method'),
+ data: $form.serialize() + extData,
+ dataType: 'json',
+ success: function (msgs) {
+ if (msgs !== null && typeof msgs === 'object') {
+ $.each(data.attributes, function () {
+ if (!this.enableAjaxValidation) {
+ delete msgs[this.name];
+ }
+ });
+ successCallback($.extend({}, messages, msgs));
+ } else {
+ successCallback(messages);
+ }
+ },
+ error: errorCallback
+ });
+ } else if (data.submitting) {
+ // delay callback so that the form can be submitted without problem
+ setTimeout(function () {
+ successCallback(messages);
+ }, 200);
+ } else {
+ successCallback(messages);
+ }
+ };
+
+ /**
+ * Updates the error message and the input container for a particular attribute.
+ * @param $form the form jQuery object
+ * @param attribute object the configuration for a particular attribute.
+ * @param messages array the validation error messages
+ * @return boolean whether there is a validation error for the specified attribute
+ */
+ var updateInput = function ($form, attribute, messages) {
+ var data = $form.data('yiiActiveForm'),
+ $input = findInput($form, attribute),
+ hasError = false;
+
+ if (data.settings.afterValidate) {
+ data.settings.afterValidate($form, attribute, messages);
+ }
+ attribute.status = 1;
+ if ($input.length) {
+ hasError = messages && $.isArray(messages[attribute.name]) && messages[attribute.name].length;
+ var $container = $form.find(attribute.container);
+ var $error = $container.find(attribute.error);
+ if (hasError) {
+ $error.text(messages[attribute.name][0]);
+ $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.successCssClass)
+ .addClass(data.settings.errorCssClass);
+ } else {
+ $error.html('');
+ $container.removeClass(data.settings.validatingCssClass + ' ' + data.settings.errorCssClass + ' ')
+ .addClass(data.settings.successCssClass);
+ }
+ attribute.value = getValue($form, attribute);
+ }
+ return hasError;
+ };
+
+ /**
+ * Updates the error summary.
+ * @param $form the form jQuery object
+ * @param messages array the validation error messages
+ */
+ var updateSummary = function ($form, messages) {
+ var data = $form.data('yiiActiveForm'),
+ $summary = $form.find(data.settings.errorSummary),
+ content = '';
+
+ if ($summary.length && messages) {
+ $.each(data.attributes, function () {
+ if ($.isArray(messages[this.name]) && messages[this.name].length) {
+ content += '' + messages[this.name][0] + ' ';
+ }
+ });
+ $summary.toggle(content !== '').find('ul').html(content);
+ }
+ };
+
+ var getValue = function ($form, attribute) {
+ var $input = findInput($form, attribute);
+ var type = $input.prop('type');
+ if (type === 'checkbox' || type === 'radio') {
+ return $input.filter(':checked').val();
+ } else {
+ return $input.val();
+ }
+ };
+
+ var findInput = function ($form, attribute) {
+ var $input = $form.find(attribute.input);
+ if ($input.length && $input[0].tagName.toLowerCase() === 'div') {
+ // checkbox list or radio list
+ return $input.find('input');
+ } else {
+ return $input;
+ }
+ };
+
+})(window.jQuery);
diff --git a/framework/yii/assets/yii.captcha.js b/framework/yii/assets/yii.captcha.js
new file mode 100644
index 0000000..af14faa
--- /dev/null
+++ b/framework/yii/assets/yii.captcha.js
@@ -0,0 +1,72 @@
+/**
+ * Yii Captcha widget.
+ *
+ * This is the JavaScript widget used by the yii\captcha\Captcha widget.
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @author Qiang Xue
+ * @since 2.0
+ */
+(function ($) {
+ $.fn.yiiCaptcha = function (method) {
+ if (methods[method]) {
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else if (typeof method === 'object' || !method) {
+ return methods.init.apply(this, arguments);
+ } else {
+ $.error('Method ' + method + ' does not exist on jQuery.yiiCaptcha');
+ return false;
+ }
+ };
+
+ var defaults = {
+ refreshUrl: undefined,
+ hashKey: undefined
+ };
+
+ var methods = {
+ init: function (options) {
+ return this.each(function () {
+ var $e = $(this);
+ var settings = $.extend({}, defaults, options || {});
+ $e.data('yiiCaptcha', {
+ settings: settings
+ });
+
+ $e.on('click.yiiCaptcha', function() {
+ methods.refresh.apply($e);
+ return false;
+ });
+
+ });
+ },
+
+ refresh: function () {
+ var $e = this,
+ settings = this.data('yiiCaptcha').settings;
+ $.ajax({
+ url: $e.data('yiiCaptcha').settings.refreshUrl,
+ dataType: 'json',
+ cache: false,
+ success: function(data) {
+ $e.attr('src', data.url);
+ $('body').data(settings.hashKey, [data.hash1, data.hash2]);
+ }
+ });
+ },
+
+ destroy: function () {
+ return this.each(function () {
+ $(window).unbind('.yiiCaptcha');
+ $(this).removeData('yiiCaptcha');
+ });
+ },
+
+ data: function() {
+ return this.data('yiiCaptcha');
+ }
+ };
+})(window.jQuery);
+
diff --git a/framework/yii/assets/yii.gridView.js b/framework/yii/assets/yii.gridView.js
new file mode 100644
index 0000000..a452c17
--- /dev/null
+++ b/framework/yii/assets/yii.gridView.js
@@ -0,0 +1,139 @@
+/**
+ * Yii GridView widget.
+ *
+ * This is the JavaScript widget used by the yii\grid\GridView widget.
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @author Qiang Xue
+ * @since 2.0
+ */
+(function ($) {
+ $.fn.yiiGridView = function (method) {
+ if (methods[method]) {
+ return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else if (typeof method === 'object' || !method) {
+ return methods.init.apply(this, arguments);
+ } else {
+ $.error('Method ' + method + ' does not exist on jQuery.yiiGridView');
+ return false;
+ }
+ };
+
+ var defaults = {
+ filterUrl: undefined,
+ filterSelector: undefined
+ };
+
+ var methods = {
+ init: function (options) {
+ return this.each(function () {
+ var $e = $(this);
+ var settings = $.extend({}, defaults, options || {});
+ $e.data('yiiGridView', {
+ settings: settings
+ });
+
+ var enterPressed = false;
+ $(document).on('change.yiiGridView keydown.yiiGridView', settings.filterSelector, function (event) {
+ if (event.type === 'keydown') {
+ if (event.keyCode !== 13) {
+ return; // only react to enter key
+ } else {
+ enterPressed = true;
+ }
+ } else {
+ // prevent processing for both keydown and change events
+ if (enterPressed) {
+ enterPressed = false;
+ return;
+ }
+ }
+ var data = $(settings.filterSelector).serialize();
+ var url = settings.filterUrl;
+ if (url.indexOf('?') >= 0) {
+ url += '&' + data;
+ } else {
+ url += '?' + data;
+ }
+ window.location.href = url;
+ return false;
+ });
+ });
+ },
+
+ setSelectionColumn: function (options) {
+ var $grid = $(this);
+ var data = $grid.data('yiiGridView');
+ data.selectionColumn = options.name;
+ if (!options.multiple) {
+ return;
+ }
+ $grid.on('click.yiiGridView', "input[name='" + options.checkAll + "']", function () {
+ $grid.find("input[name='" + options.name + "']:enabled").prop('checked', this.checked);
+ });
+ $grid.on('click.yiiGridView', "input[name='" + options.name + "']:enabled", function () {
+ var all = $grid.find("input[name='" + options.name + "']").length == $grid.find("input[name='" + options.name + "']:checked").length;
+ $grid.find("input[name='" + options.checkAll + "']").prop('checked', all);
+ });
+ },
+
+ getSelectedRows: function () {
+ var $grid = $(this);
+ var data = $grid.data('yiiGridView');
+ var keys = [];
+ if (data.selectionColumn) {
+ $grid.find("input[name='" + data.selectionColumn + "']:checked").each(function () {
+ keys.push($(this).parent().closest('tr').data('key'));
+ });
+ }
+ return keys;
+ },
+
+ destroy: function () {
+ return this.each(function () {
+ $(window).unbind('.yiiGridView');
+ $(this).removeData('yiiGridView');
+ });
+ },
+
+ data: function() {
+ return this.data('yiiGridView');
+ }
+ };
+
+ var enterPressed = false;
+
+ var filterChanged = function (event) {
+ if (event.type === 'keydown') {
+ if (event.keyCode !== 13) {
+ return; // only react to enter key
+ } else {
+ enterPressed = true;
+ }
+ } else {
+ // prevent processing for both keydown and change events
+ if (enterPressed) {
+ enterPressed = false;
+ return;
+ }
+ }
+ var data = $(settings.filterSelector).serialize();
+ if (settings.pageVar !== undefined) {
+ data += '&' + settings.pageVar + '=1';
+ }
+ if (settings.enableHistory && settings.ajaxUpdate !== false && window.History.enabled) {
+ // Ajaxify this link
+ var url = $('#' + id).yiiGridView('getUrl'),
+ params = $.deparam.querystring($.param.querystring(url, data));
+
+ delete params[settings.ajaxVar];
+ window.History.pushState(null, document.title, decodeURIComponent($.param.querystring(url.substr(0, url.indexOf('?')), params)));
+ } else {
+ $('#' + id).yiiGridView('update', {data: data});
+ }
+ return false;
+ };
+})(window.jQuery);
+
diff --git a/framework/yii/assets/yii.js b/framework/yii/assets/yii.js
new file mode 100644
index 0000000..add3a02
--- /dev/null
+++ b/framework/yii/assets/yii.js
@@ -0,0 +1,209 @@
+/**
+ * Yii JavaScript module.
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @author Qiang Xue
+ * @since 2.0
+ */
+
+/**
+ * yii is the root module for all Yii JavaScript modules.
+ * It implements a mechanism of organizing JavaScript code in modules through the function "yii.initModule()".
+ *
+ * Each module should be named as "x.y.z", where "x" stands for the root module (for the Yii core code, this is "yii").
+ *
+ * A module may be structured as follows:
+ *
+ * ~~~
+ * yii.sample = (function($) {
+ * var pub = {
+ * // whether this module is currently active. If false, init() will not be called for this module
+ * // it will also not be called for all its child modules. If this property is undefined, it means true.
+ * isActive: true,
+ * init: function() {
+ * // ... module initialization code go here ...
+ * },
+ *
+ * // ... other public functions and properties go here ...
+ * };
+ *
+ * // ... private functions and properties go here ...
+ *
+ * return pub;
+ * })(jQuery);
+ * ~~~
+ *
+ * Using this structure, you can define public and private functions/properties for a module.
+ * Private functions/properties are only visible within the module, while public functions/properties
+ * may be accessed outside of the module. For example, you can access "yii.sample.isActive".
+ *
+ * You must call "yii.initModule()" once for the root module of all your modules.
+ */
+yii = (function ($) {
+ var pub = {
+ /**
+ * The selector for clickable elements that need to support confirmation and form submission.
+ */
+ clickableSelector: 'a, button, input[type="submit"], input[type="button"], input[type="reset"], input[type="image"]',
+ /**
+ * The selector for changeable elements that need to support confirmation and form submission.
+ */
+ changeableSelector: 'select, input, textarea',
+
+ /**
+ * @return string|undefined the CSRF variable name. Undefined is returned is CSRF validation is not enabled.
+ */
+ getCsrfVar: function () {
+ return $('meta[name=csrf-var]').prop('content');
+ },
+
+ /**
+ * @return string|undefined the CSRF token. Undefined is returned is CSRF validation is not enabled.
+ */
+ getCsrfToken: function () {
+ return $('meta[name=csrf-token]').prop('content');
+ },
+
+ /**
+ * Displays a confirmation dialog.
+ * The default implementation simply displays a js confirmation dialog.
+ * You may override this by setting `yii.confirm`.
+ * @param message the confirmation message.
+ * @return boolean whether the user confirms with the message in the dialog
+ */
+ confirm: function (message) {
+ return confirm(message);
+ },
+
+ /**
+ * Returns a value indicating whether to allow executing the action defined for the specified element.
+ * This method recognizes the `data-confirm` attribute of the element and uses it
+ * as the message in a confirmation dialog. The method will return true if this special attribute
+ * is not defined or if the user confirms the message.
+ * @param $e the jQuery representation of the element
+ * @return boolean whether to allow executing the action defined for the specified element.
+ */
+ allowAction: function ($e) {
+ var message = $e.data('confirm');
+ return message === undefined || pub.confirm(message);
+ },
+
+ /**
+ * Handles the action triggered by user.
+ * This method recognizes the `data-method` attribute of the element. If the attribute exists,
+ * the method will submit the form containing this element. If there is no containing form, a form
+ * will be created and submitted using the method given by this attribute value (e.g. "post", "put").
+ * For hyperlinks, the form action will take the value of the "href" attribute of the link.
+ * For other elements, either the containing form action or the current page URL will be used
+ * as the form action URL.
+ *
+ * If the `data-method` attribute is not defined, the default element action will be performed.
+ *
+ * @param $e the jQuery representation of the element
+ * @return boolean whether to execute the default action for the element.
+ */
+ handleAction: function ($e) {
+ var method = $e.data('method');
+ if (method === undefined) {
+ return true;
+ }
+
+ var $form = $e.closest('form');
+ var newForm = !$form.length;
+ if (newForm) {
+ var action = $e.prop('href');
+ if (!action || !action.match(/(^\/|:\/\/)/)) {
+ action = window.location.href;
+ }
+ $form = $('');
+ var target = $e.prop('target');
+ if (target) {
+ $form.attr('target', target);
+ }
+ if (!method.match(/(get|post)/i)) {
+ $form.append(' ');
+ }
+ var csrfVar = pub.getCsrfVar();
+ if (csrfVar) {
+ $form.append(' ');
+ }
+ $form.hide().appendTo('body');
+ }
+
+ var activeFormData = $form.data('yiiActiveForm');
+ if (activeFormData) {
+ // remember who triggers the form submission. This is used by yii.activeForm.js
+ activeFormData.submitObject = $e;
+ }
+
+ $form.trigger('submit');
+
+ if (newForm) {
+ $form.remove();
+ }
+
+ return false;
+ },
+
+ initModule: function (module) {
+ if (module.isActive === undefined || module.isActive) {
+ if ($.isFunction(module.init)) {
+ module.init();
+ }
+ $.each(module, function () {
+ if ($.isPlainObject(this)) {
+ pub.initModule(this);
+ }
+ });
+ }
+ },
+
+ init: function () {
+ var $document = $(document);
+
+ // automatically send CSRF token for all AJAX requests
+ $.ajaxPrefilter(function (options, originalOptions, xhr) {
+ if (!options.crossDomain && pub.getCsrfVar()) {
+ xhr.setRequestHeader('X-CSRF-Token', pub.getCsrfToken());
+ }
+ });
+
+ // handle AJAX redirection
+ $document.ajaxComplete(function (event, xhr, settings) {
+ var url = xhr.getResponseHeader('X-Redirect');
+ if (url) {
+ window.location = url;
+ }
+ });
+
+ // handle data-confirm and data-method for clickable elements
+ $document.on('click.yii', pub.clickableSelector, function (event) {
+ var $this = $(this);
+ if (pub.allowAction($this)) {
+ return pub.handleAction($this);
+ } else {
+ event.stopImmediatePropagation();
+ return false;
+ }
+ });
+
+ // handle data-confirm and data-method for changeable elements
+ $document.on('change.yii', pub.changeableSelector, function (event) {
+ var $this = $(this);
+ if (pub.allowAction($this)) {
+ return pub.handleAction($this);
+ } else {
+ event.stopImmediatePropagation();
+ return false;
+ }
+ });
+ }
+ };
+ return pub;
+})(jQuery);
+
+jQuery(document).ready(function () {
+ yii.initModule(yii);
+});
diff --git a/framework/yii/assets/yii.validation.js b/framework/yii/assets/yii.validation.js
new file mode 100644
index 0000000..3ce9edb
--- /dev/null
+++ b/framework/yii/assets/yii.validation.js
@@ -0,0 +1,227 @@
+/**
+ * Yii validation module.
+ *
+ * This JavaScript module provides the validation methods for the built-in validators.
+ *
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ * @author Qiang Xue
+ * @since 2.0
+ */
+
+yii.validation = (function ($) {
+ var isEmpty = function (value, trim) {
+ return value === null || value === undefined || value == []
+ || value === '' || trim && $.trim(value) === '';
+ };
+
+ var addMessage = function (messages, message, value) {
+ messages.push(message.replace(/\{value\}/g, value));
+ };
+
+ return {
+ required: function (value, messages, options) {
+ var valid = false;
+ if (options.requiredValue === undefined) {
+ if (options.strict && value !== undefined || !options.strict && !isEmpty(value, true)) {
+ valid = true;
+ }
+ } else if (!options.strict && value == options.requiredValue || options.strict && value === options.requiredValue) {
+ valid = true;
+ }
+
+ if (!valid) {
+ addMessage(messages, options.message, value);
+ }
+ },
+
+ boolean: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+ var valid = !options.strict && (value == options.trueValue || value == options.falseValue)
+ || options.strict && (value === options.trueValue || value === options.falseValue);
+
+ if (!valid) {
+ addMessage(messages, options.message, value);
+ }
+ },
+
+ string: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ if (typeof value !== 'string') {
+ addMessage(messages, options.message, value);
+ return;
+ }
+
+ if (options.min !== undefined && value.length < options.min) {
+ addMessage(messages, options.tooShort, value);
+ }
+ if (options.max !== undefined && value.length > options.max) {
+ addMessage(messages, options.tooLong, value);
+ }
+ if (options.is !== undefined && value.length != options.is) {
+ addMessage(messages, options.is, value);
+ }
+ },
+
+ number: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ if (typeof value === 'string' && !value.match(options.pattern)) {
+ addMessage(messages, options.message, value);
+ return;
+ }
+
+ if (options.min !== undefined && value < options.min) {
+ addMessage(messages, options.tooSmall, value);
+ }
+ if (options.max !== undefined && value > options.max) {
+ addMessage(messages, options.tooBig, value);
+ }
+ },
+
+ range: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+ var valid = !options.not && $.inArray(value, options.range) > -1
+ || options.not && $.inArray(value, options.range) == -1;
+
+ if (!valid) {
+ addMessage(messages, options.message, value);
+ }
+ },
+
+ regularExpression: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ if (!options.not && !value.match(options.pattern) || options.not && value.match(options.pattern)) {
+ addMessage(messages, options.message, value);
+ }
+ },
+
+ email: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ var valid = true;
+
+ if (options.enableIDN) {
+ var regexp = /^(.*)@(.*)$/,
+ matches = regexp.exec(value);
+ if (matches === null) {
+ valid = false;
+ } else {
+ value = punycode.toASCII(matches[1]) + '@' + punycode.toASCII(matches[2]);
+ }
+ }
+
+ if (!valid || !(value.match(options.pattern) && (!options.allowName || value.match(options.fullPattern)))) {
+ addMessage(messages, options.message, value);
+ }
+ },
+
+ url: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ if (options.defaultScheme && !value.match(/:\/\//)) {
+ value = options.defaultScheme + '://' + value;
+ }
+
+ var valid = true;
+
+ if (options.enableIDN) {
+ var regexp = /^([^:]+):\/\/([^\/]+)(.*)$/,
+ matches = regexp.exec(value);
+ if (matches === null) {
+ valid = false;
+ } else {
+ value = matches[1] + '://' + punycode.toASCII(matches[2]) + matches[3];
+ }
+ }
+
+ if (!valid || !value.match(options.pattern)) {
+ addMessage(messages, options.message, value);
+ }
+ },
+
+ captcha: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ // CAPTCHA may be updated via AJAX and the updated hash is stored in body data
+ var hash = $('body').data(options.hashKey);
+ if (hash == null) {
+ hash = options.hash;
+ } else {
+ hash = hash[options.caseSensitive ? 0 : 1];
+ }
+ var v = options.caseSensitive ? value : value.toLowerCase();
+ for (var i = v.length - 1, h = 0; i >= 0; --i) {
+ h += v.charCodeAt(i);
+ }
+ if (h != hash) {
+ addMessage(messages, options.message, value);
+ }
+ },
+
+ compare: function (value, messages, options) {
+ if (options.skipOnEmpty && isEmpty(value)) {
+ return;
+ }
+
+ var compareValue, valid = true;
+ if (options.compareAttribute === undefined) {
+ compareValue = options.compareValue;
+ } else {
+ compareValue = $('#' + options.compareAttribute).val();
+ }
+ switch (options.operator) {
+ case '==':
+ valid = value == compareValue;
+ break;
+ case '===':
+ valid = value === compareValue;
+ break;
+ case '!=':
+ valid = value != compareValue;
+ break;
+ case '!==':
+ valid = value !== compareValue;
+ break;
+ case '>':
+ valid = value > compareValue;
+ break;
+ case '>=':
+ valid = value >= compareValue;
+ break;
+ case '<':
+ valid = value < compareValue;
+ break;
+ case '<=':
+ valid = value <= compareValue;
+ break;
+ default:
+ valid = false;
+ break;
+ }
+
+ if (!valid) {
+ addMessage(messages, options.message, value);
+ }
+ }
+ };
+})(jQuery);
diff --git a/framework/yii/base/Action.php b/framework/yii/base/Action.php
new file mode 100644
index 0000000..2693003
--- /dev/null
+++ b/framework/yii/base/Action.php
@@ -0,0 +1,89 @@
+ 1)`.
+ * Then the `run()` method will be invoked as `run(1)` automatically.
+ *
+ * @property string $uniqueId The unique ID of this action among the whole application. This property is
+ * read-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Action extends Component
+{
+ /**
+ * @var string ID of the action
+ */
+ public $id;
+ /**
+ * @var Controller the controller that owns this action
+ */
+ public $controller;
+
+ /**
+ * Constructor.
+ * @param string $id the ID of this action
+ * @param Controller $controller the controller that owns this action
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($id, $controller, $config = array())
+ {
+ $this->id = $id;
+ $this->controller = $controller;
+ parent::__construct($config);
+ }
+
+ /**
+ * Returns the unique ID of this action among the whole application.
+ * @return string the unique ID of this action among the whole application.
+ */
+ public function getUniqueId()
+ {
+ return $this->controller->getUniqueId() . '/' . $this->id;
+ }
+
+ /**
+ * Runs this action with the specified parameters.
+ * This method is mainly invoked by the controller.
+ * @param array $params the parameters to be bound to the action's run() method.
+ * @return mixed the result of the action
+ * @throws InvalidConfigException if the action class does not have a run() method
+ */
+ public function runWithParams($params)
+ {
+ if (!method_exists($this, 'run')) {
+ throw new InvalidConfigException(get_class($this) . ' must define a "run()" method.');
+ }
+ $args = $this->controller->bindActionParams($this, $params);
+ Yii::trace('Running action: ' . get_class($this) . '::run()', __METHOD__);
+ if (Yii::$app->requestedParams === null) {
+ Yii::$app->requestedParams = $args;
+ }
+ return call_user_func_array(array($this, 'run'), $args);
+ }
+}
diff --git a/framework/yii/base/ActionEvent.php b/framework/yii/base/ActionEvent.php
new file mode 100644
index 0000000..9b6c2f0
--- /dev/null
+++ b/framework/yii/base/ActionEvent.php
@@ -0,0 +1,45 @@
+
+ * @since 2.0
+ */
+class ActionEvent extends Event
+{
+ /**
+ * @var Action the action currently being executed
+ */
+ public $action;
+ /**
+ * @var mixed the action result. Event handlers may modify this property to change the action result.
+ */
+ public $result;
+ /**
+ * @var boolean whether to continue running the action. Event handlers of
+ * [[Controller::EVENT_BEFORE_ACTION]] may set this property to decide whether
+ * to continue running the current action.
+ */
+ public $isValid = true;
+
+ /**
+ * Constructor.
+ * @param Action $action the action associated with this action event.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($action, $config = array())
+ {
+ $this->action = $action;
+ parent::__construct($config);
+ }
+}
diff --git a/framework/yii/base/ActionFilter.php b/framework/yii/base/ActionFilter.php
new file mode 100644
index 0000000..bc3b60c
--- /dev/null
+++ b/framework/yii/base/ActionFilter.php
@@ -0,0 +1,94 @@
+
+ * @since 2.0
+ */
+class ActionFilter extends Behavior
+{
+ /**
+ * @var array list of action IDs that this filter should apply to. If this property is not set,
+ * then the filter applies to all actions, unless they are listed in [[except]].
+ * If an action ID appears in both [[only]] and [[except]], this filter will NOT apply to it.
+ * @see except
+ */
+ public $only;
+ /**
+ * @var array list of action IDs that this filter should not apply to.
+ * @see only
+ */
+ public $except = array();
+
+ /**
+ * Declares event handlers for the [[owner]]'s events.
+ * @return array events (array keys) and the corresponding event handler methods (array values).
+ */
+ public function events()
+ {
+ return array(
+ Controller::EVENT_BEFORE_ACTION => 'beforeFilter',
+ Controller::EVENT_AFTER_ACTION => 'afterFilter',
+ );
+ }
+
+ /**
+ * @param ActionEvent $event
+ * @return boolean
+ */
+ public function beforeFilter($event)
+ {
+ if ($this->isActive($event->action)) {
+ $event->isValid = $this->beforeAction($event->action);
+ }
+ return $event->isValid;
+ }
+
+ /**
+ * @param ActionEvent $event
+ * @return boolean
+ */
+ public function afterFilter($event)
+ {
+ if ($this->isActive($event->action)) {
+ $this->afterAction($event->action, $event->result);
+ }
+ }
+
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ return true;
+ }
+
+ /**
+ * This method is invoked right after an action is executed.
+ * You may override this method to do some postprocessing for the action.
+ * @param Action $action the action just executed.
+ * @param mixed $result the action execution result
+ */
+ public function afterAction($action, &$result)
+ {
+ }
+
+ /**
+ * Returns a value indicating whether the filer is active for the given action.
+ * @param Action $action the action being filtered
+ * @return boolean whether the filer is active for the given action.
+ */
+ protected function isActive($action)
+ {
+ return !in_array($action->id, $this->except, true) && (empty($this->only) || in_array($action->id, $this->only, true));
+ }
+}
diff --git a/framework/yii/base/Application.php b/framework/yii/base/Application.php
new file mode 100644
index 0000000..683b9ce
--- /dev/null
+++ b/framework/yii/base/Application.php
@@ -0,0 +1,575 @@
+
+ * @since 2.0
+ */
+abstract class Application extends Module
+{
+ /**
+ * @event Event an event raised before the application starts to handle a request.
+ */
+ const EVENT_BEFORE_REQUEST = 'beforeRequest';
+ /**
+ * @event Event an event raised after the application successfully handles a request (before the response is sent out).
+ */
+ const EVENT_AFTER_REQUEST = 'afterRequest';
+ /**
+ * @event ActionEvent an event raised before executing a controller action.
+ * You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
+ */
+ const EVENT_BEFORE_ACTION = 'beforeAction';
+ /**
+ * @event ActionEvent an event raised after executing a controller action.
+ */
+ const EVENT_AFTER_ACTION = 'afterAction';
+ /**
+ * @var string the application name.
+ */
+ public $name = 'My Application';
+ /**
+ * @var string the version of this application.
+ */
+ public $version = '1.0';
+ /**
+ * @var string the charset currently used for the application.
+ */
+ public $charset = 'UTF-8';
+ /**
+ * @var string the language that is meant to be used for end users.
+ * @see sourceLanguage
+ */
+ public $language = 'en_US';
+ /**
+ * @var string the language that the application is written in. This mainly refers to
+ * the language that the messages and view files are written in.
+ * @see language
+ */
+ public $sourceLanguage = 'en_US';
+ /**
+ * @var array IDs of the components that need to be loaded when the application starts.
+ */
+ public $preload = array();
+ /**
+ * @var Controller the currently active controller instance
+ */
+ public $controller;
+ /**
+ * @var string|boolean the layout that should be applied for views in this application. Defaults to 'main'.
+ * If this is false, layout will be disabled.
+ */
+ public $layout = 'main';
+ /**
+ * @var integer the size of the reserved memory. A portion of memory is pre-allocated so that
+ * when an out-of-memory issue occurs, the error handler is able to handle the error with
+ * the help of this reserved memory. If you set this value to be 0, no memory will be reserved.
+ * Defaults to 256KB.
+ */
+ public $memoryReserveSize = 262144;
+ /**
+ * @var string the requested route
+ */
+ public $requestedRoute;
+ /**
+ * @var Action the requested Action. If null, it means the request cannot be resolved into an action.
+ */
+ public $requestedAction;
+ /**
+ * @var array the parameters supplied to the requested action.
+ */
+ public $requestedParams;
+
+ /**
+ * @var string Used to reserve memory for fatal error handler.
+ */
+ private $_memoryReserve;
+
+ /**
+ * Constructor.
+ * @param array $config name-value pairs that will be used to initialize the object properties.
+ * Note that the configuration must contain both [[id]] and [[basePath]].
+ * @throws InvalidConfigException if either [[id]] or [[basePath]] configuration is missing.
+ */
+ public function __construct($config = array())
+ {
+ Yii::$app = $this;
+ if (!isset($config['id'])) {
+ throw new InvalidConfigException('The "id" configuration is required.');
+ }
+ if (isset($config['basePath'])) {
+ $this->setBasePath($config['basePath']);
+ unset($config['basePath']);
+ } else {
+ throw new InvalidConfigException('The "basePath" configuration is required.');
+ }
+
+ $this->preInit($config);
+
+ $this->registerErrorHandlers();
+ $this->registerCoreComponents();
+
+ Component::__construct($config);
+ }
+
+ /**
+ * Pre-initializes the application.
+ * This method is called at the beginning of the application constructor.
+ * @param array $config the application configuration
+ */
+ public function preInit(&$config)
+ {
+ if (isset($config['vendorPath'])) {
+ $this->setVendorPath($config['vendorPath']);
+ unset($config['vendorPath']);
+ } else {
+ // set "@vendor"
+ $this->getVendorPath();
+ }
+ if (isset($config['runtimePath'])) {
+ $this->setRuntimePath($config['runtimePath']);
+ unset($config['runtimePath']);
+ } else {
+ // set "@runtime"
+ $this->getRuntimePath();
+ }
+ if (isset($config['timeZone'])) {
+ $this->setTimeZone($config['timeZone']);
+ unset($config['timeZone']);
+ } elseif (!ini_get('date.timezone')) {
+ $this->setTimeZone('UTC');
+ }
+ }
+
+ /**
+ * Loads components that are declared in [[preload]].
+ * @throws InvalidConfigException if a component or module to be preloaded is unknown
+ */
+ public function preloadComponents()
+ {
+ $this->getComponent('log');
+ parent::preloadComponents();
+ }
+
+ /**
+ * Registers error handlers.
+ */
+ public function registerErrorHandlers()
+ {
+ if (YII_ENABLE_ERROR_HANDLER) {
+ ini_set('display_errors', 0);
+ set_exception_handler(array($this, 'handleException'));
+ set_error_handler(array($this, 'handleError'), error_reporting());
+ if ($this->memoryReserveSize > 0) {
+ $this->_memoryReserve = str_repeat('x', $this->memoryReserveSize);
+ }
+ register_shutdown_function(array($this, 'handleFatalError'));
+ }
+ }
+
+ /**
+ * Returns an ID that uniquely identifies this module among all modules within the current application.
+ * Since this is an application instance, it will always return an empty string.
+ * @return string the unique ID of the module.
+ */
+ public function getUniqueId()
+ {
+ return '';
+ }
+
+ /**
+ * Runs the application.
+ * This is the main entrance of an application.
+ * @return integer the exit status (0 means normal, non-zero values mean abnormal)
+ */
+ public function run()
+ {
+ $this->trigger(self::EVENT_BEFORE_REQUEST);
+ $response = $this->handleRequest($this->getRequest());
+ $this->trigger(self::EVENT_AFTER_REQUEST);
+ $response->send();
+ return $response->exitStatus;
+ }
+
+ /**
+ * Handles the specified request.
+ *
+ * This method should return an instance of [[Response]] or its child class
+ * which represents the handling result of the request.
+ *
+ * @param Request $request the request to be handled
+ * @return Response the resulting response
+ */
+ abstract public function handleRequest($request);
+
+
+ private $_runtimePath;
+
+ /**
+ * Returns the directory that stores runtime files.
+ * @return string the directory that stores runtime files.
+ * Defaults to the "runtime" subdirectory under [[basePath]].
+ */
+ public function getRuntimePath()
+ {
+ if ($this->_runtimePath === null) {
+ $this->setRuntimePath($this->getBasePath() . DIRECTORY_SEPARATOR . 'runtime');
+ }
+ return $this->_runtimePath;
+ }
+
+ /**
+ * Sets the directory that stores runtime files.
+ * @param string $path the directory that stores runtime files.
+ */
+ public function setRuntimePath($path)
+ {
+ $this->_runtimePath = Yii::getAlias($path);
+ Yii::setAlias('@runtime', $this->_runtimePath);
+ }
+
+ private $_vendorPath;
+
+ /**
+ * Returns the directory that stores vendor files.
+ * @return string the directory that stores vendor files.
+ * Defaults to "vendor" directory under [[basePath]].
+ */
+ public function getVendorPath()
+ {
+ if ($this->_vendorPath === null) {
+ $this->setVendorPath($this->getBasePath() . DIRECTORY_SEPARATOR . 'vendor');
+ }
+ return $this->_vendorPath;
+ }
+
+ /**
+ * Sets the directory that stores vendor files.
+ * @param string $path the directory that stores vendor files.
+ */
+ public function setVendorPath($path)
+ {
+ $this->_vendorPath = Yii::getAlias($path);
+ Yii::setAlias('@vendor', $this->_vendorPath);
+ }
+
+ /**
+ * Returns the time zone used by this application.
+ * This is a simple wrapper of PHP function date_default_timezone_get().
+ * If time zone is not configured in php.ini or application config,
+ * it will be set to UTC by default.
+ * @return string the time zone used by this application.
+ * @see http://php.net/manual/en/function.date-default-timezone-get.php
+ */
+ public function getTimeZone()
+ {
+ return date_default_timezone_get();
+ }
+
+ /**
+ * Sets the time zone used by this application.
+ * This is a simple wrapper of PHP function date_default_timezone_set().
+ * @param string $value the time zone used by this application.
+ * @see http://php.net/manual/en/function.date-default-timezone-set.php
+ */
+ public function setTimeZone($value)
+ {
+ date_default_timezone_set($value);
+ }
+
+ /**
+ * Returns the database connection component.
+ * @return \yii\db\Connection the database connection
+ */
+ public function getDb()
+ {
+ return $this->getComponent('db');
+ }
+
+ /**
+ * Returns the log component.
+ * @return \yii\log\Logger the log component
+ */
+ public function getLog()
+ {
+ return $this->getComponent('log');
+ }
+
+ /**
+ * Returns the error handler component.
+ * @return ErrorHandler the error handler application component.
+ */
+ public function getErrorHandler()
+ {
+ return $this->getComponent('errorHandler');
+ }
+
+ /**
+ * Returns the cache component.
+ * @return \yii\caching\Cache the cache application component. Null if the component is not enabled.
+ */
+ public function getCache()
+ {
+ return $this->getComponent('cache');
+ }
+
+ /**
+ * Returns the formatter component.
+ * @return \yii\base\Formatter the formatter application component.
+ */
+ public function getFormatter()
+ {
+ return $this->getComponent('formatter');
+ }
+
+ /**
+ * Returns the request component.
+ * @return \yii\web\Request|\yii\console\Request the request component
+ */
+ public function getRequest()
+ {
+ return $this->getComponent('request');
+ }
+
+ /**
+ * Returns the view object.
+ * @return View the view object that is used to render various view files.
+ */
+ public function getView()
+ {
+ return $this->getComponent('view');
+ }
+
+ /**
+ * Returns the URL manager for this application.
+ * @return \yii\web\UrlManager the URL manager for this application.
+ */
+ public function getUrlManager()
+ {
+ return $this->getComponent('urlManager');
+ }
+
+ /**
+ * Returns the internationalization (i18n) component
+ * @return \yii\i18n\I18N the internationalization component
+ */
+ public function getI18n()
+ {
+ return $this->getComponent('i18n');
+ }
+
+ /**
+ * Returns the auth manager for this application.
+ * @return \yii\rbac\Manager the auth manager for this application.
+ */
+ public function getAuthManager()
+ {
+ return $this->getComponent('authManager');
+ }
+
+ /**
+ * Registers the core application components.
+ * @see setComponents
+ */
+ public function registerCoreComponents()
+ {
+ $this->setComponents(array(
+ 'log' => array(
+ 'class' => 'yii\log\Logger',
+ ),
+ 'errorHandler' => array(
+ 'class' => 'yii\base\ErrorHandler',
+ ),
+ 'formatter' => array(
+ 'class' => 'yii\base\Formatter',
+ ),
+ 'i18n' => array(
+ 'class' => 'yii\i18n\I18N',
+ ),
+ 'urlManager' => array(
+ 'class' => 'yii\web\UrlManager',
+ ),
+ 'view' => array(
+ 'class' => 'yii\base\View',
+ ),
+ ));
+ }
+
+ /**
+ * Handles uncaught PHP exceptions.
+ *
+ * This method is implemented as a PHP exception handler.
+ *
+ * @param \Exception $exception the exception that is not caught
+ */
+ public function handleException($exception)
+ {
+ // disable error capturing to avoid recursive errors while handling exceptions
+ restore_error_handler();
+ restore_exception_handler();
+ try {
+ $this->logException($exception);
+ if (($handler = $this->getErrorHandler()) !== null) {
+ $handler->handle($exception);
+ } else {
+ echo $this->renderException($exception);
+ }
+ } catch (\Exception $e) {
+ // exception could be thrown in ErrorHandler::handle()
+ $msg = (string)$e;
+ $msg .= "\nPrevious exception:\n";
+ $msg .= (string)$exception;
+ if (YII_DEBUG) {
+ if (PHP_SAPI === 'cli') {
+ echo $msg . "\n";
+ } else {
+ echo '' . htmlspecialchars($msg, ENT_QUOTES, $this->charset) . ' ';
+ }
+ }
+ $msg .= "\n\$_SERVER = " . var_export($_SERVER, true);
+ error_log($msg);
+ exit(1);
+ }
+ }
+
+ /**
+ * Handles PHP execution errors such as warnings, notices.
+ *
+ * This method is used as a PHP error handler. It will simply raise an `ErrorException`.
+ *
+ * @param integer $code the level of the error raised
+ * @param string $message the error message
+ * @param string $file the filename that the error was raised in
+ * @param integer $line the line number the error was raised at
+ *
+ * @throws ErrorException
+ */
+ public function handleError($code, $message, $file, $line)
+ {
+ if (error_reporting() !== 0) {
+ // load ErrorException manually here because autoloading them will not work
+ // when error occurs while autoloading a class
+ if (!class_exists('\\yii\\base\\Exception', false)) {
+ require_once(__DIR__ . '/Exception.php');
+ }
+ if (!class_exists('\\yii\\base\\ErrorException', false)) {
+ require_once(__DIR__ . '/ErrorException.php');
+ }
+ $exception = new ErrorException($message, $code, $code, $file, $line);
+
+ // in case error appeared in __toString method we can't throw any exception
+ $trace = debug_backtrace(false);
+ array_shift($trace);
+ foreach ($trace as $frame) {
+ if ($frame['function'] == '__toString') {
+ $this->handleException($exception);
+ exit(1);
+ }
+ }
+
+ throw $exception;
+ }
+ }
+
+ /**
+ * Handles fatal PHP errors
+ */
+ public function handleFatalError()
+ {
+ unset($this->_memoryReserve);
+
+ // load ErrorException manually here because autoloading them will not work
+ // when error occurs while autoloading a class
+ if (!class_exists('\\yii\\base\\Exception', false)) {
+ require_once(__DIR__ . '/Exception.php');
+ }
+ if (!class_exists('\\yii\\base\\ErrorException', false)) {
+ require_once(__DIR__ . '/ErrorException.php');
+ }
+
+ $error = error_get_last();
+
+ if (ErrorException::isFatalError($error)) {
+ $exception = new ErrorException($error['message'], $error['type'], $error['type'], $error['file'], $error['line']);
+ // use error_log because it's too late to use Yii log
+ error_log($exception);
+
+ if (($handler = $this->getErrorHandler()) !== null) {
+ $handler->handle($exception);
+ } else {
+ echo $this->renderException($exception);
+ }
+
+ exit(1);
+ }
+ }
+
+ /**
+ * Renders an exception without using rich format.
+ * @param \Exception $exception the exception to be rendered.
+ * @return string the rendering result
+ */
+ public function renderException($exception)
+ {
+ if ($exception instanceof Exception && ($exception instanceof UserException || !YII_DEBUG)) {
+ $message = $exception->getName() . ': ' . $exception->getMessage();
+ if (Yii::$app->controller instanceof \yii\console\Controller) {
+ $message = Yii::$app->controller->ansiFormat($message, Console::FG_RED);
+ }
+ } else {
+ $message = YII_DEBUG ? (string)$exception : 'Error: ' . $exception->getMessage();
+ }
+ if (PHP_SAPI === 'cli') {
+ return $message . "\n";
+ } else {
+ return '' . htmlspecialchars($message, ENT_QUOTES, $this->charset) . ' ';
+ }
+ }
+
+ /**
+ * Logs the given exception
+ * @param \Exception $exception the exception to be logged
+ */
+ protected function logException($exception)
+ {
+ $category = get_class($exception);
+ if ($exception instanceof HttpException) {
+ /** @var $exception HttpException */
+ $category .= '\\' . $exception->statusCode;
+ } elseif ($exception instanceof \ErrorException) {
+ /** @var $exception \ErrorException */
+ $category .= '\\' . $exception->getSeverity();
+ }
+ Yii::error((string)$exception, $category);
+ }
+}
diff --git a/framework/yii/base/Arrayable.php b/framework/yii/base/Arrayable.php
new file mode 100644
index 0000000..1822e51
--- /dev/null
+++ b/framework/yii/base/Arrayable.php
@@ -0,0 +1,23 @@
+
+ * @since 2.0
+ */
+interface Arrayable
+{
+ /**
+ * Converts the object into an array.
+ * @return array the array representation of this object
+ */
+ public function toArray();
+}
diff --git a/yii/base/Behavior.php b/framework/yii/base/Behavior.php
similarity index 100%
rename from yii/base/Behavior.php
rename to framework/yii/base/Behavior.php
diff --git a/framework/yii/base/Component.php b/framework/yii/base/Component.php
new file mode 100644
index 0000000..2ad2c94
--- /dev/null
+++ b/framework/yii/base/Component.php
@@ -0,0 +1,579 @@
+
+ * @since 2.0
+ */
+class Component extends Object
+{
+ /**
+ * @var array the attached event handlers (event name => handlers)
+ */
+ private $_events;
+ /**
+ * @var Behavior[] the attached behaviors (behavior name => behavior)
+ */
+ private $_behaviors;
+
+ /**
+ * Returns the value of a component property.
+ * This method will check in the following order and act accordingly:
+ *
+ * - a property defined by a getter: return the getter result
+ * - a property of a behavior: return the behavior property value
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `$value = $component->property;`.
+ * @param string $name the property name
+ * @return mixed the property value or the value of a behavior's property
+ * @throws UnknownPropertyException if the property is not defined
+ * @throws InvalidCallException if the property is write-only.
+ * @see __set
+ */
+ public function __get($name)
+ {
+ $getter = 'get' . $name;
+ if (method_exists($this, $getter)) {
+ // read property, e.g. getName()
+ return $this->$getter();
+ } else {
+ // behavior property
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canGetProperty($name)) {
+ return $behavior->$name;
+ }
+ }
+ }
+ if (method_exists($this, 'set' . $name)) {
+ throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
+ } else {
+ throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
+ }
+ }
+
+ /**
+ * Sets the value of a component property.
+ * This method will check in the following order and act accordingly:
+ *
+ * - a property defined by a setter: set the property value
+ * - an event in the format of "on xyz": attach the handler to the event "xyz"
+ * - a behavior in the format of "as xyz": attach the behavior named as "xyz"
+ * - a property of a behavior: set the behavior property value
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `$component->property = $value;`.
+ * @param string $name the property name or the event name
+ * @param mixed $value the property value
+ * @throws UnknownPropertyException if the property is not defined
+ * @throws InvalidCallException if the property is read-only.
+ * @see __get
+ */
+ public function __set($name, $value)
+ {
+ $setter = 'set' . $name;
+ if (method_exists($this, $setter)) {
+ // set property
+ $this->$setter($value);
+ return;
+ } elseif (strncmp($name, 'on ', 3) === 0) {
+ // on event: attach event handler
+ $this->on(trim(substr($name, 3)), $value);
+ return;
+ } elseif (strncmp($name, 'as ', 3) === 0) {
+ // as behavior: attach behavior
+ $name = trim(substr($name, 3));
+ $this->attachBehavior($name, $value instanceof Behavior ? $value : Yii::createObject($value));
+ return;
+ } else {
+ // behavior property
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canSetProperty($name)) {
+ $behavior->$name = $value;
+ return;
+ }
+ }
+ }
+ if (method_exists($this, 'get' . $name)) {
+ throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
+ } else {
+ throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
+ }
+ }
+
+ /**
+ * Checks if a property value is null.
+ * This method will check in the following order and act accordingly:
+ *
+ * - a property defined by a setter: return whether the property value is null
+ * - a property of a behavior: return whether the property value is null
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `isset($component->property)`.
+ * @param string $name the property name or the event name
+ * @return boolean whether the named property is null
+ */
+ public function __isset($name)
+ {
+ $getter = 'get' . $name;
+ if (method_exists($this, $getter)) {
+ return $this->$getter() !== null;
+ } else {
+ // behavior property
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canGetProperty($name)) {
+ return $behavior->$name !== null;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets a component property to be null.
+ * This method will check in the following order and act accordingly:
+ *
+ * - a property defined by a setter: set the property value to be null
+ * - a property of a behavior: set the property value to be null
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `unset($component->property)`.
+ * @param string $name the property name
+ * @throws InvalidCallException if the property is read only.
+ */
+ public function __unset($name)
+ {
+ $setter = 'set' . $name;
+ if (method_exists($this, $setter)) {
+ $this->$setter(null);
+ return;
+ } else {
+ // behavior property
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canSetProperty($name)) {
+ $behavior->$name = null;
+ return;
+ }
+ }
+ }
+ if (method_exists($this, 'get' . $name)) {
+ throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '.' . $name);
+ }
+ }
+
+ /**
+ * Calls the named method which is not a class method.
+ *
+ * This method will check if any attached behavior has
+ * the named method and will execute it if available.
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when an unknown method is being invoked.
+ * @param string $name the method name
+ * @param array $params method parameters
+ * @return mixed the method return value
+ * @throws UnknownMethodException when calling unknown method
+ */
+ public function __call($name, $params)
+ {
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $object) {
+ if ($object->hasMethod($name)) {
+ return call_user_func_array(array($object, $name), $params);
+ }
+ }
+
+ throw new UnknownMethodException('Calling unknown method: ' . get_class($this) . "::$name()");
+ }
+
+ /**
+ * This method is called after the object is created by cloning an existing one.
+ * It removes all behaviors because they are attached to the old object.
+ */
+ public function __clone()
+ {
+ $this->_events = null;
+ $this->_behaviors = null;
+ }
+
+ /**
+ * Returns a value indicating whether a property is defined for this component.
+ * A property is defined if:
+ *
+ * - the class has a getter or setter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ * - an attached behavior has a property of the given name (when `$checkBehaviors` is true).
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
+ * @return boolean whether the property is defined
+ * @see canGetProperty
+ * @see canSetProperty
+ */
+ public function hasProperty($name, $checkVars = true, $checkBehaviors = true)
+ {
+ return $this->canGetProperty($name, $checkVars, $checkBehaviors) || $this->canSetProperty($name, false, $checkBehaviors);
+ }
+
+ /**
+ * Returns a value indicating whether a property can be read.
+ * A property can be read if:
+ *
+ * - the class has a getter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ * - an attached behavior has a readable property of the given name (when `$checkBehaviors` is true).
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
+ * @return boolean whether the property can be read
+ * @see canSetProperty
+ */
+ public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
+ {
+ if (method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name)) {
+ return true;
+ } elseif ($checkBehaviors) {
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canGetProperty($name, $checkVars)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a value indicating whether a property can be set.
+ * A property can be written if:
+ *
+ * - the class has a setter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ * - an attached behavior has a writable property of the given name (when `$checkBehaviors` is true).
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @param boolean $checkBehaviors whether to treat behaviors' properties as properties of this component
+ * @return boolean whether the property can be written
+ * @see canGetProperty
+ */
+ public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
+ {
+ if (method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name)) {
+ return true;
+ } elseif ($checkBehaviors) {
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->canSetProperty($name, $checkVars)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a value indicating whether a method is defined.
+ * A method is defined if:
+ *
+ * - the class has a method with the specified name
+ * - an attached behavior has a method with the given name (when `$checkBehaviors` is true).
+ *
+ * @param string $name the property name
+ * @param boolean $checkBehaviors whether to treat behaviors' methods as methods of this component
+ * @return boolean whether the property is defined
+ */
+ public function hasMethod($name, $checkBehaviors = true)
+ {
+ if (method_exists($this, $name)) {
+ return true;
+ } elseif ($checkBehaviors) {
+ $this->ensureBehaviors();
+ foreach ($this->_behaviors as $behavior) {
+ if ($behavior->hasMethod($name)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a list of behaviors that this component should behave as.
+ *
+ * Child classes may override this method to specify the behaviors they want to behave as.
+ *
+ * The return value of this method should be an array of behavior objects or configurations
+ * indexed by behavior names. A behavior configuration can be either a string specifying
+ * the behavior class or an array of the following structure:
+ *
+ * ~~~
+ * 'behaviorName' => array(
+ * 'class' => 'BehaviorClass',
+ * 'property1' => 'value1',
+ * 'property2' => 'value2',
+ * )
+ * ~~~
+ *
+ * Note that a behavior class must extend from [[Behavior]]. Behavior names can be strings
+ * or integers. If the former, they uniquely identify the behaviors. If the latter, the corresponding
+ * behaviors are anonymous and their properties and methods will NOT be made available via the component
+ * (however, the behaviors can still respond to the component's events).
+ *
+ * Behaviors declared in this method will be attached to the component automatically (on demand).
+ *
+ * @return array the behavior configurations.
+ */
+ public function behaviors()
+ {
+ return array();
+ }
+
+ /**
+ * Returns a value indicating whether there is any handler attached to the named event.
+ * @param string $name the event name
+ * @return boolean whether there is any handler attached to the event.
+ */
+ public function hasEventHandlers($name)
+ {
+ $this->ensureBehaviors();
+ return !empty($this->_events[$name]);
+ }
+
+ /**
+ * Attaches an event handler to an event.
+ *
+ * An event handler must be a valid PHP callback. The followings are
+ * some examples:
+ *
+ * ~~~
+ * function ($event) { ... } // anonymous function
+ * array($object, 'handleClick') // $object->handleClick()
+ * array('Page', 'handleClick') // Page::handleClick()
+ * 'handleClick' // global function handleClick()
+ * ~~~
+ *
+ * An event handler must be defined with the following signature,
+ *
+ * ~~~
+ * function ($event)
+ * ~~~
+ *
+ * where `$event` is an [[Event]] object which includes parameters associated with the event.
+ *
+ * @param string $name the event name
+ * @param callback $handler the event handler
+ * @param mixed $data the data to be passed to the event handler when the event is triggered.
+ * When the event handler is invoked, this data can be accessed via [[Event::data]].
+ * @see off()
+ */
+ public function on($name, $handler, $data = null)
+ {
+ $this->ensureBehaviors();
+ $this->_events[$name][] = array($handler, $data);
+ }
+
+ /**
+ * Detaches an existing event handler from this component.
+ * This method is the opposite of [[on()]].
+ * @param string $name event name
+ * @param callback $handler the event handler to be removed.
+ * If it is null, all handlers attached to the named event will be removed.
+ * @return boolean if a handler is found and detached
+ * @see on()
+ */
+ public function off($name, $handler = null)
+ {
+ $this->ensureBehaviors();
+ if (isset($this->_events[$name])) {
+ if ($handler === null) {
+ $this->_events[$name] = array();
+ } else {
+ $removed = false;
+ foreach ($this->_events[$name] as $i => $event) {
+ if ($event[0] === $handler) {
+ unset($this->_events[$name][$i]);
+ $removed = true;
+ }
+ }
+ if ($removed) {
+ $this->_events[$name] = array_values($this->_events[$name]);
+ }
+ return $removed;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Triggers an event.
+ * This method represents the happening of an event. It invokes
+ * all attached handlers for the event.
+ * @param string $name the event name
+ * @param Event $event the event parameter. If not set, a default [[Event]] object will be created.
+ */
+ public function trigger($name, $event = null)
+ {
+ $this->ensureBehaviors();
+ if (!empty($this->_events[$name])) {
+ if ($event === null) {
+ $event = new Event;
+ }
+ if ($event->sender === null) {
+ $event->sender = $this;
+ }
+ $event->handled = false;
+ $event->name = $name;
+ foreach ($this->_events[$name] as $handler) {
+ $event->data = $handler[1];
+ call_user_func($handler[0], $event);
+ // stop further handling if the event is handled
+ if ($event instanceof Event && $event->handled) {
+ return;
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the named behavior object.
+ * @param string $name the behavior name
+ * @return Behavior the behavior object, or null if the behavior does not exist
+ */
+ public function getBehavior($name)
+ {
+ $this->ensureBehaviors();
+ return isset($this->_behaviors[$name]) ? $this->_behaviors[$name] : null;
+ }
+
+ /**
+ * Returns all behaviors attached to this component.
+ * @return Behavior[] list of behaviors attached to this component
+ */
+ public function getBehaviors()
+ {
+ $this->ensureBehaviors();
+ return $this->_behaviors;
+ }
+
+ /**
+ * Attaches a behavior to this component.
+ * This method will create the behavior object based on the given
+ * configuration. After that, the behavior object will be attached to
+ * this component by calling the [[Behavior::attach()]] method.
+ * @param string $name the name of the behavior.
+ * @param string|array|Behavior $behavior the behavior configuration. This can be one of the following:
+ *
+ * - a [[Behavior]] object
+ * - a string specifying the behavior class
+ * - an object configuration array that will be passed to [[Yii::createObject()]] to create the behavior object.
+ *
+ * @return Behavior the behavior object
+ * @see detachBehavior
+ */
+ public function attachBehavior($name, $behavior)
+ {
+ $this->ensureBehaviors();
+ return $this->attachBehaviorInternal($name, $behavior);
+ }
+
+ /**
+ * Attaches a list of behaviors to the component.
+ * Each behavior is indexed by its name and should be a [[Behavior]] object,
+ * a string specifying the behavior class, or an configuration array for creating the behavior.
+ * @param array $behaviors list of behaviors to be attached to the component
+ * @see attachBehavior
+ */
+ public function attachBehaviors($behaviors)
+ {
+ $this->ensureBehaviors();
+ foreach ($behaviors as $name => $behavior) {
+ $this->attachBehaviorInternal($name, $behavior);
+ }
+ }
+
+ /**
+ * Detaches a behavior from the component.
+ * The behavior's [[Behavior::detach()]] method will be invoked.
+ * @param string $name the behavior's name.
+ * @return Behavior the detached behavior. Null if the behavior does not exist.
+ */
+ public function detachBehavior($name)
+ {
+ $this->ensureBehaviors();
+ if (isset($this->_behaviors[$name])) {
+ $behavior = $this->_behaviors[$name];
+ unset($this->_behaviors[$name]);
+ $behavior->detach();
+ return $behavior;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Detaches all behaviors from the component.
+ */
+ public function detachBehaviors()
+ {
+ $this->ensureBehaviors();
+ if ($this->_behaviors !== null) {
+ foreach ($this->_behaviors as $name => $behavior) {
+ $this->detachBehavior($name);
+ }
+ }
+ $this->_behaviors = array();
+ }
+
+ /**
+ * Makes sure that the behaviors declared in [[behaviors()]] are attached to this component.
+ */
+ public function ensureBehaviors()
+ {
+ if ($this->_behaviors === null) {
+ $this->_behaviors = array();
+ foreach ($this->behaviors() as $name => $behavior) {
+ $this->attachBehaviorInternal($name, $behavior);
+ }
+ }
+ }
+
+ /**
+ * Attaches a behavior to this component.
+ * @param string $name the name of the behavior.
+ * @param string|array|Behavior $behavior the behavior to be attached
+ * @return Behavior the attached behavior.
+ */
+ private function attachBehaviorInternal($name, $behavior)
+ {
+ if (!($behavior instanceof Behavior)) {
+ $behavior = Yii::createObject($behavior);
+ }
+ if (isset($this->_behaviors[$name])) {
+ $this->_behaviors[$name]->detach();
+ }
+ $behavior->attach($this);
+ return $this->_behaviors[$name] = $behavior;
+ }
+}
diff --git a/framework/yii/base/Controller.php b/framework/yii/base/Controller.php
new file mode 100644
index 0000000..3eebaa0
--- /dev/null
+++ b/framework/yii/base/Controller.php
@@ -0,0 +1,440 @@
+
+ * @since 2.0
+ */
+class Controller extends Component
+{
+ /**
+ * @event ActionEvent an event raised right before executing a controller action.
+ * You may set [[ActionEvent::isValid]] to be false to cancel the action execution.
+ */
+ const EVENT_BEFORE_ACTION = 'beforeAction';
+ /**
+ * @event ActionEvent an event raised right after executing a controller action.
+ */
+ const EVENT_AFTER_ACTION = 'afterAction';
+ /**
+ * @var string the ID of this controller
+ */
+ public $id;
+ /**
+ * @var Module $module the module that this controller belongs to.
+ */
+ public $module;
+ /**
+ * @var string the ID of the action that is used when the action ID is not specified
+ * in the request. Defaults to 'index'.
+ */
+ public $defaultAction = 'index';
+ /**
+ * @var string|boolean the name of the layout to be applied to this controller's views.
+ * This property mainly affects the behavior of [[render()]].
+ * Defaults to null, meaning the actual layout value should inherit that from [[module]]'s layout value.
+ * If false, no layout will be applied.
+ */
+ public $layout;
+ /**
+ * @var Action the action that is currently being executed. This property will be set
+ * by [[run()]] when it is called by [[Application]] to run an action.
+ */
+ public $action;
+ /**
+ * @var View the view object that can be used to render views or view files.
+ */
+ private $_view;
+
+
+ /**
+ * @param string $id the ID of this controller
+ * @param Module $module the module that this controller belongs to.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($id, $module, $config = array())
+ {
+ $this->id = $id;
+ $this->module = $module;
+ parent::__construct($config);
+ }
+
+ /**
+ * Declares external actions for the controller.
+ * This method is meant to be overwritten to declare external actions for the controller.
+ * It should return an array, with array keys being action IDs, and array values the corresponding
+ * action class names or action configuration arrays. For example,
+ *
+ * ~~~
+ * return array(
+ * 'action1' => '@app/components/Action1',
+ * 'action2' => array(
+ * 'class' => '@app/components/Action2',
+ * 'property1' => 'value1',
+ * 'property2' => 'value2',
+ * ),
+ * );
+ * ~~~
+ *
+ * [[\Yii::createObject()]] will be used later to create the requested action
+ * using the configuration provided here.
+ */
+ public function actions()
+ {
+ return array();
+ }
+
+ /**
+ * Runs an action within this controller with the specified action ID and parameters.
+ * If the action ID is empty, the method will use [[defaultAction]].
+ * @param string $id the ID of the action to be executed.
+ * @param array $params the parameters (name-value pairs) to be passed to the action.
+ * @return mixed the result of the action
+ * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
+ * @see createAction
+ */
+ public function runAction($id, $params = array())
+ {
+ $action = $this->createAction($id);
+ if ($action !== null) {
+ Yii::trace("Route to run: " . $action->getUniqueId(), __METHOD__);
+ if (Yii::$app->requestedAction === null) {
+ Yii::$app->requestedAction = $action;
+ }
+ $oldAction = $this->action;
+ $this->action = $action;
+ $result = null;
+ $event = new ActionEvent($action);
+ Yii::$app->trigger(Application::EVENT_BEFORE_ACTION, $event);
+ if ($event->isValid && $this->module->beforeAction($action) && $this->beforeAction($action)) {
+ $result = $action->runWithParams($params);
+ $this->afterAction($action, $result);
+ $this->module->afterAction($action, $result);
+ $event = new ActionEvent($action);
+ $event->result = &$result;
+ Yii::$app->trigger(Application::EVENT_AFTER_ACTION, $event);
+ }
+ $this->action = $oldAction;
+ return $result;
+ } else {
+ throw new InvalidRouteException('Unable to resolve the request: ' . $this->getUniqueId() . '/' . $id);
+ }
+ }
+
+ /**
+ * Runs a request specified in terms of a route.
+ * The route can be either an ID of an action within this controller or a complete route consisting
+ * of module IDs, controller ID and action ID. If the route starts with a slash '/', the parsing of
+ * the route will start from the application; otherwise, it will start from the parent module of this controller.
+ * @param string $route the route to be handled, e.g., 'view', 'comment/view', '/admin/comment/view'.
+ * @param array $params the parameters to be passed to the action.
+ * @return mixed the result of the action
+ * @see runAction
+ * @see forward
+ */
+ public function run($route, $params = array())
+ {
+ $pos = strpos($route, '/');
+ if ($pos === false) {
+ return $this->runAction($route, $params);
+ } elseif ($pos > 0) {
+ return $this->module->runAction($route, $params);
+ } else {
+ return Yii::$app->runAction(ltrim($route, '/'), $params);
+ }
+ }
+
+ /**
+ * Binds the parameters to the action.
+ * This method is invoked by [[Action]] when it begins to run with the given parameters.
+ * @param Action $action the action to be bound with parameters
+ * @param array $params the parameters to be bound to the action
+ * @return array the valid parameters that the action can run with.
+ */
+ public function bindActionParams($action, $params)
+ {
+ return array();
+ }
+
+ /**
+ * Creates an action based on the given action ID.
+ * The method first checks if the action ID has been declared in [[actions()]]. If so,
+ * it will use the configuration declared there to create the action object.
+ * If not, it will look for a controller method whose name is in the format of `actionXyz`
+ * where `Xyz` stands for the action ID. If found, an [[InlineAction]] representing that
+ * method will be created and returned.
+ * @param string $id the action ID
+ * @return Action the newly created action instance. Null if the ID doesn't resolve into any action.
+ */
+ public function createAction($id)
+ {
+ if ($id === '') {
+ $id = $this->defaultAction;
+ }
+
+ $actionMap = $this->actions();
+ if (isset($actionMap[$id])) {
+ return Yii::createObject($actionMap[$id], $id, $this);
+ } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
+ $methodName = 'action' . str_replace(' ', '', ucwords(implode(' ', explode('-', $id))));
+ if (method_exists($this, $methodName)) {
+ $method = new \ReflectionMethod($this, $methodName);
+ if ($method->getName() === $methodName) {
+ return new InlineAction($id, $this, $methodName);
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * If you override this method, please make sure you call the parent implementation first.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ $event = new ActionEvent($action);
+ $this->trigger(self::EVENT_BEFORE_ACTION, $event);
+ return $event->isValid;
+ }
+
+ /**
+ * This method is invoked right after an action is executed.
+ * You may override this method to do some postprocessing for the action.
+ * If you override this method, please make sure you call the parent implementation first.
+ * @param Action $action the action just executed.
+ * @param mixed $result the action return result.
+ */
+ public function afterAction($action, &$result)
+ {
+ $event = new ActionEvent($action);
+ $event->result = & $result;
+ $this->trigger(self::EVENT_AFTER_ACTION, $event);
+ }
+
+ /**
+ * Returns the request parameters that will be used for action parameter binding.
+ * Default implementation simply returns an empty array.
+ * Child classes may override this method to customize the parameters to be provided
+ * for action parameter binding (e.g. `$_GET`).
+ * @return array the request parameters (name-value pairs) to be used for action parameter binding
+ */
+ public function getActionParams()
+ {
+ return array();
+ }
+
+ /**
+ * @return string the controller ID that is prefixed with the module ID (if any).
+ */
+ public function getUniqueId()
+ {
+ return $this->module instanceof Application ? $this->id : $this->module->getUniqueId() . '/' . $this->id;
+ }
+
+ /**
+ * Returns the route of the current request.
+ * @return string the route (module ID, controller ID and action ID) of the current request.
+ */
+ public function getRoute()
+ {
+ return $this->action !== null ? $this->action->getUniqueId() : $this->getUniqueId();
+ }
+
+ /**
+ * Renders a view and applies layout if available.
+ *
+ * The view to be rendered can be specified in one of the following formats:
+ *
+ * - path alias (e.g. "@app/views/site/index");
+ * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
+ * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
+ * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
+ * The actual view file will be looked for under the [[Module::viewPath|view path]] of [[module]].
+ * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
+ *
+ * To determine which layout should be applied, the following two steps are conducted:
+ *
+ * 1. In the first step, it determines the layout name and the context module:
+ *
+ * - If [[layout]] is specified as a string, use it as the layout name and [[module]] as the context module;
+ * - If [[layout]] is null, search through all ancestor modules of this controller and find the first
+ * module whose [[Module::layout|layout]] is not null. The layout and the corresponding module
+ * are used as the layout name and the context module, respectively. If such a module is not found
+ * or the corresponding layout is not a string, it will return false, meaning no applicable layout.
+ *
+ * 2. In the second step, it determines the actual layout file according to the previously found layout name
+ * and context module. The layout name can be
+ *
+ * - a path alias (e.g. "@app/views/layouts/main");
+ * - an absolute path (e.g. "/main"): the layout name starts with a slash. The actual layout file will be
+ * looked for under the [[Application::layoutPath|layout path]] of the application;
+ * - a relative path (e.g. "main"): the actual layout layout file will be looked for under the
+ * [[Module::layoutPath|layout path]] of the context module.
+ *
+ * If the layout name does not contain a file extension, it will use the default one `.php`.
+ *
+ * @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * These parameters will not be available in the layout.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file or the layout file does not exist.
+ */
+ public function render($view, $params = array())
+ {
+ $viewFile = $this->findViewFile($view);
+ $output = $this->getView()->renderFile($viewFile, $params, $this);
+ $layoutFile = $this->findLayoutFile();
+ if ($layoutFile !== false) {
+ return $this->getView()->renderFile($layoutFile, array('content' => $output), $this);
+ } else {
+ return $output;
+ }
+ }
+
+ /**
+ * Renders a view.
+ * This method differs from [[render()]] in that it does not apply any layout.
+ * @param string $view the view name. Please refer to [[render()]] on how to specify a view name.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file does not exist.
+ */
+ public function renderPartial($view, $params = array())
+ {
+ $viewFile = $this->findViewFile($view);
+ return $this->getView()->renderFile($viewFile, $params, $this);
+ }
+
+ /**
+ * Renders a view file.
+ * @param string $file the view file to be rendered. This can be either a file path or a path alias.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file does not exist.
+ */
+ public function renderFile($file, $params = array())
+ {
+ return $this->getView()->renderFile($file, $params, $this);
+ }
+
+ /**
+ * Returns the view object that can be used to render views or view files.
+ * The [[render()]], [[renderPartial()]] and [[renderFile()]] methods will use
+ * this view object to implement the actual view rendering.
+ * If not set, it will default to the "view" application component.
+ * @return View the view object that can be used to render views or view files.
+ */
+ public function getView()
+ {
+ if ($this->_view === null) {
+ $this->_view = Yii::$app->getView();
+ }
+ return $this->_view;
+ }
+
+ /**
+ * Sets the view object to be used by this controller.
+ * @param View $view the view object that can be used to render views or view files.
+ */
+ public function setView($view)
+ {
+ $this->_view = $view;
+ }
+
+ /**
+ * Returns the directory containing view files for this controller.
+ * The default implementation returns the directory named as controller [[id]] under the [[module]]'s
+ * [[viewPath]] directory.
+ * @return string the directory containing the view files for this controller.
+ */
+ public function getViewPath()
+ {
+ return $this->module->getViewPath() . DIRECTORY_SEPARATOR . $this->id;
+ }
+
+ /**
+ * Finds the view file based on the given view name.
+ * @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
+ * on how to specify this parameter.
+ * @return string the view file path. Note that the file may not exist.
+ */
+ protected function findViewFile($view)
+ {
+ if (strncmp($view, '@', 1) === 0) {
+ // e.g. "@app/views/main"
+ $file = Yii::getAlias($view);
+ } elseif (strncmp($view, '//', 2) === 0) {
+ // e.g. "//layouts/main"
+ $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } elseif (strncmp($view, '/', 1) === 0) {
+ // e.g. "/site/index"
+ $file = $this->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } else {
+ $file = $this->getViewPath() . DIRECTORY_SEPARATOR . $view;
+ }
+
+ return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file;
+ }
+
+ /**
+ * Finds the applicable layout file.
+ * @return string|boolean the layout file path, or false if layout is not needed.
+ * Please refer to [[render()]] on how to specify this parameter.
+ * @throws InvalidParamException if an invalid path alias is used to specify the layout
+ */
+ protected function findLayoutFile()
+ {
+ $module = $this->module;
+ if (is_string($this->layout)) {
+ $view = $this->layout;
+ } elseif ($this->layout === null) {
+ while ($module !== null && $module->layout === null) {
+ $module = $module->module;
+ }
+ if ($module !== null && is_string($module->layout)) {
+ $view = $module->layout;
+ }
+ }
+
+ if (!isset($view)) {
+ return false;
+ }
+
+ if (strncmp($view, '@', 1) === 0) {
+ $file = Yii::getAlias($view);
+ } elseif (strncmp($view, '/', 1) === 0) {
+ $file = Yii::$app->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
+ } else {
+ $file = $module->getLayoutPath() . DIRECTORY_SEPARATOR . $view;
+ }
+
+ if (pathinfo($file, PATHINFO_EXTENSION) === '') {
+ $file .= '.php';
+ }
+ return $file;
+ }
+}
diff --git a/framework/yii/base/ErrorException.php b/framework/yii/base/ErrorException.php
new file mode 100644
index 0000000..8e1977a
--- /dev/null
+++ b/framework/yii/base/ErrorException.php
@@ -0,0 +1,109 @@
+
+ * @since 2.0
+ */
+class ErrorException extends Exception
+{
+ protected $severity;
+
+ /**
+ * Constructs the exception
+ * @link http://php.net/manual/en/errorexception.construct.php
+ * @param $message [optional]
+ * @param $code [optional]
+ * @param $severity [optional]
+ * @param $filename [optional]
+ * @param $lineno [optional]
+ * @param $previous [optional]
+ */
+ public function __construct($message = '', $code = 0, $severity = 1, $filename = __FILE__, $lineno = __LINE__, \Exception $previous = null)
+ {
+ parent::__construct($message, $code, $previous);
+ $this->severity = $severity;
+ $this->file = $filename;
+ $this->line = $lineno;
+
+ if (function_exists('xdebug_get_function_stack')) {
+ $trace = array_slice(array_reverse(xdebug_get_function_stack()), 3, -1);
+ foreach ($trace as &$frame) {
+ if (!isset($frame['function'])) {
+ $frame['function'] = 'unknown';
+ }
+
+ // XDebug < 2.1.1: http://bugs.xdebug.org/view.php?id=695
+ if (!isset($frame['type']) || $frame['type'] === 'static') {
+ $frame['type'] = '::';
+ } elseif ($frame['type'] === 'dynamic') {
+ $frame['type'] = '->';
+ }
+
+ // XDebug has a different key name
+ $frame['args'] = array();
+ if (isset($frame['params']) && !isset($frame['args'])) {
+ $frame['args'] = $frame['params'];
+ }
+ }
+
+ $ref = new \ReflectionProperty('Exception', 'trace');
+ $ref->setAccessible(true);
+ $ref->setValue($this, $trace);
+ }
+ }
+
+ /**
+ * Gets the exception severity
+ * @link http://php.net/manual/en/errorexception.getseverity.php
+ * @return int the severity level of the exception.
+ */
+ final public function getSeverity()
+ {
+ return $this->severity;
+ }
+
+ /**
+ * Returns if error is one of fatal type
+ *
+ * @param array $error error got from error_get_last()
+ * @return bool if error is one of fatal type
+ */
+ public static function isFatalError($error)
+ {
+ return isset($error['type']) && in_array($error['type'], array(E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, E_COMPILE_WARNING));
+ }
+
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ $names = array(
+ E_ERROR => Yii::t('yii', 'Fatal Error'),
+ E_PARSE => Yii::t('yii', 'Parse Error'),
+ E_CORE_ERROR => Yii::t('yii', 'Core Error'),
+ E_COMPILE_ERROR => Yii::t('yii', 'Compile Error'),
+ E_USER_ERROR => Yii::t('yii', 'User Error'),
+ E_WARNING => Yii::t('yii', 'Warning'),
+ E_CORE_WARNING => Yii::t('yii', 'Core Warning'),
+ E_COMPILE_WARNING => Yii::t('yii', 'Compile Warning'),
+ E_USER_WARNING => Yii::t('yii', 'User Warning'),
+ E_STRICT => Yii::t('yii', 'Strict'),
+ E_NOTICE => Yii::t('yii', 'Notice'),
+ E_RECOVERABLE_ERROR => Yii::t('yii', 'Recoverable Error'),
+ E_DEPRECATED => Yii::t('yii', 'Deprecated'),
+ );
+ return isset($names[$this->getCode()]) ? $names[$this->getCode()] : Yii::t('yii', 'Error');
+ }
+}
diff --git a/framework/yii/base/ErrorHandler.php b/framework/yii/base/ErrorHandler.php
new file mode 100644
index 0000000..dec5779
--- /dev/null
+++ b/framework/yii/base/ErrorHandler.php
@@ -0,0 +1,332 @@
+
+ * @author Timur Ruziev
+ * @since 2.0
+ */
+class ErrorHandler extends Component
+{
+ /**
+ * @var integer maximum number of source code lines to be displayed. Defaults to 25.
+ */
+ public $maxSourceLines = 25;
+ /**
+ * @var integer maximum number of trace source code lines to be displayed. Defaults to 10.
+ */
+ public $maxTraceSourceLines = 10;
+ /**
+ * @var boolean whether to discard any existing page output before error display. Defaults to true.
+ */
+ public $discardExistingOutput = true;
+ /**
+ * @var string the route (e.g. 'site/error') to the controller action that will be used
+ * to display external errors. Inside the action, it can retrieve the error information
+ * by Yii::$app->errorHandler->exception. This property defaults to null, meaning ErrorHandler
+ * will handle the error display.
+ */
+ public $errorAction;
+ /**
+ * @var string the path of the view file for rendering exceptions without call stack information.
+ */
+ public $errorView = '@yii/views/errorHandler/error.php';
+ /**
+ * @var string the path of the view file for rendering exceptions.
+ */
+ public $exceptionView = '@yii/views/errorHandler/exception.php';
+ /**
+ * @var string the path of the view file for rendering exceptions and errors call stack element.
+ */
+ public $callStackItemView = '@yii/views/errorHandler/callStackItem.php';
+ /**
+ * @var string the path of the view file for rendering previous exceptions.
+ */
+ public $previousExceptionView = '@yii/views/errorHandler/previousException.php';
+ /**
+ * @var \Exception the exception that is being handled currently.
+ */
+ public $exception;
+
+
+ /**
+ * Handles exception.
+ * @param \Exception $exception to be handled.
+ */
+ public function handle($exception)
+ {
+ $this->exception = $exception;
+ if ($this->discardExistingOutput) {
+ $this->clearOutput();
+ }
+ $this->renderException($exception);
+ }
+
+ /**
+ * Renders the exception.
+ * @param \Exception $exception the exception to be handled.
+ */
+ protected function renderException($exception)
+ {
+ if (Yii::$app instanceof \yii\console\Application || YII_ENV_TEST) {
+ echo Yii::$app->renderException($exception);
+ return;
+ }
+
+ $useErrorView = !YII_DEBUG || $exception instanceof UserException;
+
+ $response = Yii::$app->getResponse();
+ $response->getHeaders()->removeAll();
+
+ if ($useErrorView && $this->errorAction !== null) {
+ // disable CSRF validation so that errorAction can run in case the error is caused by CSRF validation failure
+ Yii::$app->getRequest()->enableCsrfValidation = false;
+ $result = Yii::$app->runAction($this->errorAction);
+ if ($result instanceof Response) {
+ $response = $result;
+ } else {
+ $response->data = $result;
+ }
+ } elseif ($response->format === \yii\web\Response::FORMAT_HTML) {
+ if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] === 'XMLHttpRequest') {
+ // AJAX request
+ $response->data = Yii::$app->renderException($exception);
+ } else {
+ // if there is an error during error rendering it's useful to
+ // display PHP error in debug mode instead of a blank screen
+ if (YII_DEBUG) {
+ ini_set('display_errors', 1);
+ }
+ $file = $useErrorView ? $this->errorView : $this->exceptionView;
+ $response->data = $this->renderFile($file, array(
+ 'exception' => $exception,
+ ));
+ }
+ } elseif ($exception instanceof Arrayable) {
+ $response->data = $exception;
+ } else {
+ $response->data = array(
+ 'type' => get_class($exception),
+ 'name' => 'Exception',
+ 'message' => $exception->getMessage(),
+ 'code' => $exception->getCode(),
+ );
+ }
+
+ if ($exception instanceof HttpException) {
+ $response->setStatusCode($exception->statusCode);
+ } else {
+ $response->setStatusCode(500);
+ }
+
+ $response->send();
+ }
+
+ /**
+ * Converts special characters to HTML entities.
+ * @param string $text to encode.
+ * @return string encoded original text.
+ */
+ public function htmlEncode($text)
+ {
+ return htmlspecialchars($text, ENT_QUOTES, Yii::$app->charset);
+ }
+
+ /**
+ * Removes all output echoed before calling this method.
+ */
+ public function clearOutput()
+ {
+ // the following manual level counting is to deal with zlib.output_compression set to On
+ for ($level = ob_get_level(); $level > 0; --$level) {
+ if (!@ob_end_clean()) {
+ ob_clean();
+ }
+ }
+ }
+
+ /**
+ * Adds informational links to the given PHP type/class.
+ * @param string $code type/class name to be linkified.
+ * @return string linkified with HTML type/class name.
+ */
+ public function addTypeLinks($code)
+ {
+ $html = '';
+ if (strpos($code, '\\') !== false) {
+ // namespaced class
+ foreach (explode('\\', $code) as $part) {
+ $html .= '' . $this->htmlEncode($part) . ' \\';
+ }
+ $html = rtrim($html, '\\');
+ } elseif (strpos($code, '()') !== false) {
+ // method/function call
+ $html = preg_replace_callback('/^(.*)\(\)$/', function ($matches) {
+ return '' .
+ $this->htmlEncode($matches[1]) . ' ()';
+ }, $code);
+ }
+ return $html;
+ }
+
+ /**
+ * Renders a view file as a PHP script.
+ * @param string $_file_ the view file.
+ * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
+ * @return string the rendering result
+ */
+ public function renderFile($_file_, $_params_)
+ {
+ $_params_['handler'] = $this;
+ if ($this->exception instanceof ErrorException) {
+ ob_start();
+ ob_implicit_flush(false);
+ extract($_params_, EXTR_OVERWRITE);
+ require(Yii::getAlias($_file_));
+ return ob_get_clean();
+ } else {
+ return Yii::$app->getView()->renderFile($_file_, $_params_, $this);
+ }
+ }
+
+ /**
+ * Renders the previous exception stack for a given Exception.
+ * @param \Exception $exception the exception whose precursors should be rendered.
+ * @return string HTML content of the rendered previous exceptions.
+ * Empty string if there are none.
+ */
+ public function renderPreviousExceptions($exception)
+ {
+ if (($previous = $exception->getPrevious()) !== null) {
+ return $this->renderFile($this->previousExceptionView, array(
+ 'exception' => $previous,
+ ));
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Renders a single call stack element.
+ * @param string|null $file name where call has happened.
+ * @param integer|null $line number on which call has happened.
+ * @param string|null $class called class name.
+ * @param string|null $method called function/method name.
+ * @param integer $index number of the call stack element.
+ * @return string HTML content of the rendered call stack element.
+ */
+ public function renderCallStackItem($file, $line, $class, $method, $index)
+ {
+ $lines = array();
+ $begin = $end = 0;
+ if ($file !== null && $line !== null) {
+ $line--; // adjust line number from one-based to zero-based
+ $lines = @file($file);
+ if ($line < 0 || $lines === false || ($lineCount = count($lines)) < $line + 1) {
+ return '';
+ }
+
+ $half = (int)(($index == 0 ? $this->maxSourceLines : $this->maxTraceSourceLines) / 2);
+ $begin = $line - $half > 0 ? $line - $half : 0;
+ $end = $line + $half < $lineCount ? $line + $half : $lineCount - 1;
+ }
+
+ return $this->renderFile($this->callStackItemView, array(
+ 'file' => $file,
+ 'line' => $line,
+ 'class' => $class,
+ 'method' => $method,
+ 'index' => $index,
+ 'lines' => $lines,
+ 'begin' => $begin,
+ 'end' => $end,
+ ));
+ }
+
+ /**
+ * Renders the request information.
+ * @return string the rendering result
+ */
+ public function renderRequest()
+ {
+ $request = '';
+ foreach (array('_GET', '_POST', '_SERVER', '_FILES', '_COOKIE', '_SESSION', '_ENV') as $name) {
+ if (!empty($GLOBALS[$name])) {
+ $request .= '$' . $name . ' = ' . var_export($GLOBALS[$name], true) . ";\n\n";
+ }
+ }
+ return '' . rtrim($request, "\n") . ' ';
+ }
+
+ /**
+ * Determines whether given name of the file belongs to the framework.
+ * @param string $file name to be checked.
+ * @return boolean whether given name of the file belongs to the framework.
+ */
+ public function isCoreFile($file)
+ {
+ return $file === null || strpos(realpath($file), YII_PATH . DIRECTORY_SEPARATOR) === 0;
+ }
+
+ /**
+ * Creates HTML containing link to the page with the information on given HTTP status code.
+ * @param integer $statusCode to be used to generate information link.
+ * @param string $statusDescription Description to display after the the status code.
+ * @return string generated HTML with HTTP status code information.
+ */
+ public function createHttpStatusLink($statusCode, $statusDescription)
+ {
+ return 'HTTP ' . (int)$statusCode . ' – ' . $statusDescription . ' ';
+ }
+
+ /**
+ * Creates string containing HTML link which refers to the home page of determined web-server software
+ * and its full name.
+ * @return string server software information hyperlink.
+ */
+ public function createServerInformationLink()
+ {
+ static $serverUrls = array(
+ 'http://httpd.apache.org/' => array('apache'),
+ 'http://nginx.org/' => array('nginx'),
+ 'http://lighttpd.net/' => array('lighttpd'),
+ 'http://gwan.com/' => array('g-wan', 'gwan'),
+ 'http://iis.net/' => array('iis', 'services'),
+ 'http://php.net/manual/en/features.commandline.webserver.php' => array('development'),
+ );
+ if (isset($_SERVER['SERVER_SOFTWARE'])) {
+ foreach ($serverUrls as $url => $keywords) {
+ foreach ($keywords as $keyword) {
+ if (stripos($_SERVER['SERVER_SOFTWARE'], $keyword) !== false) {
+ return '' . $this->htmlEncode($_SERVER['SERVER_SOFTWARE']) . ' ';
+ }
+ }
+ }
+ }
+ return '';
+ }
+
+ /**
+ * Creates string containing HTML link which refers to the page with the current version
+ * of the framework and version number text.
+ * @return string framework version information hyperlink.
+ */
+ public function createFrameworkVersionLink()
+ {
+ return '' . $this->htmlEncode(Yii::getVersion()) . ' ';
+ }
+}
diff --git a/yii/base/Event.php b/framework/yii/base/Event.php
similarity index 100%
rename from yii/base/Event.php
rename to framework/yii/base/Event.php
diff --git a/framework/yii/base/Exception.php b/framework/yii/base/Exception.php
new file mode 100644
index 0000000..64f1d1b
--- /dev/null
+++ b/framework/yii/base/Exception.php
@@ -0,0 +1,53 @@
+
+ * @since 2.0
+ */
+class Exception extends \Exception implements Arrayable
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Exception');
+ }
+
+ /**
+ * Returns the array representation of this object.
+ * @return array the array representation of this object.
+ */
+ public function toArray()
+ {
+ return $this->toArrayRecursive($this);
+ }
+
+ /**
+ * Returns the array representation of the exception and all previous exceptions recursively.
+ * @param \Exception exception object
+ * @return array the array representation of the exception.
+ */
+ protected function toArrayRecursive($exception)
+ {
+ $array = array(
+ 'type' => get_class($exception),
+ 'name' => $exception instanceof self ? $exception->getName() : 'Exception',
+ 'message' => $exception->getMessage(),
+ 'code' => $exception->getCode(),
+ );
+ if (($prev = $exception->getPrevious()) !== null) {
+ $array['previous'] = $this->toArrayRecursive($prev);
+ }
+ return $array;
+ }
+}
diff --git a/framework/yii/base/Formatter.php b/framework/yii/base/Formatter.php
new file mode 100644
index 0000000..84b4b8d
--- /dev/null
+++ b/framework/yii/base/Formatter.php
@@ -0,0 +1,381 @@
+
+ * @since 2.0
+ */
+class Formatter extends Component
+{
+ /**
+ * @var string the default format string to be used to format a date using PHP date() function.
+ */
+ public $dateFormat = 'Y/m/d';
+ /**
+ * @var string the default format string to be used to format a time using PHP date() function.
+ */
+ public $timeFormat = 'h:i:s A';
+ /**
+ * @var string the default format string to be used to format a date and time using PHP date() function.
+ */
+ public $datetimeFormat = 'Y/m/d h:i:s A';
+ /**
+ * @var string the text to be displayed when formatting a null. Defaults to '(not set)'.
+ */
+ public $nullDisplay;
+ /**
+ * @var array the text to be displayed when formatting a boolean value. The first element corresponds
+ * to the text display for false, the second element for true. Defaults to `array('No', 'Yes')`.
+ */
+ public $booleanFormat;
+ /**
+ * @var string the character displayed as the decimal point when formatting a number.
+ * If not set, "." will be used.
+ */
+ public $decimalSeparator;
+ /**
+ * @var string the character displayed as the thousands separator character when formatting a number.
+ * If not set, "," will be used.
+ */
+ public $thousandSeparator;
+
+
+ /**
+ * Initializes the component.
+ */
+ public function init()
+ {
+ if (empty($this->booleanFormat)) {
+ $this->booleanFormat = array(Yii::t('yii', 'No'), Yii::t('yii', 'Yes'));
+ }
+ if ($this->nullDisplay === null) {
+ $this->nullDisplay = Yii::t('yii', '(not set)');
+ }
+ }
+
+ /**
+ * Formats the value based on the given format type.
+ * This method will call one of the "as" methods available in this class to do the formatting.
+ * For type "xyz", the method "asXyz" will be used. For example, if the format is "html",
+ * then [[asHtml()]] will be used. Format names are case insensitive.
+ * @param mixed $value the value to be formatted
+ * @param string|array $format the format of the value, e.g., "html", "text". To specify additional
+ * parameters of the formatting method, you may use an array. The first element of the array
+ * specifies the format name, while the rest of the elements will be used as the parameters to the formatting
+ * method. For example, a format of `array('date', 'Y-m-d')` will cause the invocation of `asDate($value, 'Y-m-d')`.
+ * @return string the formatting result
+ * @throws InvalidParamException if the type is not supported by this class.
+ */
+ public function format($value, $format)
+ {
+ if (is_array($format)) {
+ if (!isset($format[0])) {
+ throw new InvalidParamException('The $format array must contain at least one element.');
+ }
+ $f = $format[0];
+ $format[0] = $value;
+ $params = $format;
+ $format = $f;
+ } else {
+ $params = array($value);
+ }
+ $method = 'as' . $format;
+ if (method_exists($this, $method)) {
+ return call_user_func_array(array($this, $method), $params);
+ } else {
+ throw new InvalidParamException("Unknown type: $format");
+ }
+ }
+
+ /**
+ * Formats the value as is without any formatting.
+ * This method simply returns back the parameter without any format.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asRaw($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ return $value;
+ }
+
+ /**
+ * Formats the value as an HTML-encoded plain text.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asText($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ return Html::encode($value);
+ }
+
+ /**
+ * Formats the value as an HTML-encoded plain text with newlines converted into breaks.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asNtext($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ return nl2br(Html::encode($value));
+ }
+
+ /**
+ * Formats the value as HTML-encoded text paragraphs.
+ * Each text paragraph is enclosed within a `` tag.
+ * One or multiple consecutive empty lines divide two paragraphs.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asParagraphs($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ return str_replace('
', '',
+ '' . preg_replace('/[\r\n]{2,}/', "
\n", Html::encode($value)) . '
'
+ );
+ }
+
+ /**
+ * Formats the value as HTML text.
+ * The value will be purified using [[HtmlPurifier]] to avoid XSS attacks.
+ * Use [[asRaw()]] if you do not want any purification of the value.
+ * @param mixed $value the value to be formatted
+ * @param array|null $config the configuration for the HTMLPurifier class.
+ * @return string the formatted result
+ */
+ public function asHtml($value, $config = null)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ return HtmlPurifier::process($value, $config);
+ }
+
+ /**
+ * Formats the value as a mailto link.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asEmail($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ return Html::mailto($value);
+ }
+
+ /**
+ * Formats the value as an image tag.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asImage($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ return Html::img($value);
+ }
+
+ /**
+ * Formats the value as a hyperlink.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ */
+ public function asUrl($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $url = $value;
+ if (strpos($url, 'http://') !== 0 && strpos($url, 'https://') !== 0) {
+ $url = 'http://' . $url;
+ }
+ return Html::a(Html::encode($value), $url);
+ }
+
+ /**
+ * Formats the value as a boolean.
+ * @param mixed $value the value to be formatted
+ * @return string the formatted result
+ * @see booleanFormat
+ */
+ public function asBoolean($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ return $value ? $this->booleanFormat[1] : $this->booleanFormat[0];
+ }
+
+ /**
+ * Formats the value as a date.
+ * @param integer|string|DateTime $value the value to be formatted. The following
+ * types of value are supported:
+ *
+ * - an integer representing a UNIX timestamp
+ * - a string that can be parsed into a UNIX timestamp via `strtotime()`
+ * - a PHP DateTime object
+ *
+ * @param string $format the format used to convert the value into a date string.
+ * If null, [[dateFormat]] will be used. The format string should be one
+ * that can be recognized by the PHP `date()` function.
+ * @return string the formatted result
+ * @see dateFormat
+ */
+ public function asDate($value, $format = null)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $value = $this->normalizeDatetimeValue($value);
+ return date($format === null ? $this->dateFormat : $format, $value);
+ }
+
+ /**
+ * Formats the value as a time.
+ * @param integer|string|DateTime $value the value to be formatted. The following
+ * types of value are supported:
+ *
+ * - an integer representing a UNIX timestamp
+ * - a string that can be parsed into a UNIX timestamp via `strtotime()`
+ * - a PHP DateTime object
+ *
+ * @param string $format the format used to convert the value into a date string.
+ * If null, [[timeFormat]] will be used. The format string should be one
+ * that can be recognized by the PHP `date()` function.
+ * @return string the formatted result
+ * @see timeFormat
+ */
+ public function asTime($value, $format = null)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $value = $this->normalizeDatetimeValue($value);
+ return date($format === null ? $this->timeFormat : $format, $value);
+ }
+
+ /**
+ * Formats the value as a datetime.
+ * @param integer|string|DateTime $value the value to be formatted. The following
+ * types of value are supported:
+ *
+ * - an integer representing a UNIX timestamp
+ * - a string that can be parsed into a UNIX timestamp via `strtotime()`
+ * - a PHP DateTime object
+ *
+ * @param string $format the format used to convert the value into a date string.
+ * If null, [[datetimeFormat]] will be used. The format string should be one
+ * that can be recognized by the PHP `date()` function.
+ * @return string the formatted result
+ * @see datetimeFormat
+ */
+ public function asDatetime($value, $format = null)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $value = $this->normalizeDatetimeValue($value);
+ return date($format === null ? $this->datetimeFormat : $format, $value);
+ }
+
+ /**
+ * Normalizes the given datetime value as one that can be taken by various date/time formatting methods.
+ * @param mixed $value the datetime value to be normalized.
+ * @return integer the normalized datetime value
+ */
+ protected function normalizeDatetimeValue($value)
+ {
+ if (is_string($value)) {
+ return is_numeric($value) || $value === '' ? (int)$value : strtotime($value);
+ } elseif ($value instanceof DateTime) {
+ return $value->getTimestamp();
+ } else {
+ return (int)$value;
+ }
+ }
+
+ /**
+ * Formats the value as an integer.
+ * @param mixed $value the value to be formatted
+ * @return string the formatting result.
+ */
+ public function asInteger($value)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ if (is_string($value) && preg_match('/^(-?\d+)/', $value, $matches)) {
+ return $matches[1];
+ } else {
+ $value = (int)$value;
+ return "$value";
+ }
+ }
+
+ /**
+ * Formats the value as a double number.
+ * Property [[decimalSeparator]] will be used to represent the decimal point.
+ * @param mixed $value the value to be formatted
+ * @param integer $decimals the number of digits after the decimal point
+ * @return string the formatting result.
+ * @see decimalSeparator
+ */
+ public function asDouble($value, $decimals = 2)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ if ($this->decimalSeparator === null) {
+ return sprintf("%.{$decimals}f", $value);
+ } else {
+ return str_replace('.', $this->decimalSeparator, sprintf("%.{$decimals}f", $value));
+ }
+ }
+
+ /**
+ * Formats the value as a number with decimal and thousand separators.
+ * This method calls the PHP number_format() function to do the formatting.
+ * @param mixed $value the value to be formatted
+ * @param integer $decimals the number of digits after the decimal point
+ * @return string the formatted result
+ * @see decimalSeparator
+ * @see thousandSeparator
+ */
+ public function asNumber($value, $decimals = 0)
+ {
+ if ($value === null) {
+ return $this->nullDisplay;
+ }
+ $ds = isset($this->decimalSeparator) ? $this->decimalSeparator: '.';
+ $ts = isset($this->thousandSeparator) ? $this->thousandSeparator: ',';
+ return number_format($value, $decimals, $ds, $ts);
+ }
+}
diff --git a/framework/yii/base/InlineAction.php b/framework/yii/base/InlineAction.php
new file mode 100644
index 0000000..a669563
--- /dev/null
+++ b/framework/yii/base/InlineAction.php
@@ -0,0 +1,55 @@
+
+ * @since 2.0
+ */
+class InlineAction extends Action
+{
+ /**
+ * @var string the controller method that this inline action is associated with
+ */
+ public $actionMethod;
+
+ /**
+ * @param string $id the ID of this action
+ * @param Controller $controller the controller that owns this action
+ * @param string $actionMethod the controller method that this inline action is associated with
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($id, $controller, $actionMethod, $config = array())
+ {
+ $this->actionMethod = $actionMethod;
+ parent::__construct($id, $controller, $config);
+ }
+
+ /**
+ * Runs this action with the specified parameters.
+ * This method is mainly invoked by the controller.
+ * @param array $params action parameters
+ * @return mixed the result of the action
+ */
+ public function runWithParams($params)
+ {
+ $args = $this->controller->bindActionParams($this, $params);
+ Yii::trace('Running action: ' . get_class($this->controller) . '::' . $this->actionMethod . '()', __METHOD__);
+ if (Yii::$app->requestedParams === null) {
+ Yii::$app->requestedParams = $args;
+ }
+ return call_user_func_array(array($this->controller, $this->actionMethod), $args);
+ }
+}
diff --git a/framework/yii/base/InvalidCallException.php b/framework/yii/base/InvalidCallException.php
new file mode 100644
index 0000000..73cb4b9
--- /dev/null
+++ b/framework/yii/base/InvalidCallException.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class InvalidCallException extends Exception
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Invalid Call');
+ }
+}
diff --git a/framework/yii/base/InvalidConfigException.php b/framework/yii/base/InvalidConfigException.php
new file mode 100644
index 0000000..0a6b4c5
--- /dev/null
+++ b/framework/yii/base/InvalidConfigException.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class InvalidConfigException extends Exception
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Invalid Configuration');
+ }
+}
diff --git a/framework/yii/base/InvalidParamException.php b/framework/yii/base/InvalidParamException.php
new file mode 100644
index 0000000..44430a8
--- /dev/null
+++ b/framework/yii/base/InvalidParamException.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class InvalidParamException extends Exception
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Invalid Parameter');
+ }
+}
diff --git a/framework/yii/base/InvalidRouteException.php b/framework/yii/base/InvalidRouteException.php
new file mode 100644
index 0000000..fbcc087
--- /dev/null
+++ b/framework/yii/base/InvalidRouteException.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class InvalidRouteException extends UserException
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Invalid Route');
+ }
+}
diff --git a/framework/yii/base/Model.php b/framework/yii/base/Model.php
new file mode 100644
index 0000000..7fb8c35
--- /dev/null
+++ b/framework/yii/base/Model.php
@@ -0,0 +1,832 @@
+ value).
+ * @property array $errors An array of errors for all attributes. Empty array is returned if no error. The
+ * result is a two-dimensional array. See [[getErrors()]] for detailed description. This property is read-only.
+ * @property array $firstErrors The first errors. An empty array will be returned if there is no error. This
+ * property is read-only.
+ * @property ArrayIterator $iterator An iterator for traversing the items in the list. This property is
+ * read-only.
+ * @property string $scenario The scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]].
+ * @property ArrayObject $validators All the validators declared in the model. This property is read-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Model extends Component implements IteratorAggregate, ArrayAccess
+{
+ /**
+ * The name of the default scenario.
+ */
+ const DEFAULT_SCENARIO = 'default';
+
+ /**
+ * @event ModelEvent an event raised at the beginning of [[validate()]]. You may set
+ * [[ModelEvent::isValid]] to be false to stop the validation.
+ */
+ const EVENT_BEFORE_VALIDATE = 'beforeValidate';
+ /**
+ * @event Event an event raised at the end of [[validate()]]
+ */
+ const EVENT_AFTER_VALIDATE = 'afterValidate';
+
+ /**
+ * @var array validation errors (attribute name => array of errors)
+ */
+ private $_errors;
+ /**
+ * @var ArrayObject list of validators
+ */
+ private $_validators;
+ /**
+ * @var string current scenario
+ */
+ private $_scenario = self::DEFAULT_SCENARIO;
+
+ /**
+ * Returns the validation rules for attributes.
+ *
+ * Validation rules are used by [[validate()]] to check if attribute values are valid.
+ * Child classes may override this method to declare different validation rules.
+ *
+ * Each rule is an array with the following structure:
+ *
+ * ~~~
+ * array(
+ * 'attribute list',
+ * 'validator type',
+ * 'on' => 'scenario name',
+ * ...other parameters...
+ * )
+ * ~~~
+ *
+ * where
+ *
+ * - attribute list: required, specifies the attributes (separated by commas) to be validated;
+ * - validator type: required, specifies the validator to be used. It can be the name of a model
+ * class method, the name of a built-in validator, or a validator class name (or its path alias).
+ * - on: optional, specifies the [[scenario|scenarios]] (separated by commas) when the validation
+ * rule can be applied. If this option is not set, the rule will apply to all scenarios.
+ * - additional name-value pairs can be specified to initialize the corresponding validator properties.
+ * Please refer to individual validator class API for possible properties.
+ *
+ * A validator can be either an object of a class extending [[Validator]], or a model class method
+ * (called *inline validator*) that has the following signature:
+ *
+ * ~~~
+ * // $params refers to validation parameters given in the rule
+ * function validatorName($attribute, $params)
+ * ~~~
+ *
+ * In the above `$attribute` refers to currently validated attribute name while `$params` contains an array of
+ * validator configuration options such as `max` in case of `string` validator. Currently validate attribute value
+ * can be accessed as `$this->[$attribute]`.
+ *
+ * Yii also provides a set of [[Validator::builtInValidators|built-in validators]].
+ * They each has an alias name which can be used when specifying a validation rule.
+ *
+ * Below are some examples:
+ *
+ * ~~~
+ * array(
+ * // built-in "required" validator
+ * array('username', 'required'),
+ * // built-in "string" validator customized with "min" and "max" properties
+ * array('username', 'string', 'min' => 3, 'max' => 12),
+ * // built-in "compare" validator that is used in "register" scenario only
+ * array('password', 'compare', 'compareAttribute' => 'password2', 'on' => 'register'),
+ * // an inline validator defined via the "authenticate()" method in the model class
+ * array('password', 'authenticate', 'on' => 'login'),
+ * // a validator of class "DateRangeValidator"
+ * array('dateRange', 'DateRangeValidator'),
+ * );
+ * ~~~
+ *
+ * Note, in order to inherit rules defined in the parent class, a child class needs to
+ * merge the parent rules with child rules using functions such as `array_merge()`.
+ *
+ * @return array validation rules
+ * @see scenarios
+ */
+ public function rules()
+ {
+ return array();
+ }
+
+ /**
+ * Returns a list of scenarios and the corresponding active attributes.
+ * An active attribute is one that is subject to validation in the current scenario.
+ * The returned array should be in the following format:
+ *
+ * ~~~
+ * array(
+ * 'scenario1' => array('attribute11', 'attribute12', ...),
+ * 'scenario2' => array('attribute21', 'attribute22', ...),
+ * ...
+ * )
+ * ~~~
+ *
+ * By default, an active attribute that is considered safe and can be massively assigned.
+ * If an attribute should NOT be massively assigned (thus considered unsafe),
+ * please prefix the attribute with an exclamation character (e.g. '!rank').
+ *
+ * The default implementation of this method will return all scenarios found in the [[rules()]]
+ * declaration. A special scenario named [[DEFAULT_SCENARIO]] will contain all attributes
+ * found in the [[rules()]]. Each scenario will be associated with the attributes that
+ * are being validated by the validation rules that apply to the scenario.
+ *
+ * @return array a list of scenarios and the corresponding active attributes.
+ */
+ public function scenarios()
+ {
+ $scenarios = array();
+ $defaults = array();
+ /** @var $validator Validator */
+ foreach ($this->getValidators() as $validator) {
+ if (empty($validator->on)) {
+ foreach ($validator->attributes as $attribute) {
+ $defaults[$attribute] = true;
+ }
+ } else {
+ foreach ($validator->on as $scenario) {
+ foreach ($validator->attributes as $attribute) {
+ $scenarios[$scenario][$attribute] = true;
+ }
+ }
+ }
+ }
+ foreach ($scenarios as $scenario => $attributes) {
+ foreach (array_keys($defaults) as $attribute) {
+ $attributes[$attribute] = true;
+ }
+ $scenarios[$scenario] = array_keys($attributes);
+ }
+ $scenarios[self::DEFAULT_SCENARIO] = array_keys($defaults);
+ return $scenarios;
+ }
+
+ /**
+ * Returns the form name that this model class should use.
+ *
+ * The form name is mainly used by [[\yii\web\ActiveForm]] to determine how to name
+ * the input fields for the attributes in a model. If the form name is "A" and an attribute
+ * name is "b", then the corresponding input name would be "A[b]". If the form name is
+ * an empty string, then the input name would be "b".
+ *
+ * By default, this method returns the model class name (without the namespace part)
+ * as the form name. You may override it when the model is used in different forms.
+ *
+ * @return string the form name of this model class.
+ */
+ public function formName()
+ {
+ $reflector = new ReflectionClass($this);
+ return $reflector->getShortName();
+ }
+
+ /**
+ * Returns the list of attribute names.
+ * By default, this method returns all public non-static properties of the class.
+ * You may override this method to change the default behavior.
+ * @return array list of attribute names.
+ */
+ public function attributes()
+ {
+ $class = new ReflectionClass($this);
+ $names = array();
+ foreach ($class->getProperties(\ReflectionProperty::IS_PUBLIC) as $property) {
+ $name = $property->getName();
+ if (!$property->isStatic()) {
+ $names[] = $name;
+ }
+ }
+ return $names;
+ }
+
+ /**
+ * Returns the attribute labels.
+ *
+ * Attribute labels are mainly used for display purpose. For example, given an attribute
+ * `firstName`, we can declare a label `First Name` which is more user-friendly and can
+ * be displayed to end users.
+ *
+ * By default an attribute label is generated using [[generateAttributeLabel()]].
+ * This method allows you to explicitly specify attribute labels.
+ *
+ * Note, in order to inherit labels defined in the parent class, a child class needs to
+ * merge the parent labels with child labels using functions such as `array_merge()`.
+ *
+ * @return array attribute labels (name => label)
+ * @see generateAttributeLabel
+ */
+ public function attributeLabels()
+ {
+ return array();
+ }
+
+ /**
+ * Performs the data validation.
+ *
+ * This method executes the validation rules applicable to the current [[scenario]].
+ * The following criteria are used to determine whether a rule is currently applicable:
+ *
+ * - the rule must be associated with the attributes relevant to the current scenario;
+ * - the rules must be effective for the current scenario.
+ *
+ * This method will call [[beforeValidate()]] and [[afterValidate()]] before and
+ * after the actual validation, respectively. If [[beforeValidate()]] returns false,
+ * the validation will be cancelled and [[afterValidate()]] will not be called.
+ *
+ * Errors found during the validation can be retrieved via [[getErrors()]],
+ * [[getFirstErrors()]] and [[getFirstError()]].
+ *
+ * @param array $attributes list of attributes that should be validated.
+ * If this parameter is empty, it means any attribute listed in the applicable
+ * validation rules should be validated.
+ * @param boolean $clearErrors whether to call [[clearErrors()]] before performing validation
+ * @return boolean whether the validation is successful without any error.
+ * @throws InvalidParamException if the current scenario is unknown.
+ */
+ public function validate($attributes = null, $clearErrors = true)
+ {
+ $scenarios = $this->scenarios();
+ $scenario = $this->getScenario();
+ if (!isset($scenarios[$scenario])) {
+ throw new InvalidParamException("Unknown scenario: $scenario");
+ }
+
+ if ($clearErrors) {
+ $this->clearErrors();
+ }
+ if ($attributes === null) {
+ $attributes = $this->activeAttributes();
+ }
+ if ($this->beforeValidate()) {
+ foreach ($this->getActiveValidators() as $validator) {
+ $validator->validate($this, $attributes);
+ }
+ $this->afterValidate();
+ return !$this->hasErrors();
+ }
+ return false;
+ }
+
+ /**
+ * This method is invoked before validation starts.
+ * The default implementation raises a `beforeValidate` event.
+ * You may override this method to do preliminary checks before validation.
+ * Make sure the parent implementation is invoked so that the event can be raised.
+ * @return boolean whether the validation should be executed. Defaults to true.
+ * If false is returned, the validation will stop and the model is considered invalid.
+ */
+ public function beforeValidate()
+ {
+ $event = new ModelEvent;
+ $this->trigger(self::EVENT_BEFORE_VALIDATE, $event);
+ return $event->isValid;
+ }
+
+ /**
+ * This method is invoked after validation ends.
+ * The default implementation raises an `afterValidate` event.
+ * You may override this method to do postprocessing after validation.
+ * Make sure the parent implementation is invoked so that the event can be raised.
+ */
+ public function afterValidate()
+ {
+ $this->trigger(self::EVENT_AFTER_VALIDATE);
+ }
+
+ /**
+ * Returns all the validators declared in [[rules()]].
+ *
+ * This method differs from [[getActiveValidators()]] in that the latter
+ * only returns the validators applicable to the current [[scenario]].
+ *
+ * Because this method returns an ArrayObject object, you may
+ * manipulate it by inserting or removing validators (useful in model behaviors).
+ * For example,
+ *
+ * ~~~
+ * $model->validators[] = $newValidator;
+ * ~~~
+ *
+ * @return ArrayObject all the validators declared in the model.
+ */
+ public function getValidators()
+ {
+ if ($this->_validators === null) {
+ $this->_validators = $this->createValidators();
+ }
+ return $this->_validators;
+ }
+
+ /**
+ * Returns the validators applicable to the current [[scenario]].
+ * @param string $attribute the name of the attribute whose applicable validators should be returned.
+ * If this is null, the validators for ALL attributes in the model will be returned.
+ * @return \yii\validators\Validator[] the validators applicable to the current [[scenario]].
+ */
+ public function getActiveValidators($attribute = null)
+ {
+ $validators = array();
+ $scenario = $this->getScenario();
+ /** @var $validator Validator */
+ foreach ($this->getValidators() as $validator) {
+ if ($validator->isActive($scenario) && ($attribute === null || in_array($attribute, $validator->attributes, true))) {
+ $validators[] = $validator;
+ }
+ }
+ return $validators;
+ }
+
+ /**
+ * Creates validator objects based on the validation rules specified in [[rules()]].
+ * Unlike [[getValidators()]], each time this method is called, a new list of validators will be returned.
+ * @return ArrayObject validators
+ * @throws InvalidConfigException if any validation rule configuration is invalid
+ */
+ public function createValidators()
+ {
+ $validators = new ArrayObject;
+ foreach ($this->rules() as $rule) {
+ if ($rule instanceof Validator) {
+ $validators->append($rule);
+ } elseif (is_array($rule) && isset($rule[0], $rule[1])) { // attributes, validator type
+ $validator = Validator::createValidator($rule[1], $this, $rule[0], array_slice($rule, 2));
+ $validators->append($validator);
+ } else {
+ throw new InvalidConfigException('Invalid validation rule: a rule must specify both attribute names and validator type.');
+ }
+ }
+ return $validators;
+ }
+
+ /**
+ * Returns a value indicating whether the attribute is required.
+ * This is determined by checking if the attribute is associated with a
+ * [[\yii\validators\RequiredValidator|required]] validation rule in the
+ * current [[scenario]].
+ * @param string $attribute attribute name
+ * @return boolean whether the attribute is required
+ */
+ public function isAttributeRequired($attribute)
+ {
+ foreach ($this->getActiveValidators($attribute) as $validator) {
+ if ($validator instanceof RequiredValidator) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Returns a value indicating whether the attribute is safe for massive assignments.
+ * @param string $attribute attribute name
+ * @return boolean whether the attribute is safe for massive assignments
+ * @see safeAttributes()
+ */
+ public function isAttributeSafe($attribute)
+ {
+ return in_array($attribute, $this->safeAttributes(), true);
+ }
+
+ /**
+ * Returns a value indicating whether the attribute is active in the current scenario.
+ * @param string $attribute attribute name
+ * @return boolean whether the attribute is active in the current scenario
+ * @see activeAttributes()
+ */
+ public function isAttributeActive($attribute)
+ {
+ return in_array($attribute, $this->activeAttributes(), true);
+ }
+
+ /**
+ * Returns the text label for the specified attribute.
+ * @param string $attribute the attribute name
+ * @return string the attribute label
+ * @see generateAttributeLabel
+ * @see attributeLabels
+ */
+ public function getAttributeLabel($attribute)
+ {
+ $labels = $this->attributeLabels();
+ return isset($labels[$attribute]) ? $labels[$attribute] : $this->generateAttributeLabel($attribute);
+ }
+
+ /**
+ * Returns a value indicating whether there is any validation error.
+ * @param string|null $attribute attribute name. Use null to check all attributes.
+ * @return boolean whether there is any error.
+ */
+ public function hasErrors($attribute = null)
+ {
+ return $attribute === null ? !empty($this->_errors) : isset($this->_errors[$attribute]);
+ }
+
+ /**
+ * Returns the errors for all attribute or a single attribute.
+ * @param string $attribute attribute name. Use null to retrieve errors for all attributes.
+ * @property array An array of errors for all attributes. Empty array is returned if no error.
+ * The result is a two-dimensional array. See [[getErrors()]] for detailed description.
+ * @return array errors for all attributes or the specified attribute. Empty array is returned if no error.
+ * Note that when returning errors for all attributes, the result is a two-dimensional array, like the following:
+ *
+ * ~~~
+ * array(
+ * 'username' => array(
+ * 'Username is required.',
+ * 'Username must contain only word characters.',
+ * ),
+ * 'email' => array(
+ * 'Email address is invalid.',
+ * )
+ * )
+ * ~~~
+ *
+ * @see getFirstErrors
+ * @see getFirstError
+ */
+ public function getErrors($attribute = null)
+ {
+ if ($attribute === null) {
+ return $this->_errors === null ? array() : $this->_errors;
+ } else {
+ return isset($this->_errors[$attribute]) ? $this->_errors[$attribute] : array();
+ }
+ }
+
+ /**
+ * Returns the first error of every attribute in the model.
+ * @return array the first errors. An empty array will be returned if there is no error.
+ * @see getErrors
+ * @see getFirstError
+ */
+ public function getFirstErrors()
+ {
+ if (empty($this->_errors)) {
+ return array();
+ } else {
+ $errors = array();
+ foreach ($this->_errors as $attributeErrors) {
+ if (isset($attributeErrors[0])) {
+ $errors[] = $attributeErrors[0];
+ }
+ }
+ }
+ return $errors;
+ }
+
+ /**
+ * Returns the first error of the specified attribute.
+ * @param string $attribute attribute name.
+ * @return string the error message. Null is returned if no error.
+ * @see getErrors
+ * @see getFirstErrors
+ */
+ public function getFirstError($attribute)
+ {
+ return isset($this->_errors[$attribute]) ? reset($this->_errors[$attribute]) : null;
+ }
+
+ /**
+ * Adds a new error to the specified attribute.
+ * @param string $attribute attribute name
+ * @param string $error new error message
+ */
+ public function addError($attribute, $error = '')
+ {
+ $this->_errors[$attribute][] = $error;
+ }
+
+ /**
+ * Removes errors for all attributes or a single attribute.
+ * @param string $attribute attribute name. Use null to remove errors for all attribute.
+ */
+ public function clearErrors($attribute = null)
+ {
+ if ($attribute === null) {
+ $this->_errors = array();
+ } else {
+ unset($this->_errors[$attribute]);
+ }
+ }
+
+ /**
+ * Generates a user friendly attribute label based on the give attribute name.
+ * This is done by replacing underscores, dashes and dots with blanks and
+ * changing the first letter of each word to upper case.
+ * For example, 'department_name' or 'DepartmentName' will generate 'Department Name'.
+ * @param string $name the column name
+ * @return string the attribute label
+ */
+ public function generateAttributeLabel($name)
+ {
+ return Inflector::camel2words($name, true);
+ }
+
+ /**
+ * Returns attribute values.
+ * @param array $names list of attributes whose value needs to be returned.
+ * Defaults to null, meaning all attributes listed in [[attributes()]] will be returned.
+ * If it is an array, only the attributes in the array will be returned.
+ * @param array $except list of attributes whose value should NOT be returned.
+ * @return array attribute values (name => value).
+ */
+ public function getAttributes($names = null, $except = array())
+ {
+ $values = array();
+ if ($names === null) {
+ $names = $this->attributes();
+ }
+ foreach ($names as $name) {
+ $values[$name] = $this->$name;
+ }
+ foreach ($except as $name) {
+ unset($values[$name]);
+ }
+
+ return $values;
+ }
+
+ /**
+ * Sets the attribute values in a massive way.
+ * @param array $values attribute values (name => value) to be assigned to the model.
+ * @param boolean $safeOnly whether the assignments should only be done to the safe attributes.
+ * A safe attribute is one that is associated with a validation rule in the current [[scenario]].
+ * @see safeAttributes()
+ * @see attributes()
+ */
+ public function setAttributes($values, $safeOnly = true)
+ {
+ if (is_array($values)) {
+ $attributes = array_flip($safeOnly ? $this->safeAttributes() : $this->attributes());
+ foreach ($values as $name => $value) {
+ if (isset($attributes[$name])) {
+ $this->$name = $value;
+ } elseif ($safeOnly) {
+ $this->onUnsafeAttribute($name, $value);
+ }
+ }
+ }
+ }
+
+ /**
+ * This method is invoked when an unsafe attribute is being massively assigned.
+ * The default implementation will log a warning message if YII_DEBUG is on.
+ * It does nothing otherwise.
+ * @param string $name the unsafe attribute name
+ * @param mixed $value the attribute value
+ */
+ public function onUnsafeAttribute($name, $value)
+ {
+ if (YII_DEBUG) {
+ Yii::trace("Failed to set unsafe attribute '$name' in '" . get_class($this) . "'.", __METHOD__);
+ }
+ }
+
+ /**
+ * Returns the scenario that this model is used in.
+ *
+ * Scenario affects how validation is performed and which attributes can
+ * be massively assigned.
+ *
+ * @return string the scenario that this model is in. Defaults to [[DEFAULT_SCENARIO]].
+ */
+ public function getScenario()
+ {
+ return $this->_scenario;
+ }
+
+ /**
+ * Sets the scenario for the model.
+ * Note that this method does not check if the scenario exists or not.
+ * The method [[validate()]] will perform this check.
+ * @param string $value the scenario that this model is in.
+ */
+ public function setScenario($value)
+ {
+ $this->_scenario = $value;
+ }
+
+ /**
+ * Returns the attribute names that are safe to be massively assigned in the current scenario.
+ * @return string[] safe attribute names
+ */
+ public function safeAttributes()
+ {
+ $scenario = $this->getScenario();
+ $scenarios = $this->scenarios();
+ if (!isset($scenarios[$scenario])) {
+ return array();
+ }
+ $attributes = array();
+ foreach ($scenarios[$scenario] as $attribute) {
+ if ($attribute[0] !== '!') {
+ $attributes[] = $attribute;
+ }
+ }
+ return $attributes;
+ }
+
+ /**
+ * Returns the attribute names that are subject to validation in the current scenario.
+ * @return string[] safe attribute names
+ */
+ public function activeAttributes()
+ {
+ $scenario = $this->getScenario();
+ $scenarios = $this->scenarios();
+ if (!isset($scenarios[$scenario])) {
+ return array();
+ }
+ $attributes = $scenarios[$scenario];
+ foreach ($attributes as $i => $attribute) {
+ if ($attribute[0] === '!') {
+ $attributes[$i] = substr($attribute, 1);
+ }
+ }
+ return $attributes;
+ }
+
+ /**
+ * Populates the model with the data from end user.
+ * The data to be loaded is `$data[formName]`, where `formName` refers to the value of [[formName()]].
+ * If [[formName()]] is empty, the whole `$data` array will be used to populate the model.
+ * The data being populated is subject to the safety check by [[setAttributes()]].
+ * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
+ * supplied by end user.
+ * @return boolean whether the model is successfully populated with some data.
+ */
+ public function load($data)
+ {
+ $scope = $this->formName();
+ if ($scope == '') {
+ $this->setAttributes($data);
+ return true;
+ } elseif (isset($data[$scope])) {
+ $this->setAttributes($data[$scope]);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Populates a set of models with the data from end user.
+ * This method is mainly used to collect tabular data input.
+ * The data to be loaded for each model is `$data[formName][index]`, where `formName`
+ * refers to the value of [[formName()]], and `index` the index of the model in the `$models` array.
+ * If [[formName()]] is empty, `$data[index]` will be used to populate each model.
+ * The data being populated to each model is subject to the safety check by [[setAttributes()]].
+ * @param array $models the models to be populated. Note that all models should have the same class.
+ * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array
+ * supplied by end user.
+ * @return boolean whether the model is successfully populated with some data.
+ */
+ public static function loadMultiple($models, $data)
+ {
+ /** @var Model $model */
+ $model = reset($models);
+ if ($model === false) {
+ return false;
+ }
+ $success = false;
+ $scope = $model->formName();
+ foreach ($models as $i => $model) {
+ if ($scope == '') {
+ if (isset($data[$i])) {
+ $model->setAttributes($data[$i]);
+ $success = true;
+ }
+ } elseif (isset($data[$scope][$i])) {
+ $model->setAttributes($data[$scope][$i]);
+ $success = true;
+ }
+ }
+ return $success;
+ }
+
+ /**
+ * Validates multiple models.
+ * @param array $models the models to be validated
+ * @return boolean whether all models are valid. False will be returned if one
+ * or multiple models have validation error.
+ */
+ public static function validateMultiple($models)
+ {
+ $valid = true;
+ /** @var Model $model */
+ foreach ($models as $model) {
+ $valid = $model->validate() && $valid;
+ }
+ return $valid;
+ }
+
+ /**
+ * Converts the object into an array.
+ * The default implementation will return [[attributes]].
+ * @return array the array representation of the object
+ */
+ public function toArray()
+ {
+ return $this->getAttributes();
+ }
+
+ /**
+ * Returns an iterator for traversing the attributes in the model.
+ * This method is required by the interface IteratorAggregate.
+ * @return ArrayIterator an iterator for traversing the items in the list.
+ */
+ public function getIterator()
+ {
+ $attributes = $this->getAttributes();
+ return new ArrayIterator($attributes);
+ }
+
+ /**
+ * Returns whether there is an element at the specified offset.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `isset($model[$offset])`.
+ * @param mixed $offset the offset to check on
+ * @return boolean
+ */
+ public function offsetExists($offset)
+ {
+ return $this->$offset !== null;
+ }
+
+ /**
+ * Returns the element at the specified offset.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$value = $model[$offset];`.
+ * @param mixed $offset the offset to retrieve element.
+ * @return mixed the element at the offset, null if no element is found at the offset
+ */
+ public function offsetGet($offset)
+ {
+ return $this->$offset;
+ }
+
+ /**
+ * Sets the element at the specified offset.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `$model[$offset] = $item;`.
+ * @param integer $offset the offset to set element
+ * @param mixed $item the element value
+ */
+ public function offsetSet($offset, $item)
+ {
+ $this->$offset = $item;
+ }
+
+ /**
+ * Sets the element value at the specified offset to null.
+ * This method is required by the SPL interface `ArrayAccess`.
+ * It is implicitly called when you use something like `unset($model[$offset])`.
+ * @param mixed $offset the offset to unset element
+ */
+ public function offsetUnset($offset)
+ {
+ $this->$offset = null;
+ }
+}
diff --git a/yii/base/ModelEvent.php b/framework/yii/base/ModelEvent.php
similarity index 100%
rename from yii/base/ModelEvent.php
rename to framework/yii/base/ModelEvent.php
diff --git a/framework/yii/base/Module.php b/framework/yii/base/Module.php
new file mode 100644
index 0000000..7c29a8b
--- /dev/null
+++ b/framework/yii/base/Module.php
@@ -0,0 +1,674 @@
+
+ * @since 2.0
+ */
+abstract class Module extends Component
+{
+ /**
+ * @var array custom module parameters (name => value).
+ */
+ public $params = array();
+ /**
+ * @var array the IDs of the components or modules that should be preloaded when this module is created.
+ */
+ public $preload = array();
+ /**
+ * @var string an ID that uniquely identifies this module among other modules which have the same [[module|parent]].
+ */
+ public $id;
+ /**
+ * @var Module the parent module of this module. Null if this module does not have a parent.
+ */
+ public $module;
+ /**
+ * @var string|boolean the layout that should be applied for views within this module. This refers to a view name
+ * relative to [[layoutPath]]. If this is not set, it means the layout value of the [[module|parent module]]
+ * will be taken. If this is false, layout will be disabled within this module.
+ */
+ public $layout;
+ /**
+ * @var array mapping from controller ID to controller configurations.
+ * Each name-value pair specifies the configuration of a single controller.
+ * A controller configuration can be either a string or an array.
+ * If the former, the string should be the class name or path alias of the controller.
+ * If the latter, the array must contain a 'class' element which specifies
+ * the controller's class name or path alias, and the rest of the name-value pairs
+ * in the array are used to initialize the corresponding controller properties. For example,
+ *
+ * ~~~
+ * array(
+ * 'account' => '@app/controllers/UserController',
+ * 'article' => array(
+ * 'class' => '@app/controllers/PostController',
+ * 'pageTitle' => 'something new',
+ * ),
+ * )
+ * ~~~
+ */
+ public $controllerMap = array();
+ /**
+ * @var string the namespace that controller classes are in. If not set,
+ * it will use the "controllers" sub-namespace under the namespace of this module.
+ * For example, if the namespace of this module is "foo\bar", then the default
+ * controller namespace would be "foo\bar\controllers".
+ * If the module is an application, it will default to "app\controllers".
+ */
+ public $controllerNamespace;
+ /**
+ * @return string the default route of this module. Defaults to 'default'.
+ * The route may consist of child module ID, controller ID, and/or action ID.
+ * For example, `help`, `post/create`, `admin/post/create`.
+ * If action ID is not given, it will take the default value as specified in
+ * [[Controller::defaultAction]].
+ */
+ public $defaultRoute = 'default';
+ /**
+ * @var string the root directory of the module.
+ */
+ private $_basePath;
+ /**
+ * @var string the root directory that contains view files for this module
+ */
+ private $_viewPath;
+ /**
+ * @var string the root directory that contains layout view files for this module.
+ */
+ private $_layoutPath;
+ /**
+ * @var string the directory containing controller classes in the module.
+ */
+ private $_controllerPath;
+ /**
+ * @var array child modules of this module
+ */
+ private $_modules = array();
+ /**
+ * @var array components registered under this module
+ */
+ private $_components = array();
+
+ /**
+ * Constructor.
+ * @param string $id the ID of this module
+ * @param Module $parent the parent module (if any)
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($id, $parent = null, $config = array())
+ {
+ $this->id = $id;
+ $this->module = $parent;
+ parent::__construct($config);
+ }
+
+ /**
+ * Getter magic method.
+ * This method is overridden to support accessing components
+ * like reading module properties.
+ * @param string $name component or property name
+ * @return mixed the named property value
+ */
+ public function __get($name)
+ {
+ if ($this->hasComponent($name)) {
+ return $this->getComponent($name);
+ } else {
+ return parent::__get($name);
+ }
+ }
+
+ /**
+ * Checks if a property value is null.
+ * This method overrides the parent implementation by checking
+ * if the named component is loaded.
+ * @param string $name the property name or the event name
+ * @return boolean whether the property value is null
+ */
+ public function __isset($name)
+ {
+ if ($this->hasComponent($name)) {
+ return $this->getComponent($name) !== null;
+ } else {
+ return parent::__isset($name);
+ }
+ }
+
+ /**
+ * Initializes the module.
+ * This method is called after the module is created and initialized with property values
+ * given in configuration. The default implement will create a path alias using the module [[id]]
+ * and then call [[preloadComponents()]] to load components that are declared in [[preload]].
+ */
+ public function init()
+ {
+ $this->preloadComponents();
+ if ($this->controllerNamespace === null) {
+ if ($this instanceof Application) {
+ $this->controllerNamespace = 'app\\controllers';
+ } else {
+ $class = get_class($this);
+ if (($pos = strrpos($class, '\\')) !== false) {
+ $this->controllerNamespace = substr($class, 0, $pos) . '\\controllers';
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns an ID that uniquely identifies this module among all modules within the current application.
+ * Note that if the module is an application, an empty string will be returned.
+ * @return string the unique ID of the module.
+ */
+ public function getUniqueId()
+ {
+ return $this->module ? ltrim($this->module->getUniqueId() . '/' . $this->id, '/') : $this->id;
+ }
+
+ /**
+ * Returns the root directory of the module.
+ * It defaults to the directory containing the module class file.
+ * @return string the root directory of the module.
+ */
+ public function getBasePath()
+ {
+ if ($this->_basePath === null) {
+ $class = new \ReflectionClass($this);
+ $this->_basePath = dirname($class->getFileName());
+ }
+ return $this->_basePath;
+ }
+
+ /**
+ * Sets the root directory of the module.
+ * This method can only be invoked at the beginning of the constructor.
+ * @param string $path the root directory of the module. This can be either a directory name or a path alias.
+ * @throws InvalidParamException if the directory does not exist.
+ */
+ public function setBasePath($path)
+ {
+ $path = Yii::getAlias($path);
+ $p = realpath($path);
+ if ($p !== false && is_dir($p)) {
+ $this->_basePath = $p;
+ if ($this instanceof Application) {
+ Yii::setAlias('@app', $p);
+ }
+ } else {
+ throw new InvalidParamException("The directory does not exist: $path");
+ }
+ }
+
+ /**
+ * Returns the directory that contains the controller classes.
+ * Defaults to "[[basePath]]/controllers".
+ * @return string the directory that contains the controller classes.
+ */
+ public function getControllerPath()
+ {
+ if ($this->_controllerPath !== null) {
+ return $this->_controllerPath;
+ } else {
+ return $this->_controllerPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'controllers';
+ }
+ }
+
+ /**
+ * Sets the directory that contains the controller classes.
+ * @param string $path the directory that contains the controller classes.
+ * This can be either a directory name or a path alias.
+ * @throws Exception if the directory is invalid
+ */
+ public function setControllerPath($path)
+ {
+ $this->_controllerPath = Yii::getAlias($path);
+ }
+
+ /**
+ * Returns the directory that contains the view files for this module.
+ * @return string the root directory of view files. Defaults to "[[basePath]]/view".
+ */
+ public function getViewPath()
+ {
+ if ($this->_viewPath !== null) {
+ return $this->_viewPath;
+ } else {
+ return $this->_viewPath = $this->getBasePath() . DIRECTORY_SEPARATOR . 'views';
+ }
+ }
+
+ /**
+ * Sets the directory that contains the view files.
+ * @param string $path the root directory of view files.
+ * @throws Exception if the directory is invalid
+ */
+ public function setViewPath($path)
+ {
+ $this->_viewPath = Yii::getAlias($path);
+ }
+
+ /**
+ * Returns the directory that contains layout view files for this module.
+ * @return string the root directory of layout files. Defaults to "[[viewPath]]/layouts".
+ */
+ public function getLayoutPath()
+ {
+ if ($this->_layoutPath !== null) {
+ return $this->_layoutPath;
+ } else {
+ return $this->_layoutPath = $this->getViewPath() . DIRECTORY_SEPARATOR . 'layouts';
+ }
+ }
+
+ /**
+ * Sets the directory that contains the layout files.
+ * @param string $path the root directory of layout files.
+ * @throws Exception if the directory is invalid
+ */
+ public function setLayoutPath($path)
+ {
+ $this->_layoutPath = Yii::getAlias($path);
+ }
+
+ /**
+ * Defines path aliases.
+ * This method calls [[Yii::setAlias()]] to register the path aliases.
+ * This method is provided so that you can define path aliases when configuring a module.
+ * @property array list of path aliases to be defined. The array keys are alias names
+ * (must start with '@') and the array values are the corresponding paths or aliases.
+ * See [[setAliases()]] for an example.
+ * @param array $aliases list of path aliases to be defined. The array keys are alias names
+ * (must start with '@') and the array values are the corresponding paths or aliases.
+ * For example,
+ *
+ * ~~~
+ * array(
+ * '@models' => '@app/models', // an existing alias
+ * '@backend' => __DIR__ . '/../backend', // a directory
+ * )
+ * ~~~
+ */
+ public function setAliases($aliases)
+ {
+ foreach ($aliases as $name => $alias) {
+ Yii::setAlias($name, $alias);
+ }
+ }
+
+ /**
+ * Checks whether the child module of the specified ID exists.
+ * This method supports checking the existence of both child and grand child modules.
+ * @param string $id module ID. For grand child modules, use ID path relative to this module (e.g. `admin/content`).
+ * @return boolean whether the named module exists. Both loaded and unloaded modules
+ * are considered.
+ */
+ public function hasModule($id)
+ {
+ if (($pos = strpos($id, '/')) !== false) {
+ // sub-module
+ $module = $this->getModule(substr($id, 0, $pos));
+ return $module === null ? false : $module->hasModule(substr($id, $pos + 1));
+ } else {
+ return isset($this->_modules[$id]);
+ }
+ }
+
+ /**
+ * Retrieves the child module of the specified ID.
+ * This method supports retrieving both child modules and grand child modules.
+ * @param string $id module ID (case-sensitive). To retrieve grand child modules,
+ * use ID path relative to this module (e.g. `admin/content`).
+ * @param boolean $load whether to load the module if it is not yet loaded.
+ * @return Module|null the module instance, null if the module does not exist.
+ * @see hasModule()
+ */
+ public function getModule($id, $load = true)
+ {
+ if (($pos = strpos($id, '/')) !== false) {
+ // sub-module
+ $module = $this->getModule(substr($id, 0, $pos));
+ return $module === null ? null : $module->getModule(substr($id, $pos + 1), $load);
+ }
+
+ if (isset($this->_modules[$id])) {
+ if ($this->_modules[$id] instanceof Module) {
+ return $this->_modules[$id];
+ } elseif ($load) {
+ Yii::trace("Loading module: $id", __METHOD__);
+ return $this->_modules[$id] = Yii::createObject($this->_modules[$id], $id, $this);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Adds a sub-module to this module.
+ * @param string $id module ID
+ * @param Module|array|null $module the sub-module to be added to this module. This can
+ * be one of the followings:
+ *
+ * - a [[Module]] object
+ * - a configuration array: when [[getModule()]] is called initially, the array
+ * will be used to instantiate the sub-module
+ * - null: the named sub-module will be removed from this module
+ */
+ public function setModule($id, $module)
+ {
+ if ($module === null) {
+ unset($this->_modules[$id]);
+ } else {
+ $this->_modules[$id] = $module;
+ }
+ }
+
+ /**
+ * Returns the sub-modules in this module.
+ * @param boolean $loadedOnly whether to return the loaded sub-modules only. If this is set false,
+ * then all sub-modules registered in this module will be returned, whether they are loaded or not.
+ * Loaded modules will be returned as objects, while unloaded modules as configuration arrays.
+ * @return array the modules (indexed by their IDs)
+ */
+ public function getModules($loadedOnly = false)
+ {
+ if ($loadedOnly) {
+ $modules = array();
+ foreach ($this->_modules as $module) {
+ if ($module instanceof Module) {
+ $modules[] = $module;
+ }
+ }
+ return $modules;
+ } else {
+ return $this->_modules;
+ }
+ }
+
+ /**
+ * Registers sub-modules in the current module.
+ *
+ * Each sub-module should be specified as a name-value pair, where
+ * name refers to the ID of the module and value the module or a configuration
+ * array that can be used to create the module. In the latter case, [[Yii::createObject()]]
+ * will be used to create the module.
+ *
+ * If a new sub-module has the same ID as an existing one, the existing one will be overwritten silently.
+ *
+ * The following is an example for registering two sub-modules:
+ *
+ * ~~~
+ * array(
+ * 'comment' => array(
+ * 'class' => 'app\modules\comment\CommentModule',
+ * 'db' => 'db',
+ * ),
+ * 'booking' => array(
+ * 'class' => 'app\modules\booking\BookingModule',
+ * ),
+ * )
+ * ~~~
+ *
+ * @param array $modules modules (id => module configuration or instances)
+ */
+ public function setModules($modules)
+ {
+ foreach ($modules as $id => $module) {
+ $this->_modules[$id] = $module;
+ }
+ }
+
+ /**
+ * Checks whether the named component exists.
+ * @param string $id component ID
+ * @return boolean whether the named component exists. Both loaded and unloaded components
+ * are considered.
+ */
+ public function hasComponent($id)
+ {
+ return isset($this->_components[$id]);
+ }
+
+ /**
+ * Retrieves the named component.
+ * @param string $id component ID (case-sensitive)
+ * @param boolean $load whether to load the component if it is not yet loaded.
+ * @return Component|null the component instance, null if the component does not exist.
+ * @see hasComponent()
+ */
+ public function getComponent($id, $load = true)
+ {
+ if (isset($this->_components[$id])) {
+ if ($this->_components[$id] instanceof Object) {
+ return $this->_components[$id];
+ } elseif ($load) {
+ return $this->_components[$id] = Yii::createObject($this->_components[$id]);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Registers a component with this module.
+ * @param string $id component ID
+ * @param Component|array|null $component the component to be registered with the module. This can
+ * be one of the followings:
+ *
+ * - a [[Component]] object
+ * - a configuration array: when [[getComponent()]] is called initially for this component, the array
+ * will be used to instantiate the component via [[Yii::createObject()]].
+ * - null: the named component will be removed from the module
+ */
+ public function setComponent($id, $component)
+ {
+ if ($component === null) {
+ unset($this->_components[$id]);
+ } else {
+ $this->_components[$id] = $component;
+ }
+ }
+
+ /**
+ * Returns the registered components.
+ * @param boolean $loadedOnly whether to return the loaded components only. If this is set false,
+ * then all components specified in the configuration will be returned, whether they are loaded or not.
+ * Loaded components will be returned as objects, while unloaded components as configuration arrays.
+ * @return array the components (indexed by their IDs)
+ */
+ public function getComponents($loadedOnly = false)
+ {
+ if ($loadedOnly) {
+ $components = array();
+ foreach ($this->_components as $component) {
+ if ($component instanceof Component) {
+ $components[] = $component;
+ }
+ }
+ return $components;
+ } else {
+ return $this->_components;
+ }
+ }
+
+ /**
+ * Registers a set of components in this module.
+ *
+ * Each component should be specified as a name-value pair, where
+ * name refers to the ID of the component and value the component or a configuration
+ * array that can be used to create the component. In the latter case, [[Yii::createObject()]]
+ * will be used to create the component.
+ *
+ * If a new component has the same ID as an existing one, the existing one will be overwritten silently.
+ *
+ * The following is an example for setting two components:
+ *
+ * ~~~
+ * array(
+ * 'db' => array(
+ * 'class' => 'yii\db\Connection',
+ * 'dsn' => 'sqlite:path/to/file.db',
+ * ),
+ * 'cache' => array(
+ * 'class' => 'yii\caching\DbCache',
+ * 'db' => 'db',
+ * ),
+ * )
+ * ~~~
+ *
+ * @param array $components components (id => component configuration or instance)
+ */
+ public function setComponents($components)
+ {
+ foreach ($components as $id => $component) {
+ if (isset($this->_components[$id]['class']) && !isset($component['class'])) {
+ $component['class'] = $this->_components[$id]['class'];
+ }
+ $this->_components[$id] = $component;
+ }
+ }
+
+ /**
+ * Loads components that are declared in [[preload]].
+ * @throws InvalidConfigException if a component or module to be preloaded is unknown
+ */
+ public function preloadComponents()
+ {
+ foreach ($this->preload as $id) {
+ if ($this->hasComponent($id)) {
+ $this->getComponent($id);
+ } elseif ($this->hasModule($id)) {
+ $this->getModule($id);
+ } else {
+ throw new InvalidConfigException("Unknown component or module: $id");
+ }
+ }
+ }
+
+ /**
+ * Runs a controller action specified by a route.
+ * This method parses the specified route and creates the corresponding child module(s), controller and action
+ * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
+ * If the route is empty, the method will use [[defaultRoute]].
+ * @param string $route the route that specifies the action.
+ * @param array $params the parameters to be passed to the action
+ * @return mixed the result of the action.
+ * @throws InvalidRouteException if the requested route cannot be resolved into an action successfully
+ */
+ public function runAction($route, $params = array())
+ {
+ $parts = $this->createController($route);
+ if (is_array($parts)) {
+ /** @var $controller Controller */
+ list($controller, $actionID) = $parts;
+ $oldController = Yii::$app->controller;
+ Yii::$app->controller = $controller;
+ $result = $controller->runAction($actionID, $params);
+ Yii::$app->controller = $oldController;
+ return $result;
+ } else {
+ throw new InvalidRouteException('Unable to resolve the request "' . trim($this->getUniqueId() . '/' . $route, '/') . '".');
+ }
+ }
+
+ /**
+ * Creates a controller instance based on the controller ID.
+ *
+ * The controller is created within this module. The method first attempts to
+ * create the controller based on the [[controllerMap]] of the module. If not available,
+ * it will look for the controller class under the [[controllerPath]] and create an
+ * instance of it.
+ *
+ * @param string $route the route consisting of module, controller and action IDs.
+ * @return array|boolean If the controller is created successfully, it will be returned together
+ * with the requested action ID. Otherwise false will be returned.
+ * @throws InvalidConfigException if the controller class and its file do not match.
+ */
+ public function createController($route)
+ {
+ if ($route === '') {
+ $route = $this->defaultRoute;
+ }
+ if (($pos = strpos($route, '/')) !== false) {
+ $id = substr($route, 0, $pos);
+ $route = substr($route, $pos + 1);
+ } else {
+ $id = $route;
+ $route = '';
+ }
+
+ $module = $this->getModule($id);
+ if ($module !== null) {
+ return $module->createController($route);
+ }
+
+ if (isset($this->controllerMap[$id])) {
+ $controller = Yii::createObject($this->controllerMap[$id], $id, $this);
+ } elseif (preg_match('/^[a-z0-9\\-_]+$/', $id)) {
+ $className = str_replace(' ', '', ucwords(str_replace('-', ' ', $id))) . 'Controller';
+ $classFile = $this->controllerPath . DIRECTORY_SEPARATOR . $className . '.php';
+ if (!is_file($classFile)) {
+ return false;
+ }
+ $className = ltrim($this->controllerNamespace . '\\' . $className, '\\');
+ Yii::$classMap[$className] = $classFile;
+ if (is_subclass_of($className, 'yii\base\Controller')) {
+ $controller = new $className($id, $this);
+ } elseif (YII_DEBUG) {
+ throw new InvalidConfigException("Controller class must extend from \\yii\\base\\Controller.");
+ }
+ }
+
+ return isset($controller) ? array($controller, $route) : false;
+ }
+
+ /**
+ * This method is invoked right before an action of this module is to be executed (after all possible filters.)
+ * You may override this method to do last-minute preparation for the action.
+ * Make sure you call the parent implementation so that the relevant event is triggered.
+ * @param Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ */
+ public function beforeAction($action)
+ {
+ return true;
+ }
+
+ /**
+ * This method is invoked right after an action of this module has been executed.
+ * You may override this method to do some postprocessing for the action.
+ * Make sure you call the parent implementation so that the relevant event is triggered.
+ * @param Action $action the action just executed.
+ * @param mixed $result the action return result.
+ */
+ public function afterAction($action, &$result)
+ {
+ }
+}
diff --git a/framework/yii/base/NotSupportedException.php b/framework/yii/base/NotSupportedException.php
new file mode 100644
index 0000000..33f936f
--- /dev/null
+++ b/framework/yii/base/NotSupportedException.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class NotSupportedException extends Exception
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Not Supported');
+ }
+}
diff --git a/framework/yii/base/Object.php b/framework/yii/base/Object.php
new file mode 100644
index 0000000..55754de
--- /dev/null
+++ b/framework/yii/base/Object.php
@@ -0,0 +1,236 @@
+
+ * @since 2.0
+ */
+class Object implements Arrayable
+{
+ /**
+ * @return string the fully qualified name of this class.
+ */
+ public static function className()
+ {
+ return get_called_class();
+ }
+
+ /**
+ * Constructor.
+ * The default implementation does two things:
+ *
+ * - Initializes the object with the given configuration `$config`.
+ * - Call [[init()]].
+ *
+ * If this method is overridden in a child class, it is recommended that
+ *
+ * - the last parameter of the constructor is a configuration array, like `$config` here.
+ * - call the parent implementation at the end of the constructor.
+ *
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($config = array())
+ {
+ if (!empty($config)) {
+ Yii::configure($this, $config);
+ }
+ $this->init();
+ }
+
+ /**
+ * Initializes the object.
+ * This method is invoked at the end of the constructor after the object is initialized with the
+ * given configuration.
+ */
+ public function init()
+ {
+ }
+
+ /**
+ * Returns the value of an object property.
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `$value = $object->property;`.
+ * @param string $name the property name
+ * @return mixed the property value
+ * @throws UnknownPropertyException if the property is not defined
+ * @see __set
+ */
+ public function __get($name)
+ {
+ $getter = 'get' . $name;
+ if (method_exists($this, $getter)) {
+ return $this->$getter();
+ } elseif (method_exists($this, 'set' . $name)) {
+ throw new InvalidCallException('Getting write-only property: ' . get_class($this) . '::' . $name);
+ } else {
+ throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
+ }
+ }
+
+ /**
+ * Sets value of an object property.
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `$object->property = $value;`.
+ * @param string $name the property name or the event name
+ * @param mixed $value the property value
+ * @throws UnknownPropertyException if the property is not defined
+ * @throws InvalidCallException if the property is read-only.
+ * @see __get
+ */
+ public function __set($name, $value)
+ {
+ $setter = 'set' . $name;
+ if (method_exists($this, $setter)) {
+ $this->$setter($value);
+ } elseif (method_exists($this, 'get' . $name)) {
+ throw new InvalidCallException('Setting read-only property: ' . get_class($this) . '::' . $name);
+ } else {
+ throw new UnknownPropertyException('Setting unknown property: ' . get_class($this) . '::' . $name);
+ }
+ }
+
+ /**
+ * Checks if the named property is set (not null).
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `isset($object->property)`.
+ *
+ * Note that if the property is not defined, false will be returned.
+ * @param string $name the property name or the event name
+ * @return boolean whether the named property is set (not null).
+ */
+ public function __isset($name)
+ {
+ $getter = 'get' . $name;
+ if (method_exists($this, $getter)) {
+ return $this->$getter() !== null;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Sets an object property to null.
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when executing `unset($object->property)`.
+ *
+ * Note that if the property is not defined, this method will do nothing.
+ * If the property is read-only, it will throw an exception.
+ * @param string $name the property name
+ * @throws InvalidCallException if the property is read only.
+ */
+ public function __unset($name)
+ {
+ $setter = 'set' . $name;
+ if (method_exists($this, $setter)) {
+ $this->$setter(null);
+ } elseif (method_exists($this, 'get' . $name)) {
+ throw new InvalidCallException('Unsetting read-only property: ' . get_class($this) . '::' . $name);
+ }
+ }
+
+ /**
+ * Calls the named method which is not a class method.
+ *
+ * Do not call this method directly as it is a PHP magic method that
+ * will be implicitly called when an unknown method is being invoked.
+ * @param string $name the method name
+ * @param array $params method parameters
+ * @throws UnknownMethodException when calling unknown method
+ * @return mixed the method return value
+ */
+ public function __call($name, $params)
+ {
+ throw new UnknownMethodException('Unknown method: ' . get_class($this) . "::$name()");
+ }
+
+ /**
+ * Returns a value indicating whether a property is defined.
+ * A property is defined if:
+ *
+ * - the class has a getter or setter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @return boolean whether the property is defined
+ * @see canGetProperty
+ * @see canSetProperty
+ */
+ public function hasProperty($name, $checkVars = true)
+ {
+ return $this->canGetProperty($name, $checkVars) || $this->canSetProperty($name, false);
+ }
+
+ /**
+ * Returns a value indicating whether a property can be read.
+ * A property is readable if:
+ *
+ * - the class has a getter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @return boolean whether the property can be read
+ * @see canSetProperty
+ */
+ public function canGetProperty($name, $checkVars = true)
+ {
+ return method_exists($this, 'get' . $name) || $checkVars && property_exists($this, $name);
+ }
+
+ /**
+ * Returns a value indicating whether a property can be set.
+ * A property is writable if:
+ *
+ * - the class has a setter method associated with the specified name
+ * (in this case, property name is case-insensitive);
+ * - the class has a member variable with the specified name (when `$checkVars` is true);
+ *
+ * @param string $name the property name
+ * @param boolean $checkVars whether to treat member variables as properties
+ * @return boolean whether the property can be written
+ * @see canGetProperty
+ */
+ public function canSetProperty($name, $checkVars = true)
+ {
+ return method_exists($this, 'set' . $name) || $checkVars && property_exists($this, $name);
+ }
+
+ /**
+ * Returns a value indicating whether a method is defined.
+ *
+ * The default implementation is a call to php function `method_exists()`.
+ * You may override this method when you implemented the php magic method `__call()`.
+ * @param string $name the property name
+ * @return boolean whether the property is defined
+ */
+ public function hasMethod($name)
+ {
+ return method_exists($this, $name);
+ }
+
+ /**
+ * Converts the object into an array.
+ * The default implementation will return all public property values as an array.
+ * @return array the array representation of the object
+ */
+ public function toArray()
+ {
+ return Yii::getObjectVars($this);
+ }
+}
diff --git a/framework/yii/base/Request.php b/framework/yii/base/Request.php
new file mode 100644
index 0000000..0d660d6
--- /dev/null
+++ b/framework/yii/base/Request.php
@@ -0,0 +1,81 @@
+
+ * @since 2.0
+ */
+abstract class Request extends Component
+{
+ private $_scriptFile;
+ private $_isConsoleRequest;
+
+ /**
+ * Resolves the current request into a route and the associated parameters.
+ * @return array the first element is the route, and the second is the associated parameters.
+ */
+ abstract public function resolve();
+
+ /**
+ * Returns a value indicating whether the current request is made via command line
+ * @return boolean the value indicating whether the current request is made via console
+ */
+ public function getIsConsoleRequest()
+ {
+ return $this->_isConsoleRequest !== null ? $this->_isConsoleRequest : PHP_SAPI === 'cli';
+ }
+
+ /**
+ * Sets the value indicating whether the current request is made via command line
+ * @param boolean $value the value indicating whether the current request is made via command line
+ */
+ public function setIsConsoleRequest($value)
+ {
+ $this->_isConsoleRequest = $value;
+ }
+
+ /**
+ * Returns entry script file path.
+ * @return string entry script file path (processed w/ realpath())
+ * @throws InvalidConfigException if the entry script file path cannot be determined automatically.
+ */
+ public function getScriptFile()
+ {
+ if ($this->_scriptFile === null) {
+ if (isset($_SERVER['SCRIPT_FILENAME'])) {
+ $this->setScriptFile($_SERVER['SCRIPT_FILENAME']);
+ } else {
+ throw new InvalidConfigException('Unable to determine the entry script file path.');
+ }
+ }
+ return $this->_scriptFile;
+ }
+
+ /**
+ * Sets the entry script file path.
+ * The entry script file path can normally be determined based on the `SCRIPT_FILENAME` SERVER variable.
+ * However, for some server configurations, this may not be correct or feasible.
+ * This setter is provided so that the entry script file path can be manually specified.
+ * @param string $value the entry script file path. This can be either a file path or a path alias.
+ * @throws InvalidConfigException if the provided entry script file path is invalid.
+ */
+ public function setScriptFile($value)
+ {
+ $scriptFile = realpath(\Yii::getAlias($value));
+ if ($scriptFile !== false && is_file($scriptFile)) {
+ $this->_scriptFile = $scriptFile;
+ } else {
+ throw new InvalidConfigException('Unable to determine the entry script file path.');
+ }
+ }
+}
diff --git a/framework/yii/base/Response.php b/framework/yii/base/Response.php
new file mode 100644
index 0000000..467de9e
--- /dev/null
+++ b/framework/yii/base/Response.php
@@ -0,0 +1,28 @@
+
+ * @since 2.0
+ */
+class Response extends Component
+{
+ /**
+ * @var integer the exit status. Exit statuses should be in the range 0 to 254.
+ * The status 0 means the program terminates successfully.
+ */
+ public $exitStatus = 0;
+
+ /**
+ * Sends the response to client.
+ */
+ public function send()
+ {
+ }
+}
diff --git a/framework/yii/base/Theme.php b/framework/yii/base/Theme.php
new file mode 100644
index 0000000..658ada1
--- /dev/null
+++ b/framework/yii/base/Theme.php
@@ -0,0 +1,128 @@
+ '/web/themes/basic')`,
+ * then the themed version for a view file `/web/views/site/index.php` will be
+ * `/web/themes/basic/site/index.php`.
+ *
+ * To use a theme, you should configure the [[View::theme|theme]] property of the "view" application
+ * component like the following:
+ *
+ * ~~~
+ * 'view' => array(
+ * 'theme' => array(
+ * 'basePath' => '@webroot/themes/basic',
+ * 'baseUrl' => '@web/themes/basic',
+ * ),
+ * ),
+ * ~~~
+ *
+ * The above configuration specifies a theme located under the "themes/basic" directory of the Web folder
+ * that contains the entry script of the application. If your theme is designed to handle modules,
+ * you may configure the [[pathMap]] property like described above.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Theme extends Component
+{
+ /**
+ * @var string the root path or path alias of this theme. All resources of this theme are located
+ * under this directory. This property must be set if [[pathMap]] is not set.
+ * @see pathMap
+ */
+ public $basePath;
+ /**
+ * @var string the base URL (or path alias) for this theme. All resources of this theme are considered
+ * to be under this base URL. This property must be set. It is mainly used by [[getUrl()]].
+ */
+ public $baseUrl;
+ /**
+ * @var array the mapping between view directories and their corresponding themed versions.
+ * If not set, it will be initialized as a mapping from [[Application::basePath]] to [[basePath]].
+ * This property is used by [[applyTo()]] when a view is trying to apply the theme.
+ * Path aliases can be used when specifying directories.
+ */
+ public $pathMap;
+
+
+ /**
+ * Initializes the theme.
+ * @throws InvalidConfigException if [[basePath]] is not set.
+ */
+ public function init()
+ {
+ parent::init();
+ if (empty($this->pathMap)) {
+ if ($this->basePath !== null) {
+ $this->basePath = Yii::getAlias($this->basePath);
+ $this->pathMap = array(Yii::$app->getBasePath() => $this->basePath);
+ } else {
+ throw new InvalidConfigException('The "basePath" property must be set.');
+ }
+ }
+ $paths = array();
+ foreach ($this->pathMap as $from => $to) {
+ $from = FileHelper::normalizePath(Yii::getAlias($from));
+ $to = FileHelper::normalizePath(Yii::getAlias($to));
+ $paths[$from . DIRECTORY_SEPARATOR] = $to . DIRECTORY_SEPARATOR;
+ }
+ $this->pathMap = $paths;
+ if ($this->baseUrl === null) {
+ throw new InvalidConfigException('The "baseUrl" property must be set.');
+ } else {
+ $this->baseUrl = rtrim(Yii::getAlias($this->baseUrl), '/');
+ }
+ }
+
+ /**
+ * Converts a file to a themed file if possible.
+ * If there is no corresponding themed file, the original file will be returned.
+ * @param string $path the file to be themed
+ * @return string the themed file, or the original file if the themed version is not available.
+ */
+ public function applyTo($path)
+ {
+ $path = FileHelper::normalizePath($path);
+ foreach ($this->pathMap as $from => $to) {
+ if (strpos($path, $from) === 0) {
+ $n = strlen($from);
+ $file = $to . substr($path, $n);
+ if (is_file($file)) {
+ return $file;
+ }
+ }
+ }
+ return $path;
+ }
+
+ /**
+ * Converts a relative URL into an absolute URL using [[baseUrl]].
+ * @param string $url the relative URL to be converted.
+ * @return string the absolute URL
+ */
+ public function getUrl($url)
+ {
+ return $this->baseUrl . '/' . ltrim($url, '/');
+ }
+}
diff --git a/framework/yii/base/UnknownClassException.php b/framework/yii/base/UnknownClassException.php
new file mode 100644
index 0000000..8ccd09c
--- /dev/null
+++ b/framework/yii/base/UnknownClassException.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class UnknownClassException extends Exception
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Unknown Class');
+ }
+}
diff --git a/framework/yii/base/UnknownMethodException.php b/framework/yii/base/UnknownMethodException.php
new file mode 100644
index 0000000..3b33659
--- /dev/null
+++ b/framework/yii/base/UnknownMethodException.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class UnknownMethodException extends Exception
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Unknown Method');
+ }
+}
diff --git a/framework/yii/base/UnknownPropertyException.php b/framework/yii/base/UnknownPropertyException.php
new file mode 100644
index 0000000..682fdfa
--- /dev/null
+++ b/framework/yii/base/UnknownPropertyException.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class UnknownPropertyException extends Exception
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Unknown Property');
+ }
+}
diff --git a/yii/base/UserException.php b/framework/yii/base/UserException.php
similarity index 100%
rename from yii/base/UserException.php
rename to framework/yii/base/UserException.php
diff --git a/framework/yii/base/View.php b/framework/yii/base/View.php
new file mode 100644
index 0000000..f9cae98
--- /dev/null
+++ b/framework/yii/base/View.php
@@ -0,0 +1,816 @@
+
+ * @since 2.0
+ */
+class View extends Component
+{
+ /**
+ * @event Event an event that is triggered by [[beginPage()]].
+ */
+ const EVENT_BEGIN_PAGE = 'beginPage';
+ /**
+ * @event Event an event that is triggered by [[endPage()]].
+ */
+ const EVENT_END_PAGE = 'endPage';
+ /**
+ * @event Event an event that is triggered by [[beginBody()]].
+ */
+ const EVENT_BEGIN_BODY = 'beginBody';
+ /**
+ * @event Event an event that is triggered by [[endBody()]].
+ */
+ const EVENT_END_BODY = 'endBody';
+ /**
+ * @event ViewEvent an event that is triggered by [[renderFile()]] right before it renders a view file.
+ */
+ const EVENT_BEFORE_RENDER = 'beforeRender';
+ /**
+ * @event ViewEvent an event that is triggered by [[renderFile()]] right after it renders a view file.
+ */
+ const EVENT_AFTER_RENDER = 'afterRender';
+
+ /**
+ * The location of registered JavaScript code block or files.
+ * This means the location is in the head section.
+ */
+ const POS_HEAD = 1;
+ /**
+ * The location of registered JavaScript code block or files.
+ * This means the location is at the beginning of the body section.
+ */
+ const POS_BEGIN = 2;
+ /**
+ * The location of registered JavaScript code block or files.
+ * This means the location is at the end of the body section.
+ */
+ const POS_END = 3;
+ /**
+ * The location of registered JavaScript code block.
+ * This means the JavaScript code block will be enclosed within `jQuery(document).ready()`.
+ */
+ const POS_READY = 4;
+ /**
+ * This is internally used as the placeholder for receiving the content registered for the head section.
+ */
+ const PH_HEAD = '';
+ /**
+ * This is internally used as the placeholder for receiving the content registered for the beginning of the body section.
+ */
+ const PH_BODY_BEGIN = '';
+ /**
+ * This is internally used as the placeholder for receiving the content registered for the end of the body section.
+ */
+ const PH_BODY_END = '';
+
+
+ /**
+ * @var object the context under which the [[renderFile()]] method is being invoked.
+ * This can be a controller, a widget, or any other object.
+ */
+ public $context;
+ /**
+ * @var mixed custom parameters that are shared among view templates.
+ */
+ public $params = array();
+ /**
+ * @var array a list of available renderers indexed by their corresponding supported file extensions.
+ * Each renderer may be a view renderer object or the configuration for creating the renderer object.
+ * For example, the following configuration enables both Smarty and Twig view renderers:
+ *
+ * ~~~
+ * array(
+ * 'tpl' => array(
+ * 'class' => 'yii\smarty\ViewRenderer',
+ * ),
+ * 'twig' => array(
+ * 'class' => 'yii\twig\ViewRenderer',
+ * ),
+ * )
+ * ~~~
+ *
+ * If no renderer is available for the given view file, the view file will be treated as a normal PHP
+ * and rendered via [[renderPhpFile()]].
+ */
+ public $renderers;
+ /**
+ * @var Theme|array the theme object or the configuration array for creating the theme object.
+ * If not set, it means theming is not enabled.
+ */
+ public $theme;
+ /**
+ * @var array a list of named output blocks. The keys are the block names and the values
+ * are the corresponding block content. You can call [[beginBlock()]] and [[endBlock()]]
+ * to capture small fragments of a view. They can be later accessed somewhere else
+ * through this property.
+ */
+ public $blocks;
+ /**
+ * @var array a list of currently active fragment cache widgets. This property
+ * is used internally to implement the content caching feature. Do not modify it directly.
+ * @internal
+ */
+ public $cacheStack = array();
+ /**
+ * @var array a list of placeholders for embedding dynamic contents. This property
+ * is used internally to implement the content caching feature. Do not modify it directly.
+ * @internal
+ */
+ public $dynamicPlaceholders = array();
+ /**
+ * @var AssetBundle[] list of the registered asset bundles. The keys are the bundle names, and the values
+ * are the registered [[AssetBundle]] objects.
+ * @see registerAssetBundle
+ */
+ public $assetBundles = array();
+ /**
+ * @var string the page title
+ */
+ public $title;
+ /**
+ * @var array the registered meta tags.
+ * @see registerMetaTag
+ */
+ public $metaTags;
+ /**
+ * @var array the registered link tags.
+ * @see registerLinkTag
+ */
+ public $linkTags;
+ /**
+ * @var array the registered CSS code blocks.
+ * @see registerCss
+ */
+ public $css;
+ /**
+ * @var array the registered CSS files.
+ * @see registerCssFile
+ */
+ public $cssFiles;
+ /**
+ * @var array the registered JS code blocks
+ * @see registerJs
+ */
+ public $js;
+ /**
+ * @var array the registered JS files.
+ * @see registerJsFile
+ */
+ public $jsFiles;
+
+
+ /**
+ * Initializes the view component.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_array($this->theme)) {
+ if (!isset($this->theme['class'])) {
+ $this->theme['class'] = 'yii\base\Theme';
+ }
+ $this->theme = Yii::createObject($this->theme);
+ }
+ }
+
+ /**
+ * Renders a view.
+ *
+ * This method delegates the call to the [[context]] object:
+ *
+ * - If [[context]] is a controller, the [[Controller::renderPartial()]] method will be called;
+ * - If [[context]] is a widget, the [[Widget::render()]] method will be called;
+ * - Otherwise, an InvalidCallException exception will be thrown.
+ *
+ * @param string $view the view name. Please refer to [[Controller::findViewFile()]]
+ * and [[Widget::findViewFile()]] on how to specify this parameter.
+ * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
+ * @return string the rendering result
+ * @throws InvalidCallException if [[context]] is neither a controller nor a widget.
+ * @throws InvalidParamException if the view cannot be resolved or the view file does not exist.
+ * @see renderFile
+ */
+ public function render($view, $params = array())
+ {
+ if ($this->context instanceof Controller) {
+ return $this->context->renderPartial($view, $params);
+ } elseif ($this->context instanceof Widget) {
+ return $this->context->render($view, $params);
+ } else {
+ throw new InvalidCallException('View::render() is not supported for the current context.');
+ }
+ }
+
+ /**
+ * Renders a view file.
+ *
+ * If [[theme]] is enabled (not null), it will try to render the themed version of the view file as long
+ * as it is available.
+ *
+ * The method will call [[FileHelper::localize()]] to localize the view file.
+ *
+ * If [[renderer]] is enabled (not null), the method will use it to render the view file.
+ * Otherwise, it will simply include the view file as a normal PHP file, capture its output and
+ * return it as a string.
+ *
+ * @param string $viewFile the view file. This can be either a file path or a path alias.
+ * @param array $params the parameters (name-value pairs) that will be extracted and made available in the view file.
+ * @param object $context the context that the view should use for rendering the view. If null,
+ * existing [[context]] will be used.
+ * @return string the rendering result
+ * @throws InvalidParamException if the view file does not exist
+ */
+ public function renderFile($viewFile, $params = array(), $context = null)
+ {
+ $viewFile = Yii::getAlias($viewFile);
+ if ($this->theme !== null) {
+ $viewFile = $this->theme->applyTo($viewFile);
+ }
+ if (is_file($viewFile)) {
+ $viewFile = FileHelper::localize($viewFile);
+ } else {
+ throw new InvalidParamException("The view file does not exist: $viewFile");
+ }
+
+ $oldContext = $this->context;
+ if ($context !== null) {
+ $this->context = $context;
+ }
+
+ $output = '';
+ if ($this->beforeRender($viewFile)) {
+ $ext = pathinfo($viewFile, PATHINFO_EXTENSION);
+ if (isset($this->renderers[$ext])) {
+ if (is_array($this->renderers[$ext])) {
+ $this->renderers[$ext] = Yii::createObject($this->renderers[$ext]);
+ }
+ /** @var ViewRenderer $renderer */
+ $renderer = $this->renderers[$ext];
+ $output = $renderer->render($this, $viewFile, $params);
+ } else {
+ Yii::trace("Rendering view file: $viewFile", __METHOD__);
+ $output = $this->renderPhpFile($viewFile, $params);
+ }
+ $this->afterRender($viewFile, $output);
+ }
+
+ $this->context = $oldContext;
+
+ return $output;
+ }
+
+ /**
+ * This method is invoked right before [[renderFile()]] renders a view file.
+ * The default implementation will trigger the [[EVENT_BEFORE_RENDER]] event.
+ * If you override this method, make sure you call the parent implementation first.
+ * @param string $viewFile the view file to be rendered
+ * @return boolean whether to continue rendering the view file.
+ */
+ public function beforeRender($viewFile)
+ {
+ $event = new ViewEvent($viewFile);
+ $this->trigger(self::EVENT_BEFORE_RENDER, $event);
+ return $event->isValid;
+ }
+
+ /**
+ * This method is invoked right after [[renderFile()]] renders a view file.
+ * The default implementation will trigger the [[EVENT_AFTER_RENDER]] event.
+ * If you override this method, make sure you call the parent implementation first.
+ * @param string $viewFile the view file to be rendered
+ * @param string $output the rendering result of the view file. Updates to this parameter
+ * will be passed back and returned by [[renderFile()]].
+ */
+ public function afterRender($viewFile, &$output)
+ {
+ if ($this->hasEventHandlers(self::EVENT_AFTER_RENDER)) {
+ $event = new ViewEvent($viewFile);
+ $event->output = $output;
+ $this->trigger(self::EVENT_AFTER_RENDER, $event);
+ $output = $event->output;
+ }
+ }
+
+ /**
+ * Renders a view file as a PHP script.
+ *
+ * This method treats the view file as a PHP script and includes the file.
+ * It extracts the given parameters and makes them available in the view file.
+ * The method captures the output of the included view file and returns it as a string.
+ *
+ * This method should mainly be called by view renderer or [[renderFile()]].
+ *
+ * @param string $_file_ the view file.
+ * @param array $_params_ the parameters (name-value pairs) that will be extracted and made available in the view file.
+ * @return string the rendering result
+ */
+ public function renderPhpFile($_file_, $_params_ = array())
+ {
+ ob_start();
+ ob_implicit_flush(false);
+ extract($_params_, EXTR_OVERWRITE);
+ require($_file_);
+ return ob_get_clean();
+ }
+
+ /**
+ * Renders dynamic content returned by the given PHP statements.
+ * This method is mainly used together with content caching (fragment caching and page caching)
+ * when some portions of the content (called *dynamic content*) should not be cached.
+ * The dynamic content must be returned by some PHP statements.
+ * @param string $statements the PHP statements for generating the dynamic content.
+ * @return string the placeholder of the dynamic content, or the dynamic content if there is no
+ * active content cache currently.
+ */
+ public function renderDynamic($statements)
+ {
+ if (!empty($this->cacheStack)) {
+ $n = count($this->dynamicPlaceholders);
+ $placeholder = "";
+ $this->addDynamicPlaceholder($placeholder, $statements);
+ return $placeholder;
+ } else {
+ return $this->evaluateDynamicContent($statements);
+ }
+ }
+
+ /**
+ * Adds a placeholder for dynamic content.
+ * This method is internally used.
+ * @param string $placeholder the placeholder name
+ * @param string $statements the PHP statements for generating the dynamic content
+ */
+ public function addDynamicPlaceholder($placeholder, $statements)
+ {
+ foreach ($this->cacheStack as $cache) {
+ $cache->dynamicPlaceholders[$placeholder] = $statements;
+ }
+ $this->dynamicPlaceholders[$placeholder] = $statements;
+ }
+
+ /**
+ * Evaluates the given PHP statements.
+ * This method is mainly used internally to implement dynamic content feature.
+ * @param string $statements the PHP statements to be evaluated.
+ * @return mixed the return value of the PHP statements.
+ */
+ public function evaluateDynamicContent($statements)
+ {
+ return eval($statements);
+ }
+
+ /**
+ * Begins recording a block.
+ * This method is a shortcut to beginning [[Block]]
+ * @param string $id the block ID.
+ * @param boolean $renderInPlace whether to render the block content in place.
+ * Defaults to false, meaning the captured block will not be displayed.
+ * @return Block the Block widget instance
+ */
+ public function beginBlock($id, $renderInPlace = false)
+ {
+ return Block::begin(array(
+ 'id' => $id,
+ 'renderInPlace' => $renderInPlace,
+ 'view' => $this,
+ ));
+ }
+
+ /**
+ * Ends recording a block.
+ */
+ public function endBlock()
+ {
+ Block::end();
+ }
+
+ /**
+ * Begins the rendering of content that is to be decorated by the specified view.
+ * This method can be used to implement nested layout. For example, a layout can be embedded
+ * in another layout file specified as '@app/view/layouts/base.php' like the following:
+ *
+ * ~~~
+ * beginContent('@app/view/layouts/base.php'); ?>
+ * ...layout content here...
+ * endContent(); ?>
+ * ~~~
+ *
+ * @param string $viewFile the view file that will be used to decorate the content enclosed by this widget.
+ * This can be specified as either the view file path or path alias.
+ * @param array $params the variables (name => value) to be extracted and made available in the decorative view.
+ * @return ContentDecorator the ContentDecorator widget instance
+ * @see ContentDecorator
+ */
+ public function beginContent($viewFile, $params = array())
+ {
+ return ContentDecorator::begin(array(
+ 'viewFile' => $viewFile,
+ 'params' => $params,
+ 'view' => $this,
+ ));
+ }
+
+ /**
+ * Ends the rendering of content.
+ */
+ public function endContent()
+ {
+ ContentDecorator::end();
+ }
+
+ /**
+ * Begins fragment caching.
+ * This method will display cached content if it is available.
+ * If not, it will start caching and would expect an [[endCache()]]
+ * call to end the cache and save the content into cache.
+ * A typical usage of fragment caching is as follows,
+ *
+ * ~~~
+ * if ($this->beginCache($id)) {
+ * // ...generate content here
+ * $this->endCache();
+ * }
+ * ~~~
+ *
+ * @param string $id a unique ID identifying the fragment to be cached.
+ * @param array $properties initial property values for [[FragmentCache]]
+ * @return boolean whether you should generate the content for caching.
+ * False if the cached version is available.
+ */
+ public function beginCache($id, $properties = array())
+ {
+ $properties['id'] = $id;
+ $properties['view'] = $this;
+ /** @var $cache FragmentCache */
+ $cache = FragmentCache::begin($properties);
+ if ($cache->getCachedContent() !== false) {
+ $this->endCache();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Ends fragment caching.
+ */
+ public function endCache()
+ {
+ FragmentCache::end();
+ }
+
+
+ private $_assetManager;
+
+ /**
+ * Registers the asset manager being used by this view object.
+ * @return \yii\web\AssetManager the asset manager. Defaults to the "assetManager" application component.
+ */
+ public function getAssetManager()
+ {
+ return $this->_assetManager ?: Yii::$app->getAssetManager();
+ }
+
+ /**
+ * Sets the asset manager.
+ * @param \yii\web\AssetManager $value the asset manager
+ */
+ public function setAssetManager($value)
+ {
+ $this->_assetManager = $value;
+ }
+
+ /**
+ * Marks the beginning of an HTML page.
+ */
+ public function beginPage()
+ {
+ ob_start();
+ ob_implicit_flush(false);
+
+ $this->trigger(self::EVENT_BEGIN_PAGE);
+ }
+
+ /**
+ * Marks the ending of an HTML page.
+ */
+ public function endPage()
+ {
+ $this->trigger(self::EVENT_END_PAGE);
+
+ $content = ob_get_clean();
+ foreach(array_keys($this->assetBundles) as $bundle) {
+ $this->registerAssetFiles($bundle);
+ }
+ echo strtr($content, array(
+ self::PH_HEAD => $this->renderHeadHtml(),
+ self::PH_BODY_BEGIN => $this->renderBodyBeginHtml(),
+ self::PH_BODY_END => $this->renderBodyEndHtml(),
+ ));
+
+ unset(
+ $this->metaTags,
+ $this->linkTags,
+ $this->css,
+ $this->cssFiles,
+ $this->js,
+ $this->jsFiles
+ );
+ }
+
+ /**
+ * Registers all files provided by an asset bundle including depending bundles files.
+ * Removes a bundle from [[assetBundles]] once registered.
+ * @param string $name name of the bundle to register
+ */
+ private function registerAssetFiles($name)
+ {
+ if (!isset($this->assetBundles[$name])) {
+ return;
+ }
+ $bundle = $this->assetBundles[$name];
+ foreach($bundle->depends as $dep) {
+ $this->registerAssetFiles($dep);
+ }
+ $bundle->registerAssets($this);
+ unset($this->assetBundles[$name]);
+ }
+
+ /**
+ * Marks the beginning of an HTML body section.
+ */
+ public function beginBody()
+ {
+ echo self::PH_BODY_BEGIN;
+ $this->trigger(self::EVENT_BEGIN_BODY);
+ }
+
+ /**
+ * Marks the ending of an HTML body section.
+ */
+ public function endBody()
+ {
+ $this->trigger(self::EVENT_END_BODY);
+ echo self::PH_BODY_END;
+ }
+
+ /**
+ * Marks the position of an HTML head section.
+ */
+ public function head()
+ {
+ echo self::PH_HEAD;
+ }
+
+ /**
+ * Registers the named asset bundle.
+ * All dependent asset bundles will be registered.
+ * @param string $name the name of the asset bundle.
+ * @param integer|null $position if set, this forces a minimum position for javascript files.
+ * This will adjust depending assets javascript file position or fail if requirement can not be met.
+ * If this is null, asset bundles position settings will not be changed.
+ * See [[registerJsFile]] for more details on javascript position.
+ * @return AssetBundle the registered asset bundle instance
+ * @throws InvalidConfigException if the asset bundle does not exist or a circular dependency is detected
+ */
+ public function registerAssetBundle($name, $position = null)
+ {
+ if (!isset($this->assetBundles[$name])) {
+ $am = $this->getAssetManager();
+ $bundle = $am->getBundle($name);
+ $this->assetBundles[$name] = false;
+ // register dependencies
+ $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null;
+ foreach ($bundle->depends as $dep) {
+ $this->registerAssetBundle($dep, $pos);
+ }
+ $this->assetBundles[$name] = $bundle;
+ } elseif ($this->assetBundles[$name] === false) {
+ throw new InvalidConfigException("A circular dependency is detected for bundle '$name'.");
+ } else {
+ $bundle = $this->assetBundles[$name];
+ }
+
+ if ($position !== null) {
+ $pos = isset($bundle->jsOptions['position']) ? $bundle->jsOptions['position'] : null;
+ if ($pos === null) {
+ $bundle->jsOptions['position'] = $pos = $position;
+ } elseif ($pos > $position) {
+ throw new InvalidConfigException("An asset bundle that depends on '$name' has a higher javascript file position configured than '$name'.");
+ }
+ // update position for all dependencies
+ foreach ($bundle->depends as $dep) {
+ $this->registerAssetBundle($dep, $pos);
+ }
+ }
+ return $bundle;
+ }
+
+ /**
+ * Registers a meta tag.
+ * @param array $options the HTML attributes for the meta tag.
+ * @param string $key the key that identifies the meta tag. If two meta tags are registered
+ * with the same key, the latter will overwrite the former. If this is null, the new meta tag
+ * will be appended to the existing ones.
+ */
+ public function registerMetaTag($options, $key = null)
+ {
+ if ($key === null) {
+ $this->metaTags[] = Html::tag('meta', '', $options);
+ } else {
+ $this->metaTags[$key] = Html::tag('meta', '', $options);
+ }
+ }
+
+ /**
+ * Registers a link tag.
+ * @param array $options the HTML attributes for the link tag.
+ * @param string $key the key that identifies the link tag. If two link tags are registered
+ * with the same key, the latter will overwrite the former. If this is null, the new link tag
+ * will be appended to the existing ones.
+ */
+ public function registerLinkTag($options, $key = null)
+ {
+ if ($key === null) {
+ $this->linkTags[] = Html::tag('link', '', $options);
+ } else {
+ $this->linkTags[$key] = Html::tag('link', '', $options);
+ }
+ }
+
+ /**
+ * Registers a CSS code block.
+ * @param string $css the CSS code block to be registered
+ * @param array $options the HTML attributes for the style tag.
+ * @param string $key the key that identifies the CSS code block. If null, it will use
+ * $css as the key. If two CSS code blocks are registered with the same key, the latter
+ * will overwrite the former.
+ */
+ public function registerCss($css, $options = array(), $key = null)
+ {
+ $key = $key ?: md5($css);
+ $this->css[$key] = Html::style($css, $options);
+ }
+
+ /**
+ * Registers a CSS file.
+ * @param string $url the CSS file to be registered.
+ * @param array $options the HTML attributes for the link tag.
+ * @param string $key the key that identifies the CSS script file. If null, it will use
+ * $url as the key. If two CSS files are registered with the same key, the latter
+ * will overwrite the former.
+ */
+ public function registerCssFile($url, $options = array(), $key = null)
+ {
+ $key = $key ?: $url;
+ $this->cssFiles[$key] = Html::cssFile($url, $options);
+ }
+
+ /**
+ * Registers a JS code block.
+ * @param string $js the JS code block to be registered
+ * @param integer $position the position at which the JS script tag should be inserted
+ * in a page. The possible values are:
+ *
+ * - [[POS_HEAD]]: in the head section
+ * - [[POS_BEGIN]]: at the beginning of the body section
+ * - [[POS_END]]: at the end of the body section
+ * - [[POS_READY]]: enclosed within jQuery(document).ready(). This is the default value.
+ * Note that by using this position, the method will automatically register the jQuery js file.
+ *
+ * @param string $key the key that identifies the JS code block. If null, it will use
+ * $js as the key. If two JS code blocks are registered with the same key, the latter
+ * will overwrite the former.
+ */
+ public function registerJs($js, $position = self::POS_READY, $key = null)
+ {
+ $key = $key ?: md5($js);
+ $this->js[$position][$key] = $js;
+ if ($position === self::POS_READY) {
+ JqueryAsset::register($this);
+ }
+ }
+
+ /**
+ * Registers a JS file.
+ * Please note that when this file depends on other JS files to be registered before,
+ * for example jQuery, you should use [[registerAssetBundle]] instead.
+ * @param string $url the JS file to be registered.
+ * @param array $options the HTML attributes for the script tag. A special option
+ * named "position" is supported which specifies where the JS script tag should be inserted
+ * in a page. The possible values of "position" are:
+ *
+ * - [[POS_HEAD]]: in the head section
+ * - [[POS_BEGIN]]: at the beginning of the body section
+ * - [[POS_END]]: at the end of the body section. This is the default value.
+ *
+ * @param string $key the key that identifies the JS script file. If null, it will use
+ * $url as the key. If two JS files are registered with the same key, the latter
+ * will overwrite the former.
+ */
+ public function registerJsFile($url, $options = array(), $key = null)
+ {
+ $position = isset($options['position']) ? $options['position'] : self::POS_END;
+ unset($options['position']);
+ $key = $key ?: $url;
+ $this->jsFiles[$position][$key] = Html::jsFile($url, $options);
+ }
+
+ /**
+ * Renders the content to be inserted in the head section.
+ * The content is rendered using the registered meta tags, link tags, CSS/JS code blocks and files.
+ * @return string the rendered content
+ */
+ protected function renderHeadHtml()
+ {
+ $lines = array();
+ if (!empty($this->metaTags)) {
+ $lines[] = implode("\n", $this->metaTags);
+ }
+
+ $request = Yii::$app->getRequest();
+ if ($request instanceof \yii\web\Request && $request->enableCsrfValidation) {
+ $lines[] = Html::tag('meta', '', array('name' => 'csrf-var', 'content' => $request->csrfVar));
+ $lines[] = Html::tag('meta', '', array('name' => 'csrf-token', 'content' => $request->getCsrfToken()));
+ }
+
+ if (!empty($this->linkTags)) {
+ $lines[] = implode("\n", $this->linkTags);
+ }
+ if (!empty($this->cssFiles)) {
+ $lines[] = implode("\n", $this->cssFiles);
+ }
+ if (!empty($this->css)) {
+ $lines[] = implode("\n", $this->css);
+ }
+ if (!empty($this->jsFiles[self::POS_HEAD])) {
+ $lines[] = implode("\n", $this->jsFiles[self::POS_HEAD]);
+ }
+ if (!empty($this->js[self::POS_HEAD])) {
+ $lines[] = Html::script(implode("\n", $this->js[self::POS_HEAD]), array('type' => 'text/javascript'));
+ }
+ return empty($lines) ? '' : implode("\n", $lines);
+ }
+
+ /**
+ * Renders the content to be inserted at the beginning of the body section.
+ * The content is rendered using the registered JS code blocks and files.
+ * @return string the rendered content
+ */
+ protected function renderBodyBeginHtml()
+ {
+ $lines = array();
+ if (!empty($this->jsFiles[self::POS_BEGIN])) {
+ $lines[] = implode("\n", $this->jsFiles[self::POS_BEGIN]);
+ }
+ if (!empty($this->js[self::POS_BEGIN])) {
+ $lines[] = Html::script(implode("\n", $this->js[self::POS_BEGIN]), array('type' => 'text/javascript'));
+ }
+ return empty($lines) ? '' : implode("\n", $lines);
+ }
+
+ /**
+ * Renders the content to be inserted at the end of the body section.
+ * The content is rendered using the registered JS code blocks and files.
+ * @return string the rendered content
+ */
+ protected function renderBodyEndHtml()
+ {
+ $lines = array();
+ if (!empty($this->jsFiles[self::POS_END])) {
+ $lines[] = implode("\n", $this->jsFiles[self::POS_END]);
+ }
+ if (!empty($this->js[self::POS_END])) {
+ $lines[] = Html::script(implode("\n", $this->js[self::POS_END]), array('type' => 'text/javascript'));
+ }
+ if (!empty($this->js[self::POS_READY])) {
+ $js = "jQuery(document).ready(function(){\n" . implode("\n", $this->js[self::POS_READY]) . "\n});";
+ $lines[] = Html::script($js, array('type' => 'text/javascript'));
+ }
+ return empty($lines) ? '' : implode("\n", $lines);
+ }
+}
diff --git a/yii/base/ViewEvent.php b/framework/yii/base/ViewEvent.php
similarity index 100%
rename from yii/base/ViewEvent.php
rename to framework/yii/base/ViewEvent.php
diff --git a/yii/base/ViewRenderer.php b/framework/yii/base/ViewRenderer.php
similarity index 100%
rename from yii/base/ViewRenderer.php
rename to framework/yii/base/ViewRenderer.php
diff --git a/framework/yii/base/Widget.php b/framework/yii/base/Widget.php
new file mode 100644
index 0000000..943d007
--- /dev/null
+++ b/framework/yii/base/Widget.php
@@ -0,0 +1,221 @@
+
+ * @since 2.0
+ */
+class Widget extends Component
+{
+ /**
+ * @var integer a counter used to generate [[id]] for widgets.
+ * @internal
+ */
+ public static $_counter = 0;
+ /**
+ * @var Widget[] the widgets that are currently being rendered (not ended). This property
+ * is maintained by [[begin()]] and [[end()]] methods.
+ * @internal
+ */
+ public static $_stack = array();
+
+
+ /**
+ * Begins a widget.
+ * This method creates an instance of the calling class. It will apply the configuration
+ * to the created instance. A matching [[end()]] call should be called later.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ * @return Widget the newly created widget instance
+ */
+ public static function begin($config = array())
+ {
+ $config['class'] = get_called_class();
+ /** @var Widget $widget */
+ $widget = Yii::createObject($config);
+ self::$_stack[] = $widget;
+ return $widget;
+ }
+
+ /**
+ * Ends a widget.
+ * Note that the rendering result of the widget is directly echoed out.
+ * @return Widget the widget instance that is ended.
+ * @throws InvalidCallException if [[begin()]] and [[end()]] calls are not properly nested
+ */
+ public static function end()
+ {
+ if (!empty(self::$_stack)) {
+ $widget = array_pop(self::$_stack);
+ if (get_class($widget) === get_called_class()) {
+ $widget->run();
+ return $widget;
+ } else {
+ throw new InvalidCallException("Expecting end() of " . get_class($widget) . ", found " . get_called_class());
+ }
+ } else {
+ throw new InvalidCallException("Unexpected " . get_called_class() . '::end() call. A matching begin() is not found.');
+ }
+ }
+
+ /**
+ * Creates a widget instance and runs it.
+ * The widget rendering result is returned by this method.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ * @return string the rendering result of the widget.
+ */
+ public static function widget($config = array())
+ {
+ ob_start();
+ ob_implicit_flush(false);
+ /** @var Widget $widget */
+ $config['class'] = get_called_class();
+ $widget = Yii::createObject($config);
+ $widget->run();
+ return ob_get_clean();
+ }
+
+ private $_id;
+
+ /**
+ * Returns the ID of the widget.
+ * @param boolean $autoGenerate whether to generate an ID if it is not set previously
+ * @return string ID of the widget.
+ */
+ public function getId($autoGenerate = true)
+ {
+ if ($autoGenerate && $this->_id === null) {
+ $this->_id = 'w' . self::$_counter++;
+ }
+ return $this->_id;
+ }
+
+ /**
+ * Sets the ID of the widget.
+ * @param string $value id of the widget.
+ */
+ public function setId($value)
+ {
+ $this->_id = $value;
+ }
+
+ private $_view;
+
+ /**
+ * Returns the view object that can be used to render views or view files.
+ * The [[render()]] and [[renderFile()]] methods will use
+ * this view object to implement the actual view rendering.
+ * If not set, it will default to the "view" application component.
+ * @return View the view object that can be used to render views or view files.
+ */
+ public function getView()
+ {
+ if ($this->_view === null) {
+ $this->_view = Yii::$app->getView();
+ }
+ return $this->_view;
+ }
+
+ /**
+ * Sets the view object to be used by this widget.
+ * @param View $view the view object that can be used to render views or view files.
+ */
+ public function setView($view)
+ {
+ $this->_view = $view;
+ }
+
+ /**
+ * Executes the widget.
+ */
+ public function run()
+ {
+ }
+
+ /**
+ * Renders a view.
+ * The view to be rendered can be specified in one of the following formats:
+ *
+ * - path alias (e.g. "@app/views/site/index");
+ * - absolute path within application (e.g. "//site/index"): the view name starts with double slashes.
+ * The actual view file will be looked for under the [[Application::viewPath|view path]] of the application.
+ * - absolute path within module (e.g. "/site/index"): the view name starts with a single slash.
+ * The actual view file will be looked for under the [[Module::viewPath|view path]] of the currently
+ * active module.
+ * - relative path (e.g. "index"): the actual view file will be looked for under [[viewPath]].
+ *
+ * If the view name does not contain a file extension, it will use the default one `.php`.
+
+ * @param string $view the view name. Please refer to [[findViewFile()]] on how to specify a view name.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file does not exist.
+ */
+ public function render($view, $params = array())
+ {
+ $viewFile = $this->findViewFile($view);
+ return $this->getView()->renderFile($viewFile, $params, $this);
+ }
+
+ /**
+ * Renders a view file.
+ * @param string $file the view file to be rendered. This can be either a file path or a path alias.
+ * @param array $params the parameters (name-value pairs) that should be made available in the view.
+ * @return string the rendering result.
+ * @throws InvalidParamException if the view file does not exist.
+ */
+ public function renderFile($file, $params = array())
+ {
+ return $this->getView()->renderFile($file, $params, $this);
+ }
+
+ /**
+ * Returns the directory containing the view files for this widget.
+ * The default implementation returns the 'views' subdirectory under the directory containing the widget class file.
+ * @return string the directory containing the view files for this widget.
+ */
+ public function getViewPath()
+ {
+ $className = get_class($this);
+ $class = new \ReflectionClass($className);
+ return dirname($class->getFileName()) . DIRECTORY_SEPARATOR . 'views';
+ }
+
+ /**
+ * Finds the view file based on the given view name.
+ * @param string $view the view name or the path alias of the view file. Please refer to [[render()]]
+ * on how to specify this parameter.
+ * @return string the view file path. Note that the file may not exist.
+ */
+ protected function findViewFile($view)
+ {
+ if (strncmp($view, '@', 1) === 0) {
+ // e.g. "@app/views/main"
+ $file = Yii::getAlias($view);
+ } elseif (strncmp($view, '//', 2) === 0) {
+ // e.g. "//layouts/main"
+ $file = Yii::$app->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } elseif (strncmp($view, '/', 1) === 0 && Yii::$app->controller !== null) {
+ // e.g. "/site/index"
+ $file = Yii::$app->controller->module->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ } else {
+ $file = $this->getViewPath() . DIRECTORY_SEPARATOR . ltrim($view, '/');
+ }
+
+ return pathinfo($file, PATHINFO_EXTENSION) === '' ? $file . '.php' : $file;
+ }
+}
diff --git a/framework/yii/behaviors/AutoTimestamp.php b/framework/yii/behaviors/AutoTimestamp.php
new file mode 100644
index 0000000..63a3ef0
--- /dev/null
+++ b/framework/yii/behaviors/AutoTimestamp.php
@@ -0,0 +1,104 @@
+ array(
+ * 'class' => 'yii\behaviors\AutoTimestamp',
+ * ),
+ * );
+ * }
+ * ~~~
+ *
+ * By default, AutoTimestamp will fill the `create_time` attribute with the current timestamp
+ * when the associated AR object is being inserted; it will fill the `update_time` attribute
+ * with the timestamp when the AR object is being updated.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class AutoTimestamp extends Behavior
+{
+ /**
+ * @var array list of attributes that are to be automatically filled with timestamps.
+ * The array keys are the ActiveRecord events upon which the attributes are to be filled with timestamps,
+ * and the array values are the corresponding attribute to be updated. You can use a string to represent
+ * a single attribute, or an array to represent a list of attributes.
+ * The default setting is to update the `create_time` attribute upon AR insertion,
+ * and update the `update_time` attribute upon AR updating.
+ */
+ public $attributes = array(
+ ActiveRecord::EVENT_BEFORE_INSERT => 'create_time',
+ ActiveRecord::EVENT_BEFORE_UPDATE => 'update_time',
+ );
+ /**
+ * @var \Closure|Expression The expression that will be used for generating the timestamp.
+ * This can be either an anonymous function that returns the timestamp value,
+ * or an [[Expression]] object representing a DB expression (e.g. `new Expression('NOW()')`).
+ * If not set, it will use the value of `time()` to fill the attributes.
+ */
+ public $timestamp;
+
+
+ /**
+ * Declares event handlers for the [[owner]]'s events.
+ * @return array events (array keys) and the corresponding event handler methods (array values).
+ */
+ public function events()
+ {
+ $events = $this->attributes;
+ foreach ($events as $i => $event) {
+ $events[$i] = 'updateTimestamp';
+ }
+ return $events;
+ }
+
+ /**
+ * Updates the attributes with the current timestamp.
+ * @param Event $event
+ */
+ public function updateTimestamp($event)
+ {
+ $attributes = isset($this->attributes[$event->name]) ? (array)$this->attributes[$event->name] : array();
+ if (!empty($attributes)) {
+ $timestamp = $this->evaluateTimestamp();
+ foreach ($attributes as $attribute) {
+ $this->owner->$attribute = $timestamp;
+ }
+ }
+ }
+
+ /**
+ * Gets the current timestamp.
+ * @return mixed the timestamp value
+ */
+ protected function evaluateTimestamp()
+ {
+ if ($this->timestamp instanceof Expression) {
+ return $this->timestamp;
+ } elseif ($this->timestamp !== null) {
+ return call_user_func($this->timestamp);
+ } else {
+ return time();
+ }
+ }
+}
diff --git a/framework/yii/bootstrap/Alert.php b/framework/yii/bootstrap/Alert.php
new file mode 100644
index 0000000..d57bcbe
--- /dev/null
+++ b/framework/yii/bootstrap/Alert.php
@@ -0,0 +1,153 @@
+ 'Say hello...',
+ * 'closeButton' => array(
+ * 'label' => '×',
+ * 'tag' => 'a',
+ * ),
+ * ));
+ * ```
+ *
+ * The following example will show the content enclosed between the [[begin()]]
+ * and [[end()]] calls within the alert box:
+ *
+ * ```php
+ * Alert::begin(array(
+ * 'closeButton' => array(
+ * 'label' => '×',
+ * ),
+ * ));
+ *
+ * echo 'Say hello...';
+ *
+ * Alert::end();
+ * ```
+ *
+ * @see http://twitter.github.io/bootstrap/javascript.html#alerts
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class Alert extends Widget
+{
+ /**
+ * @var string the body content in the alert component. Note that anything between
+ * the [[begin()]] and [[end()]] calls of the Alert widget will also be treated
+ * as the body content, and will be rendered before this.
+ */
+ public $body;
+ /**
+ * @var array the options for rendering the close button tag.
+ * The close button is displayed in the header of the modal window. Clicking
+ * on the button will hide the modal window. If this is null, no close button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to '×'.
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Alert plugin help](http://twitter.github.com/bootstrap/javascript.html#alerts)
+ * for the supported HTML attributes.
+ */
+ public $closeButton = array();
+
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+
+ $this->initOptions();
+
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderBodyBegin() . "\n";
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo "\n" . $this->renderBodyEnd();
+ echo "\n" . Html::endTag('div');
+
+ $this->registerPlugin('alert');
+ }
+
+ /**
+ * Renders the close button if any before rendering the content.
+ * @return string the rendering result
+ */
+ protected function renderBodyBegin()
+ {
+ return $this->renderCloseButton();
+ }
+
+ /**
+ * Renders the alert body (if any).
+ * @return string the rendering result
+ */
+ protected function renderBodyEnd()
+ {
+ return $this->body . "\n";
+ }
+
+ /**
+ * Renders the close button.
+ * @return string the rendering result
+ */
+ protected function renderCloseButton()
+ {
+ if ($this->closeButton !== null) {
+ $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button');
+ $label = ArrayHelper::remove($this->closeButton, 'label', '×');
+ if ($tag === 'button' && !isset($this->closeButton['type'])) {
+ $this->closeButton['type'] = 'button';
+ }
+ return Html::tag($tag, $label, $this->closeButton);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Initializes the widget options.
+ * This method sets the default values for various options.
+ */
+ protected function initOptions()
+ {
+ $this->options = array_merge(array(
+ 'class' => 'fade in',
+ ), $this->options);
+
+ Html::addCssClass($this->options, 'alert');
+
+ if ($this->closeButton !== null) {
+ $this->closeButton = array_merge(array(
+ 'data-dismiss' => 'alert',
+ 'aria-hidden' => 'true',
+ 'class' => 'close',
+ ), $this->closeButton);
+ }
+ }
+}
diff --git a/framework/yii/bootstrap/BootstrapAsset.php b/framework/yii/bootstrap/BootstrapAsset.php
new file mode 100644
index 0000000..26dad31
--- /dev/null
+++ b/framework/yii/bootstrap/BootstrapAsset.php
@@ -0,0 +1,22 @@
+
+ * @since 2.0
+ */
+class BootstrapAsset extends AssetBundle
+{
+ public $sourcePath = '@yii/bootstrap/assets';
+ public $css = array(
+ 'css/bootstrap.css',
+ );
+}
diff --git a/framework/yii/bootstrap/BootstrapPluginAsset.php b/framework/yii/bootstrap/BootstrapPluginAsset.php
new file mode 100644
index 0000000..613e120
--- /dev/null
+++ b/framework/yii/bootstrap/BootstrapPluginAsset.php
@@ -0,0 +1,27 @@
+
+ * @since 2.0
+ */
+class BootstrapPluginAsset extends AssetBundle
+{
+ public $sourcePath = '@yii/bootstrap/assets';
+ public $js = array(
+ 'js/bootstrap.js',
+ );
+ public $depends = array(
+ 'yii\web\JqueryAsset',
+ 'yii\bootstrap\BootstrapAsset',
+ );
+}
diff --git a/framework/yii/bootstrap/BootstrapThemeAsset.php b/framework/yii/bootstrap/BootstrapThemeAsset.php
new file mode 100644
index 0000000..d21f308
--- /dev/null
+++ b/framework/yii/bootstrap/BootstrapThemeAsset.php
@@ -0,0 +1,27 @@
+
+ * @since 2.0
+ */
+class BootstrapThemeAsset extends AssetBundle
+{
+ public $sourcePath = '@yii/bootstrap/assets';
+ public $css = array(
+ 'css/bootstrap-theme.css',
+ );
+ public $depends = array(
+ 'yii\bootstrap\BootstrapAsset',
+ );
+}
diff --git a/framework/yii/bootstrap/Button.php b/framework/yii/bootstrap/Button.php
new file mode 100644
index 0000000..5c45747
--- /dev/null
+++ b/framework/yii/bootstrap/Button.php
@@ -0,0 +1,62 @@
+ 'Action',
+ * 'options' => array('class' => 'btn-lg'),
+ * ));
+ * ```
+ * @see http://twitter.github.io/bootstrap/javascript.html#buttons
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class Button extends Widget
+{
+ /**
+ * @var string the tag to use to render the button
+ */
+ public $tagName = 'button';
+ /**
+ * @var string the button label
+ */
+ public $label = 'Button';
+ /**
+ * @var boolean whether the label should be HTML-encoded.
+ */
+ public $encodeLabel = true;
+
+
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->clientOptions = false;
+ Html::addCssClass($this->options, 'btn');
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::tag($this->tagName, $this->encodeLabel ? Html::encode($this->label) : $this->label, $this->options);
+ $this->registerPlugin('button');
+ }
+}
diff --git a/framework/yii/bootstrap/ButtonDropdown.php b/framework/yii/bootstrap/ButtonDropdown.php
new file mode 100644
index 0000000..2e04cb6
--- /dev/null
+++ b/framework/yii/bootstrap/ButtonDropdown.php
@@ -0,0 +1,115 @@
+ 'Action',
+ * 'dropdown' => array(
+ * 'items' => array(
+ * array(
+ * 'label' => 'DropdownA',
+ * 'url' => '/',
+ * ),
+ * array(
+ * 'label' => 'DropdownB',
+ * 'url' => '#',
+ * ),
+ * ),
+ * ),
+ * ));
+ * ```
+ * @see http://twitter.github.io/bootstrap/javascript.html#buttons
+ * @see http://twitter.github.io/bootstrap/components.html#buttonDropdowns
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class ButtonDropdown extends Widget
+{
+ /**
+ * @var string the button label
+ */
+ public $label = 'Button';
+ /**
+ * @var array the HTML attributes of the button.
+ */
+ public $options = array();
+ /**
+ * @var array the configuration array for [[Dropdown]].
+ */
+ public $dropdown = array();
+ /**
+ * @var boolean whether to display a group of split-styled button group.
+ */
+ public $split = false;
+
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderButton() . "\n" . $this->renderDropdown();
+ $this->registerPlugin('button');
+ }
+
+ /**
+ * Generates the button dropdown.
+ * @return string the rendering result.
+ */
+ protected function renderButton()
+ {
+ Html::addCssClass($this->options, 'btn');
+ if ($this->split) {
+ $tag = 'button';
+ $options = $this->options;
+ $this->options['data-toggle'] = 'dropdown';
+ Html::addCssClass($this->options, 'dropdown-toggle');
+ $splitButton = Button::widget(array(
+ 'label' => ' ',
+ 'encodeLabel' => false,
+ 'options' => $this->options,
+ ));
+ } else {
+ $tag = 'a';
+ $this->label .= ' ';
+ $options = $this->options;
+ if (!isset($options['href'])) {
+ $options['href'] = '#';
+ }
+ Html::addCssClass($options, 'dropdown-toggle');
+ $options['data-toggle'] = 'dropdown';
+ $splitButton = '';
+ }
+ return Button::widget(array(
+ 'tagName' => $tag,
+ 'label' => $this->label,
+ 'options' => $options,
+ 'encodeLabel' => false,
+ )) . "\n" . $splitButton;
+ }
+
+ /**
+ * Generates the dropdown menu.
+ * @return string the rendering result.
+ */
+ protected function renderDropdown()
+ {
+ $config = $this->dropdown;
+ $config['clientOptions'] = false;
+ return Dropdown::widget($config);
+ }
+}
diff --git a/framework/yii/bootstrap/ButtonGroup.php b/framework/yii/bootstrap/ButtonGroup.php
new file mode 100644
index 0000000..f2656c0
--- /dev/null
+++ b/framework/yii/bootstrap/ButtonGroup.php
@@ -0,0 +1,97 @@
+ array(
+ * array('label' => 'A'),
+ * array('label' => 'B'),
+ * )
+ * ));
+ *
+ * // button group with an item as a string
+ * echo ButtonGroup::::widget(array(
+ * 'buttons' => array(
+ * Button::widget(array('label' => 'A')),
+ * array('label' => 'B'),
+ * )
+ * ));
+ * ```
+ * @see http://twitter.github.io/bootstrap/javascript.html#buttons
+ * @see http://twitter.github.io/bootstrap/components.html#buttonGroups
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class ButtonGroup extends Widget
+{
+ /**
+ * @var array list of buttons. Each array element represents a single button
+ * which can be specified as a string or an array of the following structure:
+ *
+ * - label: string, required, the button label.
+ * - options: array, optional, the HTML attributes of the button.
+ */
+ public $buttons = array();
+ /**
+ * @var boolean whether to HTML-encode the button labels.
+ */
+ public $encodeLabels = true;
+
+
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'btn-group');
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::tag('div', $this->renderButtons(), $this->options);
+ BootstrapAsset::register($this->getView());
+ }
+
+ /**
+ * Generates the buttons that compound the group as specified on [[items]].
+ * @return string the rendering result.
+ */
+ protected function renderButtons()
+ {
+ $buttons = array();
+ foreach ($this->buttons as $button) {
+ if (is_array($button)) {
+ $label = ArrayHelper::getValue($button, 'label');
+ $options = ArrayHelper::getValue($button, 'options');
+ $buttons[] = Button::widget(array(
+ 'label' => $label,
+ 'options' => $options,
+ 'encodeLabel' => $this->encodeLabels
+ ));
+ } else {
+ $buttons[] = $button;
+ }
+ }
+ return implode("\n", $buttons);
+ }
+}
diff --git a/framework/yii/bootstrap/Carousel.php b/framework/yii/bootstrap/Carousel.php
new file mode 100644
index 0000000..ec9a0c9
--- /dev/null
+++ b/framework/yii/bootstrap/Carousel.php
@@ -0,0 +1,172 @@
+ array(
+ * // the item contains only the image
+ * ' ',
+ * // equivalent to the above
+ * array(
+ * 'content' => ' ',
+ * ),
+ * // the item contains both the image and the caption
+ * array(
+ * 'content' => ' ',
+ * 'caption' => 'This is title This is the caption text
',
+ * 'options' => array(...),
+ * ),
+ * )
+ * ));
+ * ```
+ *
+ * @see http://twitter.github.io/bootstrap/javascript.html#carousel
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class Carousel extends Widget
+{
+ /**
+ * @var array|boolean the labels for the previous and the next control buttons.
+ * If false, it means the previous and the next control buttons should not be displayed.
+ */
+ public $controls = array('‹', '›');
+ /**
+ * @var array list of slides in the carousel. Each array element represents a single
+ * slide with the following structure:
+ *
+ * ```php
+ * array(
+ * // required, slide content (HTML), such as an image tag
+ * 'content' => ' ',
+ * // optional, the caption (HTML) of the slide
+ * 'caption' => 'This is title This is the caption text
',
+ * // optional the HTML attributes of the slide container
+ * 'options' => array(),
+ * )
+ * ```
+ */
+ public $items = array();
+
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'carousel');
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderIndicators() . "\n";
+ echo $this->renderItems() . "\n";
+ echo $this->renderControls() . "\n";
+ echo Html::endTag('div') . "\n";
+ $this->registerPlugin('carousel');
+ }
+
+ /**
+ * Renders carousel indicators.
+ * @return string the rendering result
+ */
+ public function renderIndicators()
+ {
+ $indicators = array();
+ for ($i = 0, $count = count($this->items); $i < $count; $i++) {
+ $options = array('data-target' => '#' . $this->options['id'], 'data-slide-to' => $i);
+ if ($i === 0) {
+ Html::addCssClass($options, 'active');
+ }
+ $indicators[] = Html::tag('li', '', $options);
+ }
+ return Html::tag('ol', implode("\n", $indicators), array('class' => 'carousel-indicators'));
+ }
+
+ /**
+ * Renders carousel items as specified on [[items]].
+ * @return string the rendering result
+ */
+ public function renderItems()
+ {
+ $items = array();
+ for ($i = 0, $count = count($this->items); $i < $count; $i++) {
+ $items[] = $this->renderItem($this->items[$i], $i);
+ }
+ return Html::tag('div', implode("\n", $items), array('class' => 'carousel-inner'));
+ }
+
+ /**
+ * Renders a single carousel item
+ * @param string|array $item a single item from [[items]]
+ * @param integer $index the item index as the first item should be set to `active`
+ * @return string the rendering result
+ * @throws InvalidConfigException if the item is invalid
+ */
+ public function renderItem($item, $index)
+ {
+ if (is_string($item)) {
+ $content = $item;
+ $caption = null;
+ $options = array();
+ } elseif (isset($item['content'])) {
+ $content = $item['content'];
+ $caption = ArrayHelper::getValue($item, 'caption');
+ if ($caption !== null) {
+ $caption = Html::tag('div', $caption, array('class' => 'carousel-caption'));
+ }
+ $options = ArrayHelper::getValue($item, 'options', array());
+ } else {
+ throw new InvalidConfigException('The "content" option is required.');
+ }
+
+ Html::addCssClass($options, 'item');
+ if ($index === 0) {
+ Html::addCssClass($options, 'active');
+ }
+
+ return Html::tag('div', $content . "\n" . $caption, $options);
+ }
+
+ /**
+ * Renders previous and next control buttons.
+ * @throws InvalidConfigException if [[controls]] is invalid.
+ */
+ public function renderControls()
+ {
+ if (isset($this->controls[0], $this->controls[1])) {
+ return Html::a($this->controls[0], '#' . $this->options['id'], array(
+ 'class' => 'left carousel-control',
+ 'data-slide' => 'prev',
+ )) . "\n"
+ . Html::a($this->controls[1], '#' . $this->options['id'], array(
+ 'class' => 'right carousel-control',
+ 'data-slide' => 'next',
+ ));
+ } elseif ($this->controls === false) {
+ return '';
+ } else {
+ throw new InvalidConfigException('The "controls" property must be either false or an array of two elements.');
+ }
+ }
+}
diff --git a/framework/yii/bootstrap/Collapse.php b/framework/yii/bootstrap/Collapse.php
new file mode 100644
index 0000000..77fdde7
--- /dev/null
+++ b/framework/yii/bootstrap/Collapse.php
@@ -0,0 +1,133 @@
+ array(
+ * // equivalent to the above
+ * 'Collapsible Group Item #1' => array(
+ * 'content' => 'Anim pariatur cliche...',
+ * // open its content by default
+ * 'contentOptions' => array('class' => 'in')
+ * ),
+ * // another group item
+ * 'Collapsible Group Item #2' => array(
+ * 'content' => 'Anim pariatur cliche...',
+ * 'contentOptions' => array(...),
+ * 'options' => array(...),
+ * ),
+ * )
+ * ));
+ * ```
+ *
+ * @see http://twitter.github.io/bootstrap/javascript.html#collapse
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class Collapse extends Widget
+{
+ /**
+ * @var array list of groups in the collapse widget. Each array element represents a single
+ * group with the following structure:
+ *
+ * ```php
+ * // item key is the actual group header
+ * 'Collapsible Group Item #1' => array(
+ * // required, the content (HTML) of the group
+ * 'content' => 'Anim pariatur cliche...',
+ * // optional the HTML attributes of the content group
+ * 'contentOptions' => array(),
+ * // optional the HTML attributes of the group
+ * 'options' => array(),
+ * )
+ * ```
+ */
+ public $items = array();
+
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'accordion');
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderItems() . "\n";
+ echo Html::endTag('div') . "\n";
+ $this->registerPlugin('collapse');
+ }
+
+ /**
+ * Renders collapsible items as specified on [[items]].
+ * @return string the rendering result
+ */
+ public function renderItems()
+ {
+ $items = array();
+ $index = 0;
+ foreach ($this->items as $header => $item) {
+ $options = ArrayHelper::getValue($item, 'options', array());
+ Html::addCssClass($options, 'accordion-group');
+ $items[] = Html::tag('div', $this->renderItem($header, $item, ++$index), $options);
+ }
+
+ return implode("\n", $items);
+ }
+
+ /**
+ * Renders a single collapsible item group
+ * @param string $header a label of the item group [[items]]
+ * @param array $item a single item from [[items]]
+ * @param integer $index the item index as each item group content must have an id
+ * @return string the rendering result
+ * @throws InvalidConfigException
+ */
+ public function renderItem($header, $item, $index)
+ {
+ if (isset($item['content'])) {
+ $id = $this->options['id'] . '-collapse' . $index;
+ $options = ArrayHelper::getValue($item, 'contentOptions', array());
+ $options['id'] = $id;
+ Html::addCssClass($options, 'accordion-body collapse');
+
+ $header = Html::a($header, '#' . $id, array(
+ 'class' => 'accordion-toggle',
+ 'data-toggle' => 'collapse',
+ 'data-parent' => '#' . $this->options['id']
+ )) . "\n";
+
+ $content = Html::tag('div', $item['content'], array('class' => 'accordion-inner')) . "\n";
+ } else {
+ throw new InvalidConfigException('The "content" option is required.');
+ }
+ $group = array();
+
+ $group[] = Html::tag('div', $header, array('class' => 'accordion-heading'));
+ $group[] = Html::tag('div', $content, $options);
+
+ return implode("\n", $group);
+ }
+}
diff --git a/framework/yii/bootstrap/Dropdown.php b/framework/yii/bootstrap/Dropdown.php
new file mode 100644
index 0000000..1cc389e
--- /dev/null
+++ b/framework/yii/bootstrap/Dropdown.php
@@ -0,0 +1,90 @@
+
+ * @since 2.0
+ */
+class Dropdown extends Widget
+{
+ /**
+ * @var array list of menu items in the dropdown. Each array element can be either an HTML string,
+ * or an array representing a single menu with the following structure:
+ *
+ * - label: string, required, the label of the item link
+ * - url: string, optional, the url of the item link. Defaults to "#".
+ * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
+ * - linkOptions: array, optional, the HTML attributes of the item link.
+ * - options: array, optional, the HTML attributes of the item.
+ */
+ public $items = array();
+ /**
+ * @var boolean whether the labels for header items should be HTML-encoded.
+ */
+ public $encodeLabels = true;
+
+
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'dropdown-menu');
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderItems($this->items);
+ $this->registerPlugin('dropdown');
+ }
+
+ /**
+ * Renders menu items.
+ * @param array $items the menu items to be rendered
+ * @return string the rendering result.
+ * @throws InvalidConfigException if the label option is not specified in one of the items.
+ */
+ protected function renderItems($items)
+ {
+ $lines = array();
+ foreach ($items as $i => $item) {
+ if (isset($item['visible']) && !$item['visible']) {
+ unset($items[$i]);
+ continue;
+ }
+ if (is_string($item)) {
+ $lines[] = $item;
+ continue;
+ }
+ if (!isset($item['label'])) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+ $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
+ $options = ArrayHelper::getValue($item, 'options', array());
+ $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array());
+ $linkOptions['tabindex'] = '-1';
+ $content = Html::a($label, ArrayHelper::getValue($item, 'url', '#'), $linkOptions);
+ $lines[] = Html::tag('li', $content, $options);
+ }
+
+ return Html::tag('ul', implode("\n", $lines), $this->options);
+ }
+}
diff --git a/framework/yii/bootstrap/Modal.php b/framework/yii/bootstrap/Modal.php
new file mode 100644
index 0000000..e1f042f
--- /dev/null
+++ b/framework/yii/bootstrap/Modal.php
@@ -0,0 +1,231 @@
+ 'Hello world ',
+ * 'toggleButton' => array(
+ * 'label' => 'click me',
+ * ),
+ * ));
+ *
+ * echo 'Say hello...';
+ *
+ * Modal::end();
+ * ~~~
+ *
+ * @see http://twitter.github.io/bootstrap/javascript.html#modals
+ * @author Antonio Ramirez
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Modal extends Widget
+{
+ /**
+ * @var string the header content in the modal window.
+ */
+ public $header;
+ /**
+ * @var string the footer content in the modal window.
+ */
+ public $footer;
+ /**
+ * @var array the options for rendering the close button tag.
+ * The close button is displayed in the header of the modal window. Clicking
+ * on the button will hide the modal window. If this is null, no close button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to '×'.
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Modal plugin help](http://twitter.github.com/bootstrap/javascript.html#modals)
+ * for the supported HTML attributes.
+ */
+ public $closeButton = array();
+ /**
+ * @var array the options for rendering the toggle button tag.
+ * The toggle button is used to toggle the visibility of the modal window.
+ * If this property is null, no toggle button will be rendered.
+ *
+ * The following special options are supported:
+ *
+ * - tag: string, the tag name of the button. Defaults to 'button'.
+ * - label: string, the label of the button. Defaults to 'Show'.
+ *
+ * The rest of the options will be rendered as the HTML attributes of the button tag.
+ * Please refer to the [Modal plugin help](http://twitter.github.com/bootstrap/javascript.html#modals)
+ * for the supported HTML attributes.
+ */
+ public $toggleButton;
+
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+
+ $this->initOptions();
+
+ echo $this->renderToggleButton() . "\n";
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo Html::beginTag('div', array('class' => 'modal-dialog')) . "\n";
+ echo Html::beginTag('div', array('class' => 'modal-content')) . "\n";
+ echo $this->renderHeader() . "\n";
+ echo $this->renderBodyBegin() . "\n";
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo "\n" . $this->renderBodyEnd();
+ echo "\n" . $this->renderFooter();
+ echo "\n" . Html::endTag('div'); // modal-content
+ echo "\n" . Html::endTag('div'); // modal-dialog
+ echo "\n" . Html::endTag('div');
+
+ $this->registerPlugin('modal');
+ }
+
+ /**
+ * Renders the header HTML markup of the modal
+ * @return string the rendering result
+ */
+ protected function renderHeader()
+ {
+ $button = $this->renderCloseButton();
+ if ($button !== null) {
+ $this->header = $button . "\n" . $this->header;
+ }
+ if ($this->header !== null) {
+ return Html::tag('div', "\n" . $this->header . "\n", array('class' => 'modal-header'));
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Renders the opening tag of the modal body.
+ * @return string the rendering result
+ */
+ protected function renderBodyBegin()
+ {
+ return Html::beginTag('div', array('class' => 'modal-body'));
+ }
+
+ /**
+ * Renders the closing tag of the modal body.
+ * @return string the rendering result
+ */
+ protected function renderBodyEnd()
+ {
+ return Html::endTag('div');
+ }
+
+ /**
+ * Renders the HTML markup for the footer of the modal
+ * @return string the rendering result
+ */
+ protected function renderFooter()
+ {
+ if ($this->footer !== null) {
+ return Html::tag('div', "\n" . $this->footer . "\n", array('class' => 'modal-footer'));
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Renders the toggle button.
+ * @return string the rendering result
+ */
+ protected function renderToggleButton()
+ {
+ if ($this->toggleButton !== null) {
+ $tag = ArrayHelper::remove($this->toggleButton, 'tag', 'button');
+ $label = ArrayHelper::remove($this->toggleButton, 'label', 'Show');
+ if ($tag === 'button' && !isset($this->toggleButton['type'])) {
+ $this->toggleButton['type'] = 'button';
+ }
+ return Html::tag($tag, $label, $this->toggleButton);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Renders the close button.
+ * @return string the rendering result
+ */
+ protected function renderCloseButton()
+ {
+ if ($this->closeButton !== null) {
+ $tag = ArrayHelper::remove($this->closeButton, 'tag', 'button');
+ $label = ArrayHelper::remove($this->closeButton, 'label', '×');
+ if ($tag === 'button' && !isset($this->closeButton['type'])) {
+ $this->closeButton['type'] = 'button';
+ }
+ return Html::tag($tag, $label, $this->closeButton);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Initializes the widget options.
+ * This method sets the default values for various options.
+ */
+ protected function initOptions()
+ {
+ $this->options = array_merge(array(
+ 'class' => 'fade',
+ 'role' => 'dialog',
+ 'tabindex' => -1,
+ ), $this->options);
+ Html::addCssClass($this->options, 'modal');
+
+ if ($this->clientOptions !== false) {
+ $this->clientOptions = array_merge(array(
+ 'show' => false,
+ ), $this->clientOptions);
+ }
+
+ if ($this->closeButton !== null) {
+ $this->closeButton = array_merge(array(
+ 'data-dismiss' => 'modal',
+ 'aria-hidden' => 'true',
+ 'class' => 'close',
+ ), $this->closeButton);
+ }
+
+ if ($this->toggleButton !== null) {
+ $this->toggleButton = array_merge(array(
+ 'data-toggle' => 'modal',
+ ), $this->toggleButton);
+ if (!isset($this->toggleButton['data-target']) && !isset($this->toggleButton['href'])) {
+ $this->toggleButton['data-target'] = '#' . $this->options['id'];
+ }
+ }
+ }
+}
diff --git a/framework/yii/bootstrap/Nav.php b/framework/yii/bootstrap/Nav.php
new file mode 100644
index 0000000..f24f729
--- /dev/null
+++ b/framework/yii/bootstrap/Nav.php
@@ -0,0 +1,220 @@
+ array(
+ * array(
+ * 'label' => 'Home',
+ * 'url' => array('site/index'),
+ * 'linkOptions' => array(...),
+ * ),
+ * array(
+ * 'label' => 'Dropdown',
+ * 'items' => array(
+ * array(
+ * 'label' => 'Level 1 -DropdownA',
+ * 'url' => '#',
+ * 'items' => array(
+ * array(
+ * 'label' => 'Level 2 -DropdownA',
+ * 'url' => '#',
+ * ),
+ * ),
+ * ),
+ * array(
+ * 'label' => 'Level 1 -DropdownB',
+ * 'url' => '#',
+ * ),
+ * ),
+ * ),
+ * ),
+ * ));
+ * ```
+ *
+ * @see http://twitter.github.io/bootstrap/components.html#nav
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class Nav extends Widget
+{
+ /**
+ * @var array list of items in the nav widget. Each array element represents a single
+ * menu item which can be either a string or an array with the following structure:
+ *
+ * - label: string, required, the nav item label.
+ * - url: optional, the item's URL. Defaults to "#".
+ * - visible: boolean, optional, whether this menu item is visible. Defaults to true.
+ * - linkOptions: array, optional, the HTML attributes of the item's link.
+ * - options: array, optional, the HTML attributes of the item container (LI).
+ * - active: boolean, optional, whether the item should be on active state or not.
+ * - items: array|string, optional, the configuration array for creating a [[Dropdown]] widget,
+ * or a string representing the dropdown menu. Note that Bootstrap does not support sub-dropdown menus.
+ *
+ * If a menu item is a string, it will be rendered directly without HTML encoding.
+ */
+ public $items = array();
+ /**
+ * @var boolean whether the nav items labels should be HTML-encoded.
+ */
+ public $encodeLabels = true;
+ /**
+ * @var boolean whether to automatically activate items according to whether their route setting
+ * matches the currently requested route.
+ * @see isItemActive
+ */
+ public $activateItems = true;
+ /**
+ * @var string the route used to determine if a menu item is active or not.
+ * If not set, it will use the route of the current request.
+ * @see params
+ * @see isItemActive
+ */
+ public $route;
+ /**
+ * @var array the parameters used to determine if a menu item is active or not.
+ * If not set, it will use `$_GET`.
+ * @see route
+ * @see isItemActive
+ */
+ public $params;
+
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->route === null && Yii::$app->controller !== null) {
+ $this->route = Yii::$app->controller->getRoute();
+ }
+ if ($this->params === null) {
+ $this->params = $_GET;
+ }
+ Html::addCssClass($this->options, 'nav');
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderItems();
+ BootstrapAsset::register($this->getView());
+ }
+
+ /**
+ * Renders widget items.
+ */
+ public function renderItems()
+ {
+ $items = array();
+ foreach ($this->items as $i => $item) {
+ if (isset($item['visible']) && !$item['visible']) {
+ unset($items[$i]);
+ continue;
+ }
+ $items[] = $this->renderItem($item);
+ }
+
+ return Html::tag('ul', implode("\n", $items), $this->options);
+ }
+
+ /**
+ * Renders a widget's item.
+ * @param string|array $item the item to render.
+ * @return string the rendering result.
+ * @throws InvalidConfigException
+ */
+ public function renderItem($item)
+ {
+ if (is_string($item)) {
+ return $item;
+ }
+ if (!isset($item['label'])) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+ $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
+ $options = ArrayHelper::getValue($item, 'options', array());
+ $items = ArrayHelper::getValue($item, 'items');
+ $url = Html::url(ArrayHelper::getValue($item, 'url', '#'));
+ $linkOptions = ArrayHelper::getValue($item, 'linkOptions', array());
+
+ if (isset($item['active'])) {
+ $active = ArrayHelper::remove($item, 'active', false);
+ } else {
+ $active = $this->isItemActive($item);
+ }
+
+ if ($active) {
+ Html::addCssClass($options, 'active');
+ }
+
+ if ($items !== null) {
+ $linkOptions['data-toggle'] = 'dropdown';
+ Html::addCssClass($options, 'dropdown');
+ Html::addCssClass($urlOptions, 'dropdown-toggle');
+ $label .= ' ' . Html::tag('b', '', array('class' => 'caret'));
+ if (is_array($items)) {
+ $items = Dropdown::widget(array(
+ 'items' => $items,
+ 'encodeLabels' => $this->encodeLabels,
+ 'clientOptions' => false,
+ ));
+ }
+ }
+
+ return Html::tag('li', Html::a($label, $url, $linkOptions) . $items, $options);
+ }
+
+
+ /**
+ * Checks whether a menu item is active.
+ * This is done by checking if [[route]] and [[params]] match that specified in the `url` option of the menu item.
+ * When the `url` option of a menu item is specified in terms of an array, its first element is treated
+ * as the route for the item and the rest of the elements are the associated parameters.
+ * Only when its route and parameters match [[route]] and [[params]], respectively, will a menu item
+ * be considered active.
+ * @param array $item the menu item to be checked
+ * @return boolean whether the menu item is active
+ */
+ protected function isItemActive($item)
+ {
+ if (isset($item['url']) && is_array($item['url']) && isset($item['url'][0])) {
+ $route = $item['url'][0];
+ if ($route[0] !== '/' && Yii::$app->controller) {
+ $route = Yii::$app->controller->module->getUniqueId() . '/' . $route;
+ }
+ if (ltrim($route, '/') !== $this->route) {
+ return false;
+ }
+ unset($item['url']['#']);
+ if (count($item['url']) > 1) {
+ foreach (array_splice($item['url'], 1) as $name => $value) {
+ if (!isset($this->params[$name]) || $this->params[$name] != $value) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/framework/yii/bootstrap/NavBar.php b/framework/yii/bootstrap/NavBar.php
new file mode 100644
index 0000000..7aafbbb
--- /dev/null
+++ b/framework/yii/bootstrap/NavBar.php
@@ -0,0 +1,108 @@
+ 'NavBar Test'));
+ * echo Nav::widget(array(
+ * 'items' => array(
+ * array('label' => 'Home', 'url' => array('/site/index')),
+ * array('label' => 'About', 'url' => array('/site/about')),
+ * ),
+ * ));
+ * NavBar::end();
+ * ```
+ *
+ * @see http://twitter.github.io/bootstrap/components.html#navbar
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class NavBar extends Widget
+{
+ /**
+ * @var string the text of the brand. Note that this is not HTML-encoded.
+ * @see http://twitter.github.io/bootstrap/components.html#navbar
+ */
+ public $brandLabel;
+ /**
+ * @param array|string $url the URL for the brand's hyperlink tag. This parameter will be processed by [[Html::url()]]
+ * and will be used for the "href" attribute of the brand link. Defaults to site root.
+ */
+ public $brandUrl = '/';
+ /**
+ * @var array the HTML attributes of the brand link.
+ */
+ public $brandOptions = array();
+
+ public $screenReaderToggleText = 'Toggle navigation';
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->clientOptions = false;
+ Html::addCssClass($this->options, 'navbar navbar-default');
+ Html::addCssClass($this->brandOptions, 'navbar-brand');
+ if (empty($this->options['role'])) {
+ $this->options['role'] = 'navigation';
+ }
+
+ echo Html::beginTag('nav', $this->options);
+ echo Html::beginTag('div', array('class' => 'container'));
+
+ echo Html::beginTag('div', array('class' => 'navbar-header'));
+ echo $this->renderToggleButton();
+ if ($this->brandLabel !== null) {
+ echo Html::a($this->brandLabel, $this->brandUrl, $this->brandOptions);
+ }
+ echo Html::endTag('div');
+
+ echo Html::beginTag('div', array('class' => 'collapse navbar-collapse navbar-ex1-collapse'));
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+
+ echo Html::endTag('div');
+ echo Html::endTag('div');
+ echo Html::endTag('nav');
+ BootstrapPluginAsset::register($this->getView());
+ }
+
+ /**
+ * Renders collapsible toggle button.
+ * @return string the rendering toggle button.
+ */
+ protected function renderToggleButton()
+ {
+ $bar = Html::tag('span', '', array('class' => 'icon-bar'));
+ $screenReader = ''.$this->screenReaderToggleText.' ';
+ return Html::button("{$screenReader}\n{$bar}\n{$bar}\n{$bar}", array(
+ 'class' => 'navbar-toggle',
+ 'data-toggle' => 'collapse',
+ 'data-target' => '.navbar-ex1-collapse',
+ ));
+ }
+}
diff --git a/framework/yii/bootstrap/Progress.php b/framework/yii/bootstrap/Progress.php
new file mode 100644
index 0000000..d8e5a69
--- /dev/null
+++ b/framework/yii/bootstrap/Progress.php
@@ -0,0 +1,146 @@
+ 60,
+ * 'label' => 'test',
+ * ));
+ *
+ * // styled
+ * echo Progress::widget(array(
+ * 'percent' => 65,
+ * 'barOptions' => array('class' => 'bar-danger')
+ * ));
+ *
+ * // striped
+ * echo Progress::widget(array(
+ * 'percent' => 70,
+ * 'barOptions' => array('class' => 'bar-warning'),
+ * 'options' => array('class' => 'progress-striped')
+ * ));
+ *
+ * // striped animated
+ * echo Progress::widget(array(
+ * 'percent' => 70,
+ * 'barOptions' => array('class' => 'bar-success'),
+ * 'options' => array('class' => 'active progress-striped')
+ * ));
+ *
+ * // stacked bars
+ * echo Progress::widget(array(
+ * 'bars' => array(
+ * array('percent' => 30, 'options' => array('class' => 'bar-danger')),
+ * array('percent' => 30, 'label' => 'test', 'options' => array('class' => 'bar-success')),
+ * array('percent' => 35, 'options' => array('class' => 'bar-warning'))
+ * )
+ * ));
+ * ```
+ * @see http://twitter.github.io/bootstrap/components.html#progress
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class Progress extends Widget
+{
+ /**
+ * @var string the button label
+ */
+ public $label;
+ /**
+ * @var integer the amount of progress as a percentage.
+ */
+ public $percent = 0;
+ /**
+ * @var array the HTML attributes of the
+ */
+ public $barOptions = array();
+ /**
+ * @var array a set of bars that are stacked together to form a single progress bar.
+ * Each bar is an array of the following structure:
+ *
+ * ```php
+ * array(
+ * // required, the amount of progress as a percentage.
+ * 'percent' => 30,
+ * // optional, the label to be displayed on the bar
+ * 'label' => '30%',
+ * // optional, array, additional HTML attributes for the bar tag
+ * 'options' => array(),
+ * )
+ */
+ public $bars;
+
+
+ /**
+ * Initializes the widget.
+ * If you override this method, make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'progress');
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo Html::beginTag('div', $this->options) . "\n";
+ echo $this->renderProgress() . "\n";
+ echo Html::endTag('div') . "\n";
+ BootstrapAsset::register($this->getView());
+ }
+
+ /**
+ * Renders the progress.
+ * @return string the rendering result.
+ * @throws InvalidConfigException if the "percent" option is not set in a stacked progress bar.
+ */
+ protected function renderProgress()
+ {
+ if (empty($this->bars)) {
+ return $this->renderBar($this->percent, $this->label, $this->barOptions);
+ }
+ $bars = array();
+ foreach ($this->bars as $bar) {
+ $label = ArrayHelper::getValue($bar, 'label', '');
+ if (!isset($bar['percent'])) {
+ throw new InvalidConfigException("The 'percent' option is required.");
+ }
+ $options = ArrayHelper::getValue($bar, 'options', array());
+ $bars[] = $this->renderBar($bar['percent'], $label, $options);
+ }
+ return implode("\n", $bars);
+ }
+
+ /**
+ * Generates a bar
+ * @param int $percent the percentage of the bar
+ * @param string $label, optional, the label to display at the bar
+ * @param array $options the HTML attributes of the bar
+ * @return string the rendering result.
+ */
+ protected function renderBar($percent, $label = '', $options = array())
+ {
+ Html::addCssClass($options, 'bar');
+ $options['style'] = "width:{$percent}%";
+ return Html::tag('div', $label, $options);
+ }
+}
diff --git a/framework/yii/bootstrap/Tabs.php b/framework/yii/bootstrap/Tabs.php
new file mode 100644
index 0000000..d6ddd7b
--- /dev/null
+++ b/framework/yii/bootstrap/Tabs.php
@@ -0,0 +1,195 @@
+ array(
+ * array(
+ * 'label' => 'One',
+ * 'content' => 'Anim pariatur cliche...',
+ * 'active' => true
+ * ),
+ * array(
+ * 'label' => 'Two',
+ * 'content' => 'Anim pariatur cliche...',
+ * 'headerOptions' => array(...),
+ * 'options' => array('id' => 'myveryownID'),
+ * ),
+ * array(
+ * 'label' => 'Dropdown',
+ * 'items' => array(
+ * array(
+ * 'label' => 'DropdownA',
+ * 'content' => 'DropdownA, Anim pariatur cliche...',
+ * ),
+ * array(
+ * 'label' => 'DropdownB',
+ * 'content' => 'DropdownB, Anim pariatur cliche...',
+ * ),
+ * ),
+ * ),
+ * ),
+ * ));
+ * ```
+ *
+ * @see http://twitter.github.io/bootstrap/javascript.html#tabs
+ * @author Antonio Ramirez
+ * @since 2.0
+ */
+class Tabs extends Widget
+{
+ /**
+ * @var array list of tabs in the tabs widget. Each array element represents a single
+ * tab with the following structure:
+ *
+ * - label: string, required, the tab header label.
+ * - headerOptions: array, optional, the HTML attributes of the tab header.
+ * - content: array, required if `items` is not set. The content (HTML) of the tab pane.
+ * - options: array, optional, the HTML attributes of the tab pane container.
+ * - active: boolean, optional, whether the item tab header and pane should be visible or not.
+ * - items: array, optional, if not set then `content` will be required. The `items` specify a dropdown items
+ * configuration array. Each item can hold two extra keys, besides the above ones:
+ * * active: boolean, optional, whether the item tab header and pane should be visible or not.
+ * * content: string, required if `items` is not set. The content (HTML) of the tab pane.
+ * * contentOptions: optional, array, the HTML attributes of the tab content container.
+ */
+ public $items = array();
+ /**
+ * @var array list of HTML attributes for the item container tags. This will be overwritten
+ * by the "options" set in individual [[items]]. The following special options are recognized:
+ *
+ * - tag: string, defaults to "div", the tag name of the item container tags.
+ */
+ public $itemOptions = array();
+ /**
+ * @var array list of HTML attributes for the header container tags. This will be overwritten
+ * by the "headerOptions" set in individual [[items]].
+ */
+ public $headerOptions = array();
+ /**
+ * @var boolean whether the labels for header items should be HTML-encoded.
+ */
+ public $encodeLabels = true;
+
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+ Html::addCssClass($this->options, 'nav nav-tabs');
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ echo $this->renderItems();
+ $this->registerPlugin('tab');
+ }
+
+ /**
+ * Renders tab items as specified on [[items]].
+ * @return string the rendering result.
+ * @throws InvalidConfigException.
+ */
+ protected function renderItems()
+ {
+ $headers = array();
+ $panes = array();
+ foreach ($this->items as $n => $item) {
+ if (!isset($item['label'])) {
+ throw new InvalidConfigException("The 'label' option is required.");
+ }
+ $label = $this->encodeLabels ? Html::encode($item['label']) : $item['label'];
+ $headerOptions = array_merge($this->headerOptions, ArrayHelper::getValue($item, 'headerOptions', array()));
+
+ if (isset($item['items'])) {
+ $label .= ' ';
+ Html::addCssClass($headerOptions, 'dropdown');
+
+ if ($this->renderDropdown($item['items'], $panes)) {
+ Html::addCssClass($headerOptions, 'active');
+ }
+
+ $header = Html::a($label, "#", array('class' => 'dropdown-toggle', 'data-toggle' => 'dropdown')) . "\n"
+ . Dropdown::widget(array('items' => $item['items'], 'clientOptions' => false));
+ } elseif (isset($item['content'])) {
+ $options = array_merge($this->itemOptions, ArrayHelper::getValue($item, 'options', array()));
+ $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-tab' . $n);
+
+ Html::addCssClass($options, 'tab-pane');
+ if (ArrayHelper::remove($item, 'active')) {
+ Html::addCssClass($options, 'active');
+ Html::addCssClass($headerOptions, 'active');
+ }
+ $header = Html::a($label, '#' . $options['id'], array('data-toggle' => 'tab'));
+ $panes[] = Html::tag('div', $item['content'], $options);
+ } else {
+ throw new InvalidConfigException("Either the 'content' or 'items' option must be set.");
+ }
+
+ $headers[] = Html::tag('li', $header, $headerOptions);
+ }
+
+ return Html::tag('ul', implode("\n", $headers), $this->options) . "\n"
+ . Html::tag('div', implode("\n", $panes), array('class' => 'tab-content'));
+ }
+
+ /**
+ * Normalizes dropdown item options by removing tab specific keys `content` and `contentOptions`, and also
+ * configure `panes` accordingly.
+ * @param array $items the dropdown items configuration.
+ * @param array $panes the panes reference array.
+ * @return boolean whether any of the dropdown items is `active` or not.
+ * @throws InvalidConfigException
+ */
+ protected function renderDropdown(&$items, &$panes)
+ {
+ $itemActive = false;
+
+ foreach ($items as $n => &$item) {
+ if (is_string($item)) {
+ continue;
+ }
+ if (!isset($item['content'])) {
+ throw new InvalidConfigException("The 'content' option is required.");
+ }
+
+ $content = ArrayHelper::remove($item, 'content');
+ $options = ArrayHelper::remove($item, 'contentOptions', array());
+ Html::addCssClass($options, 'tab-pane');
+ if (ArrayHelper::remove($item, 'active')) {
+ Html::addCssClass($options, 'active');
+ Html::addCssClass($item['options'], 'active');
+ $itemActive = true;
+ }
+
+ $options['id'] = ArrayHelper::getValue($options, 'id', $this->options['id'] . '-dd-tab' . $n);
+ $item['url'] = '#' . $options['id'];
+ $item['linkOptions']['data-toggle'] = 'tab';
+
+ $panes[] = Html::tag('div', $content, $options);
+
+ unset($item);
+ }
+ return $itemActive;
+ }
+}
diff --git a/framework/yii/bootstrap/Widget.php b/framework/yii/bootstrap/Widget.php
new file mode 100644
index 0000000..2959f09
--- /dev/null
+++ b/framework/yii/bootstrap/Widget.php
@@ -0,0 +1,82 @@
+
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Widget extends \yii\base\Widget
+{
+ /**
+ * @var array the HTML attributes for the widget container tag.
+ */
+ public $options = array();
+ /**
+ * @var array the options for the underlying Bootstrap JS plugin.
+ * Please refer to the corresponding Bootstrap plugin Web page for possible options.
+ * For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows
+ * how to use the "Modal" plugin and the supported options (e.g. "remote").
+ */
+ public $clientOptions = array();
+ /**
+ * @var array the event handlers for the underlying Bootstrap JS plugin.
+ * Please refer to the corresponding Bootstrap plugin Web page for possible events.
+ * For example, [this page](http://twitter.github.io/bootstrap/javascript.html#modals) shows
+ * how to use the "Modal" plugin and the supported events (e.g. "shown").
+ */
+ public $clientEvents = array();
+
+
+ /**
+ * Initializes the widget.
+ * This method will register the bootstrap asset bundle. If you override this method,
+ * make sure you call the parent implementation first.
+ */
+ public function init()
+ {
+ parent::init();
+ if (!isset($this->options['id'])) {
+ $this->options['id'] = $this->getId();
+ }
+ }
+
+ /**
+ * Registers a specific Bootstrap plugin and the related events
+ * @param string $name the name of the Bootstrap plugin
+ */
+ protected function registerPlugin($name)
+ {
+ $view = $this->getView();
+
+ BootstrapPluginAsset::register($view);
+
+ $id = $this->options['id'];
+
+ if ($this->clientOptions !== false) {
+ $options = empty($this->clientOptions) ? '' : Json::encode($this->clientOptions);
+ $js = "jQuery('#$id').$name($options);";
+ $view->registerJs($js);
+ }
+
+ if (!empty($this->clientEvents)) {
+ $js = array();
+ foreach ($this->clientEvents as $event => $handler) {
+ $js[] = "jQuery('#$id').on('$event', $handler);";
+ }
+ $view->registerJs(implode("\n", $js));
+ }
+ }
+}
diff --git a/framework/yii/bootstrap/assets/css/bootstrap-theme.css b/framework/yii/bootstrap/assets/css/bootstrap-theme.css
new file mode 100644
index 0000000..ad11735
--- /dev/null
+++ b/framework/yii/bootstrap/assets/css/bootstrap-theme.css
@@ -0,0 +1,384 @@
+.btn-default,
+.btn-primary,
+.btn-success,
+.btn-info,
+.btn-warning,
+.btn-danger {
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.btn-default:active,
+.btn-primary:active,
+.btn-success:active,
+.btn-info:active,
+.btn-warning:active,
+.btn-danger:active,
+.btn-default.active,
+.btn-primary.active,
+.btn-success.active,
+.btn-info.active,
+.btn-warning.active,
+.btn-danger.active {
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn:active,
+.btn.active {
+ background-image: none;
+}
+
+.btn-default {
+ text-shadow: 0 1px 0 #fff;
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#e6e6e6));
+ background-image: -webkit-linear-gradient(top, #ffffff, 0%, #e6e6e6, 100%);
+ background-image: -moz-linear-gradient(top, #ffffff 0%, #e6e6e6 100%);
+ background-image: linear-gradient(to bottom, #ffffff 0%, #e6e6e6 100%);
+ background-repeat: repeat-x;
+ border-color: #e0e0e0;
+ border-color: #ccc;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
+}
+
+.btn-default:active,
+.btn-default.active {
+ background-color: #e6e6e6;
+ border-color: #e0e0e0;
+}
+
+.btn-primary {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
+ background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%);
+ background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
+ background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
+ background-repeat: repeat-x;
+ border-color: #2d6ca2;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
+}
+
+.btn-primary:active,
+.btn-primary.active {
+ background-color: #3071a9;
+ border-color: #2d6ca2;
+}
+
+.btn-success {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44));
+ background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%);
+ background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+ background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
+ background-repeat: repeat-x;
+ border-color: #419641;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
+}
+
+.btn-success:active,
+.btn-success.active {
+ background-color: #449d44;
+ border-color: #419641;
+}
+
+.btn-warning {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f));
+ background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%);
+ background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+ background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
+ background-repeat: repeat-x;
+ border-color: #eb9316;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
+}
+
+.btn-warning:active,
+.btn-warning.active {
+ background-color: #ec971f;
+ border-color: #eb9316;
+}
+
+.btn-danger {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c));
+ background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%);
+ background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+ background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
+ background-repeat: repeat-x;
+ border-color: #c12e2a;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
+}
+
+.btn-danger:active,
+.btn-danger.active {
+ background-color: #c9302c;
+ border-color: #c12e2a;
+}
+
+.btn-info {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5));
+ background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%);
+ background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+ background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
+ background-repeat: repeat-x;
+ border-color: #2aabd2;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
+}
+
+.btn-info:active,
+.btn-info.active {
+ background-color: #31b0d5;
+ border-color: #2aabd2;
+}
+
+.thumbnail,
+.img-thumbnail {
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus,
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+ background-color: #357ebd;
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
+ background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%);
+ background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
+ background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+}
+
+.navbar {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ffffff), to(#f8f8f8));
+ background-image: -webkit-linear-gradient(top, #ffffff, 0%, #f8f8f8, 100%);
+ background-image: -moz-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);
+ background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);
+ background-repeat: repeat-x;
+ border-radius: 4px;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);
+}
+
+.navbar .navbar-nav > .active > a {
+ background-color: #f8f8f8;
+}
+
+.navbar-brand,
+.navbar-nav > li > a {
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);
+}
+
+.navbar-inverse {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#3c3c3c), to(#222222));
+ background-image: -webkit-linear-gradient(top, #3c3c3c, 0%, #222222, 100%);
+ background-image: -moz-linear-gradient(top, #3c3c3c 0%, #222222 100%);
+ background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
+}
+
+.navbar-inverse .navbar-nav > .active > a {
+ background-color: #222222;
+}
+
+.navbar-inverse .navbar-brand,
+.navbar-inverse .navbar-nav > li > a {
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+}
+
+.navbar-static-top,
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ border-radius: 0;
+}
+
+.alert {
+ text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.alert-success {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#c8e5bc));
+ background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #c8e5bc, 100%);
+ background-image: -moz-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
+ background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
+ background-repeat: repeat-x;
+ border-color: #b2dba1;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
+}
+
+.alert-info {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#b9def0));
+ background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #b9def0, 100%);
+ background-image: -moz-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
+ background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
+ background-repeat: repeat-x;
+ border-color: #9acfea;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
+}
+
+.alert-warning {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#f8efc0));
+ background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #f8efc0, 100%);
+ background-image: -moz-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
+ background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
+ background-repeat: repeat-x;
+ border-color: #f5e79e;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
+}
+
+.alert-danger {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#e7c3c3));
+ background-image: -webkit-linear-gradient(top, #f2dede, 0%, #e7c3c3, 100%);
+ background-image: -moz-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
+ background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
+ background-repeat: repeat-x;
+ border-color: #dca7a7;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
+}
+
+.progress {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#ebebeb), to(#f5f5f5));
+ background-image: -webkit-linear-gradient(top, #ebebeb, 0%, #f5f5f5, 100%);
+ background-image: -moz-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
+ background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
+}
+
+.progress-bar {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3071a9));
+ background-image: -webkit-linear-gradient(top, #428bca, 0%, #3071a9, 100%);
+ background-image: -moz-linear-gradient(top, #428bca 0%, #3071a9 100%);
+ background-image: linear-gradient(to bottom, #428bca 0%, #3071a9 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3071a9', GradientType=0);
+}
+
+.progress-bar-success {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5cb85c), to(#449d44));
+ background-image: -webkit-linear-gradient(top, #5cb85c, 0%, #449d44, 100%);
+ background-image: -moz-linear-gradient(top, #5cb85c 0%, #449d44 100%);
+ background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
+}
+
+.progress-bar-info {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#5bc0de), to(#31b0d5));
+ background-image: -webkit-linear-gradient(top, #5bc0de, 0%, #31b0d5, 100%);
+ background-image: -moz-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
+ background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
+}
+
+.progress-bar-warning {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f0ad4e), to(#ec971f));
+ background-image: -webkit-linear-gradient(top, #f0ad4e, 0%, #ec971f, 100%);
+ background-image: -moz-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
+ background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
+}
+
+.progress-bar-danger {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9534f), to(#c9302c));
+ background-image: -webkit-linear-gradient(top, #d9534f, 0%, #c9302c, 100%);
+ background-image: -moz-linear-gradient(top, #d9534f 0%, #c9302c 100%);
+ background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
+}
+
+.list-group {
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);
+}
+
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+ text-shadow: 0 -1px 0 #3071a9;
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#3278b3));
+ background-image: -webkit-linear-gradient(top, #428bca, 0%, #3278b3, 100%);
+ background-image: -moz-linear-gradient(top, #428bca 0%, #3278b3 100%);
+ background-image: linear-gradient(to bottom, #428bca 0%, #3278b3 100%);
+ background-repeat: repeat-x;
+ border-color: #3278b3;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff3278b3', GradientType=0);
+}
+
+.panel {
+ -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
+}
+
+.panel-default > .panel-heading {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f5f5f5), to(#e8e8e8));
+ background-image: -webkit-linear-gradient(top, #f5f5f5, 0%, #e8e8e8, 100%);
+ background-image: -moz-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
+ background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
+}
+
+.panel-primary > .panel-heading {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#428bca), to(#357ebd));
+ background-image: -webkit-linear-gradient(top, #428bca, 0%, #357ebd, 100%);
+ background-image: -moz-linear-gradient(top, #428bca 0%, #357ebd 100%);
+ background-image: linear-gradient(to bottom, #428bca 0%, #357ebd 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff428bca', endColorstr='#ff357ebd', GradientType=0);
+}
+
+.panel-success > .panel-heading {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#dff0d8), to(#d0e9c6));
+ background-image: -webkit-linear-gradient(top, #dff0d8, 0%, #d0e9c6, 100%);
+ background-image: -moz-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
+ background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
+}
+
+.panel-info > .panel-heading {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#d9edf7), to(#c4e3f3));
+ background-image: -webkit-linear-gradient(top, #d9edf7, 0%, #c4e3f3, 100%);
+ background-image: -moz-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
+ background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
+}
+
+.panel-warning > .panel-heading {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#fcf8e3), to(#faf2cc));
+ background-image: -webkit-linear-gradient(top, #fcf8e3, 0%, #faf2cc, 100%);
+ background-image: -moz-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
+ background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
+}
+
+.panel-danger > .panel-heading {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#f2dede), to(#ebcccc));
+ background-image: -webkit-linear-gradient(top, #f2dede, 0%, #ebcccc, 100%);
+ background-image: -moz-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
+ background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
+}
+
+.well {
+ background-image: -webkit-gradient(linear, left 0%, left 100%, from(#e8e8e8), to(#f5f5f5));
+ background-image: -webkit-linear-gradient(top, #e8e8e8, 0%, #f5f5f5, 100%);
+ background-image: -moz-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
+ background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
+ background-repeat: repeat-x;
+ border-color: #dcdcdc;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
+ -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
+ box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);
+}
\ No newline at end of file
diff --git a/framework/yii/bootstrap/assets/css/bootstrap.css b/framework/yii/bootstrap/assets/css/bootstrap.css
new file mode 100644
index 0000000..bbda4ee
--- /dev/null
+++ b/framework/yii/bootstrap/assets/css/bootstrap.css
@@ -0,0 +1,6805 @@
+/*!
+ * Bootstrap v3.0.0
+ *
+ * Copyright 2013 Twitter, Inc
+ * Licensed under the Apache License v2.0
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Designed and built with all the love in the world by @mdo and @fat.
+ */
+
+/*! normalize.css v2.1.0 | MIT License | git.io/normalize */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+nav,
+section,
+summary {
+ display: block;
+}
+
+audio,
+canvas,
+video {
+ display: inline-block;
+}
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+[hidden] {
+ display: none;
+}
+
+html {
+ font-family: sans-serif;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+}
+
+body {
+ margin: 0;
+}
+
+a:focus {
+ outline: thin dotted;
+}
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+h1 {
+ margin: 0.67em 0;
+ font-size: 2em;
+}
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+b,
+strong {
+ font-weight: bold;
+}
+
+dfn {
+ font-style: italic;
+}
+
+hr {
+ height: 0;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+}
+
+mark {
+ color: #000;
+ background: #ff0;
+}
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, serif;
+ font-size: 1em;
+}
+
+pre {
+ white-space: pre-wrap;
+}
+
+q {
+ quotes: "\201C" "\201D" "\2018" "\2019";
+}
+
+small {
+ font-size: 80%;
+}
+
+sub,
+sup {
+ position: relative;
+ font-size: 75%;
+ line-height: 0;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+img {
+ border: 0;
+}
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+figure {
+ margin: 0;
+}
+
+fieldset {
+ padding: 0.35em 0.625em 0.75em;
+ margin: 0 2px;
+ border: 1px solid #c0c0c0;
+}
+
+legend {
+ padding: 0;
+ border: 0;
+}
+
+button,
+input,
+select,
+textarea {
+ margin: 0;
+ font-family: inherit;
+ font-size: 100%;
+}
+
+button,
+input {
+ line-height: normal;
+}
+
+button,
+select {
+ text-transform: none;
+}
+
+button,
+html input[type="button"],
+input[type="reset"],
+input[type="submit"] {
+ cursor: pointer;
+ -webkit-appearance: button;
+}
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+input[type="checkbox"],
+input[type="radio"] {
+ padding: 0;
+ box-sizing: border-box;
+}
+
+input[type="search"] {
+ -webkit-box-sizing: content-box;
+ -moz-box-sizing: content-box;
+ box-sizing: content-box;
+ -webkit-appearance: textfield;
+}
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ padding: 0;
+ border: 0;
+}
+
+textarea {
+ overflow: auto;
+ vertical-align: top;
+}
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+@media print {
+ * {
+ color: #000 !important;
+ text-shadow: none !important;
+ background: transparent !important;
+ box-shadow: none !important;
+ }
+ a,
+ a:visited {
+ text-decoration: underline;
+ }
+ a[href]:after {
+ content: " (" attr(href) ")";
+ }
+ abbr[title]:after {
+ content: " (" attr(title) ")";
+ }
+ .ir a:after,
+ a[href^="javascript:"]:after,
+ a[href^="#"]:after {
+ content: "";
+ }
+ pre,
+ blockquote {
+ border: 1px solid #999;
+ page-break-inside: avoid;
+ }
+ thead {
+ display: table-header-group;
+ }
+ tr,
+ img {
+ page-break-inside: avoid;
+ }
+ img {
+ max-width: 100% !important;
+ }
+ @page {
+ margin: 2cm .5cm;
+ }
+ p,
+ h2,
+ h3 {
+ orphans: 3;
+ widows: 3;
+ }
+ h2,
+ h3 {
+ page-break-after: avoid;
+ }
+ .navbar {
+ display: none;
+ }
+ .table td,
+ .table th {
+ background-color: #fff !important;
+ }
+ .btn > .caret,
+ .dropup > .btn > .caret {
+ border-top-color: #000 !important;
+ }
+ .label {
+ border: 1px solid #000;
+ }
+ .table {
+ border-collapse: collapse !important;
+ }
+ .table-bordered th,
+ .table-bordered td {
+ border: 1px solid #ddd !important;
+ }
+}
+
+*,
+*:before,
+*:after {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+html {
+ font-size: 62.5%;
+ -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
+}
+
+body {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 1.428571429;
+ color: #333333;
+ background-color: #ffffff;
+}
+
+input,
+button,
+select,
+textarea {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+}
+
+button,
+input,
+select[multiple],
+textarea {
+ background-image: none;
+}
+
+a {
+ color: #428bca;
+ text-decoration: none;
+}
+
+a:hover,
+a:focus {
+ color: #2a6496;
+ text-decoration: underline;
+}
+
+a:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+img {
+ vertical-align: middle;
+}
+
+.img-responsive {
+ display: block;
+ height: auto;
+ max-width: 100%;
+}
+
+.img-rounded {
+ border-radius: 6px;
+}
+
+.img-thumbnail {
+ display: inline-block;
+ height: auto;
+ max-width: 100%;
+ padding: 4px;
+ line-height: 1.428571429;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-radius: 4px;
+ -webkit-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+}
+
+.img-circle {
+ border-radius: 50%;
+}
+
+hr {
+ margin-top: 20px;
+ margin-bottom: 20px;
+ border: 0;
+ border-top: 1px solid #eeeeee;
+}
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0 0 0 0);
+ border: 0;
+}
+
+p {
+ margin: 0 0 10px;
+}
+
+.lead {
+ margin-bottom: 20px;
+ font-size: 16.099999999999998px;
+ font-weight: 200;
+ line-height: 1.4;
+}
+
+@media (min-width: 768px) {
+ .lead {
+ font-size: 21px;
+ }
+}
+
+small {
+ font-size: 85%;
+}
+
+cite {
+ font-style: normal;
+}
+
+.text-muted {
+ color: #999999;
+}
+
+.text-primary {
+ color: #428bca;
+}
+
+.text-warning {
+ color: #c09853;
+}
+
+.text-danger {
+ color: #b94a48;
+}
+
+.text-success {
+ color: #468847;
+}
+
+.text-info {
+ color: #3a87ad;
+}
+
+.text-left {
+ text-align: left;
+}
+
+.text-right {
+ text-align: right;
+}
+
+.text-center {
+ text-align: center;
+}
+
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+.h1,
+.h2,
+.h3,
+.h4,
+.h5,
+.h6 {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-weight: 500;
+ line-height: 1.1;
+}
+
+h1 small,
+h2 small,
+h3 small,
+h4 small,
+h5 small,
+h6 small,
+.h1 small,
+.h2 small,
+.h3 small,
+.h4 small,
+.h5 small,
+.h6 small {
+ font-weight: normal;
+ line-height: 1;
+ color: #999999;
+}
+
+h1,
+h2,
+h3 {
+ margin-top: 20px;
+ margin-bottom: 10px;
+}
+
+h4,
+h5,
+h6 {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
+h1,
+.h1 {
+ font-size: 36px;
+}
+
+h2,
+.h2 {
+ font-size: 30px;
+}
+
+h3,
+.h3 {
+ font-size: 24px;
+}
+
+h4,
+.h4 {
+ font-size: 18px;
+}
+
+h5,
+.h5 {
+ font-size: 14px;
+}
+
+h6,
+.h6 {
+ font-size: 12px;
+}
+
+h1 small,
+.h1 small {
+ font-size: 24px;
+}
+
+h2 small,
+.h2 small {
+ font-size: 18px;
+}
+
+h3 small,
+.h3 small,
+h4 small,
+.h4 small {
+ font-size: 14px;
+}
+
+.page-header {
+ padding-bottom: 9px;
+ margin: 40px 0 20px;
+ border-bottom: 1px solid #eeeeee;
+}
+
+ul,
+ol {
+ margin-top: 0;
+ margin-bottom: 10px;
+}
+
+ul ul,
+ol ul,
+ul ol,
+ol ol {
+ margin-bottom: 0;
+}
+
+.list-unstyled {
+ padding-left: 0;
+ list-style: none;
+}
+
+.list-inline {
+ padding-left: 0;
+ list-style: none;
+}
+
+.list-inline > li {
+ display: inline-block;
+ padding-right: 5px;
+ padding-left: 5px;
+}
+
+dl {
+ margin-bottom: 20px;
+}
+
+dt,
+dd {
+ line-height: 1.428571429;
+}
+
+dt {
+ font-weight: bold;
+}
+
+dd {
+ margin-left: 0;
+}
+
+@media (min-width: 768px) {
+ .dl-horizontal dt {
+ float: left;
+ width: 160px;
+ overflow: hidden;
+ clear: left;
+ text-align: right;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ .dl-horizontal dd {
+ margin-left: 180px;
+ }
+ .dl-horizontal dd:before,
+ .dl-horizontal dd:after {
+ display: table;
+ content: " ";
+ }
+ .dl-horizontal dd:after {
+ clear: both;
+ }
+ .dl-horizontal dd:before,
+ .dl-horizontal dd:after {
+ display: table;
+ content: " ";
+ }
+ .dl-horizontal dd:after {
+ clear: both;
+ }
+}
+
+abbr[title],
+abbr[data-original-title] {
+ cursor: help;
+ border-bottom: 1px dotted #999999;
+}
+
+abbr.initialism {
+ font-size: 90%;
+ text-transform: uppercase;
+}
+
+blockquote {
+ padding: 10px 20px;
+ margin: 0 0 20px;
+ border-left: 5px solid #eeeeee;
+}
+
+blockquote p {
+ font-size: 17.5px;
+ font-weight: 300;
+ line-height: 1.25;
+}
+
+blockquote p:last-child {
+ margin-bottom: 0;
+}
+
+blockquote small {
+ display: block;
+ line-height: 1.428571429;
+ color: #999999;
+}
+
+blockquote small:before {
+ content: '\2014 \00A0';
+}
+
+blockquote.pull-right {
+ padding-right: 15px;
+ padding-left: 0;
+ border-right: 5px solid #eeeeee;
+ border-left: 0;
+}
+
+blockquote.pull-right p,
+blockquote.pull-right small {
+ text-align: right;
+}
+
+blockquote.pull-right small:before {
+ content: '';
+}
+
+blockquote.pull-right small:after {
+ content: '\00A0 \2014';
+}
+
+q:before,
+q:after,
+blockquote:before,
+blockquote:after {
+ content: "";
+}
+
+address {
+ display: block;
+ margin-bottom: 20px;
+ font-style: normal;
+ line-height: 1.428571429;
+}
+
+code,
+pre {
+ font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
+}
+
+code {
+ padding: 2px 4px;
+ font-size: 90%;
+ color: #c7254e;
+ white-space: nowrap;
+ background-color: #f9f2f4;
+ border-radius: 4px;
+}
+
+pre {
+ display: block;
+ padding: 9.5px;
+ margin: 0 0 10px;
+ font-size: 13px;
+ line-height: 1.428571429;
+ color: #333333;
+ word-break: break-all;
+ word-wrap: break-word;
+ background-color: #f5f5f5;
+ border: 1px solid #cccccc;
+ border-radius: 4px;
+}
+
+pre.prettyprint {
+ margin-bottom: 20px;
+}
+
+pre code {
+ padding: 0;
+ font-size: inherit;
+ color: inherit;
+ white-space: pre-wrap;
+ background-color: transparent;
+ border: 0;
+}
+
+.pre-scrollable {
+ max-height: 340px;
+ overflow-y: scroll;
+}
+
+.container {
+ padding-right: 15px;
+ padding-left: 15px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.container:before,
+.container:after {
+ display: table;
+ content: " ";
+}
+
+.container:after {
+ clear: both;
+}
+
+.container:before,
+.container:after {
+ display: table;
+ content: " ";
+}
+
+.container:after {
+ clear: both;
+}
+
+.row {
+ margin-right: -15px;
+ margin-left: -15px;
+}
+
+.row:before,
+.row:after {
+ display: table;
+ content: " ";
+}
+
+.row:after {
+ clear: both;
+}
+
+.row:before,
+.row:after {
+ display: table;
+ content: " ";
+}
+
+.row:after {
+ clear: both;
+}
+
+.col-xs-1,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.col-xs-10,
+.col-xs-11,
+.col-xs-12,
+.col-sm-1,
+.col-sm-2,
+.col-sm-3,
+.col-sm-4,
+.col-sm-5,
+.col-sm-6,
+.col-sm-7,
+.col-sm-8,
+.col-sm-9,
+.col-sm-10,
+.col-sm-11,
+.col-sm-12,
+.col-md-1,
+.col-md-2,
+.col-md-3,
+.col-md-4,
+.col-md-5,
+.col-md-6,
+.col-md-7,
+.col-md-8,
+.col-md-9,
+.col-md-10,
+.col-md-11,
+.col-md-12,
+.col-lg-1,
+.col-lg-2,
+.col-lg-3,
+.col-lg-4,
+.col-lg-5,
+.col-lg-6,
+.col-lg-7,
+.col-lg-8,
+.col-lg-9,
+.col-lg-10,
+.col-lg-11,
+.col-lg-12 {
+ position: relative;
+ min-height: 1px;
+ padding-right: 15px;
+ padding-left: 15px;
+}
+
+.col-xs-1,
+.col-xs-2,
+.col-xs-3,
+.col-xs-4,
+.col-xs-5,
+.col-xs-6,
+.col-xs-7,
+.col-xs-8,
+.col-xs-9,
+.col-xs-10,
+.col-xs-11 {
+ float: left;
+}
+
+.col-xs-1 {
+ width: 8.333333333333332%;
+}
+
+.col-xs-2 {
+ width: 16.666666666666664%;
+}
+
+.col-xs-3 {
+ width: 25%;
+}
+
+.col-xs-4 {
+ width: 33.33333333333333%;
+}
+
+.col-xs-5 {
+ width: 41.66666666666667%;
+}
+
+.col-xs-6 {
+ width: 50%;
+}
+
+.col-xs-7 {
+ width: 58.333333333333336%;
+}
+
+.col-xs-8 {
+ width: 66.66666666666666%;
+}
+
+.col-xs-9 {
+ width: 75%;
+}
+
+.col-xs-10 {
+ width: 83.33333333333334%;
+}
+
+.col-xs-11 {
+ width: 91.66666666666666%;
+}
+
+.col-xs-12 {
+ width: 100%;
+}
+
+@media (min-width: 768px) {
+ .container {
+ max-width: 750px;
+ }
+ .col-sm-1,
+ .col-sm-2,
+ .col-sm-3,
+ .col-sm-4,
+ .col-sm-5,
+ .col-sm-6,
+ .col-sm-7,
+ .col-sm-8,
+ .col-sm-9,
+ .col-sm-10,
+ .col-sm-11 {
+ float: left;
+ }
+ .col-sm-1 {
+ width: 8.333333333333332%;
+ }
+ .col-sm-2 {
+ width: 16.666666666666664%;
+ }
+ .col-sm-3 {
+ width: 25%;
+ }
+ .col-sm-4 {
+ width: 33.33333333333333%;
+ }
+ .col-sm-5 {
+ width: 41.66666666666667%;
+ }
+ .col-sm-6 {
+ width: 50%;
+ }
+ .col-sm-7 {
+ width: 58.333333333333336%;
+ }
+ .col-sm-8 {
+ width: 66.66666666666666%;
+ }
+ .col-sm-9 {
+ width: 75%;
+ }
+ .col-sm-10 {
+ width: 83.33333333333334%;
+ }
+ .col-sm-11 {
+ width: 91.66666666666666%;
+ }
+ .col-sm-12 {
+ width: 100%;
+ }
+ .col-sm-push-1 {
+ left: 8.333333333333332%;
+ }
+ .col-sm-push-2 {
+ left: 16.666666666666664%;
+ }
+ .col-sm-push-3 {
+ left: 25%;
+ }
+ .col-sm-push-4 {
+ left: 33.33333333333333%;
+ }
+ .col-sm-push-5 {
+ left: 41.66666666666667%;
+ }
+ .col-sm-push-6 {
+ left: 50%;
+ }
+ .col-sm-push-7 {
+ left: 58.333333333333336%;
+ }
+ .col-sm-push-8 {
+ left: 66.66666666666666%;
+ }
+ .col-sm-push-9 {
+ left: 75%;
+ }
+ .col-sm-push-10 {
+ left: 83.33333333333334%;
+ }
+ .col-sm-push-11 {
+ left: 91.66666666666666%;
+ }
+ .col-sm-pull-1 {
+ right: 8.333333333333332%;
+ }
+ .col-sm-pull-2 {
+ right: 16.666666666666664%;
+ }
+ .col-sm-pull-3 {
+ right: 25%;
+ }
+ .col-sm-pull-4 {
+ right: 33.33333333333333%;
+ }
+ .col-sm-pull-5 {
+ right: 41.66666666666667%;
+ }
+ .col-sm-pull-6 {
+ right: 50%;
+ }
+ .col-sm-pull-7 {
+ right: 58.333333333333336%;
+ }
+ .col-sm-pull-8 {
+ right: 66.66666666666666%;
+ }
+ .col-sm-pull-9 {
+ right: 75%;
+ }
+ .col-sm-pull-10 {
+ right: 83.33333333333334%;
+ }
+ .col-sm-pull-11 {
+ right: 91.66666666666666%;
+ }
+ .col-sm-offset-1 {
+ margin-left: 8.333333333333332%;
+ }
+ .col-sm-offset-2 {
+ margin-left: 16.666666666666664%;
+ }
+ .col-sm-offset-3 {
+ margin-left: 25%;
+ }
+ .col-sm-offset-4 {
+ margin-left: 33.33333333333333%;
+ }
+ .col-sm-offset-5 {
+ margin-left: 41.66666666666667%;
+ }
+ .col-sm-offset-6 {
+ margin-left: 50%;
+ }
+ .col-sm-offset-7 {
+ margin-left: 58.333333333333336%;
+ }
+ .col-sm-offset-8 {
+ margin-left: 66.66666666666666%;
+ }
+ .col-sm-offset-9 {
+ margin-left: 75%;
+ }
+ .col-sm-offset-10 {
+ margin-left: 83.33333333333334%;
+ }
+ .col-sm-offset-11 {
+ margin-left: 91.66666666666666%;
+ }
+}
+
+@media (min-width: 992px) {
+ .container {
+ max-width: 970px;
+ }
+ .col-md-1,
+ .col-md-2,
+ .col-md-3,
+ .col-md-4,
+ .col-md-5,
+ .col-md-6,
+ .col-md-7,
+ .col-md-8,
+ .col-md-9,
+ .col-md-10,
+ .col-md-11 {
+ float: left;
+ }
+ .col-md-1 {
+ width: 8.333333333333332%;
+ }
+ .col-md-2 {
+ width: 16.666666666666664%;
+ }
+ .col-md-3 {
+ width: 25%;
+ }
+ .col-md-4 {
+ width: 33.33333333333333%;
+ }
+ .col-md-5 {
+ width: 41.66666666666667%;
+ }
+ .col-md-6 {
+ width: 50%;
+ }
+ .col-md-7 {
+ width: 58.333333333333336%;
+ }
+ .col-md-8 {
+ width: 66.66666666666666%;
+ }
+ .col-md-9 {
+ width: 75%;
+ }
+ .col-md-10 {
+ width: 83.33333333333334%;
+ }
+ .col-md-11 {
+ width: 91.66666666666666%;
+ }
+ .col-md-12 {
+ width: 100%;
+ }
+ .col-md-push-0 {
+ left: auto;
+ }
+ .col-md-push-1 {
+ left: 8.333333333333332%;
+ }
+ .col-md-push-2 {
+ left: 16.666666666666664%;
+ }
+ .col-md-push-3 {
+ left: 25%;
+ }
+ .col-md-push-4 {
+ left: 33.33333333333333%;
+ }
+ .col-md-push-5 {
+ left: 41.66666666666667%;
+ }
+ .col-md-push-6 {
+ left: 50%;
+ }
+ .col-md-push-7 {
+ left: 58.333333333333336%;
+ }
+ .col-md-push-8 {
+ left: 66.66666666666666%;
+ }
+ .col-md-push-9 {
+ left: 75%;
+ }
+ .col-md-push-10 {
+ left: 83.33333333333334%;
+ }
+ .col-md-push-11 {
+ left: 91.66666666666666%;
+ }
+ .col-md-pull-0 {
+ right: auto;
+ }
+ .col-md-pull-1 {
+ right: 8.333333333333332%;
+ }
+ .col-md-pull-2 {
+ right: 16.666666666666664%;
+ }
+ .col-md-pull-3 {
+ right: 25%;
+ }
+ .col-md-pull-4 {
+ right: 33.33333333333333%;
+ }
+ .col-md-pull-5 {
+ right: 41.66666666666667%;
+ }
+ .col-md-pull-6 {
+ right: 50%;
+ }
+ .col-md-pull-7 {
+ right: 58.333333333333336%;
+ }
+ .col-md-pull-8 {
+ right: 66.66666666666666%;
+ }
+ .col-md-pull-9 {
+ right: 75%;
+ }
+ .col-md-pull-10 {
+ right: 83.33333333333334%;
+ }
+ .col-md-pull-11 {
+ right: 91.66666666666666%;
+ }
+ .col-md-offset-0 {
+ margin-left: 0;
+ }
+ .col-md-offset-1 {
+ margin-left: 8.333333333333332%;
+ }
+ .col-md-offset-2 {
+ margin-left: 16.666666666666664%;
+ }
+ .col-md-offset-3 {
+ margin-left: 25%;
+ }
+ .col-md-offset-4 {
+ margin-left: 33.33333333333333%;
+ }
+ .col-md-offset-5 {
+ margin-left: 41.66666666666667%;
+ }
+ .col-md-offset-6 {
+ margin-left: 50%;
+ }
+ .col-md-offset-7 {
+ margin-left: 58.333333333333336%;
+ }
+ .col-md-offset-8 {
+ margin-left: 66.66666666666666%;
+ }
+ .col-md-offset-9 {
+ margin-left: 75%;
+ }
+ .col-md-offset-10 {
+ margin-left: 83.33333333333334%;
+ }
+ .col-md-offset-11 {
+ margin-left: 91.66666666666666%;
+ }
+}
+
+@media (min-width: 1200px) {
+ .container {
+ max-width: 1170px;
+ }
+ .col-lg-1,
+ .col-lg-2,
+ .col-lg-3,
+ .col-lg-4,
+ .col-lg-5,
+ .col-lg-6,
+ .col-lg-7,
+ .col-lg-8,
+ .col-lg-9,
+ .col-lg-10,
+ .col-lg-11 {
+ float: left;
+ }
+ .col-lg-1 {
+ width: 8.333333333333332%;
+ }
+ .col-lg-2 {
+ width: 16.666666666666664%;
+ }
+ .col-lg-3 {
+ width: 25%;
+ }
+ .col-lg-4 {
+ width: 33.33333333333333%;
+ }
+ .col-lg-5 {
+ width: 41.66666666666667%;
+ }
+ .col-lg-6 {
+ width: 50%;
+ }
+ .col-lg-7 {
+ width: 58.333333333333336%;
+ }
+ .col-lg-8 {
+ width: 66.66666666666666%;
+ }
+ .col-lg-9 {
+ width: 75%;
+ }
+ .col-lg-10 {
+ width: 83.33333333333334%;
+ }
+ .col-lg-11 {
+ width: 91.66666666666666%;
+ }
+ .col-lg-12 {
+ width: 100%;
+ }
+ .col-lg-push-0 {
+ left: auto;
+ }
+ .col-lg-push-1 {
+ left: 8.333333333333332%;
+ }
+ .col-lg-push-2 {
+ left: 16.666666666666664%;
+ }
+ .col-lg-push-3 {
+ left: 25%;
+ }
+ .col-lg-push-4 {
+ left: 33.33333333333333%;
+ }
+ .col-lg-push-5 {
+ left: 41.66666666666667%;
+ }
+ .col-lg-push-6 {
+ left: 50%;
+ }
+ .col-lg-push-7 {
+ left: 58.333333333333336%;
+ }
+ .col-lg-push-8 {
+ left: 66.66666666666666%;
+ }
+ .col-lg-push-9 {
+ left: 75%;
+ }
+ .col-lg-push-10 {
+ left: 83.33333333333334%;
+ }
+ .col-lg-push-11 {
+ left: 91.66666666666666%;
+ }
+ .col-lg-pull-0 {
+ right: auto;
+ }
+ .col-lg-pull-1 {
+ right: 8.333333333333332%;
+ }
+ .col-lg-pull-2 {
+ right: 16.666666666666664%;
+ }
+ .col-lg-pull-3 {
+ right: 25%;
+ }
+ .col-lg-pull-4 {
+ right: 33.33333333333333%;
+ }
+ .col-lg-pull-5 {
+ right: 41.66666666666667%;
+ }
+ .col-lg-pull-6 {
+ right: 50%;
+ }
+ .col-lg-pull-7 {
+ right: 58.333333333333336%;
+ }
+ .col-lg-pull-8 {
+ right: 66.66666666666666%;
+ }
+ .col-lg-pull-9 {
+ right: 75%;
+ }
+ .col-lg-pull-10 {
+ right: 83.33333333333334%;
+ }
+ .col-lg-pull-11 {
+ right: 91.66666666666666%;
+ }
+ .col-lg-offset-0 {
+ margin-left: 0;
+ }
+ .col-lg-offset-1 {
+ margin-left: 8.333333333333332%;
+ }
+ .col-lg-offset-2 {
+ margin-left: 16.666666666666664%;
+ }
+ .col-lg-offset-3 {
+ margin-left: 25%;
+ }
+ .col-lg-offset-4 {
+ margin-left: 33.33333333333333%;
+ }
+ .col-lg-offset-5 {
+ margin-left: 41.66666666666667%;
+ }
+ .col-lg-offset-6 {
+ margin-left: 50%;
+ }
+ .col-lg-offset-7 {
+ margin-left: 58.333333333333336%;
+ }
+ .col-lg-offset-8 {
+ margin-left: 66.66666666666666%;
+ }
+ .col-lg-offset-9 {
+ margin-left: 75%;
+ }
+ .col-lg-offset-10 {
+ margin-left: 83.33333333333334%;
+ }
+ .col-lg-offset-11 {
+ margin-left: 91.66666666666666%;
+ }
+}
+
+table {
+ max-width: 100%;
+ background-color: transparent;
+}
+
+th {
+ text-align: left;
+}
+
+.table {
+ width: 100%;
+ margin-bottom: 20px;
+}
+
+.table thead > tr > th,
+.table tbody > tr > th,
+.table tfoot > tr > th,
+.table thead > tr > td,
+.table tbody > tr > td,
+.table tfoot > tr > td {
+ padding: 8px;
+ line-height: 1.428571429;
+ vertical-align: top;
+ border-top: 1px solid #dddddd;
+}
+
+.table thead > tr > th {
+ vertical-align: bottom;
+ border-bottom: 2px solid #dddddd;
+}
+
+.table caption + thead tr:first-child th,
+.table colgroup + thead tr:first-child th,
+.table thead:first-child tr:first-child th,
+.table caption + thead tr:first-child td,
+.table colgroup + thead tr:first-child td,
+.table thead:first-child tr:first-child td {
+ border-top: 0;
+}
+
+.table tbody + tbody {
+ border-top: 2px solid #dddddd;
+}
+
+.table .table {
+ background-color: #ffffff;
+}
+
+.table-condensed thead > tr > th,
+.table-condensed tbody > tr > th,
+.table-condensed tfoot > tr > th,
+.table-condensed thead > tr > td,
+.table-condensed tbody > tr > td,
+.table-condensed tfoot > tr > td {
+ padding: 5px;
+}
+
+.table-bordered {
+ border: 1px solid #dddddd;
+}
+
+.table-bordered > thead > tr > th,
+.table-bordered > tbody > tr > th,
+.table-bordered > tfoot > tr > th,
+.table-bordered > thead > tr > td,
+.table-bordered > tbody > tr > td,
+.table-bordered > tfoot > tr > td {
+ border: 1px solid #dddddd;
+}
+
+.table-bordered > thead > tr > th,
+.table-bordered > thead > tr > td {
+ border-bottom-width: 2px;
+}
+
+.table-striped > tbody > tr:nth-child(odd) > td,
+.table-striped > tbody > tr:nth-child(odd) > th {
+ background-color: #f9f9f9;
+}
+
+.table-hover > tbody > tr:hover > td,
+.table-hover > tbody > tr:hover > th {
+ background-color: #f5f5f5;
+}
+
+table col[class*="col-"] {
+ display: table-column;
+ float: none;
+}
+
+table td[class*="col-"],
+table th[class*="col-"] {
+ display: table-cell;
+ float: none;
+}
+
+.table > thead > tr > td.active,
+.table > tbody > tr > td.active,
+.table > tfoot > tr > td.active,
+.table > thead > tr > th.active,
+.table > tbody > tr > th.active,
+.table > tfoot > tr > th.active,
+.table > thead > tr.active > td,
+.table > tbody > tr.active > td,
+.table > tfoot > tr.active > td,
+.table > thead > tr.active > th,
+.table > tbody > tr.active > th,
+.table > tfoot > tr.active > th {
+ background-color: #f5f5f5;
+}
+
+.table > thead > tr > td.success,
+.table > tbody > tr > td.success,
+.table > tfoot > tr > td.success,
+.table > thead > tr > th.success,
+.table > tbody > tr > th.success,
+.table > tfoot > tr > th.success,
+.table > thead > tr.success > td,
+.table > tbody > tr.success > td,
+.table > tfoot > tr.success > td,
+.table > thead > tr.success > th,
+.table > tbody > tr.success > th,
+.table > tfoot > tr.success > th {
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+
+.table-hover > tbody > tr > td.success:hover,
+.table-hover > tbody > tr > th.success:hover,
+.table-hover > tbody > tr.success:hover > td {
+ background-color: #d0e9c6;
+ border-color: #c9e2b3;
+}
+
+.table > thead > tr > td.danger,
+.table > tbody > tr > td.danger,
+.table > tfoot > tr > td.danger,
+.table > thead > tr > th.danger,
+.table > tbody > tr > th.danger,
+.table > tfoot > tr > th.danger,
+.table > thead > tr.danger > td,
+.table > tbody > tr.danger > td,
+.table > tfoot > tr.danger > td,
+.table > thead > tr.danger > th,
+.table > tbody > tr.danger > th,
+.table > tfoot > tr.danger > th {
+ background-color: #f2dede;
+ border-color: #eed3d7;
+}
+
+.table-hover > tbody > tr > td.danger:hover,
+.table-hover > tbody > tr > th.danger:hover,
+.table-hover > tbody > tr.danger:hover > td {
+ background-color: #ebcccc;
+ border-color: #e6c1c7;
+}
+
+.table > thead > tr > td.warning,
+.table > tbody > tr > td.warning,
+.table > tfoot > tr > td.warning,
+.table > thead > tr > th.warning,
+.table > tbody > tr > th.warning,
+.table > tfoot > tr > th.warning,
+.table > thead > tr.warning > td,
+.table > tbody > tr.warning > td,
+.table > tfoot > tr.warning > td,
+.table > thead > tr.warning > th,
+.table > tbody > tr.warning > th,
+.table > tfoot > tr.warning > th {
+ background-color: #fcf8e3;
+ border-color: #fbeed5;
+}
+
+.table-hover > tbody > tr > td.warning:hover,
+.table-hover > tbody > tr > th.warning:hover,
+.table-hover > tbody > tr.warning:hover > td {
+ background-color: #faf2cc;
+ border-color: #f8e5be;
+}
+
+@media (max-width: 768px) {
+ .table-responsive {
+ width: 100%;
+ margin-bottom: 15px;
+ overflow-x: scroll;
+ overflow-y: hidden;
+ border: 1px solid #dddddd;
+ }
+ .table-responsive > .table {
+ margin-bottom: 0;
+ background-color: #fff;
+ }
+ .table-responsive > .table > thead > tr > th,
+ .table-responsive > .table > tbody > tr > th,
+ .table-responsive > .table > tfoot > tr > th,
+ .table-responsive > .table > thead > tr > td,
+ .table-responsive > .table > tbody > tr > td,
+ .table-responsive > .table > tfoot > tr > td {
+ white-space: nowrap;
+ }
+ .table-responsive > .table-bordered {
+ border: 0;
+ }
+ .table-responsive > .table-bordered > thead > tr > th:first-child,
+ .table-responsive > .table-bordered > tbody > tr > th:first-child,
+ .table-responsive > .table-bordered > tfoot > tr > th:first-child,
+ .table-responsive > .table-bordered > thead > tr > td:first-child,
+ .table-responsive > .table-bordered > tbody > tr > td:first-child,
+ .table-responsive > .table-bordered > tfoot > tr > td:first-child {
+ border-left: 0;
+ }
+ .table-responsive > .table-bordered > thead > tr > th:last-child,
+ .table-responsive > .table-bordered > tbody > tr > th:last-child,
+ .table-responsive > .table-bordered > tfoot > tr > th:last-child,
+ .table-responsive > .table-bordered > thead > tr > td:last-child,
+ .table-responsive > .table-bordered > tbody > tr > td:last-child,
+ .table-responsive > .table-bordered > tfoot > tr > td:last-child {
+ border-right: 0;
+ }
+ .table-responsive > .table-bordered > thead > tr:last-child > th,
+ .table-responsive > .table-bordered > tbody > tr:last-child > th,
+ .table-responsive > .table-bordered > tfoot > tr:last-child > th,
+ .table-responsive > .table-bordered > thead > tr:last-child > td,
+ .table-responsive > .table-bordered > tbody > tr:last-child > td,
+ .table-responsive > .table-bordered > tfoot > tr:last-child > td {
+ border-bottom: 0;
+ }
+}
+
+fieldset {
+ padding: 0;
+ margin: 0;
+ border: 0;
+}
+
+legend {
+ display: block;
+ width: 100%;
+ padding: 0;
+ margin-bottom: 20px;
+ font-size: 21px;
+ line-height: inherit;
+ color: #333333;
+ border: 0;
+ border-bottom: 1px solid #e5e5e5;
+}
+
+label {
+ display: inline-block;
+ margin-bottom: 5px;
+ font-weight: bold;
+}
+
+input[type="search"] {
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+input[type="radio"],
+input[type="checkbox"] {
+ margin: 4px 0 0;
+ margin-top: 1px \9;
+ /* IE8-9 */
+
+ line-height: normal;
+}
+
+input[type="file"] {
+ display: block;
+}
+
+select[multiple],
+select[size] {
+ height: auto;
+}
+
+select optgroup {
+ font-family: inherit;
+ font-size: inherit;
+ font-style: inherit;
+}
+
+input[type="file"]:focus,
+input[type="radio"]:focus,
+input[type="checkbox"]:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+input[type="number"]::-webkit-outer-spin-button,
+input[type="number"]::-webkit-inner-spin-button {
+ height: auto;
+}
+
+.form-control:-moz-placeholder {
+ color: #999999;
+}
+
+.form-control::-moz-placeholder {
+ color: #999999;
+}
+
+.form-control:-ms-input-placeholder {
+ color: #999999;
+}
+
+.form-control::-webkit-input-placeholder {
+ color: #999999;
+}
+
+.form-control {
+ display: block;
+ width: 100%;
+ height: 34px;
+ padding: 6px 12px;
+ font-size: 14px;
+ line-height: 1.428571429;
+ color: #555555;
+ vertical-align: middle;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ -webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+ transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
+}
+
+.form-control:focus {
+ border-color: #66afe9;
+ outline: 0;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
+}
+
+.form-control[disabled],
+.form-control[readonly],
+fieldset[disabled] .form-control {
+ cursor: not-allowed;
+ background-color: #eeeeee;
+}
+
+textarea.form-control {
+ height: auto;
+}
+
+.form-group {
+ margin-bottom: 15px;
+}
+
+.radio,
+.checkbox {
+ display: block;
+ min-height: 20px;
+ padding-left: 20px;
+ margin-top: 10px;
+ margin-bottom: 10px;
+ vertical-align: middle;
+}
+
+.radio label,
+.checkbox label {
+ display: inline;
+ margin-bottom: 0;
+ font-weight: normal;
+ cursor: pointer;
+}
+
+.radio input[type="radio"],
+.radio-inline input[type="radio"],
+.checkbox input[type="checkbox"],
+.checkbox-inline input[type="checkbox"] {
+ float: left;
+ margin-left: -20px;
+}
+
+.radio + .radio,
+.checkbox + .checkbox {
+ margin-top: -5px;
+}
+
+.radio-inline,
+.checkbox-inline {
+ display: inline-block;
+ padding-left: 20px;
+ margin-bottom: 0;
+ font-weight: normal;
+ vertical-align: middle;
+ cursor: pointer;
+}
+
+.radio-inline + .radio-inline,
+.checkbox-inline + .checkbox-inline {
+ margin-top: 0;
+ margin-left: 10px;
+}
+
+input[type="radio"][disabled],
+input[type="checkbox"][disabled],
+.radio[disabled],
+.radio-inline[disabled],
+.checkbox[disabled],
+.checkbox-inline[disabled],
+fieldset[disabled] input[type="radio"],
+fieldset[disabled] input[type="checkbox"],
+fieldset[disabled] .radio,
+fieldset[disabled] .radio-inline,
+fieldset[disabled] .checkbox,
+fieldset[disabled] .checkbox-inline {
+ cursor: not-allowed;
+}
+
+.input-sm {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+
+select.input-sm {
+ height: 30px;
+ line-height: 30px;
+}
+
+textarea.input-sm {
+ height: auto;
+}
+
+.input-lg {
+ height: 45px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.33;
+ border-radius: 6px;
+}
+
+select.input-lg {
+ height: 45px;
+ line-height: 45px;
+}
+
+textarea.input-lg {
+ height: auto;
+}
+
+.has-warning .help-block,
+.has-warning .control-label {
+ color: #c09853;
+}
+
+.has-warning .form-control {
+ border-color: #c09853;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-warning .form-control:focus {
+ border-color: #a47e3c;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
+}
+
+.has-warning .input-group-addon {
+ color: #c09853;
+ background-color: #fcf8e3;
+ border-color: #c09853;
+}
+
+.has-error .help-block,
+.has-error .control-label {
+ color: #b94a48;
+}
+
+.has-error .form-control {
+ border-color: #b94a48;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-error .form-control:focus {
+ border-color: #953b39;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
+}
+
+.has-error .input-group-addon {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #b94a48;
+}
+
+.has-success .help-block,
+.has-success .control-label {
+ color: #468847;
+}
+
+.has-success .form-control {
+ border-color: #468847;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
+}
+
+.has-success .form-control:focus {
+ border-color: #356635;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
+}
+
+.has-success .input-group-addon {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #468847;
+}
+
+.form-control-static {
+ padding-top: 7px;
+ margin-bottom: 0;
+}
+
+.help-block {
+ display: block;
+ margin-top: 5px;
+ margin-bottom: 10px;
+ color: #737373;
+}
+
+@media (min-width: 768px) {
+ .form-inline .form-group {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .form-inline .form-control {
+ display: inline-block;
+ }
+ .form-inline .radio,
+ .form-inline .checkbox {
+ display: inline-block;
+ padding-left: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+ .form-inline .radio input[type="radio"],
+ .form-inline .checkbox input[type="checkbox"] {
+ float: none;
+ margin-left: 0;
+ }
+}
+
+.form-horizontal .control-label,
+.form-horizontal .radio,
+.form-horizontal .checkbox,
+.form-horizontal .radio-inline,
+.form-horizontal .checkbox-inline {
+ padding-top: 7px;
+ margin-top: 0;
+ margin-bottom: 0;
+}
+
+.form-horizontal .form-group {
+ margin-right: -15px;
+ margin-left: -15px;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+ display: table;
+ content: " ";
+}
+
+.form-horizontal .form-group:after {
+ clear: both;
+}
+
+.form-horizontal .form-group:before,
+.form-horizontal .form-group:after {
+ display: table;
+ content: " ";
+}
+
+.form-horizontal .form-group:after {
+ clear: both;
+}
+
+@media (min-width: 768px) {
+ .form-horizontal .control-label {
+ text-align: right;
+ }
+}
+
+.btn {
+ display: inline-block;
+ padding: 6px 12px;
+ margin-bottom: 0;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 1.428571429;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: middle;
+ cursor: pointer;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ -o-user-select: none;
+ user-select: none;
+}
+
+.btn:focus {
+ outline: thin dotted #333;
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+.btn:hover,
+.btn:focus {
+ color: #333333;
+ text-decoration: none;
+}
+
+.btn:active,
+.btn.active {
+ background-image: none;
+ outline: 0;
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn.disabled,
+.btn[disabled],
+fieldset[disabled] .btn {
+ pointer-events: none;
+ cursor: not-allowed;
+ opacity: 0.65;
+ filter: alpha(opacity=65);
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-default {
+ color: #333333;
+ background-color: #ffffff;
+ border-color: #cccccc;
+}
+
+.btn-default:hover,
+.btn-default:focus,
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+ color: #333333;
+ background-color: #ebebeb;
+ border-color: #adadad;
+}
+
+.btn-default:active,
+.btn-default.active,
+.open .dropdown-toggle.btn-default {
+ background-image: none;
+}
+
+.btn-default.disabled,
+.btn-default[disabled],
+fieldset[disabled] .btn-default,
+.btn-default.disabled:hover,
+.btn-default[disabled]:hover,
+fieldset[disabled] .btn-default:hover,
+.btn-default.disabled:focus,
+.btn-default[disabled]:focus,
+fieldset[disabled] .btn-default:focus,
+.btn-default.disabled:active,
+.btn-default[disabled]:active,
+fieldset[disabled] .btn-default:active,
+.btn-default.disabled.active,
+.btn-default[disabled].active,
+fieldset[disabled] .btn-default.active {
+ background-color: #ffffff;
+ border-color: #cccccc;
+}
+
+.btn-primary {
+ color: #ffffff;
+ background-color: #428bca;
+ border-color: #357ebd;
+}
+
+.btn-primary:hover,
+.btn-primary:focus,
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+ color: #ffffff;
+ background-color: #3276b1;
+ border-color: #285e8e;
+}
+
+.btn-primary:active,
+.btn-primary.active,
+.open .dropdown-toggle.btn-primary {
+ background-image: none;
+}
+
+.btn-primary.disabled,
+.btn-primary[disabled],
+fieldset[disabled] .btn-primary,
+.btn-primary.disabled:hover,
+.btn-primary[disabled]:hover,
+fieldset[disabled] .btn-primary:hover,
+.btn-primary.disabled:focus,
+.btn-primary[disabled]:focus,
+fieldset[disabled] .btn-primary:focus,
+.btn-primary.disabled:active,
+.btn-primary[disabled]:active,
+fieldset[disabled] .btn-primary:active,
+.btn-primary.disabled.active,
+.btn-primary[disabled].active,
+fieldset[disabled] .btn-primary.active {
+ background-color: #428bca;
+ border-color: #357ebd;
+}
+
+.btn-warning {
+ color: #ffffff;
+ background-color: #f0ad4e;
+ border-color: #eea236;
+}
+
+.btn-warning:hover,
+.btn-warning:focus,
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+ color: #ffffff;
+ background-color: #ed9c28;
+ border-color: #d58512;
+}
+
+.btn-warning:active,
+.btn-warning.active,
+.open .dropdown-toggle.btn-warning {
+ background-image: none;
+}
+
+.btn-warning.disabled,
+.btn-warning[disabled],
+fieldset[disabled] .btn-warning,
+.btn-warning.disabled:hover,
+.btn-warning[disabled]:hover,
+fieldset[disabled] .btn-warning:hover,
+.btn-warning.disabled:focus,
+.btn-warning[disabled]:focus,
+fieldset[disabled] .btn-warning:focus,
+.btn-warning.disabled:active,
+.btn-warning[disabled]:active,
+fieldset[disabled] .btn-warning:active,
+.btn-warning.disabled.active,
+.btn-warning[disabled].active,
+fieldset[disabled] .btn-warning.active {
+ background-color: #f0ad4e;
+ border-color: #eea236;
+}
+
+.btn-danger {
+ color: #ffffff;
+ background-color: #d9534f;
+ border-color: #d43f3a;
+}
+
+.btn-danger:hover,
+.btn-danger:focus,
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+ color: #ffffff;
+ background-color: #d2322d;
+ border-color: #ac2925;
+}
+
+.btn-danger:active,
+.btn-danger.active,
+.open .dropdown-toggle.btn-danger {
+ background-image: none;
+}
+
+.btn-danger.disabled,
+.btn-danger[disabled],
+fieldset[disabled] .btn-danger,
+.btn-danger.disabled:hover,
+.btn-danger[disabled]:hover,
+fieldset[disabled] .btn-danger:hover,
+.btn-danger.disabled:focus,
+.btn-danger[disabled]:focus,
+fieldset[disabled] .btn-danger:focus,
+.btn-danger.disabled:active,
+.btn-danger[disabled]:active,
+fieldset[disabled] .btn-danger:active,
+.btn-danger.disabled.active,
+.btn-danger[disabled].active,
+fieldset[disabled] .btn-danger.active {
+ background-color: #d9534f;
+ border-color: #d43f3a;
+}
+
+.btn-success {
+ color: #ffffff;
+ background-color: #5cb85c;
+ border-color: #4cae4c;
+}
+
+.btn-success:hover,
+.btn-success:focus,
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+ color: #ffffff;
+ background-color: #47a447;
+ border-color: #398439;
+}
+
+.btn-success:active,
+.btn-success.active,
+.open .dropdown-toggle.btn-success {
+ background-image: none;
+}
+
+.btn-success.disabled,
+.btn-success[disabled],
+fieldset[disabled] .btn-success,
+.btn-success.disabled:hover,
+.btn-success[disabled]:hover,
+fieldset[disabled] .btn-success:hover,
+.btn-success.disabled:focus,
+.btn-success[disabled]:focus,
+fieldset[disabled] .btn-success:focus,
+.btn-success.disabled:active,
+.btn-success[disabled]:active,
+fieldset[disabled] .btn-success:active,
+.btn-success.disabled.active,
+.btn-success[disabled].active,
+fieldset[disabled] .btn-success.active {
+ background-color: #5cb85c;
+ border-color: #4cae4c;
+}
+
+.btn-info {
+ color: #ffffff;
+ background-color: #5bc0de;
+ border-color: #46b8da;
+}
+
+.btn-info:hover,
+.btn-info:focus,
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+ color: #ffffff;
+ background-color: #39b3d7;
+ border-color: #269abc;
+}
+
+.btn-info:active,
+.btn-info.active,
+.open .dropdown-toggle.btn-info {
+ background-image: none;
+}
+
+.btn-info.disabled,
+.btn-info[disabled],
+fieldset[disabled] .btn-info,
+.btn-info.disabled:hover,
+.btn-info[disabled]:hover,
+fieldset[disabled] .btn-info:hover,
+.btn-info.disabled:focus,
+.btn-info[disabled]:focus,
+fieldset[disabled] .btn-info:focus,
+.btn-info.disabled:active,
+.btn-info[disabled]:active,
+fieldset[disabled] .btn-info:active,
+.btn-info.disabled.active,
+.btn-info[disabled].active,
+fieldset[disabled] .btn-info.active {
+ background-color: #5bc0de;
+ border-color: #46b8da;
+}
+
+.btn-link {
+ font-weight: normal;
+ color: #428bca;
+ cursor: pointer;
+ border-radius: 0;
+}
+
+.btn-link,
+.btn-link:active,
+.btn-link[disabled],
+fieldset[disabled] .btn-link {
+ background-color: transparent;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+}
+
+.btn-link,
+.btn-link:hover,
+.btn-link:focus,
+.btn-link:active {
+ border-color: transparent;
+}
+
+.btn-link:hover,
+.btn-link:focus {
+ color: #2a6496;
+ text-decoration: underline;
+ background-color: transparent;
+}
+
+.btn-link[disabled]:hover,
+fieldset[disabled] .btn-link:hover,
+.btn-link[disabled]:focus,
+fieldset[disabled] .btn-link:focus {
+ color: #999999;
+ text-decoration: none;
+}
+
+.btn-lg {
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.33;
+ border-radius: 6px;
+}
+
+.btn-sm,
+.btn-xs {
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+
+.btn-xs {
+ padding: 1px 5px;
+}
+
+.btn-block {
+ display: block;
+ width: 100%;
+ padding-right: 0;
+ padding-left: 0;
+}
+
+.btn-block + .btn-block {
+ margin-top: 5px;
+}
+
+input[type="submit"].btn-block,
+input[type="reset"].btn-block,
+input[type="button"].btn-block {
+ width: 100%;
+}
+
+.fade {
+ opacity: 0;
+ -webkit-transition: opacity 0.15s linear;
+ transition: opacity 0.15s linear;
+}
+
+.fade.in {
+ opacity: 1;
+}
+
+.collapse {
+ display: none;
+}
+
+.collapse.in {
+ display: block;
+}
+
+.collapsing {
+ position: relative;
+ height: 0;
+ overflow: hidden;
+ -webkit-transition: height 0.35s ease;
+ transition: height 0.35s ease;
+}
+
+@font-face {
+ font-family: 'Glyphicons Halflings';
+ src: url('../fonts/glyphicons-halflings-regular.eot');
+ src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons-halflingsregular') format('svg');
+}
+
+.glyphicon {
+ position: relative;
+ top: 1px;
+ display: inline-block;
+ font-family: 'Glyphicons Halflings';
+ -webkit-font-smoothing: antialiased;
+ font-style: normal;
+ font-weight: normal;
+ line-height: 1;
+}
+
+.glyphicon-asterisk:before {
+ content: "\2a";
+}
+
+.glyphicon-plus:before {
+ content: "\2b";
+}
+
+.glyphicon-euro:before {
+ content: "\20ac";
+}
+
+.glyphicon-minus:before {
+ content: "\2212";
+}
+
+.glyphicon-cloud:before {
+ content: "\2601";
+}
+
+.glyphicon-envelope:before {
+ content: "\2709";
+}
+
+.glyphicon-pencil:before {
+ content: "\270f";
+}
+
+.glyphicon-glass:before {
+ content: "\e001";
+}
+
+.glyphicon-music:before {
+ content: "\e002";
+}
+
+.glyphicon-search:before {
+ content: "\e003";
+}
+
+.glyphicon-heart:before {
+ content: "\e005";
+}
+
+.glyphicon-star:before {
+ content: "\e006";
+}
+
+.glyphicon-star-empty:before {
+ content: "\e007";
+}
+
+.glyphicon-user:before {
+ content: "\e008";
+}
+
+.glyphicon-film:before {
+ content: "\e009";
+}
+
+.glyphicon-th-large:before {
+ content: "\e010";
+}
+
+.glyphicon-th:before {
+ content: "\e011";
+}
+
+.glyphicon-th-list:before {
+ content: "\e012";
+}
+
+.glyphicon-ok:before {
+ content: "\e013";
+}
+
+.glyphicon-remove:before {
+ content: "\e014";
+}
+
+.glyphicon-zoom-in:before {
+ content: "\e015";
+}
+
+.glyphicon-zoom-out:before {
+ content: "\e016";
+}
+
+.glyphicon-off:before {
+ content: "\e017";
+}
+
+.glyphicon-signal:before {
+ content: "\e018";
+}
+
+.glyphicon-cog:before {
+ content: "\e019";
+}
+
+.glyphicon-trash:before {
+ content: "\e020";
+}
+
+.glyphicon-home:before {
+ content: "\e021";
+}
+
+.glyphicon-file:before {
+ content: "\e022";
+}
+
+.glyphicon-time:before {
+ content: "\e023";
+}
+
+.glyphicon-road:before {
+ content: "\e024";
+}
+
+.glyphicon-download-alt:before {
+ content: "\e025";
+}
+
+.glyphicon-download:before {
+ content: "\e026";
+}
+
+.glyphicon-upload:before {
+ content: "\e027";
+}
+
+.glyphicon-inbox:before {
+ content: "\e028";
+}
+
+.glyphicon-play-circle:before {
+ content: "\e029";
+}
+
+.glyphicon-repeat:before {
+ content: "\e030";
+}
+
+.glyphicon-refresh:before {
+ content: "\e031";
+}
+
+.glyphicon-list-alt:before {
+ content: "\e032";
+}
+
+.glyphicon-flag:before {
+ content: "\e034";
+}
+
+.glyphicon-headphones:before {
+ content: "\e035";
+}
+
+.glyphicon-volume-off:before {
+ content: "\e036";
+}
+
+.glyphicon-volume-down:before {
+ content: "\e037";
+}
+
+.glyphicon-volume-up:before {
+ content: "\e038";
+}
+
+.glyphicon-qrcode:before {
+ content: "\e039";
+}
+
+.glyphicon-barcode:before {
+ content: "\e040";
+}
+
+.glyphicon-tag:before {
+ content: "\e041";
+}
+
+.glyphicon-tags:before {
+ content: "\e042";
+}
+
+.glyphicon-book:before {
+ content: "\e043";
+}
+
+.glyphicon-print:before {
+ content: "\e045";
+}
+
+.glyphicon-font:before {
+ content: "\e047";
+}
+
+.glyphicon-bold:before {
+ content: "\e048";
+}
+
+.glyphicon-italic:before {
+ content: "\e049";
+}
+
+.glyphicon-text-height:before {
+ content: "\e050";
+}
+
+.glyphicon-text-width:before {
+ content: "\e051";
+}
+
+.glyphicon-align-left:before {
+ content: "\e052";
+}
+
+.glyphicon-align-center:before {
+ content: "\e053";
+}
+
+.glyphicon-align-right:before {
+ content: "\e054";
+}
+
+.glyphicon-align-justify:before {
+ content: "\e055";
+}
+
+.glyphicon-list:before {
+ content: "\e056";
+}
+
+.glyphicon-indent-left:before {
+ content: "\e057";
+}
+
+.glyphicon-indent-right:before {
+ content: "\e058";
+}
+
+.glyphicon-facetime-video:before {
+ content: "\e059";
+}
+
+.glyphicon-picture:before {
+ content: "\e060";
+}
+
+.glyphicon-map-marker:before {
+ content: "\e062";
+}
+
+.glyphicon-adjust:before {
+ content: "\e063";
+}
+
+.glyphicon-tint:before {
+ content: "\e064";
+}
+
+.glyphicon-edit:before {
+ content: "\e065";
+}
+
+.glyphicon-share:before {
+ content: "\e066";
+}
+
+.glyphicon-check:before {
+ content: "\e067";
+}
+
+.glyphicon-move:before {
+ content: "\e068";
+}
+
+.glyphicon-step-backward:before {
+ content: "\e069";
+}
+
+.glyphicon-fast-backward:before {
+ content: "\e070";
+}
+
+.glyphicon-backward:before {
+ content: "\e071";
+}
+
+.glyphicon-play:before {
+ content: "\e072";
+}
+
+.glyphicon-pause:before {
+ content: "\e073";
+}
+
+.glyphicon-stop:before {
+ content: "\e074";
+}
+
+.glyphicon-forward:before {
+ content: "\e075";
+}
+
+.glyphicon-fast-forward:before {
+ content: "\e076";
+}
+
+.glyphicon-step-forward:before {
+ content: "\e077";
+}
+
+.glyphicon-eject:before {
+ content: "\e078";
+}
+
+.glyphicon-chevron-left:before {
+ content: "\e079";
+}
+
+.glyphicon-chevron-right:before {
+ content: "\e080";
+}
+
+.glyphicon-plus-sign:before {
+ content: "\e081";
+}
+
+.glyphicon-minus-sign:before {
+ content: "\e082";
+}
+
+.glyphicon-remove-sign:before {
+ content: "\e083";
+}
+
+.glyphicon-ok-sign:before {
+ content: "\e084";
+}
+
+.glyphicon-question-sign:before {
+ content: "\e085";
+}
+
+.glyphicon-info-sign:before {
+ content: "\e086";
+}
+
+.glyphicon-screenshot:before {
+ content: "\e087";
+}
+
+.glyphicon-remove-circle:before {
+ content: "\e088";
+}
+
+.glyphicon-ok-circle:before {
+ content: "\e089";
+}
+
+.glyphicon-ban-circle:before {
+ content: "\e090";
+}
+
+.glyphicon-arrow-left:before {
+ content: "\e091";
+}
+
+.glyphicon-arrow-right:before {
+ content: "\e092";
+}
+
+.glyphicon-arrow-up:before {
+ content: "\e093";
+}
+
+.glyphicon-arrow-down:before {
+ content: "\e094";
+}
+
+.glyphicon-share-alt:before {
+ content: "\e095";
+}
+
+.glyphicon-resize-full:before {
+ content: "\e096";
+}
+
+.glyphicon-resize-small:before {
+ content: "\e097";
+}
+
+.glyphicon-exclamation-sign:before {
+ content: "\e101";
+}
+
+.glyphicon-gift:before {
+ content: "\e102";
+}
+
+.glyphicon-leaf:before {
+ content: "\e103";
+}
+
+.glyphicon-eye-open:before {
+ content: "\e105";
+}
+
+.glyphicon-eye-close:before {
+ content: "\e106";
+}
+
+.glyphicon-warning-sign:before {
+ content: "\e107";
+}
+
+.glyphicon-plane:before {
+ content: "\e108";
+}
+
+.glyphicon-random:before {
+ content: "\e110";
+}
+
+.glyphicon-comment:before {
+ content: "\e111";
+}
+
+.glyphicon-magnet:before {
+ content: "\e112";
+}
+
+.glyphicon-chevron-up:before {
+ content: "\e113";
+}
+
+.glyphicon-chevron-down:before {
+ content: "\e114";
+}
+
+.glyphicon-retweet:before {
+ content: "\e115";
+}
+
+.glyphicon-shopping-cart:before {
+ content: "\e116";
+}
+
+.glyphicon-folder-close:before {
+ content: "\e117";
+}
+
+.glyphicon-folder-open:before {
+ content: "\e118";
+}
+
+.glyphicon-resize-vertical:before {
+ content: "\e119";
+}
+
+.glyphicon-resize-horizontal:before {
+ content: "\e120";
+}
+
+.glyphicon-hdd:before {
+ content: "\e121";
+}
+
+.glyphicon-bullhorn:before {
+ content: "\e122";
+}
+
+.glyphicon-certificate:before {
+ content: "\e124";
+}
+
+.glyphicon-thumbs-up:before {
+ content: "\e125";
+}
+
+.glyphicon-thumbs-down:before {
+ content: "\e126";
+}
+
+.glyphicon-hand-right:before {
+ content: "\e127";
+}
+
+.glyphicon-hand-left:before {
+ content: "\e128";
+}
+
+.glyphicon-hand-up:before {
+ content: "\e129";
+}
+
+.glyphicon-hand-down:before {
+ content: "\e130";
+}
+
+.glyphicon-circle-arrow-right:before {
+ content: "\e131";
+}
+
+.glyphicon-circle-arrow-left:before {
+ content: "\e132";
+}
+
+.glyphicon-circle-arrow-up:before {
+ content: "\e133";
+}
+
+.glyphicon-circle-arrow-down:before {
+ content: "\e134";
+}
+
+.glyphicon-globe:before {
+ content: "\e135";
+}
+
+.glyphicon-tasks:before {
+ content: "\e137";
+}
+
+.glyphicon-filter:before {
+ content: "\e138";
+}
+
+.glyphicon-fullscreen:before {
+ content: "\e140";
+}
+
+.glyphicon-dashboard:before {
+ content: "\e141";
+}
+
+.glyphicon-heart-empty:before {
+ content: "\e143";
+}
+
+.glyphicon-link:before {
+ content: "\e144";
+}
+
+.glyphicon-phone:before {
+ content: "\e145";
+}
+
+.glyphicon-usd:before {
+ content: "\e148";
+}
+
+.glyphicon-gbp:before {
+ content: "\e149";
+}
+
+.glyphicon-sort:before {
+ content: "\e150";
+}
+
+.glyphicon-sort-by-alphabet:before {
+ content: "\e151";
+}
+
+.glyphicon-sort-by-alphabet-alt:before {
+ content: "\e152";
+}
+
+.glyphicon-sort-by-order:before {
+ content: "\e153";
+}
+
+.glyphicon-sort-by-order-alt:before {
+ content: "\e154";
+}
+
+.glyphicon-sort-by-attributes:before {
+ content: "\e155";
+}
+
+.glyphicon-sort-by-attributes-alt:before {
+ content: "\e156";
+}
+
+.glyphicon-unchecked:before {
+ content: "\e157";
+}
+
+.glyphicon-expand:before {
+ content: "\e158";
+}
+
+.glyphicon-collapse-down:before {
+ content: "\e159";
+}
+
+.glyphicon-collapse-up:before {
+ content: "\e160";
+}
+
+.glyphicon-log-in:before {
+ content: "\e161";
+}
+
+.glyphicon-flash:before {
+ content: "\e162";
+}
+
+.glyphicon-log-out:before {
+ content: "\e163";
+}
+
+.glyphicon-new-window:before {
+ content: "\e164";
+}
+
+.glyphicon-record:before {
+ content: "\e165";
+}
+
+.glyphicon-save:before {
+ content: "\e166";
+}
+
+.glyphicon-open:before {
+ content: "\e167";
+}
+
+.glyphicon-saved:before {
+ content: "\e168";
+}
+
+.glyphicon-import:before {
+ content: "\e169";
+}
+
+.glyphicon-export:before {
+ content: "\e170";
+}
+
+.glyphicon-send:before {
+ content: "\e171";
+}
+
+.glyphicon-floppy-disk:before {
+ content: "\e172";
+}
+
+.glyphicon-floppy-saved:before {
+ content: "\e173";
+}
+
+.glyphicon-floppy-remove:before {
+ content: "\e174";
+}
+
+.glyphicon-floppy-save:before {
+ content: "\e175";
+}
+
+.glyphicon-floppy-open:before {
+ content: "\e176";
+}
+
+.glyphicon-credit-card:before {
+ content: "\e177";
+}
+
+.glyphicon-transfer:before {
+ content: "\e178";
+}
+
+.glyphicon-cutlery:before {
+ content: "\e179";
+}
+
+.glyphicon-header:before {
+ content: "\e180";
+}
+
+.glyphicon-compressed:before {
+ content: "\e181";
+}
+
+.glyphicon-earphone:before {
+ content: "\e182";
+}
+
+.glyphicon-phone-alt:before {
+ content: "\e183";
+}
+
+.glyphicon-tower:before {
+ content: "\e184";
+}
+
+.glyphicon-stats:before {
+ content: "\e185";
+}
+
+.glyphicon-sd-video:before {
+ content: "\e186";
+}
+
+.glyphicon-hd-video:before {
+ content: "\e187";
+}
+
+.glyphicon-subtitles:before {
+ content: "\e188";
+}
+
+.glyphicon-sound-stereo:before {
+ content: "\e189";
+}
+
+.glyphicon-sound-dolby:before {
+ content: "\e190";
+}
+
+.glyphicon-sound-5-1:before {
+ content: "\e191";
+}
+
+.glyphicon-sound-6-1:before {
+ content: "\e192";
+}
+
+.glyphicon-sound-7-1:before {
+ content: "\e193";
+}
+
+.glyphicon-copyright-mark:before {
+ content: "\e194";
+}
+
+.glyphicon-registration-mark:before {
+ content: "\e195";
+}
+
+.glyphicon-cloud-download:before {
+ content: "\e197";
+}
+
+.glyphicon-cloud-upload:before {
+ content: "\e198";
+}
+
+.glyphicon-tree-conifer:before {
+ content: "\e199";
+}
+
+.glyphicon-tree-deciduous:before {
+ content: "\e200";
+}
+
+.glyphicon-briefcase:before {
+ content: "\1f4bc";
+}
+
+.glyphicon-calendar:before {
+ content: "\1f4c5";
+}
+
+.glyphicon-pushpin:before {
+ content: "\1f4cc";
+}
+
+.glyphicon-paperclip:before {
+ content: "\1f4ce";
+}
+
+.glyphicon-camera:before {
+ content: "\1f4f7";
+}
+
+.glyphicon-lock:before {
+ content: "\1f512";
+}
+
+.glyphicon-bell:before {
+ content: "\1f514";
+}
+
+.glyphicon-bookmark:before {
+ content: "\1f516";
+}
+
+.glyphicon-fire:before {
+ content: "\1f525";
+}
+
+.glyphicon-wrench:before {
+ content: "\1f527";
+}
+
+.caret {
+ display: inline-block;
+ width: 0;
+ height: 0;
+ margin-left: 2px;
+ vertical-align: middle;
+ border-top: 4px solid #000000;
+ border-right: 4px solid transparent;
+ border-bottom: 0 dotted;
+ border-left: 4px solid transparent;
+ content: "";
+}
+
+.dropdown {
+ position: relative;
+}
+
+.dropdown-toggle:focus {
+ outline: 0;
+}
+
+.dropdown-menu {
+ position: absolute;
+ top: 100%;
+ left: 0;
+ z-index: 1000;
+ display: none;
+ float: left;
+ min-width: 160px;
+ padding: 5px 0;
+ margin: 2px 0 0;
+ font-size: 14px;
+ list-style: none;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ border: 1px solid rgba(0, 0, 0, 0.15);
+ border-radius: 4px;
+ -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
+ background-clip: padding-box;
+}
+
+.dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.dropdown-menu .divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5;
+}
+
+.dropdown-menu > li > a {
+ display: block;
+ padding: 3px 20px;
+ clear: both;
+ font-weight: normal;
+ line-height: 1.428571429;
+ color: #333333;
+ white-space: nowrap;
+}
+
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #428bca;
+}
+
+.dropdown-menu > .active > a,
+.dropdown-menu > .active > a:hover,
+.dropdown-menu > .active > a:focus {
+ color: #ffffff;
+ text-decoration: none;
+ background-color: #428bca;
+ outline: 0;
+}
+
+.dropdown-menu > .disabled > a,
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+ color: #999999;
+}
+
+.dropdown-menu > .disabled > a:hover,
+.dropdown-menu > .disabled > a:focus {
+ text-decoration: none;
+ cursor: not-allowed;
+ background-color: transparent;
+ background-image: none;
+ filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
+}
+
+.open > .dropdown-menu {
+ display: block;
+}
+
+.open > a {
+ outline: 0;
+}
+
+.dropdown-header {
+ display: block;
+ padding: 3px 20px;
+ font-size: 12px;
+ line-height: 1.428571429;
+ color: #999999;
+}
+
+.dropdown-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 990;
+}
+
+.pull-right > .dropdown-menu {
+ right: 0;
+ left: auto;
+}
+
+.dropup .caret,
+.navbar-fixed-bottom .dropdown .caret {
+ border-top: 0 dotted;
+ border-bottom: 4px solid #000000;
+ content: "";
+}
+
+.dropup .dropdown-menu,
+.navbar-fixed-bottom .dropdown .dropdown-menu {
+ top: auto;
+ bottom: 100%;
+ margin-bottom: 1px;
+}
+
+@media (min-width: 768px) {
+ .navbar-right .dropdown-menu {
+ right: 0;
+ left: auto;
+ }
+}
+
+.btn-default .caret {
+ border-top-color: #333333;
+}
+
+.btn-primary .caret,
+.btn-success .caret,
+.btn-warning .caret,
+.btn-danger .caret,
+.btn-info .caret {
+ border-top-color: #fff;
+}
+
+.dropup .btn-default .caret {
+ border-bottom-color: #333333;
+}
+
+.dropup .btn-primary .caret,
+.dropup .btn-success .caret,
+.dropup .btn-warning .caret,
+.dropup .btn-danger .caret,
+.dropup .btn-info .caret {
+ border-bottom-color: #fff;
+}
+
+.btn-group,
+.btn-group-vertical {
+ position: relative;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+.btn-group > .btn,
+.btn-group-vertical > .btn {
+ position: relative;
+ float: left;
+}
+
+.btn-group > .btn:hover,
+.btn-group-vertical > .btn:hover,
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus,
+.btn-group > .btn:active,
+.btn-group-vertical > .btn:active,
+.btn-group > .btn.active,
+.btn-group-vertical > .btn.active {
+ z-index: 2;
+}
+
+.btn-group > .btn:focus,
+.btn-group-vertical > .btn:focus {
+ outline: none;
+}
+
+.btn-group .btn + .btn,
+.btn-group .btn + .btn-group,
+.btn-group .btn-group + .btn,
+.btn-group .btn-group + .btn-group {
+ margin-left: -1px;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+ display: table;
+ content: " ";
+}
+
+.btn-toolbar:after {
+ clear: both;
+}
+
+.btn-toolbar:before,
+.btn-toolbar:after {
+ display: table;
+ content: " ";
+}
+
+.btn-toolbar:after {
+ clear: both;
+}
+
+.btn-toolbar .btn-group {
+ float: left;
+}
+
+.btn-toolbar > .btn + .btn,
+.btn-toolbar > .btn-group + .btn,
+.btn-toolbar > .btn + .btn-group,
+.btn-toolbar > .btn-group + .btn-group {
+ margin-left: 5px;
+}
+
+.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {
+ border-radius: 0;
+}
+
+.btn-group > .btn:first-child {
+ margin-left: 0;
+}
+
+.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn:last-child:not(:first-child),
+.btn-group > .dropdown-toggle:not(:first-child) {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+}
+
+.btn-group > .btn-group {
+ float: left;
+}
+
+.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {
+ border-radius: 0;
+}
+
+.btn-group > .btn-group:first-child > .btn:last-child,
+.btn-group > .btn-group:first-child > .dropdown-toggle {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.btn-group > .btn-group:last-child > .btn:first-child {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+}
+
+.btn-group .dropdown-toggle:active,
+.btn-group.open .dropdown-toggle {
+ outline: 0;
+}
+
+.btn-group-xs > .btn {
+ padding: 5px 10px;
+ padding: 1px 5px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+
+.btn-group-sm > .btn {
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+
+.btn-group-lg > .btn {
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.33;
+ border-radius: 6px;
+}
+
+.btn-group > .btn + .dropdown-toggle {
+ padding-right: 8px;
+ padding-left: 8px;
+}
+
+.btn-group > .btn-lg + .dropdown-toggle {
+ padding-right: 12px;
+ padding-left: 12px;
+}
+
+.btn-group.open .dropdown-toggle {
+ -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+ box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);
+}
+
+.btn .caret {
+ margin-left: 0;
+}
+
+.btn-lg .caret {
+ border-width: 5px 5px 0;
+ border-bottom-width: 0;
+}
+
+.dropup .btn-lg .caret {
+ border-width: 0 5px 5px;
+}
+
+.btn-group-vertical > .btn,
+.btn-group-vertical > .btn-group {
+ display: block;
+ float: none;
+ width: 100%;
+ max-width: 100%;
+}
+
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after {
+ display: table;
+ content: " ";
+}
+
+.btn-group-vertical > .btn-group:after {
+ clear: both;
+}
+
+.btn-group-vertical > .btn-group:before,
+.btn-group-vertical > .btn-group:after {
+ display: table;
+ content: " ";
+}
+
+.btn-group-vertical > .btn-group:after {
+ clear: both;
+}
+
+.btn-group-vertical > .btn-group > .btn {
+ float: none;
+}
+
+.btn-group-vertical > .btn + .btn,
+.btn-group-vertical > .btn + .btn-group,
+.btn-group-vertical > .btn-group + .btn,
+.btn-group-vertical > .btn-group + .btn-group {
+ margin-top: -1px;
+ margin-left: 0;
+}
+
+.btn-group-vertical > .btn:not(:first-child):not(:last-child) {
+ border-radius: 0;
+}
+
+.btn-group-vertical > .btn:first-child:not(:last-child) {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical > .btn:last-child:not(:first-child) {
+ border-top-right-radius: 0;
+ border-bottom-left-radius: 4px;
+ border-top-left-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {
+ border-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:first-child > .btn:last-child,
+.btn-group-vertical > .btn-group:first-child > .dropdown-toggle {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.btn-group-vertical > .btn-group:last-child > .btn:first-child {
+ border-top-right-radius: 0;
+ border-top-left-radius: 0;
+}
+
+.btn-group-justified {
+ display: table;
+ width: 100%;
+ border-collapse: separate;
+ table-layout: fixed;
+}
+
+.btn-group-justified .btn {
+ display: table-cell;
+ float: none;
+ width: 1%;
+}
+
+[data-toggle="buttons"] > .btn > input[type="radio"],
+[data-toggle="buttons"] > .btn > input[type="checkbox"] {
+ display: none;
+}
+
+.input-group {
+ position: relative;
+ display: table;
+ border-collapse: separate;
+}
+
+.input-group.col {
+ float: none;
+ padding-right: 0;
+ padding-left: 0;
+}
+
+.input-group .form-control {
+ width: 100%;
+ margin-bottom: 0;
+}
+
+.input-group-lg > .form-control,
+.input-group-lg > .input-group-addon,
+.input-group-lg > .input-group-btn > .btn {
+ height: 45px;
+ padding: 10px 16px;
+ font-size: 18px;
+ line-height: 1.33;
+ border-radius: 6px;
+}
+
+select.input-group-lg > .form-control,
+select.input-group-lg > .input-group-addon,
+select.input-group-lg > .input-group-btn > .btn {
+ height: 45px;
+ line-height: 45px;
+}
+
+textarea.input-group-lg > .form-control,
+textarea.input-group-lg > .input-group-addon,
+textarea.input-group-lg > .input-group-btn > .btn {
+ height: auto;
+}
+
+.input-group-sm > .form-control,
+.input-group-sm > .input-group-addon,
+.input-group-sm > .input-group-btn > .btn {
+ height: 30px;
+ padding: 5px 10px;
+ font-size: 12px;
+ line-height: 1.5;
+ border-radius: 3px;
+}
+
+select.input-group-sm > .form-control,
+select.input-group-sm > .input-group-addon,
+select.input-group-sm > .input-group-btn > .btn {
+ height: 30px;
+ line-height: 30px;
+}
+
+textarea.input-group-sm > .form-control,
+textarea.input-group-sm > .input-group-addon,
+textarea.input-group-sm > .input-group-btn > .btn {
+ height: auto;
+}
+
+.input-group-addon,
+.input-group-btn,
+.input-group .form-control {
+ display: table-cell;
+}
+
+.input-group-addon:not(:first-child):not(:last-child),
+.input-group-btn:not(:first-child):not(:last-child),
+.input-group .form-control:not(:first-child):not(:last-child) {
+ border-radius: 0;
+}
+
+.input-group-addon,
+.input-group-btn {
+ width: 1%;
+ white-space: nowrap;
+ vertical-align: middle;
+}
+
+.input-group-addon {
+ padding: 6px 12px;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 1;
+ text-align: center;
+ background-color: #eeeeee;
+ border: 1px solid #cccccc;
+ border-radius: 4px;
+}
+
+.input-group-addon.input-sm {
+ padding: 5px 10px;
+ font-size: 12px;
+ border-radius: 3px;
+}
+
+.input-group-addon.input-lg {
+ padding: 10px 16px;
+ font-size: 18px;
+ border-radius: 6px;
+}
+
+.input-group-addon input[type="radio"],
+.input-group-addon input[type="checkbox"] {
+ margin-top: 0;
+}
+
+.input-group .form-control:first-child,
+.input-group-addon:first-child,
+.input-group-btn:first-child > .btn,
+.input-group-btn:first-child > .dropdown-toggle,
+.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) {
+ border-top-right-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.input-group-addon:first-child {
+ border-right: 0;
+}
+
+.input-group .form-control:last-child,
+.input-group-addon:last-child,
+.input-group-btn:last-child > .btn,
+.input-group-btn:last-child > .dropdown-toggle,
+.input-group-btn:first-child > .btn:not(:first-child) {
+ border-bottom-left-radius: 0;
+ border-top-left-radius: 0;
+}
+
+.input-group-addon:last-child {
+ border-left: 0;
+}
+
+.input-group-btn {
+ position: relative;
+ white-space: nowrap;
+}
+
+.input-group-btn > .btn {
+ position: relative;
+}
+
+.input-group-btn > .btn + .btn {
+ margin-left: -4px;
+}
+
+.input-group-btn > .btn:hover,
+.input-group-btn > .btn:active {
+ z-index: 2;
+}
+
+.nav {
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none;
+}
+
+.nav:before,
+.nav:after {
+ display: table;
+ content: " ";
+}
+
+.nav:after {
+ clear: both;
+}
+
+.nav:before,
+.nav:after {
+ display: table;
+ content: " ";
+}
+
+.nav:after {
+ clear: both;
+}
+
+.nav > li {
+ position: relative;
+ display: block;
+}
+
+.nav > li > a {
+ position: relative;
+ display: block;
+ padding: 10px 15px;
+}
+
+.nav > li > a:hover,
+.nav > li > a:focus {
+ text-decoration: none;
+ background-color: #eeeeee;
+}
+
+.nav > li.disabled > a {
+ color: #999999;
+}
+
+.nav > li.disabled > a:hover,
+.nav > li.disabled > a:focus {
+ color: #999999;
+ text-decoration: none;
+ cursor: not-allowed;
+ background-color: transparent;
+}
+
+.nav .open > a,
+.nav .open > a:hover,
+.nav .open > a:focus {
+ background-color: #eeeeee;
+ border-color: #428bca;
+}
+
+.nav .nav-divider {
+ height: 1px;
+ margin: 9px 0;
+ overflow: hidden;
+ background-color: #e5e5e5;
+}
+
+.nav > li > a > img {
+ max-width: none;
+}
+
+.nav-tabs {
+ border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs > li {
+ float: left;
+ margin-bottom: -1px;
+}
+
+.nav-tabs > li > a {
+ margin-right: 2px;
+ line-height: 1.428571429;
+ border: 1px solid transparent;
+ border-radius: 4px 4px 0 0;
+}
+
+.nav-tabs > li > a:hover {
+ border-color: #eeeeee #eeeeee #dddddd;
+}
+
+.nav-tabs > li.active > a,
+.nav-tabs > li.active > a:hover,
+.nav-tabs > li.active > a:focus {
+ color: #555555;
+ cursor: default;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-bottom-color: transparent;
+}
+
+.nav-tabs.nav-justified {
+ width: 100%;
+ border-bottom: 0;
+}
+
+.nav-tabs.nav-justified > li {
+ float: none;
+}
+
+.nav-tabs.nav-justified > li > a {
+ text-align: center;
+}
+
+@media (min-width: 768px) {
+ .nav-tabs.nav-justified > li {
+ display: table-cell;
+ width: 1%;
+ }
+}
+
+.nav-tabs.nav-justified > li > a {
+ margin-right: 0;
+ border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs.nav-justified > .active > a {
+ border-bottom-color: #ffffff;
+}
+
+.nav-pills > li {
+ float: left;
+}
+
+.nav-pills > li > a {
+ border-radius: 5px;
+}
+
+.nav-pills > li + li {
+ margin-left: 2px;
+}
+
+.nav-pills > li.active > a,
+.nav-pills > li.active > a:hover,
+.nav-pills > li.active > a:focus {
+ color: #ffffff;
+ background-color: #428bca;
+}
+
+.nav-stacked > li {
+ float: none;
+}
+
+.nav-stacked > li + li {
+ margin-top: 2px;
+ margin-left: 0;
+}
+
+.nav-justified {
+ width: 100%;
+}
+
+.nav-justified > li {
+ float: none;
+}
+
+.nav-justified > li > a {
+ text-align: center;
+}
+
+@media (min-width: 768px) {
+ .nav-justified > li {
+ display: table-cell;
+ width: 1%;
+ }
+}
+
+.nav-tabs-justified {
+ border-bottom: 0;
+}
+
+.nav-tabs-justified > li > a {
+ margin-right: 0;
+ border-bottom: 1px solid #dddddd;
+}
+
+.nav-tabs-justified > .active > a {
+ border-bottom-color: #ffffff;
+}
+
+.tabbable:before,
+.tabbable:after {
+ display: table;
+ content: " ";
+}
+
+.tabbable:after {
+ clear: both;
+}
+
+.tabbable:before,
+.tabbable:after {
+ display: table;
+ content: " ";
+}
+
+.tabbable:after {
+ clear: both;
+}
+
+.tab-content > .tab-pane,
+.pill-content > .pill-pane {
+ display: none;
+}
+
+.tab-content > .active,
+.pill-content > .active {
+ display: block;
+}
+
+.nav .caret {
+ border-top-color: #428bca;
+ border-bottom-color: #428bca;
+}
+
+.nav a:hover .caret {
+ border-top-color: #2a6496;
+ border-bottom-color: #2a6496;
+}
+
+.nav-tabs .dropdown-menu {
+ margin-top: -1px;
+ border-top-right-radius: 0;
+ border-top-left-radius: 0;
+}
+
+.navbar {
+ position: relative;
+ z-index: 1000;
+ min-height: 50px;
+ margin-bottom: 20px;
+ border: 1px solid transparent;
+}
+
+.navbar:before,
+.navbar:after {
+ display: table;
+ content: " ";
+}
+
+.navbar:after {
+ clear: both;
+}
+
+.navbar:before,
+.navbar:after {
+ display: table;
+ content: " ";
+}
+
+.navbar:after {
+ clear: both;
+}
+
+@media (min-width: 768px) {
+ .navbar {
+ border-radius: 4px;
+ }
+}
+
+.navbar-header:before,
+.navbar-header:after {
+ display: table;
+ content: " ";
+}
+
+.navbar-header:after {
+ clear: both;
+}
+
+.navbar-header:before,
+.navbar-header:after {
+ display: table;
+ content: " ";
+}
+
+.navbar-header:after {
+ clear: both;
+}
+
+@media (min-width: 768px) {
+ .navbar-header {
+ float: left;
+ }
+}
+
+.navbar-collapse {
+ max-height: 340px;
+ padding-right: 15px;
+ padding-left: 15px;
+ overflow-x: visible;
+ border-top: 1px solid transparent;
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
+ -webkit-overflow-scrolling: touch;
+}
+
+.navbar-collapse:before,
+.navbar-collapse:after {
+ display: table;
+ content: " ";
+}
+
+.navbar-collapse:after {
+ clear: both;
+}
+
+.navbar-collapse:before,
+.navbar-collapse:after {
+ display: table;
+ content: " ";
+}
+
+.navbar-collapse:after {
+ clear: both;
+}
+
+.navbar-collapse.in {
+ overflow-y: auto;
+}
+
+@media (min-width: 768px) {
+ .navbar-collapse {
+ width: auto;
+ border-top: 0;
+ box-shadow: none;
+ }
+ .navbar-collapse.collapse {
+ display: block !important;
+ height: auto !important;
+ padding-bottom: 0;
+ overflow: visible !important;
+ }
+ .navbar-collapse.in {
+ overflow-y: visible;
+ }
+ .navbar-collapse .navbar-nav.navbar-left:first-child {
+ margin-left: -15px;
+ }
+ .navbar-collapse .navbar-nav.navbar-right:last-child {
+ margin-right: -15px;
+ }
+ .navbar-collapse .navbar-text:last-child {
+ margin-right: 0;
+ }
+}
+
+.container > .navbar-header,
+.container > .navbar-collapse {
+ margin-right: -15px;
+ margin-left: -15px;
+}
+
+@media (min-width: 768px) {
+ .container > .navbar-header,
+ .container > .navbar-collapse {
+ margin-right: 0;
+ margin-left: 0;
+ }
+}
+
+.navbar-static-top {
+ border-width: 0 0 1px;
+}
+
+@media (min-width: 768px) {
+ .navbar-static-top {
+ border-radius: 0;
+ }
+}
+
+.navbar-fixed-top,
+.navbar-fixed-bottom {
+ position: fixed;
+ right: 0;
+ left: 0;
+ border-width: 0 0 1px;
+}
+
+@media (min-width: 768px) {
+ .navbar-fixed-top,
+ .navbar-fixed-bottom {
+ border-radius: 0;
+ }
+}
+
+.navbar-fixed-top {
+ top: 0;
+ z-index: 1030;
+}
+
+.navbar-fixed-bottom {
+ bottom: 0;
+ margin-bottom: 0;
+}
+
+.navbar-brand {
+ float: left;
+ padding: 15px 15px;
+ font-size: 18px;
+ line-height: 20px;
+}
+
+.navbar-brand:hover,
+.navbar-brand:focus {
+ text-decoration: none;
+}
+
+@media (min-width: 768px) {
+ .navbar > .container .navbar-brand {
+ margin-left: -15px;
+ }
+}
+
+.navbar-toggle {
+ position: relative;
+ float: right;
+ padding: 9px 10px;
+ margin-top: 8px;
+ margin-right: 15px;
+ margin-bottom: 8px;
+ background-color: transparent;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+
+.navbar-toggle .icon-bar {
+ display: block;
+ width: 22px;
+ height: 2px;
+ border-radius: 1px;
+}
+
+.navbar-toggle .icon-bar + .icon-bar {
+ margin-top: 4px;
+}
+
+@media (min-width: 768px) {
+ .navbar-toggle {
+ display: none;
+ }
+}
+
+.navbar-nav {
+ margin: 7.5px -15px;
+}
+
+.navbar-nav > li > a {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ line-height: 20px;
+}
+
+@media (max-width: 767px) {
+ .navbar-nav .open .dropdown-menu {
+ position: static;
+ float: none;
+ width: auto;
+ margin-top: 0;
+ background-color: transparent;
+ border: 0;
+ box-shadow: none;
+ }
+ .navbar-nav .open .dropdown-menu > li > a,
+ .navbar-nav .open .dropdown-menu .dropdown-header {
+ padding: 5px 15px 5px 25px;
+ }
+ .navbar-nav .open .dropdown-menu > li > a {
+ line-height: 20px;
+ }
+ .navbar-nav .open .dropdown-menu > li > a:hover,
+ .navbar-nav .open .dropdown-menu > li > a:focus {
+ background-image: none;
+ }
+}
+
+@media (min-width: 768px) {
+ .navbar-nav {
+ float: left;
+ margin: 0;
+ }
+ .navbar-nav > li {
+ float: left;
+ }
+ .navbar-nav > li > a {
+ padding-top: 15px;
+ padding-bottom: 15px;
+ }
+}
+
+@media (min-width: 768px) {
+ .navbar-left {
+ float: left !important;
+ }
+ .navbar-right {
+ float: right !important;
+ }
+}
+
+.navbar-form {
+ padding: 10px 15px;
+ margin-top: 8px;
+ margin-right: -15px;
+ margin-bottom: 8px;
+ margin-left: -15px;
+ border-top: 1px solid transparent;
+ border-bottom: 1px solid transparent;
+ -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);
+}
+
+@media (min-width: 768px) {
+ .navbar-form .form-group {
+ display: inline-block;
+ margin-bottom: 0;
+ vertical-align: middle;
+ }
+ .navbar-form .form-control {
+ display: inline-block;
+ }
+ .navbar-form .radio,
+ .navbar-form .checkbox {
+ display: inline-block;
+ padding-left: 0;
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+ .navbar-form .radio input[type="radio"],
+ .navbar-form .checkbox input[type="checkbox"] {
+ float: none;
+ margin-left: 0;
+ }
+}
+
+@media (max-width: 767px) {
+ .navbar-form .form-group {
+ margin-bottom: 5px;
+ }
+}
+
+@media (min-width: 768px) {
+ .navbar-form {
+ width: auto;
+ padding-top: 0;
+ padding-bottom: 0;
+ margin-right: 0;
+ margin-left: 0;
+ border: 0;
+ -webkit-box-shadow: none;
+ box-shadow: none;
+ }
+}
+
+.navbar-nav > li > .dropdown-menu {
+ margin-top: 0;
+ border-top-right-radius: 0;
+ border-top-left-radius: 0;
+}
+
+.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {
+ border-bottom-right-radius: 0;
+ border-bottom-left-radius: 0;
+}
+
+.navbar-nav.pull-right > li > .dropdown-menu,
+.navbar-nav > li > .dropdown-menu.pull-right {
+ right: 0;
+ left: auto;
+}
+
+.navbar-btn {
+ margin-top: 8px;
+ margin-bottom: 8px;
+}
+
+.navbar-text {
+ float: left;
+ margin-top: 15px;
+ margin-bottom: 15px;
+}
+
+@media (min-width: 768px) {
+ .navbar-text {
+ margin-right: 15px;
+ margin-left: 15px;
+ }
+}
+
+.navbar-default {
+ background-color: #f8f8f8;
+ border-color: #e7e7e7;
+}
+
+.navbar-default .navbar-brand {
+ color: #777777;
+}
+
+.navbar-default .navbar-brand:hover,
+.navbar-default .navbar-brand:focus {
+ color: #5e5e5e;
+ background-color: transparent;
+}
+
+.navbar-default .navbar-text {
+ color: #777777;
+}
+
+.navbar-default .navbar-nav > li > a {
+ color: #777777;
+}
+
+.navbar-default .navbar-nav > li > a:hover,
+.navbar-default .navbar-nav > li > a:focus {
+ color: #333333;
+ background-color: transparent;
+}
+
+.navbar-default .navbar-nav > .active > a,
+.navbar-default .navbar-nav > .active > a:hover,
+.navbar-default .navbar-nav > .active > a:focus {
+ color: #555555;
+ background-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .disabled > a,
+.navbar-default .navbar-nav > .disabled > a:hover,
+.navbar-default .navbar-nav > .disabled > a:focus {
+ color: #cccccc;
+ background-color: transparent;
+}
+
+.navbar-default .navbar-toggle {
+ border-color: #dddddd;
+}
+
+.navbar-default .navbar-toggle:hover,
+.navbar-default .navbar-toggle:focus {
+ background-color: #dddddd;
+}
+
+.navbar-default .navbar-toggle .icon-bar {
+ background-color: #cccccc;
+}
+
+.navbar-default .navbar-collapse,
+.navbar-default .navbar-form {
+ border-color: #e6e6e6;
+}
+
+.navbar-default .navbar-nav > .dropdown > a:hover .caret,
+.navbar-default .navbar-nav > .dropdown > a:focus .caret {
+ border-top-color: #333333;
+ border-bottom-color: #333333;
+}
+
+.navbar-default .navbar-nav > .open > a,
+.navbar-default .navbar-nav > .open > a:hover,
+.navbar-default .navbar-nav > .open > a:focus {
+ color: #555555;
+ background-color: #e7e7e7;
+}
+
+.navbar-default .navbar-nav > .open > a .caret,
+.navbar-default .navbar-nav > .open > a:hover .caret,
+.navbar-default .navbar-nav > .open > a:focus .caret {
+ border-top-color: #555555;
+ border-bottom-color: #555555;
+}
+
+.navbar-default .navbar-nav > .dropdown > a .caret {
+ border-top-color: #777777;
+ border-bottom-color: #777777;
+}
+
+@media (max-width: 767px) {
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a {
+ color: #777777;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,
+ .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {
+ color: #333333;
+ background-color: transparent;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a,
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,
+ .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #555555;
+ background-color: #e7e7e7;
+ }
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+ .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+ color: #cccccc;
+ background-color: transparent;
+ }
+}
+
+.navbar-default .navbar-link {
+ color: #777777;
+}
+
+.navbar-default .navbar-link:hover {
+ color: #333333;
+}
+
+.navbar-inverse {
+ background-color: #222222;
+ border-color: #080808;
+}
+
+.navbar-inverse .navbar-brand {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-brand:hover,
+.navbar-inverse .navbar-brand:focus {
+ color: #ffffff;
+ background-color: transparent;
+}
+
+.navbar-inverse .navbar-text {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-nav > li > a:hover,
+.navbar-inverse .navbar-nav > li > a:focus {
+ color: #ffffff;
+ background-color: transparent;
+}
+
+.navbar-inverse .navbar-nav > .active > a,
+.navbar-inverse .navbar-nav > .active > a:hover,
+.navbar-inverse .navbar-nav > .active > a:focus {
+ color: #ffffff;
+ background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .disabled > a,
+.navbar-inverse .navbar-nav > .disabled > a:hover,
+.navbar-inverse .navbar-nav > .disabled > a:focus {
+ color: #444444;
+ background-color: transparent;
+}
+
+.navbar-inverse .navbar-toggle {
+ border-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle:hover,
+.navbar-inverse .navbar-toggle:focus {
+ background-color: #333333;
+}
+
+.navbar-inverse .navbar-toggle .icon-bar {
+ background-color: #ffffff;
+}
+
+.navbar-inverse .navbar-collapse,
+.navbar-inverse .navbar-form {
+ border-color: #101010;
+}
+
+.navbar-inverse .navbar-nav > .open > a,
+.navbar-inverse .navbar-nav > .open > a:hover,
+.navbar-inverse .navbar-nav > .open > a:focus {
+ color: #ffffff;
+ background-color: #080808;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a:hover .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+.navbar-inverse .navbar-nav > .dropdown > a .caret {
+ border-top-color: #999999;
+ border-bottom-color: #999999;
+}
+
+.navbar-inverse .navbar-nav > .open > a .caret,
+.navbar-inverse .navbar-nav > .open > a:hover .caret,
+.navbar-inverse .navbar-nav > .open > a:focus .caret {
+ border-top-color: #ffffff;
+ border-bottom-color: #ffffff;
+}
+
+@media (max-width: 767px) {
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {
+ border-color: #080808;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {
+ color: #999999;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {
+ color: #ffffff;
+ background-color: transparent;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {
+ color: #ffffff;
+ background-color: #080808;
+ }
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,
+ .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {
+ color: #444444;
+ background-color: transparent;
+ }
+}
+
+.navbar-inverse .navbar-link {
+ color: #999999;
+}
+
+.navbar-inverse .navbar-link:hover {
+ color: #ffffff;
+}
+
+.breadcrumb {
+ padding: 8px 15px;
+ margin-bottom: 20px;
+ list-style: none;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+}
+
+.breadcrumb > li {
+ display: inline-block;
+}
+
+.breadcrumb > li + li:before {
+ padding: 0 5px;
+ color: #cccccc;
+ content: "/\00a0";
+}
+
+.breadcrumb > .active {
+ color: #999999;
+}
+
+.pagination {
+ display: inline-block;
+ padding-left: 0;
+ margin: 20px 0;
+ border-radius: 4px;
+}
+
+.pagination > li {
+ display: inline;
+}
+
+.pagination > li > a,
+.pagination > li > span {
+ position: relative;
+ float: left;
+ padding: 6px 12px;
+ margin-left: -1px;
+ line-height: 1.428571429;
+ text-decoration: none;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+}
+
+.pagination > li:first-child > a,
+.pagination > li:first-child > span {
+ margin-left: 0;
+ border-bottom-left-radius: 4px;
+ border-top-left-radius: 4px;
+}
+
+.pagination > li:last-child > a,
+.pagination > li:last-child > span {
+ border-top-right-radius: 4px;
+ border-bottom-right-radius: 4px;
+}
+
+.pagination > li > a:hover,
+.pagination > li > span:hover,
+.pagination > li > a:focus,
+.pagination > li > span:focus {
+ background-color: #eeeeee;
+}
+
+.pagination > .active > a,
+.pagination > .active > span,
+.pagination > .active > a:hover,
+.pagination > .active > span:hover,
+.pagination > .active > a:focus,
+.pagination > .active > span:focus {
+ z-index: 2;
+ color: #ffffff;
+ cursor: default;
+ background-color: #428bca;
+ border-color: #428bca;
+}
+
+.pagination > .disabled > span,
+.pagination > .disabled > a,
+.pagination > .disabled > a:hover,
+.pagination > .disabled > a:focus {
+ color: #999999;
+ cursor: not-allowed;
+ background-color: #ffffff;
+ border-color: #dddddd;
+}
+
+.pagination-lg > li > a,
+.pagination-lg > li > span {
+ padding: 10px 16px;
+ font-size: 18px;
+}
+
+.pagination-lg > li:first-child > a,
+.pagination-lg > li:first-child > span {
+ border-bottom-left-radius: 6px;
+ border-top-left-radius: 6px;
+}
+
+.pagination-lg > li:last-child > a,
+.pagination-lg > li:last-child > span {
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+.pagination-sm > li > a,
+.pagination-sm > li > span {
+ padding: 5px 10px;
+ font-size: 12px;
+}
+
+.pagination-sm > li:first-child > a,
+.pagination-sm > li:first-child > span {
+ border-bottom-left-radius: 3px;
+ border-top-left-radius: 3px;
+}
+
+.pagination-sm > li:last-child > a,
+.pagination-sm > li:last-child > span {
+ border-top-right-radius: 3px;
+ border-bottom-right-radius: 3px;
+}
+
+.pager {
+ padding-left: 0;
+ margin: 20px 0;
+ text-align: center;
+ list-style: none;
+}
+
+.pager:before,
+.pager:after {
+ display: table;
+ content: " ";
+}
+
+.pager:after {
+ clear: both;
+}
+
+.pager:before,
+.pager:after {
+ display: table;
+ content: " ";
+}
+
+.pager:after {
+ clear: both;
+}
+
+.pager li {
+ display: inline;
+}
+
+.pager li > a,
+.pager li > span {
+ display: inline-block;
+ padding: 5px 14px;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-radius: 15px;
+}
+
+.pager li > a:hover,
+.pager li > a:focus {
+ text-decoration: none;
+ background-color: #eeeeee;
+}
+
+.pager .next > a,
+.pager .next > span {
+ float: right;
+}
+
+.pager .previous > a,
+.pager .previous > span {
+ float: left;
+}
+
+.pager .disabled > a,
+.pager .disabled > a:hover,
+.pager .disabled > a:focus,
+.pager .disabled > span {
+ color: #999999;
+ cursor: not-allowed;
+ background-color: #ffffff;
+}
+
+.label {
+ display: inline;
+ padding: .2em .6em .3em;
+ font-size: 75%;
+ font-weight: bold;
+ line-height: 1;
+ color: #ffffff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ border-radius: .25em;
+}
+
+.label[href]:hover,
+.label[href]:focus {
+ color: #ffffff;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.label:empty {
+ display: none;
+}
+
+.label-default {
+ background-color: #999999;
+}
+
+.label-default[href]:hover,
+.label-default[href]:focus {
+ background-color: #808080;
+}
+
+.label-primary {
+ background-color: #428bca;
+}
+
+.label-primary[href]:hover,
+.label-primary[href]:focus {
+ background-color: #3071a9;
+}
+
+.label-success {
+ background-color: #5cb85c;
+}
+
+.label-success[href]:hover,
+.label-success[href]:focus {
+ background-color: #449d44;
+}
+
+.label-info {
+ background-color: #5bc0de;
+}
+
+.label-info[href]:hover,
+.label-info[href]:focus {
+ background-color: #31b0d5;
+}
+
+.label-warning {
+ background-color: #f0ad4e;
+}
+
+.label-warning[href]:hover,
+.label-warning[href]:focus {
+ background-color: #ec971f;
+}
+
+.label-danger {
+ background-color: #d9534f;
+}
+
+.label-danger[href]:hover,
+.label-danger[href]:focus {
+ background-color: #c9302c;
+}
+
+.badge {
+ display: inline-block;
+ min-width: 10px;
+ padding: 3px 7px;
+ font-size: 12px;
+ font-weight: bold;
+ line-height: 1;
+ color: #ffffff;
+ text-align: center;
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #999999;
+ border-radius: 10px;
+}
+
+.badge:empty {
+ display: none;
+}
+
+a.badge:hover,
+a.badge:focus {
+ color: #ffffff;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+.btn .badge {
+ position: relative;
+ top: -1px;
+}
+
+a.list-group-item.active > .badge,
+.nav-pills > .active > a > .badge {
+ color: #428bca;
+ background-color: #ffffff;
+}
+
+.nav-pills > li > a > .badge {
+ margin-left: 3px;
+}
+
+.jumbotron {
+ padding: 30px;
+ margin-bottom: 30px;
+ font-size: 21px;
+ font-weight: 200;
+ line-height: 2.1428571435;
+ color: inherit;
+ background-color: #eeeeee;
+}
+
+.jumbotron h1 {
+ line-height: 1;
+ color: inherit;
+}
+
+.jumbotron p {
+ line-height: 1.4;
+}
+
+.container .jumbotron {
+ border-radius: 6px;
+}
+
+@media screen and (min-width: 768px) {
+ .jumbotron {
+ padding-top: 48px;
+ padding-bottom: 48px;
+ }
+ .container .jumbotron {
+ padding-right: 60px;
+ padding-left: 60px;
+ }
+ .jumbotron h1 {
+ font-size: 63px;
+ }
+}
+
+.thumbnail {
+ display: inline-block;
+ display: block;
+ height: auto;
+ max-width: 100%;
+ padding: 4px;
+ line-height: 1.428571429;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+ border-radius: 4px;
+ -webkit-transition: all 0.2s ease-in-out;
+ transition: all 0.2s ease-in-out;
+}
+
+.thumbnail > img {
+ display: block;
+ height: auto;
+ max-width: 100%;
+}
+
+a.thumbnail:hover,
+a.thumbnail:focus {
+ border-color: #428bca;
+}
+
+.thumbnail > img {
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.thumbnail .caption {
+ padding: 9px;
+ color: #333333;
+}
+
+.alert {
+ padding: 15px;
+ margin-bottom: 20px;
+ border: 1px solid transparent;
+ border-radius: 4px;
+}
+
+.alert h4 {
+ margin-top: 0;
+ color: inherit;
+}
+
+.alert .alert-link {
+ font-weight: bold;
+}
+
+.alert > p,
+.alert > ul {
+ margin-bottom: 0;
+}
+
+.alert > p + p {
+ margin-top: 5px;
+}
+
+.alert-dismissable {
+ padding-right: 35px;
+}
+
+.alert-dismissable .close {
+ position: relative;
+ top: -2px;
+ right: -21px;
+ color: inherit;
+}
+
+.alert-success {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+
+.alert-success hr {
+ border-top-color: #c9e2b3;
+}
+
+.alert-success .alert-link {
+ color: #356635;
+}
+
+.alert-info {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+
+.alert-info hr {
+ border-top-color: #a6e1ec;
+}
+
+.alert-info .alert-link {
+ color: #2d6987;
+}
+
+.alert-warning {
+ color: #c09853;
+ background-color: #fcf8e3;
+ border-color: #fbeed5;
+}
+
+.alert-warning hr {
+ border-top-color: #f8e5be;
+}
+
+.alert-warning .alert-link {
+ color: #a47e3c;
+}
+
+.alert-danger {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #eed3d7;
+}
+
+.alert-danger hr {
+ border-top-color: #e6c1c7;
+}
+
+.alert-danger .alert-link {
+ color: #953b39;
+}
+
+@-webkit-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-moz-keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+@-o-keyframes progress-bar-stripes {
+ from {
+ background-position: 0 0;
+ }
+ to {
+ background-position: 40px 0;
+ }
+}
+
+@keyframes progress-bar-stripes {
+ from {
+ background-position: 40px 0;
+ }
+ to {
+ background-position: 0 0;
+ }
+}
+
+.progress {
+ height: 20px;
+ margin-bottom: 20px;
+ overflow: hidden;
+ background-color: #f5f5f5;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+ box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
+}
+
+.progress-bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 12px;
+ color: #ffffff;
+ text-align: center;
+ background-color: #428bca;
+ -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ -webkit-transition: width 0.6s ease;
+ transition: width 0.6s ease;
+}
+
+.progress-striped .progress-bar {
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-size: 40px 40px;
+}
+
+.progress.active .progress-bar {
+ -webkit-animation: progress-bar-stripes 2s linear infinite;
+ -moz-animation: progress-bar-stripes 2s linear infinite;
+ -ms-animation: progress-bar-stripes 2s linear infinite;
+ -o-animation: progress-bar-stripes 2s linear infinite;
+ animation: progress-bar-stripes 2s linear infinite;
+}
+
+.progress-bar-success {
+ background-color: #5cb85c;
+}
+
+.progress-striped .progress-bar-success {
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-info {
+ background-color: #5bc0de;
+}
+
+.progress-striped .progress-bar-info {
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-warning {
+ background-color: #f0ad4e;
+}
+
+.progress-striped .progress-bar-warning {
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.progress-bar-danger {
+ background-color: #d9534f;
+}
+
+.progress-striped .progress-bar-danger {
+ background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
+ background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+ background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
+}
+
+.media,
+.media-body {
+ overflow: hidden;
+ zoom: 1;
+}
+
+.media,
+.media .media {
+ margin-top: 15px;
+}
+
+.media:first-child {
+ margin-top: 0;
+}
+
+.media-object {
+ display: block;
+}
+
+.media-heading {
+ margin: 0 0 5px;
+}
+
+.media > .pull-left {
+ margin-right: 10px;
+}
+
+.media > .pull-right {
+ margin-left: 10px;
+}
+
+.media-list {
+ padding-left: 0;
+ list-style: none;
+}
+
+.list-group {
+ padding-left: 0;
+ margin-bottom: 20px;
+}
+
+.list-group-item {
+ position: relative;
+ display: block;
+ padding: 10px 15px;
+ margin-bottom: -1px;
+ background-color: #ffffff;
+ border: 1px solid #dddddd;
+}
+
+.list-group-item:first-child {
+ border-top-right-radius: 4px;
+ border-top-left-radius: 4px;
+}
+
+.list-group-item:last-child {
+ margin-bottom: 0;
+ border-bottom-right-radius: 4px;
+ border-bottom-left-radius: 4px;
+}
+
+.list-group-item > .badge {
+ float: right;
+}
+
+.list-group-item > .badge + .badge {
+ margin-right: 5px;
+}
+
+a.list-group-item {
+ color: #555555;
+}
+
+a.list-group-item .list-group-item-heading {
+ color: #333333;
+}
+
+a.list-group-item:hover,
+a.list-group-item:focus {
+ text-decoration: none;
+ background-color: #f5f5f5;
+}
+
+.list-group-item.active,
+.list-group-item.active:hover,
+.list-group-item.active:focus {
+ z-index: 2;
+ color: #ffffff;
+ background-color: #428bca;
+ border-color: #428bca;
+}
+
+.list-group-item.active .list-group-item-heading,
+.list-group-item.active:hover .list-group-item-heading,
+.list-group-item.active:focus .list-group-item-heading {
+ color: inherit;
+}
+
+.list-group-item.active .list-group-item-text,
+.list-group-item.active:hover .list-group-item-text,
+.list-group-item.active:focus .list-group-item-text {
+ color: #e1edf7;
+}
+
+.list-group-item-heading {
+ margin-top: 0;
+ margin-bottom: 5px;
+}
+
+.list-group-item-text {
+ margin-bottom: 0;
+ line-height: 1.3;
+}
+
+.panel {
+ margin-bottom: 20px;
+ background-color: #ffffff;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.panel-body {
+ padding: 15px;
+}
+
+.panel-body:before,
+.panel-body:after {
+ display: table;
+ content: " ";
+}
+
+.panel-body:after {
+ clear: both;
+}
+
+.panel-body:before,
+.panel-body:after {
+ display: table;
+ content: " ";
+}
+
+.panel-body:after {
+ clear: both;
+}
+
+.panel > .list-group {
+ margin-bottom: 0;
+}
+
+.panel > .list-group .list-group-item {
+ border-width: 1px 0;
+}
+
+.panel > .list-group .list-group-item:first-child {
+ border-top-right-radius: 0;
+ border-top-left-radius: 0;
+}
+
+.panel > .list-group .list-group-item:last-child {
+ border-bottom: 0;
+}
+
+.panel-heading + .list-group .list-group-item:first-child {
+ border-top-width: 0;
+}
+
+.panel > .table {
+ margin-bottom: 0;
+}
+
+.panel > .panel-body + .table {
+ border-top: 1px solid #dddddd;
+}
+
+.panel-heading {
+ padding: 10px 15px;
+ border-bottom: 1px solid transparent;
+ border-top-right-radius: 3px;
+ border-top-left-radius: 3px;
+}
+
+.panel-title {
+ margin-top: 0;
+ margin-bottom: 0;
+ font-size: 16px;
+}
+
+.panel-title > a {
+ color: inherit;
+}
+
+.panel-footer {
+ padding: 10px 15px;
+ background-color: #f5f5f5;
+ border-top: 1px solid #dddddd;
+ border-bottom-right-radius: 3px;
+ border-bottom-left-radius: 3px;
+}
+
+.panel-group .panel {
+ margin-bottom: 0;
+ overflow: hidden;
+ border-radius: 4px;
+}
+
+.panel-group .panel + .panel {
+ margin-top: 5px;
+}
+
+.panel-group .panel-heading {
+ border-bottom: 0;
+}
+
+.panel-group .panel-heading + .panel-collapse .panel-body {
+ border-top: 1px solid #dddddd;
+}
+
+.panel-group .panel-footer {
+ border-top: 0;
+}
+
+.panel-group .panel-footer + .panel-collapse .panel-body {
+ border-bottom: 1px solid #dddddd;
+}
+
+.panel-default {
+ border-color: #dddddd;
+}
+
+.panel-default > .panel-heading {
+ color: #333333;
+ background-color: #f5f5f5;
+ border-color: #dddddd;
+}
+
+.panel-default > .panel-heading + .panel-collapse .panel-body {
+ border-top-color: #dddddd;
+}
+
+.panel-default > .panel-footer + .panel-collapse .panel-body {
+ border-bottom-color: #dddddd;
+}
+
+.panel-primary {
+ border-color: #428bca;
+}
+
+.panel-primary > .panel-heading {
+ color: #ffffff;
+ background-color: #428bca;
+ border-color: #428bca;
+}
+
+.panel-primary > .panel-heading + .panel-collapse .panel-body {
+ border-top-color: #428bca;
+}
+
+.panel-primary > .panel-footer + .panel-collapse .panel-body {
+ border-bottom-color: #428bca;
+}
+
+.panel-success {
+ border-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading {
+ color: #468847;
+ background-color: #dff0d8;
+ border-color: #d6e9c6;
+}
+
+.panel-success > .panel-heading + .panel-collapse .panel-body {
+ border-top-color: #d6e9c6;
+}
+
+.panel-success > .panel-footer + .panel-collapse .panel-body {
+ border-bottom-color: #d6e9c6;
+}
+
+.panel-warning {
+ border-color: #fbeed5;
+}
+
+.panel-warning > .panel-heading {
+ color: #c09853;
+ background-color: #fcf8e3;
+ border-color: #fbeed5;
+}
+
+.panel-warning > .panel-heading + .panel-collapse .panel-body {
+ border-top-color: #fbeed5;
+}
+
+.panel-warning > .panel-footer + .panel-collapse .panel-body {
+ border-bottom-color: #fbeed5;
+}
+
+.panel-danger {
+ border-color: #eed3d7;
+}
+
+.panel-danger > .panel-heading {
+ color: #b94a48;
+ background-color: #f2dede;
+ border-color: #eed3d7;
+}
+
+.panel-danger > .panel-heading + .panel-collapse .panel-body {
+ border-top-color: #eed3d7;
+}
+
+.panel-danger > .panel-footer + .panel-collapse .panel-body {
+ border-bottom-color: #eed3d7;
+}
+
+.panel-info {
+ border-color: #bce8f1;
+}
+
+.panel-info > .panel-heading {
+ color: #3a87ad;
+ background-color: #d9edf7;
+ border-color: #bce8f1;
+}
+
+.panel-info > .panel-heading + .panel-collapse .panel-body {
+ border-top-color: #bce8f1;
+}
+
+.panel-info > .panel-footer + .panel-collapse .panel-body {
+ border-bottom-color: #bce8f1;
+}
+
+.well {
+ min-height: 20px;
+ padding: 19px;
+ margin-bottom: 20px;
+ background-color: #f5f5f5;
+ border: 1px solid #e3e3e3;
+ border-radius: 4px;
+ -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+ box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
+}
+
+.well blockquote {
+ border-color: #ddd;
+ border-color: rgba(0, 0, 0, 0.15);
+}
+
+.well-lg {
+ padding: 24px;
+ border-radius: 6px;
+}
+
+.well-sm {
+ padding: 9px;
+ border-radius: 3px;
+}
+
+.close {
+ float: right;
+ font-size: 21px;
+ font-weight: bold;
+ line-height: 1;
+ color: #000000;
+ text-shadow: 0 1px 0 #ffffff;
+ opacity: 0.2;
+ filter: alpha(opacity=20);
+}
+
+.close:hover,
+.close:focus {
+ color: #000000;
+ text-decoration: none;
+ cursor: pointer;
+ opacity: 0.5;
+ filter: alpha(opacity=50);
+}
+
+button.close {
+ padding: 0;
+ cursor: pointer;
+ background: transparent;
+ border: 0;
+ -webkit-appearance: none;
+}
+
+.modal-open {
+ overflow: hidden;
+}
+
+body.modal-open,
+.modal-open .navbar-fixed-top,
+.modal-open .navbar-fixed-bottom {
+ margin-right: 15px;
+}
+
+.modal {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ display: none;
+ overflow: auto;
+ overflow-y: scroll;
+}
+
+.modal.fade .modal-dialog {
+ -webkit-transform: translate(0, -25%);
+ -ms-transform: translate(0, -25%);
+ transform: translate(0, -25%);
+ -webkit-transition: -webkit-transform 0.3s ease-out;
+ -moz-transition: -moz-transform 0.3s ease-out;
+ -o-transition: -o-transform 0.3s ease-out;
+ transition: transform 0.3s ease-out;
+}
+
+.modal.in .modal-dialog {
+ -webkit-transform: translate(0, 0);
+ -ms-transform: translate(0, 0);
+ transform: translate(0, 0);
+}
+
+.modal-dialog {
+ z-index: 1050;
+ width: auto;
+ padding: 10px;
+ margin-right: auto;
+ margin-left: auto;
+}
+
+.modal-content {
+ position: relative;
+ background-color: #ffffff;
+ border: 1px solid #999999;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 6px;
+ outline: none;
+ -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+ box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);
+ background-clip: padding-box;
+}
+
+.modal-backdrop {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1030;
+ background-color: #000000;
+}
+
+.modal-backdrop.fade {
+ opacity: 0;
+ filter: alpha(opacity=0);
+}
+
+.modal-backdrop.in {
+ opacity: 0.5;
+ filter: alpha(opacity=50);
+}
+
+.modal-header {
+ min-height: 16.428571429px;
+ padding: 15px;
+ border-bottom: 1px solid #e5e5e5;
+}
+
+.modal-header .close {
+ margin-top: -2px;
+}
+
+.modal-title {
+ margin: 0;
+ line-height: 1.428571429;
+}
+
+.modal-body {
+ position: relative;
+ padding: 20px;
+}
+
+.modal-footer {
+ padding: 19px 20px 20px;
+ margin-top: 15px;
+ text-align: right;
+ border-top: 1px solid #e5e5e5;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+ display: table;
+ content: " ";
+}
+
+.modal-footer:after {
+ clear: both;
+}
+
+.modal-footer:before,
+.modal-footer:after {
+ display: table;
+ content: " ";
+}
+
+.modal-footer:after {
+ clear: both;
+}
+
+.modal-footer .btn + .btn {
+ margin-bottom: 0;
+ margin-left: 5px;
+}
+
+.modal-footer .btn-group .btn + .btn {
+ margin-left: -1px;
+}
+
+.modal-footer .btn-block + .btn-block {
+ margin-left: 0;
+}
+
+@media screen and (min-width: 768px) {
+ .modal-dialog {
+ right: auto;
+ left: 50%;
+ width: 600px;
+ padding-top: 30px;
+ padding-bottom: 30px;
+ }
+ .modal-content {
+ -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);
+ }
+}
+
+.tooltip {
+ position: absolute;
+ z-index: 1030;
+ display: block;
+ font-size: 12px;
+ line-height: 1.4;
+ opacity: 0;
+ filter: alpha(opacity=0);
+ visibility: visible;
+}
+
+.tooltip.in {
+ opacity: 0.9;
+ filter: alpha(opacity=90);
+}
+
+.tooltip.top {
+ padding: 5px 0;
+ margin-top: -3px;
+}
+
+.tooltip.right {
+ padding: 0 5px;
+ margin-left: 3px;
+}
+
+.tooltip.bottom {
+ padding: 5px 0;
+ margin-top: 3px;
+}
+
+.tooltip.left {
+ padding: 0 5px;
+ margin-left: -3px;
+}
+
+.tooltip-inner {
+ max-width: 200px;
+ padding: 3px 8px;
+ color: #ffffff;
+ text-align: center;
+ text-decoration: none;
+ background-color: #000000;
+ border-radius: 4px;
+}
+
+.tooltip-arrow {
+ position: absolute;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.tooltip.top .tooltip-arrow {
+ bottom: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-top-color: #000000;
+ border-width: 5px 5px 0;
+}
+
+.tooltip.top-left .tooltip-arrow {
+ bottom: 0;
+ left: 5px;
+ border-top-color: #000000;
+ border-width: 5px 5px 0;
+}
+
+.tooltip.top-right .tooltip-arrow {
+ right: 5px;
+ bottom: 0;
+ border-top-color: #000000;
+ border-width: 5px 5px 0;
+}
+
+.tooltip.right .tooltip-arrow {
+ top: 50%;
+ left: 0;
+ margin-top: -5px;
+ border-right-color: #000000;
+ border-width: 5px 5px 5px 0;
+}
+
+.tooltip.left .tooltip-arrow {
+ top: 50%;
+ right: 0;
+ margin-top: -5px;
+ border-left-color: #000000;
+ border-width: 5px 0 5px 5px;
+}
+
+.tooltip.bottom .tooltip-arrow {
+ top: 0;
+ left: 50%;
+ margin-left: -5px;
+ border-bottom-color: #000000;
+ border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-left .tooltip-arrow {
+ top: 0;
+ left: 5px;
+ border-bottom-color: #000000;
+ border-width: 0 5px 5px;
+}
+
+.tooltip.bottom-right .tooltip-arrow {
+ top: 0;
+ right: 5px;
+ border-bottom-color: #000000;
+ border-width: 0 5px 5px;
+}
+
+.popover {
+ position: absolute;
+ top: 0;
+ left: 0;
+ z-index: 1010;
+ display: none;
+ max-width: 276px;
+ padding: 1px;
+ text-align: left;
+ white-space: normal;
+ background-color: #ffffff;
+ border: 1px solid #cccccc;
+ border: 1px solid rgba(0, 0, 0, 0.2);
+ border-radius: 6px;
+ -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
+ background-clip: padding-box;
+}
+
+.popover.top {
+ margin-top: -10px;
+}
+
+.popover.right {
+ margin-left: 10px;
+}
+
+.popover.bottom {
+ margin-top: 10px;
+}
+
+.popover.left {
+ margin-left: -10px;
+}
+
+.popover-title {
+ padding: 8px 14px;
+ margin: 0;
+ font-size: 14px;
+ font-weight: normal;
+ line-height: 18px;
+ background-color: #f7f7f7;
+ border-bottom: 1px solid #ebebeb;
+ border-radius: 5px 5px 0 0;
+}
+
+.popover-content {
+ padding: 9px 14px;
+}
+
+.popover .arrow,
+.popover .arrow:after {
+ position: absolute;
+ display: block;
+ width: 0;
+ height: 0;
+ border-color: transparent;
+ border-style: solid;
+}
+
+.popover .arrow {
+ border-width: 11px;
+}
+
+.popover .arrow:after {
+ border-width: 10px;
+ content: "";
+}
+
+.popover.top .arrow {
+ bottom: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-top-color: #999999;
+ border-top-color: rgba(0, 0, 0, 0.25);
+ border-bottom-width: 0;
+}
+
+.popover.top .arrow:after {
+ bottom: 1px;
+ margin-left: -10px;
+ border-top-color: #ffffff;
+ border-bottom-width: 0;
+ content: " ";
+}
+
+.popover.right .arrow {
+ top: 50%;
+ left: -11px;
+ margin-top: -11px;
+ border-right-color: #999999;
+ border-right-color: rgba(0, 0, 0, 0.25);
+ border-left-width: 0;
+}
+
+.popover.right .arrow:after {
+ bottom: -10px;
+ left: 1px;
+ border-right-color: #ffffff;
+ border-left-width: 0;
+ content: " ";
+}
+
+.popover.bottom .arrow {
+ top: -11px;
+ left: 50%;
+ margin-left: -11px;
+ border-bottom-color: #999999;
+ border-bottom-color: rgba(0, 0, 0, 0.25);
+ border-top-width: 0;
+}
+
+.popover.bottom .arrow:after {
+ top: 1px;
+ margin-left: -10px;
+ border-bottom-color: #ffffff;
+ border-top-width: 0;
+ content: " ";
+}
+
+.popover.left .arrow {
+ top: 50%;
+ right: -11px;
+ margin-top: -11px;
+ border-left-color: #999999;
+ border-left-color: rgba(0, 0, 0, 0.25);
+ border-right-width: 0;
+}
+
+.popover.left .arrow:after {
+ right: 1px;
+ bottom: -10px;
+ border-left-color: #ffffff;
+ border-right-width: 0;
+ content: " ";
+}
+
+.carousel {
+ position: relative;
+}
+
+.carousel-inner {
+ position: relative;
+ width: 100%;
+ overflow: hidden;
+}
+
+.carousel-inner > .item {
+ position: relative;
+ display: none;
+ -webkit-transition: 0.6s ease-in-out left;
+ transition: 0.6s ease-in-out left;
+}
+
+.carousel-inner > .item > img,
+.carousel-inner > .item > a > img {
+ display: block;
+ height: auto;
+ max-width: 100%;
+ line-height: 1;
+}
+
+.carousel-inner > .active,
+.carousel-inner > .next,
+.carousel-inner > .prev {
+ display: block;
+}
+
+.carousel-inner > .active {
+ left: 0;
+}
+
+.carousel-inner > .next,
+.carousel-inner > .prev {
+ position: absolute;
+ top: 0;
+ width: 100%;
+}
+
+.carousel-inner > .next {
+ left: 100%;
+}
+
+.carousel-inner > .prev {
+ left: -100%;
+}
+
+.carousel-inner > .next.left,
+.carousel-inner > .prev.right {
+ left: 0;
+}
+
+.carousel-inner > .active.left {
+ left: -100%;
+}
+
+.carousel-inner > .active.right {
+ left: 100%;
+}
+
+.carousel-control {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 15%;
+ font-size: 20px;
+ color: #ffffff;
+ text-align: center;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+ opacity: 0.5;
+ filter: alpha(opacity=50);
+}
+
+.carousel-control.left {
+ background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.5)), to(rgba(0, 0, 0, 0.0001)));
+ background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.5) 0), color-stop(rgba(0, 0, 0, 0.0001) 100%));
+ background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+ background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0, rgba(0, 0, 0, 0.0001) 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);
+}
+
+.carousel-control.right {
+ right: 0;
+ left: auto;
+ background-image: -webkit-gradient(linear, 0 top, 100% top, from(rgba(0, 0, 0, 0.0001)), to(rgba(0, 0, 0, 0.5)));
+ background-image: -webkit-linear-gradient(left, color-stop(rgba(0, 0, 0, 0.0001) 0), color-stop(rgba(0, 0, 0, 0.5) 100%));
+ background-image: -moz-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+ background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0, rgba(0, 0, 0, 0.5) 100%);
+ background-repeat: repeat-x;
+ filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);
+}
+
+.carousel-control:hover,
+.carousel-control:focus {
+ color: #ffffff;
+ text-decoration: none;
+ opacity: 0.9;
+ filter: alpha(opacity=90);
+}
+
+.carousel-control .icon-prev,
+.carousel-control .icon-next,
+.carousel-control .glyphicon-chevron-left,
+.carousel-control .glyphicon-chevron-right {
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ z-index: 5;
+ display: inline-block;
+}
+
+.carousel-control .icon-prev,
+.carousel-control .icon-next {
+ width: 20px;
+ height: 20px;
+ margin-top: -10px;
+ margin-left: -10px;
+ font-family: serif;
+}
+
+.carousel-control .icon-prev:before {
+ content: '\2039';
+}
+
+.carousel-control .icon-next:before {
+ content: '\203a';
+}
+
+.carousel-indicators {
+ position: absolute;
+ bottom: 10px;
+ left: 50%;
+ z-index: 15;
+ width: 60%;
+ padding-left: 0;
+ margin-left: -30%;
+ text-align: center;
+ list-style: none;
+}
+
+.carousel-indicators li {
+ display: inline-block;
+ width: 10px;
+ height: 10px;
+ margin: 1px;
+ text-indent: -999px;
+ cursor: pointer;
+ border: 1px solid #ffffff;
+ border-radius: 10px;
+}
+
+.carousel-indicators .active {
+ width: 12px;
+ height: 12px;
+ margin: 0;
+ background-color: #ffffff;
+}
+
+.carousel-caption {
+ position: absolute;
+ right: 15%;
+ bottom: 20px;
+ left: 15%;
+ z-index: 10;
+ padding-top: 20px;
+ padding-bottom: 20px;
+ color: #ffffff;
+ text-align: center;
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);
+}
+
+.carousel-caption .btn {
+ text-shadow: none;
+}
+
+@media screen and (min-width: 768px) {
+ .carousel-control .icon-prev,
+ .carousel-control .icon-next {
+ width: 30px;
+ height: 30px;
+ margin-top: -15px;
+ margin-left: -15px;
+ font-size: 30px;
+ }
+ .carousel-caption {
+ right: 20%;
+ left: 20%;
+ padding-bottom: 30px;
+ }
+ .carousel-indicators {
+ bottom: 20px;
+ }
+}
+
+.clearfix:before,
+.clearfix:after {
+ display: table;
+ content: " ";
+}
+
+.clearfix:after {
+ clear: both;
+}
+
+.pull-right {
+ float: right !important;
+}
+
+.pull-left {
+ float: left !important;
+}
+
+.hide {
+ display: none !important;
+}
+
+.show {
+ display: block !important;
+}
+
+.invisible {
+ visibility: hidden;
+}
+
+.text-hide {
+ font: 0/0 a;
+ color: transparent;
+ text-shadow: none;
+ background-color: transparent;
+ border: 0;
+}
+
+.affix {
+ position: fixed;
+}
+
+@-ms-viewport {
+ width: device-width;
+}
+
+@media screen and (max-width: 400px) {
+ @-ms-viewport {
+ width: 320px;
+ }
+}
+
+.hidden {
+ display: none !important;
+ visibility: hidden !important;
+}
+
+.visible-xs {
+ display: none !important;
+}
+
+tr.visible-xs {
+ display: none !important;
+}
+
+th.visible-xs,
+td.visible-xs {
+ display: none !important;
+}
+
+@media (max-width: 767px) {
+ .visible-xs {
+ display: block !important;
+ }
+ tr.visible-xs {
+ display: table-row !important;
+ }
+ th.visible-xs,
+ td.visible-xs {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-xs.visible-sm {
+ display: block !important;
+ }
+ tr.visible-xs.visible-sm {
+ display: table-row !important;
+ }
+ th.visible-xs.visible-sm,
+ td.visible-xs.visible-sm {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-xs.visible-md {
+ display: block !important;
+ }
+ tr.visible-xs.visible-md {
+ display: table-row !important;
+ }
+ th.visible-xs.visible-md,
+ td.visible-xs.visible-md {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .visible-xs.visible-lg {
+ display: block !important;
+ }
+ tr.visible-xs.visible-lg {
+ display: table-row !important;
+ }
+ th.visible-xs.visible-lg,
+ td.visible-xs.visible-lg {
+ display: table-cell !important;
+ }
+}
+
+.visible-sm {
+ display: none !important;
+}
+
+tr.visible-sm {
+ display: none !important;
+}
+
+th.visible-sm,
+td.visible-sm {
+ display: none !important;
+}
+
+@media (max-width: 767px) {
+ .visible-sm.visible-xs {
+ display: block !important;
+ }
+ tr.visible-sm.visible-xs {
+ display: table-row !important;
+ }
+ th.visible-sm.visible-xs,
+ td.visible-sm.visible-xs {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-sm {
+ display: block !important;
+ }
+ tr.visible-sm {
+ display: table-row !important;
+ }
+ th.visible-sm,
+ td.visible-sm {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-sm.visible-md {
+ display: block !important;
+ }
+ tr.visible-sm.visible-md {
+ display: table-row !important;
+ }
+ th.visible-sm.visible-md,
+ td.visible-sm.visible-md {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .visible-sm.visible-lg {
+ display: block !important;
+ }
+ tr.visible-sm.visible-lg {
+ display: table-row !important;
+ }
+ th.visible-sm.visible-lg,
+ td.visible-sm.visible-lg {
+ display: table-cell !important;
+ }
+}
+
+.visible-md {
+ display: none !important;
+}
+
+tr.visible-md {
+ display: none !important;
+}
+
+th.visible-md,
+td.visible-md {
+ display: none !important;
+}
+
+@media (max-width: 767px) {
+ .visible-md.visible-xs {
+ display: block !important;
+ }
+ tr.visible-md.visible-xs {
+ display: table-row !important;
+ }
+ th.visible-md.visible-xs,
+ td.visible-md.visible-xs {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-md.visible-sm {
+ display: block !important;
+ }
+ tr.visible-md.visible-sm {
+ display: table-row !important;
+ }
+ th.visible-md.visible-sm,
+ td.visible-md.visible-sm {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-md {
+ display: block !important;
+ }
+ tr.visible-md {
+ display: table-row !important;
+ }
+ th.visible-md,
+ td.visible-md {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .visible-md.visible-lg {
+ display: block !important;
+ }
+ tr.visible-md.visible-lg {
+ display: table-row !important;
+ }
+ th.visible-md.visible-lg,
+ td.visible-md.visible-lg {
+ display: table-cell !important;
+ }
+}
+
+.visible-lg {
+ display: none !important;
+}
+
+tr.visible-lg {
+ display: none !important;
+}
+
+th.visible-lg,
+td.visible-lg {
+ display: none !important;
+}
+
+@media (max-width: 767px) {
+ .visible-lg.visible-xs {
+ display: block !important;
+ }
+ tr.visible-lg.visible-xs {
+ display: table-row !important;
+ }
+ th.visible-lg.visible-xs,
+ td.visible-lg.visible-xs {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .visible-lg.visible-sm {
+ display: block !important;
+ }
+ tr.visible-lg.visible-sm {
+ display: table-row !important;
+ }
+ th.visible-lg.visible-sm,
+ td.visible-lg.visible-sm {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .visible-lg.visible-md {
+ display: block !important;
+ }
+ tr.visible-lg.visible-md {
+ display: table-row !important;
+ }
+ th.visible-lg.visible-md,
+ td.visible-lg.visible-md {
+ display: table-cell !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .visible-lg {
+ display: block !important;
+ }
+ tr.visible-lg {
+ display: table-row !important;
+ }
+ th.visible-lg,
+ td.visible-lg {
+ display: table-cell !important;
+ }
+}
+
+.hidden-xs {
+ display: block !important;
+}
+
+tr.hidden-xs {
+ display: table-row !important;
+}
+
+th.hidden-xs,
+td.hidden-xs {
+ display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+ .hidden-xs {
+ display: none !important;
+ }
+ tr.hidden-xs {
+ display: none !important;
+ }
+ th.hidden-xs,
+ td.hidden-xs {
+ display: none !important;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .hidden-xs.hidden-sm {
+ display: none !important;
+ }
+ tr.hidden-xs.hidden-sm {
+ display: none !important;
+ }
+ th.hidden-xs.hidden-sm,
+ td.hidden-xs.hidden-sm {
+ display: none !important;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .hidden-xs.hidden-md {
+ display: none !important;
+ }
+ tr.hidden-xs.hidden-md {
+ display: none !important;
+ }
+ th.hidden-xs.hidden-md,
+ td.hidden-xs.hidden-md {
+ display: none !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .hidden-xs.hidden-lg {
+ display: none !important;
+ }
+ tr.hidden-xs.hidden-lg {
+ display: none !important;
+ }
+ th.hidden-xs.hidden-lg,
+ td.hidden-xs.hidden-lg {
+ display: none !important;
+ }
+}
+
+.hidden-sm {
+ display: block !important;
+}
+
+tr.hidden-sm {
+ display: table-row !important;
+}
+
+th.hidden-sm,
+td.hidden-sm {
+ display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+ .hidden-sm.hidden-xs {
+ display: none !important;
+ }
+ tr.hidden-sm.hidden-xs {
+ display: none !important;
+ }
+ th.hidden-sm.hidden-xs,
+ td.hidden-sm.hidden-xs {
+ display: none !important;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .hidden-sm {
+ display: none !important;
+ }
+ tr.hidden-sm {
+ display: none !important;
+ }
+ th.hidden-sm,
+ td.hidden-sm {
+ display: none !important;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .hidden-sm.hidden-md {
+ display: none !important;
+ }
+ tr.hidden-sm.hidden-md {
+ display: none !important;
+ }
+ th.hidden-sm.hidden-md,
+ td.hidden-sm.hidden-md {
+ display: none !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .hidden-sm.hidden-lg {
+ display: none !important;
+ }
+ tr.hidden-sm.hidden-lg {
+ display: none !important;
+ }
+ th.hidden-sm.hidden-lg,
+ td.hidden-sm.hidden-lg {
+ display: none !important;
+ }
+}
+
+.hidden-md {
+ display: block !important;
+}
+
+tr.hidden-md {
+ display: table-row !important;
+}
+
+th.hidden-md,
+td.hidden-md {
+ display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+ .hidden-md.hidden-xs {
+ display: none !important;
+ }
+ tr.hidden-md.hidden-xs {
+ display: none !important;
+ }
+ th.hidden-md.hidden-xs,
+ td.hidden-md.hidden-xs {
+ display: none !important;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .hidden-md.hidden-sm {
+ display: none !important;
+ }
+ tr.hidden-md.hidden-sm {
+ display: none !important;
+ }
+ th.hidden-md.hidden-sm,
+ td.hidden-md.hidden-sm {
+ display: none !important;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .hidden-md {
+ display: none !important;
+ }
+ tr.hidden-md {
+ display: none !important;
+ }
+ th.hidden-md,
+ td.hidden-md {
+ display: none !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .hidden-md.hidden-lg {
+ display: none !important;
+ }
+ tr.hidden-md.hidden-lg {
+ display: none !important;
+ }
+ th.hidden-md.hidden-lg,
+ td.hidden-md.hidden-lg {
+ display: none !important;
+ }
+}
+
+.hidden-lg {
+ display: block !important;
+}
+
+tr.hidden-lg {
+ display: table-row !important;
+}
+
+th.hidden-lg,
+td.hidden-lg {
+ display: table-cell !important;
+}
+
+@media (max-width: 767px) {
+ .hidden-lg.hidden-xs {
+ display: none !important;
+ }
+ tr.hidden-lg.hidden-xs {
+ display: none !important;
+ }
+ th.hidden-lg.hidden-xs,
+ td.hidden-lg.hidden-xs {
+ display: none !important;
+ }
+}
+
+@media (min-width: 768px) and (max-width: 991px) {
+ .hidden-lg.hidden-sm {
+ display: none !important;
+ }
+ tr.hidden-lg.hidden-sm {
+ display: none !important;
+ }
+ th.hidden-lg.hidden-sm,
+ td.hidden-lg.hidden-sm {
+ display: none !important;
+ }
+}
+
+@media (min-width: 992px) and (max-width: 1199px) {
+ .hidden-lg.hidden-md {
+ display: none !important;
+ }
+ tr.hidden-lg.hidden-md {
+ display: none !important;
+ }
+ th.hidden-lg.hidden-md,
+ td.hidden-lg.hidden-md {
+ display: none !important;
+ }
+}
+
+@media (min-width: 1200px) {
+ .hidden-lg {
+ display: none !important;
+ }
+ tr.hidden-lg {
+ display: none !important;
+ }
+ th.hidden-lg,
+ td.hidden-lg {
+ display: none !important;
+ }
+}
+
+.visible-print {
+ display: none !important;
+}
+
+tr.visible-print {
+ display: none !important;
+}
+
+th.visible-print,
+td.visible-print {
+ display: none !important;
+}
+
+@media print {
+ .visible-print {
+ display: block !important;
+ }
+ tr.visible-print {
+ display: table-row !important;
+ }
+ th.visible-print,
+ td.visible-print {
+ display: table-cell !important;
+ }
+ .hidden-print {
+ display: none !important;
+ }
+ tr.hidden-print {
+ display: none !important;
+ }
+ th.hidden-print,
+ td.hidden-print {
+ display: none !important;
+ }
+}
\ No newline at end of file
diff --git a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.eot b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.eot
new file mode 100644
index 0000000..87eaa43
Binary files /dev/null and b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.eot differ
diff --git a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.svg b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.svg
new file mode 100644
index 0000000..5fee068
--- /dev/null
+++ b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.svg
@@ -0,0 +1,228 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf
new file mode 100644
index 0000000..be784dc
Binary files /dev/null and b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.ttf differ
diff --git a/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.woff b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.woff
new file mode 100644
index 0000000..2cc3e48
Binary files /dev/null and b/framework/yii/bootstrap/assets/fonts/glyphicons-halflings-regular.woff differ
diff --git a/framework/yii/bootstrap/assets/js/bootstrap.js b/framework/yii/bootstrap/assets/js/bootstrap.js
new file mode 100644
index 0000000..2c64257
--- /dev/null
+++ b/framework/yii/bootstrap/assets/js/bootstrap.js
@@ -0,0 +1,1999 @@
+/**
+* bootstrap.js v3.0.0 by @fat and @mdo
+* Copyright 2013 Twitter Inc.
+* http://www.apache.org/licenses/LICENSE-2.0
+*/
+if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
+
+/* ========================================================================
+ * Bootstrap: transition.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#transitions
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
+ // ============================================================
+
+ function transitionEnd() {
+ var el = document.createElement('bootstrap')
+
+ var transEndEventNames = {
+ 'WebkitTransition' : 'webkitTransitionEnd'
+ , 'MozTransition' : 'transitionend'
+ , 'OTransition' : 'oTransitionEnd otransitionend'
+ , 'transition' : 'transitionend'
+ }
+
+ for (var name in transEndEventNames) {
+ if (el.style[name] !== undefined) {
+ return { end: transEndEventNames[name] }
+ }
+ }
+ }
+
+ // http://blog.alexmaccaw.com/css-transitions
+ $.fn.emulateTransitionEnd = function (duration) {
+ var called = false, $el = this
+ $(this).one($.support.transition.end, function () { called = true })
+ var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
+ setTimeout(callback, duration)
+ return this
+ }
+
+ $(function () {
+ $.support.transition = transitionEnd()
+ })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: alert.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#alerts
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // ALERT CLASS DEFINITION
+ // ======================
+
+ var dismiss = '[data-dismiss="alert"]'
+ var Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
+ }
+
+ var $parent = $(selector)
+
+ if (e) e.preventDefault()
+
+ if (!$parent.length) {
+ $parent = $this.hasClass('alert') ? $this : $this.parent()
+ }
+
+ $parent.trigger(e = $.Event('close.bs.alert'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ $parent.trigger('closed.bs.alert').remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent
+ .one($.support.transition.end, removeElement)
+ .emulateTransitionEnd(150) :
+ removeElement()
+ }
+
+
+ // ALERT PLUGIN DEFINITION
+ // =======================
+
+ var old = $.fn.alert
+
+ $.fn.alert = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.alert')
+
+ if (!data) $this.data('bs.alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.alert.Constructor = Alert
+
+
+ // ALERT NO CONFLICT
+ // =================
+
+ $.fn.alert.noConflict = function () {
+ $.fn.alert = old
+ return this
+ }
+
+
+ // ALERT DATA-API
+ // ==============
+
+ $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: button.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#buttons
+ * ========================================================================
+ * Copyright 2013 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // BUTTON PUBLIC CLASS DEFINITION
+ // ==============================
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Button.DEFAULTS, options)
+ }
+
+ Button.DEFAULTS = {
+ loadingText: 'loading...'
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ var $el = this.$element
+ var val = $el.is('input') ? 'val' : 'html'
+ var data = $el.data()
+
+ state = state + 'Text'
+
+ if (!data.resetText) $el.data('resetText', $el[val]())
+
+ $el[val](data[state] || this.options[state])
+
+ // push to event loop to allow forms to submit
+ setTimeout(function () {
+ state == 'loadingText' ?
+ $el.addClass(d).attr(d, d) :
+ $el.removeClass(d).removeAttr(d);
+ }, 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var $parent = this.$element.closest('[data-toggle="buttons"]')
+
+ if ($parent.length) {
+ var $input = this.$element.find('input')
+ .prop('checked', !this.$element.hasClass('active'))
+ .trigger('change')
+ if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active')
+ }
+
+ this.$element.toggleClass('active')
+ }
+
+
+ // BUTTON PLUGIN DEFINITION
+ // ========================
+
+ var old = $.fn.button
+
+ $.fn.button = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.button')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.button', (data = new Button(this, options)))
+
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ $.fn.button.Constructor = Button
+
+
+ // BUTTON NO CONFLICT
+ // ==================
+
+ $.fn.button.noConflict = function () {
+ $.fn.button = old
+ return this
+ }
+
+
+ // BUTTON DATA-API
+ // ===============
+
+ $(document).on('click.bs.button.data-api', '[data-toggle^=button]', function (e) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ $btn.button('toggle')
+ e.preventDefault()
+ })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: carousel.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#carousel
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // CAROUSEL CLASS DEFINITION
+ // =========================
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.$indicators = this.$element.find('.carousel-indicators')
+ this.options = options
+ this.paused =
+ this.sliding =
+ this.interval =
+ this.$active =
+ this.$items = null
+
+ this.options.pause == 'hover' && this.$element
+ .on('mouseenter', $.proxy(this.pause, this))
+ .on('mouseleave', $.proxy(this.cycle, this))
+ }
+
+ Carousel.DEFAULTS = {
+ interval: 5000
+ , pause: 'hover'
+ , wrap: true
+ }
+
+ Carousel.prototype.cycle = function (e) {
+ e || (this.paused = false)
+
+ this.interval && clearInterval(this.interval)
+
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+
+ return this
+ }
+
+ Carousel.prototype.getActiveIndex = function () {
+ this.$active = this.$element.find('.item.active')
+ this.$items = this.$active.parent().children()
+
+ return this.$items.index(this.$active)
+ }
+
+ Carousel.prototype.to = function (pos) {
+ var that = this
+ var activeIndex = this.getActiveIndex()
+
+ if (pos > (this.$items.length - 1) || pos < 0) return
+
+ if (this.sliding) return this.$element.one('slid', function () { that.to(pos) })
+ if (activeIndex == pos) return this.pause().cycle()
+
+ return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+ }
+
+ Carousel.prototype.pause = function (e) {
+ e || (this.paused = true)
+
+ if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle(true)
+ }
+
+ this.interval = clearInterval(this.interval)
+
+ return this
+ }
+
+ Carousel.prototype.next = function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ Carousel.prototype.prev = function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ Carousel.prototype.slide = function (type, next) {
+ var $active = this.$element.find('.item.active')
+ var $next = next || $active[type]()
+ var isCycling = this.interval
+ var direction = type == 'next' ? 'left' : 'right'
+ var fallback = type == 'next' ? 'first' : 'last'
+ var that = this
+
+ if (!$next.length) {
+ if (!this.options.wrap) return
+ $next = this.$element.find('.item')[fallback]()
+ }
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })
+
+ if ($next.hasClass('active')) return
+
+ if (this.$indicators.length) {
+ this.$indicators.find('.active').removeClass('active')
+ this.$element.one('slid', function () {
+ var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
+ $nextIndicator && $nextIndicator.addClass('active')
+ })
+ }
+
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ $active
+ .one($.support.transition.end, function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () { that.$element.trigger('slid') }, 0)
+ })
+ .emulateTransitionEnd(600)
+ } else {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger('slid')
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+
+ // CAROUSEL PLUGIN DEFINITION
+ // ==========================
+
+ var old = $.fn.carousel
+
+ $.fn.carousel = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.carousel')
+ var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
+ var action = typeof option == 'string' ? option : options.slide
+
+ if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.pause().cycle()
+ })
+ }
+
+ $.fn.carousel.Constructor = Carousel
+
+
+ // CAROUSEL NO CONFLICT
+ // ====================
+
+ $.fn.carousel.noConflict = function () {
+ $.fn.carousel = old
+ return this
+ }
+
+
+ // CAROUSEL DATA-API
+ // =================
+
+ $(document).on('click.bs.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+ var $this = $(this), href
+ var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ var options = $.extend({}, $target.data(), $this.data())
+ var slideIndex = $this.attr('data-slide-to')
+ if (slideIndex) options.interval = false
+
+ $target.carousel(options)
+
+ if (slideIndex = $this.attr('data-slide-to')) {
+ $target.data('bs.carousel').to(slideIndex)
+ }
+
+ e.preventDefault()
+ })
+
+ $(window).on('load', function () {
+ $('[data-ride="carousel"]').each(function () {
+ var $carousel = $(this)
+ $carousel.carousel($carousel.data())
+ })
+ })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: collapse.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#collapse
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // COLLAPSE PUBLIC CLASS DEFINITION
+ // ================================
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, Collapse.DEFAULTS, options)
+ this.transitioning = null
+
+ if (this.options.parent) this.$parent = $(this.options.parent)
+ if (this.options.toggle) this.toggle()
+ }
+
+ Collapse.DEFAULTS = {
+ toggle: true
+ }
+
+ Collapse.prototype.dimension = function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ Collapse.prototype.show = function () {
+ if (this.transitioning || this.$element.hasClass('in')) return
+
+ var startEvent = $.Event('show.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ var actives = this.$parent && this.$parent.find('> .panel > .in')
+
+ if (actives && actives.length) {
+ var hasData = actives.data('bs.collapse')
+ if (hasData && hasData.transitioning) return
+ actives.collapse('hide')
+ hasData || actives.data('bs.collapse', null)
+ }
+
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ .addClass('collapsing')
+ [dimension](0)
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.$element
+ .removeClass('collapsing')
+ .addClass('in')
+ [dimension]('auto')
+ this.transitioning = 0
+ this.$element.trigger('shown.bs.collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ var scrollSize = $.camelCase(['scroll', dimension].join('-'))
+
+ this.$element
+ .one($.support.transition.end, $.proxy(complete, this))
+ .emulateTransitionEnd(350)
+ [dimension](this.$element[0][scrollSize])
+ }
+
+ Collapse.prototype.hide = function () {
+ if (this.transitioning || !this.$element.hasClass('in')) return
+
+ var startEvent = $.Event('hide.bs.collapse')
+ this.$element.trigger(startEvent)
+ if (startEvent.isDefaultPrevented()) return
+
+ var dimension = this.dimension()
+
+ this.$element
+ [dimension](this.$element[dimension]())
+ [0].offsetHeight
+
+ this.$element
+ .addClass('collapsing')
+ .removeClass('collapse')
+ .removeClass('in')
+
+ this.transitioning = 1
+
+ var complete = function () {
+ this.transitioning = 0
+ this.$element
+ .trigger('hidden.bs.collapse')
+ .removeClass('collapsing')
+ .addClass('collapse')
+ }
+
+ if (!$.support.transition) return complete.call(this)
+
+ this.$element
+ [dimension](0)
+ .one($.support.transition.end, $.proxy(complete, this))
+ .emulateTransitionEnd(350)
+ }
+
+ Collapse.prototype.toggle = function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+
+ // COLLAPSE PLUGIN DEFINITION
+ // ==========================
+
+ var old = $.fn.collapse
+
+ $.fn.collapse = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.collapse')
+ var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.collapse.Constructor = Collapse
+
+
+ // COLLAPSE NO CONFLICT
+ // ====================
+
+ $.fn.collapse.noConflict = function () {
+ $.fn.collapse = old
+ return this
+ }
+
+
+ // COLLAPSE DATA-API
+ // =================
+
+ $(document).on('click.bs.collapse.data-api', '[data-toggle=collapse]', function (e) {
+ var $this = $(this), href
+ var target = $this.attr('data-target')
+ || e.preventDefault()
+ || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+ var $target = $(target)
+ var data = $target.data('bs.collapse')
+ var option = data ? 'toggle' : $this.data()
+ var parent = $this.attr('data-parent')
+ var $parent = parent && $(parent)
+
+ if (!data || !data.transitioning) {
+ if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed')
+ $this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+ }
+
+ $target.collapse(option)
+ })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: dropdown.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#dropdowns
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // DROPDOWN CLASS DEFINITION
+ // =========================
+
+ var backdrop = '.dropdown-backdrop'
+ var toggle = '[data-toggle=dropdown]'
+ var Dropdown = function (element) {
+ var $el = $(element).on('click.bs.dropdown', this.toggle)
+ }
+
+ Dropdown.prototype.toggle = function (e) {
+ var $this = $(this)
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
+ // if mobile we we use a backdrop because click events don't delegate
+ $('
').insertAfter($(this)).on('click', clearMenus)
+ }
+
+ $parent.trigger(e = $.Event('show.bs.dropdown'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent
+ .toggleClass('open')
+ .trigger('shown.bs.dropdown')
+
+ $this.focus()
+ }
+
+ return false
+ }
+
+ Dropdown.prototype.keydown = function (e) {
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ var $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ var $parent = getParent($this)
+ var isActive = $parent.hasClass('open')
+
+ if (!isActive || (isActive && e.keyCode == 27)) {
+ if (e.which == 27) $parent.find(toggle).focus()
+ return $this.click()
+ }
+
+ var $items = $('[role=menu] li:not(.divider):visible a', $parent)
+
+ if (!$items.length) return
+
+ var index = $items.index($items.filter(':focus'))
+
+ if (e.keyCode == 38 && index > 0) index-- // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index=0
+
+ $items.eq(index).focus()
+ }
+
+ function clearMenus() {
+ $(backdrop).remove()
+ $(toggle).each(function (e) {
+ var $parent = getParent($(this))
+ if (!$parent.hasClass('open')) return
+ $parent.trigger(e = $.Event('hide.bs.dropdown'))
+ if (e.isDefaultPrevented()) return
+ $parent.removeClass('open').trigger('hidden.bs.dropdown')
+ })
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ var $parent = selector && $(selector)
+
+ return $parent && $parent.length ? $parent : $this.parent()
+ }
+
+
+ // DROPDOWN PLUGIN DEFINITION
+ // ==========================
+
+ var old = $.fn.dropdown
+
+ $.fn.dropdown = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('dropdown')
+
+ if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ // DROPDOWN NO CONFLICT
+ // ====================
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old
+ return this
+ }
+
+
+ // APPLY TO STANDARD DROPDOWN ELEMENTS
+ // ===================================
+
+ $(document)
+ .on('click.bs.dropdown.data-api', clearMenus)
+ .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.bs.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
+ .on('keydown.bs.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: modal.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#modals
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // MODAL CLASS DEFINITION
+ // ======================
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$element = $(element)
+ this.$backdrop =
+ this.isShown = null
+
+ if (this.options.remote) this.$element.load(this.options.remote)
+ }
+
+ Modal.DEFAULTS = {
+ backdrop: true
+ , keyboard: true
+ , show: true
+ }
+
+ Modal.prototype.toggle = function (_relatedTarget) {
+ return this[!this.isShown ? 'show' : 'hide'](_relatedTarget)
+ }
+
+ Modal.prototype.show = function (_relatedTarget) {
+ var that = this
+ var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = true
+
+ this.escape()
+
+ this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(document.body) // don't move modals dom position
+ }
+
+ that.$element.show()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+
+ that.enforceFocus()
+
+ var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
+
+ transition ?
+ that.$element.find('.modal-dialog') // wait for modal to slide in
+ .one($.support.transition.end, function () {
+ that.$element.focus().trigger(e)
+ })
+ .emulateTransitionEnd(300) :
+ that.$element.focus().trigger(e)
+ })
+ }
+
+ Modal.prototype.hide = function (e) {
+ if (e) e.preventDefault()
+
+ e = $.Event('hide.bs.modal')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ this.escape()
+
+ $(document).off('focusin.bs.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+ .off('click.dismiss.modal')
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.$element
+ .one($.support.transition.end, $.proxy(this.hideModal, this))
+ .emulateTransitionEnd(300) :
+ this.hideModal()
+ }
+
+ Modal.prototype.enforceFocus = function () {
+ $(document)
+ .off('focusin.bs.modal') // guard against infinite focus loop
+ .on('focusin.bs.modal', $.proxy(function (e) {
+ if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
+ this.$element.focus()
+ }
+ }, this))
+ }
+
+ Modal.prototype.escape = function () {
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keyup.dismiss.bs.modal', $.proxy(function (e) {
+ e.which == 27 && this.hide()
+ }, this))
+ } else if (!this.isShown) {
+ this.$element.off('keyup.dismiss.bs.modal')
+ }
+ }
+
+ Modal.prototype.hideModal = function () {
+ var that = this
+ this.$element.hide()
+ this.backdrop(function () {
+ that.removeBackdrop()
+ that.$element.trigger('hidden.bs.modal')
+ })
+ }
+
+ Modal.prototype.removeBackdrop = function () {
+ this.$backdrop && this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ Modal.prototype.backdrop = function (callback) {
+ var that = this
+ var animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('
')
+ .appendTo(document.body)
+
+ this.$element.on('click.dismiss.modal', $.proxy(function (e) {
+ if (e.target !== e.currentTarget) return
+ this.options.backdrop == 'static'
+ ? this.$element[0].focus.call(this.$element[0])
+ : this.hide.call(this)
+ }, this))
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ if (!callback) return
+
+ doAnimate ?
+ this.$backdrop
+ .one($.support.transition.end, callback)
+ .emulateTransitionEnd(150) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ $.support.transition && this.$element.hasClass('fade')?
+ this.$backdrop
+ .one($.support.transition.end, callback)
+ .emulateTransitionEnd(150) :
+ callback()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+
+
+ // MODAL PLUGIN DEFINITION
+ // =======================
+
+ var old = $.fn.modal
+
+ $.fn.modal = function (option, _relatedTarget) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.modal')
+ var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
+
+ if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option](_relatedTarget)
+ else if (options.show) data.show(_relatedTarget)
+ })
+ }
+
+ $.fn.modal.Constructor = Modal
+
+
+ // MODAL NO CONFLICT
+ // =================
+
+ $.fn.modal.noConflict = function () {
+ $.fn.modal = old
+ return this
+ }
+
+
+ // MODAL DATA-API
+ // ==============
+
+ $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
+ var $this = $(this)
+ var href = $this.attr('href')
+ var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+ var option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
+
+ e.preventDefault()
+
+ $target
+ .modal(option, this)
+ .one('hide', function () {
+ $this.is(':visible') && $this.focus()
+ })
+ })
+
+ $(document)
+ .on('show.bs.modal', '.modal', function () { $(document.body).addClass('modal-open') })
+ .on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: tooltip.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#tooltip
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // TOOLTIP PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Tooltip = function (element, options) {
+ this.type =
+ this.options =
+ this.enabled =
+ this.timeout =
+ this.hoverState =
+ this.$element = null
+
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.DEFAULTS = {
+ animation: true
+ , placement: 'top'
+ , selector: false
+ , template: ''
+ , trigger: 'hover focus'
+ , title: ''
+ , delay: 0
+ , html: false
+ , container: false
+ }
+
+ Tooltip.prototype.init = function (type, element, options) {
+ this.enabled = true
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+
+ var triggers = this.options.trigger.split(' ')
+
+ for (var i = triggers.length; i--;) {
+ var trigger = triggers[i]
+
+ if (trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (trigger != 'manual') {
+ var eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
+ var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
+
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ Tooltip.prototype.getDefaults = function () {
+ return Tooltip.DEFAULTS
+ }
+
+ Tooltip.prototype.getOptions = function (options) {
+ options = $.extend({}, this.getDefaults(), this.$element.data(), options)
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay
+ , hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ Tooltip.prototype.getDelegateOptions = function () {
+ var options = {}
+ var defaults = this.getDefaults()
+
+ this._options && $.each(this._options, function (key, value) {
+ if (defaults[key] != value) options[key] = value
+ })
+
+ return options
+ }
+
+ Tooltip.prototype.enter = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'in'
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ Tooltip.prototype.leave = function (obj) {
+ var self = obj instanceof this.constructor ?
+ obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
+
+ clearTimeout(self.timeout)
+
+ self.hoverState = 'out'
+
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.timeout = setTimeout(function () {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ Tooltip.prototype.show = function () {
+ var e = $.Event('show.bs.'+ this.type)
+
+ if (this.hasContent() && this.enabled) {
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ var $tip = this.tip()
+
+ this.setContent()
+
+ if (this.options.animation) $tip.addClass('fade')
+
+ var placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ var autoToken = /\s?auto?\s?/i
+ var autoPlace = autoToken.test(placement)
+ if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
+
+ $tip
+ .detach()
+ .css({ top: 0, left: 0, display: 'block' })
+ .addClass(placement)
+
+ this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+ var pos = this.getPosition()
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (autoPlace) {
+ var $parent = this.$element.parent()
+
+ var orgPlacement = placement
+ var docScroll = document.documentElement.scrollTop || document.body.scrollTop
+ var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth()
+ var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight()
+ var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left
+
+ placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' :
+ placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' :
+ placement == 'right' && pos.right + actualWidth > parentWidth ? 'left' :
+ placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' :
+ placement
+
+ $tip
+ .removeClass(orgPlacement)
+ .addClass(placement)
+ }
+
+ var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
+
+ this.applyPlacement(calculatedOffset, placement)
+ this.$element.trigger('shown.bs.' + this.type)
+ }
+ }
+
+ Tooltip.prototype.applyPlacement = function(offset, placement) {
+ var replace
+ var $tip = this.tip()
+ var width = $tip[0].offsetWidth
+ var height = $tip[0].offsetHeight
+
+ // manually read margins because getBoundingClientRect includes difference
+ var marginTop = parseInt($tip.css('margin-top'), 10)
+ var marginLeft = parseInt($tip.css('margin-left'), 10)
+
+ // we must check for NaN for ie 8/9
+ if (isNaN(marginTop)) marginTop = 0
+ if (isNaN(marginLeft)) marginLeft = 0
+
+ offset.top = offset.top + marginTop
+ offset.left = offset.left + marginLeft
+
+ $tip
+ .offset(offset)
+ .addClass('in')
+
+ // check to see if placing tip in new offset caused the tip to resize itself
+ var actualWidth = $tip[0].offsetWidth
+ var actualHeight = $tip[0].offsetHeight
+
+ if (placement == 'top' && actualHeight != height) {
+ replace = true
+ offset.top = offset.top + height - actualHeight
+ }
+
+ if (/bottom|top/.test(placement)) {
+ var delta = 0
+
+ if (offset.left < 0) {
+ delta = offset.left * -2
+ offset.left = 0
+
+ $tip.offset(offset)
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+ }
+
+ this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
+ } else {
+ this.replaceArrow(actualHeight - height, actualHeight, 'top')
+ }
+
+ if (replace) $tip.offset(offset)
+ }
+
+ Tooltip.prototype.replaceArrow = function(delta, dimension, position) {
+ this.arrow().css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
+ }
+
+ Tooltip.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ Tooltip.prototype.hide = function () {
+ var that = this
+ var $tip = this.tip()
+ var e = $.Event('hide.bs.' + this.type)
+
+ function complete() {
+ if (that.hoverState != 'in') $tip.detach()
+ }
+
+ this.$element.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $tip.removeClass('in')
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ $tip
+ .one($.support.transition.end, complete)
+ .emulateTransitionEnd(150) :
+ complete()
+
+ this.$element.trigger('hidden.bs.' + this.type)
+
+ return this
+ }
+
+ Tooltip.prototype.fixTitle = function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+ }
+ }
+
+ Tooltip.prototype.hasContent = function () {
+ return this.getTitle()
+ }
+
+ Tooltip.prototype.getPosition = function () {
+ var el = this.$element[0]
+ return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
+ width: el.offsetWidth
+ , height: el.offsetHeight
+ }, this.$element.offset())
+ }
+
+ Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
+ return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
+ placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
+ /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
+ }
+
+ Tooltip.prototype.getTitle = function () {
+ var title
+ var $e = this.$element
+ var o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ Tooltip.prototype.tip = function () {
+ return this.$tip = this.$tip || $(this.options.template)
+ }
+
+ Tooltip.prototype.arrow = function () {
+ return this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')
+ }
+
+ Tooltip.prototype.validate = function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ Tooltip.prototype.enable = function () {
+ this.enabled = true
+ }
+
+ Tooltip.prototype.disable = function () {
+ this.enabled = false
+ }
+
+ Tooltip.prototype.toggleEnabled = function () {
+ this.enabled = !this.enabled
+ }
+
+ Tooltip.prototype.toggle = function (e) {
+ var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
+ self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
+ }
+
+ Tooltip.prototype.destroy = function () {
+ this.hide().$element.off('.' + this.type).removeData('bs.' + this.type)
+ }
+
+
+ // TOOLTIP PLUGIN DEFINITION
+ // =========================
+
+ var old = $.fn.tooltip
+
+ $.fn.tooltip = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tooltip')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tooltip.Constructor = Tooltip
+
+
+ // TOOLTIP NO CONFLICT
+ // ===================
+
+ $.fn.tooltip.noConflict = function () {
+ $.fn.tooltip = old
+ return this
+ }
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: popover.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#popovers
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // POPOVER PUBLIC CLASS DEFINITION
+ // ===============================
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+ if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')
+
+ Popover.DEFAULTS = $.extend({} , $.fn.tooltip.Constructor.DEFAULTS, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: ''
+ })
+
+
+ // NOTE: POPOVER EXTENDS tooltip.js
+ // ================================
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)
+
+ Popover.prototype.constructor = Popover
+
+ Popover.prototype.getDefaults = function () {
+ return Popover.DEFAULTS
+ }
+
+ Popover.prototype.setContent = function () {
+ var $tip = this.tip()
+ var title = this.getTitle()
+ var content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+
+ // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
+ // this manually by checking the contents.
+ if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
+ }
+
+ Popover.prototype.hasContent = function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ Popover.prototype.getContent = function () {
+ var $e = this.$element
+ var o = this.options
+
+ return $e.attr('data-content')
+ || (typeof o.content == 'function' ?
+ o.content.call($e[0]) :
+ o.content)
+ }
+
+ Popover.prototype.arrow = function () {
+ return this.$arrow = this.$arrow || this.tip().find('.arrow')
+ }
+
+ Popover.prototype.tip = function () {
+ if (!this.$tip) this.$tip = $(this.options.template)
+ return this.$tip
+ }
+
+
+ // POPOVER PLUGIN DEFINITION
+ // =========================
+
+ var old = $.fn.popover
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.popover')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+
+ // POPOVER NO CONFLICT
+ // ===================
+
+ $.fn.popover.noConflict = function () {
+ $.fn.popover = old
+ return this
+ }
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: scrollspy.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#scrollspy
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // SCROLLSPY CLASS DEFINITION
+ // ==========================
+
+ function ScrollSpy(element, options) {
+ var href
+ var process = $.proxy(this.process, this)
+
+ this.$element = $(element).is('body') ? $(window) : $(element)
+ this.$body = $('body')
+ this.$scrollElement = this.$element.on('scroll.bs.scroll-spy.data-api', process)
+ this.options = $.extend({}, ScrollSpy.DEFAULTS, options)
+ this.selector = (this.options.target
+ || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ || '') + ' .nav li > a'
+ this.offsets = $([])
+ this.targets = $([])
+ this.activeTarget = null
+
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.DEFAULTS = {
+ offset: 10
+ }
+
+ ScrollSpy.prototype.refresh = function () {
+ var offsetMethod = this.$element[0] == window ? 'offset' : 'position'
+
+ this.offsets = $([])
+ this.targets = $([])
+
+ var self = this
+ var $targets = this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ var href = $el.data('target') || $el.attr('href')
+ var $href = /^#\w/.test(href) && $(href)
+
+ return ($href
+ && $href.length
+ && [[ $href[offsetMethod]().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]]) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ ScrollSpy.prototype.process = function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ var scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+ var maxScroll = scrollHeight - this.$scrollElement.height()
+ var offsets = this.offsets
+ var targets = this.targets
+ var activeTarget = this.activeTarget
+ var i
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets.last()[0]) && this.activate(i)
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate( targets[i] )
+ }
+ }
+
+ ScrollSpy.prototype.activate = function (target) {
+ this.activeTarget = target
+
+ $(this.selector)
+ .parents('.active')
+ .removeClass('active')
+
+ var selector = this.selector
+ + '[data-target="' + target + '"],'
+ + this.selector + '[href="' + target + '"]'
+
+ var active = $(selector)
+ .parents('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active
+ .closest('li.dropdown')
+ .addClass('active')
+ }
+
+ active.trigger('activate')
+ }
+
+
+ // SCROLLSPY PLUGIN DEFINITION
+ // ===========================
+
+ var old = $.fn.scrollspy
+
+ $.fn.scrollspy = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.scrollspy')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+
+ // SCROLLSPY NO CONFLICT
+ // =====================
+
+ $.fn.scrollspy.noConflict = function () {
+ $.fn.scrollspy = old
+ return this
+ }
+
+
+ // SCROLLSPY DATA-API
+ // ==================
+
+ $(window).on('load', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ $spy.scrollspy($spy.data())
+ })
+ })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: tab.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#tabs
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // TAB CLASS DEFINITION
+ // ====================
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.prototype.show = function () {
+ var $this = this.element
+ var $ul = $this.closest('ul:not(.dropdown-menu)')
+ var selector = $this.attr('data-target')
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ if ($this.parent('li').hasClass('active')) return
+
+ var previous = $ul.find('.active:last a')[0]
+ var e = $.Event('show.bs.tab', {
+ relatedTarget: previous
+ })
+
+ $this.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ var $target = $(selector)
+
+ this.activate($this.parent('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $this.trigger({
+ type: 'shown.bs.tab'
+ , relatedTarget: previous
+ })
+ })
+ }
+
+ Tab.prototype.activate = function (element, container, callback) {
+ var $active = container.find('> .active')
+ var transition = callback
+ && $.support.transition
+ && $active.hasClass('fade')
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+
+ element.addClass('active')
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if (element.parent('.dropdown-menu')) {
+ element.closest('li.dropdown').addClass('active')
+ }
+
+ callback && callback()
+ }
+
+ transition ?
+ $active
+ .one($.support.transition.end, next)
+ .emulateTransitionEnd(150) :
+ next()
+
+ $active.removeClass('in')
+ }
+
+
+ // TAB PLUGIN DEFINITION
+ // =====================
+
+ var old = $.fn.tab
+
+ $.fn.tab = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.tab')
+
+ if (!data) $this.data('bs.tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tab.Constructor = Tab
+
+
+ // TAB NO CONFLICT
+ // ===============
+
+ $.fn.tab.noConflict = function () {
+ $.fn.tab = old
+ return this
+ }
+
+
+ // TAB DATA-API
+ // ============
+
+ $(document).on('click.bs.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+ e.preventDefault()
+ $(this).tab('show')
+ })
+
+}(window.jQuery);
+
+/* ========================================================================
+ * Bootstrap: affix.js v3.0.0
+ * http://twbs.github.com/bootstrap/javascript.html#affix
+ * ========================================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * 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.
+ * ======================================================================== */
+
+
++function ($) { "use strict";
+
+ // AFFIX CLASS DEFINITION
+ // ======================
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, Affix.DEFAULTS, options)
+ this.$window = $(window)
+ .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
+ .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this))
+
+ this.$element = $(element)
+ this.affixed =
+ this.unpin = null
+
+ this.checkPosition()
+ }
+
+ Affix.RESET = 'affix affix-top affix-bottom'
+
+ Affix.DEFAULTS = {
+ offset: 0
+ }
+
+ Affix.prototype.checkPositionWithEventLoop = function () {
+ setTimeout($.proxy(this.checkPosition, this), 1)
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var scrollHeight = $(document).height()
+ var scrollTop = this.$window.scrollTop()
+ var position = this.$element.offset()
+ var offset = this.options.offset
+ var offsetTop = offset.top
+ var offsetBottom = offset.bottom
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top()
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+ var affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ? false :
+ offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ? 'bottom' :
+ offsetTop != null && (scrollTop <= offsetTop) ? 'top' : false
+
+ if (this.affixed === affix) return
+ if (this.unpin) this.$element.css('top', '')
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+ this.$element.removeClass(Affix.RESET).addClass('affix' + (affix ? '-' + affix : ''))
+
+ if (affix == 'bottom') {
+ this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() })
+ }
+ }
+
+
+ // AFFIX PLUGIN DEFINITION
+ // =======================
+
+ var old = $.fn.affix
+
+ $.fn.affix = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ var data = $this.data('bs.affix')
+ var options = typeof option == 'object' && option
+
+ if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.affix.Constructor = Affix
+
+
+ // AFFIX NO CONFLICT
+ // =================
+
+ $.fn.affix.noConflict = function () {
+ $.fn.affix = old
+ return this
+ }
+
+
+ // AFFIX DATA-API
+ // ==============
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ var data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ if (data.offsetBottom) data.offset.bottom = data.offsetBottom
+ if (data.offsetTop) data.offset.top = data.offsetTop
+
+ $spy.affix(data)
+ })
+ })
+
+}(window.jQuery);
diff --git a/framework/yii/caching/ApcCache.php b/framework/yii/caching/ApcCache.php
new file mode 100644
index 0000000..6c2754a
--- /dev/null
+++ b/framework/yii/caching/ApcCache.php
@@ -0,0 +1,107 @@
+
+ * @since 2.0
+ */
+class ApcCache extends Cache
+{
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
+ return apc_exists($key);
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return apc_fetch($key);
+ }
+
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ return apc_fetch($keys);
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return apc_store($key, $value, $expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return apc_add($key, $value, $expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return apc_delete($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return apc_clear_cache('user');
+ }
+}
diff --git a/framework/yii/caching/Cache.php b/framework/yii/caching/Cache.php
new file mode 100644
index 0000000..56c6d96
--- /dev/null
+++ b/framework/yii/caching/Cache.php
@@ -0,0 +1,373 @@
+get($key);
+ * if ($data === false) {
+ * // ...generate $data here...
+ * $cache->set($key, $data, $expire, $dependency);
+ * }
+ * ~~~
+ *
+ * Because Cache implements the ArrayAccess interface, it can be used like an array. For example,
+ *
+ * ~~~
+ * $cache['foo'] = 'some data';
+ * echo $cache['foo'];
+ * ~~~
+ *
+ * Derived classes should implement the following methods:
+ *
+ * - [[getValue()]]: retrieve the value with a key (if any) from cache
+ * - [[setValue()]]: store the value with a key into cache
+ * - [[addValue()]]: store the value only if the cache does not have this key before
+ * - [[deleteValue()]]: delete the value with the specified key from cache
+ * - [[flushValues()]]: delete all values from cache
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+abstract class Cache extends Component implements \ArrayAccess
+{
+ /**
+ * @var string a string prefixed to every cache key so that it is unique. If not set,
+ * it will use a prefix generated from [[Application::id]]. You may set this property to be an empty string
+ * if you don't want to use key prefix. It is recommended that you explicitly set this property to some
+ * static value if the cached data needs to be shared among multiple applications.
+ *
+ * To ensure interoperability, only use alphanumeric characters should be used.
+ */
+ public $keyPrefix;
+ /**
+ * @var array|boolean the functions used to serialize and unserialize cached data. Defaults to null, meaning
+ * using the default PHP `serialize()` and `unserialize()` functions. If you want to use some more efficient
+ * serializer (e.g. [igbinary](http://pecl.php.net/package/igbinary)), you may configure this property with
+ * a two-element array. The first element specifies the serialization function, and the second the deserialization
+ * function. If this property is set false, data will be directly sent to and retrieved from the underlying
+ * cache component without any serialization or deserialization. You should not turn off serialization if
+ * you are using [[Dependency|cache dependency]], because it relies on data serialization.
+ */
+ public $serializer;
+
+
+ /**
+ * Initializes the application component.
+ * This method overrides the parent implementation by setting default cache key prefix.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->keyPrefix === null) {
+ $this->keyPrefix = substr(md5(Yii::$app->id), 0, 5);
+ } elseif (!ctype_alnum($this->keyPrefix)) {
+ throw new InvalidConfigException(get_class($this) . '::keyPrefix should only contain alphanumeric characters.');
+ }
+ }
+
+ /**
+ * Builds a normalized cache key from a given key.
+ *
+ * If the given key is a string containing alphanumeric characters only and no more than 32 characters,
+ * then the key will be returned back prefixed with [[keyPrefix]]. Otherwise, a normalized key
+ * is generated by serializing the given key, applying MD5 hashing, and prefixing with [[keyPrefix]].
+ *
+ * @param mixed $key the key to be normalized
+ * @return string the generated cache key
+ */
+ protected function buildKey($key)
+ {
+ if (is_string($key)) {
+ $key = ctype_alnum($key) && StringHelper::strlen($key) <= 32 ? $key : md5($key);
+ } else {
+ $key = md5(json_encode($key));
+ }
+ return $this->keyPrefix . $key;
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return mixed the value stored in cache, false if the value is not in the cache, expired,
+ * or the dependency associated with the cached data has changed.
+ */
+ public function get($key)
+ {
+ $key = $this->buildKey($key);
+ $value = $this->getValue($key);
+ if ($value === false || $this->serializer === false) {
+ return $value;
+ } elseif ($this->serializer === null) {
+ $value = unserialize($value);
+ } else {
+ $value = call_user_func($this->serializer[1], $value);
+ }
+ if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) {
+ return $value[0];
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * In case a cache does not support this feature natively, this method will try to simulate it
+ * but has no performance improvement over getting it.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
+ $value = $this->getValue($key);
+ return $value !== false;
+ }
+
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * Some caches (such as memcache, apc) allow retrieving multiple cached values at the same time,
+ * which may improve the performance. In case a cache does not support this feature natively,
+ * this method will try to simulate it.
+ * @param array $keys list of keys identifying the cached values
+ * @return array list of cached values corresponding to the specified keys. The array
+ * is returned in terms of (key, value) pairs.
+ * If a value is not cached or expired, the corresponding array value will be false.
+ */
+ public function mget($keys)
+ {
+ $keyMap = array();
+ foreach ($keys as $key) {
+ $keyMap[$key] = $this->buildKey($key);
+ }
+ $values = $this->getValues(array_values($keyMap));
+ $results = array();
+ foreach ($keyMap as $key => $newKey) {
+ $results[$key] = false;
+ if (isset($values[$newKey])) {
+ if ($this->serializer === false) {
+ $results[$key] = $values[$newKey];
+ } else {
+ $value = $this->serializer === null ? unserialize($values[$newKey])
+ : call_user_func($this->serializer[1], $values[$newKey]);
+
+ if (is_array($value) && !($value[1] instanceof Dependency && $value[1]->getHasChanged($this))) {
+ $results[$key] = $value[0];
+ }
+ }
+ }
+ }
+ return $results;
+ }
+
+ /**
+ * Stores a value identified by a key into cache.
+ * If the cache already contains such a key, the existing value and
+ * expiration time will be replaced with the new ones, respectively.
+ *
+ * @param mixed $key a key identifying the value to be cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @param mixed $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @param Dependency $dependency dependency of the cached item. If the dependency changes,
+ * the corresponding value in the cache will be invalidated when it is fetched via [[get()]].
+ * This parameter is ignored if [[serializer]] is false.
+ * @return boolean whether the value is successfully stored into cache
+ */
+ public function set($key, $value, $expire = 0, $dependency = null)
+ {
+ if ($dependency !== null && $this->serializer !== false) {
+ $dependency->evaluateDependency($this);
+ }
+ if ($this->serializer === null) {
+ $value = serialize(array($value, $dependency));
+ } elseif ($this->serializer !== false) {
+ $value = call_user_func($this->serializer[0], array($value, $dependency));
+ }
+ $key = $this->buildKey($key);
+ return $this->setValue($key, $value, $expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * Nothing will be done if the cache already contains the key.
+ * @param mixed $key a key identifying the value to be cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @param mixed $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @param Dependency $dependency dependency of the cached item. If the dependency changes,
+ * the corresponding value in the cache will be invalidated when it is fetched via [[get()]].
+ * This parameter is ignored if [[serializer]] is false.
+ * @return boolean whether the value is successfully stored into cache
+ */
+ public function add($key, $value, $expire = 0, $dependency = null)
+ {
+ if ($dependency !== null && $this->serializer !== false) {
+ $dependency->evaluateDependency($this);
+ }
+ if ($this->serializer === null) {
+ $value = serialize(array($value, $dependency));
+ } elseif ($this->serializer !== false) {
+ $value = call_user_func($this->serializer[0], array($value, $dependency));
+ }
+ $key = $this->buildKey($key);
+ return $this->addValue($key, $value, $expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * @param mixed $key a key identifying the value to be deleted from cache. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean if no error happens during deletion
+ */
+ public function delete($key)
+ {
+ $key = $this->buildKey($key);
+ return $this->deleteValue($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * Be careful of performing this operation if the cache is shared among multiple applications.
+ * @return boolean whether the flush operation was successful.
+ */
+ public function flush()
+ {
+ return $this->flushValues();
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This method should be implemented by child classes to retrieve the data
+ * from specific cache storage.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ abstract protected function getValue($key);
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This method should be implemented by child classes to store the data
+ * in specific cache storage.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ abstract protected function setValue($key, $value, $expire);
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This method should be implemented by child classes to store the data
+ * in specific cache storage.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ abstract protected function addValue($key, $value, $expire);
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This method should be implemented by child classes to delete the data from actual cache storage.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ abstract protected function deleteValue($key);
+
+ /**
+ * Deletes all values from cache.
+ * Child classes may implement this method to realize the flush operation.
+ * @return boolean whether the flush operation was successful.
+ */
+ abstract protected function flushValues();
+
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * The default implementation calls [[getValue()]] multiple times to retrieve
+ * the cached values one by one. If the underlying cache storage supports multiget,
+ * this method should be overridden to exploit that feature.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ $results = array();
+ foreach ($keys as $key) {
+ $results[$key] = $this->getValue($key);
+ }
+ return $results;
+ }
+
+ /**
+ * Returns whether there is a cache entry with a specified key.
+ * This method is required by the interface ArrayAccess.
+ * @param string $key a key identifying the cached value
+ * @return boolean
+ */
+ public function offsetExists($key)
+ {
+ return $this->get($key) !== false;
+ }
+
+ /**
+ * Retrieves the value from cache with a specified key.
+ * This method is required by the interface ArrayAccess.
+ * @param string $key a key identifying the cached value
+ * @return mixed the value stored in cache, false if the value is not in the cache or expired.
+ */
+ public function offsetGet($key)
+ {
+ return $this->get($key);
+ }
+
+ /**
+ * Stores the value identified by a key into cache.
+ * If the cache already contains such a key, the existing value will be
+ * replaced with the new ones. To add expiration and dependencies, use the [[set()]] method.
+ * This method is required by the interface ArrayAccess.
+ * @param string $key the key identifying the value to be cached
+ * @param mixed $value the value to be cached
+ */
+ public function offsetSet($key, $value)
+ {
+ $this->set($key, $value);
+ }
+
+ /**
+ * Deletes the value with the specified key from cache
+ * This method is required by the interface ArrayAccess.
+ * @param string $key the key of the value to be deleted
+ */
+ public function offsetUnset($key)
+ {
+ $this->delete($key);
+ }
+}
diff --git a/framework/yii/caching/ChainedDependency.php b/framework/yii/caching/ChainedDependency.php
new file mode 100644
index 0000000..d3b9953
--- /dev/null
+++ b/framework/yii/caching/ChainedDependency.php
@@ -0,0 +1,87 @@
+
+ * @since 2.0
+ */
+class ChainedDependency extends Dependency
+{
+ /**
+ * @var Dependency[] list of dependencies that this dependency is composed of.
+ * Each array element must be a dependency object.
+ */
+ public $dependencies;
+ /**
+ * @var boolean whether this dependency is depending on every dependency in [[dependencies]].
+ * Defaults to true, meaning if any of the dependencies has changed, this dependency is considered changed.
+ * When it is set false, it means if one of the dependencies has NOT changed, this dependency
+ * is considered NOT changed.
+ */
+ public $dependOnAll = true;
+
+ /**
+ * Constructor.
+ * @param Dependency[] $dependencies list of dependencies that this dependency is composed of.
+ * Each array element should be a dependency object.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($dependencies = array(), $config = array())
+ {
+ $this->dependencies = $dependencies;
+ parent::__construct($config);
+ }
+
+ /**
+ * Evaluates the dependency by generating and saving the data related with dependency.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ */
+ public function evaluateDependency($cache)
+ {
+ foreach ($this->dependencies as $dependency) {
+ $dependency->evaluateDependency($cache);
+ }
+ }
+
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method does nothing in this class.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ */
+ protected function generateDependencyData($cache)
+ {
+ return null;
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * This method returns true if any of the dependency objects
+ * reports a dependency change.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return boolean whether the dependency is changed or not.
+ */
+ public function getHasChanged($cache)
+ {
+ foreach ($this->dependencies as $dependency) {
+ if ($this->dependOnAll && $dependency->getHasChanged($cache)) {
+ return true;
+ } elseif (!$this->dependOnAll && !$dependency->getHasChanged($cache)) {
+ return false;
+ }
+ }
+ return !$this->dependOnAll;
+ }
+}
diff --git a/framework/yii/caching/DbCache.php b/framework/yii/caching/DbCache.php
new file mode 100644
index 0000000..b7b6692
--- /dev/null
+++ b/framework/yii/caching/DbCache.php
@@ -0,0 +1,276 @@
+ array(
+ * 'class' => 'yii\caching\DbCache',
+ * // 'db' => 'mydb',
+ * // 'cacheTable' => 'my_cache',
+ * )
+ * ~~~
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class DbCache extends Cache
+{
+ /**
+ * @var Connection|string the DB connection object or the application component ID of the DB connection.
+ * After the DbCache object is created, if you want to change this property, you should only assign it
+ * with a DB connection object.
+ */
+ public $db = 'db';
+ /**
+ * @var string name of the DB table to store cache content.
+ * The table should be pre-created as follows:
+ *
+ * ~~~
+ * CREATE TABLE tbl_cache (
+ * id char(128) NOT NULL PRIMARY KEY,
+ * expire int(11),
+ * data BLOB
+ * );
+ * ~~~
+ *
+ * where 'BLOB' refers to the BLOB-type of your preferred DBMS. Below are the BLOB type
+ * that can be used for some popular DBMS:
+ *
+ * - MySQL: LONGBLOB
+ * - PostgreSQL: BYTEA
+ * - MSSQL: BLOB
+ *
+ * When using DbCache in a production server, we recommend you create a DB index for the 'expire'
+ * column in the cache table to improve the performance.
+ */
+ public $cacheTable = 'tbl_cache';
+ /**
+ * @var integer the probability (parts per million) that garbage collection (GC) should be performed
+ * when storing a piece of data in the cache. Defaults to 100, meaning 0.01% chance.
+ * This number should be between 0 and 1000000. A value 0 meaning no GC will be performed at all.
+ **/
+ public $gcProbability = 100;
+
+
+ /**
+ * Initializes the DbCache component.
+ * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+ * @throws InvalidConfigException if [[db]] is invalid.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new InvalidConfigException("DbCache::db must be either a DB connection instance or the application component ID of a DB connection.");
+ }
+ }
+
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
+
+ $query = new Query;
+ $query->select(array('COUNT(*)'))
+ ->from($this->cacheTable)
+ ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', array(':id' => $key));
+ if ($this->db->enableQueryCache) {
+ // temporarily disable and re-enable query caching
+ $this->db->enableQueryCache = false;
+ $result = $query->createCommand($this->db)->queryScalar();
+ $this->db->enableQueryCache = true;
+ } else {
+ $result = $query->createCommand($this->db)->queryScalar();
+ }
+ return $result > 0;
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ $query = new Query;
+ $query->select(array('data'))
+ ->from($this->cacheTable)
+ ->where('[[id]] = :id AND ([[expire]] = 0 OR [[expire]] >' . time() . ')', array(':id' => $key));
+ if ($this->db->enableQueryCache) {
+ // temporarily disable and re-enable query caching
+ $this->db->enableQueryCache = false;
+ $result = $query->createCommand($this->db)->queryScalar();
+ $this->db->enableQueryCache = true;
+ return $result;
+ } else {
+ return $query->createCommand($this->db)->queryScalar();
+ }
+ }
+
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ if (empty($keys)) {
+ return array();
+ }
+ $query = new Query;
+ $query->select(array('id', 'data'))
+ ->from($this->cacheTable)
+ ->where(array('id' => $keys))
+ ->andWhere('([[expire]] = 0 OR [[expire]] > ' . time() . ')');
+
+ if ($this->db->enableQueryCache) {
+ $this->db->enableQueryCache = false;
+ $rows = $query->createCommand($this->db)->queryAll();
+ $this->db->enableQueryCache = true;
+ } else {
+ $rows = $query->createCommand($this->db)->queryAll();
+ }
+
+ $results = array();
+ foreach ($keys as $key) {
+ $results[$key] = false;
+ }
+ foreach ($rows as $row) {
+ $results[$row['id']] = $row['data'];
+ }
+ return $results;
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ $command = $this->db->createCommand()
+ ->update($this->cacheTable, array(
+ 'expire' => $expire > 0 ? $expire + time() : 0,
+ 'data' => array($value, \PDO::PARAM_LOB),
+ ), array(
+ 'id' => $key,
+ ));
+
+ if ($command->execute()) {
+ $this->gc();
+ return true;
+ } else {
+ return $this->addValue($key, $value, $expire);
+ }
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ $this->gc();
+
+ if ($expire > 0) {
+ $expire += time();
+ } else {
+ $expire = 0;
+ }
+
+ try {
+ $this->db->createCommand()
+ ->insert($this->cacheTable, array(
+ 'id' => $key,
+ 'expire' => $expire,
+ 'data' => array($value, \PDO::PARAM_LOB),
+ ))->execute();
+ return true;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ $this->db->createCommand()
+ ->delete($this->cacheTable, array('id' => $key))
+ ->execute();
+ return true;
+ }
+
+ /**
+ * Removes the expired data values.
+ * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
+ * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
+ */
+ public function gc($force = false)
+ {
+ if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
+ $this->db->createCommand()
+ ->delete($this->cacheTable, '[[expire]] > 0 AND [[expire]] < ' . time())
+ ->execute();
+ }
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ $this->db->createCommand()
+ ->delete($this->cacheTable)
+ ->execute();
+ return true;
+ }
+}
diff --git a/framework/yii/caching/DbDependency.php b/framework/yii/caching/DbDependency.php
new file mode 100644
index 0000000..0b559d7
--- /dev/null
+++ b/framework/yii/caching/DbDependency.php
@@ -0,0 +1,76 @@
+
+ * @since 2.0
+ */
+class DbDependency extends Dependency
+{
+ /**
+ * @var string the application component ID of the DB connection.
+ */
+ public $db = 'db';
+ /**
+ * @var string the SQL query whose result is used to determine if the dependency has been changed.
+ * Only the first row of the query result will be used.
+ */
+ public $sql;
+ /**
+ * @var array the parameters (name => value) to be bound to the SQL statement specified by [[sql]].
+ */
+ public $params;
+
+ /**
+ * Constructor.
+ * @param string $sql the SQL query whose result is used to determine if the dependency has been changed.
+ * @param array $params the parameters (name => value) to be bound to the SQL statement specified by [[sql]].
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($sql, $params = array(), $config = array())
+ {
+ $this->sql = $sql;
+ $this->params = $params;
+ parent::__construct($config);
+ }
+
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method returns the value of the global state.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ * @throws InvalidConfigException if [[db]] is not a valid application component ID
+ */
+ protected function generateDependencyData($cache)
+ {
+ $db = Yii::$app->getComponent($this->db);
+ if (!$db instanceof Connection) {
+ throw new InvalidConfigException("DbDependency::db must be the application component ID of a DB connection.");
+ }
+
+ if ($db->enableQueryCache) {
+ // temporarily disable and re-enable query caching
+ $db->enableQueryCache = false;
+ $result = $db->createCommand($this->sql, $this->params)->queryOne();
+ $db->enableQueryCache = true;
+ } else {
+ $result = $db->createCommand($this->sql, $this->params)->queryOne();
+ }
+ return $result;
+ }
+}
diff --git a/framework/yii/caching/Dependency.php b/framework/yii/caching/Dependency.php
new file mode 100644
index 0000000..91c0568
--- /dev/null
+++ b/framework/yii/caching/Dependency.php
@@ -0,0 +1,99 @@
+
+ * @since 2.0
+ */
+abstract class Dependency extends \yii\base\Object
+{
+ /**
+ * @var mixed the dependency data that is saved in cache and later is compared with the
+ * latest dependency data.
+ */
+ public $data;
+ /**
+ * @var boolean whether this dependency is reusable or not. True value means that dependent
+ * data for this cache dependency will be generated only once per request. This allows you
+ * to use the same cache dependency for multiple separate cache calls while generating the same
+ * page without an overhead of re-evaluating dependency data each time. Defaults to false.
+ */
+ public $reusable = false;
+
+ /**
+ * @var array static storage of cached data for reusable dependencies.
+ */
+ private static $_reusableData = array();
+ /**
+ * @var string a unique hash value for this cache dependency.
+ */
+ private $_hash;
+
+
+ /**
+ * Evaluates the dependency by generating and saving the data related with dependency.
+ * This method is invoked by cache before writing data into it.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ */
+ public function evaluateDependency($cache)
+ {
+ if (!$this->reusable) {
+ $this->data = $this->generateDependencyData($cache);
+ } else {
+ if ($this->_hash === null) {
+ $this->_hash = sha1(serialize($this));
+ }
+ if (!array_key_exists($this->_hash, self::$_reusableData)) {
+ self::$_reusableData[$this->_hash] = $this->generateDependencyData($cache);
+ }
+ $this->data = self::$_reusableData[$this->_hash];
+ }
+ }
+
+ /**
+ * Returns a value indicating whether the dependency has changed.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return boolean whether the dependency has changed.
+ */
+ public function getHasChanged($cache)
+ {
+ if (!$this->reusable) {
+ return $this->generateDependencyData($cache) !== $this->data;
+ } else {
+ if ($this->_hash === null) {
+ $this->_hash = sha1(serialize($this));
+ }
+ if (!array_key_exists($this->_hash, self::$_reusableData)) {
+ self::$_reusableData[$this->_hash] = $this->generateDependencyData($cache);
+ }
+ return self::$_reusableData[$this->_hash] !== $this->data;
+ }
+ }
+
+ /**
+ * Resets all cached data for reusable dependencies.
+ */
+ public static function resetReusableData()
+ {
+ self::$_reusableData = array();
+ }
+
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * Derived classes should override this method to generate the actual dependency data.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ */
+ abstract protected function generateDependencyData($cache);
+}
diff --git a/framework/yii/caching/DummyCache.php b/framework/yii/caching/DummyCache.php
new file mode 100644
index 0000000..8d900df
--- /dev/null
+++ b/framework/yii/caching/DummyCache.php
@@ -0,0 +1,81 @@
+cache`.
+ * By replacing DummyCache with some other cache component, one can quickly switch from
+ * non-caching mode to caching mode.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class DummyCache extends Cache
+{
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return false;
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return true;
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return true;
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return true;
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return true;
+ }
+}
diff --git a/framework/yii/caching/ExpressionDependency.php b/framework/yii/caching/ExpressionDependency.php
new file mode 100644
index 0000000..533c2ab
--- /dev/null
+++ b/framework/yii/caching/ExpressionDependency.php
@@ -0,0 +1,60 @@
+
+ * @since 2.0
+ */
+class ExpressionDependency extends Dependency
+{
+ /**
+ * @var string the string representation of a PHP expression whose result is used to determine the dependency.
+ * A PHP expression can be any PHP code that evaluates to a value. To learn more about what an expression is,
+ * please refer to the [php manual](http://www.php.net/manual/en/language.expressions.php).
+ */
+ public $expression;
+ /**
+ * @var mixed custom parameters associated with this dependency. You may get the value
+ * of this property in [[expression]] using `$this->params`.
+ */
+ public $params;
+
+ /**
+ * Constructor.
+ * @param string $expression the PHP expression whose result is used to determine the dependency.
+ * @param mixed $params the custom parameters associated with this dependency
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($expression = 'true', $params = null, $config = array())
+ {
+ $this->expression = $expression;
+ $this->params = $params;
+ parent::__construct($config);
+ }
+
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method returns the result of the PHP expression.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ */
+ protected function generateDependencyData($cache)
+ {
+ return eval("return {$this->expression};");
+ }
+}
diff --git a/framework/yii/caching/FileCache.php b/framework/yii/caching/FileCache.php
new file mode 100644
index 0000000..32cdf58
--- /dev/null
+++ b/framework/yii/caching/FileCache.php
@@ -0,0 +1,240 @@
+
+ * @since 2.0
+ */
+class FileCache extends Cache
+{
+ /**
+ * @var string the directory to store cache files. You may use path alias here.
+ * If not set, it will use the "cache" subdirectory under the application runtime path.
+ */
+ public $cachePath = '@runtime/cache';
+ /**
+ * @var string cache file suffix. Defaults to '.bin'.
+ */
+ public $cacheFileSuffix = '.bin';
+ /**
+ * @var integer the level of sub-directories to store cache files. Defaults to 1.
+ * If the system has huge number of cache files (e.g. one million), you may use a bigger value
+ * (usually no bigger than 3). Using sub-directories is mainly to ensure the file system
+ * is not over burdened with a single directory having too many files.
+ */
+ public $directoryLevel = 1;
+ /**
+ * @var integer the probability (parts per million) that garbage collection (GC) should be performed
+ * when storing a piece of data in the cache. Defaults to 10, meaning 0.001% chance.
+ * This number should be between 0 and 1000000. A value 0 means no GC will be performed at all.
+ **/
+ public $gcProbability = 10;
+ /**
+ * @var integer the permission to be set for newly created cache files.
+ * This value will be used by PHP chmod() function. No umask will be applied.
+ * If not set, the permission will be determined by the current environment.
+ */
+ public $fileMode;
+ /**
+ * @var integer the permission to be set for newly created directories.
+ * This value will be used by PHP chmod() function. No umask will be applied.
+ * Defaults to 0775, meaning the directory is read-writable by owner and group,
+ * but read-only for other users.
+ */
+ public $dirMode = 0775;
+
+
+ /**
+ * Initializes this component by ensuring the existence of the cache path.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->cachePath = Yii::getAlias($this->cachePath);
+ if (!is_dir($this->cachePath)) {
+ FileHelper::createDirectory($this->cachePath, $this->dirMode, true);
+ }
+ }
+
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $cacheFile = $this->getCacheFile($this->buildKey($key));
+ return @filemtime($cacheFile) > time();
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ $cacheFile = $this->getCacheFile($key);
+ if (@filemtime($cacheFile) > time()) {
+ return @file_get_contents($cacheFile);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ if ($expire <= 0) {
+ $expire = 31536000; // 1 year
+ }
+ $expire += time();
+
+ $cacheFile = $this->getCacheFile($key);
+ if ($this->directoryLevel > 0) {
+ @FileHelper::createDirectory(dirname($cacheFile), $this->dirMode, true);
+ }
+ if (@file_put_contents($cacheFile, $value, LOCK_EX) !== false) {
+ if ($this->fileMode !== null) {
+ @chmod($cacheFile, $this->fileMode);
+ }
+ return @touch($cacheFile, $expire);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ $cacheFile = $this->getCacheFile($key);
+ if (@filemtime($cacheFile) > time()) {
+ return false;
+ }
+ return $this->setValue($key, $value, $expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ $cacheFile = $this->getCacheFile($key);
+ return @unlink($cacheFile);
+ }
+
+ /**
+ * Returns the cache file path given the cache key.
+ * @param string $key cache key
+ * @return string the cache file path
+ */
+ protected function getCacheFile($key)
+ {
+ if ($this->directoryLevel > 0) {
+ $base = $this->cachePath;
+ for ($i = 0; $i < $this->directoryLevel; ++$i) {
+ if (($prefix = substr($key, $i + $i, 2)) !== false) {
+ $base .= DIRECTORY_SEPARATOR . $prefix;
+ }
+ }
+ return $base . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix;
+ } else {
+ return $this->cachePath . DIRECTORY_SEPARATOR . $key . $this->cacheFileSuffix;
+ }
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ $this->gc(true, false);
+ return true;
+ }
+
+ /**
+ * Removes expired cache files.
+ * @param boolean $force whether to enforce the garbage collection regardless of [[gcProbability]].
+ * Defaults to false, meaning the actual deletion happens with the probability as specified by [[gcProbability]].
+ * @param boolean $expiredOnly whether to removed expired cache files only.
+ * If true, all cache files under [[cachePath]] will be removed.
+ */
+ public function gc($force = false, $expiredOnly = true)
+ {
+ if ($force || mt_rand(0, 1000000) < $this->gcProbability) {
+ $this->gcRecursive($this->cachePath, $expiredOnly);
+ }
+ }
+
+ /**
+ * Recursively removing expired cache files under a directory.
+ * This method is mainly used by [[gc()]].
+ * @param string $path the directory under which expired cache files are removed.
+ * @param boolean $expiredOnly whether to only remove expired cache files. If false, all files
+ * under `$path` will be removed.
+ */
+ protected function gcRecursive($path, $expiredOnly)
+ {
+ if (($handle = opendir($path)) !== false) {
+ while (($file = readdir($handle)) !== false) {
+ if ($file[0] === '.') {
+ continue;
+ }
+ $fullPath = $path . DIRECTORY_SEPARATOR . $file;
+ if (is_dir($fullPath)) {
+ $this->gcRecursive($fullPath, $expiredOnly);
+ if (!$expiredOnly) {
+ @rmdir($fullPath);
+ }
+ } elseif (!$expiredOnly || $expiredOnly && @filemtime($fullPath) < time()) {
+ @unlink($fullPath);
+ }
+ }
+ closedir($handle);
+ }
+ }
+}
diff --git a/framework/yii/caching/FileDependency.php b/framework/yii/caching/FileDependency.php
new file mode 100644
index 0000000..bcc48a8
--- /dev/null
+++ b/framework/yii/caching/FileDependency.php
@@ -0,0 +1,48 @@
+
+ * @since 2.0
+ */
+class FileDependency extends Dependency
+{
+ /**
+ * @var string the name of the file whose last modification time is used to
+ * check if the dependency has been changed.
+ */
+ public $fileName;
+
+ /**
+ * Constructor.
+ * @param string $fileName name of the file whose change is to be checked.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($fileName = null, $config = array())
+ {
+ $this->fileName = $fileName;
+ parent::__construct($config);
+ }
+
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method returns the file's last modification time.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ */
+ protected function generateDependencyData($cache)
+ {
+ return @filemtime($this->fileName);
+ }
+}
diff --git a/framework/yii/caching/GroupDependency.php b/framework/yii/caching/GroupDependency.php
new file mode 100644
index 0000000..d63cee4
--- /dev/null
+++ b/framework/yii/caching/GroupDependency.php
@@ -0,0 +1,75 @@
+
+ * @since 2.0
+ */
+class GroupDependency extends Dependency
+{
+ /**
+ * @var string the group name
+ */
+ public $group;
+
+ /**
+ * Constructor.
+ * @param string $group the group name
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($group, $config = array())
+ {
+ $this->group = $group;
+ parent::__construct($config);
+ }
+
+ /**
+ * Generates the data needed to determine if dependency has been changed.
+ * This method does nothing in this class.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return mixed the data needed to determine if dependency has been changed.
+ */
+ protected function generateDependencyData($cache)
+ {
+ $version = $cache->get(array(__CLASS__, $this->group));
+ if ($version === false) {
+ $version = $this->invalidate($cache, $this->group);
+ }
+ return $version;
+ }
+
+ /**
+ * Performs the actual dependency checking.
+ * @param Cache $cache the cache component that is currently evaluating this dependency
+ * @return boolean whether the dependency is changed or not.
+ */
+ public function getHasChanged($cache)
+ {
+ $version = $cache->get(array(__CLASS__, $this->group));
+ return $version === false || $version !== $this->data;
+ }
+
+ /**
+ * Invalidates all of the cached data items that have the same [[group]].
+ * @param Cache $cache the cache component that caches the data items
+ * @param string $group the group name
+ * @return string the current version number
+ */
+ public static function invalidate($cache, $group)
+ {
+ $version = microtime();
+ $cache->set(array(__CLASS__, $group), $version);
+ return $version;
+ }
+}
diff --git a/framework/yii/caching/MemCache.php b/framework/yii/caching/MemCache.php
new file mode 100644
index 0000000..69a90b4
--- /dev/null
+++ b/framework/yii/caching/MemCache.php
@@ -0,0 +1,244 @@
+ array(
+ * 'cache' => array(
+ * 'class' => 'MemCache',
+ * 'servers' => array(
+ * array(
+ * 'host' => 'server1',
+ * 'port' => 11211,
+ * 'weight' => 60,
+ * ),
+ * array(
+ * 'host' => 'server2',
+ * 'port' => 11211,
+ * 'weight' => 40,
+ * ),
+ * ),
+ * ),
+ * ),
+ * )
+ * ~~~
+ *
+ * In the above, two memcache servers are used: server1 and server2. You can configure more properties of
+ * each server, such as `persistent`, `weight`, `timeout`. Please see [[MemCacheServer]] for available options.
+ *
+ * @property \Memcache|\Memcached $memcache The memcache (or memcached) object used by this cache component.
+ * This property is read-only.
+ * @property MemCacheServer[] $servers List of memcache server configurations. Note that the type of this
+ * property differs in getter and setter. See [[getServers()]] and [[setServers()]] for details.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class MemCache extends Cache
+{
+ /**
+ * @var boolean whether to use memcached or memcache as the underlying caching extension.
+ * If true, [memcached](http://pecl.php.net/package/memcached) will be used.
+ * If false, [memcache](http://pecl.php.net/package/memcache) will be used.
+ * Defaults to false.
+ */
+ public $useMemcached = false;
+ /**
+ * @var \Memcache|\Memcached the Memcache instance
+ */
+ private $_cache = null;
+ /**
+ * @var array list of memcache server configurations
+ */
+ private $_servers = array();
+
+ /**
+ * Initializes this application component.
+ * It creates the memcache instance and adds memcache servers.
+ */
+ public function init()
+ {
+ parent::init();
+ $servers = $this->getServers();
+ $cache = $this->getMemCache();
+ if (empty($servers)) {
+ $cache->addServer('127.0.0.1', 11211);
+ } else {
+ if (!$this->useMemcached) {
+ // different version of memcache may have different number of parameters for the addServer method.
+ $class = new \ReflectionClass($cache);
+ $paramCount = $class->getMethod('addServer')->getNumberOfParameters();
+ }
+ foreach ($servers as $server) {
+ if ($server->host === null) {
+ throw new InvalidConfigException("The 'host' property must be specified for every memcache server.");
+ }
+ if ($this->useMemcached) {
+ $cache->addServer($server->host, $server->port, $server->weight);
+ } else {
+ // $timeout is used for memcache versions that do not have timeoutms parameter
+ $timeout = (int) ($server->timeout / 1000) + (($server->timeout % 1000 > 0) ? 1 : 0);
+ if ($paramCount === 9) {
+ $cache->addServer(
+ $server->host, $server->port, $server->persistent,
+ $server->weight, $timeout, $server->retryInterval,
+ $server->status, $server->failureCallback, $server->timeout
+ );
+ } else {
+ $cache->addServer(
+ $server->host, $server->port, $server->persistent,
+ $server->weight, $timeout, $server->retryInterval,
+ $server->status, $server->failureCallback
+ );
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Returns the underlying memcache (or memcached) object.
+ * @return \Memcache|\Memcached the memcache (or memcached) object used by this cache component.
+ * @throws InvalidConfigException if memcache or memcached extension is not loaded
+ */
+ public function getMemcache()
+ {
+ if ($this->_cache === null) {
+ $extension = $this->useMemcached ? 'memcached' : 'memcache';
+ if (!extension_loaded($extension)) {
+ throw new InvalidConfigException("MemCache requires PHP $extension extension to be loaded.");
+ }
+ $this->_cache = $this->useMemcached ? new \Memcached : new \Memcache;
+ }
+ return $this->_cache;
+ }
+
+ /**
+ * Returns the memcache server configurations.
+ * @return MemCacheServer[] list of memcache server configurations.
+ */
+ public function getServers()
+ {
+ return $this->_servers;
+ }
+
+ /**
+ * @param array $config list of memcache server configurations. Each element must be an array
+ * with the following keys: host, port, persistent, weight, timeout, retryInterval, status.
+ * @see http://www.php.net/manual/en/function.Memcache-addServer.php
+ */
+ public function setServers($config)
+ {
+ foreach ($config as $c) {
+ $this->_servers[] = new MemCacheServer($c);
+ }
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return $this->_cache->get($key);
+ }
+
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ return $this->useMemcached ? $this->_cache->getMulti($keys) : $this->_cache->get($keys);
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ if ($expire > 0) {
+ $expire += time();
+ } else {
+ $expire = 0;
+ }
+
+ return $this->useMemcached ? $this->_cache->set($key, $value, $expire) : $this->_cache->set($key, $value, 0, $expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ if ($expire > 0) {
+ $expire += time();
+ } else {
+ $expire = 0;
+ }
+
+ return $this->useMemcached ? $this->_cache->add($key, $value, $expire) : $this->_cache->add($key, $value, 0, $expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return $this->_cache->delete($key, 0);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return $this->_cache->flush();
+ }
+}
diff --git a/framework/yii/caching/MemCacheServer.php b/framework/yii/caching/MemCacheServer.php
new file mode 100644
index 0000000..b1b7727
--- /dev/null
+++ b/framework/yii/caching/MemCacheServer.php
@@ -0,0 +1,58 @@
+
+ * @since 2.0
+ */
+class MemCacheServer extends \yii\base\Object
+{
+ /**
+ * @var string memcache server hostname or IP address
+ */
+ public $host;
+ /**
+ * @var integer memcache server port
+ */
+ public $port = 11211;
+ /**
+ * @var integer probability of using this server among all servers.
+ */
+ public $weight = 1;
+ /**
+ * @var boolean whether to use a persistent connection. This is used by memcache only.
+ */
+ public $persistent = true;
+ /**
+ * @var integer timeout in milliseconds which will be used for connecting to the server.
+ * This is used by memcache only. For old versions of memcache that only support specifying
+ * timeout in seconds this will be rounded up to full seconds.
+ */
+ public $timeout = 1000;
+ /**
+ * @var integer how often a failed server will be retried (in seconds). This is used by memcache only.
+ */
+ public $retryInterval = 15;
+ /**
+ * @var boolean if the server should be flagged as online upon a failure. This is used by memcache only.
+ */
+ public $status = true;
+ /**
+ * @var \Closure this callback function will run upon encountering an error.
+ * The callback is run before fail over is attempted. The function takes two parameters,
+ * the [[host]] and the [[port]] of the failed server.
+ * This is used by memcache only.
+ */
+ public $failureCallback;
+}
diff --git a/framework/yii/caching/RedisCache.php b/framework/yii/caching/RedisCache.php
new file mode 100644
index 0000000..7cb6451
--- /dev/null
+++ b/framework/yii/caching/RedisCache.php
@@ -0,0 +1,213 @@
+array(
+ * 'cache'=>array(
+ * 'class'=>'RedisCache',
+ * 'hostname'=>'localhost',
+ * 'port'=>6379,
+ * 'database'=>0,
+ * ),
+ * ),
+ * )
+ * ~~~
+ *
+ * @property Connection $connection The redis connection object. This property is read-only.
+ *
+ * @author Carsten Brandt
+ * @since 2.0
+ */
+class RedisCache extends Cache
+{
+ /**
+ * @var string hostname to use for connecting to the redis server. Defaults to 'localhost'.
+ */
+ public $hostname = 'localhost';
+ /**
+ * @var int the port to use for connecting to the redis server. Default port is 6379.
+ */
+ public $port = 6379;
+ /**
+ * @var string the password to use to authenticate with the redis server. If not set, no AUTH command will be sent.
+ */
+ public $password;
+ /**
+ * @var int the redis database to use. This is an integer value starting from 0. Defaults to 0.
+ */
+ public $database = 0;
+ /**
+ * @var float timeout to use for connection to redis. If not set the timeout set in php.ini will be used: ini_get("default_socket_timeout")
+ */
+ public $connectionTimeout = null;
+ /**
+ * @var float timeout to use for redis socket when reading and writing data. If not set the php default value will be used.
+ */
+ public $dataTimeout = null;
+ /**
+ * @var Connection the redis connection
+ */
+ private $_connection;
+
+
+ /**
+ * Initializes the cache component by establishing a connection to the redis server.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->getConnection();
+ }
+
+ /**
+ * Returns the redis connection object.
+ * Establishes a connection to the redis server if it does not already exists.
+ * @return Connection the redis connection object.
+ */
+ public function getConnection()
+ {
+ if ($this->_connection === null) {
+ $this->_connection = new Connection(array(
+ 'dsn' => 'redis://' . $this->hostname . ':' . $this->port . '/' . $this->database,
+ 'password' => $this->password,
+ 'connectionTimeout' => $this->connectionTimeout,
+ 'dataTimeout' => $this->dataTimeout,
+ ));
+ }
+ return $this->_connection;
+ }
+
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ return (bool) $this->_connection->executeCommand('EXISTS', array($this->buildKey($key)));
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return $this->_connection->executeCommand('GET', array($key));
+ }
+
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ $response = $this->_connection->executeCommand('MGET', $keys);
+ $result = array();
+ $i = 0;
+ foreach($keys as $key) {
+ $result[$key] = $response[$i++];
+ }
+ return $result;
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param float $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * This can be a floating point number to specify the time in milliseconds.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key,$value,$expire)
+ {
+ if ($expire == 0) {
+ return (bool) $this->_connection->executeCommand('SET', array($key, $value));
+ } else {
+ $expire = (int) ($expire * 1000);
+ return (bool) $this->_connection->executeCommand('PSETEX', array($key, $expire, $value));
+ }
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param float $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * This can be a floating point number to specify the time in milliseconds.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key,$value,$expire)
+ {
+ if ($expire == 0) {
+ return (bool) $this->_connection->executeCommand('SETNX', array($key, $value));
+ } else {
+ // TODO consider requiring redis version >= 2.6.12 that supports this in one command
+ $expire = (int) ($expire * 1000);
+ $this->_connection->executeCommand('MULTI');
+ $this->_connection->executeCommand('SETNX', array($key, $value));
+ $this->_connection->executeCommand('PEXPIRE', array($key, $expire));
+ $response = $this->_connection->executeCommand('EXEC');
+ return (bool) $response[0];
+ }
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return (bool) $this->_connection->executeCommand('DEL', array($key));
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return $this->_connection->executeCommand('FLUSHDB');
+ }
+}
diff --git a/framework/yii/caching/WinCache.php b/framework/yii/caching/WinCache.php
new file mode 100644
index 0000000..3679884
--- /dev/null
+++ b/framework/yii/caching/WinCache.php
@@ -0,0 +1,108 @@
+
+ * @since 2.0
+ */
+class WinCache extends Cache
+{
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
+ return wincache_ucache_exists($key);
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return wincache_ucache_get($key);
+ }
+
+ /**
+ * Retrieves multiple values from cache with the specified keys.
+ * @param array $keys a list of keys identifying the cached values
+ * @return array a list of cached values indexed by the keys
+ */
+ protected function getValues($keys)
+ {
+ return wincache_ucache_get($keys);
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return wincache_ucache_set($key, $value, $expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return wincache_ucache_add($key, $value, $expire);
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return wincache_ucache_delete($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return wincache_ucache_clear();
+ }
+}
diff --git a/framework/yii/caching/XCache.php b/framework/yii/caching/XCache.php
new file mode 100644
index 0000000..4221a39
--- /dev/null
+++ b/framework/yii/caching/XCache.php
@@ -0,0 +1,104 @@
+
+ * @since 2.0
+ */
+class XCache extends Cache
+{
+ /**
+ * Checks whether a specified key exists in the cache.
+ * This can be faster than getting the value from the cache if the data is big.
+ * Note that this method does not check whether the dependency associated
+ * with the cached data, if there is any, has changed. So a call to [[get]]
+ * may return false while exists returns true.
+ * @param mixed $key a key identifying the cached value. This can be a simple string or
+ * a complex data structure consisting of factors representing the key.
+ * @return boolean true if a value exists in cache, false if the value is not in the cache or expired.
+ */
+ public function exists($key)
+ {
+ $key = $this->buildKey($key);
+ return xcache_isset($key);
+ }
+
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ return xcache_isset($key) ? xcache_get($key) : false;
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return xcache_set($key, $value, $expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return !xcache_isset($key) ? $this->setValue($key, $value, $expire) : false;
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return xcache_unset($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ for ($i = 0, $max = xcache_count(XC_TYPE_VAR); $i < $max; $i++) {
+ if (xcache_clear_cache(XC_TYPE_VAR, $i) === false) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
diff --git a/framework/yii/caching/ZendDataCache.php b/framework/yii/caching/ZendDataCache.php
new file mode 100644
index 0000000..9ff2fd0
--- /dev/null
+++ b/framework/yii/caching/ZendDataCache.php
@@ -0,0 +1,83 @@
+
+ * @since 2.0
+ */
+class ZendDataCache extends Cache
+{
+ /**
+ * Retrieves a value from cache with a specified key.
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key a unique key identifying the cached value
+ * @return string|boolean the value stored in cache, false if the value is not in the cache or expired.
+ */
+ protected function getValue($key)
+ {
+ $result = zend_shm_cache_fetch($key);
+ return $result === null ? false : $result;
+ }
+
+ /**
+ * Stores a value identified by a key in cache.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function setValue($key, $value, $expire)
+ {
+ return zend_shm_cache_store($key, $value, $expire);
+ }
+
+ /**
+ * Stores a value identified by a key into cache if the cache does not contain this key.
+ * This is the implementation of the method declared in the parent class.
+ *
+ * @param string $key the key identifying the value to be cached
+ * @param string $value the value to be cached
+ * @param integer $expire the number of seconds in which the cached value will expire. 0 means never expire.
+ * @return boolean true if the value is successfully stored into cache, false otherwise
+ */
+ protected function addValue($key, $value, $expire)
+ {
+ return zend_shm_cache_fetch($key) === null ? $this->setValue($key, $value, $expire) : false;
+ }
+
+ /**
+ * Deletes a value with the specified key from cache
+ * This is the implementation of the method declared in the parent class.
+ * @param string $key the key of the value to be deleted
+ * @return boolean if no error happens during deletion
+ */
+ protected function deleteValue($key)
+ {
+ return zend_shm_cache_delete($key);
+ }
+
+ /**
+ * Deletes all values from cache.
+ * This is the implementation of the method declared in the parent class.
+ * @return boolean whether the flush operation was successful.
+ */
+ protected function flushValues()
+ {
+ return zend_shm_cache_clear();
+ }
+}
diff --git a/framework/yii/captcha/Captcha.php b/framework/yii/captcha/Captcha.php
new file mode 100644
index 0000000..fc1d881
--- /dev/null
+++ b/framework/yii/captcha/Captcha.php
@@ -0,0 +1,141 @@
+
+ * @since 2.0
+ */
+class Captcha extends InputWidget
+{
+ /**
+ * @var string the route of the action that generates the CAPTCHA images.
+ * The action represented by this route must be an action of [[CaptchaAction]].
+ */
+ public $captchaAction = 'site/captcha';
+ /**
+ * @var array HTML attributes to be applied to the text input field.
+ */
+ public $options = array();
+ /**
+ * @var array HTML attributes to be applied to the CAPTCHA image tag.
+ */
+ public $imageOptions = array();
+ /**
+ * @var string the template for arranging the CAPTCHA image tag and the text input tag.
+ * In this template, the token `{image}` will be replaced with the actual image tag,
+ * while `{input}` will be replaced with the text input tag.
+ */
+ public $template = '{image} {input}';
+
+ /**
+ * Initializes the widget.
+ */
+ public function init()
+ {
+ parent::init();
+
+ $this->checkRequirements();
+
+ if (!isset($this->options['id'])) {
+ $this->options['id'] = $this->hasModel() ? Html::getInputId($this->model, $this->attribute) : $this->getId();
+ }
+ if (!isset($this->imageOptions['id'])) {
+ $this->imageOptions['id'] = $this->options['id'] . '-image';
+ }
+ }
+
+ /**
+ * Renders the widget.
+ */
+ public function run()
+ {
+ $this->registerClientScript();
+ if ($this->hasModel()) {
+ $input = Html::activeTextInput($this->model, $this->attribute, $this->options);
+ } else {
+ $input = Html::textInput($this->name, $this->value, $this->options);
+ }
+ $url = Yii::$app->getUrlManager()->createUrl($this->captchaAction, array('v' => uniqid()));
+ $image = Html::img($url, $this->imageOptions);
+ echo strtr($this->template, array(
+ '{input}' => $input,
+ '{image}' => $image,
+ ));
+ }
+
+ /**
+ * Registers the needed JavaScript.
+ */
+ public function registerClientScript()
+ {
+ $options = $this->getClientOptions();
+ $options = empty($options) ? '' : Json::encode($options);
+ $id = $this->imageOptions['id'];
+ $view = $this->getView();
+ CaptchaAsset::register($view);
+ $view->registerJs("jQuery('#$id').yiiCaptcha($options);");
+ }
+
+ /**
+ * Returns the options for the captcha JS widget.
+ * @return array the options
+ */
+ protected function getClientOptions()
+ {
+ $options = array(
+ 'refreshUrl' => Html::url(array($this->captchaAction, CaptchaAction::REFRESH_GET_VAR => 1)),
+ 'hashKey' => "yiiCaptcha/{$this->captchaAction}",
+ );
+ return $options;
+ }
+
+ /**
+ * Checks if there is graphic extension available to generate CAPTCHA images.
+ * This method will check the existence of ImageMagick and GD extensions.
+ * @return string the name of the graphic extension, either "imagick" or "gd".
+ * @throws InvalidConfigException if neither ImageMagick nor GD is installed.
+ */
+ public static function checkRequirements()
+ {
+ if (extension_loaded('imagick')) {
+ $imagick = new \Imagick();
+ $imagickFormats = $imagick->queryFormats('PNG');
+ if (in_array('PNG', $imagickFormats)) {
+ return 'imagick';
+ }
+ }
+ if (extension_loaded('gd')) {
+ $gdInfo = gd_info();
+ if (!empty($gdInfo['FreeType Support'])) {
+ return 'gd';
+ }
+ }
+ throw new InvalidConfigException('GD with FreeType or ImageMagick PHP extensions are required.');
+ }
+}
diff --git a/framework/yii/captcha/CaptchaAction.php b/framework/yii/captcha/CaptchaAction.php
new file mode 100644
index 0000000..e1761a1
--- /dev/null
+++ b/framework/yii/captcha/CaptchaAction.php
@@ -0,0 +1,341 @@
+
+ * @since 2.0
+ */
+class CaptchaAction extends Action
+{
+ /**
+ * The name of the GET parameter indicating whether the CAPTCHA image should be regenerated.
+ */
+ const REFRESH_GET_VAR = 'refresh';
+ /**
+ * @var integer how many times should the same CAPTCHA be displayed. Defaults to 3.
+ * A value less than or equal to 0 means the test is unlimited (available since version 1.1.2).
+ */
+ public $testLimit = 3;
+ /**
+ * @var integer the width of the generated CAPTCHA image. Defaults to 120.
+ */
+ public $width = 120;
+ /**
+ * @var integer the height of the generated CAPTCHA image. Defaults to 50.
+ */
+ public $height = 50;
+ /**
+ * @var integer padding around the text. Defaults to 2.
+ */
+ public $padding = 2;
+ /**
+ * @var integer the background color. For example, 0x55FF00.
+ * Defaults to 0xFFFFFF, meaning white color.
+ */
+ public $backColor = 0xFFFFFF;
+ /**
+ * @var integer the font color. For example, 0x55FF00. Defaults to 0x2040A0 (blue color).
+ */
+ public $foreColor = 0x2040A0;
+ /**
+ * @var boolean whether to use transparent background. Defaults to false.
+ */
+ public $transparent = false;
+ /**
+ * @var integer the minimum length for randomly generated word. Defaults to 6.
+ */
+ public $minLength = 6;
+ /**
+ * @var integer the maximum length for randomly generated word. Defaults to 7.
+ */
+ public $maxLength = 7;
+ /**
+ * @var integer the offset between characters. Defaults to -2. You can adjust this property
+ * in order to decrease or increase the readability of the captcha.
+ **/
+ public $offset = -2;
+ /**
+ * @var string the TrueType font file. This can be either a file path or path alias.
+ */
+ public $fontFile = '@yii/captcha/SpicyRice.ttf';
+ /**
+ * @var string the fixed verification code. When this property is set,
+ * [[getVerifyCode()]] will always return the value of this property.
+ * This is mainly used in automated tests where we want to be able to reproduce
+ * the same verification code each time we run the tests.
+ * If not set, it means the verification code will be randomly generated.
+ */
+ public $fixedVerifyCode;
+
+
+ /**
+ * Initializes the action.
+ * @throws InvalidConfigException if the font file does not exist.
+ */
+ public function init()
+ {
+ $this->fontFile = Yii::getAlias($this->fontFile);
+ if (!is_file($this->fontFile)) {
+ throw new InvalidConfigException("The font file does not exist: {$this->fontFile}");
+ }
+ }
+
+ /**
+ * Runs the action.
+ */
+ public function run()
+ {
+ if (isset($_GET[self::REFRESH_GET_VAR])) {
+ // AJAX request for regenerating code
+ $code = $this->getVerifyCode(true);
+ /** @var \yii\web\Controller $controller */
+ $controller = $this->controller;
+ return json_encode(array(
+ 'hash1' => $this->generateValidationHash($code),
+ 'hash2' => $this->generateValidationHash(strtolower($code)),
+ // we add a random 'v' parameter so that FireFox can refresh the image
+ // when src attribute of image tag is changed
+ 'url' => $controller->createUrl($this->id, array('v' => uniqid())),
+ ));
+ } else {
+ $this->setHttpHeaders();
+ return $this->renderImage($this->getVerifyCode());
+ }
+ }
+
+ /**
+ * Generates a hash code that can be used for client side validation.
+ * @param string $code the CAPTCHA code
+ * @return string a hash code generated from the CAPTCHA code
+ */
+ public function generateValidationHash($code)
+ {
+ for ($h = 0, $i = strlen($code) - 1; $i >= 0; --$i) {
+ $h += ord($code[$i]);
+ }
+ return $h;
+ }
+
+ /**
+ * Gets the verification code.
+ * @param boolean $regenerate whether the verification code should be regenerated.
+ * @return string the verification code.
+ */
+ public function getVerifyCode($regenerate = false)
+ {
+ if ($this->fixedVerifyCode !== null) {
+ return $this->fixedVerifyCode;
+ }
+
+ $session = Yii::$app->getSession();
+ $session->open();
+ $name = $this->getSessionKey();
+ if ($session[$name] === null || $regenerate) {
+ $session[$name] = $this->generateVerifyCode();
+ $session[$name . 'count'] = 1;
+ }
+ return $session[$name];
+ }
+
+ /**
+ * Validates the input to see if it matches the generated code.
+ * @param string $input user input
+ * @param boolean $caseSensitive whether the comparison should be case-sensitive
+ * @return boolean whether the input is valid
+ */
+ public function validate($input, $caseSensitive)
+ {
+ $code = $this->getVerifyCode();
+ $valid = $caseSensitive ? ($input === $code) : strcasecmp($input, $code) === 0;
+ $session = Yii::$app->getSession();
+ $session->open();
+ $name = $this->getSessionKey() . 'count';
+ $session[$name] = $session[$name] + 1;
+ if ($valid || $session[$name] > $this->testLimit && $this->testLimit > 0) {
+ $this->getVerifyCode(true);
+ }
+ return $valid;
+ }
+
+ /**
+ * Generates a new verification code.
+ * @return string the generated verification code
+ */
+ protected function generateVerifyCode()
+ {
+ if ($this->minLength > $this->maxLength) {
+ $this->maxLength = $this->minLength;
+ }
+ if ($this->minLength < 3) {
+ $this->minLength = 3;
+ }
+ if ($this->maxLength > 20) {
+ $this->maxLength = 20;
+ }
+ $length = mt_rand($this->minLength, $this->maxLength);
+
+ $letters = 'bcdfghjklmnpqrstvwxyz';
+ $vowels = 'aeiou';
+ $code = '';
+ for ($i = 0; $i < $length; ++$i) {
+ if ($i % 2 && mt_rand(0, 10) > 2 || !($i % 2) && mt_rand(0, 10) > 9) {
+ $code .= $vowels[mt_rand(0, 4)];
+ } else {
+ $code .= $letters[mt_rand(0, 20)];
+ }
+ }
+
+ return $code;
+ }
+
+ /**
+ * Returns the session variable name used to store verification code.
+ * @return string the session variable name
+ */
+ protected function getSessionKey()
+ {
+ return '__captcha/' . $this->getUniqueId();
+ }
+
+ /**
+ * Renders the CAPTCHA image.
+ * @param string $code the verification code
+ * @return string image contents
+ */
+ protected function renderImage($code)
+ {
+ if (Captcha::checkRequirements() === 'gd') {
+ return $this->renderImageByGD($code);
+ } else {
+ return $this->renderImageByImagick($code);
+ }
+ }
+
+ /**
+ * Renders the CAPTCHA image based on the code using GD library.
+ * @param string $code the verification code
+ * @return string image contents
+ */
+ protected function renderImageByGD($code)
+ {
+ $image = imagecreatetruecolor($this->width, $this->height);
+
+ $backColor = imagecolorallocate($image,
+ (int)($this->backColor % 0x1000000 / 0x10000),
+ (int)($this->backColor % 0x10000 / 0x100),
+ $this->backColor % 0x100);
+ imagefilledrectangle($image, 0, 0, $this->width, $this->height, $backColor);
+ imagecolordeallocate($image, $backColor);
+
+ if ($this->transparent) {
+ imagecolortransparent($image, $backColor);
+ }
+
+ $foreColor = imagecolorallocate($image,
+ (int)($this->foreColor % 0x1000000 / 0x10000),
+ (int)($this->foreColor % 0x10000 / 0x100),
+ $this->foreColor % 0x100);
+
+ $length = strlen($code);
+ $box = imagettfbbox(30, 0, $this->fontFile, $code);
+ $w = $box[4] - $box[0] + $this->offset * ($length - 1);
+ $h = $box[1] - $box[5];
+ $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h);
+ $x = 10;
+ $y = round($this->height * 27 / 40);
+ for ($i = 0; $i < $length; ++$i) {
+ $fontSize = (int)(rand(26, 32) * $scale * 0.8);
+ $angle = rand(-10, 10);
+ $letter = $code[$i];
+ $box = imagettftext($image, $fontSize, $angle, $x, $y, $foreColor, $this->fontFile, $letter);
+ $x = $box[2] + $this->offset;
+ }
+
+ imagecolordeallocate($image, $foreColor);
+
+ ob_start();
+ imagepng($image);
+ imagedestroy($image);
+ return ob_get_clean();
+ }
+
+ /**
+ * Renders the CAPTCHA image based on the code using ImageMagick library.
+ * @param string $code the verification code
+ * @return \Imagick image instance. Can be used as string. In this case it will contain image contents.
+ */
+ protected function renderImageByImagick($code)
+ {
+ $backColor = $this->transparent ? new \ImagickPixel('transparent') : new \ImagickPixel('#' . dechex($this->backColor));
+ $foreColor = new \ImagickPixel('#' . dechex($this->foreColor));
+
+ $image = new \Imagick();
+ $image->newImage($this->width, $this->height, $backColor);
+
+ $draw = new \ImagickDraw();
+ $draw->setFont($this->fontFile);
+ $draw->setFontSize(30);
+ $fontMetrics = $image->queryFontMetrics($draw, $code);
+
+ $length = strlen($code);
+ $w = (int)($fontMetrics['textWidth']) - 8 + $this->offset * ($length - 1);
+ $h = (int)($fontMetrics['textHeight']) - 8;
+ $scale = min(($this->width - $this->padding * 2) / $w, ($this->height - $this->padding * 2) / $h);
+ $x = 10;
+ $y = round($this->height * 27 / 40);
+ for ($i = 0; $i < $length; ++$i) {
+ $draw = new \ImagickDraw();
+ $draw->setFont($this->fontFile);
+ $draw->setFontSize((int)(rand(26, 32) * $scale * 0.8));
+ $draw->setFillColor($foreColor);
+ $image->annotateImage($draw, $x, $y, rand(-10, 10), $code[$i]);
+ $fontMetrics = $image->queryFontMetrics($draw, $code[$i]);
+ $x += (int)($fontMetrics['textWidth']) + $this->offset;
+ }
+
+ $image->setImageFormat('png');
+ return $image;
+ }
+
+ /**
+ * Sets the HTTP headers needed by image response.
+ */
+ protected function setHttpHeaders()
+ {
+ Yii::$app->getResponse()->getHeaders()
+ ->set('Pragma', 'public')
+ ->set('Expires', '0')
+ ->set('Cache-Control', 'must-revalidate, post-check=0, pre-check=0')
+ ->set('Content-Transfer-Encoding', 'binary')
+ ->set('Content-type', 'image/png');
+ }
+}
diff --git a/framework/yii/captcha/CaptchaAsset.php b/framework/yii/captcha/CaptchaAsset.php
new file mode 100644
index 0000000..50c75b7
--- /dev/null
+++ b/framework/yii/captcha/CaptchaAsset.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class CaptchaAsset extends AssetBundle
+{
+ public $sourcePath = '@yii/assets';
+ public $js = array(
+ 'yii.captcha.js',
+ );
+ public $depends = array(
+ 'yii\web\YiiAsset',
+ );
+}
diff --git a/framework/yii/captcha/CaptchaValidator.php b/framework/yii/captcha/CaptchaValidator.php
new file mode 100644
index 0000000..97bfd1b
--- /dev/null
+++ b/framework/yii/captcha/CaptchaValidator.php
@@ -0,0 +1,126 @@
+
+ * @since 2.0
+ */
+class CaptchaValidator extends Validator
+{
+ /**
+ * @var boolean whether to skip this validator if the input is empty.
+ */
+ public $skipOnEmpty = false;
+ /**
+ * @var boolean whether the comparison is case sensitive. Defaults to false.
+ */
+ public $caseSensitive = false;
+ /**
+ * @var string the route of the controller action that renders the CAPTCHA image.
+ */
+ public $captchaAction = 'site/captcha';
+
+
+ /**
+ * Initializes the validator.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->message === null) {
+ $this->message = Yii::t('yii', 'The verification code is incorrect.');
+ }
+ }
+
+ /**
+ * Validates the attribute of the object.
+ * If there is any error, the error message is added to the object.
+ * @param \yii\base\Model $object the object being validated
+ * @param string $attribute the attribute being validated
+ */
+ public function validateAttribute($object, $attribute)
+ {
+ $value = $object->$attribute;
+ if (!$this->validateValue($value)) {
+ $this->addError($object, $attribute, $this->message);
+ }
+ }
+
+ /**
+ * Validates the given value.
+ * @param mixed $value the value to be validated.
+ * @return boolean whether the value is valid.
+ */
+ public function validateValue($value)
+ {
+ $captcha = $this->getCaptchaAction();
+ return !is_array($value) && $captcha->validate($value, $this->caseSensitive);
+ }
+
+ /**
+ * Returns the CAPTCHA action object.
+ * @throws InvalidConfigException
+ * @return \yii\captcha\CaptchaAction the action object
+ */
+ public function getCaptchaAction()
+ {
+ $ca = Yii::$app->createController($this->captchaAction);
+ if ($ca !== false) {
+ /** @var \yii\base\Controller $controller */
+ list($controller, $actionID) = $ca;
+ $action = $controller->createAction($actionID);
+ if ($action !== null) {
+ return $action;
+ }
+ }
+ throw new InvalidConfigException('Invalid CAPTCHA action ID: ' . $this->captchaAction);
+ }
+
+ /**
+ * Returns the JavaScript needed for performing client-side validation.
+ * @param \yii\base\Model $object the data object being validated
+ * @param string $attribute the name of the attribute to be validated.
+ * @param \yii\base\View $view the view object that is going to be used to render views or view files
+ * containing a model form with this validator applied.
+ * @return string the client-side validation script.
+ */
+ public function clientValidateAttribute($object, $attribute, $view)
+ {
+ $captcha = $this->getCaptchaAction();
+ $code = $captcha->getVerifyCode(false);
+ $hash = $captcha->generateValidationHash($this->caseSensitive ? $code : strtolower($code));
+ $options = array(
+ 'hash' => $hash,
+ 'hashKey' => 'yiiCaptcha/' . $this->captchaAction,
+ 'caseSensitive' => $this->caseSensitive,
+ 'message' => Html::encode(strtr($this->message, array(
+ '{attribute}' => $object->getAttributeLabel($attribute),
+ '{value}' => $object->$attribute,
+ ))),
+ );
+ if ($this->skipOnEmpty) {
+ $options['skipOnEmpty'] = 1;
+ }
+
+ ValidationAsset::register($view);
+ return 'yii.validation.captcha(value, messages, ' . json_encode($options) . ');';
+ }
+}
diff --git a/framework/yii/captcha/SpicyRice.md b/framework/yii/captcha/SpicyRice.md
new file mode 100644
index 0000000..7049bd1
--- /dev/null
+++ b/framework/yii/captcha/SpicyRice.md
@@ -0,0 +1,11 @@
+## Spicy Rice font
+
+* **Author:** Brian J. Bonislawsky, Astigmatic (AOETI, Astigmatic One Eye Typographic Institute)
+* **License:** SIL Open Font License (OFL), version 1.1, [notes and FAQ](http://scripts.sil.org/OFL)
+
+## Links
+
+* [Astigmatic](http://www.astigmatic.com/)
+* [Google WebFonts](http://www.google.com/webfonts/specimen/Spicy+Rice)
+* [fontsquirrel.com](http://www.fontsquirrel.com/fonts/spicy-rice)
+* [fontspace.com](http://www.fontspace.com/astigmatic-one-eye-typographic-institute/spicy-rice)
diff --git a/yii/web/SpicyRice.ttf b/framework/yii/captcha/SpicyRice.ttf
similarity index 100%
rename from yii/web/SpicyRice.ttf
rename to framework/yii/captcha/SpicyRice.ttf
diff --git a/framework/yii/classes.php b/framework/yii/classes.php
new file mode 100644
index 0000000..78cce95
--- /dev/null
+++ b/framework/yii/classes.php
@@ -0,0 +1,244 @@
+ YII_PATH . '/base/Action.php',
+ 'yii\base\ActionEvent' => YII_PATH . '/base/ActionEvent.php',
+ 'yii\base\ActionFilter' => YII_PATH . '/base/ActionFilter.php',
+ 'yii\base\Application' => YII_PATH . '/base/Application.php',
+ 'yii\base\Arrayable' => YII_PATH . '/base/Arrayable.php',
+ 'yii\base\Behavior' => YII_PATH . '/base/Behavior.php',
+ 'yii\base\Component' => YII_PATH . '/base/Component.php',
+ 'yii\base\Controller' => YII_PATH . '/base/Controller.php',
+ 'yii\base\ErrorException' => YII_PATH . '/base/ErrorException.php',
+ 'yii\base\ErrorHandler' => YII_PATH . '/base/ErrorHandler.php',
+ 'yii\base\Event' => YII_PATH . '/base/Event.php',
+ 'yii\base\Exception' => YII_PATH . '/base/Exception.php',
+ 'yii\base\Formatter' => YII_PATH . '/base/Formatter.php',
+ 'yii\base\InlineAction' => YII_PATH . '/base/InlineAction.php',
+ 'yii\base\InvalidCallException' => YII_PATH . '/base/InvalidCallException.php',
+ 'yii\base\InvalidConfigException' => YII_PATH . '/base/InvalidConfigException.php',
+ 'yii\base\InvalidParamException' => YII_PATH . '/base/InvalidParamException.php',
+ 'yii\base\InvalidRouteException' => YII_PATH . '/base/InvalidRouteException.php',
+ 'yii\base\Model' => YII_PATH . '/base/Model.php',
+ 'yii\base\ModelEvent' => YII_PATH . '/base/ModelEvent.php',
+ 'yii\base\Module' => YII_PATH . '/base/Module.php',
+ 'yii\base\NotSupportedException' => YII_PATH . '/base/NotSupportedException.php',
+ 'yii\base\Object' => YII_PATH . '/base/Object.php',
+ 'yii\base\Request' => YII_PATH . '/base/Request.php',
+ 'yii\base\Response' => YII_PATH . '/base/Response.php',
+ 'yii\base\Theme' => YII_PATH . '/base/Theme.php',
+ 'yii\base\UnknownClassException' => YII_PATH . '/base/UnknownClassException.php',
+ 'yii\base\UnknownMethodException' => YII_PATH . '/base/UnknownMethodException.php',
+ 'yii\base\UnknownPropertyException' => YII_PATH . '/base/UnknownPropertyException.php',
+ 'yii\base\UserException' => YII_PATH . '/base/UserException.php',
+ 'yii\base\View' => YII_PATH . '/base/View.php',
+ 'yii\base\ViewEvent' => YII_PATH . '/base/ViewEvent.php',
+ 'yii\base\ViewRenderer' => YII_PATH . '/base/ViewRenderer.php',
+ 'yii\base\Widget' => YII_PATH . '/base/Widget.php',
+ 'yii\behaviors\AutoTimestamp' => YII_PATH . '/behaviors/AutoTimestamp.php',
+ 'yii\bootstrap\Alert' => YII_PATH . '/bootstrap/Alert.php',
+ 'yii\bootstrap\BootstrapAsset' => YII_PATH . '/bootstrap/BootstrapAsset.php',
+ 'yii\bootstrap\BootstrapPluginAsset' => YII_PATH . '/bootstrap/BootstrapPluginAsset.php',
+ 'yii\bootstrap\BootstrapThemeAsset' => YII_PATH . '/bootstrap/BootstrapThemeAsset.php',
+ 'yii\bootstrap\Button' => YII_PATH . '/bootstrap/Button.php',
+ 'yii\bootstrap\ButtonDropdown' => YII_PATH . '/bootstrap/ButtonDropdown.php',
+ 'yii\bootstrap\ButtonGroup' => YII_PATH . '/bootstrap/ButtonGroup.php',
+ 'yii\bootstrap\Carousel' => YII_PATH . '/bootstrap/Carousel.php',
+ 'yii\bootstrap\Collapse' => YII_PATH . '/bootstrap/Collapse.php',
+ 'yii\bootstrap\Dropdown' => YII_PATH . '/bootstrap/Dropdown.php',
+ 'yii\bootstrap\Modal' => YII_PATH . '/bootstrap/Modal.php',
+ 'yii\bootstrap\Nav' => YII_PATH . '/bootstrap/Nav.php',
+ 'yii\bootstrap\NavBar' => YII_PATH . '/bootstrap/NavBar.php',
+ 'yii\bootstrap\Progress' => YII_PATH . '/bootstrap/Progress.php',
+ 'yii\bootstrap\Tabs' => YII_PATH . '/bootstrap/Tabs.php',
+ 'yii\bootstrap\Widget' => YII_PATH . '/bootstrap/Widget.php',
+ 'yii\caching\ApcCache' => YII_PATH . '/caching/ApcCache.php',
+ 'yii\caching\Cache' => YII_PATH . '/caching/Cache.php',
+ 'yii\caching\ChainedDependency' => YII_PATH . '/caching/ChainedDependency.php',
+ 'yii\caching\DbCache' => YII_PATH . '/caching/DbCache.php',
+ 'yii\caching\DbDependency' => YII_PATH . '/caching/DbDependency.php',
+ 'yii\caching\Dependency' => YII_PATH . '/caching/Dependency.php',
+ 'yii\caching\DummyCache' => YII_PATH . '/caching/DummyCache.php',
+ 'yii\caching\ExpressionDependency' => YII_PATH . '/caching/ExpressionDependency.php',
+ 'yii\caching\FileCache' => YII_PATH . '/caching/FileCache.php',
+ 'yii\caching\FileDependency' => YII_PATH . '/caching/FileDependency.php',
+ 'yii\caching\GroupDependency' => YII_PATH . '/caching/GroupDependency.php',
+ 'yii\caching\MemCache' => YII_PATH . '/caching/MemCache.php',
+ 'yii\caching\MemCacheServer' => YII_PATH . '/caching/MemCacheServer.php',
+ 'yii\caching\RedisCache' => YII_PATH . '/caching/RedisCache.php',
+ 'yii\caching\WinCache' => YII_PATH . '/caching/WinCache.php',
+ 'yii\caching\XCache' => YII_PATH . '/caching/XCache.php',
+ 'yii\caching\ZendDataCache' => YII_PATH . '/caching/ZendDataCache.php',
+ 'yii\captcha\Captcha' => YII_PATH . '/captcha/Captcha.php',
+ 'yii\captcha\CaptchaAction' => YII_PATH . '/captcha/CaptchaAction.php',
+ 'yii\captcha\CaptchaAsset' => YII_PATH . '/captcha/CaptchaAsset.php',
+ 'yii\captcha\CaptchaValidator' => YII_PATH . '/captcha/CaptchaValidator.php',
+ 'yii\data\ActiveDataProvider' => YII_PATH . '/data/ActiveDataProvider.php',
+ 'yii\data\ArrayDataProvider' => YII_PATH . '/data/ArrayDataProvider.php',
+ 'yii\data\BaseDataProvider' => YII_PATH . '/data/BaseDataProvider.php',
+ 'yii\data\DataProviderInterface' => YII_PATH . '/data/DataProviderInterface.php',
+ 'yii\data\Pagination' => YII_PATH . '/data/Pagination.php',
+ 'yii\data\Sort' => YII_PATH . '/data/Sort.php',
+ 'yii\db\ActiveQuery' => YII_PATH . '/db/ActiveQuery.php',
+ 'yii\db\ActiveRecord' => YII_PATH . '/db/ActiveRecord.php',
+ 'yii\db\ActiveRelation' => YII_PATH . '/db/ActiveRelation.php',
+ 'yii\db\ColumnSchema' => YII_PATH . '/db/ColumnSchema.php',
+ 'yii\db\Command' => YII_PATH . '/db/Command.php',
+ 'yii\db\Connection' => YII_PATH . '/db/Connection.php',
+ 'yii\db\DataReader' => YII_PATH . '/db/DataReader.php',
+ 'yii\db\Exception' => YII_PATH . '/db/Exception.php',
+ 'yii\db\Expression' => YII_PATH . '/db/Expression.php',
+ 'yii\db\Migration' => YII_PATH . '/db/Migration.php',
+ 'yii\db\Query' => YII_PATH . '/db/Query.php',
+ 'yii\db\QueryBuilder' => YII_PATH . '/db/QueryBuilder.php',
+ 'yii\db\Schema' => YII_PATH . '/db/Schema.php',
+ 'yii\db\StaleObjectException' => YII_PATH . '/db/StaleObjectException.php',
+ 'yii\db\TableSchema' => YII_PATH . '/db/TableSchema.php',
+ 'yii\db\Transaction' => YII_PATH . '/db/Transaction.php',
+ 'yii\db\cubrid\QueryBuilder' => YII_PATH . '/db/cubrid/QueryBuilder.php',
+ 'yii\db\cubrid\Schema' => YII_PATH . '/db/cubrid/Schema.php',
+ 'yii\db\mssql\PDO' => YII_PATH . '/db/mssql/PDO.php',
+ 'yii\db\mssql\QueryBuilder' => YII_PATH . '/db/mssql/QueryBuilder.php',
+ 'yii\db\mssql\Schema' => YII_PATH . '/db/mssql/Schema.php',
+ 'yii\db\mssql\SqlsrvPDO' => YII_PATH . '/db/mssql/SqlsrvPDO.php',
+ 'yii\db\mssql\TableSchema' => YII_PATH . '/db/mssql/TableSchema.php',
+ 'yii\db\mysql\QueryBuilder' => YII_PATH . '/db/mysql/QueryBuilder.php',
+ 'yii\db\mysql\Schema' => YII_PATH . '/db/mysql/Schema.php',
+ 'yii\db\pgsql\QueryBuilder' => YII_PATH . '/db/pgsql/QueryBuilder.php',
+ 'yii\db\pgsql\Schema' => YII_PATH . '/db/pgsql/Schema.php',
+ 'yii\db\sqlite\QueryBuilder' => YII_PATH . '/db/sqlite/QueryBuilder.php',
+ 'yii\db\sqlite\Schema' => YII_PATH . '/db/sqlite/Schema.php',
+ 'yii\grid\ActionColumn' => YII_PATH . '/grid/ActionColumn.php',
+ 'yii\grid\CheckboxColumn' => YII_PATH . '/grid/CheckboxColumn.php',
+ 'yii\grid\Column' => YII_PATH . '/grid/Column.php',
+ 'yii\grid\DataColumn' => YII_PATH . '/grid/DataColumn.php',
+ 'yii\grid\GridView' => YII_PATH . '/grid/GridView.php',
+ 'yii\grid\GridViewAsset' => YII_PATH . '/grid/GridViewAsset.php',
+ 'yii\grid\SerialColumn' => YII_PATH . '/grid/SerialColumn.php',
+ 'yii\helpers\ArrayHelper' => YII_PATH . '/helpers/ArrayHelper.php',
+ 'yii\helpers\BaseArrayHelper' => YII_PATH . '/helpers/BaseArrayHelper.php',
+ 'yii\helpers\BaseConsole' => YII_PATH . '/helpers/BaseConsole.php',
+ 'yii\helpers\BaseFileHelper' => YII_PATH . '/helpers/BaseFileHelper.php',
+ 'yii\helpers\BaseHtml' => YII_PATH . '/helpers/BaseHtml.php',
+ 'yii\helpers\BaseHtmlPurifier' => YII_PATH . '/helpers/BaseHtmlPurifier.php',
+ 'yii\helpers\BaseInflector' => YII_PATH . '/helpers/BaseInflector.php',
+ 'yii\helpers\BaseJson' => YII_PATH . '/helpers/BaseJson.php',
+ 'yii\helpers\BaseMarkdown' => YII_PATH . '/helpers/BaseMarkdown.php',
+ 'yii\helpers\BaseSecurity' => YII_PATH . '/helpers/BaseSecurity.php',
+ 'yii\helpers\BaseStringHelper' => YII_PATH . '/helpers/BaseStringHelper.php',
+ 'yii\helpers\BaseVarDumper' => YII_PATH . '/helpers/BaseVarDumper.php',
+ 'yii\helpers\Console' => YII_PATH . '/helpers/Console.php',
+ 'yii\helpers\FileHelper' => YII_PATH . '/helpers/FileHelper.php',
+ 'yii\helpers\Html' => YII_PATH . '/helpers/Html.php',
+ 'yii\helpers\HtmlPurifier' => YII_PATH . '/helpers/HtmlPurifier.php',
+ 'yii\helpers\Inflector' => YII_PATH . '/helpers/Inflector.php',
+ 'yii\helpers\Json' => YII_PATH . '/helpers/Json.php',
+ 'yii\helpers\Markdown' => YII_PATH . '/helpers/Markdown.php',
+ 'yii\helpers\Security' => YII_PATH . '/helpers/Security.php',
+ 'yii\helpers\StringHelper' => YII_PATH . '/helpers/StringHelper.php',
+ 'yii\helpers\VarDumper' => YII_PATH . '/helpers/VarDumper.php',
+ 'yii\i18n\DbMessageSource' => YII_PATH . '/i18n/DbMessageSource.php',
+ 'yii\i18n\Formatter' => YII_PATH . '/i18n/Formatter.php',
+ 'yii\i18n\GettextFile' => YII_PATH . '/i18n/GettextFile.php',
+ 'yii\i18n\GettextMessageSource' => YII_PATH . '/i18n/GettextMessageSource.php',
+ 'yii\i18n\GettextMoFile' => YII_PATH . '/i18n/GettextMoFile.php',
+ 'yii\i18n\GettextPoFile' => YII_PATH . '/i18n/GettextPoFile.php',
+ 'yii\i18n\I18N' => YII_PATH . '/i18n/I18N.php',
+ 'yii\i18n\MessageSource' => YII_PATH . '/i18n/MessageSource.php',
+ 'yii\i18n\MissingTranslationEvent' => YII_PATH . '/i18n/MissingTranslationEvent.php',
+ 'yii\i18n\PhpMessageSource' => YII_PATH . '/i18n/PhpMessageSource.php',
+ 'yii\log\DbTarget' => YII_PATH . '/log/DbTarget.php',
+ 'yii\log\EmailTarget' => YII_PATH . '/log/EmailTarget.php',
+ 'yii\log\FileTarget' => YII_PATH . '/log/FileTarget.php',
+ 'yii\log\Logger' => YII_PATH . '/log/Logger.php',
+ 'yii\log\Target' => YII_PATH . '/log/Target.php',
+ 'yii\rbac\Assignment' => YII_PATH . '/rbac/Assignment.php',
+ 'yii\rbac\DbManager' => YII_PATH . '/rbac/DbManager.php',
+ 'yii\rbac\Item' => YII_PATH . '/rbac/Item.php',
+ 'yii\rbac\Manager' => YII_PATH . '/rbac/Manager.php',
+ 'yii\rbac\PhpManager' => YII_PATH . '/rbac/PhpManager.php',
+ 'yii\redis\Connection' => YII_PATH . '/redis/Connection.php',
+ 'yii\redis\Transaction' => YII_PATH . '/redis/Transaction.php',
+ 'yii\requirements\YiiRequirementChecker' => YII_PATH . '/requirements/YiiRequirementChecker.php',
+ 'yii\validators\BooleanValidator' => YII_PATH . '/validators/BooleanValidator.php',
+ 'yii\validators\CompareValidator' => YII_PATH . '/validators/CompareValidator.php',
+ 'yii\validators\DateValidator' => YII_PATH . '/validators/DateValidator.php',
+ 'yii\validators\DefaultValueValidator' => YII_PATH . '/validators/DefaultValueValidator.php',
+ 'yii\validators\EmailValidator' => YII_PATH . '/validators/EmailValidator.php',
+ 'yii\validators\ExistValidator' => YII_PATH . '/validators/ExistValidator.php',
+ 'yii\validators\FileValidator' => YII_PATH . '/validators/FileValidator.php',
+ 'yii\validators\FilterValidator' => YII_PATH . '/validators/FilterValidator.php',
+ 'yii\validators\InlineValidator' => YII_PATH . '/validators/InlineValidator.php',
+ 'yii\validators\NumberValidator' => YII_PATH . '/validators/NumberValidator.php',
+ 'yii\validators\PunycodeAsset' => YII_PATH . '/validators/PunycodeAsset.php',
+ 'yii\validators\RangeValidator' => YII_PATH . '/validators/RangeValidator.php',
+ 'yii\validators\RegularExpressionValidator' => YII_PATH . '/validators/RegularExpressionValidator.php',
+ 'yii\validators\RequiredValidator' => YII_PATH . '/validators/RequiredValidator.php',
+ 'yii\validators\SafeValidator' => YII_PATH . '/validators/SafeValidator.php',
+ 'yii\validators\StringValidator' => YII_PATH . '/validators/StringValidator.php',
+ 'yii\validators\UniqueValidator' => YII_PATH . '/validators/UniqueValidator.php',
+ 'yii\validators\UrlValidator' => YII_PATH . '/validators/UrlValidator.php',
+ 'yii\validators\ValidationAsset' => YII_PATH . '/validators/ValidationAsset.php',
+ 'yii\validators\Validator' => YII_PATH . '/validators/Validator.php',
+ 'yii\web\AccessControl' => YII_PATH . '/web/AccessControl.php',
+ 'yii\web\AccessRule' => YII_PATH . '/web/AccessRule.php',
+ 'yii\web\Application' => YII_PATH . '/web/Application.php',
+ 'yii\web\AssetBundle' => YII_PATH . '/web/AssetBundle.php',
+ 'yii\web\AssetConverter' => YII_PATH . '/web/AssetConverter.php',
+ 'yii\web\AssetConverterInterface' => YII_PATH . '/web/AssetConverterInterface.php',
+ 'yii\web\AssetManager' => YII_PATH . '/web/AssetManager.php',
+ 'yii\web\CacheSession' => YII_PATH . '/web/CacheSession.php',
+ 'yii\web\Controller' => YII_PATH . '/web/Controller.php',
+ 'yii\web\Cookie' => YII_PATH . '/web/Cookie.php',
+ 'yii\web\CookieCollection' => YII_PATH . '/web/CookieCollection.php',
+ 'yii\web\DbSession' => YII_PATH . '/web/DbSession.php',
+ 'yii\web\ErrorAction' => YII_PATH . '/web/ErrorAction.php',
+ 'yii\web\HeaderCollection' => YII_PATH . '/web/HeaderCollection.php',
+ 'yii\web\HttpCache' => YII_PATH . '/web/HttpCache.php',
+ 'yii\web\HttpException' => YII_PATH . '/web/HttpException.php',
+ 'yii\web\IdentityInterface' => YII_PATH . '/web/IdentityInterface.php',
+ 'yii\web\JqueryAsset' => YII_PATH . '/web/JqueryAsset.php',
+ 'yii\web\JsExpression' => YII_PATH . '/web/JsExpression.php',
+ 'yii\web\PageCache' => YII_PATH . '/web/PageCache.php',
+ 'yii\web\Request' => YII_PATH . '/web/Request.php',
+ 'yii\web\Response' => YII_PATH . '/web/Response.php',
+ 'yii\web\ResponseEvent' => YII_PATH . '/web/ResponseEvent.php',
+ 'yii\web\ResponseFormatterInterface' => YII_PATH . '/web/ResponseFormatterInterface.php',
+ 'yii\web\Session' => YII_PATH . '/web/Session.php',
+ 'yii\web\SessionIterator' => YII_PATH . '/web/SessionIterator.php',
+ 'yii\web\UploadedFile' => YII_PATH . '/web/UploadedFile.php',
+ 'yii\web\UrlManager' => YII_PATH . '/web/UrlManager.php',
+ 'yii\web\UrlRule' => YII_PATH . '/web/UrlRule.php',
+ 'yii\web\User' => YII_PATH . '/web/User.php',
+ 'yii\web\UserEvent' => YII_PATH . '/web/UserEvent.php',
+ 'yii\web\VerbFilter' => YII_PATH . '/web/VerbFilter.php',
+ 'yii\web\XmlResponseFormatter' => YII_PATH . '/web/XmlResponseFormatter.php',
+ 'yii\web\YiiAsset' => YII_PATH . '/web/YiiAsset.php',
+ 'yii\widgets\ActiveField' => YII_PATH . '/widgets/ActiveField.php',
+ 'yii\widgets\ActiveForm' => YII_PATH . '/widgets/ActiveForm.php',
+ 'yii\widgets\ActiveFormAsset' => YII_PATH . '/widgets/ActiveFormAsset.php',
+ 'yii\widgets\BaseListView' => YII_PATH . '/widgets/BaseListView.php',
+ 'yii\widgets\Block' => YII_PATH . '/widgets/Block.php',
+ 'yii\widgets\Breadcrumbs' => YII_PATH . '/widgets/Breadcrumbs.php',
+ 'yii\widgets\ContentDecorator' => YII_PATH . '/widgets/ContentDecorator.php',
+ 'yii\widgets\DetailView' => YII_PATH . '/widgets/DetailView.php',
+ 'yii\widgets\FragmentCache' => YII_PATH . '/widgets/FragmentCache.php',
+ 'yii\widgets\InputWidget' => YII_PATH . '/widgets/InputWidget.php',
+ 'yii\widgets\LinkPager' => YII_PATH . '/widgets/LinkPager.php',
+ 'yii\widgets\LinkSorter' => YII_PATH . '/widgets/LinkSorter.php',
+ 'yii\widgets\ListView' => YII_PATH . '/widgets/ListView.php',
+ 'yii\widgets\MaskedInput' => YII_PATH . '/widgets/MaskedInput.php',
+ 'yii\widgets\MaskedInputAsset' => YII_PATH . '/widgets/MaskedInputAsset.php',
+ 'yii\widgets\Menu' => YII_PATH . '/widgets/Menu.php',
+ 'yii\widgets\Spaceless' => YII_PATH . '/widgets/Spaceless.php',
+);
diff --git a/framework/yii/console/Application.php b/framework/yii/console/Application.php
new file mode 100644
index 0000000..9ca7393
--- /dev/null
+++ b/framework/yii/console/Application.php
@@ -0,0 +1,168 @@
+ [--param1=value1 --param2 ...]
+ * ~~~
+ *
+ * where `` refers to a controller route in the form of `ModuleID/ControllerID/ActionID`
+ * (e.g. `sitemap/create`), and `param1`, `param2` refers to a set of named parameters that
+ * will be used to initialize the controller action (e.g. `--since=0` specifies a `since` parameter
+ * whose value is 0 and a corresponding `$since` parameter is passed to the action method).
+ *
+ * A `help` command is provided by default, which lists available commands and shows their usage.
+ * To use this command, simply type:
+ *
+ * ~~~
+ * yii help
+ * ~~~
+ *
+ * @property Response $response The response component. This property is read-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Application extends \yii\base\Application
+{
+ /**
+ * @var string the default route of this application. Defaults to 'help',
+ * meaning the `help` command.
+ */
+ public $defaultRoute = 'help';
+ /**
+ * @var boolean whether to enable the commands provided by the core framework.
+ * Defaults to true.
+ */
+ public $enableCoreCommands = true;
+ /**
+ * @var Controller the currently active controller instance
+ */
+ public $controller;
+
+ /**
+ * Initialize the application.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->enableCoreCommands) {
+ foreach ($this->coreCommands() as $id => $command) {
+ if (!isset($this->controllerMap[$id])) {
+ $this->controllerMap[$id] = $command;
+ }
+ }
+ }
+ // ensure we have the 'help' command so that we can list the available commands
+ if (!isset($this->controllerMap['help'])) {
+ $this->controllerMap['help'] = 'yii\console\controllers\HelpController';
+ }
+ }
+
+ /**
+ * Handles the specified request.
+ * @param Request $request the request to be handled
+ * @return Response the resulting response
+ */
+ public function handleRequest($request)
+ {
+ list ($route, $params) = $request->resolve();
+ $this->requestedRoute = $route;
+ $result = $this->runAction($route, $params);
+ if ($result instanceof Response) {
+ return $result;
+ } else {
+ $response = $this->getResponse();
+ $response->exitStatus = (int)$result;
+ return $response;
+ }
+ }
+
+ /**
+ * Returns the response component.
+ * @return Response the response component
+ */
+ public function getResponse()
+ {
+ return $this->getComponent('response');
+ }
+
+ /**
+ * Runs a controller action specified by a route.
+ * This method parses the specified route and creates the corresponding child module(s), controller and action
+ * instances. It then calls [[Controller::runAction()]] to run the action with the given parameters.
+ * If the route is empty, the method will use [[defaultRoute]].
+ * @param string $route the route that specifies the action.
+ * @param array $params the parameters to be passed to the action
+ * @return integer the status code returned by the action execution. 0 means normal, and other values mean abnormal.
+ * @throws Exception if the route is invalid
+ */
+ public function runAction($route, $params = array())
+ {
+ try {
+ return parent::runAction($route, $params);
+ } catch (InvalidRouteException $e) {
+ throw new Exception(Yii::t('yii', 'Unknown command "{command}".', array('command' => $route)), 0, $e);
+ }
+ }
+
+ /**
+ * Returns the configuration of the built-in commands.
+ * @return array the configuration of the built-in commands.
+ */
+ public function coreCommands()
+ {
+ return array(
+ 'message' => 'yii\console\controllers\MessageController',
+ 'help' => 'yii\console\controllers\HelpController',
+ 'migrate' => 'yii\console\controllers\MigrateController',
+ 'cache' => 'yii\console\controllers\CacheController',
+ 'asset' => 'yii\console\controllers\AssetController',
+ );
+ }
+
+ /**
+ * Registers the core application components.
+ * @see setComponents
+ */
+ public function registerCoreComponents()
+ {
+ parent::registerCoreComponents();
+ $this->setComponents(array(
+ 'request' => array(
+ 'class' => 'yii\console\Request',
+ ),
+ 'response' => array(
+ 'class' => 'yii\console\Response',
+ ),
+ ));
+ }
+}
diff --git a/framework/yii/console/Controller.php b/framework/yii/console/Controller.php
new file mode 100644
index 0000000..73b74f8
--- /dev/null
+++ b/framework/yii/console/Controller.php
@@ -0,0 +1,278 @@
+ [--param1=value1 --param2 ...]
+ * ~~~
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Controller extends \yii\base\Controller
+{
+ /**
+ * @var boolean whether to run the command interactively.
+ */
+ public $interactive = true;
+
+ /**
+ * @var boolean whether to enable ANSI color in the output.
+ * If not set, ANSI color will only be enabled for terminals that support it.
+ */
+ public $color;
+
+ /**
+ * Returns a value indicating whether ANSI color is enabled.
+ *
+ * ANSI color is enabled only if [[color]] is set true or is not set
+ * and the terminal supports ANSI color.
+ *
+ * @param resource $stream the stream to check.
+ * @return boolean Whether to enable ANSI style in output.
+ */
+ public function isColorEnabled($stream = STDOUT)
+ {
+ return $this->color === null ? Console::streamSupportsAnsiColors($stream) : $this->color;
+ }
+
+ /**
+ * Runs an action with the specified action ID and parameters.
+ * If the action ID is empty, the method will use [[defaultAction]].
+ * @param string $id the ID of the action to be executed.
+ * @param array $params the parameters (name-value pairs) to be passed to the action.
+ * @return integer the status of the action execution. 0 means normal, other values mean abnormal.
+ * @throws InvalidRouteException if the requested action ID cannot be resolved into an action successfully.
+ * @see createAction
+ */
+ public function runAction($id, $params = array())
+ {
+ if (!empty($params)) {
+ $options = $this->globalOptions();
+ foreach ($params as $name => $value) {
+ if (in_array($name, $options, true)) {
+ $this->$name = $value;
+ unset($params[$name]);
+ }
+ }
+ }
+ return parent::runAction($id, $params);
+ }
+
+ /**
+ * Binds the parameters to the action.
+ * This method is invoked by [[Action]] when it begins to run with the given parameters.
+ * This method will first bind the parameters with the [[globalOptions()|global options]]
+ * available to the action. It then validates the given arguments.
+ * @param Action $action the action to be bound with parameters
+ * @param array $params the parameters to be bound to the action
+ * @return array the valid parameters that the action can run with.
+ * @throws Exception if there are unknown options or missing arguments
+ */
+ public function bindActionParams($action, $params)
+ {
+ $args = array();
+ if (!empty($params)) {
+ $options = $this->globalOptions();
+ foreach ($params as $name => $value) {
+ if (in_array($name, $options, true)) {
+ $this->$name = $value;
+ } elseif (is_int($name)) {
+ $args[] = $value;
+ } else {
+ throw new Exception(Yii::t('yii', 'Unknown option: --{name}', array(
+ 'name' => $name,
+ )));
+ }
+ }
+ }
+
+ if ($action instanceof InlineAction) {
+ $method = new \ReflectionMethod($this, $action->actionMethod);
+ } else {
+ $method = new \ReflectionMethod($action, 'run');
+ }
+
+ $missing = array();
+ foreach ($method->getParameters() as $i => $param) {
+ $name = $param->getName();
+ if (!isset($args[$i])) {
+ if ($param->isDefaultValueAvailable()) {
+ $args[$i] = $param->getDefaultValue();
+ } else {
+ $missing[] = $name;
+ }
+ }
+ }
+
+ if (!empty($missing)) {
+ throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', array(
+ 'params' => implode(', ', $missing),
+ )));
+ }
+
+ return $args;
+ }
+
+ /**
+ * Formats a string with ANSI codes
+ *
+ * You may pass additional parameters using the constants defined in [[yii\helpers\Console]].
+ *
+ * Example:
+ *
+ * ~~~
+ * echo $this->ansiFormat('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
+ * ~~~
+ *
+ * @param string $string the string to be formatted
+ * @return string
+ */
+ public function ansiFormat($string)
+ {
+ if ($this->isColorEnabled()) {
+ $args = func_get_args();
+ array_shift($args);
+ $string = Console::ansiFormat($string, $args);
+ }
+ return $string;
+ }
+
+ /**
+ * Prints a string to STDOUT
+ *
+ * You may optionally format the string with ANSI codes by
+ * passing additional parameters using the constants defined in [[yii\helpers\Console]].
+ *
+ * Example:
+ *
+ * ~~~
+ * $this->stdout('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
+ * ~~~
+ *
+ * @param string $string the string to print
+ * @return int|boolean Number of bytes printed or false on error
+ */
+ public function stdout($string)
+ {
+ if ($this->isColorEnabled()) {
+ $args = func_get_args();
+ array_shift($args);
+ $string = Console::ansiFormat($string, $args);
+ }
+ return Console::stdout($string);
+ }
+
+ /**
+ * Prints a string to STDERR
+ *
+ * You may optionally format the string with ANSI codes by
+ * passing additional parameters using the constants defined in [[yii\helpers\Console]].
+ *
+ * Example:
+ *
+ * ~~~
+ * $this->stderr('This will be red and underlined.', Console::FG_RED, Console::UNDERLINE);
+ * ~~~
+ *
+ * @param string $string the string to print
+ * @return int|boolean Number of bytes printed or false on error
+ */
+ public function stderr($string)
+ {
+ if ($this->isColorEnabled(STDERR)) {
+ $args = func_get_args();
+ array_shift($args);
+ $string = Console::ansiFormat($string, $args);
+ }
+ return fwrite(STDERR, $string);
+ }
+
+ /**
+ * Prompts the user for input and validates it
+ *
+ * @param string $text prompt string
+ * @param array $options the options to validate the input:
+ *
+ * - required: whether it is required or not
+ * - default: default value if no input is inserted by the user
+ * - pattern: regular expression pattern to validate user input
+ * - validator: a callable function to validate input. The function must accept two parameters:
+ * - $input: the user input to validate
+ * - $error: the error value passed by reference if validation failed.
+ * @return string the user input
+ */
+ public function prompt($text, $options = array())
+ {
+ if ($this->interactive) {
+ return Console::prompt($text, $options);
+ } else {
+ return isset($options['default']) ? $options['default'] : '';
+ }
+ }
+
+ /**
+ * Asks user to confirm by typing y or n.
+ *
+ * @param string $message to echo out before waiting for user input
+ * @param boolean $default this value is returned if no selection is made.
+ * @return boolean whether user confirmed.
+ * Will return true if [[interactive]] is false.
+ */
+ public function confirm($message, $default = false)
+ {
+ if ($this->interactive) {
+ return Console::confirm($message, $default);
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Gives the user an option to choose from. Giving '?' as an input will show
+ * a list of options to choose from and their explanations.
+ *
+ * @param string $prompt the prompt message
+ * @param array $options Key-value array of options to choose from
+ *
+ * @return string An option character the user chose
+ */
+ public function select($prompt, $options = array())
+ {
+ return Console::select($prompt, $options);
+ }
+
+ /**
+ * Returns the names of the global options for this command.
+ * A global option requires the existence of a public member variable whose
+ * name is the option name.
+ * Child classes may override this method to specify possible global options.
+ *
+ * Note that the values setting via global options are not available
+ * until [[beforeAction()]] is being called.
+ *
+ * @return array the names of the global options for this command.
+ */
+ public function globalOptions()
+ {
+ return array('color', 'interactive');
+ }
+}
diff --git a/framework/yii/console/Exception.php b/framework/yii/console/Exception.php
new file mode 100644
index 0000000..f272bde
--- /dev/null
+++ b/framework/yii/console/Exception.php
@@ -0,0 +1,27 @@
+
+ * @since 2.0
+ */
+class Exception extends UserException
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Error');
+ }
+}
diff --git a/framework/yii/console/Request.php b/framework/yii/console/Request.php
new file mode 100644
index 0000000..dca0240
--- /dev/null
+++ b/framework/yii/console/Request.php
@@ -0,0 +1,73 @@
+
+ * @since 2.0
+ */
+class Request extends \yii\base\Request
+{
+ private $_params;
+
+ /**
+ * Returns the command line arguments.
+ * @return array the command line arguments. It does not include the entry script name.
+ */
+ public function getParams()
+ {
+ if (!isset($this->_params)) {
+ if (isset($_SERVER['argv'])) {
+ $this->_params = $_SERVER['argv'];
+ array_shift($this->_params);
+ } else {
+ $this->_params = array();
+ }
+ }
+ return $this->_params;
+ }
+
+ /**
+ * Sets the command line arguments.
+ * @param array $params the command line arguments
+ */
+ public function setParams($params)
+ {
+ $this->_params = $params;
+ }
+
+ /**
+ * Resolves the current request into a route and the associated parameters.
+ * @return array the first element is the route, and the second is the associated parameters.
+ */
+ public function resolve()
+ {
+ $rawParams = $this->getParams();
+ if (isset($rawParams[0])) {
+ $route = $rawParams[0];
+ array_shift($rawParams);
+ } else {
+ $route = '';
+ }
+
+ $params = array();
+ foreach ($rawParams as $param) {
+ if (preg_match('/^--(\w+)(=(.*))?$/', $param, $matches)) {
+ $name = $matches[1];
+ $params[$name] = isset($matches[3]) ? $matches[3] : true;
+ } else {
+ $params[] = $param;
+ }
+ }
+
+ return array($route, $params);
+ }
+}
diff --git a/framework/yii/console/Response.php b/framework/yii/console/Response.php
new file mode 100644
index 0000000..9c23e83
--- /dev/null
+++ b/framework/yii/console/Response.php
@@ -0,0 +1,16 @@
+
+ * @since 2.0
+ */
+class Response extends \yii\base\Response
+{
+}
diff --git a/framework/yii/console/controllers/AssetController.php b/framework/yii/console/controllers/AssetController.php
new file mode 100644
index 0000000..1e95dd8
--- /dev/null
+++ b/framework/yii/console/controllers/AssetController.php
@@ -0,0 +1,627 @@
+
+ * @since 2.0
+ */
+class AssetController extends Controller
+{
+ /**
+ * @var string controller default action ID.
+ */
+ public $defaultAction = 'compress';
+ /**
+ * @var array list of asset bundles to be compressed.
+ */
+ public $bundles = array();
+ /**
+ * @var array list of asset bundles, which represents output compressed files.
+ * You can specify the name of the output compressed file using 'css' and 'js' keys:
+ * For example:
+ *
+ * ~~~
+ * 'app\config\AllAsset' => array(
+ * 'js' => 'js/all-{ts}.js',
+ * 'css' => 'css/all-{ts}.css',
+ * 'depends' => array( ... ),
+ * )
+ * ~~~
+ *
+ * File names can contain placeholder "{ts}", which will be filled by current timestamp, while
+ * file creation.
+ */
+ public $targets = array();
+ /**
+ * @var string|callback JavaScript file compressor.
+ * If a string, it is treated as shell command template, which should contain
+ * placeholders {from} - source file name - and {to} - output file name.
+ * Otherwise, it is treated as PHP callback, which should perform the compression.
+ *
+ * Default value relies on usage of "Closure Compiler"
+ * @see https://developers.google.com/closure/compiler/
+ */
+ public $jsCompressor = 'java -jar compiler.jar --js {from} --js_output_file {to}';
+ /**
+ * @var string|callback CSS file compressor.
+ * If a string, it is treated as shell command template, which should contain
+ * placeholders {from} - source file name - and {to} - output file name.
+ * Otherwise, it is treated as PHP callback, which should perform the compression.
+ *
+ * Default value relies on usage of "YUI Compressor"
+ * @see https://github.com/yui/yuicompressor/
+ */
+ public $cssCompressor = 'java -jar yuicompressor.jar {from} -o {to}';
+
+ /**
+ * @var array|\yii\web\AssetManager [[yii\web\AssetManager]] instance or its array configuration, which will be used
+ * for assets processing.
+ */
+ private $_assetManager = array();
+
+ /**
+ * Returns the asset manager instance.
+ * @throws \yii\console\Exception on invalid configuration.
+ * @return \yii\web\AssetManager asset manager instance.
+ */
+ public function getAssetManager()
+ {
+ if (!is_object($this->_assetManager)) {
+ $options = $this->_assetManager;
+ if (!isset($options['class'])) {
+ $options['class'] = 'yii\\web\\AssetManager';
+ }
+ if (!isset($options['basePath'])) {
+ throw new Exception("Please specify 'basePath' for the 'assetManager' option.");
+ }
+ if (!isset($options['baseUrl'])) {
+ throw new Exception("Please specify 'baseUrl' for the 'assetManager' option.");
+ }
+ $this->_assetManager = Yii::createObject($options);
+ }
+ return $this->_assetManager;
+ }
+
+ /**
+ * Sets asset manager instance or configuration.
+ * @param \yii\web\AssetManager|array $assetManager asset manager instance or its array configuration.
+ * @throws \yii\console\Exception on invalid argument type.
+ */
+ public function setAssetManager($assetManager)
+ {
+ if (is_scalar($assetManager)) {
+ throw new Exception('"' . get_class($this) . '::assetManager" should be either object or array - "' . gettype($assetManager) . '" given.');
+ }
+ $this->_assetManager = $assetManager;
+ }
+
+ /**
+ * Combines and compresses the asset files according to the given configuration.
+ * During the process new asset bundle configuration file will be created.
+ * You should replace your original asset bundle configuration with this file in order to use compressed files.
+ * @param string $configFile configuration file name.
+ * @param string $bundleFile output asset bundles configuration file name.
+ */
+ public function actionCompress($configFile, $bundleFile)
+ {
+ $this->loadConfiguration($configFile);
+ $bundles = $this->loadBundles($this->bundles);
+ $targets = $this->loadTargets($this->targets, $bundles);
+ $this->publishBundles($bundles, $this->assetManager);
+ $timestamp = time();
+ foreach ($targets as $name => $target) {
+ echo "Creating output bundle '{$name}':\n";
+ if (!empty($target->js)) {
+ $this->buildTarget($target, 'js', $bundles, $timestamp);
+ }
+ if (!empty($target->css)) {
+ $this->buildTarget($target, 'css', $bundles, $timestamp);
+ }
+ echo "\n";
+ }
+
+ $targets = $this->adjustDependency($targets, $bundles);
+ $this->saveTargets($targets, $bundleFile);
+ }
+
+ /**
+ * Applies configuration from the given file to self instance.
+ * @param string $configFile configuration file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ protected function loadConfiguration($configFile)
+ {
+ echo "Loading configuration from '{$configFile}'...\n";
+ foreach (require($configFile) as $name => $value) {
+ if (property_exists($this, $name) || $this->canSetProperty($name)) {
+ $this->$name = $value;
+ } else {
+ throw new Exception("Unknown configuration option: $name");
+ }
+ }
+
+ $this->getAssetManager(); // check if asset manager configuration is correct
+ }
+
+ /**
+ * Creates full list of source asset bundles.
+ * @param string[] $bundles list of asset bundle names
+ * @return \yii\web\AssetBundle[] list of source asset bundles.
+ */
+ protected function loadBundles($bundles)
+ {
+ echo "Collecting source bundles information...\n";
+
+ $am = $this->getAssetManager();
+ $result = array();
+ foreach ($bundles as $name) {
+ $result[$name] = $am->getBundle($name);
+ }
+ foreach ($result as $bundle) {
+ $this->loadDependency($bundle, $result);
+ }
+
+ return $result;
+ }
+
+ /**
+ * Loads asset bundle dependencies recursively.
+ * @param \yii\web\AssetBundle $bundle bundle instance
+ * @param array $result already loaded bundles list.
+ * @throws Exception on failure.
+ */
+ protected function loadDependency($bundle, &$result)
+ {
+ $am = $this->getAssetManager();
+ foreach ($bundle->depends as $name) {
+ if (!isset($result[$name])) {
+ $dependencyBundle = $am->getBundle($name);
+ $result[$name] = false;
+ $this->loadDependency($dependencyBundle, $result);
+ $result[$name] = $dependencyBundle;
+ } elseif ($result[$name] === false) {
+ throw new Exception("A circular dependency is detected for bundle '$name'.");
+ }
+ }
+ }
+
+ /**
+ * Creates full list of output asset bundles.
+ * @param array $targets output asset bundles configuration.
+ * @param \yii\web\AssetBundle[] $bundles list of source asset bundles.
+ * @return \yii\web\AssetBundle[] list of output asset bundles.
+ * @throws Exception on failure.
+ */
+ protected function loadTargets($targets, $bundles)
+ {
+ // build the dependency order of bundles
+ $registered = array();
+ foreach ($bundles as $name => $bundle) {
+ $this->registerBundle($bundles, $name, $registered);
+ }
+ $bundleOrders = array_combine(array_keys($registered), range(0, count($bundles) - 1));
+
+ // fill up the target which has empty 'depends'.
+ $referenced = array();
+ foreach ($targets as $name => $target) {
+ if (empty($target['depends'])) {
+ if (!isset($all)) {
+ $all = $name;
+ } else {
+ throw new Exception("Only one target can have empty 'depends' option. Found two now: $all, $name");
+ }
+ } else {
+ foreach ($target['depends'] as $bundle) {
+ if (!isset($referenced[$bundle])) {
+ $referenced[$bundle] = $name;
+ } else {
+ throw new Exception("Target '{$referenced[$bundle]}' and '$name' cannot contain the bundle '$bundle' at the same time.");
+ }
+ }
+ }
+ }
+ if (isset($all)) {
+ $targets[$all]['depends'] = array_diff(array_keys($registered), array_keys($referenced));
+ }
+
+ // adjust the 'depends' order for each target according to the dependency order of bundles
+ // create an AssetBundle object for each target
+ foreach ($targets as $name => $target) {
+ if (!isset($target['basePath'])) {
+ throw new Exception("Please specify 'basePath' for the '$name' target.");
+ }
+ if (!isset($target['baseUrl'])) {
+ throw new Exception("Please specify 'baseUrl' for the '$name' target.");
+ }
+ usort($target['depends'], function ($a, $b) use ($bundleOrders) {
+ if ($bundleOrders[$a] == $bundleOrders[$b]) {
+ return 0;
+ } else {
+ return $bundleOrders[$a] > $bundleOrders[$b] ? 1 : -1;
+ }
+ });
+ $target['class'] = $name;
+ $targets[$name] = Yii::createObject($target);
+ }
+ return $targets;
+ }
+
+ /**
+ * Publishes given asset bundles.
+ * @param \yii\web\AssetBundle[] $bundles asset bundles to be published.
+ */
+ protected function publishBundles($bundles)
+ {
+ echo "\nPublishing bundles:\n";
+ $assetManager = $this->getAssetManager();
+ foreach ($bundles as $name => $bundle) {
+ $bundle->publish($assetManager);
+ echo " '".$name."' published.\n";
+ }
+ echo "\n";
+ }
+
+ /**
+ * Builds output asset bundle.
+ * @param \yii\web\AssetBundle $target output asset bundle
+ * @param string $type either 'js' or 'css'.
+ * @param \yii\web\AssetBundle[] $bundles source asset bundles.
+ * @param integer $timestamp current timestamp.
+ * @throws Exception on failure.
+ */
+ protected function buildTarget($target, $type, $bundles, $timestamp)
+ {
+ $outputFile = strtr($target->$type, array(
+ '{ts}' => $timestamp,
+ ));
+ $inputFiles = array();
+
+ foreach ($target->depends as $name) {
+ if (isset($bundles[$name])) {
+ foreach ($bundles[$name]->$type as $file) {
+ $inputFiles[] = $bundles[$name]->basePath . '/' . $file;
+ }
+ } else {
+ throw new Exception("Unknown bundle: '{$name}'");
+ }
+ }
+ if ($type === 'js') {
+ $this->compressJsFiles($inputFiles, $target->basePath . '/' . $outputFile);
+ } else {
+ $this->compressCssFiles($inputFiles, $target->basePath . '/' . $outputFile);
+ }
+ $target->$type = array($outputFile);
+ }
+
+ /**
+ * Adjust dependencies between asset bundles in the way source bundles begin to depend on output ones.
+ * @param \yii\web\AssetBundle[] $targets output asset bundles.
+ * @param \yii\web\AssetBundle[] $bundles source asset bundles.
+ * @return \yii\web\AssetBundle[] output asset bundles.
+ */
+ protected function adjustDependency($targets, $bundles)
+ {
+ echo "Creating new bundle configuration...\n";
+
+ $map = array();
+ foreach ($targets as $name => $target) {
+ foreach ($target->depends as $bundle) {
+ $map[$bundle] = $name;
+ }
+ }
+
+ foreach ($targets as $name => $target) {
+ $depends = array();
+ foreach ($target->depends as $bn) {
+ foreach ($bundles[$bn]->depends as $bundle) {
+ $depends[$map[$bundle]] = true;
+ }
+ }
+ unset($depends[$name]);
+ $target->depends = array_keys($depends);
+ }
+
+ // detect possible circular dependencies
+ foreach ($targets as $name => $target) {
+ $registered = array();
+ $this->registerBundle($targets, $name, $registered);
+ }
+
+ foreach ($map as $bundle => $target) {
+ $targets[$bundle] = Yii::createObject(array(
+ 'class' => 'yii\\web\\AssetBundle',
+ 'depends' => array($target),
+ ));
+ }
+ return $targets;
+ }
+
+ /**
+ * Registers asset bundles including their dependencies.
+ * @param \yii\web\AssetBundle[] $bundles asset bundles list.
+ * @param string $name bundle name.
+ * @param array $registered stores already registered names.
+ * @throws Exception if circular dependency is detected.
+ */
+ protected function registerBundle($bundles, $name, &$registered)
+ {
+ if (!isset($registered[$name])) {
+ $registered[$name] = false;
+ $bundle = $bundles[$name];
+ foreach ($bundle->depends as $depend) {
+ $this->registerBundle($bundles, $depend, $registered);
+ }
+ unset($registered[$name]);
+ $registered[$name] = true;
+ } elseif ($registered[$name] === false) {
+ throw new Exception("A circular dependency is detected for target '$name'.");
+ }
+ }
+
+ /**
+ * Saves new asset bundles configuration.
+ * @param \yii\web\AssetBundle[] $targets list of asset bundles to be saved.
+ * @param string $bundleFile output file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ protected function saveTargets($targets, $bundleFile)
+ {
+ $array = array();
+ foreach ($targets as $name => $target) {
+ foreach (array('js', 'css', 'depends', 'basePath', 'baseUrl') as $prop) {
+ if (!empty($target->$prop)) {
+ $array[$name][$prop] = $target->$prop;
+ }
+ }
+ }
+ $array = var_export($array, true);
+ $version = date('Y-m-d H:i:s', time());
+ $bundleFileContent = <<id}" command.
+ * DO NOT MODIFY THIS FILE DIRECTLY.
+ * @version {$version}
+ */
+return {$array};
+EOD;
+ if (!file_put_contents($bundleFile, $bundleFileContent)) {
+ throw new Exception("Unable to write output bundle configuration at '{$bundleFile}'.");
+ }
+ echo "Output bundle configuration created at '{$bundleFile}'.\n";
+ }
+
+ /**
+ * Compresses given JavaScript files and combines them into the single one.
+ * @param array $inputFiles list of source file names.
+ * @param string $outputFile output file name.
+ * @throws \yii\console\Exception on failure
+ */
+ protected function compressJsFiles($inputFiles, $outputFile)
+ {
+ if (empty($inputFiles)) {
+ return;
+ }
+ echo " Compressing JavaScript files...\n";
+ if (is_string($this->jsCompressor)) {
+ $tmpFile = $outputFile . '.tmp';
+ $this->combineJsFiles($inputFiles, $tmpFile);
+ echo shell_exec(strtr($this->jsCompressor, array(
+ '{from}' => escapeshellarg($tmpFile),
+ '{to}' => escapeshellarg($outputFile),
+ )));
+ @unlink($tmpFile);
+ } else {
+ call_user_func($this->jsCompressor, $this, $inputFiles, $outputFile);
+ }
+ if (!file_exists($outputFile)) {
+ throw new Exception("Unable to compress JavaScript files into '{$outputFile}'.");
+ }
+ echo " JavaScript files compressed into '{$outputFile}'.\n";
+ }
+
+ /**
+ * Compresses given CSS files and combines them into the single one.
+ * @param array $inputFiles list of source file names.
+ * @param string $outputFile output file name.
+ * @throws \yii\console\Exception on failure
+ */
+ protected function compressCssFiles($inputFiles, $outputFile)
+ {
+ if (empty($inputFiles)) {
+ return;
+ }
+ echo " Compressing CSS files...\n";
+ if (is_string($this->cssCompressor)) {
+ $tmpFile = $outputFile . '.tmp';
+ $this->combineCssFiles($inputFiles, $tmpFile);
+ echo shell_exec(strtr($this->cssCompressor, array(
+ '{from}' => escapeshellarg($tmpFile),
+ '{to}' => escapeshellarg($outputFile),
+ )));
+ @unlink($tmpFile);
+ } else {
+ call_user_func($this->cssCompressor, $this, $inputFiles, $outputFile);
+ }
+ if (!file_exists($outputFile)) {
+ throw new Exception("Unable to compress CSS files into '{$outputFile}'.");
+ }
+ echo " CSS files compressed into '{$outputFile}'.\n";
+ }
+
+ /**
+ * Combines JavaScript files into a single one.
+ * @param array $inputFiles source file names.
+ * @param string $outputFile output file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ public function combineJsFiles($inputFiles, $outputFile)
+ {
+ $content = '';
+ foreach ($inputFiles as $file) {
+ $content .= "/*** BEGIN FILE: $file ***/\n"
+ . file_get_contents($file)
+ . "/*** END FILE: $file ***/\n";
+ }
+ if (!file_put_contents($outputFile, $content)) {
+ throw new Exception("Unable to write output JavaScript file '{$outputFile}'.");
+ }
+ }
+
+ /**
+ * Combines CSS files into a single one.
+ * @param array $inputFiles source file names.
+ * @param string $outputFile output file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ public function combineCssFiles($inputFiles, $outputFile)
+ {
+ $content = '';
+ foreach ($inputFiles as $file) {
+ $content .= "/*** BEGIN FILE: $file ***/\n"
+ . $this->adjustCssUrl(file_get_contents($file), dirname($file), dirname($outputFile))
+ . "/*** END FILE: $file ***/\n";
+ }
+ if (!file_put_contents($outputFile, $content)) {
+ throw new Exception("Unable to write output CSS file '{$outputFile}'.");
+ }
+ }
+
+ /**
+ * Adjusts CSS content allowing URL references pointing to the original resources.
+ * @param string $cssContent source CSS content.
+ * @param string $inputFilePath input CSS file name.
+ * @param string $outputFilePath output CSS file name.
+ * @return string adjusted CSS content.
+ */
+ protected function adjustCssUrl($cssContent, $inputFilePath, $outputFilePath)
+ {
+ $sharedPathParts = array();
+ $inputFilePathParts = explode('/', $inputFilePath);
+ $inputFilePathPartsCount = count($inputFilePathParts);
+ $outputFilePathParts = explode('/', $outputFilePath);
+ $outputFilePathPartsCount = count($outputFilePathParts);
+ for ($i =0; $i < $inputFilePathPartsCount && $i < $outputFilePathPartsCount; $i++) {
+ if ($inputFilePathParts[$i] == $outputFilePathParts[$i]) {
+ $sharedPathParts[] = $inputFilePathParts[$i];
+ } else {
+ break;
+ }
+ }
+ $sharedPath = implode('/', $sharedPathParts);
+
+ $inputFileRelativePath = trim(str_replace($sharedPath, '', $inputFilePath), '/');
+ $outputFileRelativePath = trim(str_replace($sharedPath, '', $outputFilePath), '/');
+ $inputFileRelativePathParts = explode('/', $inputFileRelativePath);
+ $outputFileRelativePathParts = explode('/', $outputFileRelativePath);
+
+ $callback = function ($matches) use ($inputFileRelativePathParts, $outputFileRelativePathParts) {
+ $fullMatch = $matches[0];
+ $inputUrl = $matches[1];
+
+ if (preg_match('/https?:\/\//is', $inputUrl)) {
+ return $fullMatch;
+ }
+
+ $outputUrlParts = array_fill(0, count($outputFileRelativePathParts), '..');
+ $outputUrlParts = array_merge($outputUrlParts, $inputFileRelativePathParts);
+
+ if (strpos($inputUrl, '/') !== false) {
+ $inputUrlParts = explode('/', $inputUrl);
+ foreach ($inputUrlParts as $key => $inputUrlPart) {
+ if ($inputUrlPart == '..') {
+ array_pop($outputUrlParts);
+ unset($inputUrlParts[$key]);
+ }
+ }
+ $outputUrlParts[] = implode('/', $inputUrlParts);
+ } else {
+ $outputUrlParts[] = $inputUrl;
+ }
+ $outputUrl = implode('/', $outputUrlParts);
+
+ return str_replace($inputUrl, $outputUrl, $fullMatch);
+ };
+
+ $cssContent = preg_replace_callback('/url\(["\']?([^"]*)["\']?\)/is', $callback, $cssContent);
+
+ return $cssContent;
+ }
+
+ /**
+ * Creates template of configuration file for [[actionCompress]].
+ * @param string $configFile output file name.
+ * @throws \yii\console\Exception on failure.
+ */
+ public function actionTemplate($configFile)
+ {
+ $template = << array(
+ // 'yii\web\YiiAsset',
+ // 'yii\web\JqueryAsset',
+ ),
+ // Asset bundle for compression output:
+ 'targets' => array(
+ 'app\config\AllAsset' => array(
+ 'basePath' => 'path/to/web',
+ 'baseUrl' => '',
+ 'js' => 'js/all-{ts}.js',
+ 'css' => 'css/all-{ts}.css',
+ ),
+ ),
+ // Asset manager configuration:
+ 'assetManager' => array(
+ 'basePath' => __DIR__,
+ 'baseUrl' => '',
+ ),
+);
+EOD;
+ if (file_exists($configFile)) {
+ if (!$this->confirm("File '{$configFile}' already exists. Do you wish to overwrite it?")) {
+ return;
+ }
+ }
+ if (!file_put_contents($configFile, $template)) {
+ throw new Exception("Unable to write template file '{$configFile}'.");
+ } else {
+ echo "Configuration file template created at '{$configFile}'.\n\n";
+ }
+ }
+}
diff --git a/framework/yii/console/controllers/CacheController.php b/framework/yii/console/controllers/CacheController.php
new file mode 100644
index 0000000..95765fa
--- /dev/null
+++ b/framework/yii/console/controllers/CacheController.php
@@ -0,0 +1,67 @@
+
+ * @since 2.0
+ */
+class CacheController extends Controller
+{
+ /**
+ * Lists the caches that can be flushed.
+ */
+ public function actionIndex()
+ {
+ $caches = array();
+ $components = Yii::$app->getComponents();
+ foreach ($components as $name => $component) {
+ if ($component instanceof Cache) {
+ $caches[$name] = get_class($component);
+ } elseif (is_array($component) && isset($component['class']) && strpos($component['class'], 'Cache') !== false) {
+ $caches[$name] = $component['class'];
+ }
+ }
+ if (!empty($caches)) {
+ echo "The following caches can be flushed:\n\n";
+ foreach ($caches as $name => $class) {
+ echo " * $name: $class\n";
+ }
+ } else {
+ echo "No cache is used.\n";
+ }
+ }
+
+ /**
+ * Flushes cache.
+ * @param string $component Name of the cache application component to use.
+ *
+ * @throws \yii\console\Exception
+ */
+ public function actionFlush($component = 'cache')
+ {
+ /** @var $cache Cache */
+ $cache = Yii::$app->getComponent($component);
+ if (!$cache || !$cache instanceof Cache) {
+ throw new Exception('Application component "'.$component.'" is not defined or not a cache.');
+ }
+
+ if (!$cache->flush()) {
+ throw new Exception('Unable to flush cache.');
+ }
+
+ echo "\nDone.\n";
+ }
+}
diff --git a/framework/yii/console/controllers/HelpController.php b/framework/yii/console/controllers/HelpController.php
new file mode 100644
index 0000000..678d9f9
--- /dev/null
+++ b/framework/yii/console/controllers/HelpController.php
@@ -0,0 +1,428 @@
+
+ * @since 2.0
+ */
+class HelpController extends Controller
+{
+ /**
+ * Displays available commands or the detailed information
+ * about a particular command. For example,
+ *
+ * ~~~
+ * yii help # list available commands
+ * yii help message # display help info about "message"
+ * ~~~
+ *
+ * @param string $command The name of the command to show help about.
+ * If not provided, all available commands will be displayed.
+ * @return integer the exit status
+ * @throws Exception if the command for help is unknown
+ */
+ public function actionIndex($command = null)
+ {
+ if ($command !== null) {
+ $result = Yii::$app->createController($command);
+ if ($result === false) {
+ throw new Exception(Yii::t('yii', 'No help for unknown command "{command}".', array(
+ 'command' => $this->ansiFormat($command, Console::FG_YELLOW),
+ )));
+ }
+
+ list($controller, $actionID) = $result;
+
+ $actions = $this->getActions($controller);
+ if ($actionID !== '' || count($actions) === 1 && $actions[0] === $controller->defaultAction) {
+ $this->getActionHelp($controller, $actionID);
+ } else {
+ $this->getControllerHelp($controller);
+ }
+ } else {
+ $this->getHelp();
+ }
+ }
+
+ /**
+ * Returns all available command names.
+ * @return array all available command names
+ */
+ public function getCommands()
+ {
+ $commands = $this->getModuleCommands(Yii::$app);
+ sort($commands);
+ return array_unique($commands);
+ }
+
+ /**
+ * Returns all available actions of the specified controller.
+ * @param Controller $controller the controller instance
+ * @return array all available action IDs.
+ */
+ public function getActions($controller)
+ {
+ $actions = array_keys($controller->actions());
+ $class = new \ReflectionClass($controller);
+ foreach ($class->getMethods() as $method) {
+ $name = $method->getName();
+ if ($method->isPublic() && !$method->isStatic() && strpos($name, 'action') === 0 && $name !== 'actions') {
+ $actions[] = Inflector::camel2id(substr($name, 6));
+ }
+ }
+ sort($actions);
+ return array_unique($actions);
+ }
+
+ /**
+ * Returns available commands of a specified module.
+ * @param \yii\base\Module $module the module instance
+ * @return array the available command names
+ */
+ protected function getModuleCommands($module)
+ {
+ $prefix = $module instanceof Application ? '' : $module->getUniqueID() . '/';
+
+ $commands = array();
+ foreach (array_keys($module->controllerMap) as $id) {
+ $commands[] = $prefix . $id;
+ }
+
+ foreach ($module->getModules() as $id => $child) {
+ if (($child = $module->getModule($id)) === null) {
+ continue;
+ }
+ foreach ($this->getModuleCommands($child) as $command) {
+ $commands[] = $prefix . $id . '/' . $command;
+ }
+ }
+
+ $files = scandir($module->getControllerPath());
+ foreach ($files as $file) {
+ if (strcmp(substr($file, -14), 'Controller.php') === 0) {
+ $commands[] = $prefix . Inflector::camel2id(substr(basename($file), 0, -14));
+ }
+ }
+
+ return $commands;
+ }
+
+ /**
+ * Displays all available commands.
+ */
+ protected function getHelp()
+ {
+ $commands = $this->getCommands();
+ if (!empty($commands)) {
+ $this->stdout("\nThe following commands are available:\n\n", Console::BOLD);
+ foreach ($commands as $command) {
+ echo "- " . $this->ansiFormat($command, Console::FG_YELLOW) . "\n";
+ }
+ $this->stdout("\nTo see the help of each command, enter:\n", Console::BOLD);
+ echo "\n yii " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
+ . $this->ansiFormat('', Console::FG_CYAN) . "\n\n";
+ } else {
+ $this->stdout("\nNo commands are found.\n\n", Console::BOLD);
+ }
+ }
+
+ /**
+ * Displays the overall information of the command.
+ * @param Controller $controller the controller instance
+ */
+ protected function getControllerHelp($controller)
+ {
+ $class = new \ReflectionClass($controller);
+ $comment = strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($class->getDocComment(), '/'))), "\r", '');
+ if (preg_match('/^\s*@\w+/m', $comment, $matches, PREG_OFFSET_CAPTURE)) {
+ $comment = trim(substr($comment, 0, $matches[0][1]));
+ }
+
+ if ($comment !== '') {
+ $this->stdout("\nDESCRIPTION\n", Console::BOLD);
+ echo "\n" . Console::renderColoredString($comment) . "\n\n";
+ }
+
+ $actions = $this->getActions($controller);
+ if (!empty($actions)) {
+ $this->stdout("\nSUB-COMMANDS\n\n", Console::BOLD);
+ $prefix = $controller->getUniqueId();
+ foreach ($actions as $action) {
+ echo '- ' . $this->ansiFormat($prefix.'/'.$action, Console::FG_YELLOW);
+ if ($action === $controller->defaultAction) {
+ $this->stdout(' (default)', Console::FG_GREEN);
+ }
+ $summary = $this->getActionSummary($controller, $action);
+ if ($summary !== '') {
+ echo ': ' . $summary;
+ }
+ echo "\n";
+ }
+ echo "\nTo see the detailed information about individual sub-commands, enter:\n";
+ echo "\n yii " . $this->ansiFormat('help', Console::FG_YELLOW) . ' '
+ . $this->ansiFormat('', Console::FG_CYAN) . "\n\n";
+ }
+ }
+
+ /**
+ * Returns the short summary of the action.
+ * @param Controller $controller the controller instance
+ * @param string $actionID action ID
+ * @return string the summary about the action
+ */
+ protected function getActionSummary($controller, $actionID)
+ {
+ $action = $controller->createAction($actionID);
+ if ($action === null) {
+ return '';
+ }
+ if ($action instanceof InlineAction) {
+ $reflection = new \ReflectionMethod($controller, $action->actionMethod);
+ } else {
+ $reflection = new \ReflectionClass($action);
+ }
+ $tags = $this->parseComment($reflection->getDocComment());
+ if ($tags['description'] !== '') {
+ $limit = 73 - strlen($action->getUniqueId());
+ if ($actionID === $controller->defaultAction) {
+ $limit -= 10;
+ }
+ if ($limit < 0) {
+ $limit = 50;
+ }
+ $description = $tags['description'];
+ if (($pos = strpos($tags['description'], "\n")) !== false) {
+ $description = substr($description, 0, $pos);
+ }
+ $text = substr($description, 0, $limit);
+ return strlen($description) > $limit ? $text . '...' : $text;
+ } else {
+ return '';
+ }
+ }
+
+ /**
+ * Displays the detailed information of a command action.
+ * @param Controller $controller the controller instance
+ * @param string $actionID action ID
+ * @throws Exception if the action does not exist
+ */
+ protected function getActionHelp($controller, $actionID)
+ {
+ $action = $controller->createAction($actionID);
+ if ($action === null) {
+ throw new Exception(Yii::t('yii', 'No help for unknown sub-command "{command}".', array(
+ 'command' => rtrim($controller->getUniqueId() . '/' . $actionID, '/'),
+ )));
+ }
+ if ($action instanceof InlineAction) {
+ $method = new \ReflectionMethod($controller, $action->actionMethod);
+ } else {
+ $method = new \ReflectionMethod($action, 'run');
+ }
+
+ $tags = $this->parseComment($method->getDocComment());
+ $options = $this->getOptionHelps($controller);
+
+ if ($tags['description'] !== '') {
+ $this->stdout("\nDESCRIPTION\n", Console::BOLD);
+ echo "\n" . Console::renderColoredString($tags['description']) . "\n\n";
+ }
+
+ $this->stdout("\nUSAGE\n\n", Console::BOLD);
+ if ($action->id === $controller->defaultAction) {
+ echo 'yii ' . $this->ansiFormat($controller->getUniqueId(), Console::FG_YELLOW);
+ } else {
+ echo 'yii ' . $this->ansiFormat($action->getUniqueId(), Console::FG_YELLOW);
+ }
+ list ($required, $optional) = $this->getArgHelps($method, isset($tags['param']) ? $tags['param'] : array());
+ foreach ($required as $arg => $description) {
+ $this->stdout(' <' . $arg . '>', Console::FG_CYAN);
+ }
+ foreach ($optional as $arg => $description) {
+ $this->stdout(' [' . $arg . ']', Console::FG_CYAN);
+ }
+ if (!empty($options)) {
+ $this->stdout(' [...options...]', Console::FG_RED);
+ }
+ echo "\n\n";
+
+ if (!empty($required) || !empty($optional)) {
+ echo implode("\n\n", array_merge($required, $optional)) . "\n\n";
+ }
+
+ $options = $this->getOptionHelps($controller);
+ if (!empty($options)) {
+ $this->stdout("\nOPTIONS\n\n", Console::BOLD);
+ echo implode("\n\n", $options) . "\n\n";
+ }
+ }
+
+ /**
+ * Returns the help information about arguments.
+ * @param \ReflectionMethod $method
+ * @param string $tags the parsed comment block related with arguments
+ * @return array the required and optional argument help information
+ */
+ protected function getArgHelps($method, $tags)
+ {
+ if (is_string($tags)) {
+ $tags = array($tags);
+ }
+ $params = $method->getParameters();
+ $optional = $required = array();
+ foreach ($params as $i => $param) {
+ $name = $param->getName();
+ $tag = isset($tags[$i]) ? $tags[$i] : '';
+ if (preg_match('/^([^\s]+)\s+(\$\w+\s+)?(.*)/s', $tag, $matches)) {
+ $type = $matches[1];
+ $comment = $matches[3];
+ } else {
+ $type = null;
+ $comment = $tag;
+ }
+ if ($param->isDefaultValueAvailable()) {
+ $optional[$name] = $this->formatOptionHelp('- ' . $this->ansiFormat($name, Console::FG_CYAN), false, $type, $param->getDefaultValue(), $comment);
+ } else {
+ $required[$name] = $this->formatOptionHelp('- ' . $this->ansiFormat($name, Console::FG_CYAN), true, $type, null, $comment);
+ }
+ }
+
+ return array($required, $optional);
+ }
+
+ /**
+ * Returns the help information about the options available for a console controller.
+ * @param Controller $controller the console controller
+ * @return array the help information about the options
+ */
+ protected function getOptionHelps($controller)
+ {
+ $optionNames = $controller->globalOptions();
+ if (empty($optionNames)) {
+ return array();
+ }
+
+ $class = new \ReflectionClass($controller);
+ $options = array();
+ foreach ($class->getProperties() as $property) {
+ $name = $property->getName();
+ if (!in_array($name, $optionNames, true)) {
+ continue;
+ }
+ $defaultValue = $property->getValue($controller);
+ $tags = $this->parseComment($property->getDocComment());
+ if (isset($tags['var']) || isset($tags['property'])) {
+ $doc = isset($tags['var']) ? $tags['var'] : $tags['property'];
+ if (is_array($doc)) {
+ $doc = reset($doc);
+ }
+ if (preg_match('/^([^\s]+)(.*)/s', $doc, $matches)) {
+ $type = $matches[1];
+ $comment = $matches[2];
+ } else {
+ $type = null;
+ $comment = $doc;
+ }
+ $options[$name] = $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, $type, $defaultValue, $comment);
+ } else {
+ $options[$name] = $this->formatOptionHelp($this->ansiFormat('--' . $name, Console::FG_RED), false, null, $defaultValue, '');
+ }
+ }
+ ksort($options);
+ return $options;
+ }
+
+ /**
+ * Parses the comment block into tags.
+ * @param string $comment the comment block
+ * @return array the parsed tags
+ */
+ protected function parseComment($comment)
+ {
+ $tags = array();
+ $comment = "@description \n" . strtr(trim(preg_replace('/^\s*\**( |\t)?/m', '', trim($comment, '/'))), "\r", '');
+ $parts = preg_split('/^\s*@/m', $comment, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($parts as $part) {
+ if (preg_match('/^(\w+)(.*)/ms', trim($part), $matches)) {
+ $name = $matches[1];
+ if (!isset($tags[$name])) {
+ $tags[$name] = trim($matches[2]);
+ } elseif (is_array($tags[$name])) {
+ $tags[$name][] = trim($matches[2]);
+ } else {
+ $tags[$name] = array($tags[$name], trim($matches[2]));
+ }
+ }
+ }
+ return $tags;
+ }
+
+ /**
+ * Generates a well-formed string for an argument or option.
+ * @param string $name the name of the argument or option
+ * @param boolean $required whether the argument is required
+ * @param string $type the type of the option or argument
+ * @param mixed $defaultValue the default value of the option or argument
+ * @param string $comment comment about the option or argument
+ * @return string the formatted string for the argument or option
+ */
+ protected function formatOptionHelp($name, $required, $type, $defaultValue, $comment)
+ {
+ $doc = '';
+ $comment = trim($comment);
+
+ if ($defaultValue !== null && !is_array($defaultValue)) {
+ if ($type === null) {
+ $type = gettype($defaultValue);
+ }
+ if (is_bool($defaultValue)) {
+ // show as integer to avoid confusion
+ $defaultValue = (int)$defaultValue;
+ }
+ $doc = "$type (defaults to " . var_export($defaultValue, true) . ")";
+ } elseif (trim($type) !== '') {
+ $doc = $type;
+ }
+
+ if ($doc === '') {
+ $doc = $comment;
+ } elseif ($comment !== '') {
+ $doc .= "\n" . preg_replace("/^/m", " ", $comment);
+ }
+
+ $name = $required ? "$name (required)" : $name;
+ return $doc === '' ? $name : "$name: $doc";
+ }
+}
diff --git a/framework/yii/console/controllers/MessageController.php b/framework/yii/console/controllers/MessageController.php
new file mode 100644
index 0000000..306eb41
--- /dev/null
+++ b/framework/yii/console/controllers/MessageController.php
@@ -0,0 +1,237 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\console\controllers;
+
+use Yii;
+use yii\console\Controller;
+use yii\console\Exception;
+use yii\helpers\FileHelper;
+
+/**
+ * This command extracts messages to be translated from source files.
+ * The extracted messages are saved as PHP message source files
+ * under the specified directory.
+ *
+ * Usage:
+ * 1. Create a configuration file using the 'message/config' command:
+ * yii message/config /path/to/myapp/messages/config.php
+ * 2. Edit the created config file, adjusting it for your web application needs.
+ * 3. Run the 'message/extract' extract, using created config:
+ * yii message /path/to/myapp/messages/config.php
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class MessageController extends Controller
+{
+ /**
+ * @var string controller default action ID.
+ */
+ public $defaultAction = 'extract';
+
+
+ /**
+ * Creates a configuration file for the "extract" command.
+ *
+ * The generated configuration file contains detailed instructions on
+ * how to customize it to fit for your needs. After customization,
+ * you may use this configuration file with the "extract" command.
+ *
+ * @param string $filePath output file name.
+ * @throws Exception on failure.
+ */
+ public function actionConfig($filePath)
+ {
+ if (file_exists($filePath)) {
+ if (!$this->confirm("File '{$filePath}' already exists. Do you wish to overwrite it?")) {
+ return;
+ }
+ }
+ copy(Yii::getAlias('@yii/views/messageConfig.php'), $filePath);
+ echo "Configuration file template created at '{$filePath}'.\n\n";
+ }
+
+ /**
+ * Extracts messages to be translated from source code.
+ *
+ * This command will search through source code files and extract
+ * messages that need to be translated in different languages.
+ *
+ * @param string $configFile the path of the configuration file.
+ * You may use the "yii message/config" command to generate
+ * this file and then customize it for your needs.
+ * @throws Exception on failure.
+ */
+ public function actionExtract($configFile)
+ {
+ if (!is_file($configFile)) {
+ throw new Exception("The configuration file does not exist: $configFile");
+ }
+
+ $config = array_merge(array(
+ 'translator' => 'Yii::t',
+ 'overwrite' => false,
+ 'removeUnused' => false,
+ 'sort' => false,
+ ), require($configFile));
+
+ if (!isset($config['sourcePath'], $config['messagePath'], $config['languages'])) {
+ throw new Exception('The configuration file must specify "sourcePath", "messagePath" and "languages".');
+ }
+ if (!is_dir($config['sourcePath'])) {
+ throw new Exception("The source path {$config['sourcePath']} is not a valid directory.");
+ }
+ if (!is_dir($config['messagePath'])) {
+ throw new Exception("The message path {$config['messagePath']} is not a valid directory.");
+ }
+ if (empty($config['languages'])) {
+ throw new Exception("Languages cannot be empty.");
+ }
+
+ $files = FileHelper::findFiles(realpath($config['sourcePath']), $config);
+
+ $messages = array();
+ foreach ($files as $file) {
+ $messages = array_merge_recursive($messages, $this->extractMessages($file, $config['translator']));
+ }
+
+ foreach ($config['languages'] as $language) {
+ $dir = $config['messagePath'] . DIRECTORY_SEPARATOR . $language;
+ if (!is_dir($dir)) {
+ @mkdir($dir);
+ }
+ foreach ($messages as $category => $msgs) {
+ $msgs = array_values(array_unique($msgs));
+ $this->generateMessageFile($msgs, $dir . DIRECTORY_SEPARATOR . $category . '.php',
+ $config['overwrite'],
+ $config['removeUnused'],
+ $config['sort']);
+ }
+ }
+ }
+
+ /**
+ * Extracts messages from a file
+ *
+ * @param string $fileName name of the file to extract messages from
+ * @param string $translator name of the function used to translate messages
+ * @return array
+ */
+ protected function extractMessages($fileName, $translator)
+ {
+ echo "Extracting messages from $fileName...\n";
+ $subject = file_get_contents($fileName);
+ $messages = array();
+ if (!is_array($translator)) {
+ $translator = array($translator);
+ }
+ foreach ($translator as $currentTranslator) {
+ $n = preg_match_all(
+ '/\b' . $currentTranslator . '\s*\(\s*(\'.*?(? 0) {
+ $merged[$message] = $translated[$message];
+ } else {
+ $untranslated[] = $message;
+ }
+ }
+ ksort($merged);
+ sort($untranslated);
+ $todo = array();
+ foreach ($untranslated as $message) {
+ $todo[$message] = '';
+ }
+ ksort($translated);
+ foreach ($translated as $message => $translation) {
+ if (!isset($merged[$message]) && !isset($todo[$message]) && !$removeUnused) {
+ if (substr($translation, 0, 2) === '@@' && substr($translation, -2) === '@@') {
+ $todo[$message] = $translation;
+ } else {
+ $todo[$message] = '@@' . $translation . '@@';
+ }
+ }
+ }
+ $merged = array_merge($todo, $merged);
+ if ($sort) {
+ ksort($merged);
+ }
+ if (false === $overwrite) {
+ $fileName .= '.merged';
+ }
+ echo "translation merged.\n";
+ } else {
+ $merged = array();
+ foreach ($messages as $message) {
+ $merged[$message] = '';
+ }
+ ksort($merged);
+ echo "saved.\n";
+ }
+ $array = str_replace("\r", '', var_export($merged, true));
+ $content = <<id}' command.
+ * It contains the localizable messages extracted from source code.
+ * You may modify this file by translating the extracted messages.
+ *
+ * Each array element represents the translation (value) of a message (key).
+ * If the value is empty, the message is considered as not translated.
+ * Messages that no longer need translation will have their translations
+ * enclosed between a pair of '@@' marks.
+ *
+ * Message string can be used with plural forms format. Check i18n section
+ * of the guide for details.
+ *
+ * NOTE: this file must be saved in UTF-8 encoding.
+ */
+return $array;
+
+EOD;
+ file_put_contents($fileName, $content);
+ }
+}
diff --git a/framework/yii/console/controllers/MigrateController.php b/framework/yii/console/controllers/MigrateController.php
new file mode 100644
index 0000000..e9e3973
--- /dev/null
+++ b/framework/yii/console/controllers/MigrateController.php
@@ -0,0 +1,637 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\console\controllers;
+
+use Yii;
+use yii\console\Exception;
+use yii\console\Controller;
+use yii\db\Connection;
+use yii\db\Query;
+use yii\helpers\ArrayHelper;
+
+/**
+ * This command manages application migrations.
+ *
+ * A migration means a set of persistent changes to the application environment
+ * that is shared among different developers. For example, in an application
+ * backed by a database, a migration may refer to a set of changes to
+ * the database, such as creating a new table, adding a new table column.
+ *
+ * This command provides support for tracking the migration history, upgrading
+ * or downloading with migrations, and creating new migration skeletons.
+ *
+ * The migration history is stored in a database table named
+ * as [[migrationTable]]. The table will be automatically created the first time
+ * this command is executed, if it does not exist. You may also manually
+ * create it as follows:
+ *
+ * ~~~
+ * CREATE TABLE tbl_migration (
+ * version varchar(255) PRIMARY KEY,
+ * apply_time integer
+ * )
+ * ~~~
+ *
+ * Below are some common usages of this command:
+ *
+ * ~~~
+ * # creates a new migration named 'create_user_table'
+ * yii migrate/create create_user_table
+ *
+ * # applies ALL new migrations
+ * yii migrate
+ *
+ * # reverts the last applied migration
+ * yii migrate/down
+ * ~~~
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class MigrateController extends Controller
+{
+ /**
+ * The name of the dummy migration that marks the beginning of the whole migration history.
+ */
+ const BASE_MIGRATION = 'm000000_000000_base';
+
+ /**
+ * @var string the default command action.
+ */
+ public $defaultAction = 'up';
+ /**
+ * @var string the directory storing the migration classes. This can be either
+ * a path alias or a directory.
+ */
+ public $migrationPath = '@app/migrations';
+ /**
+ * @var string the name of the table for keeping applied migration information.
+ */
+ public $migrationTable = 'tbl_migration';
+ /**
+ * @var string the template file for generating new migrations.
+ * This can be either a path alias (e.g. "@app/migrations/template.php")
+ * or a file path.
+ */
+ public $templateFile = '@yii/views/migration.php';
+ /**
+ * @var boolean whether to execute the migration in an interactive mode.
+ */
+ public $interactive = true;
+ /**
+ * @var Connection|string the DB connection object or the application
+ * component ID of the DB connection.
+ */
+ public $db = 'db';
+
+ /**
+ * Returns the names of the global options for this command.
+ * @return array the names of the global options for this command.
+ */
+ public function globalOptions()
+ {
+ return array_merge(parent::globalOptions(), array(
+ 'migrationPath', 'migrationTable', 'db', 'templateFile', 'interactive', 'color'
+ ));
+ }
+
+ /**
+ * This method is invoked right before an action is to be executed (after all possible filters.)
+ * It checks the existence of the [[migrationPath]].
+ * @param \yii\base\Action $action the action to be executed.
+ * @return boolean whether the action should continue to be executed.
+ * @throws Exception if the migration directory does not exist.
+ */
+ public function beforeAction($action)
+ {
+ if (parent::beforeAction($action)) {
+ $path = Yii::getAlias($this->migrationPath);
+ if (!is_dir($path)) {
+ throw new Exception("The migration directory \"{$this->migrationPath}\" does not exist.");
+ }
+ $this->migrationPath = $path;
+
+ if ($action->id !== 'create') {
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ }
+ if (!$this->db instanceof Connection) {
+ throw new Exception("The 'db' option must refer to the application component ID of a DB connection.");
+ }
+ }
+
+ $version = Yii::getVersion();
+ echo "Yii Migration Tool (based on Yii v{$version})\n\n";
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Upgrades the application by applying new migrations.
+ * For example,
+ *
+ * ~~~
+ * yii migrate # apply all new migrations
+ * yii migrate 3 # apply the first 3 new migrations
+ * ~~~
+ *
+ * @param integer $limit the number of new migrations to be applied. If 0, it means
+ * applying all available new migrations.
+ */
+ public function actionUp($limit = 0)
+ {
+ $migrations = $this->getNewMigrations();
+ if (empty($migrations)) {
+ echo "No new migration found. Your system is up-to-date.\n";
+ return;
+ }
+
+ $total = count($migrations);
+ $limit = (int)$limit;
+ if ($limit > 0) {
+ $migrations = array_slice($migrations, 0, $limit);
+ }
+
+ $n = count($migrations);
+ if ($n === $total) {
+ echo "Total $n new " . ($n === 1 ? 'migration' : 'migrations') . " to be applied:\n";
+ } else {
+ echo "Total $n out of $total new " . ($total === 1 ? 'migration' : 'migrations') . " to be applied:\n";
+ }
+
+ foreach ($migrations as $migration) {
+ echo " $migration\n";
+ }
+ echo "\n";
+
+ if ($this->confirm('Apply the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+ foreach ($migrations as $migration) {
+ if (!$this->migrateUp($migration)) {
+ echo "\nMigration failed. The rest of the migrations are canceled.\n";
+ return;
+ }
+ }
+ echo "\nMigrated up successfully.\n";
+ }
+ }
+
+ /**
+ * Downgrades the application by reverting old migrations.
+ * For example,
+ *
+ * ~~~
+ * yii migrate/down # revert the last migration
+ * yii migrate/down 3 # revert the last 3 migrations
+ * ~~~
+ *
+ * @param integer $limit the number of migrations to be reverted. Defaults to 1,
+ * meaning the last applied migration will be reverted.
+ * @throws Exception if the number of the steps specified is less than 1.
+ */
+ public function actionDown($limit = 1)
+ {
+ $limit = (int)$limit;
+ if ($limit < 1) {
+ throw new Exception("The step argument must be greater than 0.");
+ }
+
+ $migrations = $this->getMigrationHistory($limit);
+ if (empty($migrations)) {
+ echo "No migration has been done before.\n";
+ return;
+ }
+ $migrations = array_keys($migrations);
+
+ $n = count($migrations);
+ echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be reverted:\n";
+ foreach ($migrations as $migration) {
+ echo " $migration\n";
+ }
+ echo "\n";
+
+ if ($this->confirm('Revert the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+ foreach ($migrations as $migration) {
+ if (!$this->migrateDown($migration)) {
+ echo "\nMigration failed. The rest of the migrations are canceled.\n";
+ return;
+ }
+ }
+ echo "\nMigrated down successfully.\n";
+ }
+ }
+
+ /**
+ * Redoes the last few migrations.
+ *
+ * This command will first revert the specified migrations, and then apply
+ * them again. For example,
+ *
+ * ~~~
+ * yii migrate/redo # redo the last applied migration
+ * yii migrate/redo 3 # redo the last 3 applied migrations
+ * ~~~
+ *
+ * @param integer $limit the number of migrations to be redone. Defaults to 1,
+ * meaning the last applied migration will be redone.
+ * @throws Exception if the number of the steps specified is less than 1.
+ */
+ public function actionRedo($limit = 1)
+ {
+ $limit = (int)$limit;
+ if ($limit < 1) {
+ throw new Exception("The step argument must be greater than 0.");
+ }
+
+ $migrations = $this->getMigrationHistory($limit);
+ if (empty($migrations)) {
+ echo "No migration has been done before.\n";
+ return;
+ }
+ $migrations = array_keys($migrations);
+
+ $n = count($migrations);
+ echo "Total $n " . ($n === 1 ? 'migration' : 'migrations') . " to be redone:\n";
+ foreach ($migrations as $migration) {
+ echo " $migration\n";
+ }
+ echo "\n";
+
+ if ($this->confirm('Redo the above ' . ($n === 1 ? 'migration' : 'migrations') . "?")) {
+ foreach ($migrations as $migration) {
+ if (!$this->migrateDown($migration)) {
+ echo "\nMigration failed. The rest of the migrations are canceled.\n";
+ return;
+ }
+ }
+ foreach (array_reverse($migrations) as $migration) {
+ if (!$this->migrateUp($migration)) {
+ echo "\nMigration failed. The rest of the migrations migrations are canceled.\n";
+ return;
+ }
+ }
+ echo "\nMigration redone successfully.\n";
+ }
+ }
+
+ /**
+ * Upgrades or downgrades till the specified version.
+ *
+ * This command will first revert the specified migrations, and then apply
+ * them again. For example,
+ *
+ * ~~~
+ * yii migrate/to 101129_185401 # using timestamp
+ * yii migrate/to m101129_185401_create_user_table # using full name
+ * ~~~
+ *
+ * @param string $version the version name that the application should be migrated to.
+ * This can be either the timestamp or the full name of the migration.
+ * @throws Exception if the version argument is invalid
+ */
+ public function actionTo($version)
+ {
+ $originalVersion = $version;
+ if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
+ $version = 'm' . $matches[1];
+ } else {
+ throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
+ }
+
+ // try migrate up
+ $migrations = $this->getNewMigrations();
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ $this->actionUp($i + 1);
+ return;
+ }
+ }
+
+ // try migrate down
+ $migrations = array_keys($this->getMigrationHistory(-1));
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ if ($i === 0) {
+ echo "Already at '$originalVersion'. Nothing needs to be done.\n";
+ } else {
+ $this->actionDown($i);
+ }
+ return;
+ }
+ }
+
+ throw new Exception("Unable to find the version '$originalVersion'.");
+ }
+
+ /**
+ * Modifies the migration history to the specified version.
+ *
+ * No actual migration will be performed.
+ *
+ * ~~~
+ * yii migrate/mark 101129_185401 # using timestamp
+ * yii migrate/mark m101129_185401_create_user_table # using full name
+ * ~~~
+ *
+ * @param string $version the version at which the migration history should be marked.
+ * This can be either the timestamp or the full name of the migration.
+ * @throws Exception if the version argument is invalid or the version cannot be found.
+ */
+ public function actionMark($version)
+ {
+ $originalVersion = $version;
+ if (preg_match('/^m?(\d{6}_\d{6})(_.*?)?$/', $version, $matches)) {
+ $version = 'm' . $matches[1];
+ } else {
+ throw new Exception("The version argument must be either a timestamp (e.g. 101129_185401)\nor the full name of a migration (e.g. m101129_185401_create_user_table).");
+ }
+
+ // try mark up
+ $migrations = $this->getNewMigrations();
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ if ($this->confirm("Set migration history at $originalVersion?")) {
+ $command = $this->db->createCommand();
+ for ($j = 0; $j <= $i; ++$j) {
+ $command->insert($this->migrationTable, array(
+ 'version' => $migrations[$j],
+ 'apply_time' => time(),
+ ))->execute();
+ }
+ echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
+ }
+ return;
+ }
+ }
+
+ // try mark down
+ $migrations = array_keys($this->getMigrationHistory(-1));
+ foreach ($migrations as $i => $migration) {
+ if (strpos($migration, $version . '_') === 0) {
+ if ($i === 0) {
+ echo "Already at '$originalVersion'. Nothing needs to be done.\n";
+ } else {
+ if ($this->confirm("Set migration history at $originalVersion?")) {
+ $command = $this->db->createCommand();
+ for ($j = 0; $j < $i; ++$j) {
+ $command->delete($this->migrationTable, array(
+ 'version' => $migrations[$j],
+ ))->execute();
+ }
+ echo "The migration history is set at $originalVersion.\nNo actual migration was performed.\n";
+ }
+ }
+ return;
+ }
+ }
+
+ throw new Exception("Unable to find the version '$originalVersion'.");
+ }
+
+ /**
+ * Displays the migration history.
+ *
+ * This command will show the list of migrations that have been applied
+ * so far. For example,
+ *
+ * ~~~
+ * yii migrate/history # showing the last 10 migrations
+ * yii migrate/history 5 # showing the last 5 migrations
+ * yii migrate/history 0 # showing the whole history
+ * ~~~
+ *
+ * @param integer $limit the maximum number of migrations to be displayed.
+ * If it is 0, the whole migration history will be displayed.
+ */
+ public function actionHistory($limit = 10)
+ {
+ $limit = (int)$limit;
+ $migrations = $this->getMigrationHistory($limit);
+ if (empty($migrations)) {
+ echo "No migration has been done before.\n";
+ } else {
+ $n = count($migrations);
+ if ($limit > 0) {
+ echo "Showing the last $n applied " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+ } else {
+ echo "Total $n " . ($n === 1 ? 'migration has' : 'migrations have') . " been applied before:\n";
+ }
+ foreach ($migrations as $version => $time) {
+ echo " (" . date('Y-m-d H:i:s', $time) . ') ' . $version . "\n";
+ }
+ }
+ }
+
+ /**
+ * Displays the un-applied new migrations.
+ *
+ * This command will show the new migrations that have not been applied.
+ * For example,
+ *
+ * ~~~
+ * yii migrate/new # showing the first 10 new migrations
+ * yii migrate/new 5 # showing the first 5 new migrations
+ * yii migrate/new 0 # showing all new migrations
+ * ~~~
+ *
+ * @param integer $limit the maximum number of new migrations to be displayed.
+ * If it is 0, all available new migrations will be displayed.
+ */
+ public function actionNew($limit = 10)
+ {
+ $limit = (int)$limit;
+ $migrations = $this->getNewMigrations();
+ if (empty($migrations)) {
+ echo "No new migrations found. Your system is up-to-date.\n";
+ } else {
+ $n = count($migrations);
+ if ($limit > 0 && $n > $limit) {
+ $migrations = array_slice($migrations, 0, $limit);
+ echo "Showing $limit out of $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+ } else {
+ echo "Found $n new " . ($n === 1 ? 'migration' : 'migrations') . ":\n";
+ }
+
+ foreach ($migrations as $migration) {
+ echo " " . $migration . "\n";
+ }
+ }
+ }
+
+ /**
+ * Creates a new migration.
+ *
+ * This command creates a new migration using the available migration template.
+ * After using this command, developers should modify the created migration
+ * skeleton by filling up the actual migration logic.
+ *
+ * ~~~
+ * yii migrate/create create_user_table
+ * ~~~
+ *
+ * @param string $name the name of the new migration. This should only contain
+ * letters, digits and/or underscores.
+ * @throws Exception if the name argument is invalid.
+ */
+ public function actionCreate($name)
+ {
+ if (!preg_match('/^\w+$/', $name)) {
+ throw new Exception("The migration name should contain letters, digits and/or underscore characters only.");
+ }
+
+ $name = 'm' . gmdate('ymd_His') . '_' . $name;
+ $file = $this->migrationPath . DIRECTORY_SEPARATOR . $name . '.php';
+
+ if ($this->confirm("Create new migration '$file'?")) {
+ $content = $this->renderFile(Yii::getAlias($this->templateFile), array(
+ 'className' => $name,
+ ));
+ file_put_contents($file, $content);
+ echo "New migration created successfully.\n";
+ }
+ }
+
+ /**
+ * Upgrades with the specified migration class.
+ * @param string $class the migration class name
+ * @return boolean whether the migration is successful
+ */
+ protected function migrateUp($class)
+ {
+ if ($class === self::BASE_MIGRATION) {
+ return true;
+ }
+
+ echo "*** applying $class\n";
+ $start = microtime(true);
+ $migration = $this->createMigration($class);
+ if ($migration->up() !== false) {
+ $this->db->createCommand()->insert($this->migrationTable, array(
+ 'version' => $class,
+ 'apply_time' => time(),
+ ))->execute();
+ $time = microtime(true) - $start;
+ echo "*** applied $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+ return true;
+ } else {
+ $time = microtime(true) - $start;
+ echo "*** failed to apply $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+ return false;
+ }
+ }
+
+ /**
+ * Downgrades with the specified migration class.
+ * @param string $class the migration class name
+ * @return boolean whether the migration is successful
+ */
+ protected function migrateDown($class)
+ {
+ if ($class === self::BASE_MIGRATION) {
+ return true;
+ }
+
+ echo "*** reverting $class\n";
+ $start = microtime(true);
+ $migration = $this->createMigration($class);
+ if ($migration->down() !== false) {
+ $this->db->createCommand()->delete($this->migrationTable, array(
+ 'version' => $class,
+ ))->execute();
+ $time = microtime(true) - $start;
+ echo "*** reverted $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+ return true;
+ } else {
+ $time = microtime(true) - $start;
+ echo "*** failed to revert $class (time: " . sprintf("%.3f", $time) . "s)\n\n";
+ return false;
+ }
+ }
+
+ /**
+ * Creates a new migration instance.
+ * @param string $class the migration class name
+ * @return \yii\db\Migration the migration instance
+ */
+ protected function createMigration($class)
+ {
+ $file = $this->migrationPath . DIRECTORY_SEPARATOR . $class . '.php';
+ require_once($file);
+ return new $class(array(
+ 'db' => $this->db,
+ ));
+ }
+
+ /**
+ * Returns the migration history.
+ * @param integer $limit the maximum number of records in the history to be returned
+ * @return array the migration history
+ */
+ protected function getMigrationHistory($limit)
+ {
+ if ($this->db->schema->getTableSchema($this->migrationTable, true) === null) {
+ $this->createMigrationHistoryTable();
+ }
+ $query = new Query;
+ $rows = $query->select(array('version', 'apply_time'))
+ ->from($this->migrationTable)
+ ->orderBy('version DESC')
+ ->limit($limit)
+ ->createCommand($this->db)
+ ->queryAll();
+ $history = ArrayHelper::map($rows, 'version', 'apply_time');
+ unset($history[self::BASE_MIGRATION]);
+ return $history;
+ }
+
+ /**
+ * Creates the migration history table.
+ */
+ protected function createMigrationHistoryTable()
+ {
+ echo 'Creating migration history table "' . $this->migrationTable . '"...';
+ $this->db->createCommand()->createTable($this->migrationTable, array(
+ 'version' => 'varchar(255) NOT NULL PRIMARY KEY',
+ 'apply_time' => 'integer',
+ ))->execute();
+ $this->db->createCommand()->insert($this->migrationTable, array(
+ 'version' => self::BASE_MIGRATION,
+ 'apply_time' => time(),
+ ))->execute();
+ echo "done.\n";
+ }
+
+ /**
+ * Returns the migrations that are not applied.
+ * @return array list of new migrations
+ */
+ protected function getNewMigrations()
+ {
+ $applied = array();
+ foreach ($this->getMigrationHistory(-1) as $version => $time) {
+ $applied[substr($version, 1, 13)] = true;
+ }
+
+ $migrations = array();
+ $handle = opendir($this->migrationPath);
+ while (($file = readdir($handle)) !== false) {
+ if ($file === '.' || $file === '..') {
+ continue;
+ }
+ $path = $this->migrationPath . DIRECTORY_SEPARATOR . $file;
+ if (preg_match('/^(m(\d{6}_\d{6})_.*?)\.php$/', $file, $matches) && is_file($path) && !isset($applied[$matches[2]])) {
+ $migrations[] = $matches[1];
+ }
+ }
+ closedir($handle);
+ sort($migrations);
+ return $migrations;
+ }
+}
diff --git a/yii/console/runtime/.gitignore b/framework/yii/console/runtime/.gitignore
similarity index 100%
rename from yii/console/runtime/.gitignore
rename to framework/yii/console/runtime/.gitignore
diff --git a/framework/yii/data/ActiveDataProvider.php b/framework/yii/data/ActiveDataProvider.php
new file mode 100644
index 0000000..bd822f9
--- /dev/null
+++ b/framework/yii/data/ActiveDataProvider.php
@@ -0,0 +1,182 @@
+ Post::find(),
+ * 'pagination' => array(
+ * 'pageSize' => 20,
+ * ),
+ * ));
+ *
+ * // get the posts in the current page
+ * $posts = $provider->getModels();
+ * ~~~
+ *
+ * And the following example shows how to use ActiveDataProvider without ActiveRecord:
+ *
+ * ~~~
+ * $query = new Query;
+ * $provider = new ActiveDataProvider(array(
+ * 'query' => $query->from('tbl_post'),
+ * 'pagination' => array(
+ * 'pageSize' => 20,
+ * ),
+ * ));
+ *
+ * // get the posts in the current page
+ * $posts = $provider->getModels();
+ * ~~~
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class ActiveDataProvider extends BaseDataProvider
+{
+ /**
+ * @var Query the query that is used to fetch data models and [[totalCount]]
+ * if it is not explicitly set.
+ */
+ public $query;
+ /**
+ * @var string|callable the column that is used as the key of the data models.
+ * This can be either a column name, or a callable that returns the key value of a given data model.
+ *
+ * If this is not set, the following rules will be used to determine the keys of the data models:
+ *
+ * - If [[query]] is an [[ActiveQuery]] instance, the primary keys of [[ActiveQuery::modelClass]] will be used.
+ * - Otherwise, the keys of the [[models]] array will be used.
+ *
+ * @see getKeys()
+ */
+ public $key;
+ /**
+ * @var Connection|string the DB connection object or the application component ID of the DB connection.
+ * If not set, the default DB connection will be used.
+ */
+ public $db;
+
+ /**
+ * Initializes the DB connection component.
+ * This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
+ * @throws InvalidConfigException if [[db]] is invalid.
+ */
+ public function init()
+ {
+ parent::init();
+ if (is_string($this->db)) {
+ $this->db = Yii::$app->getComponent($this->db);
+ if (!$this->db instanceof Connection) {
+ throw new InvalidConfigException('The "db" property must be a valid DB Connection application component.');
+ }
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareModels()
+ {
+ if (!$this->query instanceof Query) {
+ throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.');
+ }
+ if (($pagination = $this->getPagination()) !== false) {
+ $pagination->totalCount = $this->getTotalCount();
+ $this->query->limit($pagination->getLimit())->offset($pagination->getOffset());
+ }
+ if (($sort = $this->getSort()) !== false) {
+ $this->query->addOrderBy($sort->getOrders());
+ }
+ return $this->query->all($this->db);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareKeys($models)
+ {
+ $keys = array();
+ if ($this->key !== null) {
+ foreach ($models as $model) {
+ if (is_string($this->key)) {
+ $keys[] = $model[$this->key];
+ } else {
+ $keys[] = call_user_func($this->key, $model);
+ }
+ }
+ return $keys;
+ } elseif ($this->query instanceof ActiveQuery) {
+ /** @var \yii\db\ActiveRecord $class */
+ $class = $this->query->modelClass;
+ $pks = $class::primaryKey();
+ if (count($pks) === 1) {
+ $pk = $pks[0];
+ foreach ($models as $model) {
+ $keys[] = $model[$pk];
+ }
+ } else {
+ foreach ($models as $model) {
+ $kk = array();
+ foreach ($pks as $pk) {
+ $kk[] = $model[$pk];
+ }
+ $keys[] = json_encode($kk);
+ }
+ }
+ return $keys;
+ } else {
+ return array_keys($models);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareTotalCount()
+ {
+ if (!$this->query instanceof Query) {
+ throw new InvalidConfigException('The "query" property must be an instance of Query or its subclass.');
+ }
+ $query = clone $this->query;
+ return $query->limit(-1)->offset(-1)->count('*', $this->db);
+ }
+
+ /**
+ * @inheritdoc
+ */
+ public function setSort($value)
+ {
+ parent::setSort($value);
+ if (($sort = $this->getSort()) !== false && empty($sort->attributes) && $this->query instanceof ActiveQuery) {
+ /** @var Model $model */
+ $model = new $this->query->modelClass;
+ foreach($model->attributes() as $attribute) {
+ $sort->attributes[$attribute] = array(
+ 'asc' => array($attribute => Sort::ASC),
+ 'desc' => array($attribute => Sort::DESC),
+ 'label' => $model->getAttributeLabel($attribute),
+ );
+ }
+ }
+ }
+}
diff --git a/framework/yii/data/ArrayDataProvider.php b/framework/yii/data/ArrayDataProvider.php
new file mode 100644
index 0000000..77f31cd
--- /dev/null
+++ b/framework/yii/data/ArrayDataProvider.php
@@ -0,0 +1,133 @@
+ $query->from('tbl_post')->all(),
+ * 'sort' => array(
+ * 'attributes' => array(
+ * 'id', 'username', 'email',
+ * ),
+ * ),
+ * 'pagination' => array(
+ * 'pageSize' => 10,
+ * ),
+ * ));
+ * // get the posts in the current page
+ * $posts = $provider->getModels();
+ * ~~~
+ *
+ * Note: if you want to use the sorting feature, you must configure the [[sort]] property
+ * so that the provider knows which columns can be sorted.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class ArrayDataProvider extends BaseDataProvider
+{
+ /**
+ * @var string|callable the column that is used as the key of the data models.
+ * This can be either a column name, or a callable that returns the key value of a given data model.
+ * If this is not set, the index of the [[models]] array will be used.
+ * @see getKeys()
+ */
+ public $key;
+ /**
+ * @var array the data that is not paginated or sorted. When pagination is enabled,
+ * this property usually contains more elements than [[models]].
+ * The array elements must use zero-based integer keys.
+ */
+ public $allModels;
+
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareModels()
+ {
+ if (($models = $this->allModels) === null) {
+ return array();
+ }
+
+ if (($sort = $this->getSort()) !== false) {
+ $models = $this->sortModels($models, $sort);
+ }
+
+ if (($pagination = $this->getPagination()) !== false) {
+ $pagination->totalCount = $this->getTotalCount();
+ $models = array_slice($models, $pagination->getOffset(), $pagination->getLimit());
+ }
+
+ return $models;
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareKeys($models)
+ {
+ if ($this->key !== null) {
+ $keys = array();
+ foreach ($models as $model) {
+ if (is_string($this->key)) {
+ $keys[] = $model[$this->key];
+ } else {
+ $keys[] = call_user_func($this->key, $model);
+ }
+ }
+ return $keys;
+ } else {
+ return array_keys($models);
+ }
+ }
+
+ /**
+ * @inheritdoc
+ */
+ protected function prepareTotalCount()
+ {
+ return count($this->allModels);
+ }
+
+ /**
+ * Sorts the data models according to the given sort definition
+ * @param array $models the models to be sorted
+ * @param Sort $sort the sort definition
+ * @return array the sorted data models
+ */
+ protected function sortModels($models, $sort)
+ {
+ $orders = $sort->getOrders();
+ if (!empty($orders)) {
+ ArrayHelper::multisort($models, array_keys($orders), array_values($orders));
+ }
+ return $models;
+ }
+}
diff --git a/framework/yii/data/BaseDataProvider.php b/framework/yii/data/BaseDataProvider.php
new file mode 100644
index 0000000..3b0669d
--- /dev/null
+++ b/framework/yii/data/BaseDataProvider.php
@@ -0,0 +1,256 @@
+
+ * @since 2.0
+ */
+abstract class BaseDataProvider extends Component implements DataProviderInterface
+{
+ /**
+ * @var string an ID that uniquely identifies the data provider among all data providers.
+ * You should set this property if the same page contains two or more different data providers.
+ * Otherwise, the [[pagination]] and [[sort]] mainly not work properly.
+ */
+ public $id;
+
+ private $_sort;
+ private $_pagination;
+ private $_keys;
+ private $_models;
+ private $_totalCount;
+
+
+ /**
+ * Prepares the data models that will be made available in the current page.
+ * @return array the available data models
+ */
+ abstract protected function prepareModels();
+
+ /**
+ * Prepares the keys associated with the currently available data models.
+ * @param array $models the available data models
+ * @return array the keys
+ */
+ abstract protected function prepareKeys($models);
+
+ /**
+ * Returns a value indicating the total number of data models in this data provider.
+ * @return integer total number of data models in this data provider.
+ */
+ abstract protected function prepareTotalCount();
+
+ /**
+ * Prepares the data models and keys.
+ *
+ * This method will prepare the data models and keys that can be retrieved via
+ * [[getModels()]] and [[getKeys()]].
+ *
+ * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
+ *
+ * @param boolean $forcePrepare whether to force data preparation even if it has been done before.
+ */
+ public function prepare($forcePrepare = false)
+ {
+ if ($forcePrepare || $this->_models === null) {
+ $this->_models = $this->prepareModels();
+ }
+ if ($forcePrepare || $this->_keys === null) {
+ $this->_keys = $this->prepareKeys($this->_models);
+ }
+ }
+
+ /**
+ * Returns the data models in the current page.
+ * @return array the list of data models in the current page.
+ */
+ public function getModels()
+ {
+ $this->prepare();
+ return $this->_models;
+ }
+
+ /**
+ * Sets the data models in the current page.
+ * @param array $models the models in the current page
+ */
+ public function setModels($models)
+ {
+ $this->_models = $models;
+ }
+
+ /**
+ * Returns the key values associated with the data models.
+ * @return array the list of key values corresponding to [[models]]. Each data model in [[models]]
+ * is uniquely identified by the corresponding key value in this array.
+ */
+ public function getKeys()
+ {
+ $this->prepare();
+ return $this->_keys;
+ }
+
+ /**
+ * Sets the key values associated with the data models.
+ * @param array $keys the list of key values corresponding to [[models]].
+ */
+ public function setKeys($keys)
+ {
+ $this->_keys = $keys;
+ }
+
+ /**
+ * Returns the number of data models in the current page.
+ * @return integer the number of data models in the current page.
+ */
+ public function getCount()
+ {
+ return count($this->getModels());
+ }
+
+ /**
+ * Returns the total number of data models.
+ * When [[pagination]] is false, this returns the same value as [[count]].
+ * Otherwise, it will call [[prepareTotalCount()]] to get the count.
+ * @return integer total number of possible data models.
+ */
+ public function getTotalCount()
+ {
+ if ($this->getPagination() === false) {
+ return $this->getCount();
+ } elseif ($this->_totalCount === null) {
+ $this->_totalCount = $this->prepareTotalCount();
+ }
+ return $this->_totalCount;
+ }
+
+ /**
+ * Sets the total number of data models.
+ * @param integer $value the total number of data models.
+ */
+ public function setTotalCount($value)
+ {
+ $this->_totalCount = $value;
+ }
+
+ /**
+ * Returns the pagination object used by this data provider.
+ * Note that you should call [[prepare()]] or [[getModels()]] first to get correct values
+ * of [[Pagination::totalCount]] and [[Pagination::pageCount]].
+ * @return Pagination|boolean the pagination object. If this is false, it means the pagination is disabled.
+ */
+ public function getPagination()
+ {
+ if ($this->_pagination === null) {
+ $this->_pagination = new Pagination;
+ if ($this->id !== null) {
+ $this->_pagination->pageVar = $this->id . '-page';
+ }
+ }
+ return $this->_pagination;
+ }
+
+ /**
+ * Sets the pagination for this data provider.
+ * @param array|Pagination|boolean $value the pagination to be used by this data provider.
+ * This can be one of the following:
+ *
+ * - a configuration array for creating the pagination object. The "class" element defaults
+ * to 'yii\data\Pagination'
+ * - an instance of [[Pagination]] or its subclass
+ * - false, if pagination needs to be disabled.
+ *
+ * @throws InvalidParamException
+ */
+ public function setPagination($value)
+ {
+ if (is_array($value)) {
+ $config = array(
+ 'class' => Pagination::className(),
+ );
+ if ($this->id !== null) {
+ $config['pageVar'] = $this->id . '-page';
+ }
+ $this->_pagination = Yii::createObject(array_merge($config, $value));
+ } elseif ($value instanceof Pagination || $value === false) {
+ $this->_pagination = $value;
+ } else {
+ throw new InvalidParamException('Only Pagination instance, configuration array or false is allowed.');
+ }
+ }
+
+ /**
+ * @return Sort|boolean the sorting object. If this is false, it means the sorting is disabled.
+ */
+ public function getSort()
+ {
+ if ($this->_sort === null) {
+ $this->setSort(array());
+ }
+ return $this->_sort;
+ }
+
+ /**
+ * Sets the sort definition for this data provider.
+ * @param array|Sort|boolean $value the sort definition to be used by this data provider.
+ * This can be one of the following:
+ *
+ * - a configuration array for creating the sort definition object. The "class" element defaults
+ * to 'yii\data\Sort'
+ * - an instance of [[Sort]] or its subclass
+ * - false, if sorting needs to be disabled.
+ *
+ * @throws InvalidParamException
+ */
+ public function setSort($value)
+ {
+ if (is_array($value)) {
+ $config = array(
+ 'class' => Sort::className(),
+ );
+ if ($this->id !== null) {
+ $config['sortVar'] = $this->id . '-sort';
+ }
+ $this->_sort = Yii::createObject(array_merge($config, $value));
+ } elseif ($value instanceof Sort || $value === false) {
+ $this->_sort = $value;
+ } else {
+ throw new InvalidParamException('Only Sort instance, configuration array or false is allowed.');
+ }
+ }
+
+ /**
+ * Refreshes the data provider.
+ * After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again,
+ * they will re-execute the query and return the latest data available.
+ */
+ public function refresh()
+ {
+ $this->_totalCount = null;
+ $this->_models = null;
+ $this->_keys = null;
+ }
+}
diff --git a/framework/yii/data/DataProviderInterface.php b/framework/yii/data/DataProviderInterface.php
new file mode 100644
index 0000000..1dea1e6
--- /dev/null
+++ b/framework/yii/data/DataProviderInterface.php
@@ -0,0 +1,70 @@
+
+ * @since 2.0
+ */
+interface DataProviderInterface
+{
+ /**
+ * Prepares the data models and keys.
+ *
+ * This method will prepare the data models and keys that can be retrieved via
+ * [[getModels()]] and [[getKeys()]].
+ *
+ * This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
+ *
+ * @param boolean $forcePrepare whether to force data preparation even if it has been done before.
+ */
+ public function prepare($forcePrepare = false);
+
+ /**
+ * Returns the number of data models in the current page.
+ * This is equivalent to `count($provider->getModels())`.
+ * When [[pagination]] is false, this is the same as [[totalCount]].
+ * @return integer the number of data models in the current page.
+ */
+ public function getCount();
+
+ /**
+ * Returns the total number of data models.
+ * When [[pagination]] is false, this is the same as [[count]].
+ * @return integer total number of possible data models.
+ */
+ public function getTotalCount();
+
+ /**
+ * Returns the data models in the current page.
+ * @return array the list of data models in the current page.
+ */
+ public function getModels();
+
+ /**
+ * Returns the key values associated with the data models.
+ * @return array the list of key values corresponding to [[models]]. Each data model in [[models]]
+ * is uniquely identified by the corresponding key value in this array.
+ */
+ public function getKeys();
+
+ /**
+ * @return Sort the sorting object. If this is false, it means the sorting is disabled.
+ */
+ public function getSort();
+
+ /**
+ * @return Pagination the pagination object. If this is false, it means the pagination is disabled.
+ */
+ public function getPagination();
+}
diff --git a/framework/yii/data/Pagination.php b/framework/yii/data/Pagination.php
new file mode 100644
index 0000000..04af828
--- /dev/null
+++ b/framework/yii/data/Pagination.php
@@ -0,0 +1,200 @@
+where(array('status' => 1));
+ * $countQuery = clone $query;
+ * $pages = new Pagination(array('totalCount' => $countQuery->count()));
+ * $models = $query->offset($pages->offset)
+ * ->limit($pages->limit)
+ * ->all();
+ *
+ * return $this->render('index', array(
+ * 'models' => $models,
+ * 'pages' => $pages,
+ * ));
+ * }
+ * ~~~
+ *
+ * View:
+ *
+ * ~~~
+ * foreach ($models as $model) {
+ * // display $model here
+ * }
+ *
+ * // display pagination
+ * echo LinkPager::widget(array(
+ * 'pagination' => $pages,
+ * ));
+ * ~~~
+ *
+ * @property integer $limit The limit of the data. This may be used to set the LIMIT value for a SQL statement
+ * for fetching the current page of data. Note that if the page size is infinite, a value -1 will be returned.
+ * This property is read-only.
+ * @property integer $offset The offset of the data. This may be used to set the OFFSET value for a SQL
+ * statement for fetching the current page of data. This property is read-only.
+ * @property integer $page The zero-based current page number.
+ * @property integer $pageCount Number of pages. This property is read-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Pagination extends Object
+{
+ /**
+ * @var string name of the parameter storing the current page index. Defaults to 'page'.
+ * @see params
+ */
+ public $pageVar = 'page';
+ /**
+ * @var boolean whether to always have the page parameter in the URL created by [[createUrl()]].
+ * If false and [[page]] is 0, the page parameter will not be put in the URL.
+ */
+ public $forcePageVar = true;
+ /**
+ * @var string the route of the controller action for displaying the paged contents.
+ * If not set, it means using the currently requested route.
+ */
+ public $route;
+ /**
+ * @var array parameters (name => value) that should be used to obtain the current page number
+ * and to create new pagination URLs. If not set, $_GET will be used instead.
+ *
+ * The array element indexed by [[pageVar]] is considered to be the current page number.
+ * If the element does not exist, the current page number is considered 0.
+ */
+ public $params;
+ /**
+ * @var boolean whether to check if [[page]] is within valid range.
+ * When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1).
+ * Because [[pageCount]] relies on the correct value of [[totalCount]] which may not be available
+ * in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page
+ * number validation. By doing so, [[page]] will return the value indexed by [[pageVar]] in [[params]].
+ */
+ public $validatePage = true;
+ /**
+ * @var integer number of items on each page. Defaults to 20.
+ * If it is less than 1, it means the page size is infinite, and thus a single page contains all items.
+ */
+ public $pageSize = 20;
+ /**
+ * @var integer total number of items.
+ */
+ public $totalCount = 0;
+
+
+ /**
+ * @return integer number of pages
+ */
+ public function getPageCount()
+ {
+ if ($this->pageSize < 1) {
+ return $this->totalCount > 0 ? 1 : 0;
+ } else {
+ $totalCount = $this->totalCount < 0 ? 0 : (int)$this->totalCount;
+ return (int)(($totalCount + $this->pageSize - 1) / $this->pageSize);
+ }
+ }
+
+ private $_page;
+
+ /**
+ * Returns the zero-based current page number.
+ * @param boolean $recalculate whether to recalculate the current page based on the page size and item count.
+ * @return integer the zero-based current page number.
+ */
+ public function getPage($recalculate = false)
+ {
+ if ($this->_page === null || $recalculate) {
+ $params = $this->params === null ? $_GET : $this->params;
+ if (isset($params[$this->pageVar]) && is_scalar($params[$this->pageVar])) {
+ $this->_page = (int)$params[$this->pageVar] - 1;
+ if ($this->validatePage) {
+ $pageCount = $this->getPageCount();
+ if ($this->_page >= $pageCount) {
+ $this->_page = $pageCount - 1;
+ }
+ }
+ if ($this->_page < 0) {
+ $this->_page = 0;
+ }
+ } else {
+ $this->_page = 0;
+ }
+ }
+ return $this->_page;
+ }
+
+ /**
+ * Sets the current page number.
+ * @param integer $value the zero-based index of the current page.
+ */
+ public function setPage($value)
+ {
+ $this->_page = $value;
+ }
+
+ /**
+ * Creates the URL suitable for pagination with the specified page number.
+ * This method is mainly called by pagers when creating URLs used to perform pagination.
+ * @param integer $page the zero-based page number that the URL should point to.
+ * @return string the created URL
+ * @see params
+ * @see forcePageVar
+ */
+ public function createUrl($page)
+ {
+ $params = $this->params === null ? $_GET : $this->params;
+ if ($page > 0 || $page >= 0 && $this->forcePageVar) {
+ $params[$this->pageVar] = $page + 1;
+ } else {
+ unset($params[$this->pageVar]);
+ }
+ $route = $this->route === null ? Yii::$app->controller->route : $this->route;
+ return Yii::$app->getUrlManager()->createUrl($route, $params);
+ }
+
+ /**
+ * @return integer the offset of the data. This may be used to set the
+ * OFFSET value for a SQL statement for fetching the current page of data.
+ */
+ public function getOffset()
+ {
+ return $this->pageSize < 1 ? 0 : $this->getPage() * $this->pageSize;
+ }
+
+ /**
+ * @return integer the limit of the data. This may be used to set the
+ * LIMIT value for a SQL statement for fetching the current page of data.
+ * Note that if the page size is infinite, a value -1 will be returned.
+ */
+ public function getLimit()
+ {
+ return $this->pageSize < 1 ? -1 : $this->pageSize;
+ }
+}
diff --git a/framework/yii/data/Sort.php b/framework/yii/data/Sort.php
new file mode 100644
index 0000000..5eb031e
--- /dev/null
+++ b/framework/yii/data/Sort.php
@@ -0,0 +1,396 @@
+ array(
+ * 'age',
+ * 'name' => array(
+ * 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC),
+ * 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC),
+ * 'default' => Sort::DESC,
+ * 'label' => 'Name',
+ * ),
+ * ),
+ * ));
+ *
+ * $models = Article::find()
+ * ->where(array('status' => 1))
+ * ->orderBy($sort->orders)
+ * ->all();
+ *
+ * return $this->render('index', array(
+ * 'models' => $models,
+ * 'sort' => $sort,
+ * ));
+ * }
+ * ~~~
+ *
+ * View:
+ *
+ * ~~~
+ * // display links leading to sort actions
+ * echo $sort->link('name') . ' | ' . $sort->link('age');
+ *
+ * foreach ($models as $model) {
+ * // display $model here
+ * }
+ * ~~~
+ *
+ * In the above, we declare two [[attributes]] that support sorting: name and age.
+ * We pass the sort information to the Article query so that the query results are
+ * sorted by the orders specified by the Sort object. In the view, we show two hyperlinks
+ * that can lead to pages with the data sorted by the corresponding attributes.
+ *
+ * @property array $attributeOrders Sort directions indexed by attribute names. Sort direction can be either
+ * [[Sort::ASC]] for ascending order or [[Sort::DESC]] for descending order. This property is read-only.
+ * @property array $orders The columns (keys) and their corresponding sort directions (values). This can be
+ * passed to [[\yii\db\Query::orderBy()]] to construct a DB query. This property is read-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Sort extends Object
+{
+ /**
+ * Sort ascending
+ */
+ const ASC = false;
+
+ /**
+ * Sort descending
+ */
+ const DESC = true;
+
+ /**
+ * @var boolean whether the sorting can be applied to multiple attributes simultaneously.
+ * Defaults to false, which means each time the data can only be sorted by one attribute.
+ */
+ public $enableMultiSort = false;
+
+ /**
+ * @var array list of attributes that are allowed to be sorted. Its syntax can be
+ * described using the following example:
+ *
+ * ~~~
+ * array(
+ * 'age',
+ * 'name' => array(
+ * 'asc' => array('first_name' => Sort::ASC, 'last_name' => Sort::ASC),
+ * 'desc' => array('first_name' => Sort::DESC, 'last_name' => Sort::DESC),
+ * 'default' => Sort::DESC,
+ * 'label' => 'Name',
+ * ),
+ * )
+ * ~~~
+ *
+ * In the above, two attributes are declared: "age" and "user". The "age" attribute is
+ * a simple attribute which is equivalent to the following:
+ *
+ * ~~~
+ * 'age' => array(
+ * 'asc' => array('age' => Sort::ASC),
+ * 'desc' => array('age' => Sort::DESC),
+ * 'default' => Sort::ASC,
+ * 'label' => Inflector::camel2words('age'),
+ * )
+ * ~~~
+ *
+ * The "user" attribute is a composite attribute:
+ *
+ * - The "user" key represents the attribute name which will appear in the URLs leading
+ * to sort actions. Attribute names cannot contain characters listed in [[separators]].
+ * - The "asc" and "desc" elements specify how to sort by the attribute in ascending
+ * and descending orders, respectively. Their values represent the actual columns and
+ * the directions by which the data should be sorted by.
+ * - The "default" element specifies by which direction the attribute should be sorted
+ * if it is not currently sorted (the default value is ascending order).
+ * - The "label" element specifies what label should be used when calling [[link()]] to create
+ * a sort link. If not set, [[Inflector::camel2words()]] will be called to get a label.
+ * Note that it will not be HTML-encoded.
+ *
+ * Note that if the Sort object is already created, you can only use the full format
+ * to configure every attribute. Each attribute must include these elements: asc and desc.
+ */
+ public $attributes = array();
+ /**
+ * @var string the name of the parameter that specifies which attributes to be sorted
+ * in which direction. Defaults to 'sort'.
+ * @see params
+ */
+ public $sortVar = 'sort';
+ /**
+ * @var string the tag appeared in the [[sortVar]] parameter that indicates the attribute should be sorted
+ * in descending order. Defaults to 'desc'.
+ */
+ public $descTag = 'desc';
+ /**
+ * @var array the order that should be used when the current request does not specify any order.
+ * The array keys are attribute names and the array values are the corresponding sort directions. For example,
+ *
+ * ~~~
+ * array(
+ * 'name' => Sort::ASC,
+ * 'create_time' => Sort::DESC,
+ * )
+ * ~~~
+ *
+ * @see attributeOrders
+ */
+ public $defaultOrder;
+ /**
+ * @var string the route of the controller action for displaying the sorted contents.
+ * If not set, it means using the currently requested route.
+ */
+ public $route;
+ /**
+ * @var array separators used in the generated URL. This must be an array consisting of
+ * two elements. The first element specifies the character separating different
+ * attributes, while the second element specifies the character separating attribute name
+ * and the corresponding sort direction. Defaults to `array('.', '-')`.
+ */
+ public $separators = array('.', '-');
+ /**
+ * @var array parameters (name => value) that should be used to obtain the current sort directions
+ * and to create new sort URLs. If not set, $_GET will be used instead.
+ *
+ * The array element indexed by [[sortVar]] is considered to be the current sort directions.
+ * If the element does not exist, the [[defaults|default order]] will be used.
+ *
+ * @see sortVar
+ * @see defaultOrder
+ */
+ public $params;
+ /**
+ * @var \yii\web\UrlManager the URL manager used for creating sort URLs. If not set,
+ * the "urlManager" application component will be used.
+ */
+ public $urlManager;
+
+ /**
+ * Normalizes the [[attributes]] property.
+ */
+ public function init()
+ {
+ $attributes = array();
+ foreach ($this->attributes as $name => $attribute) {
+ if (!is_array($attribute)) {
+ $attributes[$attribute] = array(
+ 'asc' => array($attribute => self::ASC),
+ 'desc' => array($attribute => self::DESC),
+ );
+ } elseif (!isset($attribute['asc'], $attribute['desc'])) {
+ $attributes[$name] = array_merge(array(
+ 'asc' => array($name => self::ASC),
+ 'desc' => array($name => self::DESC),
+ ), $attribute);
+ } else {
+ $attributes[$name] = $attribute;
+ }
+ }
+ $this->attributes = $attributes;
+ }
+
+ /**
+ * Returns the columns and their corresponding sort directions.
+ * @param boolean $recalculate whether to recalculate the sort directions
+ * @return array the columns (keys) and their corresponding sort directions (values).
+ * This can be passed to [[\yii\db\Query::orderBy()]] to construct a DB query.
+ */
+ public function getOrders($recalculate = false)
+ {
+ $attributeOrders = $this->getAttributeOrders($recalculate);
+ $orders = array();
+ foreach ($attributeOrders as $attribute => $direction) {
+ $definition = $this->attributes[$attribute];
+ $columns = $definition[$direction === self::ASC ? 'asc' : 'desc'];
+ foreach ($columns as $name => $dir) {
+ $orders[$name] = $dir;
+ }
+ }
+ return $orders;
+ }
+
+ /**
+ * @var array the currently requested sort order as computed by [[getAttributeOrders]].
+ */
+ private $_attributeOrders;
+
+ /**
+ * Returns the currently requested sort information.
+ * @param boolean $recalculate whether to recalculate the sort directions
+ * @return array sort directions indexed by attribute names.
+ * Sort direction can be either [[Sort::ASC]] for ascending order or
+ * [[Sort::DESC]] for descending order.
+ */
+ public function getAttributeOrders($recalculate = false)
+ {
+ if ($this->_attributeOrders === null || $recalculate) {
+ $this->_attributeOrders = array();
+ $params = $this->params === null ? $_GET : $this->params;
+ if (isset($params[$this->sortVar]) && is_scalar($params[$this->sortVar])) {
+ $attributes = explode($this->separators[0], $params[$this->sortVar]);
+ foreach ($attributes as $attribute) {
+ $descending = false;
+ if (($pos = strrpos($attribute, $this->separators[1])) !== false) {
+ if ($descending = (substr($attribute, $pos + 1) === $this->descTag)) {
+ $attribute = substr($attribute, 0, $pos);
+ }
+ }
+
+ if (isset($this->attributes[$attribute])) {
+ $this->_attributeOrders[$attribute] = $descending;
+ if (!$this->enableMultiSort) {
+ return $this->_attributeOrders;
+ }
+ }
+ }
+ }
+ if (empty($this->_attributeOrders) && is_array($this->defaultOrder)) {
+ $this->_attributeOrders = $this->defaultOrder;
+ }
+ }
+ return $this->_attributeOrders;
+ }
+
+ /**
+ * Returns the sort direction of the specified attribute in the current request.
+ * @param string $attribute the attribute name
+ * @return boolean|null Sort direction of the attribute. Can be either [[Sort::ASC]]
+ * for ascending order or [[Sort::DESC]] for descending order. Null is returned
+ * if the attribute is invalid or does not need to be sorted.
+ */
+ public function getAttributeOrder($attribute)
+ {
+ $orders = $this->getAttributeOrders();
+ return isset($orders[$attribute]) ? $orders[$attribute] : null;
+ }
+
+ /**
+ * Generates a hyperlink that links to the sort action to sort by the specified attribute.
+ * Based on the sort direction, the CSS class of the generated hyperlink will be appended
+ * with "asc" or "desc".
+ * @param string $attribute the attribute name by which the data should be sorted by.
+ * @param array $options additional HTML attributes for the hyperlink tag.
+ * There is one special attribute `label` which will be used as the label of the hyperlink.
+ * If this is not set, the label defined in [[attributes]] will be used.
+ * If no label is defined, [[yii\helpers\Inflector::camel2words()]] will be called to get a label.
+ * Note that it will not be HTML-encoded.
+ * @return string the generated hyperlink
+ * @throws InvalidConfigException if the attribute is unknown
+ */
+ public function link($attribute, $options = array())
+ {
+ if (($direction = $this->getAttributeOrder($attribute)) !== null) {
+ $class = $direction ? 'desc' : 'asc';
+ if (isset($options['class'])) {
+ $options['class'] .= ' ' . $class;
+ } else {
+ $options['class'] = $class;
+ }
+ }
+
+ $url = $this->createUrl($attribute);
+ $options['data-sort'] = $this->createSortVar($attribute);
+
+ if (isset($options['label'])) {
+ $label = $options['label'];
+ unset($options['label']);
+ } else {
+ if (isset($this->attributes[$attribute]['label'])) {
+ $label = $this->attributes[$attribute]['label'];
+ } else {
+ $label = Inflector::camel2words($attribute);
+ }
+ }
+ return Html::a($label, $url, $options);
+ }
+
+ /**
+ * Creates a URL for sorting the data by the specified attribute.
+ * This method will consider the current sorting status given by [[attributeOrders]].
+ * For example, if the current page already sorts the data by the specified attribute in ascending order,
+ * then the URL created will lead to a page that sorts the data by the specified attribute in descending order.
+ * @param string $attribute the attribute name
+ * @return string the URL for sorting. False if the attribute is invalid.
+ * @throws InvalidConfigException if the attribute is unknown
+ * @see attributeOrders
+ * @see params
+ */
+ public function createUrl($attribute)
+ {
+ $params = $this->params === null ? $_GET : $this->params;
+ $params[$this->sortVar] = $this->createSortVar($attribute);
+ $route = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
+ $urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
+ return $urlManager->createUrl($route, $params);
+ }
+
+ /**
+ * Creates the sort variable for the specified attribute.
+ * The newly created sort variable can be used to create a URL that will lead to
+ * sorting by the specified attribute.
+ * @param string $attribute the attribute name
+ * @return string the value of the sort variable
+ * @throws InvalidConfigException if the specified attribute is not defined in [[attributes]]
+ */
+ public function createSortVar($attribute)
+ {
+ if (!isset($this->attributes[$attribute])) {
+ throw new InvalidConfigException("Unknown attribute: $attribute");
+ }
+ $definition = $this->attributes[$attribute];
+ $directions = $this->getAttributeOrders();
+ if (isset($directions[$attribute])) {
+ $descending = !$directions[$attribute];
+ unset($directions[$attribute]);
+ } else {
+ $descending = !empty($definition['default']);
+ }
+
+ if ($this->enableMultiSort) {
+ $directions = array_merge(array($attribute => $descending), $directions);
+ } else {
+ $directions = array($attribute => $descending);
+ }
+
+ $sorts = array();
+ foreach ($directions as $attribute => $descending) {
+ $sorts[] = $descending ? $attribute . $this->separators[1] . $this->descTag : $attribute;
+ }
+ return implode($this->separators[0], $sorts);
+ }
+
+ /**
+ * Returns a value indicating whether the sort definition supports sorting by the named attribute.
+ * @param string $name the attribute name
+ * @return boolean whether the sort definition supports sorting by the named attribute.
+ */
+ public function hasAttribute($name)
+ {
+ return isset($this->attributes[$name]);
+ }
+}
diff --git a/framework/yii/db/ActiveQuery.php b/framework/yii/db/ActiveQuery.php
new file mode 100644
index 0000000..375e91f
--- /dev/null
+++ b/framework/yii/db/ActiveQuery.php
@@ -0,0 +1,321 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\db;
+
+/**
+ * ActiveQuery represents a DB query associated with an Active Record class.
+ *
+ * ActiveQuery instances are usually created by [[ActiveRecord::find()]], [[ActiveRecord::findBySql()]]
+ * and [[ActiveRecord::count()]].
+ *
+ * ActiveQuery mainly provides the following methods to retrieve the query results:
+ *
+ * - [[one()]]: returns a single record populated with the first row of data.
+ * - [[all()]]: returns all records based on the query results.
+ * - [[count()]]: returns the number of records.
+ * - [[sum()]]: returns the sum over the specified column.
+ * - [[average()]]: returns the average over the specified column.
+ * - [[min()]]: returns the min over the specified column.
+ * - [[max()]]: returns the max over the specified column.
+ * - [[scalar()]]: returns the value of the first column in the first row of the query result.
+ * - [[exists()]]: returns a value indicating whether the query result has data or not.
+ *
+ * Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]],
+ * [[orderBy()]] to customize the query options.
+ *
+ * ActiveQuery also provides the following additional query options:
+ *
+ * - [[with()]]: list of relations that this query should be performed with.
+ * - [[indexBy()]]: the name of the column by which the query result should be indexed.
+ * - [[asArray()]]: whether to return each record as an array.
+ *
+ * These options can be configured using methods of the same name. For example:
+ *
+ * ~~~
+ * $customers = Customer::find()->with('orders')->asArray()->all();
+ * ~~~
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class ActiveQuery extends Query
+{
+ /**
+ * @var string the name of the ActiveRecord class.
+ */
+ public $modelClass;
+ /**
+ * @var array list of relations that this query should be performed with
+ */
+ public $with;
+ /**
+ * @var boolean whether to return each record as an array. If false (default), an object
+ * of [[modelClass]] will be created to represent each record.
+ */
+ public $asArray;
+ /**
+ * @var string the SQL statement to be executed for retrieving AR records.
+ * This is set by [[ActiveRecord::findBySql()]].
+ */
+ public $sql;
+
+
+ /**
+ * PHP magic method.
+ * This method allows calling static method defined in [[modelClass]] via this query object.
+ * It is mainly implemented for supporting the feature of scope.
+ * @param string $name the method name to be called
+ * @param array $params the parameters passed to the method
+ * @return mixed the method return result
+ */
+ public function __call($name, $params)
+ {
+ if (method_exists($this->modelClass, $name)) {
+ array_unshift($params, $this);
+ call_user_func_array(array($this->modelClass, $name), $params);
+ return $this;
+ } else {
+ return parent::__call($name, $params);
+ }
+ }
+
+ /**
+ * Executes query and returns all results as an array.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ $command = $this->createCommand($db);
+ $rows = $command->queryAll();
+ if (!empty($rows)) {
+ $models = $this->createModels($rows);
+ if (!empty($this->with)) {
+ $this->populateRelations($models, $this->with);
+ }
+ return $models;
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Executes query and returns a single row of result.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
+ * the query result may be either an array or an ActiveRecord object. Null will be returned
+ * if the query results in nothing.
+ */
+ public function one($db = null)
+ {
+ $command = $this->createCommand($db);
+ $row = $command->queryOne();
+ if ($row !== false) {
+ if ($this->asArray) {
+ $model = $row;
+ } else {
+ /** @var $class ActiveRecord */
+ $class = $this->modelClass;
+ $model = $class::create($row);
+ }
+ if (!empty($this->with)) {
+ $models = array($model);
+ $this->populateRelations($models, $this->with);
+ $model = $models[0];
+ }
+ return $model;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Creates a DB command that can be used to execute this query.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return Command the created DB command instance.
+ */
+ public function createCommand($db = null)
+ {
+ /** @var $modelClass ActiveRecord */
+ $modelClass = $this->modelClass;
+ if ($db === null) {
+ $db = $modelClass::getDb();
+ }
+
+ $params = $this->params;
+ if ($this->sql === null) {
+ if ($this->from === null) {
+ $tableName = $modelClass::tableName();
+ if ($this->select === null && !empty($this->join)) {
+ $this->select = array("$tableName.*");
+ }
+ $this->from = array($tableName);
+ }
+ list ($this->sql, $params) = $db->getQueryBuilder()->build($this);
+ }
+ return $db->createCommand($this->sql, $params);
+ }
+
+ /**
+ * Sets the [[asArray]] property.
+ * @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
+ * @return static the query object itself
+ */
+ public function asArray($value = true)
+ {
+ $this->asArray = $value;
+ return $this;
+ }
+
+ /**
+ * Specifies the relations with which this query should be performed.
+ *
+ * The parameters to this method can be either one or multiple strings, or a single array
+ * of relation names and the optional callbacks to customize the relations.
+ *
+ * The followings are some usage examples:
+ *
+ * ~~~
+ * // find customers together with their orders and country
+ * Customer::find()->with('orders', 'country')->all();
+ * // find customers together with their country and orders of status 1
+ * Customer::find()->with(array(
+ * 'orders' => function($query) {
+ * $query->andWhere('status = 1');
+ * },
+ * 'country',
+ * ))->all();
+ * ~~~
+ *
+ * @return static the query object itself
+ */
+ public function with()
+ {
+ $this->with = func_get_args();
+ if (isset($this->with[0]) && is_array($this->with[0])) {
+ // the parameter is given as an array
+ $this->with = $this->with[0];
+ }
+ return $this;
+ }
+
+ /**
+ * Sets the [[indexBy]] property.
+ * @param string|callable $column the name of the column by which the query results should be indexed by.
+ * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
+ * row or model data. The signature of the callable should be:
+ *
+ * ~~~
+ * // $model is an AR instance when `asArray` is false,
+ * // or an array of column values when `asArray` is true.
+ * function ($model)
+ * {
+ * // return the index value corresponding to $model
+ * }
+ * ~~~
+ *
+ * @return static the query object itself
+ */
+ public function indexBy($column)
+ {
+ return parent::indexBy($column);
+ }
+
+ private function createModels($rows)
+ {
+ $models = array();
+ if ($this->asArray) {
+ if ($this->indexBy === null) {
+ return $rows;
+ }
+ foreach ($rows as $row) {
+ if (is_string($this->indexBy)) {
+ $key = $row[$this->indexBy];
+ } else {
+ $key = call_user_func($this->indexBy, $row);
+ }
+ $models[$key] = $row;
+ }
+ } else {
+ /** @var $class ActiveRecord */
+ $class = $this->modelClass;
+ if ($this->indexBy === null) {
+ foreach ($rows as $row) {
+ $models[] = $class::create($row);
+ }
+ } else {
+ foreach ($rows as $row) {
+ $model = $class::create($row);
+ if (is_string($this->indexBy)) {
+ $key = $model->{$this->indexBy};
+ } else {
+ $key = call_user_func($this->indexBy, $model);
+ }
+ $models[$key] = $model;
+ }
+ }
+ }
+ return $models;
+ }
+
+ private function populateRelations(&$models, $with)
+ {
+ $primaryModel = new $this->modelClass;
+ $relations = $this->normalizeRelations($primaryModel, $with);
+ foreach ($relations as $name => $relation) {
+ if ($relation->asArray === null) {
+ // inherit asArray from primary query
+ $relation->asArray = $this->asArray;
+ }
+ $relation->findWith($name, $models);
+ }
+ }
+
+ /**
+ * @param ActiveRecord $model
+ * @param array $with
+ * @return ActiveRelation[]
+ */
+ private function normalizeRelations($model, $with)
+ {
+ $relations = array();
+ foreach ($with as $name => $callback) {
+ if (is_integer($name)) {
+ $name = $callback;
+ $callback = null;
+ }
+ if (($pos = strpos($name, '.')) !== false) {
+ // with sub-relations
+ $childName = substr($name, $pos + 1);
+ $name = substr($name, 0, $pos);
+ } else {
+ $childName = null;
+ }
+
+ $t = strtolower($name);
+ if (!isset($relations[$t])) {
+ $relation = $model->getRelation($name);
+ $relation->primaryModel = null;
+ $relations[$t] = $relation;
+ } else {
+ $relation = $relations[$t];
+ }
+
+ if (isset($childName)) {
+ $relation->with[$childName] = $callback;
+ } elseif ($callback !== null) {
+ call_user_func($callback, $relation);
+ }
+ }
+ return $relations;
+ }
+}
diff --git a/framework/yii/db/ActiveRecord.php b/framework/yii/db/ActiveRecord.php
new file mode 100644
index 0000000..e1c4b4f
--- /dev/null
+++ b/framework/yii/db/ActiveRecord.php
@@ -0,0 +1,1505 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\db;
+
+use yii\base\InvalidConfigException;
+use yii\base\Model;
+use yii\base\InvalidParamException;
+use yii\base\ModelEvent;
+use yii\base\UnknownMethodException;
+use yii\base\InvalidCallException;
+use yii\helpers\StringHelper;
+use yii\helpers\Inflector;
+
+/**
+ * ActiveRecord is the base class for classes representing relational data in terms of objects.
+ *
+ * @include @yii/db/ActiveRecord.md
+ *
+ * @property array $dirtyAttributes The changed attribute values (name-value pairs). This property is
+ * read-only.
+ * @property boolean $isNewRecord Whether the record is new and should be inserted when calling [[save()]].
+ * @property array $oldAttributes The old attribute values (name-value pairs).
+ * @property mixed $oldPrimaryKey The old primary key value. An array (column name => column value) is
+ * returned if the primary key is composite or `$asArray` is true. A string is returned otherwise (null will be
+ * returned if the key value is null). This property is read-only.
+ * @property mixed $primaryKey The primary key value. An array (column name => column value) is returned if
+ * the primary key is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+ * the key value is null). This property is read-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class ActiveRecord extends Model
+{
+ /**
+ * @event Event an event that is triggered when the record is initialized via [[init()]].
+ */
+ const EVENT_INIT = 'init';
+ /**
+ * @event Event an event that is triggered after the record is created and populated with query result.
+ */
+ const EVENT_AFTER_FIND = 'afterFind';
+ /**
+ * @event ModelEvent an event that is triggered before inserting a record.
+ * You may set [[ModelEvent::isValid]] to be false to stop the insertion.
+ */
+ const EVENT_BEFORE_INSERT = 'beforeInsert';
+ /**
+ * @event Event an event that is triggered after a record is inserted.
+ */
+ const EVENT_AFTER_INSERT = 'afterInsert';
+ /**
+ * @event ModelEvent an event that is triggered before updating a record.
+ * You may set [[ModelEvent::isValid]] to be false to stop the update.
+ */
+ const EVENT_BEFORE_UPDATE = 'beforeUpdate';
+ /**
+ * @event Event an event that is triggered after a record is updated.
+ */
+ const EVENT_AFTER_UPDATE = 'afterUpdate';
+ /**
+ * @event ModelEvent an event that is triggered before deleting a record.
+ * You may set [[ModelEvent::isValid]] to be false to stop the deletion.
+ */
+ const EVENT_BEFORE_DELETE = 'beforeDelete';
+ /**
+ * @event Event an event that is triggered after a record is deleted.
+ */
+ const EVENT_AFTER_DELETE = 'afterDelete';
+
+ /**
+ * The insert operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
+ */
+ const OP_INSERT = 0x01;
+ /**
+ * The update operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
+ */
+ const OP_UPDATE = 0x02;
+ /**
+ * The delete operation. This is mainly used when overriding [[transactions()]] to specify which operations are transactional.
+ */
+ const OP_DELETE = 0x04;
+ /**
+ * All three operations: insert, update, delete.
+ * This is a shortcut of the expression: OP_INSERT | OP_UPDATE | OP_DELETE.
+ */
+ const OP_ALL = 0x07;
+
+ /**
+ * @var array attribute values indexed by attribute names
+ */
+ private $_attributes = array();
+ /**
+ * @var array old attribute values indexed by attribute names.
+ */
+ private $_oldAttributes;
+ /**
+ * @var array related models indexed by the relation names
+ */
+ private $_related;
+
+
+ /**
+ * Returns the database connection used by this AR class.
+ * By default, the "db" application component is used as the database connection.
+ * You may override this method if you want to use a different database connection.
+ * @return Connection the database connection used by this AR class.
+ */
+ public static function getDb()
+ {
+ return \Yii::$app->getDb();
+ }
+
+ /**
+ * Creates an [[ActiveQuery]] instance for query purpose.
+ *
+ * @include @yii/db/ActiveRecord-find.md
+ *
+ * @param mixed $q the query parameter. This can be one of the followings:
+ *
+ * - a scalar value (integer or string): query by a single primary key value and return the
+ * corresponding record.
+ * - an array of name-value pairs: query by a set of column values and return a single record matching all of them.
+ * - null: return a new [[ActiveQuery]] object for further query purpose.
+ *
+ * @return ActiveQuery|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance
+ * is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
+ * returned (null will be returned if there is no matching).
+ * @throws InvalidConfigException if the AR class does not have a primary key
+ * @see createQuery()
+ */
+ public static function find($q = null)
+ {
+ $query = static::createQuery();
+ if (is_array($q)) {
+ return $query->where($q)->one();
+ } elseif ($q !== null) {
+ // query by primary key
+ $primaryKey = static::primaryKey();
+ if (isset($primaryKey[0])) {
+ return $query->where(array($primaryKey[0] => $q))->one();
+ } else {
+ throw new InvalidConfigException(get_called_class() . ' must have a primary key.');
+ }
+ }
+ return $query;
+ }
+
+ /**
+ * Creates an [[ActiveQuery]] instance with a given SQL statement.
+ *
+ * Note that because the SQL statement is already specified, calling additional
+ * query modification methods (such as `where()`, `order()`) on the created [[ActiveQuery]]
+ * instance will have no effect. However, calling `with()`, `asArray()` or `indexBy()` is
+ * still fine.
+ *
+ * Below is an example:
+ *
+ * ~~~
+ * $customers = Customer::findBySql('SELECT * FROM tbl_customer')->all();
+ * ~~~
+ *
+ * @param string $sql the SQL statement to be executed
+ * @param array $params parameters to be bound to the SQL statement during execution.
+ * @return ActiveQuery the newly created [[ActiveQuery]] instance
+ */
+ public static function findBySql($sql, $params = array())
+ {
+ $query = static::createQuery();
+ $query->sql = $sql;
+ return $query->params($params);
+ }
+
+ /**
+ * Updates the whole table using the provided attribute values and conditions.
+ * For example, to change the status to be 1 for all customers whose status is 2:
+ *
+ * ~~~
+ * Customer::updateAll(array('status' => 1), 'status = 2');
+ * ~~~
+ *
+ * @param array $attributes attribute values (name-value pairs) to be saved into the table
+ * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return integer the number of rows updated
+ */
+ public static function updateAll($attributes, $condition = '', $params = array())
+ {
+ $command = static::getDb()->createCommand();
+ $command->update(static::tableName(), $attributes, $condition, $params);
+ return $command->execute();
+ }
+
+ /**
+ * Updates the whole table using the provided counter changes and conditions.
+ * For example, to increment all customers' age by 1,
+ *
+ * ~~~
+ * Customer::updateAllCounters(array('age' => 1));
+ * ~~~
+ *
+ * @param array $counters the counters to be updated (attribute name => increment value).
+ * Use negative values if you want to decrement the counters.
+ * @param string|array $condition the conditions that will be put in the WHERE part of the UPDATE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * Do not name the parameters as `:bp0`, `:bp1`, etc., because they are used internally by this method.
+ * @return integer the number of rows updated
+ */
+ public static function updateAllCounters($counters, $condition = '', $params = array())
+ {
+ $n = 0;
+ foreach ($counters as $name => $value) {
+ $counters[$name] = new Expression("[[$name]]+:bp{$n}", array(":bp{$n}" => $value));
+ $n++;
+ }
+ $command = static::getDb()->createCommand();
+ $command->update(static::tableName(), $counters, $condition, $params);
+ return $command->execute();
+ }
+
+ /**
+ * Deletes rows in the table using the provided conditions.
+ * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
+ *
+ * For example, to delete all customers whose status is 3:
+ *
+ * ~~~
+ * Customer::deleteAll('status = 3');
+ * ~~~
+ *
+ * @param string|array $condition the conditions that will be put in the WHERE part of the DELETE SQL.
+ * Please refer to [[Query::where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return integer the number of rows deleted
+ */
+ public static function deleteAll($condition = '', $params = array())
+ {
+ $command = static::getDb()->createCommand();
+ $command->delete(static::tableName(), $condition, $params);
+ return $command->execute();
+ }
+
+ /**
+ * Creates an [[ActiveQuery]] instance.
+ * This method is called by [[find()]], [[findBySql()]] and [[count()]] to start a SELECT query.
+ * You may override this method to return a customized query (e.g. `CustomerQuery` specified
+ * written for querying `Customer` purpose.)
+ * @return ActiveQuery the newly created [[ActiveQuery]] instance.
+ */
+ public static function createQuery()
+ {
+ return new ActiveQuery(array(
+ 'modelClass' => get_called_class(),
+ ));
+ }
+
+ /**
+ * Declares the name of the database table associated with this AR class.
+ * By default this method returns the class name as the table name by calling [[Inflector::camel2id()]]
+ * with prefix 'tbl_'. For example, 'Customer' becomes 'tbl_customer', and 'OrderItem' becomes
+ * 'tbl_order_item'. You may override this method if the table is not named after this convention.
+ * @return string the table name
+ */
+ public static function tableName()
+ {
+ return 'tbl_' . Inflector::camel2id(StringHelper::basename(get_called_class()), '_');
+ }
+
+ /**
+ * Returns the schema information of the DB table associated with this AR class.
+ * @return TableSchema the schema information of the DB table associated with this AR class.
+ * @throws InvalidConfigException if the table for the AR class does not exist.
+ */
+ public static function getTableSchema()
+ {
+ $schema = static::getDb()->getTableSchema(static::tableName());
+ if ($schema !== null) {
+ return $schema;
+ } else {
+ throw new InvalidConfigException("The table does not exist: " . static::tableName());
+ }
+ }
+
+ /**
+ * Returns the primary key name(s) for this AR class.
+ * The default implementation will return the primary key(s) as declared
+ * in the DB table that is associated with this AR class.
+ *
+ * If the DB table does not declare any primary key, you should override
+ * this method to return the attributes that you want to use as primary keys
+ * for this AR class.
+ *
+ * Note that an array should be returned even for a table with single primary key.
+ *
+ * @return string[] the primary keys of the associated database table.
+ */
+ public static function primaryKey()
+ {
+ return static::getTableSchema()->primaryKey;
+ }
+
+ /**
+ * Returns the name of the column that stores the lock version for implementing optimistic locking.
+ *
+ * Optimistic locking allows multiple users to access the same record for edits and avoids
+ * potential conflicts. In case when a user attempts to save the record upon some staled data
+ * (because another user has modified the data), a [[StaleObjectException]] exception will be thrown,
+ * and the update or deletion is skipped.
+ *
+ * Optimized locking is only supported by [[update()]] and [[delete()]].
+ *
+ * To use optimized locking:
+ *
+ * 1. Create a column to store the version number of each row. The column type should be `BIGINT DEFAULT 0`.
+ * Override this method to return the name of this column.
+ * 2. In the Web form that collects the user input, add a hidden field that stores
+ * the lock version of the recording being updated.
+ * 3. In the controller action that does the data updating, try to catch the [[StaleObjectException]]
+ * and implement necessary business logic (e.g. merging the changes, prompting stated data)
+ * to resolve the conflict.
+ *
+ * @return string the column name that stores the lock version of a table row.
+ * If null is returned (default implemented), optimistic locking will not be supported.
+ */
+ public function optimisticLock()
+ {
+ return null;
+ }
+
+ /**
+ * Declares which DB operations should be performed within a transaction in different scenarios.
+ * The supported DB operations are: [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]],
+ * which correspond to the [[insert()]], [[update()]] and [[delete()]] methods, respectively.
+ * By default, these methods are NOT enclosed in a DB transaction.
+ *
+ * In some scenarios, to ensure data consistency, you may want to enclose some or all of them
+ * in transactions. You can do so by overriding this method and returning the operations
+ * that need to be transactional. For example,
+ *
+ * ~~~
+ * return array(
+ * 'admin' => self::OP_INSERT,
+ * 'api' => self::OP_INSERT | self::OP_UPDATE | self::OP_DELETE,
+ * // the above is equivalent to the following:
+ * // 'api' => self::OP_ALL,
+ *
+ * );
+ * ~~~
+ *
+ * The above declaration specifies that in the "admin" scenario, the insert operation ([[insert()]])
+ * should be done in a transaction; and in the "api" scenario, all the operations should be done
+ * in a transaction.
+ *
+ * @return array the declarations of transactional operations. The array keys are scenarios names,
+ * and the array values are the corresponding transaction operations.
+ */
+ public function transactions()
+ {
+ return array();
+ }
+
+ /**
+ * PHP getter magic method.
+ * This method is overridden so that attributes and related objects can be accessed like properties.
+ * @param string $name property name
+ * @return mixed property value
+ * @see getAttribute
+ */
+ public function __get($name)
+ {
+ if (isset($this->_attributes[$name]) || array_key_exists($name, $this->_attributes)) {
+ return $this->_attributes[$name];
+ } elseif (isset($this->getTableSchema()->columns[$name])) {
+ return null;
+ } else {
+ $t = strtolower($name);
+ if (isset($this->_related[$t]) || $this->_related !== null && array_key_exists($t, $this->_related)) {
+ return $this->_related[$t];
+ }
+ $value = parent::__get($name);
+ if ($value instanceof ActiveRelation) {
+ return $this->_related[$t] = $value->multiple ? $value->all() : $value->one();
+ } else {
+ return $value;
+ }
+ }
+ }
+
+ /**
+ * PHP setter magic method.
+ * This method is overridden so that AR attributes can be accessed like properties.
+ * @param string $name property name
+ * @param mixed $value property value
+ */
+ public function __set($name, $value)
+ {
+ if ($this->hasAttribute($name)) {
+ $this->_attributes[$name] = $value;
+ } else {
+ parent::__set($name, $value);
+ }
+ }
+
+ /**
+ * Checks if a property value is null.
+ * This method overrides the parent implementation by checking if the named attribute is null or not.
+ * @param string $name the property name or the event name
+ * @return boolean whether the property value is null
+ */
+ public function __isset($name)
+ {
+ try {
+ return $this->__get($name) !== null;
+ } catch (\Exception $e) {
+ return false;
+ }
+ }
+
+ /**
+ * Sets a component property to be null.
+ * This method overrides the parent implementation by clearing
+ * the specified attribute value.
+ * @param string $name the property name or the event name
+ */
+ public function __unset($name)
+ {
+ if (isset($this->getTableSchema()->columns[$name])) {
+ unset($this->_attributes[$name]);
+ } else {
+ $t = strtolower($name);
+ if (isset($this->_related[$t])) {
+ unset($this->_related[$t]);
+ } else {
+ parent::__unset($name);
+ }
+ }
+ }
+
+ /**
+ * Declares a `has-one` relation.
+ * The declaration is returned in terms of an [[ActiveRelation]] instance
+ * through which the related record can be queried and retrieved back.
+ *
+ * A `has-one` relation means that there is at most one related record matching
+ * the criteria set by this relation, e.g., a customer has one country.
+ *
+ * For example, to declare the `country` relation for `Customer` class, we can write
+ * the following code in the `Customer` class:
+ *
+ * ~~~
+ * public function getCountry()
+ * {
+ * return $this->hasOne('Country', array('id' => 'country_id'));
+ * }
+ * ~~~
+ *
+ * Note that in the above, the 'id' key in the `$link` parameter refers to an attribute name
+ * in the related class `Country`, while the 'country_id' value refers to an attribute name
+ * in the current AR class.
+ *
+ * Call methods declared in [[ActiveRelation]] to further customize the relation.
+ *
+ * @param string $class the class name of the related record
+ * @param array $link the primary-foreign key constraint. The keys of the array refer to
+ * the columns in the table associated with the `$class` model, while the values of the
+ * array refer to the corresponding columns in the table associated with this AR class.
+ * @return ActiveRelation the relation object.
+ */
+ public function hasOne($class, $link)
+ {
+ return new ActiveRelation(array(
+ 'modelClass' => $this->getNamespacedClass($class),
+ 'primaryModel' => $this,
+ 'link' => $link,
+ 'multiple' => false,
+ ));
+ }
+
+ /**
+ * Declares a `has-many` relation.
+ * The declaration is returned in terms of an [[ActiveRelation]] instance
+ * through which the related record can be queried and retrieved back.
+ *
+ * A `has-many` relation means that there are multiple related records matching
+ * the criteria set by this relation, e.g., a customer has many orders.
+ *
+ * For example, to declare the `orders` relation for `Customer` class, we can write
+ * the following code in the `Customer` class:
+ *
+ * ~~~
+ * public function getOrders()
+ * {
+ * return $this->hasMany('Order', array('customer_id' => 'id'));
+ * }
+ * ~~~
+ *
+ * Note that in the above, the 'customer_id' key in the `$link` parameter refers to
+ * an attribute name in the related class `Order`, while the 'id' value refers to
+ * an attribute name in the current AR class.
+ *
+ * @param string $class the class name of the related record
+ * @param array $link the primary-foreign key constraint. The keys of the array refer to
+ * the columns in the table associated with the `$class` model, while the values of the
+ * array refer to the corresponding columns in the table associated with this AR class.
+ * @return ActiveRelation the relation object.
+ */
+ public function hasMany($class, $link)
+ {
+ return new ActiveRelation(array(
+ 'modelClass' => $this->getNamespacedClass($class),
+ 'primaryModel' => $this,
+ 'link' => $link,
+ 'multiple' => true,
+ ));
+ }
+
+ /**
+ * Populates the named relation with the related records.
+ * Note that this method does not check if the relation exists or not.
+ * @param string $name the relation name (case-insensitive)
+ * @param ActiveRecord|array|null the related records to be populated into the relation.
+ */
+ public function populateRelation($name, $records)
+ {
+ $this->_related[strtolower($name)] = $records;
+ }
+
+ /**
+ * Returns the list of all attribute names of the model.
+ * The default implementation will return all column names of the table associated with this AR class.
+ * @return array list of attribute names.
+ */
+ public function attributes()
+ {
+ return array_keys($this->getTableSchema()->columns);
+ }
+
+ /**
+ * Returns the named attribute value.
+ * If this record is the result of a query and the attribute is not loaded,
+ * null will be returned.
+ * @param string $name the attribute name
+ * @return mixed the attribute value. Null if the attribute is not set or does not exist.
+ * @see hasAttribute
+ */
+ public function getAttribute($name)
+ {
+ return isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
+ }
+
+ /**
+ * Sets the named attribute value.
+ * @param string $name the attribute name
+ * @param mixed $value the attribute value.
+ * @throws InvalidParamException if the named attribute does not exist.
+ * @see hasAttribute
+ */
+ public function setAttribute($name, $value)
+ {
+ if ($this->hasAttribute($name)) {
+ $this->_attributes[$name] = $value;
+ } else {
+ throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
+ }
+ }
+
+ /**
+ * Returns a value indicating whether the model has an attribute with the specified name.
+ * @param string $name the name of the attribute
+ * @return boolean whether the model has an attribute with the specified name.
+ */
+ public function hasAttribute($name)
+ {
+ return isset($this->_attributes[$name]) || isset($this->getTableSchema()->columns[$name]);
+ }
+
+ /**
+ * Returns the old attribute values.
+ * @return array the old attribute values (name-value pairs)
+ */
+ public function getOldAttributes()
+ {
+ return $this->_oldAttributes === null ? array() : $this->_oldAttributes;
+ }
+
+ /**
+ * Sets the old attribute values.
+ * All existing old attribute values will be discarded.
+ * @param array $values old attribute values to be set.
+ */
+ public function setOldAttributes($values)
+ {
+ $this->_oldAttributes = $values;
+ }
+
+ /**
+ * Returns the old value of the named attribute.
+ * If this record is the result of a query and the attribute is not loaded,
+ * null will be returned.
+ * @param string $name the attribute name
+ * @return mixed the old attribute value. Null if the attribute is not loaded before
+ * or does not exist.
+ * @see hasAttribute
+ */
+ public function getOldAttribute($name)
+ {
+ return isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
+ }
+
+ /**
+ * Sets the old value of the named attribute.
+ * @param string $name the attribute name
+ * @param mixed $value the old attribute value.
+ * @throws InvalidParamException if the named attribute does not exist.
+ * @see hasAttribute
+ */
+ public function setOldAttribute($name, $value)
+ {
+ if (isset($this->_oldAttributes[$name]) || isset($this->getTableSchema()->columns[$name])) {
+ $this->_oldAttributes[$name] = $value;
+ } else {
+ throw new InvalidParamException(get_class($this) . ' has no attribute named "' . $name . '".');
+ }
+ }
+
+ /**
+ * Returns a value indicating whether the named attribute has been changed.
+ * @param string $name the name of the attribute
+ * @return boolean whether the attribute has been changed
+ */
+ public function isAttributeChanged($name)
+ {
+ if (isset($this->_attributes[$name], $this->_oldAttributes[$name])) {
+ return $this->_attributes[$name] !== $this->_oldAttributes[$name];
+ } else {
+ return isset($this->_attributes[$name]) || isset($this->_oldAttributes[$name]);
+ }
+ }
+
+ /**
+ * Returns the attribute values that have been modified since they are loaded or saved most recently.
+ * @param string[]|null $names the names of the attributes whose values may be returned if they are
+ * changed recently. If null, [[attributes()]] will be used.
+ * @return array the changed attribute values (name-value pairs)
+ */
+ public function getDirtyAttributes($names = null)
+ {
+ if ($names === null) {
+ $names = $this->attributes();
+ }
+ $names = array_flip($names);
+ $attributes = array();
+ if ($this->_oldAttributes === null) {
+ foreach ($this->_attributes as $name => $value) {
+ if (isset($names[$name])) {
+ $attributes[$name] = $value;
+ }
+ }
+ } else {
+ foreach ($this->_attributes as $name => $value) {
+ if (isset($names[$name]) && (!array_key_exists($name, $this->_oldAttributes) || $value !== $this->_oldAttributes[$name])) {
+ $attributes[$name] = $value;
+ }
+ }
+ }
+ return $attributes;
+ }
+
+ /**
+ * Saves the current record.
+ *
+ * This method will call [[insert()]] when [[isNewRecord]] is true, or [[update()]]
+ * when [[isNewRecord]] is false.
+ *
+ * For example, to save a customer record:
+ *
+ * ~~~
+ * $customer = new Customer; // or $customer = Customer::find($id);
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->save();
+ * ~~~
+ *
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be saved to database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return boolean whether the saving succeeds
+ */
+ public function save($runValidation = true, $attributes = null)
+ {
+ if ($this->getIsNewRecord()) {
+ return $this->insert($runValidation, $attributes);
+ } else {
+ return $this->update($runValidation, $attributes) !== false;
+ }
+ }
+
+ /**
+ * Inserts a row into the associated database table using the attribute values of this record.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
+ * fails, it will skip the rest of the steps;
+ * 2. call [[afterValidate()]] when `$runValidation` is true.
+ * 3. call [[beforeSave()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 4. insert the record into database. If this fails, it will skip the rest of the steps;
+ * 5. call [[afterSave()]];
+ *
+ * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
+ * [[EVENT_BEFORE_INSERT]], [[EVENT_AFTER_INSERT]] and [[EVENT_AFTER_VALIDATE]]
+ * will be raised by the corresponding methods.
+ *
+ * Only the [[changedAttributes|changed attribute values]] will be inserted into database.
+ *
+ * If the table's primary key is auto-incremental and is null during insertion,
+ * it will be populated with the actual value after insertion.
+ *
+ * For example, to insert a customer record:
+ *
+ * ~~~
+ * $customer = new Customer;
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->insert();
+ * ~~~
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted into the database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return boolean whether the attributes are valid and the record is inserted successfully.
+ * @throws \Exception in case insert failed.
+ */
+ public function insert($runValidation = true, $attributes = null)
+ {
+ if ($runValidation && !$this->validate($attributes)) {
+ return false;
+ }
+ $db = static::getDb();
+ if ($this->isTransactional(self::OP_INSERT) && $db->getTransaction() === null) {
+ $transaction = $db->beginTransaction();
+ try {
+ $result = $this->insertInternal($attributes);
+ if ($result === false) {
+ $transaction->rollback();
+ } else {
+ $transaction->commit();
+ }
+ } catch (\Exception $e) {
+ $transaction->rollback();
+ throw $e;
+ }
+ } else {
+ $result = $this->insertInternal($attributes);
+ }
+ return $result;
+ }
+
+ /**
+ * @see ActiveRecord::insert()
+ */
+ private function insertInternal($attributes = null)
+ {
+ if (!$this->beforeSave(true)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ foreach ($this->primaryKey() as $key) {
+ $values[$key] = isset($this->_attributes[$key]) ? $this->_attributes[$key] : null;
+ }
+ }
+ $db = static::getDb();
+ $command = $db->createCommand()->insert($this->tableName(), $values);
+ if (!$command->execute()) {
+ return false;
+ }
+ $table = $this->getTableSchema();
+ if ($table->sequenceName !== null) {
+ foreach ($table->primaryKey as $name) {
+ if (!isset($this->_attributes[$name])) {
+ $this->_oldAttributes[$name] = $this->_attributes[$name] = $db->getLastInsertID($table->sequenceName);
+ break;
+ }
+ }
+ }
+ foreach ($values as $name => $value) {
+ $this->_oldAttributes[$name] = $value;
+ }
+ $this->afterSave(true);
+ return true;
+ }
+
+ /**
+ * Saves the changes to this active record into the associated database table.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeValidate()]] when `$runValidation` is true. If validation
+ * fails, it will skip the rest of the steps;
+ * 2. call [[afterValidate()]] when `$runValidation` is true.
+ * 3. call [[beforeSave()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 4. save the record into database. If this fails, it will skip the rest of the steps;
+ * 5. call [[afterSave()]];
+ *
+ * In the above step 1, 2, 3 and 5, events [[EVENT_BEFORE_VALIDATE]],
+ * [[EVENT_BEFORE_UPDATE]], [[EVENT_AFTER_UPDATE]] and [[EVENT_AFTER_VALIDATE]]
+ * will be raised by the corresponding methods.
+ *
+ * Only the [[changedAttributes|changed attribute values]] will be saved into database.
+ *
+ * For example, to update a customer record:
+ *
+ * ~~~
+ * $customer = Customer::find($id);
+ * $customer->name = $name;
+ * $customer->email = $email;
+ * $customer->update();
+ * ~~~
+ *
+ * Note that it is possible the update does not affect any row in the table.
+ * In this case, this method will return 0. For this reason, you should use the following
+ * code to check if update() is successful or not:
+ *
+ * ~~~
+ * if ($this->update() !== false) {
+ * // update successful
+ * } else {
+ * // update failed
+ * }
+ * ~~~
+ *
+ * @param boolean $runValidation whether to perform validation before saving the record.
+ * If the validation fails, the record will not be inserted into the database.
+ * @param array $attributes list of attributes that need to be saved. Defaults to null,
+ * meaning all attributes that are loaded from DB will be saved.
+ * @return integer|boolean the number of rows affected, or false if validation fails
+ * or [[beforeSave()]] stops the updating process.
+ * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+ * being updated is outdated.
+ * @throws \Exception in case update failed.
+ */
+ public function update($runValidation = true, $attributes = null)
+ {
+ if ($runValidation && !$this->validate($attributes)) {
+ return false;
+ }
+ $db = static::getDb();
+ if ($this->isTransactional(self::OP_UPDATE) && $db->getTransaction() === null) {
+ $transaction = $db->beginTransaction();
+ try {
+ $result = $this->updateInternal($attributes);
+ if ($result === false) {
+ $transaction->rollback();
+ } else {
+ $transaction->commit();
+ }
+ } catch (\Exception $e) {
+ $transaction->rollback();
+ throw $e;
+ }
+ } else {
+ $result = $this->updateInternal($attributes);
+ }
+ return $result;
+ }
+
+ /**
+ * @see CActiveRecord::update()
+ * @throws StaleObjectException
+ */
+ private function updateInternal($attributes = null)
+ {
+ if (!$this->beforeSave(false)) {
+ return false;
+ }
+ $values = $this->getDirtyAttributes($attributes);
+ if (empty($values)) {
+ $this->afterSave(false);
+ return 0;
+ }
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ if (!isset($values[$lock])) {
+ $values[$lock] = $this->$lock + 1;
+ }
+ $condition[$lock] = $this->$lock;
+ }
+ // We do not check the return value of updateAll() because it's possible
+ // that the UPDATE statement doesn't change anything and thus returns 0.
+ $rows = $this->updateAll($values, $condition);
+
+ if ($lock !== null && !$rows) {
+ throw new StaleObjectException('The object being updated is outdated.');
+ }
+
+ foreach ($values as $name => $value) {
+ $this->_oldAttributes[$name] = $this->_attributes[$name];
+ }
+ $this->afterSave(false);
+ return $rows;
+ }
+
+ /**
+ * Updates one or several counter columns for the current AR object.
+ * Note that this method differs from [[updateAllCounters()]] in that it only
+ * saves counters for the current AR object.
+ *
+ * An example usage is as follows:
+ *
+ * ~~~
+ * $post = Post::find($id);
+ * $post->updateCounters(array('view_count' => 1));
+ * ~~~
+ *
+ * @param array $counters the counters to be updated (attribute name => increment value)
+ * Use negative values if you want to decrement the counters.
+ * @return boolean whether the saving is successful
+ * @see updateAllCounters()
+ */
+ public function updateCounters($counters)
+ {
+ if ($this->updateAllCounters($counters, $this->getOldPrimaryKey(true)) > 0) {
+ foreach ($counters as $name => $value) {
+ $this->_attributes[$name] += $value;
+ $this->_oldAttributes[$name] = $this->_attributes[$name];
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Deletes the table row corresponding to this active record.
+ *
+ * This method performs the following steps in order:
+ *
+ * 1. call [[beforeDelete()]]. If the method returns false, it will skip the
+ * rest of the steps;
+ * 2. delete the record from the database;
+ * 3. call [[afterDelete()]].
+ *
+ * In the above step 1 and 3, events named [[EVENT_BEFORE_DELETE]] and [[EVENT_AFTER_DELETE]]
+ * will be raised by the corresponding methods.
+ *
+ * @return integer|boolean the number of rows deleted, or false if the deletion is unsuccessful for some reason.
+ * Note that it is possible the number of rows deleted is 0, even though the deletion execution is successful.
+ * @throws StaleObjectException if [[optimisticLock|optimistic locking]] is enabled and the data
+ * being deleted is outdated.
+ * @throws \Exception in case delete failed.
+ */
+ public function delete()
+ {
+ $db = static::getDb();
+ $transaction = $this->isTransactional(self::OP_DELETE) && $db->getTransaction() === null ? $db->beginTransaction() : null;
+ try {
+ $result = false;
+ if ($this->beforeDelete()) {
+ // we do not check the return value of deleteAll() because it's possible
+ // the record is already deleted in the database and thus the method will return 0
+ $condition = $this->getOldPrimaryKey(true);
+ $lock = $this->optimisticLock();
+ if ($lock !== null) {
+ $condition[$lock] = $this->$lock;
+ }
+ $result = $this->deleteAll($condition);
+ if ($lock !== null && !$result) {
+ throw new StaleObjectException('The object being deleted is outdated.');
+ }
+ $this->_oldAttributes = null;
+ $this->afterDelete();
+ }
+ if ($transaction !== null) {
+ if ($result === false) {
+ $transaction->rollback();
+ } else {
+ $transaction->commit();
+ }
+ }
+ } catch (\Exception $e) {
+ if ($transaction !== null) {
+ $transaction->rollback();
+ }
+ throw $e;
+ }
+ return $result;
+ }
+
+ /**
+ * Returns a value indicating whether the current record is new.
+ * @return boolean whether the record is new and should be inserted when calling [[save()]].
+ */
+ public function getIsNewRecord()
+ {
+ return $this->_oldAttributes === null;
+ }
+
+ /**
+ * Sets the value indicating whether the record is new.
+ * @param boolean $value whether the record is new and should be inserted when calling [[save()]].
+ * @see getIsNewRecord
+ */
+ public function setIsNewRecord($value)
+ {
+ $this->_oldAttributes = $value ? null : $this->_attributes;
+ }
+
+ /**
+ * Initializes the object.
+ * This method is called at the end of the constructor.
+ * The default implementation will trigger an [[EVENT_INIT]] event.
+ * If you override this method, make sure you call the parent implementation at the end
+ * to ensure triggering of the event.
+ */
+ public function init()
+ {
+ parent::init();
+ $this->trigger(self::EVENT_INIT);
+ }
+
+ /**
+ * This method is called when the AR object is created and populated with the query result.
+ * The default implementation will trigger an [[EVENT_AFTER_FIND]] event.
+ * When overriding this method, make sure you call the parent implementation to ensure the
+ * event is triggered.
+ */
+ public function afterFind()
+ {
+ $this->trigger(self::EVENT_AFTER_FIND);
+ }
+
+ /**
+ * This method is called at the beginning of inserting or updating a record.
+ * The default implementation will trigger an [[EVENT_BEFORE_INSERT]] event when `$insert` is true,
+ * or an [[EVENT_BEFORE_UPDATE]] event if `$insert` is false.
+ * When overriding this method, make sure you call the parent implementation like the following:
+ *
+ * ~~~
+ * public function beforeSave($insert)
+ * {
+ * if (parent::beforeSave($insert)) {
+ * // ...custom code here...
+ * return true;
+ * } else {
+ * return false;
+ * }
+ * }
+ * ~~~
+ *
+ * @param boolean $insert whether this method called while inserting a record.
+ * If false, it means the method is called while updating a record.
+ * @return boolean whether the insertion or updating should continue.
+ * If false, the insertion or updating will be cancelled.
+ */
+ public function beforeSave($insert)
+ {
+ $event = new ModelEvent;
+ $this->trigger($insert ? self::EVENT_BEFORE_INSERT : self::EVENT_BEFORE_UPDATE, $event);
+ return $event->isValid;
+ }
+
+ /**
+ * This method is called at the end of inserting or updating a record.
+ * The default implementation will trigger an [[EVENT_AFTER_INSERT]] event when `$insert` is true,
+ * or an [[EVENT_AFTER_UPDATE]] event if `$insert` is false.
+ * When overriding this method, make sure you call the parent implementation so that
+ * the event is triggered.
+ * @param boolean $insert whether this method called while inserting a record.
+ * If false, it means the method is called while updating a record.
+ */
+ public function afterSave($insert)
+ {
+ $this->trigger($insert ? self::EVENT_AFTER_INSERT : self::EVENT_AFTER_UPDATE);
+ }
+
+ /**
+ * This method is invoked before deleting a record.
+ * The default implementation raises the [[EVENT_BEFORE_DELETE]] event.
+ * When overriding this method, make sure you call the parent implementation like the following:
+ *
+ * ~~~
+ * public function beforeDelete()
+ * {
+ * if (parent::beforeDelete()) {
+ * // ...custom code here...
+ * return true;
+ * } else {
+ * return false;
+ * }
+ * }
+ * ~~~
+ *
+ * @return boolean whether the record should be deleted. Defaults to true.
+ */
+ public function beforeDelete()
+ {
+ $event = new ModelEvent;
+ $this->trigger(self::EVENT_BEFORE_DELETE, $event);
+ return $event->isValid;
+ }
+
+ /**
+ * This method is invoked after deleting a record.
+ * The default implementation raises the [[EVENT_AFTER_DELETE]] event.
+ * You may override this method to do postprocessing after the record is deleted.
+ * Make sure you call the parent implementation so that the event is raised properly.
+ */
+ public function afterDelete()
+ {
+ $this->trigger(self::EVENT_AFTER_DELETE);
+ }
+
+ /**
+ * Repopulates this active record with the latest data.
+ * @param array $attributes
+ * @return boolean whether the row still exists in the database. If true, the latest data
+ * will be populated to this active record.
+ */
+ public function refresh($attributes = null)
+ {
+ $record = $this->find($this->getPrimaryKey(true));
+ if ($record === null) {
+ return false;
+ }
+ if ($attributes === null) {
+ foreach ($this->attributes() as $name) {
+ $this->_attributes[$name] = $record->_attributes[$name];
+ }
+ $this->_oldAttributes = $this->_attributes;
+ } else {
+ foreach ($attributes as $name) {
+ $this->_oldAttributes[$name] = $this->_attributes[$name] = $record->_attributes[$name];
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a value indicating whether the given active record is the same as the current one.
+ * The comparison is made by comparing the table names and the primary key values of the two active records.
+ * @param ActiveRecord $record record to compare to
+ * @return boolean whether the two active records refer to the same row in the same database table.
+ */
+ public function equals($record)
+ {
+ return $this->tableName() === $record->tableName() && $this->getPrimaryKey() === $record->getPrimaryKey();
+ }
+
+ /**
+ * Returns the primary key value(s).
+ * @param boolean $asArray whether to return the primary key value as an array. If true,
+ * the return value will be an array with column names as keys and column values as values.
+ * Note that for composite primary keys, an array will always be returned regardless of this parameter value.
+ * @return mixed the primary key value. An array (column name => column value) is returned if the primary key
+ * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+ * the key value is null).
+ */
+ public function getPrimaryKey($asArray = false)
+ {
+ $keys = $this->primaryKey();
+ if (count($keys) === 1 && !$asArray) {
+ return isset($this->_attributes[$keys[0]]) ? $this->_attributes[$keys[0]] : null;
+ } else {
+ $values = array();
+ foreach ($keys as $name) {
+ $values[$name] = isset($this->_attributes[$name]) ? $this->_attributes[$name] : null;
+ }
+ return $values;
+ }
+ }
+
+ /**
+ * Returns the old primary key value(s).
+ * This refers to the primary key value that is populated into the record
+ * after executing a find method (e.g. find(), findAll()).
+ * The value remains unchanged even if the primary key attribute is manually assigned with a different value.
+ * @param boolean $asArray whether to return the primary key value as an array. If true,
+ * the return value will be an array with column name as key and column value as value.
+ * If this is false (default), a scalar value will be returned for non-composite primary key.
+ * @return mixed the old primary key value. An array (column name => column value) is returned if the primary key
+ * is composite or `$asArray` is true. A string is returned otherwise (null will be returned if
+ * the key value is null).
+ */
+ public function getOldPrimaryKey($asArray = false)
+ {
+ $keys = $this->primaryKey();
+ if (count($keys) === 1 && !$asArray) {
+ return isset($this->_oldAttributes[$keys[0]]) ? $this->_oldAttributes[$keys[0]] : null;
+ } else {
+ $values = array();
+ foreach ($keys as $name) {
+ $values[$name] = isset($this->_oldAttributes[$name]) ? $this->_oldAttributes[$name] : null;
+ }
+ return $values;
+ }
+ }
+
+ /**
+ * Creates an active record object using a row of data.
+ * This method is called by [[ActiveQuery]] to populate the query results
+ * into Active Records. It is not meant to be used to create new records.
+ * @param array $row attribute values (name => value)
+ * @return ActiveRecord the newly created active record.
+ */
+ public static function create($row)
+ {
+ $record = static::instantiate($row);
+ $columns = static::getTableSchema()->columns;
+ foreach ($row as $name => $value) {
+ if (isset($columns[$name])) {
+ $record->_attributes[$name] = $value;
+ } else {
+ $record->$name = $value;
+ }
+ }
+ $record->_oldAttributes = $record->_attributes;
+ $record->afterFind();
+ return $record;
+ }
+
+ /**
+ * Creates an active record instance.
+ * This method is called by [[create()]].
+ * You may override this method if the instance being created
+ * depends on the row data to be populated into the record.
+ * For example, by creating a record based on the value of a column,
+ * you may implement the so-called single-table inheritance mapping.
+ * @param array $row row data to be populated into the record.
+ * @return ActiveRecord the newly created active record
+ */
+ public static function instantiate($row)
+ {
+ return new static;
+ }
+
+ /**
+ * Returns whether there is an element at the specified offset.
+ * This method is required by the interface ArrayAccess.
+ * @param mixed $offset the offset to check on
+ * @return boolean whether there is an element at the specified offset.
+ */
+ public function offsetExists($offset)
+ {
+ return $this->__isset($offset);
+ }
+
+ /**
+ * Returns the relation object with the specified name.
+ * A relation is defined by a getter method which returns an [[ActiveRelation]] object.
+ * It can be declared in either the Active Record class itself or one of its behaviors.
+ * @param string $name the relation name
+ * @return ActiveRelation the relation object
+ * @throws InvalidParamException if the named relation does not exist.
+ */
+ public function getRelation($name)
+ {
+ $getter = 'get' . $name;
+ try {
+ $relation = $this->$getter();
+ if ($relation instanceof ActiveRelation) {
+ return $relation;
+ }
+ } catch (UnknownMethodException $e) {
+ throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
+ }
+ }
+
+ /**
+ * Establishes the relationship between two models.
+ *
+ * The relationship is established by setting the foreign key value(s) in one model
+ * to be the corresponding primary key value(s) in the other model.
+ * The model with the foreign key will be saved into database without performing validation.
+ *
+ * If the relationship involves a pivot table, a new row will be inserted into the
+ * pivot table which contains the primary key values from both models.
+ *
+ * Note that this method requires that the primary key value is not null.
+ *
+ * @param string $name the name of the relationship
+ * @param ActiveRecord $model the model to be linked with the current one.
+ * @param array $extraColumns additional column values to be saved into the pivot table.
+ * This parameter is only meaningful for a relationship involving a pivot table
+ * (i.e., a relation set with `[[ActiveRelation::via()]]` or `[[ActiveRelation::viaTable()]]`.)
+ * @throws InvalidCallException if the method is unable to link two models.
+ */
+ public function link($name, $model, $extraColumns = array())
+ {
+ $relation = $this->getRelation($name);
+
+ if ($relation->via !== null) {
+ if ($this->getIsNewRecord() || $model->getIsNewRecord()) {
+ throw new InvalidCallException('Unable to link models: both models must NOT be newly created.');
+ }
+ if (is_array($relation->via)) {
+ /** @var $viaRelation ActiveRelation */
+ list($viaName, $viaRelation) = $relation->via;
+ /** @var $viaClass ActiveRecord */
+ $viaClass = $viaRelation->modelClass;
+ $viaTable = $viaClass::tableName();
+ // unset $viaName so that it can be reloaded to reflect the change
+ unset($this->_related[strtolower($viaName)]);
+ } else {
+ $viaRelation = $relation->via;
+ $viaTable = reset($relation->via->from);
+ }
+ $columns = array();
+ foreach ($viaRelation->link as $a => $b) {
+ $columns[$a] = $this->$b;
+ }
+ foreach ($relation->link as $a => $b) {
+ $columns[$b] = $model->$a;
+ }
+ foreach ($extraColumns as $k => $v) {
+ $columns[$k] = $v;
+ }
+ static::getDb()->createCommand()
+ ->insert($viaTable, $columns)->execute();
+ } else {
+ $p1 = $model->isPrimaryKey(array_keys($relation->link));
+ $p2 = $this->isPrimaryKey(array_values($relation->link));
+ if ($p1 && $p2) {
+ if ($this->getIsNewRecord() && $model->getIsNewRecord()) {
+ throw new InvalidCallException('Unable to link models: both models are newly created.');
+ } elseif ($this->getIsNewRecord()) {
+ $this->bindModels(array_flip($relation->link), $this, $model);
+ } else {
+ $this->bindModels($relation->link, $model, $this);
+ }
+ } elseif ($p1) {
+ $this->bindModels(array_flip($relation->link), $this, $model);
+ } elseif ($p2) {
+ $this->bindModels($relation->link, $model, $this);
+ } else {
+ throw new InvalidCallException('Unable to link models: the link does not involve any primary key.');
+ }
+ }
+
+ // update lazily loaded related objects
+ if (!$relation->multiple) {
+ $this->_related[$name] = $model;
+ } elseif (isset($this->_related[$name])) {
+ if ($relation->indexBy !== null) {
+ $indexBy = $relation->indexBy;
+ $this->_related[$name][$model->$indexBy] = $model;
+ } else {
+ $this->_related[$name][] = $model;
+ }
+ }
+ }
+
+ /**
+ * Destroys the relationship between two models.
+ *
+ * The model with the foreign key of the relationship will be deleted if `$delete` is true.
+ * Otherwise, the foreign key will be set null and the model will be saved without validation.
+ *
+ * @param string $name the name of the relationship.
+ * @param ActiveRecord $model the model to be unlinked from the current one.
+ * @param boolean $delete whether to delete the model that contains the foreign key.
+ * If false, the model's foreign key will be set null and saved.
+ * If true, the model containing the foreign key will be deleted.
+ * @throws InvalidCallException if the models cannot be unlinked
+ */
+ public function unlink($name, $model, $delete = false)
+ {
+ $relation = $this->getRelation($name);
+
+ if ($relation->via !== null) {
+ if (is_array($relation->via)) {
+ /** @var $viaRelation ActiveRelation */
+ list($viaName, $viaRelation) = $relation->via;
+ /** @var $viaClass ActiveRecord */
+ $viaClass = $viaRelation->modelClass;
+ $viaTable = $viaClass::tableName();
+ unset($this->_related[strtolower($viaName)]);
+ } else {
+ $viaRelation = $relation->via;
+ $viaTable = reset($relation->via->from);
+ }
+ $columns = array();
+ foreach ($viaRelation->link as $a => $b) {
+ $columns[$a] = $this->$b;
+ }
+ foreach ($relation->link as $a => $b) {
+ $columns[$b] = $model->$a;
+ }
+ $command = static::getDb()->createCommand();
+ if ($delete) {
+ $command->delete($viaTable, $columns)->execute();
+ } else {
+ $nulls = array();
+ foreach (array_keys($columns) as $a) {
+ $nulls[$a] = null;
+ }
+ $command->update($viaTable, $nulls, $columns)->execute();
+ }
+ } else {
+ $p1 = $model->isPrimaryKey(array_keys($relation->link));
+ $p2 = $this->isPrimaryKey(array_values($relation->link));
+ if ($p1 && $p2 || $p2) {
+ foreach ($relation->link as $a => $b) {
+ $model->$a = null;
+ }
+ $delete ? $model->delete() : $model->save(false);
+ } elseif ($p1) {
+ foreach ($relation->link as $b) {
+ $this->$b = null;
+ }
+ $delete ? $this->delete() : $this->save(false);
+ } else {
+ throw new InvalidCallException('Unable to unlink models: the link does not involve any primary key.');
+ }
+ }
+
+ if (!$relation->multiple) {
+ unset($this->_related[$name]);
+ } elseif (isset($this->_related[$name])) {
+ /** @var $b ActiveRecord */
+ foreach ($this->_related[$name] as $a => $b) {
+ if ($model->getPrimaryKey() == $b->getPrimaryKey()) {
+ unset($this->_related[$name][$a]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Changes the given class name into a namespaced one.
+ * If the given class name is already namespaced, no change will be made.
+ * Otherwise, the class name will be changed to use the same namespace as
+ * the current AR class.
+ * @param string $class the class name to be namespaced
+ * @return string the namespaced class name
+ */
+ protected static function getNamespacedClass($class)
+ {
+ if (strpos($class, '\\') === false) {
+ $reflector = new \ReflectionClass(static::className());
+ return $reflector->getNamespaceName() . '\\' . $class;
+ } else {
+ return $class;
+ }
+ }
+
+ /**
+ * @param array $link
+ * @param ActiveRecord $foreignModel
+ * @param ActiveRecord $primaryModel
+ * @throws InvalidCallException
+ */
+ private function bindModels($link, $foreignModel, $primaryModel)
+ {
+ foreach ($link as $fk => $pk) {
+ $value = $primaryModel->$pk;
+ if ($value === null) {
+ throw new InvalidCallException('Unable to link models: the primary key of ' . get_class($primaryModel) . ' is null.');
+ }
+ $foreignModel->$fk = $value;
+ }
+ $foreignModel->save(false);
+ }
+
+ /**
+ * @param array $keys
+ * @return boolean
+ */
+ private function isPrimaryKey($keys)
+ {
+ $pks = $this->primaryKey();
+ foreach ($keys as $key) {
+ if (!in_array($key, $pks, true)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Returns a value indicating whether the specified operation is transactional in the current [[scenario]].
+ * @param integer $operation the operation to check. Possible values are [[OP_INSERT]], [[OP_UPDATE]] and [[OP_DELETE]].
+ * @return boolean whether the specified operation is transactional in the current [[scenario]].
+ */
+ public function isTransactional($operation)
+ {
+ $scenario = $this->getScenario();
+ $transactions = $this->transactions();
+ return isset($transactions[$scenario]) && ($transactions[$scenario] & $operation);
+ }
+}
diff --git a/framework/yii/db/ActiveRelation.php b/framework/yii/db/ActiveRelation.php
new file mode 100644
index 0000000..c1c8b2d
--- /dev/null
+++ b/framework/yii/db/ActiveRelation.php
@@ -0,0 +1,315 @@
+
+ * @link http://www.yiiframework.com/
+ * @copyright Copyright (c) 2008 Yii Software LLC
+ * @license http://www.yiiframework.com/license/
+ */
+
+namespace yii\db;
+
+use yii\base\InvalidConfigException;
+
+/**
+ * ActiveRelation represents a relation between two Active Record classes.
+ *
+ * ActiveRelation instances are usually created by calling [[ActiveRecord::hasOne()]] and
+ * [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
+ * a getter method which calls one of the above methods and returns the created ActiveRelation object.
+ *
+ * A relation is specified by [[link]] which represents the association between columns
+ * of different tables; and the multiplicity of the relation is indicated by [[multiple]].
+ *
+ * If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class ActiveRelation extends ActiveQuery
+{
+ /**
+ * @var boolean whether this relation should populate all query results into AR instances.
+ * If false, only the first row of the results will be retrieved.
+ */
+ public $multiple;
+ /**
+ * @var ActiveRecord the primary model that this relation is associated with.
+ * This is used only in lazy loading with dynamic query options.
+ */
+ public $primaryModel;
+ /**
+ * @var array the columns of the primary and foreign tables that establish the relation.
+ * The array keys must be columns of the table for this relation, and the array values
+ * must be the corresponding columns from the primary table.
+ * Do not prefix or quote the column names as they will be done automatically by Yii.
+ */
+ public $link;
+ /**
+ * @var array|ActiveRelation the query associated with the pivot table. Please call [[via()]]
+ * or [[viaTable()]] to set this property instead of directly setting it.
+ */
+ public $via;
+
+ /**
+ * Clones internal objects.
+ */
+ public function __clone()
+ {
+ if (is_object($this->via)) {
+ // make a clone of "via" object so that the same query object can be reused multiple times
+ $this->via = clone $this->via;
+ }
+ }
+
+ /**
+ * Specifies the relation associated with the pivot table.
+ * @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
+ * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
+ * Its signature should be `function($query)`, where `$query` is the query to be customized.
+ * @return static the relation object itself.
+ */
+ public function via($relationName, $callable = null)
+ {
+ $relation = $this->primaryModel->getRelation($relationName);
+ $this->via = array($relationName, $relation);
+ if ($callable !== null) {
+ call_user_func($callable, $relation);
+ }
+ return $this;
+ }
+
+ /**
+ * Specifies the pivot table.
+ * @param string $tableName the name of the pivot table.
+ * @param array $link the link between the pivot table and the table associated with [[primaryModel]].
+ * The keys of the array represent the columns in the pivot table, and the values represent the columns
+ * in the [[primaryModel]] table.
+ * @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
+ * Its signature should be `function($query)`, where `$query` is the query to be customized.
+ * @return static
+ */
+ public function viaTable($tableName, $link, $callable = null)
+ {
+ $relation = new ActiveRelation(array(
+ 'modelClass' => get_class($this->primaryModel),
+ 'from' => array($tableName),
+ 'link' => $link,
+ 'multiple' => true,
+ 'asArray' => true,
+ ));
+ $this->via = $relation;
+ if ($callable !== null) {
+ call_user_func($callable, $relation);
+ }
+ return $this;
+ }
+
+ /**
+ * Creates a DB command that can be used to execute this query.
+ * @param Connection $db the DB connection used to create the DB command.
+ * If null, the DB connection returned by [[modelClass]] will be used.
+ * @return Command the created DB command instance.
+ */
+ public function createCommand($db = null)
+ {
+ if ($this->primaryModel !== null) {
+ // lazy loading
+ if ($this->via instanceof self) {
+ // via pivot table
+ $viaModels = $this->via->findPivotRows(array($this->primaryModel));
+ $this->filterByModels($viaModels);
+ } elseif (is_array($this->via)) {
+ // via relation
+ /** @var $viaQuery ActiveRelation */
+ list($viaName, $viaQuery) = $this->via;
+ if ($viaQuery->multiple) {
+ $viaModels = $viaQuery->all();
+ $this->primaryModel->populateRelation($viaName, $viaModels);
+ } else {
+ $model = $viaQuery->one();
+ $this->primaryModel->populateRelation($viaName, $model);
+ $viaModels = $model === null ? array() : array($model);
+ }
+ $this->filterByModels($viaModels);
+ } else {
+ $this->filterByModels(array($this->primaryModel));
+ }
+ }
+ return parent::createCommand($db);
+ }
+
+ /**
+ * Finds the related records and populates them into the primary models.
+ * This method is internally used by [[ActiveQuery]]. Do not call it directly.
+ * @param string $name the relation name
+ * @param array $primaryModels primary models
+ * @return array the related models
+ * @throws InvalidConfigException
+ */
+ public function findWith($name, &$primaryModels)
+ {
+ if (!is_array($this->link)) {
+ throw new InvalidConfigException('Invalid link: it must be an array of key-value pairs.');
+ }
+
+ if ($this->via instanceof self) {
+ // via pivot table
+ /** @var $viaQuery ActiveRelation */
+ $viaQuery = $this->via;
+ $viaModels = $viaQuery->findPivotRows($primaryModels);
+ $this->filterByModels($viaModels);
+ } elseif (is_array($this->via)) {
+ // via relation
+ /** @var $viaQuery ActiveRelation */
+ list($viaName, $viaQuery) = $this->via;
+ $viaQuery->primaryModel = null;
+ $viaModels = $viaQuery->findWith($viaName, $primaryModels);
+ $this->filterByModels($viaModels);
+ } else {
+ $this->filterByModels($primaryModels);
+ }
+
+ if (count($primaryModels) === 1 && !$this->multiple) {
+ $model = $this->one();
+ foreach ($primaryModels as $i => $primaryModel) {
+ if ($primaryModel instanceof ActiveRecord) {
+ $primaryModel->populateRelation($name, $model);
+ } else {
+ $primaryModels[$i][$name] = $model;
+ }
+ }
+ return array($model);
+ } else {
+ $models = $this->all();
+ if (isset($viaModels, $viaQuery)) {
+ $buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link);
+ } else {
+ $buckets = $this->buildBuckets($models, $this->link);
+ }
+
+ $link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link);
+ foreach ($primaryModels as $i => $primaryModel) {
+ $key = $this->getModelKey($primaryModel, $link);
+ $value = isset($buckets[$key]) ? $buckets[$key] : ($this->multiple ? array() : null);
+ if ($primaryModel instanceof ActiveRecord) {
+ $primaryModel->populateRelation($name, $value);
+ } else {
+ $primaryModels[$i][$name] = $value;
+ }
+ }
+ return $models;
+ }
+ }
+
+ /**
+ * @param array $models
+ * @param array $link
+ * @param array $viaModels
+ * @param array $viaLink
+ * @return array
+ */
+ private function buildBuckets($models, $link, $viaModels = null, $viaLink = null)
+ {
+ $buckets = array();
+ $linkKeys = array_keys($link);
+ foreach ($models as $i => $model) {
+ $key = $this->getModelKey($model, $linkKeys);
+ if ($this->indexBy !== null) {
+ $buckets[$key][$i] = $model;
+ } else {
+ $buckets[$key][] = $model;
+ }
+ }
+
+ if ($viaModels !== null) {
+ $viaBuckets = array();
+ $viaLinkKeys = array_keys($viaLink);
+ $linkValues = array_values($link);
+ foreach ($viaModels as $viaModel) {
+ $key1 = $this->getModelKey($viaModel, $viaLinkKeys);
+ $key2 = $this->getModelKey($viaModel, $linkValues);
+ if (isset($buckets[$key2])) {
+ foreach ($buckets[$key2] as $i => $bucket) {
+ if ($this->indexBy !== null) {
+ $viaBuckets[$key1][$i] = $bucket;
+ } else {
+ $viaBuckets[$key1][] = $bucket;
+ }
+ }
+ }
+ }
+ $buckets = $viaBuckets;
+ }
+
+ if (!$this->multiple) {
+ foreach ($buckets as $i => $bucket) {
+ $buckets[$i] = reset($bucket);
+ }
+ }
+ return $buckets;
+ }
+
+ /**
+ * @param ActiveRecord|array $model
+ * @param array $attributes
+ * @return string
+ */
+ private function getModelKey($model, $attributes)
+ {
+ if (count($attributes) > 1) {
+ $key = array();
+ foreach ($attributes as $attribute) {
+ $key[] = $model[$attribute];
+ }
+ return serialize($key);
+ } else {
+ $attribute = reset($attributes);
+ return $model[$attribute];
+ }
+ }
+
+ /**
+ * @param array $models
+ */
+ private function filterByModels($models)
+ {
+ $attributes = array_keys($this->link);
+ $values = array();
+ if (count($attributes) === 1) {
+ // single key
+ $attribute = reset($this->link);
+ foreach ($models as $model) {
+ if (($value = $model[$attribute]) !== null) {
+ $values[] = $value;
+ }
+ }
+ } else {
+ // composite keys
+ foreach ($models as $model) {
+ $v = array();
+ foreach ($this->link as $attribute => $link) {
+ $v[$attribute] = $model[$link];
+ }
+ $values[] = $v;
+ }
+ }
+ $this->andWhere(array('in', $attributes, array_unique($values, SORT_REGULAR)));
+ }
+
+ /**
+ * @param ActiveRecord[] $primaryModels
+ * @return array
+ */
+ private function findPivotRows($primaryModels)
+ {
+ if (empty($primaryModels)) {
+ return array();
+ }
+ $this->filterByModels($primaryModels);
+ /** @var $primaryModel ActiveRecord */
+ $primaryModel = reset($primaryModels);
+ $db = $primaryModel->getDb();
+ list ($sql, $params) = $db->getQueryBuilder()->build($this);
+ return $db->createCommand($sql, $params)->queryAll();
+ }
+}
diff --git a/framework/yii/db/ColumnSchema.php b/framework/yii/db/ColumnSchema.php
new file mode 100644
index 0000000..cd2d9fa
--- /dev/null
+++ b/framework/yii/db/ColumnSchema.php
@@ -0,0 +1,106 @@
+
+ * @since 2.0
+ */
+class ColumnSchema extends Object
+{
+ /**
+ * @var string name of this column (without quotes).
+ */
+ public $name;
+ /**
+ * @var boolean whether this column can be null.
+ */
+ public $allowNull;
+ /**
+ * @var string abstract type of this column. Possible abstract types include:
+ * string, text, boolean, smallint, integer, bigint, float, decimal, datetime,
+ * timestamp, time, date, binary, and money.
+ */
+ public $type;
+ /**
+ * @var string the PHP type of this column. Possible PHP types include:
+ * string, boolean, integer, double.
+ */
+ public $phpType;
+ /**
+ * @var string the DB type of this column. Possible DB types vary according to the type of DBMS.
+ */
+ public $dbType;
+ /**
+ * @var mixed default value of this column
+ */
+ public $defaultValue;
+ /**
+ * @var array enumerable values. This is set only if the column is declared to be an enumerable type.
+ */
+ public $enumValues;
+ /**
+ * @var integer display size of the column.
+ */
+ public $size;
+ /**
+ * @var integer precision of the column data, if it is numeric.
+ */
+ public $precision;
+ /**
+ * @var integer scale of the column data, if it is numeric.
+ */
+ public $scale;
+ /**
+ * @var boolean whether this column is a primary key
+ */
+ public $isPrimaryKey;
+ /**
+ * @var boolean whether this column is auto-incremental
+ */
+ public $autoIncrement = false;
+ /**
+ * @var boolean whether this column is unsigned. This is only meaningful
+ * when [[type]] is `smallint`, `integer` or `bigint`.
+ */
+ public $unsigned;
+ /**
+ * @var string comment of this column. Not all DBMS support this.
+ */
+ public $comment;
+
+
+ /**
+ * Converts the input value according to [[phpType]].
+ * If the value is null or an [[Expression]], it will not be converted.
+ * @param mixed $value input value
+ * @return mixed converted value
+ */
+ public function typecast($value)
+ {
+ if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression) {
+ return $value;
+ }
+ if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING) {
+ return null;
+ }
+ switch ($this->phpType) {
+ case 'string':
+ return (string)$value;
+ case 'integer':
+ return (integer)$value;
+ case 'boolean':
+ return (boolean)$value;
+ }
+ return $value;
+ }
+}
diff --git a/framework/yii/db/Command.php b/framework/yii/db/Command.php
new file mode 100644
index 0000000..bd18e1f
--- /dev/null
+++ b/framework/yii/db/Command.php
@@ -0,0 +1,746 @@
+createCommand('SELECT * FROM tbl_user')->queryAll();
+ * ~~~
+ *
+ * Command supports SQL statement preparation and parameter binding.
+ * Call [[bindValue()]] to bind a value to a SQL parameter;
+ * Call [[bindParam()]] to bind a PHP variable to a SQL parameter.
+ * When binding a parameter, the SQL statement is automatically prepared.
+ * You may also call [[prepare()]] explicitly to prepare a SQL statement.
+ *
+ * Command also supports building SQL statements by providing methods such as [[insert()]],
+ * [[update()]], etc. For example,
+ *
+ * ~~~
+ * $connection->createCommand()->insert('tbl_user', array(
+ * 'name' => 'Sam',
+ * 'age' => 30,
+ * ))->execute();
+ * ~~~
+ *
+ * To build SELECT SQL statements, please use [[QueryBuilder]] instead.
+ *
+ * @property string $rawSql The raw SQL with parameter values inserted into the corresponding placeholders in
+ * [[sql]]. This property is read-only.
+ * @property string $sql The SQL statement to be executed.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Command extends \yii\base\Component
+{
+ /**
+ * @var Connection the DB connection that this command is associated with
+ */
+ public $db;
+ /**
+ * @var \PDOStatement the PDOStatement object that this command is associated with
+ */
+ public $pdoStatement;
+ /**
+ * @var integer the default fetch mode for this command.
+ * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
+ */
+ public $fetchMode = \PDO::FETCH_ASSOC;
+ /**
+ * @var string the SQL statement that this command represents
+ */
+ private $_sql;
+ /**
+ * @var array the parameter log information (name => value)
+ */
+ private $_params = array();
+
+ /**
+ * Returns the SQL statement for this command.
+ * @return string the SQL statement to be executed
+ */
+ public function getSql()
+ {
+ return $this->_sql;
+ }
+
+ /**
+ * Specifies the SQL statement to be executed.
+ * The previous SQL execution (if any) will be cancelled, and [[params]] will be cleared as well.
+ * @param string $sql the SQL statement to be set.
+ * @return static this command instance
+ */
+ public function setSql($sql)
+ {
+ if ($sql !== $this->_sql) {
+ $this->cancel();
+ $this->_sql = $this->db->quoteSql($sql);
+ $this->_params = array();
+ }
+ return $this;
+ }
+
+ /**
+ * Returns the raw SQL by inserting parameter values into the corresponding placeholders in [[sql]].
+ * Note that the return value of this method should mainly be used for logging purpose.
+ * It is likely that this method returns an invalid SQL due to improper replacement of parameter placeholders.
+ * @return string the raw SQL with parameter values inserted into the corresponding placeholders in [[sql]].
+ */
+ public function getRawSql()
+ {
+ if (empty($this->_params)) {
+ return $this->_sql;
+ } else {
+ $params = array();
+ foreach ($this->_params as $name => $value) {
+ if (is_string($value)) {
+ $params[$name] = $this->db->quoteValue($value);
+ } elseif ($value === null) {
+ $params[$name] = 'NULL';
+ } else {
+ $params[$name] = $value;
+ }
+ }
+ if (isset($params[1])) {
+ $sql = '';
+ foreach (explode('?', $this->_sql) as $i => $part) {
+ $sql .= (isset($params[$i]) ? $params[$i] : '') . $part;
+ }
+ return $sql;
+ } else {
+ return strtr($this->_sql, $params);
+ }
+ }
+ }
+
+ /**
+ * Prepares the SQL statement to be executed.
+ * For complex SQL statement that is to be executed multiple times,
+ * this may improve performance.
+ * For SQL statement with binding parameters, this method is invoked
+ * automatically.
+ * @throws Exception if there is any DB error
+ */
+ public function prepare()
+ {
+ if ($this->pdoStatement == null) {
+ $sql = $this->getSql();
+ try {
+ $this->pdoStatement = $this->db->pdo->prepare($sql);
+ } catch (\Exception $e) {
+ $message = $e->getMessage() . "\nFailed to prepare SQL: $sql";
+ $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
+ throw new Exception($message, $errorInfo, (int)$e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Cancels the execution of the SQL statement.
+ * This method mainly sets [[pdoStatement]] to be null.
+ */
+ public function cancel()
+ {
+ $this->pdoStatement = null;
+ }
+
+ /**
+ * Binds a parameter to the SQL statement to be executed.
+ * @param string|integer $name parameter identifier. For a prepared statement
+ * using named placeholders, this will be a parameter name of
+ * the form `:name`. For a prepared statement using question mark
+ * placeholders, this will be the 1-indexed position of the parameter.
+ * @param mixed $value Name of the PHP variable to bind to the SQL statement parameter
+ * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
+ * @param integer $length length of the data type
+ * @param mixed $driverOptions the driver-specific options
+ * @return static the current command being executed
+ * @see http://www.php.net/manual/en/function.PDOStatement-bindParam.php
+ */
+ public function bindParam($name, &$value, $dataType = null, $length = null, $driverOptions = null)
+ {
+ $this->prepare();
+ if ($dataType === null) {
+ $dataType = $this->db->getSchema()->getPdoType($value);
+ }
+ if ($length === null) {
+ $this->pdoStatement->bindParam($name, $value, $dataType);
+ } elseif ($driverOptions === null) {
+ $this->pdoStatement->bindParam($name, $value, $dataType, $length);
+ } else {
+ $this->pdoStatement->bindParam($name, $value, $dataType, $length, $driverOptions);
+ }
+ $this->_params[$name] =& $value;
+ return $this;
+ }
+
+ /**
+ * Binds a value to a parameter.
+ * @param string|integer $name Parameter identifier. For a prepared statement
+ * using named placeholders, this will be a parameter name of
+ * the form `:name`. For a prepared statement using question mark
+ * placeholders, this will be the 1-indexed position of the parameter.
+ * @param mixed $value The value to bind to the parameter
+ * @param integer $dataType SQL data type of the parameter. If null, the type is determined by the PHP type of the value.
+ * @return static the current command being executed
+ * @see http://www.php.net/manual/en/function.PDOStatement-bindValue.php
+ */
+ public function bindValue($name, $value, $dataType = null)
+ {
+ $this->prepare();
+ if ($dataType === null) {
+ $dataType = $this->db->getSchema()->getPdoType($value);
+ }
+ $this->pdoStatement->bindValue($name, $value, $dataType);
+ $this->_params[$name] = $value;
+ return $this;
+ }
+
+ /**
+ * Binds a list of values to the corresponding parameters.
+ * This is similar to [[bindValue()]] except that it binds multiple values at a time.
+ * Note that the SQL data type of each value is determined by its PHP type.
+ * @param array $values the values to be bound. This must be given in terms of an associative
+ * array with array keys being the parameter names, and array values the corresponding parameter values,
+ * e.g. `array(':name' => 'John', ':age' => 25)`. By default, the PDO type of each value is determined
+ * by its PHP type. You may explicitly specify the PDO type by using an array: `array(value, type)`,
+ * e.g. `array(':name' => 'John', ':profile' => array($profile, \PDO::PARAM_LOB))`.
+ * @return static the current command being executed
+ */
+ public function bindValues($values)
+ {
+ if (!empty($values)) {
+ $this->prepare();
+ foreach ($values as $name => $value) {
+ if (is_array($value)) {
+ $type = $value[1];
+ $value = $value[0];
+ } else {
+ $type = $this->db->getSchema()->getPdoType($value);
+ }
+ $this->pdoStatement->bindValue($name, $value, $type);
+ $this->_params[$name] = $value;
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Executes the SQL statement.
+ * This method should only be used for executing non-query SQL statement, such as `INSERT`, `DELETE`, `UPDATE` SQLs.
+ * No result set will be returned.
+ * @return integer number of rows affected by the execution.
+ * @throws Exception execution failed
+ */
+ public function execute()
+ {
+ $sql = $this->getSql();
+
+ $rawSql = $this->getRawSql();
+
+ Yii::trace($rawSql, __METHOD__);
+
+ if ($sql == '') {
+ return 0;
+ }
+
+ $token = $rawSql;
+ try {
+ Yii::beginProfile($token, __METHOD__);
+
+ $this->prepare();
+ $this->pdoStatement->execute();
+ $n = $this->pdoStatement->rowCount();
+
+ Yii::endProfile($token, __METHOD__);
+ return $n;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
+ $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
+ throw new Exception($message, $errorInfo, (int)$e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Executes the SQL statement and returns query result.
+ * This method is for executing a SQL query that returns result set, such as `SELECT`.
+ * @return DataReader the reader object for fetching the query result
+ * @throws Exception execution failed
+ */
+ public function query()
+ {
+ return $this->queryInternal('');
+ }
+
+ /**
+ * Executes the SQL statement and returns ALL rows at once.
+ * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
+ * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
+ * @return array all rows of the query result. Each array element is an array representing a row of data.
+ * An empty array is returned if the query results in nothing.
+ * @throws Exception execution failed
+ */
+ public function queryAll($fetchMode = null)
+ {
+ return $this->queryInternal('fetchAll', $fetchMode);
+ }
+
+ /**
+ * Executes the SQL statement and returns the first row of the result.
+ * This method is best used when only the first row of result is needed for a query.
+ * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
+ * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
+ * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
+ * results in nothing.
+ * @throws Exception execution failed
+ */
+ public function queryOne($fetchMode = null)
+ {
+ return $this->queryInternal('fetch', $fetchMode);
+ }
+
+ /**
+ * Executes the SQL statement and returns the value of the first column in the first row of data.
+ * This method is best used when only a single value is needed for a query.
+ * @return string|boolean the value of the first column in the first row of the query result.
+ * False is returned if there is no value.
+ * @throws Exception execution failed
+ */
+ public function queryScalar()
+ {
+ $result = $this->queryInternal('fetchColumn', 0);
+ if (is_resource($result) && get_resource_type($result) === 'stream') {
+ return stream_get_contents($result);
+ } else {
+ return $result;
+ }
+ }
+
+ /**
+ * Executes the SQL statement and returns the first column of the result.
+ * This method is best used when only the first column of result (i.e. the first element in each row)
+ * is needed for a query.
+ * @return array the first column of the query result. Empty array is returned if the query results in nothing.
+ * @throws Exception execution failed
+ */
+ public function queryColumn()
+ {
+ return $this->queryInternal('fetchAll', \PDO::FETCH_COLUMN);
+ }
+
+ /**
+ * Performs the actual DB query of a SQL statement.
+ * @param string $method method of PDOStatement to be called
+ * @param integer $fetchMode the result fetch mode. Please refer to [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
+ * for valid fetch modes. If this parameter is null, the value set in [[fetchMode]] will be used.
+ * @return mixed the method execution result
+ * @throws Exception if the query causes any problem
+ */
+ private function queryInternal($method, $fetchMode = null)
+ {
+ $db = $this->db;
+ $rawSql = $this->getRawSql();
+
+ Yii::trace($rawSql, __METHOD__);
+
+ /** @var $cache \yii\caching\Cache */
+ if ($db->enableQueryCache && $method !== '') {
+ $cache = is_string($db->queryCache) ? Yii::$app->getComponent($db->queryCache) : $db->queryCache;
+ }
+
+ if (isset($cache) && $cache instanceof Cache) {
+ $cacheKey = array(
+ __CLASS__,
+ $db->dsn,
+ $db->username,
+ $rawSql,
+ );
+ if (($result = $cache->get($cacheKey)) !== false) {
+ Yii::trace('Query result served from cache', __METHOD__);
+ return $result;
+ }
+ }
+
+ $token = $rawSql;
+ try {
+ Yii::beginProfile($token, __METHOD__);
+
+ $this->prepare();
+ $this->pdoStatement->execute();
+
+ if ($method === '') {
+ $result = new DataReader($this);
+ } else {
+ if ($fetchMode === null) {
+ $fetchMode = $this->fetchMode;
+ }
+ $result = call_user_func_array(array($this->pdoStatement, $method), (array)$fetchMode);
+ $this->pdoStatement->closeCursor();
+ }
+
+ Yii::endProfile($token, __METHOD__);
+
+ if (isset($cache, $cacheKey) && $cache instanceof Cache) {
+ $cache->set($cacheKey, $result, $db->queryCacheDuration, $db->queryCacheDependency);
+ Yii::trace('Saved query result in cache', __METHOD__);
+ }
+
+ return $result;
+ } catch (\Exception $e) {
+ Yii::endProfile($token, __METHOD__);
+ $message = $e->getMessage() . "\nThe SQL being executed was: $rawSql";
+ $errorInfo = $e instanceof \PDOException ? $e->errorInfo : null;
+ throw new Exception($message, $errorInfo, (int)$e->getCode(), $e);
+ }
+ }
+
+ /**
+ * Creates an INSERT command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->insert('tbl_user', array(
+ * 'name' => 'Sam',
+ * 'age' => 30,
+ * ))->execute();
+ * ~~~
+ *
+ * The method will properly escape the column names, and bind the values to be inserted.
+ *
+ * Note that the created command is not executed until [[execute()]] is called.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column data (name => value) to be inserted into the table.
+ * @return Command the command object itself
+ */
+ public function insert($table, $columns)
+ {
+ $params = array();
+ $sql = $this->db->getQueryBuilder()->insert($table, $columns, $params);
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Creates a batch INSERT command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array(
+ * array('Tom', 30),
+ * array('Jane', 20),
+ * array('Linda', 25),
+ * ))->execute();
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch inserted into the table
+ * @return Command the command object itself
+ */
+ public function batchInsert($table, $columns, $rows)
+ {
+ $sql = $this->db->getQueryBuilder()->batchInsert($table, $columns, $rows);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates an UPDATE command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->update('tbl_user', array(
+ * 'status' => 1,
+ * ), 'age > 30')->execute();
+ * ~~~
+ *
+ * The method will properly escape the column names and bind the values to be updated.
+ *
+ * Note that the created command is not executed until [[execute()]] is called.
+ *
+ * @param string $table the table to be updated.
+ * @param array $columns the column data (name => value) to be updated.
+ * @param string|array $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the parameters to be bound to the command
+ * @return Command the command object itself
+ */
+ public function update($table, $columns, $condition = '', $params = array())
+ {
+ $sql = $this->db->getQueryBuilder()->update($table, $columns, $condition, $params);
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+ /**
+ * Creates a DELETE command.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->delete('tbl_user', 'status = 0')->execute();
+ * ~~~
+ *
+ * The method will properly escape the table and column names.
+ *
+ * Note that the created command is not executed until [[execute()]] is called.
+ *
+ * @param string $table the table where the data will be deleted from.
+ * @param string|array $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the parameters to be bound to the command
+ * @return Command the command object itself
+ */
+ public function delete($table, $condition = '', $params = array())
+ {
+ $sql = $this->db->getQueryBuilder()->delete($table, $condition, $params);
+ return $this->setSql($sql)->bindValues($params);
+ }
+
+
+ /**
+ * Creates a SQL command for creating a new DB table.
+ *
+ * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
+ * where name stands for a column name which will be properly quoted by the method, and definition
+ * stands for the column type which can contain an abstract DB type.
+ * The method [[QueryBuilder::getColumnType()]] will be called
+ * to convert the abstract column types to physical ones. For example, `string` will be converted
+ * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+ *
+ * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
+ * inserted into the generated SQL.
+ *
+ * @param string $table the name of the table to be created. The name will be properly quoted by the method.
+ * @param array $columns the columns (name => definition) in the new table.
+ * @param string $options additional SQL fragment that will be appended to the generated SQL.
+ * @return Command the command object itself
+ */
+ public function createTable($table, $columns, $options = null)
+ {
+ $sql = $this->db->getQueryBuilder()->createTable($table, $columns, $options);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for renaming a DB table.
+ * @param string $table the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function renameTable($table, $newName)
+ {
+ $sql = $this->db->getQueryBuilder()->renameTable($table, $newName);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for dropping a DB table.
+ * @param string $table the table to be dropped. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function dropTable($table)
+ {
+ $sql = $this->db->getQueryBuilder()->dropTable($table);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for truncating a DB table.
+ * @param string $table the table to be truncated. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function truncateTable($table)
+ {
+ $sql = $this->db->getQueryBuilder()->truncateTable($table);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for adding a new DB column.
+ * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
+ * @param string $column the name of the new column. The name will be properly quoted by the method.
+ * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
+ * to convert the give column type to the physical one. For example, `string` will be converted
+ * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+ * @return Command the command object itself
+ */
+ public function addColumn($table, $column, $type)
+ {
+ $sql = $this->db->getQueryBuilder()->addColumn($table, $column, $type);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for dropping a DB column.
+ * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
+ * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function dropColumn($table, $column)
+ {
+ $sql = $this->db->getQueryBuilder()->dropColumn($table, $column);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $oldName the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function renameColumn($table, $oldName, $newName)
+ {
+ $sql = $this->db->getQueryBuilder()->renameColumn($table, $oldName, $newName);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the column type. [[\yii\db\QueryBuilder::getColumnType()]] will be called
+ * to convert the give column type to the physical one. For example, `string` will be converted
+ * as `varchar(255)`, and `string not null` becomes `varchar(255) not null`.
+ * @return Command the command object itself
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ $sql = $this->db->getQueryBuilder()->alterColumn($table, $column, $type);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for adding a primary key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the primary key constraint.
+ * @param string $table the table that the primary key constraint will be added to.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ * @return Command the command object itself.
+ */
+ public function addPrimaryKey($name, $table, $columns)
+ {
+ $sql = $this->db->getQueryBuilder()->addPrimaryKey($name, $table, $columns);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for removing a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ * @return Command the command object itself
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ $sql = $this->db->getQueryBuilder()->dropPrimaryKey($name, $table);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for adding a foreign key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the foreign key constraint.
+ * @param string $table the table that the foreign key constraint will be added to.
+ * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
+ * @param string $refTable the table that the foreign key references to.
+ * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
+ * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @return Command the command object itself
+ */
+ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+ {
+ $sql = $this->db->getQueryBuilder()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function dropForeignKey($name, $table)
+ {
+ $sql = $this->db->getQueryBuilder()->dropForeignKey($name, $table);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for creating a new index.
+ * @param string $name the name of the index. The name will be properly quoted by the method.
+ * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
+ * @param string $columns the column(s) that should be included in the index. If there are multiple columns, please separate them
+ * by commas. The column names will be properly quoted by the method.
+ * @param boolean $unique whether to add UNIQUE constraint on the created index.
+ * @return Command the command object itself
+ */
+ public function createIndex($name, $table, $columns, $unique = false)
+ {
+ $sql = $this->db->getQueryBuilder()->createIndex($name, $table, $columns, $unique);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ * @return Command the command object itself
+ */
+ public function dropIndex($name, $table)
+ {
+ $sql = $this->db->getQueryBuilder()->dropIndex($name, $table);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Creates a SQL command for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $table the name of the table whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return Command the command object itself
+ * @throws NotSupportedException if this is not supported by the underlying DBMS
+ */
+ public function resetSequence($table, $value = null)
+ {
+ $sql = $this->db->getQueryBuilder()->resetSequence($table, $value);
+ return $this->setSql($sql);
+ }
+
+ /**
+ * Builds a SQL command for enabling or disabling integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $schema the schema name of the tables. Defaults to empty string, meaning the current
+ * or default schema.
+ * @return Command the command object itself
+ * @throws NotSupportedException if this is not supported by the underlying DBMS
+ */
+ public function checkIntegrity($check = true, $schema = '')
+ {
+ $sql = $this->db->getQueryBuilder()->checkIntegrity($check, $schema);
+ return $this->setSql($sql);
+ }
+}
diff --git a/framework/yii/db/Connection.php b/framework/yii/db/Connection.php
new file mode 100644
index 0000000..b79146d
--- /dev/null
+++ b/framework/yii/db/Connection.php
@@ -0,0 +1,534 @@
+ $dsn,
+ * 'username' => $username,
+ * 'password' => $password,
+ * ));
+ * $connection->open();
+ * ~~~
+ *
+ * After the DB connection is established, one can execute SQL statements like the following:
+ *
+ * ~~~
+ * $command = $connection->createCommand('SELECT * FROM tbl_post');
+ * $posts = $command->queryAll();
+ * $command = $connection->createCommand('UPDATE tbl_post SET status=1');
+ * $command->execute();
+ * ~~~
+ *
+ * One can also do prepared SQL execution and bind parameters to the prepared SQL.
+ * When the parameters are coming from user input, you should use this approach
+ * to prevent SQL injection attacks. The following is an example:
+ *
+ * ~~~
+ * $command = $connection->createCommand('SELECT * FROM tbl_post WHERE id=:id');
+ * $command->bindValue(':id', $_GET['id']);
+ * $post = $command->query();
+ * ~~~
+ *
+ * For more information about how to perform various DB queries, please refer to [[Command]].
+ *
+ * If the underlying DBMS supports transactions, you can perform transactional SQL queries
+ * like the following:
+ *
+ * ~~~
+ * $transaction = $connection->beginTransaction();
+ * try {
+ * $connection->createCommand($sql1)->execute();
+ * $connection->createCommand($sql2)->execute();
+ * // ... executing other SQL statements ...
+ * $transaction->commit();
+ * } catch(Exception $e) {
+ * $transaction->rollback();
+ * }
+ * ~~~
+ *
+ * Connection is often used as an application component and configured in the application
+ * configuration like the following:
+ *
+ * ~~~
+ * array(
+ * 'components' => array(
+ * 'db' => array(
+ * 'class' => '\yii\db\Connection',
+ * 'dsn' => 'mysql:host=127.0.0.1;dbname=demo',
+ * 'username' => 'root',
+ * 'password' => '',
+ * 'charset' => 'utf8',
+ * ),
+ * ),
+ * )
+ * ~~~
+ *
+ * @property string $driverName Name of the DB driver. This property is read-only.
+ * @property boolean $isActive Whether the DB connection is established. This property is read-only.
+ * @property string $lastInsertID The row ID of the last row inserted, or the last value retrieved from the
+ * sequence object. This property is read-only.
+ * @property QueryBuilder $queryBuilder The query builder for the current DB connection. This property is
+ * read-only.
+ * @property Schema $schema The schema information for the database opened by this connection. This property
+ * is read-only.
+ * @property Transaction $transaction The currently active transaction. Null if no active transaction. This
+ * property is read-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Connection extends Component
+{
+ /**
+ * @event Event an event that is triggered after a DB connection is established
+ */
+ const EVENT_AFTER_OPEN = 'afterOpen';
+
+ /**
+ * @var string the Data Source Name, or DSN, contains the information required to connect to the database.
+ * Please refer to the [PHP manual](http://www.php.net/manual/en/function.PDO-construct.php) on
+ * the format of the DSN string.
+ * @see charset
+ */
+ public $dsn;
+ /**
+ * @var string the username for establishing DB connection. Defaults to empty string.
+ */
+ public $username = '';
+ /**
+ * @var string the password for establishing DB connection. Defaults to empty string.
+ */
+ public $password = '';
+ /**
+ * @var array PDO attributes (name => value) that should be set when calling [[open()]]
+ * to establish a DB connection. Please refer to the
+ * [PHP manual](http://www.php.net/manual/en/function.PDO-setAttribute.php) for
+ * details about available attributes.
+ */
+ public $attributes;
+ /**
+ * @var PDO the PHP PDO instance associated with this DB connection.
+ * This property is mainly managed by [[open()]] and [[close()]] methods.
+ * When a DB connection is active, this property will represent a PDO instance;
+ * otherwise, it will be null.
+ */
+ public $pdo;
+ /**
+ * @var boolean whether to enable schema caching.
+ * Note that in order to enable truly schema caching, a valid cache component as specified
+ * by [[schemaCache]] must be enabled and [[enableSchemaCache]] must be set true.
+ * @see schemaCacheDuration
+ * @see schemaCacheExclude
+ * @see schemaCache
+ */
+ public $enableSchemaCache = false;
+ /**
+ * @var integer number of seconds that table metadata can remain valid in cache.
+ * Use 0 to indicate that the cached data will never expire.
+ * @see enableSchemaCache
+ */
+ public $schemaCacheDuration = 3600;
+ /**
+ * @var array list of tables whose metadata should NOT be cached. Defaults to empty array.
+ * The table names may contain schema prefix, if any. Do not quote the table names.
+ * @see enableSchemaCache
+ */
+ public $schemaCacheExclude = array();
+ /**
+ * @var Cache|string the cache object or the ID of the cache application component that
+ * is used to cache the table metadata.
+ * @see enableSchemaCache
+ */
+ public $schemaCache = 'cache';
+ /**
+ * @var boolean whether to enable query caching.
+ * Note that in order to enable query caching, a valid cache component as specified
+ * by [[queryCache]] must be enabled and [[enableQueryCache]] must be set true.
+ *
+ * Methods [[beginCache()]] and [[endCache()]] can be used as shortcuts to turn on
+ * and off query caching on the fly.
+ * @see queryCacheDuration
+ * @see queryCache
+ * @see queryCacheDependency
+ * @see beginCache()
+ * @see endCache()
+ */
+ public $enableQueryCache = false;
+ /**
+ * @var integer number of seconds that query results can remain valid in cache.
+ * Defaults to 3600, meaning 3600 seconds, or one hour.
+ * Use 0 to indicate that the cached data will never expire.
+ * @see enableQueryCache
+ */
+ public $queryCacheDuration = 3600;
+ /**
+ * @var \yii\caching\Dependency the dependency that will be used when saving query results into cache.
+ * Defaults to null, meaning no dependency.
+ * @see enableQueryCache
+ */
+ public $queryCacheDependency;
+ /**
+ * @var Cache|string the cache object or the ID of the cache application component
+ * that is used for query caching.
+ * @see enableQueryCache
+ */
+ public $queryCache = 'cache';
+ /**
+ * @var string the charset used for database connection. The property is only used
+ * for MySQL, PostgreSQL and CUBRID databases. Defaults to null, meaning using default charset
+ * as specified by the database.
+ *
+ * Note that if you're using GBK or BIG5 then it's highly recommended to
+ * update to PHP 5.3.6+ and to specify charset via DSN like
+ * 'mysql:dbname=mydatabase;host=127.0.0.1;charset=GBK;'.
+ */
+ public $charset;
+ /**
+ * @var boolean whether to turn on prepare emulation. Defaults to false, meaning PDO
+ * will use the native prepare support if available. For some databases (such as MySQL),
+ * this may need to be set true so that PDO can emulate the prepare support to bypass
+ * the buggy native prepare support.
+ * The default value is null, which means the PDO ATTR_EMULATE_PREPARES value will not be changed.
+ */
+ public $emulatePrepare;
+ /**
+ * @var string the common prefix or suffix for table names. If a table name is given
+ * as `{{%TableName}}`, then the percentage character `%` will be replaced with this
+ * property value. For example, `{{%post}}` becomes `{{tbl_post}}` if this property is
+ * set as `"tbl_"`.
+ */
+ public $tablePrefix;
+ /**
+ * @var array mapping between PDO driver names and [[Schema]] classes.
+ * The keys of the array are PDO driver names while the values the corresponding
+ * schema class name or configuration. Please refer to [[Yii::createObject()]] for
+ * details on how to specify a configuration.
+ *
+ * This property is mainly used by [[getSchema()]] when fetching the database schema information.
+ * You normally do not need to set this property unless you want to use your own
+ * [[Schema]] class to support DBMS that is not supported by Yii.
+ */
+ public $schemaMap = array(
+ 'pgsql' => 'yii\db\pgsql\Schema', // PostgreSQL
+ 'mysqli' => 'yii\db\mysql\Schema', // MySQL
+ 'mysql' => 'yii\db\mysql\Schema', // MySQL
+ 'sqlite' => 'yii\db\sqlite\Schema', // sqlite 3
+ 'sqlite2' => 'yii\db\sqlite\Schema', // sqlite 2
+ 'sqlsrv' => 'yii\db\mssql\Schema', // newer MSSQL driver on MS Windows hosts
+ 'oci' => 'yii\db\oci\Schema', // Oracle driver
+ 'mssql' => 'yii\db\mssql\Schema', // older MSSQL driver on MS Windows hosts
+ 'dblib' => 'yii\db\mssql\Schema', // dblib drivers on GNU/Linux (and maybe other OSes) hosts
+ 'cubrid' => 'yii\db\cubrid\Schema', // CUBRID
+ );
+ /**
+ * @var Transaction the currently active transaction
+ */
+ private $_transaction;
+ /**
+ * @var Schema the database schema
+ */
+ private $_schema;
+
+
+ /**
+ * Returns a value indicating whether the DB connection is established.
+ * @return boolean whether the DB connection is established
+ */
+ public function getIsActive()
+ {
+ return $this->pdo !== null;
+ }
+
+ /**
+ * Turns on query caching.
+ * This method is provided as a shortcut to setting two properties that are related
+ * with query caching: [[queryCacheDuration]] and [[queryCacheDependency]].
+ * @param integer $duration the number of seconds that query results may remain valid in cache.
+ * If not set, it will use the value of [[queryCacheDuration]]. See [[queryCacheDuration]] for more details.
+ * @param \yii\caching\Dependency $dependency the dependency for the cached query result.
+ * See [[queryCacheDependency]] for more details.
+ */
+ public function beginCache($duration = null, $dependency = null)
+ {
+ $this->enableQueryCache = true;
+ if ($duration !== null) {
+ $this->queryCacheDuration = $duration;
+ }
+ $this->queryCacheDependency = $dependency;
+ }
+
+ /**
+ * Turns off query caching.
+ */
+ public function endCache()
+ {
+ $this->enableQueryCache = false;
+ }
+
+ /**
+ * Establishes a DB connection.
+ * It does nothing if a DB connection has already been established.
+ * @throws Exception if connection fails
+ */
+ public function open()
+ {
+ if ($this->pdo === null) {
+ if (empty($this->dsn)) {
+ throw new InvalidConfigException('Connection::dsn cannot be empty.');
+ }
+ $token = 'Opening DB connection: ' . $this->dsn;
+ try {
+ Yii::trace($token, __METHOD__);
+ Yii::beginProfile($token, __METHOD__);
+ $this->pdo = $this->createPdoInstance();
+ $this->initConnection();
+ Yii::endProfile($token, __METHOD__);
+ } catch (\PDOException $e) {
+ Yii::endProfile($token, __METHOD__);
+ throw new Exception($e->getMessage(), $e->errorInfo, (int)$e->getCode(), $e);
+ }
+ }
+ }
+
+ /**
+ * Closes the currently active DB connection.
+ * It does nothing if the connection is already closed.
+ */
+ public function close()
+ {
+ if ($this->pdo !== null) {
+ Yii::trace('Closing DB connection: ' . $this->dsn, __METHOD__);
+ $this->pdo = null;
+ $this->_schema = null;
+ $this->_transaction = null;
+ }
+ }
+
+ /**
+ * Creates the PDO instance.
+ * This method is called by [[open]] to establish a DB connection.
+ * The default implementation will create a PHP PDO instance.
+ * You may override this method if the default PDO needs to be adapted for certain DBMS.
+ * @return PDO the pdo instance
+ */
+ protected function createPdoInstance()
+ {
+ $pdoClass = 'PDO';
+ if (($pos = strpos($this->dsn, ':')) !== false) {
+ $driver = strtolower(substr($this->dsn, 0, $pos));
+ if ($driver === 'mssql' || $driver === 'dblib' || $driver === 'sqlsrv') {
+ $pdoClass = 'yii\db\mssql\PDO';
+ }
+ }
+ return new $pdoClass($this->dsn, $this->username, $this->password, $this->attributes);
+ }
+
+ /**
+ * Initializes the DB connection.
+ * This method is invoked right after the DB connection is established.
+ * The default implementation turns on `PDO::ATTR_EMULATE_PREPARES`
+ * if [[emulatePrepare]] is true, and sets the database [[charset]] if it is not empty.
+ * It then triggers an [[EVENT_AFTER_OPEN]] event.
+ */
+ protected function initConnection()
+ {
+ $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
+ if ($this->emulatePrepare !== null && constant('PDO::ATTR_EMULATE_PREPARES')) {
+ $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->emulatePrepare);
+ }
+ if ($this->charset !== null && in_array($this->getDriverName(), array('pgsql', 'mysql', 'mysqli', 'cubrid'))) {
+ $this->pdo->exec('SET NAMES ' . $this->pdo->quote($this->charset));
+ }
+ $this->trigger(self::EVENT_AFTER_OPEN);
+ }
+
+ /**
+ * Creates a command for execution.
+ * @param string $sql the SQL statement to be executed
+ * @param array $params the parameters to be bound to the SQL statement
+ * @return Command the DB command
+ */
+ public function createCommand($sql = null, $params = array())
+ {
+ $this->open();
+ $command = new Command(array(
+ 'db' => $this,
+ 'sql' => $sql,
+ ));
+ return $command->bindValues($params);
+ }
+
+ /**
+ * Returns the currently active transaction.
+ * @return Transaction the currently active transaction. Null if no active transaction.
+ */
+ public function getTransaction()
+ {
+ return $this->_transaction && $this->_transaction->isActive ? $this->_transaction : null;
+ }
+
+ /**
+ * Starts a transaction.
+ * @return Transaction the transaction initiated
+ */
+ public function beginTransaction()
+ {
+ $this->open();
+ $this->_transaction = new Transaction(array(
+ 'db' => $this,
+ ));
+ $this->_transaction->begin();
+ return $this->_transaction;
+ }
+
+ /**
+ * Returns the schema information for the database opened by this connection.
+ * @return Schema the schema information for the database opened by this connection.
+ * @throws NotSupportedException if there is no support for the current driver type
+ */
+ public function getSchema()
+ {
+ if ($this->_schema !== null) {
+ return $this->_schema;
+ } else {
+ $driver = $this->getDriverName();
+ if (isset($this->schemaMap[$driver])) {
+ $this->_schema = Yii::createObject($this->schemaMap[$driver]);
+ $this->_schema->db = $this;
+ return $this->_schema;
+ } else {
+ throw new NotSupportedException("Connection does not support reading schema information for '$driver' DBMS.");
+ }
+ }
+ }
+
+ /**
+ * Returns the query builder for the current DB connection.
+ * @return QueryBuilder the query builder for the current DB connection.
+ */
+ public function getQueryBuilder()
+ {
+ return $this->getSchema()->getQueryBuilder();
+ }
+
+ /**
+ * Obtains the schema information for the named table.
+ * @param string $name table name.
+ * @param boolean $refresh whether to reload the table schema even if it is found in the cache.
+ * @return TableSchema table schema information. Null if the named table does not exist.
+ */
+ public function getTableSchema($name, $refresh = false)
+ {
+ return $this->getSchema()->getTableSchema($name, $refresh);
+ }
+
+ /**
+ * Returns the ID of the last inserted row or sequence value.
+ * @param string $sequenceName name of the sequence object (required by some DBMS)
+ * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
+ * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
+ */
+ public function getLastInsertID($sequenceName = '')
+ {
+ return $this->getSchema()->getLastInsertID($sequenceName);
+ }
+
+ /**
+ * Quotes a string value for use in a query.
+ * Note that if the parameter is not a string, it will be returned without change.
+ * @param string $str string to be quoted
+ * @return string the properly quoted string
+ * @see http://www.php.net/manual/en/function.PDO-quote.php
+ */
+ public function quoteValue($str)
+ {
+ return $this->getSchema()->quoteValue($str);
+ }
+
+ /**
+ * Quotes a table name for use in a query.
+ * If the table name contains schema prefix, the prefix will also be properly quoted.
+ * If the table name is already quoted or contains special characters including '(', '[[' and '{{',
+ * then this method will do nothing.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteTableName($name)
+ {
+ return $this->getSchema()->quoteTableName($name);
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * If the column name contains prefix, the prefix will also be properly quoted.
+ * If the column name is already quoted or contains special characters including '(', '[[' and '{{',
+ * then this method will do nothing.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteColumnName($name)
+ {
+ return $this->getSchema()->quoteColumnName($name);
+ }
+
+ /**
+ * Processes a SQL statement by quoting table and column names that are enclosed within double brackets.
+ * Tokens enclosed within double curly brackets are treated as table names, while
+ * tokens enclosed within double square brackets are column names. They will be quoted accordingly.
+ * Also, the percentage character "%" at the beginning or ending of a table name will be replaced
+ * with [[tablePrefix]].
+ * @param string $sql the SQL to be quoted
+ * @return string the quoted SQL
+ */
+ public function quoteSql($sql)
+ {
+ return preg_replace_callback('/(\\{\\{(%?[\w\-\. ]+%?)\\}\\}|\\[\\[([\w\-\. ]+)\\]\\])/',
+ function ($matches) {
+ if (isset($matches[3])) {
+ return $this->quoteColumnName($matches[3]);
+ } else {
+ return str_replace('%', $this->tablePrefix, $this->quoteTableName($matches[2]));
+ }
+ }, $sql);
+ }
+
+ /**
+ * Returns the name of the DB driver for the current [[dsn]].
+ * @return string name of the DB driver
+ */
+ public function getDriverName()
+ {
+ if (($pos = strpos($this->dsn, ':')) !== false) {
+ return strtolower(substr($this->dsn, 0, $pos));
+ } else {
+ $this->open();
+ return strtolower($this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME));
+ }
+ }
+}
diff --git a/framework/yii/db/DataReader.php b/framework/yii/db/DataReader.php
new file mode 100644
index 0000000..f2990c1
--- /dev/null
+++ b/framework/yii/db/DataReader.php
@@ -0,0 +1,264 @@
+query('SELECT * FROM tbl_post');
+ *
+ * while ($row = $reader->read()) {
+ * $rows[] = $row;
+ * }
+ *
+ * // equivalent to:
+ * foreach ($reader as $row) {
+ * $rows[] = $row;
+ * }
+ *
+ * // equivalent to:
+ * $rows = $reader->readAll();
+ * ~~~
+ *
+ * Note that since DataReader is a forward-only stream, you can only traverse it once.
+ * Doing it the second time will throw an exception.
+ *
+ * It is possible to use a specific mode of data fetching by setting
+ * [[fetchMode]]. See the [PHP manual](http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php)
+ * for more details about possible fetch mode.
+ *
+ * @property integer $columnCount The number of columns in the result set. This property is read-only.
+ * @property integer $fetchMode Fetch mode. This property is write-only.
+ * @property boolean $isClosed Whether the reader is closed or not. This property is read-only.
+ * @property integer $rowCount Number of rows contained in the result. This property is read-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class DataReader extends \yii\base\Object implements \Iterator, \Countable
+{
+ /**
+ * @var \PDOStatement the PDOStatement associated with the command
+ */
+ private $_statement;
+ private $_closed = false;
+ private $_row;
+ private $_index = -1;
+
+ /**
+ * Constructor.
+ * @param Command $command the command generating the query result
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct(Command $command, $config = array())
+ {
+ $this->_statement = $command->pdoStatement;
+ $this->_statement->setFetchMode(\PDO::FETCH_ASSOC);
+ parent::__construct($config);
+ }
+
+ /**
+ * Binds a column to a PHP variable.
+ * When rows of data are being fetched, the corresponding column value
+ * will be set in the variable. Note, the fetch mode must include PDO::FETCH_BOUND.
+ * @param integer|string $column Number of the column (1-indexed) or name of the column
+ * in the result set. If using the column name, be aware that the name
+ * should match the case of the column, as returned by the driver.
+ * @param mixed $value Name of the PHP variable to which the column will be bound.
+ * @param integer $dataType Data type of the parameter
+ * @see http://www.php.net/manual/en/function.PDOStatement-bindColumn.php
+ */
+ public function bindColumn($column, &$value, $dataType = null)
+ {
+ if ($dataType === null) {
+ $this->_statement->bindColumn($column, $value);
+ } else {
+ $this->_statement->bindColumn($column, $value, $dataType);
+ }
+ }
+
+ /**
+ * Set the default fetch mode for this statement
+ * @param integer $mode fetch mode
+ * @see http://www.php.net/manual/en/function.PDOStatement-setFetchMode.php
+ */
+ public function setFetchMode($mode)
+ {
+ $params = func_get_args();
+ call_user_func_array(array($this->_statement, 'setFetchMode'), $params);
+ }
+
+ /**
+ * Advances the reader to the next row in a result set.
+ * @return array the current row, false if no more row available
+ */
+ public function read()
+ {
+ return $this->_statement->fetch();
+ }
+
+ /**
+ * Returns a single column from the next row of a result set.
+ * @param integer $columnIndex zero-based column index
+ * @return mixed the column of the current row, false if no more rows available
+ */
+ public function readColumn($columnIndex)
+ {
+ return $this->_statement->fetchColumn($columnIndex);
+ }
+
+ /**
+ * Returns an object populated with the next row of data.
+ * @param string $className class name of the object to be created and populated
+ * @param array $fields Elements of this array are passed to the constructor
+ * @return mixed the populated object, false if no more row of data available
+ */
+ public function readObject($className, $fields)
+ {
+ return $this->_statement->fetchObject($className, $fields);
+ }
+
+ /**
+ * Reads the whole result set into an array.
+ * @return array the result set (each array element represents a row of data).
+ * An empty array will be returned if the result contains no row.
+ */
+ public function readAll()
+ {
+ return $this->_statement->fetchAll();
+ }
+
+ /**
+ * Advances the reader to the next result when reading the results of a batch of statements.
+ * This method is only useful when there are multiple result sets
+ * returned by the query. Not all DBMS support this feature.
+ * @return boolean Returns true on success or false on failure.
+ */
+ public function nextResult()
+ {
+ if (($result = $this->_statement->nextRowset()) !== false) {
+ $this->_index = -1;
+ }
+ return $result;
+ }
+
+ /**
+ * Closes the reader.
+ * This frees up the resources allocated for executing this SQL statement.
+ * Read attempts after this method call are unpredictable.
+ */
+ public function close()
+ {
+ $this->_statement->closeCursor();
+ $this->_closed = true;
+ }
+
+ /**
+ * whether the reader is closed or not.
+ * @return boolean whether the reader is closed or not.
+ */
+ public function getIsClosed()
+ {
+ return $this->_closed;
+ }
+
+ /**
+ * Returns the number of rows in the result set.
+ * Note, most DBMS may not give a meaningful count.
+ * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
+ * @return integer number of rows contained in the result.
+ */
+ public function getRowCount()
+ {
+ return $this->_statement->rowCount();
+ }
+
+ /**
+ * Returns the number of rows in the result set.
+ * This method is required by the Countable interface.
+ * Note, most DBMS may not give a meaningful count.
+ * In this case, use "SELECT COUNT(*) FROM tableName" to obtain the number of rows.
+ * @return integer number of rows contained in the result.
+ */
+ public function count()
+ {
+ return $this->getRowCount();
+ }
+
+ /**
+ * Returns the number of columns in the result set.
+ * Note, even there's no row in the reader, this still gives correct column number.
+ * @return integer the number of columns in the result set.
+ */
+ public function getColumnCount()
+ {
+ return $this->_statement->columnCount();
+ }
+
+ /**
+ * Resets the iterator to the initial state.
+ * This method is required by the interface Iterator.
+ * @throws InvalidCallException if this method is invoked twice
+ */
+ public function rewind()
+ {
+ if ($this->_index < 0) {
+ $this->_row = $this->_statement->fetch();
+ $this->_index = 0;
+ } else {
+ throw new InvalidCallException('DataReader cannot rewind. It is a forward-only reader.');
+ }
+ }
+
+ /**
+ * Returns the index of the current row.
+ * This method is required by the interface Iterator.
+ * @return integer the index of the current row.
+ */
+ public function key()
+ {
+ return $this->_index;
+ }
+
+ /**
+ * Returns the current row.
+ * This method is required by the interface Iterator.
+ * @return mixed the current row.
+ */
+ public function current()
+ {
+ return $this->_row;
+ }
+
+ /**
+ * Moves the internal pointer to the next row.
+ * This method is required by the interface Iterator.
+ */
+ public function next()
+ {
+ $this->_row = $this->_statement->fetch();
+ $this->_index++;
+ }
+
+ /**
+ * Returns whether there is a row of data at current position.
+ * This method is required by the interface Iterator.
+ * @return boolean whether there is a row of data at current position.
+ */
+ public function valid()
+ {
+ return $this->_row !== false;
+ }
+}
diff --git a/framework/yii/db/Exception.php b/framework/yii/db/Exception.php
new file mode 100644
index 0000000..25ae39f
--- /dev/null
+++ b/framework/yii/db/Exception.php
@@ -0,0 +1,44 @@
+
+ * @since 2.0
+ */
+class Exception extends \yii\base\Exception
+{
+ /**
+ * @var array the error info provided by a PDO exception. This is the same as returned
+ * by [PDO::errorInfo](http://www.php.net/manual/en/pdo.errorinfo.php).
+ */
+ public $errorInfo = array();
+
+ /**
+ * Constructor.
+ * @param string $message PDO error message
+ * @param array $errorInfo PDO error info
+ * @param integer $code PDO error code
+ * @param \Exception $previous The previous exception used for the exception chaining.
+ */
+ public function __construct($message, $errorInfo = array(), $code = 0, \Exception $previous = null)
+ {
+ $this->errorInfo = $errorInfo;
+ parent::__construct($message, $code, $previous);
+ }
+
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Database Exception');
+ }
+}
diff --git a/yii/db/Expression.php b/framework/yii/db/Expression.php
similarity index 100%
rename from yii/db/Expression.php
rename to framework/yii/db/Expression.php
diff --git a/framework/yii/db/Migration.php b/framework/yii/db/Migration.php
new file mode 100644
index 0000000..7368788
--- /dev/null
+++ b/framework/yii/db/Migration.php
@@ -0,0 +1,401 @@
+
+ * @since 2.0
+ */
+class Migration extends \yii\base\Component
+{
+ /**
+ * @var Connection the database connection that this migration should work with.
+ * If not set, it will be initialized as the 'db' application component.
+ */
+ public $db;
+
+ /**
+ * Initializes the migration.
+ * This method will set [[db]] to be the 'db' application component, if it is null.
+ */
+ public function init()
+ {
+ parent::init();
+ if ($this->db === null) {
+ $this->db = \Yii::$app->getComponent('db');
+ }
+ }
+
+ /**
+ * This method contains the logic to be executed when applying this migration.
+ * Child classes may overwrite this method to provide actual migration logic.
+ * @return boolean return a false value to indicate the migration fails
+ * and should not proceed further. All other return values mean the migration succeeds.
+ */
+ public function up()
+ {
+ $transaction = $this->db->beginTransaction();
+ try {
+ if ($this->safeUp() === false) {
+ $transaction->rollback();
+ return false;
+ }
+ $transaction->commit();
+ } catch (\Exception $e) {
+ echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
+ echo $e->getTraceAsString() . "\n";
+ $transaction->rollback();
+ return false;
+ }
+ return null;
+ }
+
+ /**
+ * This method contains the logic to be executed when removing this migration.
+ * The default implementation throws an exception indicating the migration cannot be removed.
+ * Child classes may override this method if the corresponding migrations can be removed.
+ * @return boolean return a false value to indicate the migration fails
+ * and should not proceed further. All other return values mean the migration succeeds.
+ */
+ public function down()
+ {
+ $transaction = $this->db->beginTransaction();
+ try {
+ if ($this->safeDown() === false) {
+ $transaction->rollback();
+ return false;
+ }
+ $transaction->commit();
+ } catch (\Exception $e) {
+ echo "Exception: " . $e->getMessage() . ' (' . $e->getFile() . ':' . $e->getLine() . ")\n";
+ echo $e->getTraceAsString() . "\n";
+ $transaction->rollback();
+ return false;
+ }
+ return null;
+ }
+
+ /**
+ * This method contains the logic to be executed when applying this migration.
+ * This method differs from [[up()]] in that the DB logic implemented here will
+ * be enclosed within a DB transaction.
+ * Child classes may implement this method instead of [[up()]] if the DB logic
+ * needs to be within a transaction.
+ * @return boolean return a false value to indicate the migration fails
+ * and should not proceed further. All other return values mean the migration succeeds.
+ */
+ public function safeUp()
+ {
+ }
+
+ /**
+ * This method contains the logic to be executed when removing this migration.
+ * This method differs from [[down()]] in that the DB logic implemented here will
+ * be enclosed within a DB transaction.
+ * Child classes may implement this method instead of [[up()]] if the DB logic
+ * needs to be within a transaction.
+ * @return boolean return a false value to indicate the migration fails
+ * and should not proceed further. All other return values mean the migration succeeds.
+ */
+ public function safeDown()
+ {
+ }
+
+ /**
+ * Executes a SQL statement.
+ * This method executes the specified SQL statement using [[db]].
+ * @param string $sql the SQL statement to be executed
+ * @param array $params input parameters (name => value) for the SQL execution.
+ * See [[Command::execute()]] for more details.
+ */
+ public function execute($sql, $params = array())
+ {
+ echo " > execute SQL: $sql ...";
+ $time = microtime(true);
+ $this->db->createCommand($sql)->execute($params);
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Creates and executes an INSERT SQL statement.
+ * The method will properly escape the column names, and bind the values to be inserted.
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column data (name => value) to be inserted into the table.
+ */
+ public function insert($table, $columns)
+ {
+ echo " > insert into $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->insert($table, $columns)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Creates and executes an UPDATE SQL statement.
+ * The method will properly escape the column names and bind the values to be updated.
+ * @param string $table the table to be updated.
+ * @param array $columns the column data (name => value) to be updated.
+ * @param array|string $condition the conditions that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify conditions.
+ * @param array $params the parameters to be bound to the query.
+ */
+ public function update($table, $columns, $condition = '', $params = array())
+ {
+ echo " > update $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->update($table, $columns, $condition, $params)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Creates and executes a DELETE SQL statement.
+ * @param string $table the table where the data will be deleted from.
+ * @param array|string $condition the conditions that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify conditions.
+ * @param array $params the parameters to be bound to the query.
+ */
+ public function delete($table, $condition = '', $params = array())
+ {
+ echo " > delete from $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->delete($table, $condition, $params)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for creating a new DB table.
+ *
+ * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
+ * where name stands for a column name which will be properly quoted by the method, and definition
+ * stands for the column type which can contain an abstract DB type.
+ *
+ * The [[QueryBuilder::getColumnType()]] method will be invoked to convert any abstract type into a physical one.
+ *
+ * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
+ * put into the generated SQL.
+ *
+ * @param string $table the name of the table to be created. The name will be properly quoted by the method.
+ * @param array $columns the columns (name => definition) in the new table.
+ * @param string $options additional SQL fragment that will be appended to the generated SQL.
+ */
+ public function createTable($table, $columns, $options = null)
+ {
+ echo " > create table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->createTable($table, $columns, $options)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for renaming a DB table.
+ * @param string $table the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ */
+ public function renameTable($table, $newName)
+ {
+ echo " > rename table $table to $newName ...";
+ $time = microtime(true);
+ $this->db->createCommand()->renameTable($table, $newName)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for dropping a DB table.
+ * @param string $table the table to be dropped. The name will be properly quoted by the method.
+ */
+ public function dropTable($table)
+ {
+ echo " > drop table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropTable($table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for truncating a DB table.
+ * @param string $table the table to be truncated. The name will be properly quoted by the method.
+ */
+ public function truncateTable($table)
+ {
+ echo " > truncate table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->truncateTable($table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for adding a new DB column.
+ * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
+ * @param string $column the name of the new column. The name will be properly quoted by the method.
+ * @param string $type the column type. The [[QueryBuilder::getColumnType()]] method will be invoked to convert abstract column type (if any)
+ * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
+ * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
+ */
+ public function addColumn($table, $column, $type)
+ {
+ echo " > add column $column $type to table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->addColumn($table, $column, $type)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for dropping a DB column.
+ * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
+ * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+ */
+ public function dropColumn($table, $column)
+ {
+ echo " > drop column $column from table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropColumn($table, $column)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $name the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ */
+ public function renameColumn($table, $name, $newName)
+ {
+ echo " > rename column $name in table $table to $newName ...";
+ $time = microtime(true);
+ $this->db->createCommand()->renameColumn($table, $name, $newName)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The {@link getColumnType} method will be invoked to convert abstract column type (if any)
+ * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
+ * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ echo " > alter column $column in table $table to $type ...";
+ $time = microtime(true);
+ $this->db->createCommand()->alterColumn($table, $column, $type)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for creating a primary key.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the primary key constraint.
+ * @param string $table the table that the primary key constraint will be added to.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ */
+ public function addPrimaryKey($name, $table, $columns)
+ {
+ echo " > add primary key $name on $table (".(is_array($columns) ? implode(',', $columns) : $columns).") ...";
+ $time = microtime(true);
+ $this->db->createCommand()->addPrimaryKey($name, $table, $columns)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for dropping a primary key.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ * @return Command the command object itself
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ echo " > drop primary key $name ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropPrimaryKey($name, $table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds a SQL statement for adding a foreign key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the foreign key constraint.
+ * @param string $table the table that the foreign key constraint will be added to.
+ * @param string $columns the name of the column to that the constraint will be added on. If there are multiple columns, separate them with commas.
+ * @param string $refTable the table that the foreign key references to.
+ * @param string $refColumns the name of the column that the foreign key references to. If there are multiple columns, separate them with commas.
+ * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ */
+ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+ {
+ echo " > add foreign key $name: $table ($columns) references $refTable ($refColumns) ...";
+ $time = microtime(true);
+ $this->db->createCommand()->addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete, $update)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds a SQL statement for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ */
+ public function dropForeignKey($name, $table)
+ {
+ echo " > drop foreign key $name from table $table ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropForeignKey($name, $table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for creating a new index.
+ * @param string $name the name of the index. The name will be properly quoted by the method.
+ * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
+ * @param string $column the column(s) that should be included in the index. If there are multiple columns, please separate them
+ * by commas. The column names will be properly quoted by the method.
+ * @param boolean $unique whether to add UNIQUE constraint on the created index.
+ */
+ public function createIndex($name, $table, $column, $unique = false)
+ {
+ echo " > create" . ($unique ? ' unique' : '') . " index $name on $table ($column) ...";
+ $time = microtime(true);
+ $this->db->createCommand()->createIndex($name, $table, $column, $unique)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+
+ /**
+ * Builds and executes a SQL statement for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ */
+ public function dropIndex($name, $table)
+ {
+ echo " > drop index $name ...";
+ $time = microtime(true);
+ $this->db->createCommand()->dropIndex($name, $table)->execute();
+ echo " done (time: " . sprintf('%.3f', microtime(true) - $time) . "s)\n";
+ }
+}
diff --git a/framework/yii/db/Query.php b/framework/yii/db/Query.php
new file mode 100644
index 0000000..dbe0424
--- /dev/null
+++ b/framework/yii/db/Query.php
@@ -0,0 +1,780 @@
+select('id, name')
+ * ->from('tbl_user')
+ * ->limit(10);
+ * // build and execute the query
+ * $command = $query->createCommand();
+ * // $command->sql returns the actual SQL
+ * $rows = $command->queryAll();
+ * ~~~
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Query extends Component
+{
+ /**
+ * Sort ascending
+ * @see orderBy
+ */
+ const SORT_ASC = false;
+ /**
+ * Sort descending
+ * @see orderBy
+ */
+ const SORT_DESC = true;
+
+ /**
+ * @var array the columns being selected. For example, `array('id', 'name')`.
+ * This is used to construct the SELECT clause in a SQL statement. If not set, if means selecting all columns.
+ * @see select()
+ */
+ public $select;
+ /**
+ * @var string additional option that should be appended to the 'SELECT' keyword. For example,
+ * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
+ */
+ public $selectOption;
+ /**
+ * @var boolean whether to select distinct rows of data only. If this is set true,
+ * the SELECT clause would be changed to SELECT DISTINCT.
+ */
+ public $distinct;
+ /**
+ * @var array the table(s) to be selected from. For example, `array('tbl_user', 'tbl_post')`.
+ * This is used to construct the FROM clause in a SQL statement.
+ * @see from()
+ */
+ public $from;
+ /**
+ * @var string|array query condition. This refers to the WHERE clause in a SQL statement.
+ * For example, `age > 31 AND team = 1`.
+ * @see where()
+ */
+ public $where;
+ /**
+ * @var integer maximum number of records to be returned. If not set or less than 0, it means no limit.
+ */
+ public $limit;
+ /**
+ * @var integer zero-based offset from where the records are to be returned. If not set or
+ * less than 0, it means starting from the beginning.
+ */
+ public $offset;
+ /**
+ * @var array how to sort the query results. This is used to construct the ORDER BY clause in a SQL statement.
+ * The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
+ * can be either [[Query::SORT_ASC]] or [[Query::SORT_DESC]]. The array may also contain [[Expression]] objects.
+ * If that is the case, the expressions will be converted into strings without any change.
+ */
+ public $orderBy;
+ /**
+ * @var array how to group the query results. For example, `array('company', 'department')`.
+ * This is used to construct the GROUP BY clause in a SQL statement.
+ */
+ public $groupBy;
+ /**
+ * @var array how to join with other tables. Each array element represents the specification
+ * of one join which has the following structure:
+ *
+ * ~~~
+ * array($joinType, $tableName, $joinCondition)
+ * ~~~
+ *
+ * For example,
+ *
+ * ~~~
+ * array(
+ * array('INNER JOIN', 'tbl_user', 'tbl_user.id = author_id'),
+ * array('LEFT JOIN', 'tbl_team', 'tbl_team.id = team_id'),
+ * )
+ * ~~~
+ */
+ public $join;
+ /**
+ * @var string|array the condition to be applied in the GROUP BY clause.
+ * It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
+ */
+ public $having;
+ /**
+ * @var array this is used to construct the UNION clause(s) in a SQL statement.
+ * Each array element can be either a string or a [[Query]] object representing a sub-query.
+ */
+ public $union;
+ /**
+ * @var array list of query parameter values indexed by parameter placeholders.
+ * For example, `array(':name' => 'Dan', ':age' => 31)`.
+ */
+ public $params;
+ /**
+ * @var string|callable $column the name of the column by which the query results should be indexed by.
+ * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
+ * row data. For more details, see [[indexBy()]]. This property is only used by [[all()]].
+ */
+ public $indexBy;
+
+
+ /**
+ * Creates a DB command that can be used to execute this query.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return Command the created DB command instance.
+ */
+ public function createCommand($db = null)
+ {
+ if ($db === null) {
+ $db = Yii::$app->getDb();
+ }
+ list ($sql, $params) = $db->getQueryBuilder()->build($this);
+ return $db->createCommand($sql, $params);
+ }
+
+ /**
+ * Sets the [[indexBy]] property.
+ * @param string|callable $column the name of the column by which the query results should be indexed by.
+ * This can also be a callable (e.g. anonymous function) that returns the index value based on the given
+ * row data. The signature of the callable should be:
+ *
+ * ~~~
+ * function ($row)
+ * {
+ * // return the index value corresponding to $row
+ * }
+ * ~~~
+ *
+ * @return static the query object itself
+ */
+ public function indexBy($column)
+ {
+ $this->indexBy = $column;
+ return $this;
+ }
+
+ /**
+ * Executes the query and returns all results as an array.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array the query results. If the query results in nothing, an empty array will be returned.
+ */
+ public function all($db = null)
+ {
+ $rows = $this->createCommand($db)->queryAll();
+ if ($this->indexBy === null) {
+ return $rows;
+ }
+ $result = array();
+ foreach ($rows as $row) {
+ if (is_string($this->indexBy)) {
+ $key = $row[$this->indexBy];
+ } else {
+ $key = call_user_func($this->indexBy, $row);
+ }
+ $result[$key] = $row;
+ }
+ return $result;
+ }
+
+ /**
+ * Executes the query and returns a single row of result.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array|boolean the first row (in terms of an array) of the query result. False is returned if the query
+ * results in nothing.
+ */
+ public function one($db = null)
+ {
+ return $this->createCommand($db)->queryOne();
+ }
+
+ /**
+ * Returns the query result as a scalar value.
+ * The value returned will be the first column in the first row of the query results.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return string|boolean the value of the first column in the first row of the query result.
+ * False is returned if the query result is empty.
+ */
+ public function scalar($db = null)
+ {
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Executes the query and returns the first column of the result.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return array the first column of the query result. An empty array is returned if the query results in nothing.
+ */
+ public function column($db = null)
+ {
+ return $this->createCommand($db)->queryColumn();
+ }
+
+ /**
+ * Returns the number of records.
+ * @param string $q the COUNT expression. Defaults to '*'.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer number of records
+ */
+ public function count($q = '*', $db = null)
+ {
+ $this->select = array("COUNT($q)");
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns the sum of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the sum of the specified column values
+ */
+ public function sum($q, $db = null)
+ {
+ $this->select = array("SUM($q)");
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns the average of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the average of the specified column values.
+ */
+ public function average($q, $db = null)
+ {
+ $this->select = array("AVG($q)");
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns the minimum of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the minimum of the specified column values.
+ */
+ public function min($q, $db = null)
+ {
+ $this->select = array("MIN($q)");
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns the maximum of the specified column values.
+ * @param string $q the column name or expression.
+ * Make sure you properly quote column names in the expression.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return integer the maximum of the specified column values.
+ */
+ public function max($q, $db = null)
+ {
+ $this->select = array("MAX($q)");
+ return $this->createCommand($db)->queryScalar();
+ }
+
+ /**
+ * Returns a value indicating whether the query result contains any row of data.
+ * @param Connection $db the database connection used to generate the SQL statement.
+ * If this parameter is not given, the `db` application component will be used.
+ * @return boolean whether the query result contains any row of data.
+ */
+ public function exists($db = null)
+ {
+ $this->select = array(new Expression('1'));
+ return $this->scalar($db) !== false;
+ }
+
+ /**
+ * Sets the SELECT part of the query.
+ * @param string|array $columns the columns to be selected.
+ * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
+ * Columns can contain table prefixes (e.g. "tbl_user.id") and/or column aliases (e.g. "tbl_user.id AS user_id").
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @param string $option additional option that should be appended to the 'SELECT' keyword. For example,
+ * in MySQL, the option 'SQL_CALC_FOUND_ROWS' can be used.
+ * @return static the query object itself
+ */
+ public function select($columns, $option = null)
+ {
+ if (!is_array($columns)) {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $this->select = $columns;
+ $this->selectOption = $option;
+ return $this;
+ }
+
+ /**
+ * Sets the value indicating whether to SELECT DISTINCT or not.
+ * @param bool $value whether to SELECT DISTINCT or not.
+ * @return static the query object itself
+ */
+ public function distinct($value = true)
+ {
+ $this->distinct = $value;
+ return $this;
+ }
+
+ /**
+ * Sets the FROM part of the query.
+ * @param string|array $tables the table(s) to be selected from. This can be either a string (e.g. `'tbl_user'`)
+ * or an array (e.g. `array('tbl_user', 'tbl_profile')`) specifying one or several table names.
+ * Table names can contain schema prefixes (e.g. `'public.tbl_user'`) and/or table aliases (e.g. `'tbl_user u'`).
+ * The method will automatically quote the table names unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ * @return static the query object itself
+ */
+ public function from($tables)
+ {
+ if (!is_array($tables)) {
+ $tables = preg_split('/\s*,\s*/', trim($tables), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $this->from = $tables;
+ return $this;
+ }
+
+ /**
+ * Sets the WHERE part of the query.
+ *
+ * The method requires a $condition parameter, and optionally a $params parameter
+ * specifying the values to be bound to the query.
+ *
+ * The $condition parameter should be either a string (e.g. 'id=1') or an array.
+ * If the latter, it must be in one of the following two formats:
+ *
+ * - hash format: `array('column1' => value1, 'column2' => value2, ...)`
+ * - operator format: `array(operator, operand1, operand2, ...)`
+ *
+ * A condition in hash format represents the following SQL expression in general:
+ * `column1=value1 AND column2=value2 AND ...`. In case when a value is an array,
+ * an `IN` expression will be generated. And if a value is null, `IS NULL` will be used
+ * in the generated expression. Below are some examples:
+ *
+ * - `array('type' => 1, 'status' => 2)` generates `(type = 1) AND (status = 2)`.
+ * - `array('id' => array(1, 2, 3), 'status' => 2)` generates `(id IN (1, 2, 3)) AND (status = 2)`.
+ * - `array('status' => null) generates `status IS NULL`.
+ *
+ * A condition in operator format generates the SQL expression according to the specified operator, which
+ * can be one of the followings:
+ *
+ * - `and`: the operands should be concatenated together using `AND`. For example,
+ * `array('and', 'id=1', 'id=2')` will generate `id=1 AND id=2`. If an operand is an array,
+ * it will be converted into a string using the rules described here. For example,
+ * `array('and', 'type=1', array('or', 'id=1', 'id=2'))` will generate `type=1 AND (id=1 OR id=2)`.
+ * The method will NOT do any quoting or escaping.
+ *
+ * - `or`: similar to the `and` operator except that the operands are concatenated using `OR`.
+ *
+ * - `between`: operand 1 should be the column name, and operand 2 and 3 should be the
+ * starting and ending values of the range that the column is in.
+ * For example, `array('between', 'id', 1, 10)` will generate `id BETWEEN 1 AND 10`.
+ *
+ * - `not between`: similar to `between` except the `BETWEEN` is replaced with `NOT BETWEEN`
+ * in the generated condition.
+ *
+ * - `in`: operand 1 should be a column or DB expression, and operand 2 be an array representing
+ * the range of the values that the column or DB expression should be in. For example,
+ * `array('in', 'id', array(1, 2, 3))` will generate `id IN (1, 2, 3)`.
+ * The method will properly quote the column name and escape values in the range.
+ *
+ * - `not in`: similar to the `in` operator except that `IN` is replaced with `NOT IN` in the generated condition.
+ *
+ * - `like`: operand 1 should be a column or DB expression, and operand 2 be a string or an array representing
+ * the values that the column or DB expression should be like.
+ * For example, `array('like', 'name', '%tester%')` will generate `name LIKE '%tester%'`.
+ * When the value range is given as an array, multiple `LIKE` predicates will be generated and concatenated
+ * using `AND`. For example, `array('like', 'name', array('%test%', '%sample%'))` will generate
+ * `name LIKE '%test%' AND name LIKE '%sample%'`.
+ * The method will properly quote the column name and escape values in the range.
+ *
+ * - `or like`: similar to the `like` operator except that `OR` is used to concatenate the `LIKE`
+ * predicates when operand 2 is an array.
+ *
+ * - `not like`: similar to the `like` operator except that `LIKE` is replaced with `NOT LIKE`
+ * in the generated condition.
+ *
+ * - `or not like`: similar to the `not like` operator except that `OR` is used to concatenate
+ * the `NOT LIKE` predicates.
+ *
+ * @param string|array $condition the conditions that should be put in the WHERE part.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see andWhere()
+ * @see orWhere()
+ */
+ public function where($condition, $params = array())
+ {
+ $this->where = $condition;
+ $this->addParams($params);
+ return $this;
+ }
+
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'AND' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see where()
+ * @see orWhere()
+ */
+ public function andWhere($condition, $params = array())
+ {
+ if ($this->where === null) {
+ $this->where = $condition;
+ } else {
+ $this->where = array('and', $this->where, $condition);
+ }
+ $this->addParams($params);
+ return $this;
+ }
+
+ /**
+ * Adds an additional WHERE condition to the existing one.
+ * The new condition and the existing one will be joined using the 'OR' operator.
+ * @param string|array $condition the new WHERE condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see where()
+ * @see andWhere()
+ */
+ public function orWhere($condition, $params = array())
+ {
+ if ($this->where === null) {
+ $this->where = $condition;
+ } else {
+ $this->where = array('or', $this->where, $condition);
+ }
+ $this->addParams($params);
+ return $this;
+ }
+
+ /**
+ * Appends a JOIN part to the query.
+ * The first parameter specifies what type of join it is.
+ * @param string $type the type of join, such as INNER JOIN, LEFT JOIN.
+ * @param string $table the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ * @param string|array $on the join condition that should appear in the ON part.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return Query the query object itself
+ */
+ public function join($type, $table, $on = '', $params = array())
+ {
+ $this->join[] = array($type, $table, $on);
+ return $this->addParams($params);
+ }
+
+ /**
+ * Appends an INNER JOIN part to the query.
+ * @param string $table the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ * @param string|array $on the join condition that should appear in the ON part.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return Query the query object itself
+ */
+ public function innerJoin($table, $on = '', $params = array())
+ {
+ $this->join[] = array('INNER JOIN', $table, $on);
+ return $this->addParams($params);
+ }
+
+ /**
+ * Appends a LEFT OUTER JOIN part to the query.
+ * @param string $table the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ * @param string|array $on the join condition that should appear in the ON part.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query
+ * @return Query the query object itself
+ */
+ public function leftJoin($table, $on = '', $params = array())
+ {
+ $this->join[] = array('LEFT JOIN', $table, $on);
+ return $this->addParams($params);
+ }
+
+ /**
+ * Appends a RIGHT OUTER JOIN part to the query.
+ * @param string $table the table to be joined.
+ * Table name can contain schema prefix (e.g. 'public.tbl_user') and/or table alias (e.g. 'tbl_user u').
+ * The method will automatically quote the table name unless it contains some parenthesis
+ * (which means the table is given as a sub-query or DB expression).
+ * @param string|array $on the join condition that should appear in the ON part.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query
+ * @return Query the query object itself
+ */
+ public function rightJoin($table, $on = '', $params = array())
+ {
+ $this->join[] = array('RIGHT JOIN', $table, $on);
+ return $this->addParams($params);
+ }
+
+ /**
+ * Sets the GROUP BY part of the query.
+ * @param string|array $columns the columns to be grouped by.
+ * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see addGroupBy()
+ */
+ public function groupBy($columns)
+ {
+ if (!is_array($columns)) {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ $this->groupBy = $columns;
+ return $this;
+ }
+
+ /**
+ * Adds additional group-by columns to the existing ones.
+ * @param string|array $columns additional columns to be grouped by.
+ * Columns can be specified in either a string (e.g. "id, name") or an array (e.g. array('id', 'name')).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see groupBy()
+ */
+ public function addGroupBy($columns)
+ {
+ if (!is_array($columns)) {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ }
+ if ($this->groupBy === null) {
+ $this->groupBy = $columns;
+ } else {
+ $this->groupBy = array_merge($this->groupBy, $columns);
+ }
+ return $this;
+ }
+
+ /**
+ * Sets the HAVING part of the query.
+ * @param string|array $condition the conditions to be put after HAVING.
+ * Please refer to [[where()]] on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see andHaving()
+ * @see orHaving()
+ */
+ public function having($condition, $params = array())
+ {
+ $this->having = $condition;
+ $this->addParams($params);
+ return $this;
+ }
+
+ /**
+ * Adds an additional HAVING condition to the existing one.
+ * The new condition and the existing one will be joined using the 'AND' operator.
+ * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see having()
+ * @see orHaving()
+ */
+ public function andHaving($condition, $params = array())
+ {
+ if ($this->having === null) {
+ $this->having = $condition;
+ } else {
+ $this->having = array('and', $this->having, $condition);
+ }
+ $this->addParams($params);
+ return $this;
+ }
+
+ /**
+ * Adds an additional HAVING condition to the existing one.
+ * The new condition and the existing one will be joined using the 'OR' operator.
+ * @param string|array $condition the new HAVING condition. Please refer to [[where()]]
+ * on how to specify this parameter.
+ * @param array $params the parameters (name => value) to be bound to the query.
+ * @return static the query object itself
+ * @see having()
+ * @see andHaving()
+ */
+ public function orHaving($condition, $params = array())
+ {
+ if ($this->having === null) {
+ $this->having = $condition;
+ } else {
+ $this->having = array('or', $this->having, $condition);
+ }
+ $this->addParams($params);
+ return $this;
+ }
+
+ /**
+ * Sets the ORDER BY part of the query.
+ * @param string|array $columns the columns (and the directions) to be ordered by.
+ * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+ * (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see addOrderBy()
+ */
+ public function orderBy($columns)
+ {
+ $this->orderBy = $this->normalizeOrderBy($columns);
+ return $this;
+ }
+
+ /**
+ * Adds additional ORDER BY columns to the query.
+ * @param string|array $columns the columns (and the directions) to be ordered by.
+ * Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
+ * (e.g. `array('id' => Query::SORT_ASC, 'name' => Query::SORT_DESC)`).
+ * The method will automatically quote the column names unless a column contains some parenthesis
+ * (which means the column contains a DB expression).
+ * @return static the query object itself
+ * @see orderBy()
+ */
+ public function addOrderBy($columns)
+ {
+ $columns = $this->normalizeOrderBy($columns);
+ if ($this->orderBy === null) {
+ $this->orderBy = $columns;
+ } else {
+ $this->orderBy = array_merge($this->orderBy, $columns);
+ }
+ return $this;
+ }
+
+ protected function normalizeOrderBy($columns)
+ {
+ if (is_array($columns)) {
+ return $columns;
+ } else {
+ $columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
+ $result = array();
+ foreach ($columns as $column) {
+ if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
+ $result[$matches[1]] = strcasecmp($matches[2], 'desc') ? self::SORT_ASC : self::SORT_DESC;
+ } else {
+ $result[$column] = self::SORT_ASC;
+ }
+ }
+ return $result;
+ }
+ }
+
+ /**
+ * Sets the LIMIT part of the query.
+ * @param integer $limit the limit. Use null or negative value to disable limit.
+ * @return static the query object itself
+ */
+ public function limit($limit)
+ {
+ $this->limit = $limit;
+ return $this;
+ }
+
+ /**
+ * Sets the OFFSET part of the query.
+ * @param integer $offset the offset. Use null or negative value to disable offset.
+ * @return static the query object itself
+ */
+ public function offset($offset)
+ {
+ $this->offset = $offset;
+ return $this;
+ }
+
+ /**
+ * Appends a SQL statement using UNION operator.
+ * @param string|Query $sql the SQL statement to be appended using UNION
+ * @return static the query object itself
+ */
+ public function union($sql)
+ {
+ $this->union[] = $sql;
+ return $this;
+ }
+
+ /**
+ * Sets the parameters to be bound to the query.
+ * @param array $params list of query parameter values indexed by parameter placeholders.
+ * For example, `array(':name' => 'Dan', ':age' => 31)`.
+ * @return static the query object itself
+ * @see addParams()
+ */
+ public function params($params)
+ {
+ $this->params = $params;
+ return $this;
+ }
+
+ /**
+ * Adds additional parameters to be bound to the query.
+ * @param array $params list of query parameter values indexed by parameter placeholders.
+ * For example, `array(':name' => 'Dan', ':age' => 31)`.
+ * @return static the query object itself
+ * @see params()
+ */
+ public function addParams($params)
+ {
+ if (!empty($params)) {
+ if ($this->params === null) {
+ $this->params = $params;
+ } else {
+ foreach ($params as $name => $value) {
+ if (is_integer($name)) {
+ $this->params[] = $value;
+ } else {
+ $this->params[$name] = $value;
+ }
+ }
+ }
+ }
+ return $this;
+ }
+}
diff --git a/framework/yii/db/QueryBuilder.php b/framework/yii/db/QueryBuilder.php
new file mode 100644
index 0000000..f210f65
--- /dev/null
+++ b/framework/yii/db/QueryBuilder.php
@@ -0,0 +1,972 @@
+
+ * @since 2.0
+ */
+class QueryBuilder extends \yii\base\Object
+{
+ /**
+ * The prefix for automatically generated query binding parameters.
+ */
+ const PARAM_PREFIX = ':qp';
+
+ /**
+ * @var Connection the database connection.
+ */
+ public $db;
+ /**
+ * @var string the separator between different fragments of a SQL statement.
+ * Defaults to an empty space. This is mainly used by [[build()]] when generating a SQL statement.
+ */
+ public $separator = " ";
+ /**
+ * @var array the abstract column types mapped to physical column types.
+ * This is mainly used to support creating/modifying tables using DB-independent data type specifications.
+ * Child classes should override this property to declare supported type mappings.
+ */
+ public $typeMap = array();
+
+ /**
+ * Constructor.
+ * @param Connection $connection the database connection.
+ * @param array $config name-value pairs that will be used to initialize the object properties
+ */
+ public function __construct($connection, $config = array())
+ {
+ $this->db = $connection;
+ parent::__construct($config);
+ }
+
+ /**
+ * Generates a SELECT SQL statement from a [[Query]] object.
+ * @param Query $query the [[Query]] object from which the SQL statement will be generated
+ * @return array the generated SQL statement (the first array element) and the corresponding
+ * parameters to be bound to the SQL statement (the second array element).
+ */
+ public function build($query)
+ {
+ $params = $query->params;
+ $clauses = array(
+ $this->buildSelect($query->select, $query->distinct, $query->selectOption),
+ $this->buildFrom($query->from),
+ $this->buildJoin($query->join, $params),
+ $this->buildWhere($query->where, $params),
+ $this->buildGroupBy($query->groupBy),
+ $this->buildHaving($query->having, $params),
+ $this->buildUnion($query->union, $params),
+ $this->buildOrderBy($query->orderBy),
+ $this->buildLimit($query->limit, $query->offset),
+ );
+ return array(implode($this->separator, array_filter($clauses)), $params);
+ }
+
+ /**
+ * Creates an INSERT SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->insert('tbl_user', array(
+ * 'name' => 'Sam',
+ * 'age' => 30,
+ * ), $params);
+ * ~~~
+ *
+ * The method will properly escape the table and column names.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column data (name => value) to be inserted into the table.
+ * @param array $params the binding parameters that will be generated by this method.
+ * They should be bound to the DB command later.
+ * @return string the INSERT SQL
+ */
+ public function insert($table, $columns, &$params)
+ {
+ if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
+ $columnSchemas = $tableSchema->columns;
+ } else {
+ $columnSchemas = array();
+ }
+ $names = array();
+ $placeholders = array();
+ foreach ($columns as $name => $value) {
+ $names[] = $this->db->quoteColumnName($name);
+ if ($value instanceof Expression) {
+ $placeholders[] = $value->expression;
+ foreach ($value->params as $n => $v) {
+ $params[$n] = $v;
+ }
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $placeholders[] = $phName;
+ $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->typecast($value) : $value;
+ }
+ }
+
+ return 'INSERT INTO ' . $this->db->quoteTableName($table)
+ . ' (' . implode(', ', $names) . ') VALUES ('
+ . implode(', ', $placeholders) . ')';
+ }
+
+ /**
+ * Generates a batch INSERT SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array(
+ * array('Tom', 30),
+ * array('Jane', 20),
+ * array('Linda', 25),
+ * ))->execute();
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch inserted into the table
+ * @return string the batch INSERT SQL statement
+ * @throws NotSupportedException if this is not supported by the underlying DBMS
+ */
+ public function batchInsert($table, $columns, $rows)
+ {
+ throw new NotSupportedException($this->db->getDriverName() . ' does not support batch insert.');
+
+ }
+
+ /**
+ * Creates an UPDATE SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $params = array();
+ * $sql = $queryBuilder->update('tbl_user', array(
+ * 'status' => 1,
+ * ), 'age > 30', $params);
+ * ~~~
+ *
+ * The method will properly escape the table and column names.
+ *
+ * @param string $table the table to be updated.
+ * @param array $columns the column data (name => value) to be updated.
+ * @param array|string $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the binding parameters that will be modified by this method
+ * so that they can be bound to the DB command later.
+ * @return string the UPDATE SQL
+ */
+ public function update($table, $columns, $condition, &$params)
+ {
+ if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
+ $columnSchemas = $tableSchema->columns;
+ } else {
+ $columnSchemas = array();
+ }
+
+ $lines = array();
+ foreach ($columns as $name => $value) {
+ if ($value instanceof Expression) {
+ $lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression;
+ foreach ($value->params as $n => $v) {
+ $params[$n] = $v;
+ }
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $lines[] = $this->db->quoteColumnName($name) . '=' . $phName;
+ $params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->typecast($value) : $value;
+ }
+ }
+
+ $sql = 'UPDATE ' . $this->db->quoteTableName($table) . ' SET ' . implode(', ', $lines);
+ $where = $this->buildWhere($condition, $params);
+ return $where === '' ? $sql : $sql . ' ' . $where;
+ }
+
+ /**
+ * Creates a DELETE SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->delete('tbl_user', 'status = 0');
+ * ~~~
+ *
+ * The method will properly escape the table and column names.
+ *
+ * @param string $table the table where the data will be deleted from.
+ * @param array|string $condition the condition that will be put in the WHERE part. Please
+ * refer to [[Query::where()]] on how to specify condition.
+ * @param array $params the binding parameters that will be modified by this method
+ * so that they can be bound to the DB command later.
+ * @return string the DELETE SQL
+ */
+ public function delete($table, $condition, &$params)
+ {
+ $sql = 'DELETE FROM ' . $this->db->quoteTableName($table);
+ $where = $this->buildWhere($condition, $params);
+ return $where === '' ? $sql : $sql . ' ' . $where;
+ }
+
+ /**
+ * Builds a SQL statement for creating a new DB table.
+ *
+ * The columns in the new table should be specified as name-definition pairs (e.g. 'name' => 'string'),
+ * where name stands for a column name which will be properly quoted by the method, and definition
+ * stands for the column type which can contain an abstract DB type.
+ * The [[getColumnType()]] method will be invoked to convert any abstract type into a physical one.
+ *
+ * If a column is specified with definition only (e.g. 'PRIMARY KEY (name, type)'), it will be directly
+ * inserted into the generated SQL.
+ *
+ * For example,
+ *
+ * ~~~
+ * $sql = $queryBuilder->createTable('tbl_user', array(
+ * 'id' => 'pk',
+ * 'name' => 'string',
+ * 'age' => 'integer',
+ * ));
+ * ~~~
+ *
+ * @param string $table the name of the table to be created. The name will be properly quoted by the method.
+ * @param array $columns the columns (name => definition) in the new table.
+ * @param string $options additional SQL fragment that will be appended to the generated SQL.
+ * @return string the SQL statement for creating a new DB table.
+ */
+ public function createTable($table, $columns, $options = null)
+ {
+ $cols = array();
+ foreach ($columns as $name => $type) {
+ if (is_string($name)) {
+ $cols[] = "\t" . $this->db->quoteColumnName($name) . ' ' . $this->getColumnType($type);
+ } else {
+ $cols[] = "\t" . $type;
+ }
+ }
+ $sql = "CREATE TABLE " . $this->db->quoteTableName($table) . " (\n" . implode(",\n", $cols) . "\n)";
+ return $options === null ? $sql : $sql . ' ' . $options;
+ }
+
+ /**
+ * Builds a SQL statement for renaming a DB table.
+ * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB table.
+ */
+ public function renameTable($oldName, $newName)
+ {
+ return 'RENAME TABLE ' . $this->db->quoteTableName($oldName) . ' TO ' . $this->db->quoteTableName($newName);
+ }
+
+ /**
+ * Builds a SQL statement for dropping a DB table.
+ * @param string $table the table to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a DB table.
+ */
+ public function dropTable($table)
+ {
+ return "DROP TABLE " . $this->db->quoteTableName($table);
+ }
+
+ /**
+ * Builds a SQL statement for adding a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint.
+ * @param string $table the table that the primary key constraint will be added to.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ * @return string the SQL statement for adding a primary key constraint to an existing table.
+ */
+ public function addPrimaryKey($name, $table, $columns)
+ {
+ if (is_string($columns)) {
+ $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
+ }
+
+ foreach ($columns as $i => $col) {
+ $columns[$i] = $this->db->quoteColumnName($col);
+ }
+
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ADD CONSTRAINT '
+ . $this->db->quoteColumnName($name) . ' PRIMARY KEY ('
+ . implode(', ', $columns). ' )';
+ }
+
+ /**
+ * Builds a SQL statement for removing a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ * @return string the SQL statement for removing a primary key constraint from an existing table. *
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
+ }
+
+ /**
+ * Builds a SQL statement for truncating a DB table.
+ * @param string $table the table to be truncated. The name will be properly quoted by the method.
+ * @return string the SQL statement for truncating a DB table.
+ */
+ public function truncateTable($table)
+ {
+ return "TRUNCATE TABLE " . $this->db->quoteTableName($table);
+ }
+
+ /**
+ * Builds a SQL statement for adding a new DB column.
+ * @param string $table the table that the new column will be added to. The table name will be properly quoted by the method.
+ * @param string $column the name of the new column. The name will be properly quoted by the method.
+ * @param string $type the column type. The [[getColumnType()]] method will be invoked to convert abstract column type (if any)
+ * into the physical one. Anything that is not recognized as abstract type will be kept in the generated SQL.
+ * For example, 'string' will be turned into 'varchar(255)', while 'string not null' will become 'varchar(255) not null'.
+ * @return string the SQL statement for adding a new column.
+ */
+ public function addColumn($table, $column, $type)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' ADD ' . $this->db->quoteColumnName($column) . ' '
+ . $this->getColumnType($type);
+ }
+
+ /**
+ * Builds a SQL statement for dropping a DB column.
+ * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
+ * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a DB column.
+ */
+ public function dropColumn($table, $column)
+ {
+ return "ALTER TABLE " . $this->db->quoteTableName($table)
+ . " DROP COLUMN " . $this->db->quoteColumnName($column);
+ }
+
+ /**
+ * Builds a SQL statement for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $oldName the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB column.
+ */
+ public function renameColumn($table, $oldName, $newName)
+ {
+ return "ALTER TABLE " . $this->db->quoteTableName($table)
+ . " RENAME COLUMN " . $this->db->quoteColumnName($oldName)
+ . " TO " . $this->db->quoteColumnName($newName);
+ }
+
+ /**
+ * Builds a SQL statement for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
+ * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
+ * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
+ * will become 'varchar(255) not null'.
+ * @return string the SQL statement for changing the definition of a column.
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' CHANGE '
+ . $this->db->quoteColumnName($column) . ' '
+ . $this->db->quoteColumnName($column) . ' '
+ . $this->getColumnType($type);
+ }
+
+ /**
+ * Builds a SQL statement for adding a foreign key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the foreign key constraint.
+ * @param string $table the table that the foreign key constraint will be added to.
+ * @param string|array $columns the name of the column to that the constraint will be added on.
+ * If there are multiple columns, separate them with commas or use an array to represent them.
+ * @param string $refTable the table that the foreign key references to.
+ * @param string|array $refColumns the name of the column that the foreign key references to.
+ * If there are multiple columns, separate them with commas or use an array to represent them.
+ * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @return string the SQL statement for adding a foreign key constraint to an existing table.
+ */
+ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+ {
+ $sql = 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' ADD CONSTRAINT ' . $this->db->quoteColumnName($name)
+ . ' FOREIGN KEY (' . $this->buildColumns($columns) . ')'
+ . ' REFERENCES ' . $this->db->quoteTableName($refTable)
+ . ' (' . $this->buildColumns($refColumns) . ')';
+ if ($delete !== null) {
+ $sql .= ' ON DELETE ' . $delete;
+ }
+ if ($update !== null) {
+ $sql .= ' ON UPDATE ' . $update;
+ }
+ return $sql;
+ }
+
+ /**
+ * Builds a SQL statement for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a foreign key constraint.
+ */
+ public function dropForeignKey($name, $table)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' DROP CONSTRAINT ' . $this->db->quoteColumnName($name);
+ }
+
+ /**
+ * Builds a SQL statement for creating a new index.
+ * @param string $name the name of the index. The name will be properly quoted by the method.
+ * @param string $table the table that the new index will be created for. The table name will be properly quoted by the method.
+ * @param string|array $columns the column(s) that should be included in the index. If there are multiple columns,
+ * separate them with commas or use an array to represent them. Each column name will be properly quoted
+ * by the method, unless a parenthesis is found in the name.
+ * @param boolean $unique whether to add UNIQUE constraint on the created index.
+ * @return string the SQL statement for creating a new index.
+ */
+ public function createIndex($name, $table, $columns, $unique = false)
+ {
+ return ($unique ? 'CREATE UNIQUE INDEX ' : 'CREATE INDEX ')
+ . $this->db->quoteTableName($name) . ' ON '
+ . $this->db->quoteTableName($table)
+ . ' (' . $this->buildColumns($columns) . ')';
+ }
+
+ /**
+ * Builds a SQL statement for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping an index.
+ */
+ public function dropIndex($name, $table)
+ {
+ return 'DROP INDEX ' . $this->db->quoteTableName($name) . ' ON ' . $this->db->quoteTableName($table);
+ }
+
+ /**
+ * Creates a SQL statement for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $table the name of the table whose primary key sequence will be reset
+ * @param array|string $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return string the SQL statement for resetting sequence
+ * @throws NotSupportedException if this is not supported by the underlying DBMS
+ */
+ public function resetSequence($table, $value = null)
+ {
+ throw new NotSupportedException($this->db->getDriverName() . ' does not support resetting sequence.');
+ }
+
+ /**
+ * Builds a SQL statement for enabling or disabling integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
+ * @return string the SQL statement for checking integrity
+ * @throws NotSupportedException if this is not supported by the underlying DBMS
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ throw new NotSupportedException($this->db->getDriverName() . ' does not support enabling/disabling integrity check.');
+ }
+
+ /**
+ * Converts an abstract column type into a physical column type.
+ * The conversion is done using the type map specified in [[typeMap]].
+ * The following abstract column types are supported (using MySQL as an example to explain the corresponding
+ * physical types):
+ *
+ * - `pk`: an auto-incremental primary key type, will be converted into "int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY"
+ * - `bigpk`: an auto-incremental primary key type, will be converted into "bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY"
+ * - `string`: string type, will be converted into "varchar(255)"
+ * - `text`: a long string type, will be converted into "text"
+ * - `smallint`: a small integer type, will be converted into "smallint(6)"
+ * - `integer`: integer type, will be converted into "int(11)"
+ * - `bigint`: a big integer type, will be converted into "bigint(20)"
+ * - `boolean`: boolean type, will be converted into "tinyint(1)"
+ * - `float``: float number type, will be converted into "float"
+ * - `decimal`: decimal number type, will be converted into "decimal"
+ * - `datetime`: datetime type, will be converted into "datetime"
+ * - `timestamp`: timestamp type, will be converted into "timestamp"
+ * - `time`: time type, will be converted into "time"
+ * - `date`: date type, will be converted into "date"
+ * - `money`: money type, will be converted into "decimal(19,4)"
+ * - `binary`: binary data type, will be converted into "blob"
+ *
+ * If the abstract type contains two or more parts separated by spaces (e.g. "string NOT NULL"), then only
+ * the first part will be converted, and the rest of the parts will be appended to the converted result.
+ * For example, 'string NOT NULL' is converted to 'varchar(255) NOT NULL'.
+ *
+ * For some of the abstract types you can also specify a length or precision constraint
+ * by prepending it in round brackets directly to the type.
+ * For example `string(32)` will be converted into "varchar(32)" on a MySQL database.
+ * If the underlying DBMS does not support these kind of constraints for a type it will
+ * be ignored.
+ *
+ * If a type cannot be found in [[typeMap]], it will be returned without any change.
+ * @param string $type abstract column type
+ * @return string physical column type.
+ */
+ public function getColumnType($type)
+ {
+ if (isset($this->typeMap[$type])) {
+ return $this->typeMap[$type];
+ } elseif (preg_match('/^(\w+)\((.+?)\)(.*)$/', $type, $matches)) {
+ if (isset($this->typeMap[$matches[1]])) {
+ return preg_replace('/\(.+\)/', '(' . $matches[2] . ')', $this->typeMap[$matches[1]]) . $matches[3];
+ }
+ } elseif (preg_match('/^(\w+)\s+/', $type, $matches)) {
+ if (isset($this->typeMap[$matches[1]])) {
+ return preg_replace('/^\w+/', $this->typeMap[$matches[1]], $type);
+ }
+ }
+ return $type;
+ }
+
+ /**
+ * @param array $columns
+ * @param boolean $distinct
+ * @param string $selectOption
+ * @return string the SELECT clause built from [[query]].
+ */
+ public function buildSelect($columns, $distinct = false, $selectOption = null)
+ {
+ $select = $distinct ? 'SELECT DISTINCT' : 'SELECT';
+ if ($selectOption !== null) {
+ $select .= ' ' . $selectOption;
+ }
+
+ if (empty($columns)) {
+ return $select . ' *';
+ }
+
+ foreach ($columns as $i => $column) {
+ if (is_object($column)) {
+ $columns[$i] = (string)$column;
+ } elseif (strpos($column, '(') === false) {
+ if (preg_match('/^(.*?)(?i:\s+as\s+|\s+)([\w\-_\.]+)$/', $column, $matches)) {
+ $columns[$i] = $this->db->quoteColumnName($matches[1]) . ' AS ' . $this->db->quoteColumnName($matches[2]);
+ } else {
+ $columns[$i] = $this->db->quoteColumnName($column);
+ }
+ }
+ }
+
+ if (is_array($columns)) {
+ $columns = implode(', ', $columns);
+ }
+
+ return $select . ' ' . $columns;
+ }
+
+ /**
+ * @param array $tables
+ * @return string the FROM clause built from [[query]].
+ */
+ public function buildFrom($tables)
+ {
+ if (empty($tables)) {
+ return '';
+ }
+
+ foreach ($tables as $i => $table) {
+ if (strpos($table, '(') === false) {
+ if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
+ $tables[$i] = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
+ } else {
+ $tables[$i] = $this->db->quoteTableName($table);
+ }
+ }
+ }
+
+ if (is_array($tables)) {
+ $tables = implode(', ', $tables);
+ }
+
+ return 'FROM ' . $tables;
+ }
+
+ /**
+ * @param string|array $joins
+ * @param array $params the binding parameters to be populated
+ * @return string the JOIN clause built from [[query]].
+ * @throws Exception if the $joins parameter is not in proper format
+ */
+ public function buildJoin($joins, &$params)
+ {
+ if (empty($joins)) {
+ return '';
+ }
+
+ foreach ($joins as $i => $join) {
+ if (is_object($join)) {
+ $joins[$i] = (string)$join;
+ } elseif (is_array($join) && isset($join[0], $join[1])) {
+ // 0:join type, 1:table name, 2:on-condition
+ $table = $join[1];
+ if (strpos($table, '(') === false) {
+ if (preg_match('/^(.*?)(?i:\s+as|)\s+([^ ]+)$/', $table, $matches)) { // with alias
+ $table = $this->db->quoteTableName($matches[1]) . ' ' . $this->db->quoteTableName($matches[2]);
+ } else {
+ $table = $this->db->quoteTableName($table);
+ }
+ }
+ $joins[$i] = $join[0] . ' ' . $table;
+ if (isset($join[2])) {
+ $condition = $this->buildCondition($join[2], $params);
+ if ($condition !== '') {
+ $joins[$i] .= ' ON ' . $condition;
+ }
+ }
+ } else {
+ throw new Exception('A join clause must be specified as an array of join type, join table, and optionally join condition.');
+ }
+ }
+
+ return implode($this->separator, $joins);
+ }
+
+ /**
+ * @param string|array $condition
+ * @param array $params the binding parameters to be populated
+ * @return string the WHERE clause built from [[query]].
+ */
+ public function buildWhere($condition, &$params)
+ {
+ $where = $this->buildCondition($condition, $params);
+ return $where === '' ? '' : 'WHERE ' . $where;
+ }
+
+ /**
+ * @param array $columns
+ * @return string the GROUP BY clause
+ */
+ public function buildGroupBy($columns)
+ {
+ return empty($columns) ? '' : 'GROUP BY ' . $this->buildColumns($columns);
+ }
+
+ /**
+ * @param string|array $condition
+ * @param array $params the binding parameters to be populated
+ * @return string the HAVING clause built from [[query]].
+ */
+ public function buildHaving($condition, &$params)
+ {
+ $having = $this->buildCondition($condition, $params);
+ return $having === '' ? '' : 'HAVING ' . $having;
+ }
+
+ /**
+ * @param array $columns
+ * @return string the ORDER BY clause built from [[query]].
+ */
+ public function buildOrderBy($columns)
+ {
+ if (empty($columns)) {
+ return '';
+ }
+ $orders = array();
+ foreach ($columns as $name => $direction) {
+ if (is_object($direction)) {
+ $orders[] = (string)$direction;
+ } else {
+ $orders[] = $this->db->quoteColumnName($name) . ($direction === Query::SORT_DESC ? ' DESC' : '');
+ }
+ }
+
+ return 'ORDER BY ' . implode(', ', $orders);
+ }
+
+ /**
+ * @param integer $limit
+ * @param integer $offset
+ * @return string the LIMIT and OFFSET clauses built from [[query]].
+ */
+ public function buildLimit($limit, $offset)
+ {
+ $sql = '';
+ if ($limit !== null && $limit >= 0) {
+ $sql = 'LIMIT ' . (int)$limit;
+ }
+ if ($offset > 0) {
+ $sql .= ' OFFSET ' . (int)$offset;
+ }
+ return ltrim($sql);
+ }
+
+ /**
+ * @param array $unions
+ * @param array $params the binding parameters to be populated
+ * @return string the UNION clause built from [[query]].
+ */
+ public function buildUnion($unions, &$params)
+ {
+ if (empty($unions)) {
+ return '';
+ }
+ foreach ($unions as $i => $union) {
+ if ($union instanceof Query) {
+ // save the original parameters so that we can restore them later to prevent from modifying the query object
+ $originalParams = $union->params;
+ $union->addParams($params);
+ list ($unions[$i], $params) = $this->build($union);
+ $union->params = $originalParams;
+ }
+ }
+ return "UNION (\n" . implode("\n) UNION (\n", $unions) . "\n)";
+ }
+
+ /**
+ * Processes columns and properly quote them if necessary.
+ * It will join all columns into a string with comma as separators.
+ * @param string|array $columns the columns to be processed
+ * @return string the processing result
+ */
+ public function buildColumns($columns)
+ {
+ if (!is_array($columns)) {
+ if (strpos($columns, '(') !== false) {
+ return $columns;
+ } else {
+ $columns = preg_split('/\s*,\s*/', $columns, -1, PREG_SPLIT_NO_EMPTY);
+ }
+ }
+ foreach ($columns as $i => $column) {
+ if (is_object($column)) {
+ $columns[$i] = (string)$column;
+ } elseif (strpos($column, '(') === false) {
+ $columns[$i] = $this->db->quoteColumnName($column);
+ }
+ }
+ return is_array($columns) ? implode(', ', $columns) : $columns;
+ }
+
+
+ /**
+ * Parses the condition specification and generates the corresponding SQL expression.
+ * @param string|array $condition the condition specification. Please refer to [[Query::where()]]
+ * on how to specify a condition.
+ * @param array $params the binding parameters to be populated
+ * @return string the generated SQL expression
+ * @throws \yii\db\Exception if the condition is in bad format
+ */
+ public function buildCondition($condition, &$params)
+ {
+ static $builders = array(
+ 'AND' => 'buildAndCondition',
+ 'OR' => 'buildAndCondition',
+ 'BETWEEN' => 'buildBetweenCondition',
+ 'NOT BETWEEN' => 'buildBetweenCondition',
+ 'IN' => 'buildInCondition',
+ 'NOT IN' => 'buildInCondition',
+ 'LIKE' => 'buildLikeCondition',
+ 'NOT LIKE' => 'buildLikeCondition',
+ 'OR LIKE' => 'buildLikeCondition',
+ 'OR NOT LIKE' => 'buildLikeCondition',
+ );
+
+ if (!is_array($condition)) {
+ return (string)$condition;
+ } elseif (empty($condition)) {
+ return '';
+ }
+ if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
+ $operator = strtoupper($condition[0]);
+ if (isset($builders[$operator])) {
+ $method = $builders[$operator];
+ array_shift($condition);
+ return $this->$method($operator, $condition, $params);
+ } else {
+ throw new Exception('Found unknown operator in query: ' . $operator);
+ }
+ } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
+ return $this->buildHashCondition($condition, $params);
+ }
+ }
+
+ private function buildHashCondition($condition, &$params)
+ {
+ $parts = array();
+ foreach ($condition as $column => $value) {
+ if (is_array($value)) { // IN condition
+ $parts[] = $this->buildInCondition('IN', array($column, $value), $params);
+ } else {
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+ if ($value === null) {
+ $parts[] = "$column IS NULL";
+ } elseif ($value instanceof Expression) {
+ $parts[] = "$column=" . $value->expression;
+ foreach ($value->params as $n => $v) {
+ $params[$n] = $v;
+ }
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $parts[] = "$column=$phName";
+ $params[$phName] = $value;
+ }
+ }
+ }
+ return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
+ }
+
+ private function buildAndCondition($operator, $operands, &$params)
+ {
+ $parts = array();
+ foreach ($operands as $operand) {
+ if (is_array($operand)) {
+ $operand = $this->buildCondition($operand, $params);
+ }
+ if ($operand !== '') {
+ $parts[] = $operand;
+ }
+ }
+ if (!empty($parts)) {
+ return '(' . implode(") $operator (", $parts) . ')';
+ } else {
+ return '';
+ }
+ }
+
+ private function buildBetweenCondition($operator, $operands, &$params)
+ {
+ if (!isset($operands[0], $operands[1], $operands[2])) {
+ throw new Exception("Operator '$operator' requires three operands.");
+ }
+
+ list($column, $value1, $value2) = $operands;
+
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+ $phName1 = self::PARAM_PREFIX . count($params);
+ $params[$phName1] = $value1;
+ $phName2 = self::PARAM_PREFIX . count($params);
+ $params[$phName2] = $value2;
+
+ return "$column $operator $phName1 AND $phName2";
+ }
+
+ private function buildInCondition($operator, $operands, &$params)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new Exception("Operator '$operator' requires two operands.");
+ }
+
+ list($column, $values) = $operands;
+
+ $values = (array)$values;
+
+ if (empty($values) || $column === array()) {
+ return $operator === 'IN' ? '0=1' : '';
+ }
+
+ if (count($column) > 1) {
+ return $this->buildCompositeInCondition($operator, $column, $values, $params);
+ } elseif (is_array($column)) {
+ $column = reset($column);
+ }
+ foreach ($values as $i => $value) {
+ if (is_array($value)) {
+ $value = isset($value[$column]) ? $value[$column] : null;
+ }
+ if ($value === null) {
+ $values[$i] = 'NULL';
+ } elseif ($value instanceof Expression) {
+ $values[$i] = $value->expression;
+ foreach ($value->params as $n => $v) {
+ $params[$n] = $v;
+ }
+ } else {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = $value;
+ $values[$i] = $phName;
+ }
+ }
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+
+ if (count($values) > 1) {
+ return "$column $operator (" . implode(', ', $values) . ')';
+ } else {
+ $operator = $operator === 'IN' ? '=' : '<>';
+ return "$column$operator{$values[0]}";
+ }
+ }
+
+ protected function buildCompositeInCondition($operator, $columns, $values, &$params)
+ {
+ $vss = array();
+ foreach ($values as $value) {
+ $vs = array();
+ foreach ($columns as $column) {
+ if (isset($value[$column])) {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = $value[$column];
+ $vs[] = $phName;
+ } else {
+ $vs[] = 'NULL';
+ }
+ }
+ $vss[] = '(' . implode(', ', $vs) . ')';
+ }
+ foreach ($columns as $i => $column) {
+ if (strpos($column, '(') === false) {
+ $columns[$i] = $this->db->quoteColumnName($column);
+ }
+ }
+ return '(' . implode(', ', $columns) . ") $operator (" . implode(', ', $vss) . ')';
+ }
+
+ private function buildLikeCondition($operator, $operands, &$params)
+ {
+ if (!isset($operands[0], $operands[1])) {
+ throw new Exception("Operator '$operator' requires two operands.");
+ }
+
+ list($column, $values) = $operands;
+
+ $values = (array)$values;
+
+ if (empty($values)) {
+ return $operator === 'LIKE' || $operator === 'OR LIKE' ? '0=1' : '';
+ }
+
+ if ($operator === 'LIKE' || $operator === 'NOT LIKE') {
+ $andor = ' AND ';
+ } else {
+ $andor = ' OR ';
+ $operator = $operator === 'OR LIKE' ? 'LIKE' : 'NOT LIKE';
+ }
+
+ if (strpos($column, '(') === false) {
+ $column = $this->db->quoteColumnName($column);
+ }
+
+ $parts = array();
+ foreach ($values as $value) {
+ $phName = self::PARAM_PREFIX . count($params);
+ $params[$phName] = $value;
+ $parts[] = "$column $operator $phName";
+ }
+
+ return implode($andor, $parts);
+ }
+}
diff --git a/framework/yii/db/Schema.php b/framework/yii/db/Schema.php
new file mode 100644
index 0000000..e32917f
--- /dev/null
+++ b/framework/yii/db/Schema.php
@@ -0,0 +1,400 @@
+
+ * @since 2.0
+ */
+abstract class Schema extends Object
+{
+ /**
+ * The followings are the supported abstract column data types.
+ */
+ const TYPE_PK = 'pk';
+ const TYPE_BIGPK = 'bigpk';
+ const TYPE_STRING = 'string';
+ const TYPE_TEXT = 'text';
+ const TYPE_SMALLINT = 'smallint';
+ const TYPE_INTEGER = 'integer';
+ const TYPE_BIGINT = 'bigint';
+ const TYPE_FLOAT = 'float';
+ const TYPE_DECIMAL = 'decimal';
+ const TYPE_DATETIME = 'datetime';
+ const TYPE_TIMESTAMP = 'timestamp';
+ const TYPE_TIME = 'time';
+ const TYPE_DATE = 'date';
+ const TYPE_BINARY = 'binary';
+ const TYPE_BOOLEAN = 'boolean';
+ const TYPE_MONEY = 'money';
+
+ /**
+ * @var Connection the database connection
+ */
+ public $db;
+ /**
+ * @var array list of ALL table names in the database
+ */
+ private $_tableNames = array();
+ /**
+ * @var array list of loaded table metadata (table name => TableSchema)
+ */
+ private $_tables = array();
+ /**
+ * @var QueryBuilder the query builder for this database
+ */
+ private $_builder;
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema DBMS-dependent table metadata, null if the table does not exist.
+ */
+ abstract protected function loadTableSchema($name);
+
+
+ /**
+ * Obtains the metadata for the named table.
+ * @param string $name table name. The table name may contain schema name if any. Do not quote the table name.
+ * @param boolean $refresh whether to reload the table schema even if it is found in the cache.
+ * @return TableSchema table metadata. Null if the named table does not exist.
+ */
+ public function getTableSchema($name, $refresh = false)
+ {
+ if (isset($this->_tables[$name]) && !$refresh) {
+ return $this->_tables[$name];
+ }
+
+ $db = $this->db;
+ $realName = $this->getRawTableName($name);
+
+ if ($db->enableSchemaCache && !in_array($name, $db->schemaCacheExclude, true)) {
+ /** @var $cache Cache */
+ $cache = is_string($db->schemaCache) ? Yii::$app->getComponent($db->schemaCache) : $db->schemaCache;
+ if ($cache instanceof Cache) {
+ $key = $this->getCacheKey($name);
+ if ($refresh || ($table = $cache->get($key)) === false) {
+ $table = $this->loadTableSchema($realName);
+ if ($table !== null) {
+ $cache->set($key, $table, $db->schemaCacheDuration, new GroupDependency($this->getCacheGroup()));
+ }
+ }
+ return $this->_tables[$name] = $table;
+ }
+ }
+ return $this->_tables[$name] = $table = $this->loadTableSchema($realName);
+ }
+
+ /**
+ * Returns the cache key for the specified table name.
+ * @param string $name the table name
+ * @return mixed the cache key
+ */
+ protected function getCacheKey($name)
+ {
+ return array(
+ __CLASS__,
+ $this->db->dsn,
+ $this->db->username,
+ $name,
+ );
+ }
+
+ /**
+ * Returns the cache group name.
+ * This allows [[refresh()]] to invalidate all cached table schemas.
+ * @return string the cache group name
+ */
+ protected function getCacheGroup()
+ {
+ return md5(serialize(array(
+ __CLASS__,
+ $this->db->dsn,
+ $this->db->username,
+ )));
+ }
+
+ /**
+ * Returns the metadata for all tables in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
+ * @param boolean $refresh whether to fetch the latest available table schemas. If this is false,
+ * cached data may be returned if available.
+ * @return TableSchema[] the metadata for all tables in the database.
+ * Each array element is an instance of [[TableSchema]] or its child class.
+ */
+ public function getTableSchemas($schema = '', $refresh = false)
+ {
+ $tables = array();
+ foreach ($this->getTableNames($schema, $refresh) as $name) {
+ if ($schema !== '') {
+ $name = $schema . '.' . $name;
+ }
+ if (($table = $this->getTableSchema($name, $refresh)) !== null) {
+ $tables[] = $table;
+ }
+ }
+ return $tables;
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema name.
+ * If not empty, the returned table names will be prefixed with the schema name.
+ * @param boolean $refresh whether to fetch the latest available table names. If this is false,
+ * table names fetched previously (if available) will be returned.
+ * @return string[] all table names in the database.
+ */
+ public function getTableNames($schema = '', $refresh = false)
+ {
+ if (!isset($this->_tableNames[$schema]) || $refresh) {
+ $this->_tableNames[$schema] = $this->findTableNames($schema);
+ }
+ return $this->_tableNames[$schema];
+ }
+
+ /**
+ * @return QueryBuilder the query builder for this connection.
+ */
+ public function getQueryBuilder()
+ {
+ if ($this->_builder === null) {
+ $this->_builder = $this->createQueryBuilder();
+ }
+ return $this->_builder;
+ }
+
+ /**
+ * Determines the PDO type for the given PHP data value.
+ * @param mixed $data the data whose PDO type is to be determined
+ * @return integer the PDO type
+ * @see http://www.php.net/manual/en/pdo.constants.php
+ */
+ public function getPdoType($data)
+ {
+ static $typeMap = array(
+ // php type => PDO type
+ 'boolean' => \PDO::PARAM_BOOL,
+ 'integer' => \PDO::PARAM_INT,
+ 'string' => \PDO::PARAM_STR,
+ 'resource' => \PDO::PARAM_LOB,
+ 'NULL' => \PDO::PARAM_NULL,
+ );
+ $type = gettype($data);
+ return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
+ }
+
+ /**
+ * Refreshes the schema.
+ * This method cleans up all cached table schemas so that they can be re-created later
+ * to reflect the database schema change.
+ */
+ public function refresh()
+ {
+ /** @var $cache Cache */
+ $cache = is_string($this->db->schemaCache) ? Yii::$app->getComponent($this->db->schemaCache) : $this->db->schemaCache;
+ if ($this->db->enableSchemaCache && $cache instanceof Cache) {
+ GroupDependency::invalidate($cache, $this->getCacheGroup());
+ }
+ $this->_tableNames = array();
+ $this->_tables = array();
+ }
+
+ /**
+ * Creates a query builder for the database.
+ * This method may be overridden by child classes to create a DBMS-specific query builder.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Returns all table names in the database.
+ * This method should be overridden by child classes in order to support this feature
+ * because the default implementation simply throws an exception.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ * @throws NotSupportedException if this method is called
+ */
+ protected function findTableNames($schema = '')
+ {
+ throw new NotSupportedException(get_class($this) . ' does not support fetching all table names.');
+ }
+
+ /**
+ * Returns the ID of the last inserted row or sequence value.
+ * @param string $sequenceName name of the sequence object (required by some DBMS)
+ * @return string the row ID of the last row inserted, or the last value retrieved from the sequence object
+ * @throws InvalidCallException if the DB connection is not active
+ * @see http://www.php.net/manual/en/function.PDO-lastInsertId.php
+ */
+ public function getLastInsertID($sequenceName = '')
+ {
+ if ($this->db->isActive) {
+ return $this->db->pdo->lastInsertId($sequenceName);
+ } else {
+ throw new InvalidCallException('DB Connection is not active.');
+ }
+ }
+
+ /**
+ * Quotes a string value for use in a query.
+ * Note that if the parameter is not a string, it will be returned without change.
+ * @param string $str string to be quoted
+ * @return string the properly quoted string
+ * @see http://www.php.net/manual/en/function.PDO-quote.php
+ */
+ public function quoteValue($str)
+ {
+ if (!is_string($str)) {
+ return $str;
+ }
+
+ $this->db->open();
+ if (($value = $this->db->pdo->quote($str)) !== false) {
+ return $value;
+ } else { // the driver doesn't support quote (e.g. oci)
+ return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
+ }
+ }
+
+ /**
+ * Quotes a table name for use in a query.
+ * If the table name contains schema prefix, the prefix will also be properly quoted.
+ * If the table name is already quoted or contains '(' or '{{',
+ * then this method will do nothing.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ * @see quoteSimpleTableName
+ */
+ public function quoteTableName($name)
+ {
+ if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
+ return $name;
+ }
+ if (strpos($name, '.') === false) {
+ return $this->quoteSimpleTableName($name);
+ }
+ $parts = explode('.', $name);
+ foreach ($parts as $i => $part) {
+ $parts[$i] = $this->quoteSimpleTableName($part);
+ }
+ return implode('.', $parts);
+
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * If the column name contains prefix, the prefix will also be properly quoted.
+ * If the column name is already quoted or contains '(', '[[' or '{{',
+ * then this method will do nothing.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ * @see quoteSimpleColumnName
+ */
+ public function quoteColumnName($name)
+ {
+ if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) {
+ return $name;
+ }
+ if (($pos = strrpos($name, '.')) !== false) {
+ $prefix = $this->quoteTableName(substr($name, 0, $pos)) . '.';
+ $name = substr($name, $pos + 1);
+ } else {
+ $prefix = '';
+ }
+ return $prefix . $this->quoteSimpleColumnName($name);
+ }
+
+ /**
+ * Quotes a simple table name for use in a query.
+ * A simple table name should contain the table name only without any schema prefix.
+ * If the table name is already quoted, this method will do nothing.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, "'") !== false ? $name : "'" . $name . "'";
+ }
+
+ /**
+ * Quotes a simple column name for use in a query.
+ * A simple column name should contain the column name only without any prefix.
+ * If the column name is already quoted or is the asterisk character '*', this method will do nothing.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
+ }
+
+ /**
+ * Returns the actual name of a given table name.
+ * This method will strip off curly brackets from the given table name
+ * and replace the percentage character '%' with [[Connection::tablePrefix]].
+ * @param string $name the table name to be converted
+ * @return string the real name of the given table name
+ */
+ public function getRawTableName($name)
+ {
+ if (strpos($name, '{{') !== false) {
+ $name = preg_replace('/\\{\\{(.*?)\\}\\}/', '\1', $name);
+ return str_replace('%', $this->db->tablePrefix, $name);
+ } else {
+ return $name;
+ }
+ }
+
+ /**
+ * Extracts the PHP type from abstract DB type.
+ * @param ColumnSchema $column the column schema information
+ * @return string PHP type name
+ */
+ protected function getColumnPhpType($column)
+ {
+ static $typeMap = array( // abstract type => php type
+ 'smallint' => 'integer',
+ 'integer' => 'integer',
+ 'bigint' => 'integer',
+ 'boolean' => 'boolean',
+ 'float' => 'double',
+ );
+ if (isset($typeMap[$column->type])) {
+ if ($column->type === 'bigint') {
+ return PHP_INT_SIZE == 8 && !$column->unsigned ? 'integer' : 'string';
+ } elseif ($column->type === 'integer') {
+ return PHP_INT_SIZE == 4 && $column->unsigned ? 'string' : 'integer';
+ } else {
+ return $typeMap[$column->type];
+ }
+ } else {
+ return 'string';
+ }
+ }
+}
diff --git a/framework/yii/db/StaleObjectException.php b/framework/yii/db/StaleObjectException.php
new file mode 100644
index 0000000..dc88ceb
--- /dev/null
+++ b/framework/yii/db/StaleObjectException.php
@@ -0,0 +1,23 @@
+
+ * @since 2.0
+ */
+class StaleObjectException extends Exception
+{
+ /**
+ * @return string the user-friendly name of this exception
+ */
+ public function getName()
+ {
+ return \Yii::t('yii', 'Stale Object Exception');
+ }
+}
diff --git a/framework/yii/db/TableSchema.php b/framework/yii/db/TableSchema.php
new file mode 100644
index 0000000..910061d
--- /dev/null
+++ b/framework/yii/db/TableSchema.php
@@ -0,0 +1,98 @@
+
+ * @since 2.0
+ */
+class TableSchema extends Object
+{
+ /**
+ * @var string name of the schema that this table belongs to.
+ */
+ public $schemaName;
+ /**
+ * @var string name of this table.
+ */
+ public $name;
+ /**
+ * @var string[] primary keys of this table.
+ */
+ public $primaryKey = array();
+ /**
+ * @var string sequence name for the primary key. Null if no sequence.
+ */
+ public $sequenceName;
+ /**
+ * @var array foreign keys of this table. Each array element is of the following structure:
+ *
+ * ~~~
+ * array(
+ * 'ForeignTableName',
+ * 'fk1' => 'pk1', // pk1 is in foreign table
+ * 'fk2' => 'pk2', // if composite foreign key
+ * )
+ * ~~~
+ */
+ public $foreignKeys = array();
+ /**
+ * @var ColumnSchema[] column metadata of this table. Each array element is a [[ColumnSchema]] object, indexed by column names.
+ */
+ public $columns = array();
+
+ /**
+ * Gets the named column metadata.
+ * This is a convenient method for retrieving a named column even if it does not exist.
+ * @param string $name column name
+ * @return ColumnSchema metadata of the named column. Null if the named column does not exist.
+ */
+ public function getColumn($name)
+ {
+ return isset($this->columns[$name]) ? $this->columns[$name] : null;
+ }
+
+ /**
+ * Returns the names of all columns in this table.
+ * @return array list of column names
+ */
+ public function getColumnNames()
+ {
+ return array_keys($this->columns);
+ }
+
+ /**
+ * Manually specifies the primary key for this table.
+ * @param string|array $keys the primary key (can be composite)
+ * @throws InvalidParamException if the specified key cannot be found in the table.
+ */
+ public function fixPrimaryKey($keys)
+ {
+ if (!is_array($keys)) {
+ $keys = array($keys);
+ }
+ $this->primaryKey = $keys;
+ foreach ($this->columns as $column) {
+ $column->isPrimaryKey = false;
+ }
+ foreach ($keys as $key) {
+ if (isset($this->columns[$key])) {
+ $this->columns[$key]->isPrimaryKey = true;
+ } else {
+ throw new InvalidParamException("Primary key '$key' cannot be found in table '{$this->name}'.");
+ }
+ }
+ }
+}
diff --git a/framework/yii/db/Transaction.php b/framework/yii/db/Transaction.php
new file mode 100644
index 0000000..e0b90d9
--- /dev/null
+++ b/framework/yii/db/Transaction.php
@@ -0,0 +1,106 @@
+beginTransaction();
+ * try {
+ * $connection->createCommand($sql1)->execute();
+ * $connection->createCommand($sql2)->execute();
+ * //.... other SQL executions
+ * $transaction->commit();
+ * } catch(Exception $e) {
+ * $transaction->rollback();
+ * }
+ * ~~~
+ *
+ * @property boolean $isActive Whether this transaction is active. Only an active transaction can [[commit()]]
+ * or [[rollback()]]. This property is read-only.
+ *
+ * @author Qiang Xue
+ * @since 2.0
+ */
+class Transaction extends \yii\base\Object
+{
+ /**
+ * @var Connection the database connection that this transaction is associated with.
+ */
+ public $db;
+ /**
+ * @var boolean whether this transaction is active. Only an active transaction
+ * can [[commit()]] or [[rollback()]]. This property is set true when the transaction is started.
+ */
+ private $_active = false;
+
+ /**
+ * Returns a value indicating whether this transaction is active.
+ * @return boolean whether this transaction is active. Only an active transaction
+ * can [[commit()]] or [[rollback()]].
+ */
+ public function getIsActive()
+ {
+ return $this->_active;
+ }
+
+ /**
+ * Begins a transaction.
+ * @throws InvalidConfigException if [[connection]] is null
+ */
+ public function begin()
+ {
+ if (!$this->_active) {
+ if ($this->db === null) {
+ throw new InvalidConfigException('Transaction::db must be set.');
+ }
+ \Yii::trace('Starting transaction', __METHOD__);
+ $this->db->open();
+ $this->db->pdo->beginTransaction();
+ $this->_active = true;
+ }
+ }
+
+ /**
+ * Commits a transaction.
+ * @throws Exception if the transaction or the DB connection is not active.
+ */
+ public function commit()
+ {
+ if ($this->_active && $this->db && $this->db->isActive) {
+ \Yii::trace('Committing transaction', __METHOD__);
+ $this->db->pdo->commit();
+ $this->_active = false;
+ } else {
+ throw new Exception('Failed to commit transaction: transaction was inactive.');
+ }
+ }
+
+ /**
+ * Rolls back a transaction.
+ * @throws Exception if the transaction or the DB connection is not active.
+ */
+ public function rollback()
+ {
+ if ($this->_active && $this->db && $this->db->isActive) {
+ \Yii::trace('Rolling back transaction', __METHOD__);
+ $this->db->pdo->rollBack();
+ $this->_active = false;
+ } else {
+ throw new Exception('Failed to roll back transaction: transaction was inactive.');
+ }
+ }
+}
diff --git a/framework/yii/db/cubrid/QueryBuilder.php b/framework/yii/db/cubrid/QueryBuilder.php
new file mode 100644
index 0000000..4b7ef43
--- /dev/null
+++ b/framework/yii/db/cubrid/QueryBuilder.php
@@ -0,0 +1,117 @@
+
+ * @since 2.0
+ */
+class QueryBuilder extends \yii\db\QueryBuilder
+{
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = array(
+ Schema::TYPE_PK => 'int NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ Schema::TYPE_BIGPK => 'bigint NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'varchar',
+ Schema::TYPE_SMALLINT => 'smallint',
+ Schema::TYPE_INTEGER => 'int',
+ Schema::TYPE_BIGINT => 'bigint',
+ Schema::TYPE_FLOAT => 'float(7)',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
+ Schema::TYPE_DATETIME => 'datetime',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'blob',
+ Schema::TYPE_BOOLEAN => 'smallint',
+ Schema::TYPE_MONEY => 'decimal(19,4)',
+ );
+
+ /**
+ * Creates a SQL statement for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $tableName the name of the table whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return string the SQL statement for resetting sequence
+ * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
+ */
+ public function resetSequence($tableName, $value = null)
+ {
+ $table = $this->db->getTableSchema($tableName);
+ if ($table !== null && $table->sequenceName !== null) {
+ $tableName = $this->db->quoteTableName($tableName);
+ if ($value === null) {
+ $key = reset($table->primaryKey);
+ $value = (int)$this->db->createCommand("SELECT MAX(`$key`) FROM " . $this->db->schema->quoteTableName($tableName))->queryScalar() + 1;
+ } else {
+ $value = (int)$value;
+ }
+ return "ALTER TABLE " . $this->db->schema->quoteTableName($tableName) . " AUTO_INCREMENT=$value;";
+ } elseif ($table === null) {
+ throw new InvalidParamException("Table not found: $tableName");
+ } else {
+ throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
+ }
+ }
+
+ /**
+ * Generates a batch INSERT SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array(
+ * array('Tom', 30),
+ * array('Jane', 20),
+ * array('Linda', 25),
+ * ))->execute();
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch inserted into the table
+ * @return string the batch INSERT SQL statement
+ */
+ public function batchInsert($table, $columns, $rows)
+ {
+ if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
+ $columnSchemas = $tableSchema->columns;
+ } else {
+ $columnSchemas = array();
+ }
+
+ foreach ($columns as $i => $name) {
+ $columns[$i] = $this->db->quoteColumnName($name);
+ }
+
+ $values = array();
+ foreach ($rows as $row) {
+ $vs = array();
+ foreach ($row as $i => $value) {
+ if (!is_array($value) && isset($columnSchemas[$columns[$i]])) {
+ $value = $columnSchemas[$columns[$i]]->typecast($value);
+ }
+ $vs[] = is_string($value) ? $this->db->quoteValue($value) : $value;
+ }
+ $values[] = '(' . implode(', ', $vs) . ')';
+ }
+
+ return 'INSERT INTO ' . $this->db->quoteTableName($table)
+ . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
+ }
+}
diff --git a/framework/yii/db/cubrid/Schema.php b/framework/yii/db/cubrid/Schema.php
new file mode 100644
index 0000000..a789694
--- /dev/null
+++ b/framework/yii/db/cubrid/Schema.php
@@ -0,0 +1,261 @@
+
+ * @since 2.0
+ */
+class Schema extends \yii\db\Schema
+{
+ /**
+ * @var array mapping from physical column types (keys) to abstract column types (values)
+ * Please refer to [CUBRID manual](http://www.cubrid.org/manual/91/en/sql/datatype.html) for
+ * details on data types.
+ */
+ public $typeMap = array(
+ // Numeric data types
+ 'short' => self::TYPE_SMALLINT,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'int' => self::TYPE_INTEGER,
+ 'integer' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'numeric' => self::TYPE_DECIMAL,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'float' => self::TYPE_FLOAT,
+ 'real' => self::TYPE_FLOAT,
+ 'double' => self::TYPE_FLOAT,
+ 'double precision' => self::TYPE_FLOAT,
+ 'monetary' => self::TYPE_MONEY,
+ // Date/Time data types
+ 'date' => self::TYPE_DATE,
+ 'time' => self::TYPE_TIME,
+ 'timestamp' => self::TYPE_TIMESTAMP,
+ 'datetime' => self::TYPE_DATETIME,
+ // String data types
+ 'char' => self::TYPE_STRING,
+ 'varchar' => self::TYPE_STRING,
+ 'char varying' => self::TYPE_STRING,
+ 'nchar' => self::TYPE_STRING,
+ 'nchar varying' => self::TYPE_STRING,
+ 'string' => self::TYPE_STRING,
+ // BLOB/CLOB data types
+ 'blob' => self::TYPE_BINARY,
+ 'clob' => self::TYPE_BINARY,
+ // Bit string data types
+ 'bit' => self::TYPE_STRING,
+ 'bit varying' => self::TYPE_STRING,
+ // Collection data types (considered strings for now)
+ 'set' => self::TYPE_STRING,
+ 'multiset' => self::TYPE_STRING,
+ 'list' => self::TYPE_STRING,
+ 'sequence' => self::TYPE_STRING,
+ 'enum' => self::TYPE_STRING,
+ );
+
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, '"') !== false ? $name : '"' . $name . '"';
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * A simple column name has no prefix.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '"') !== false || $name === '*' ? $name : '"' . $name . '"';
+ }
+
+ /**
+ * Quotes a string value for use in a query.
+ * Note that if the parameter is not a string, it will be returned without change.
+ * @param string $str string to be quoted
+ * @return string the properly quoted string
+ * @see http://www.php.net/manual/en/function.PDO-quote.php
+ */
+ public function quoteValue($str)
+ {
+ if (!is_string($str)) {
+ return $str;
+ }
+
+ $this->db->open();
+ // workaround for broken PDO::quote() implementation in CUBRID 9.1.0 http://jira.cubrid.org/browse/APIS-658
+ $version = $this->db->pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION);
+ if (version_compare($version, '8.4.4.0002', '<') || $version[0] == '9' && version_compare($version, '9.2.0.0002', '<=')) {
+ return "'" . addcslashes(str_replace("'", "''", $str), "\000\n\r\\\032") . "'";
+ } else {
+ return $this->db->pdo->quote($str);
+ }
+ }
+
+ /**
+ * Creates a query builder for the CUBRID database.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema driver dependent table metadata. Null if the table does not exist.
+ */
+ protected function loadTableSchema($name)
+ {
+ $this->db->open();
+ $tableInfo = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE, $name);
+
+ if (isset($tableInfo[0]['NAME'])) {
+ $table = new TableSchema();
+ $table->name = $tableInfo[0]['NAME'];
+
+ $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name);
+ $columns = $this->db->createCommand($sql)->queryAll();
+
+ foreach ($columns as $info) {
+ $column = $this->loadColumnSchema($info);
+ $table->columns[$column->name] = $column;
+ if ($column->isPrimaryKey) {
+ $table->primaryKey[] = $column->name;
+ if ($column->autoIncrement) {
+ $table->sequenceName = '';
+ }
+ }
+ }
+
+ $foreignKeys = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_IMPORTED_KEYS, $table->name);
+ foreach($foreignKeys as $key) {
+ if (isset($table->foreignKeys[$key['FK_NAME']])) {
+ $table->foreignKeys[$key['FK_NAME']][$key['FKCOLUMN_NAME']] = $key['PKCOLUMN_NAME'];
+ } else {
+ $table->foreignKeys[$key['FK_NAME']] = array(
+ $key['PKTABLE_NAME'],
+ $key['FKCOLUMN_NAME'] => $key['PKCOLUMN_NAME']
+ );
+ }
+ }
+ $table->foreignKeys = array_values($table->foreignKeys);
+
+ return $table;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema();
+
+ $column->name = $info['Field'];
+ $column->allowNull = $info['Null'] === 'YES';
+ $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false;
+ $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
+
+ $column->dbType = strtolower($info['Type']);
+ $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
+
+ $column->type = self::TYPE_STRING;
+ if (preg_match('/^([\w ]+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
+ $type = $matches[1];
+ if (isset($this->typeMap[$type])) {
+ $column->type = $this->typeMap[$type];
+ }
+ if (!empty($matches[2])) {
+ if ($type === 'enum') {
+ $values = explode(',', $matches[2]);
+ foreach ($values as $i => $value) {
+ $values[$i] = trim($value, "'");
+ }
+ $column->enumValues = $values;
+ } else {
+ $values = explode(',', $matches[2]);
+ $column->size = $column->precision = (int)$values[0];
+ if (isset($values[1])) {
+ $column->scale = (int)$values[1];
+ }
+ }
+ }
+ }
+
+ $column->phpType = $this->getColumnPhpType($column);
+
+ if ($column->type === 'timestamp' && $info['Default'] === 'CURRENT_TIMESTAMP' ||
+ $column->type === 'datetime' && $info['Default'] === 'SYS_DATETIME' ||
+ $column->type === 'date' && $info['Default'] === 'SYS_DATE' ||
+ $column->type === 'time' && $info['Default'] === 'SYS_TIME'
+ ) {
+ $column->defaultValue = new Expression($info['Default']);
+ } else {
+ $column->defaultValue = $column->typecast($info['Default']);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ */
+ protected function findTableNames($schema = '')
+ {
+ $this->db->open();
+ $tables = $this->db->pdo->cubrid_schema(\PDO::CUBRID_SCH_TABLE);
+ $tableNames = array();
+ foreach($tables as $table) {
+ // do not list system tables
+ if ($table['TYPE'] != 0) {
+ $tableNames[] = $table['NAME'];
+ }
+ }
+ return $tableNames;
+ }
+
+ /**
+ * Determines the PDO type for the given PHP data value.
+ * @param mixed $data the data whose PDO type is to be determined
+ * @return integer the PDO type
+ * @see http://www.php.net/manual/en/pdo.constants.php
+ */
+ public function getPdoType($data)
+ {
+ static $typeMap = array(
+ // php type => PDO type
+ 'boolean' => \PDO::PARAM_INT, // PARAM_BOOL is not supported by CUBRID PDO
+ 'integer' => \PDO::PARAM_INT,
+ 'string' => \PDO::PARAM_STR,
+ 'resource' => \PDO::PARAM_LOB,
+ 'NULL' => \PDO::PARAM_NULL,
+ );
+ $type = gettype($data);
+ return isset($typeMap[$type]) ? $typeMap[$type] : \PDO::PARAM_STR;
+ }
+}
diff --git a/framework/yii/db/mssql/PDO.php b/framework/yii/db/mssql/PDO.php
new file mode 100644
index 0000000..c29cd4c
--- /dev/null
+++ b/framework/yii/db/mssql/PDO.php
@@ -0,0 +1,61 @@
+
+ * @since 2.0
+ */
+class PDO extends \PDO
+{
+ /**
+ * Returns value of the last inserted ID.
+ * @param string|null $sequence the sequence name. Defaults to null.
+ * @return integer last inserted ID value.
+ */
+ public function lastInsertId($sequence = null)
+ {
+ return $this->query('SELECT CAST(COALESCE(SCOPE_IDENTITY(), @@IDENTITY) AS bigint)')->fetchColumn();
+ }
+
+ /**
+ * Starts a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
+ * natively support transactions.
+ * @return boolean the result of a transaction start.
+ */
+ public function beginTransaction()
+ {
+ $this->exec('BEGIN TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Commits a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
+ * natively support transactions.
+ * @return boolean the result of a transaction commit.
+ */
+ public function commit()
+ {
+ $this->exec('COMMIT TRANSACTION');
+ return true;
+ }
+
+ /**
+ * Rollbacks a transaction. It is necessary to override PDO's method as MSSQL PDO driver does not
+ * natively support transactions.
+ * @return boolean the result of a transaction rollback.
+ */
+ public function rollBack()
+ {
+ $this->exec('ROLLBACK TRANSACTION');
+ return true;
+ }
+}
diff --git a/framework/yii/db/mssql/QueryBuilder.php b/framework/yii/db/mssql/QueryBuilder.php
new file mode 100644
index 0000000..aeb5be8
--- /dev/null
+++ b/framework/yii/db/mssql/QueryBuilder.php
@@ -0,0 +1,82 @@
+
+ * @since 2.0
+ */
+class QueryBuilder extends \yii\db\QueryBuilder
+{
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = array(
+ Schema::TYPE_PK => 'int IDENTITY PRIMARY KEY',
+ Schema::TYPE_BIGPK => 'bigint IDENTITY PRIMARY KEY',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'text',
+ Schema::TYPE_SMALLINT => 'smallint(6)',
+ Schema::TYPE_INTEGER => 'int(11)',
+ Schema::TYPE_BIGINT => 'bigint(20)',
+ Schema::TYPE_FLOAT => 'float',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
+ Schema::TYPE_DATETIME => 'datetime',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'binary',
+ Schema::TYPE_BOOLEAN => 'tinyint(1)',
+ Schema::TYPE_MONEY => 'decimal(19,4)',
+ );
+
+// public function update($table, $columns, $condition, &$params)
+// {
+// return '';
+// }
+
+// public function delete($table, $condition, &$params)
+// {
+// return '';
+// }
+
+// public function buildLimit($limit, $offset)
+// {
+// return '';
+// }
+
+// public function resetSequence($table, $value = null)
+// {
+// return '';
+// }
+
+ /**
+ * Builds a SQL statement for enabling or disabling integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @param string $table the table name. Defaults to empty string, meaning that no table will be changed.
+ * @return string the SQL statement for checking integrity
+ * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ if ($schema !== '') {
+ $table = "{$schema}.{$table}";
+ }
+ $table = $this->db->quoteTableName($table);
+ if ($this->db->getTableSchema($table) === null) {
+ throw new InvalidParamException("Table not found: $table");
+ }
+ $enable = $check ? 'CHECK' : 'NOCHECK';
+ return "ALTER TABLE {$table} {$enable} CONSTRAINT ALL";
+ }
+}
diff --git a/framework/yii/db/mssql/Schema.php b/framework/yii/db/mssql/Schema.php
new file mode 100644
index 0000000..9def3b4
--- /dev/null
+++ b/framework/yii/db/mssql/Schema.php
@@ -0,0 +1,357 @@
+
+ * @since 2.0
+ */
+class Schema extends \yii\db\Schema
+{
+ /**
+ * Default schema name to be used.
+ */
+ const DEFAULT_SCHEMA = 'dbo';
+
+ /**
+ * @var array mapping from physical column types (keys) to abstract column types (values)
+ */
+ public $typeMap = array(
+ // exact numerics
+ 'bigint' => self::TYPE_BIGINT,
+ 'numeric' => self::TYPE_DECIMAL,
+ 'bit' => self::TYPE_SMALLINT,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'smallmoney' => self::TYPE_MONEY,
+ 'int' => self::TYPE_INTEGER,
+ 'tinyint' => self::TYPE_SMALLINT,
+ 'money' => self::TYPE_MONEY,
+
+ // approximate numerics
+ 'float' => self::TYPE_FLOAT,
+ 'real' => self::TYPE_FLOAT,
+
+ // date and time
+ 'date' => self::TYPE_DATE,
+ 'datetimeoffset' => self::TYPE_DATETIME,
+ 'datetime2' => self::TYPE_DATETIME,
+ 'smalldatetime' => self::TYPE_DATETIME,
+ 'datetime' => self::TYPE_DATETIME,
+ 'time' => self::TYPE_TIME,
+
+ // character strings
+ 'char' => self::TYPE_STRING,
+ 'varchar' => self::TYPE_STRING,
+ 'text' => self::TYPE_TEXT,
+
+ // unicode character strings
+ 'nchar' => self::TYPE_STRING,
+ 'nvarchar' => self::TYPE_STRING,
+ 'ntext' => self::TYPE_TEXT,
+
+ // binary strings
+ 'binary' => self::TYPE_BINARY,
+ 'varbinary' => self::TYPE_BINARY,
+ 'image' => self::TYPE_BINARY,
+
+ // other data types
+ // 'cursor' type cannot be used with tables
+ 'timestamp' => self::TYPE_TIMESTAMP,
+ 'hierarchyid' => self::TYPE_STRING,
+ 'uniqueidentifier' => self::TYPE_STRING,
+ 'sql_variant' => self::TYPE_STRING,
+ 'xml' => self::TYPE_STRING,
+ 'table' => self::TYPE_STRING,
+ );
+
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name.
+ * @return string the properly quoted table name.
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, '[') === false ? "[{$name}]" : $name;
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * A simple column name has no prefix.
+ * @param string $name column name.
+ * @return string the properly quoted column name.
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '[') === false && $name !== '*' ? "[{$name}]" : $name;
+ }
+
+ /**
+ * Creates a query builder for the MSSQL database.
+ * @return QueryBuilder query builder interface.
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
+ */
+ public function loadTableSchema($name)
+ {
+ $table = new TableSchema();
+ $this->resolveTableNames($table, $name);
+ $this->findPrimaryKeys($table);
+ if ($this->findColumns($table)) {
+ $this->findForeignKeys($table);
+ return $table;
+ }
+ }
+
+ /**
+ * Resolves the table name and schema name (if any).
+ * @param TableSchema $table the table metadata object
+ * @param string $name the table name
+ */
+ protected function resolveTableNames($table, $name)
+ {
+ $parts = explode('.', str_replace(array('[', ']'), '', $name));
+ $partCount = count($parts);
+ if ($partCount == 3) {
+ // catalog name, schema name and table name passed
+ $table->catalogName = $parts[0];
+ $table->schemaName = $parts[1];
+ $table->name = $parts[2];
+ } elseif ($partCount == 2) {
+ // only schema name and table name passed
+ $table->schemaName = $parts[0];
+ $table->name = $parts[1];
+ } else {
+ // only schema name passed
+ $table->schemaName = static::DEFAULT_SCHEMA;
+ $table->name = $parts[0];
+ }
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema();
+
+ $column->name = $info['column_name'];
+ $column->allowNull = $info['is_nullable'] == 'YES';
+ $column->dbType = $info['data_type'];
+ $column->enumValues = array(); // mssql has only vague equivalents to enum
+ $column->isPrimaryKey = null; // primary key will be determined in findColumns() method
+ $column->autoIncrement = $info['is_identity'] == 1;
+ $column->unsigned = stripos($column->dbType, 'unsigned') !== false;
+ $column->comment = $info['comment'] === null ? '' : $info['comment'];
+
+ $column->type = self::TYPE_STRING;
+ if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
+ $type = $matches[1];
+ if (isset($this->typeMap[$type])) {
+ $column->type = $this->typeMap[$type];
+ }
+ if (!empty($matches[2])) {
+ $values = explode(',', $matches[2]);
+ $column->size = $column->precision = (int)$values[0];
+ if (isset($values[1])) {
+ $column->scale = (int)$values[1];
+ }
+ if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
+ $column->type = 'boolean';
+ } elseif ($type === 'bit') {
+ if ($column->size > 32) {
+ $column->type = 'bigint';
+ } elseif ($column->size === 32) {
+ $column->type = 'integer';
+ }
+ }
+ }
+ }
+
+ $column->phpType = $this->getColumnPhpType($column);
+
+ if ($info['column_default'] == '(NULL)') {
+ $info['column_default'] = null;
+ }
+ if ($column->type !== 'timestamp' || $info['column_default'] !== 'CURRENT_TIMESTAMP') {
+ $column->defaultValue = $column->typecast($info['column_default']);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Collects the metadata of table columns.
+ * @param TableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ */
+ protected function findColumns($table)
+ {
+ $columnsTableName = 'information_schema.columns';
+ $whereSql = "[t1].[table_name] = '{$table->name}'";
+ if ($table->catalogName !== null) {
+ $columnsTableName = "{$table->catalogName}.{$columnsTableName}";
+ $whereSql .= " AND [t1].[table_catalog] = '{$table->catalogName}'";
+ }
+ if ($table->schemaName !== null) {
+ $whereSql .= " AND [t1].[table_schema] = '{$table->schemaName}'";
+ }
+ $columnsTableName = $this->quoteTableName($columnsTableName);
+
+ $sql = <<db->createCommand($sql)->queryAll();
+ } catch (\Exception $e) {
+ return false;
+ }
+ foreach ($columns as $column) {
+ $column = $this->loadColumnSchema($column);
+ foreach ($table->primaryKey as $primaryKey) {
+ if (strcasecmp($column->name, $primaryKey) === 0) {
+ $column->isPrimaryKey = true;
+ break;
+ }
+ }
+ if ($column->isPrimaryKey && $column->autoIncrement) {
+ $table->sequenceName = '';
+ }
+ $table->columns[$column->name] = $column;
+ }
+ return true;
+ }
+
+ /**
+ * Collects the primary key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findPrimaryKeys($table)
+ {
+ $keyColumnUsageTableName = 'information_schema.key_column_usage';
+ $tableConstraintsTableName = 'information_schema.table_constraints';
+ if ($table->catalogName !== null) {
+ $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName;
+ $tableConstraintsTableName = $table->catalogName . '.' . $tableConstraintsTableName;
+ }
+ $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName);
+ $tableConstraintsTableName = $this->quoteTableName($tableConstraintsTableName);
+
+ $sql = <<primaryKey = $this->db
+ ->createCommand($sql, array(':tableName' => $table->name, ':schemaName' => $table->schemaName))
+ ->queryColumn();
+ }
+
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findForeignKeys($table)
+ {
+ $referentialConstraintsTableName = 'information_schema.referential_constraints';
+ $keyColumnUsageTableName = 'information_schema.key_column_usage';
+ if ($table->catalogName !== null) {
+ $referentialConstraintsTableName = $table->catalogName . '.' . $referentialConstraintsTableName;
+ $keyColumnUsageTableName = $table->catalogName . '.' . $keyColumnUsageTableName;
+ }
+ $referentialConstraintsTableName = $this->quoteTableName($referentialConstraintsTableName);
+ $keyColumnUsageTableName = $this->quoteTableName($keyColumnUsageTableName);
+
+ // please refer to the following page for more details:
+ // http://msdn2.microsoft.com/en-us/library/aa175805(SQL.80).aspx
+ $sql = <<db->createCommand($sql, array(':tableName' => $table->name))->queryAll();
+ $table->foreignKeys = array();
+ foreach ($rows as $row) {
+ $table->foreignKeys[] = array($row['uq_table_name'], $row['fk_column_name'] => $row['uq_column_name']);
+ }
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ */
+ protected function findTableNames($schema = '')
+ {
+ if ($schema === '') {
+ $schema = static::DEFAULT_SCHEMA;
+ }
+
+ $sql = <<db->createCommand($sql, array(':schema' => $schema))->queryColumn();
+ if ($schema !== static::DEFAULT_SCHEMA) {
+ foreach ($names as $index => $name) {
+ $names[$index] = $schema . '.' . $name;
+ }
+ }
+ return $names;
+ }
+}
diff --git a/framework/yii/db/mssql/SqlsrvPDO.php b/framework/yii/db/mssql/SqlsrvPDO.php
new file mode 100644
index 0000000..29444c5
--- /dev/null
+++ b/framework/yii/db/mssql/SqlsrvPDO.php
@@ -0,0 +1,33 @@
+
+ * @since 2.0
+ */
+class SqlsrvPDO extends \PDO
+{
+ /**
+ * Returns value of the last inserted ID.
+ *
+ * SQLSRV driver implements [[PDO::lastInsertId()]] method but with a single peculiarity:
+ * when `$sequence` value is a null or an empty string it returns an empty string.
+ * But when parameter is not specified it works as expected and returns actual
+ * last inserted ID (like the other PDO drivers).
+ * @param string|null $sequence the sequence name. Defaults to null.
+ * @return integer last inserted ID value.
+ */
+ public function lastInsertId($sequence = null)
+ {
+ return !$sequence ? parent::lastInsertId() : parent::lastInsertId($sequence);
+ }
+}
diff --git a/framework/yii/db/mssql/TableSchema.php b/framework/yii/db/mssql/TableSchema.php
new file mode 100644
index 0000000..67ad85c
--- /dev/null
+++ b/framework/yii/db/mssql/TableSchema.php
@@ -0,0 +1,23 @@
+
+ * @since 2.0
+ */
+class TableSchema extends \yii\db\TableSchema
+{
+ /**
+ * @var string name of the catalog (database) that this table belongs to.
+ * Defaults to null, meaning no catalog (or the current database).
+ */
+ public $catalogName;
+}
diff --git a/framework/yii/db/mysql/QueryBuilder.php b/framework/yii/db/mysql/QueryBuilder.php
new file mode 100644
index 0000000..386de2f
--- /dev/null
+++ b/framework/yii/db/mysql/QueryBuilder.php
@@ -0,0 +1,190 @@
+
+ * @since 2.0
+ */
+class QueryBuilder extends \yii\db\QueryBuilder
+{
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = array(
+ Schema::TYPE_PK => 'int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ Schema::TYPE_BIGPK => 'bigint(20) NOT NULL AUTO_INCREMENT PRIMARY KEY',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'text',
+ Schema::TYPE_SMALLINT => 'smallint(6)',
+ Schema::TYPE_INTEGER => 'int(11)',
+ Schema::TYPE_BIGINT => 'bigint(20)',
+ Schema::TYPE_FLOAT => 'float',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
+ Schema::TYPE_DATETIME => 'datetime',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'blob',
+ Schema::TYPE_BOOLEAN => 'tinyint(1)',
+ Schema::TYPE_MONEY => 'decimal(19,4)',
+ );
+
+ /**
+ * Builds a SQL statement for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $oldName the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB column.
+ * @throws Exception
+ */
+ public function renameColumn($table, $oldName, $newName)
+ {
+ $quotedTable = $this->db->quoteTableName($table);
+ $row = $this->db->createCommand('SHOW CREATE TABLE ' . $quotedTable)->queryOne();
+ if ($row === false) {
+ throw new Exception("Unable to find column '$oldName' in table '$table'.");
+ }
+ if (isset($row['Create Table'])) {
+ $sql = $row['Create Table'];
+ } else {
+ $row = array_values($row);
+ $sql = $row[1];
+ }
+ if (preg_match_all('/^\s*`(.*?)`\s+(.*?),?$/m', $sql, $matches)) {
+ foreach ($matches[1] as $i => $c) {
+ if ($c === $oldName) {
+ return "ALTER TABLE $quotedTable CHANGE "
+ . $this->db->quoteColumnName($oldName) . ' '
+ . $this->db->quoteColumnName($newName) . ' '
+ . $matches[2][$i];
+ }
+ }
+ }
+ // try to give back a SQL anyway
+ return "ALTER TABLE $quotedTable CHANGE "
+ . $this->db->quoteColumnName($oldName) . ' '
+ . $this->db->quoteColumnName($newName);
+ }
+
+ /**
+ * Builds a SQL statement for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a foreign key constraint.
+ */
+ public function dropForeignKey($name, $table)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table)
+ . ' DROP FOREIGN KEY ' . $this->db->quoteColumnName($name);
+ }
+
+ /**
+ * Builds a SQL statement for removing a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ * @return string the SQL statement for removing a primary key constraint from an existing table.
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' DROP PRIMARY KEY';
+ }
+
+ /**
+ * Creates a SQL statement for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $tableName the name of the table whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return string the SQL statement for resetting sequence
+ * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
+ */
+ public function resetSequence($tableName, $value = null)
+ {
+ $table = $this->db->getTableSchema($tableName);
+ if ($table !== null && $table->sequenceName !== null) {
+ $tableName = $this->db->quoteTableName($tableName);
+ if ($value === null) {
+ $key = reset($table->primaryKey);
+ $value = $this->db->createCommand("SELECT MAX(`$key`) FROM $tableName")->queryScalar() + 1;
+ } else {
+ $value = (int)$value;
+ }
+ return "ALTER TABLE $tableName AUTO_INCREMENT=$value";
+ } elseif ($table === null) {
+ throw new InvalidParamException("Table not found: $tableName");
+ } else {
+ throw new InvalidParamException("There is not sequence associated with table '$tableName'.");
+ }
+ }
+
+ /**
+ * Builds a SQL statement for enabling or disabling integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $table the table name. Meaningless for MySQL.
+ * @param string $schema the schema of the tables. Meaningless for MySQL.
+ * @return string the SQL statement for checking integrity
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ return 'SET FOREIGN_KEY_CHECKS = ' . ($check ? 1 : 0);
+ }
+
+ /**
+ * Generates a batch INSERT SQL statement.
+ * For example,
+ *
+ * ~~~
+ * $connection->createCommand()->batchInsert('tbl_user', array('name', 'age'), array(
+ * array('Tom', 30),
+ * array('Jane', 20),
+ * array('Linda', 25),
+ * ))->execute();
+ * ~~~
+ *
+ * Note that the values in each row must match the corresponding column names.
+ *
+ * @param string $table the table that new rows will be inserted into.
+ * @param array $columns the column names
+ * @param array $rows the rows to be batch inserted into the table
+ * @return string the batch INSERT SQL statement
+ */
+ public function batchInsert($table, $columns, $rows)
+ {
+ if (($tableSchema = $this->db->getTableSchema($table)) !== null) {
+ $columnSchemas = $tableSchema->columns;
+ } else {
+ $columnSchemas = array();
+ }
+
+ foreach ($columns as $i => $name) {
+ $columns[$i] = $this->db->quoteColumnName($name);
+ }
+
+ $values = array();
+ foreach ($rows as $row) {
+ $vs = array();
+ foreach ($row as $i => $value) {
+ if (!is_array($value) && isset($columnSchemas[$columns[$i]])) {
+ $value = $columnSchemas[$columns[$i]]->typecast($value);
+ }
+ $vs[] = is_string($value) ? $this->db->quoteValue($value) : $value;
+ }
+ $values[] = '(' . implode(', ', $vs) . ')';
+ }
+
+ return 'INSERT INTO ' . $this->db->quoteTableName($table)
+ . ' (' . implode(', ', $columns) . ') VALUES ' . implode(', ', $values);
+ }
+}
diff --git a/framework/yii/db/mysql/Schema.php b/framework/yii/db/mysql/Schema.php
new file mode 100644
index 0000000..998f49a
--- /dev/null
+++ b/framework/yii/db/mysql/Schema.php
@@ -0,0 +1,250 @@
+
+ * @since 2.0
+ */
+class Schema extends \yii\db\Schema
+{
+ /**
+ * @var array mapping from physical column types (keys) to abstract column types (values)
+ */
+ public $typeMap = array(
+ 'tinyint' => self::TYPE_SMALLINT,
+ 'bit' => self::TYPE_SMALLINT,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'mediumint' => self::TYPE_INTEGER,
+ 'int' => self::TYPE_INTEGER,
+ 'integer' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'float' => self::TYPE_FLOAT,
+ 'double' => self::TYPE_FLOAT,
+ 'real' => self::TYPE_FLOAT,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'numeric' => self::TYPE_DECIMAL,
+ 'tinytext' => self::TYPE_TEXT,
+ 'mediumtext' => self::TYPE_TEXT,
+ 'longtext' => self::TYPE_TEXT,
+ 'text' => self::TYPE_TEXT,
+ 'varchar' => self::TYPE_STRING,
+ 'string' => self::TYPE_STRING,
+ 'char' => self::TYPE_STRING,
+ 'datetime' => self::TYPE_DATETIME,
+ 'year' => self::TYPE_DATE,
+ 'date' => self::TYPE_DATE,
+ 'time' => self::TYPE_TIME,
+ 'timestamp' => self::TYPE_TIMESTAMP,
+ 'enum' => self::TYPE_STRING,
+ );
+
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, "`") !== false ? $name : "`" . $name . "`";
+ }
+
+ /**
+ * Quotes a column name for use in a query.
+ * A simple column name has no prefix.
+ * @param string $name column name
+ * @return string the properly quoted column name
+ */
+ public function quoteSimpleColumnName($name)
+ {
+ return strpos($name, '`') !== false || $name === '*' ? $name : '`' . $name . '`';
+ }
+
+ /**
+ * Creates a query builder for the MySQL database.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema driver dependent table metadata. Null if the table does not exist.
+ */
+ protected function loadTableSchema($name)
+ {
+ $table = new TableSchema;
+ $this->resolveTableNames($table, $name);
+
+ if ($this->findColumns($table)) {
+ $this->findConstraints($table);
+ return $table;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Resolves the table name and schema name (if any).
+ * @param TableSchema $table the table metadata object
+ * @param string $name the table name
+ */
+ protected function resolveTableNames($table, $name)
+ {
+ $parts = explode('.', str_replace('`', '', $name));
+ if (isset($parts[1])) {
+ $table->schemaName = $parts[0];
+ $table->name = $parts[1];
+ } else {
+ $table->name = $parts[0];
+ }
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema;
+
+ $column->name = $info['Field'];
+ $column->allowNull = $info['Null'] === 'YES';
+ $column->isPrimaryKey = strpos($info['Key'], 'PRI') !== false;
+ $column->autoIncrement = stripos($info['Extra'], 'auto_increment') !== false;
+ $column->comment = $info['Comment'];
+
+
+ $column->dbType = $info['Type'];
+ $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
+
+ $column->type = self::TYPE_STRING;
+ if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
+ $type = $matches[1];
+ if (isset($this->typeMap[$type])) {
+ $column->type = $this->typeMap[$type];
+ }
+ if (!empty($matches[2])) {
+ if ($type === 'enum') {
+ $values = explode(',', $matches[2]);
+ foreach ($values as $i => $value) {
+ $values[$i] = trim($value, "'");
+ }
+ $column->enumValues = $values;
+ } else {
+ $values = explode(',', $matches[2]);
+ $column->size = $column->precision = (int)$values[0];
+ if (isset($values[1])) {
+ $column->scale = (int)$values[1];
+ }
+ if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
+ $column->type = 'boolean';
+ } elseif ($type === 'bit') {
+ if ($column->size > 32) {
+ $column->type = 'bigint';
+ } elseif ($column->size === 32) {
+ $column->type = 'integer';
+ }
+ }
+ }
+ }
+ }
+
+ $column->phpType = $this->getColumnPhpType($column);
+
+ if ($column->type !== 'timestamp' || $info['Default'] !== 'CURRENT_TIMESTAMP') {
+ $column->defaultValue = $column->typecast($info['Default']);
+ }
+
+ return $column;
+ }
+
+ /**
+ * Collects the metadata of table columns.
+ * @param TableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ * @throws \Exception if DB query fails
+ */
+ protected function findColumns($table)
+ {
+ $sql = 'SHOW FULL COLUMNS FROM ' . $this->quoteSimpleTableName($table->name);
+ try {
+ $columns = $this->db->createCommand($sql)->queryAll();
+ } catch (\Exception $e) {
+ $previous = $e->getPrevious();
+ if ($previous instanceof \PDOException && $previous->getCode() == '42S02') {
+ // table does not exist
+ return false;
+ }
+ throw $e;
+ }
+ foreach ($columns as $info) {
+ $column = $this->loadColumnSchema($info);
+ $table->columns[$column->name] = $column;
+ if ($column->isPrimaryKey) {
+ $table->primaryKey[] = $column->name;
+ if ($column->autoIncrement) {
+ $table->sequenceName = '';
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findConstraints($table)
+ {
+ $row = $this->db->createCommand('SHOW CREATE TABLE ' . $this->quoteSimpleTableName($table->name))->queryOne();
+ if (isset($row['Create Table'])) {
+ $sql = $row['Create Table'];
+ } else {
+ $row = array_values($row);
+ $sql = $row[1];
+ }
+
+ $regexp = '/FOREIGN KEY\s+\(([^\)]+)\)\s+REFERENCES\s+([^\(^\s]+)\s*\(([^\)]+)\)/mi';
+ if (preg_match_all($regexp, $sql, $matches, PREG_SET_ORDER)) {
+ foreach ($matches as $match) {
+ $fks = array_map('trim', explode(',', str_replace('`', '', $match[1])));
+ $pks = array_map('trim', explode(',', str_replace('`', '', $match[3])));
+ $constraint = array(str_replace('`', '', $match[2]));
+ foreach ($fks as $k => $name) {
+ $constraint[$name] = $pks[$k];
+ }
+ $table->foreignKeys[] = $constraint;
+ }
+ }
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * @return array all table names in the database. The names have NO schema name prefix.
+ */
+ protected function findTableNames($schema = '')
+ {
+ $sql = 'SHOW TABLES';
+ if ($schema !== '') {
+ $sql .= ' FROM ' . $this->quoteSimpleTableName($schema);
+ }
+ return $this->db->createCommand($sql)->queryColumn();
+ }
+}
diff --git a/framework/yii/db/pgsql/QueryBuilder.php b/framework/yii/db/pgsql/QueryBuilder.php
new file mode 100644
index 0000000..667fa43
--- /dev/null
+++ b/framework/yii/db/pgsql/QueryBuilder.php
@@ -0,0 +1,80 @@
+
+ * @since 2.0
+ */
+class QueryBuilder extends \yii\db\QueryBuilder
+{
+
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = array(
+ Schema::TYPE_PK => 'serial NOT NULL PRIMARY KEY',
+ Schema::TYPE_BIGPK => 'bigserial NOT NULL PRIMARY KEY',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'text',
+ Schema::TYPE_SMALLINT => 'smallint',
+ Schema::TYPE_INTEGER => 'integer',
+ Schema::TYPE_BIGINT => 'bigint',
+ Schema::TYPE_FLOAT => 'double precision',
+ Schema::TYPE_DECIMAL => 'numeric(10,0)',
+ Schema::TYPE_DATETIME => 'timestamp',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'bytea',
+ Schema::TYPE_BOOLEAN => 'boolean',
+ Schema::TYPE_MONEY => 'numeric(19,4)',
+ );
+
+ /**
+ * Builds a SQL statement for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping an index.
+ */
+ public function dropIndex($name, $table)
+ {
+ return 'DROP INDEX ' . $this->db->quoteTableName($name);
+ }
+
+ /**
+ * Builds a SQL statement for renaming a DB table.
+ * @param string $oldName the table to be renamed. The name will be properly quoted by the method.
+ * @param string $newName the new table name. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB table.
+ */
+ public function renameTable($oldName, $newName)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($oldName) . ' RENAME TO ' . $this->db->quoteTableName($newName);
+ }
+
+ /**
+ * Builds a SQL statement for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
+ * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
+ * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
+ * will become 'varchar(255) not null'.
+ * @return string the SQL statement for changing the definition of a column.
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ return 'ALTER TABLE ' . $this->db->quoteTableName($table) . ' ALTER COLUMN '
+ . $this->db->quoteColumnName($column) . ' TYPE '
+ . $this->getColumnType($type);
+ }
+}
diff --git a/framework/yii/db/pgsql/Schema.php b/framework/yii/db/pgsql/Schema.php
new file mode 100644
index 0000000..d131342
--- /dev/null
+++ b/framework/yii/db/pgsql/Schema.php
@@ -0,0 +1,320 @@
+
+ * @since 2.0
+ */
+class Schema extends \yii\db\Schema
+{
+
+ /**
+ * The default schema used for the current session.
+ * @var string
+ */
+ public $defaultSchema = 'public';
+
+ /**
+ * @var array mapping from physical column types (keys) to abstract
+ * column types (values)
+ */
+ public $typeMap = array(
+ 'abstime' => self::TYPE_TIMESTAMP,
+ 'bit' => self::TYPE_STRING,
+ 'boolean' => self::TYPE_BOOLEAN,
+ 'box' => self::TYPE_STRING,
+ 'character' => self::TYPE_STRING,
+ 'bytea' => self::TYPE_BINARY,
+ 'char' => self::TYPE_STRING,
+ 'cidr' => self::TYPE_STRING,
+ 'circle' => self::TYPE_STRING,
+ 'date' => self::TYPE_DATE,
+ 'real' => self::TYPE_FLOAT,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'double precision' => self::TYPE_DECIMAL,
+ 'inet' => self::TYPE_STRING,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'integer' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'interval' => self::TYPE_STRING,
+ 'json' => self::TYPE_STRING,
+ 'line' => self::TYPE_STRING,
+ 'macaddr' => self::TYPE_STRING,
+ 'money' => self::TYPE_MONEY,
+ 'name' => self::TYPE_STRING,
+ 'numeric' => self::TYPE_STRING,
+ 'oid' => self::TYPE_BIGINT, // should not be used. it's pg internal!
+ 'path' => self::TYPE_STRING,
+ 'point' => self::TYPE_STRING,
+ 'polygon' => self::TYPE_STRING,
+ 'text' => self::TYPE_TEXT,
+ 'time without time zone' => self::TYPE_TIME,
+ 'timestamp without time zone' => self::TYPE_TIMESTAMP,
+ 'timestamp with time zone' => self::TYPE_TIMESTAMP,
+ 'time with time zone' => self::TYPE_TIMESTAMP,
+ 'unknown' => self::TYPE_STRING,
+ 'uuid' => self::TYPE_STRING,
+ 'bit varying' => self::TYPE_STRING,
+ 'character varying' => self::TYPE_STRING,
+ 'xml' => self::TYPE_STRING
+ );
+
+ /**
+ * Creates a query builder for the PostgreSQL database.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Resolves the table name and schema name (if any).
+ * @param TableSchema $table the table metadata object
+ * @param string $name the table name
+ */
+ protected function resolveTableNames($table, $name)
+ {
+ $parts = explode('.', str_replace('"', '', $name));
+ if (isset($parts[1])) {
+ $table->schemaName = $parts[0];
+ $table->name = $parts[1];
+ } else {
+ $table->name = $parts[0];
+ }
+ if ($table->schemaName === null) {
+ $table->schemaName = $this->defaultSchema;
+ }
+ }
+
+ /**
+ * Quotes a table name for use in a query.
+ * A simple table name has no schema prefix.
+ * @param string $name table name
+ * @return string the properly quoted table name
+ */
+ public function quoteSimpleTableName($name)
+ {
+ return strpos($name, '"') !== false ? $name : '"' . $name . '"';
+ }
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema|null driver dependent table metadata. Null if the table does not exist.
+ */
+ public function loadTableSchema($name)
+ {
+ $table = new TableSchema();
+ $this->resolveTableNames($table, $name);
+ if ($this->findColumns($table)) {
+ $this->findConstraints($table);
+ return $table;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * If not empty, the returned table names will be prefixed with the schema name.
+ * @return array all table names in the database.
+ */
+ protected function findTableNames($schema = '')
+ {
+ if ($schema === '') {
+ $schema = $this->defaultSchema;
+ }
+ $sql = <<db->createCommand($sql);
+ $command->bindParam(':schema', $schema);
+ $rows = $command->queryAll();
+ $names = array();
+ foreach ($rows as $row) {
+ if ($schema === $this->defaultSchema) {
+ $names[] = $row['table_name'];
+ } else {
+ $names[] = $row['table_schema'] . '.' . $row['table_name'];
+ }
+ }
+ return $names;
+ }
+
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findConstraints($table)
+ {
+
+ $tableName = $this->quoteValue($table->name);
+ $tableSchema = $this->quoteValue($table->schemaName);
+
+ //We need to extract the constraints de hard way since:
+ //http://www.postgresql.org/message-id/26677.1086673982@sss.pgh.pa.us
+
+ $sql = <<db->createCommand($sql)->queryAll();
+ foreach ($constraints as $constraint) {
+ $columns = explode(',', $constraint['columns']);
+ $fcolumns = explode(',', $constraint['foreign_columns']);
+ if ($constraint['foreign_table_schema'] !== $this->defaultSchema) {
+ $foreignTable = $constraint['foreign_table_schema'] . '.' . $constraint['foreign_table_name'];
+ } else {
+ $foreignTable = $constraint['foreign_table_name'];
+ }
+ $citem = array($foreignTable);
+ foreach ($columns as $idx => $column) {
+ $citem[$fcolumns[$idx]] = $column;
+ }
+ $table->foreignKeys[] = $citem;
+ }
+ }
+
+ /**
+ * Collects the metadata of table columns.
+ * @param TableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ */
+ protected function findColumns($table)
+ {
+ $tableName = $this->db->quoteValue($table->name);
+ $schemaName = $this->db->quoteValue($table->schemaName);
+ $sql = <<> 16) & 65535
+ END
+ WHEN 700 /*float4*/ THEN 24 /*FLT_MANT_DIG*/
+ WHEN 701 /*float8*/ THEN 53 /*DBL_MANT_DIG*/
+ ELSE null
+ END AS numeric_precision,
+ CASE
+ WHEN atttypid IN (21, 23, 20) THEN 0
+ WHEN atttypid IN (1700) THEN
+ CASE
+ WHEN atttypmod = -1 THEN null
+ ELSE (atttypmod - 4) & 65535
+ END
+ ELSE null
+ END AS numeric_scale,
+ CAST(
+ information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t))
+ AS numeric
+ ) AS size,
+ a.attnum = any (ct.conkey) as is_pkey
+FROM
+ pg_class c
+ LEFT JOIN pg_attribute a ON a.attrelid = c.oid
+ LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
+ LEFT JOIN pg_type t ON a.atttypid = t.oid
+ LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
+ LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p'
+WHERE
+ a.attnum > 0
+ and c.relname = {$tableName}
+ and d.nspname = {$schemaName}
+ORDER BY
+ a.attnum;
+SQL;
+
+ $columns = $this->db->createCommand($sql)->queryAll();
+ if (empty($columns)) {
+ return false;
+ }
+ foreach ($columns as $column) {
+ $column = $this->loadColumnSchema($column);
+ $table->columns[$column->name] = $column;
+ if ($column->isPrimaryKey === true) {
+ $table->primaryKey[] = $column->name;
+ if ($table->sequenceName === null && preg_match("/nextval\('\w+'(::regclass)?\)/", $column->defaultValue) === 1) {
+ $table->sequenceName = preg_replace(array('/nextval/', '/::/', '/regclass/', '/\'\)/', '/\(\'/'), '', $column->defaultValue);
+ }
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema();
+ $column->allowNull = $info['is_nullable'];
+ $column->autoIncrement = $info['is_autoinc'];
+ $column->comment = $info['column_comment'];
+ $column->dbType = $info['data_type'];
+ $column->defaultValue = $info['column_default'];
+ $column->enumValues = explode(',', str_replace(array("''"), array("'"), $info['enum_values']));
+ $column->unsigned = false; // has no meanining in PG
+ $column->isPrimaryKey = $info['is_pkey'];
+ $column->name = $info['column_name'];
+ $column->precision = $info['numeric_precision'];
+ $column->scale = $info['numeric_scale'];
+ $column->size = $info['size'];
+
+ if (isset($this->typeMap[$column->dbType])) {
+ $column->type = $this->typeMap[$column->dbType];
+ } else {
+ $column->type = self::TYPE_STRING;
+ }
+ $column->phpType = $this->getColumnPhpType($column);
+ return $column;
+ }
+}
diff --git a/framework/yii/db/sqlite/QueryBuilder.php b/framework/yii/db/sqlite/QueryBuilder.php
new file mode 100644
index 0000000..4e210f8
--- /dev/null
+++ b/framework/yii/db/sqlite/QueryBuilder.php
@@ -0,0 +1,208 @@
+
+ * @since 2.0
+ */
+class QueryBuilder extends \yii\db\QueryBuilder
+{
+ /**
+ * @var array mapping from abstract column types (keys) to physical column types (values).
+ */
+ public $typeMap = array(
+ Schema::TYPE_PK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
+ Schema::TYPE_BIGPK => 'integer PRIMARY KEY AUTOINCREMENT NOT NULL',
+ Schema::TYPE_STRING => 'varchar(255)',
+ Schema::TYPE_TEXT => 'text',
+ Schema::TYPE_SMALLINT => 'smallint',
+ Schema::TYPE_INTEGER => 'integer',
+ Schema::TYPE_BIGINT => 'bigint',
+ Schema::TYPE_FLOAT => 'float',
+ Schema::TYPE_DECIMAL => 'decimal(10,0)',
+ Schema::TYPE_DATETIME => 'datetime',
+ Schema::TYPE_TIMESTAMP => 'timestamp',
+ Schema::TYPE_TIME => 'time',
+ Schema::TYPE_DATE => 'date',
+ Schema::TYPE_BINARY => 'blob',
+ Schema::TYPE_BOOLEAN => 'boolean',
+ Schema::TYPE_MONEY => 'decimal(19,4)',
+ );
+
+ /**
+ * Creates a SQL statement for resetting the sequence value of a table's primary key.
+ * The sequence will be reset such that the primary key of the next new row inserted
+ * will have the specified value or 1.
+ * @param string $tableName the name of the table whose primary key sequence will be reset
+ * @param mixed $value the value for the primary key of the next new row inserted. If this is not set,
+ * the next new row's primary key will have a value 1.
+ * @return string the SQL statement for resetting sequence
+ * @throws InvalidParamException if the table does not exist or there is no sequence associated with the table.
+ */
+ public function resetSequence($tableName, $value = null)
+ {
+ $db = $this->db;
+ $table = $db->getTableSchema($tableName);
+ if ($table !== null && $table->sequenceName !== null) {
+ if ($value === null) {
+ $key = reset($table->primaryKey);
+ $tableName = $db->quoteTableName($tableName);
+ $value = $db->createCommand("SELECT MAX('$key') FROM $tableName")->queryScalar();
+ } else {
+ $value = (int)$value - 1;
+ }
+ try {
+ // it's possible sqlite_sequence does not exist
+ $db->createCommand("UPDATE sqlite_sequence SET seq='$value' WHERE name='{$table->name}'")->execute();
+ } catch (Exception $e) {
+ }
+ } elseif ($table === null) {
+ throw new InvalidParamException("Table not found: $tableName");
+ } else {
+ throw new InvalidParamException("There is not sequence associated with table '$tableName'.'");
+ }
+ }
+
+ /**
+ * Enables or disables integrity check.
+ * @param boolean $check whether to turn on or off the integrity check.
+ * @param string $schema the schema of the tables. Meaningless for SQLite.
+ * @param string $table the table name. Meaningless for SQLite.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function checkIntegrity($check = true, $schema = '', $table = '')
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
+
+ /**
+ * Builds a SQL statement for truncating a DB table.
+ * @param string $table the table to be truncated. The name will be properly quoted by the method.
+ * @return string the SQL statement for truncating a DB table.
+ */
+ public function truncateTable($table)
+ {
+ return "DELETE FROM " . $this->db->quoteTableName($table);
+ }
+
+ /**
+ * Builds a SQL statement for dropping an index.
+ * @param string $name the name of the index to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose index is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping an index.
+ */
+ public function dropIndex($name, $table)
+ {
+ return 'DROP INDEX ' . $this->db->quoteTableName($name);
+ }
+
+ /**
+ * Builds a SQL statement for dropping a DB column.
+ * @param string $table the table whose column is to be dropped. The name will be properly quoted by the method.
+ * @param string $column the name of the column to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a DB column.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function dropColumn($table, $column)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
+
+ /**
+ * Builds a SQL statement for renaming a column.
+ * @param string $table the table whose column is to be renamed. The name will be properly quoted by the method.
+ * @param string $oldName the old name of the column. The name will be properly quoted by the method.
+ * @param string $newName the new name of the column. The name will be properly quoted by the method.
+ * @return string the SQL statement for renaming a DB column.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function renameColumn($table, $oldName, $newName)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
+
+ /**
+ * Builds a SQL statement for adding a foreign key constraint to an existing table.
+ * The method will properly quote the table and column names.
+ * @param string $name the name of the foreign key constraint.
+ * @param string $table the table that the foreign key constraint will be added to.
+ * @param string|array $columns the name of the column to that the constraint will be added on.
+ * If there are multiple columns, separate them with commas or use an array to represent them.
+ * @param string $refTable the table that the foreign key references to.
+ * @param string|array $refColumns the name of the column that the foreign key references to.
+ * If there are multiple columns, separate them with commas or use an array to represent them.
+ * @param string $delete the ON DELETE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @param string $update the ON UPDATE option. Most DBMS support these options: RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL
+ * @return string the SQL statement for adding a foreign key constraint to an existing table.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function addForeignKey($name, $table, $columns, $refTable, $refColumns, $delete = null, $update = null)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
+
+ /**
+ * Builds a SQL statement for dropping a foreign key constraint.
+ * @param string $name the name of the foreign key constraint to be dropped. The name will be properly quoted by the method.
+ * @param string $table the table whose foreign is to be dropped. The name will be properly quoted by the method.
+ * @return string the SQL statement for dropping a foreign key constraint.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function dropForeignKey($name, $table)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
+
+ /**
+ * Builds a SQL statement for changing the definition of a column.
+ * @param string $table the table whose column is to be changed. The table name will be properly quoted by the method.
+ * @param string $column the name of the column to be changed. The name will be properly quoted by the method.
+ * @param string $type the new column type. The [[getColumnType()]] method will be invoked to convert abstract
+ * column type (if any) into the physical one. Anything that is not recognized as abstract type will be kept
+ * in the generated SQL. For example, 'string' will be turned into 'varchar(255)', while 'string not null'
+ * will become 'varchar(255) not null'.
+ * @return string the SQL statement for changing the definition of a column.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function alterColumn($table, $column, $type)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
+
+ /**
+ * Builds a SQL statement for adding a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint.
+ * @param string $table the table that the primary key constraint will be added to.
+ * @param string|array $columns comma separated string or array of columns that the primary key will consist of.
+ * @return string the SQL statement for adding a primary key constraint to an existing table.
+ * @throws NotSupportedException this is not supported by SQLite
+ */
+ public function addPrimaryKey($name, $table, $columns)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
+
+ /**
+ * Builds a SQL statement for removing a primary key constraint to an existing table.
+ * @param string $name the name of the primary key constraint to be removed.
+ * @param string $table the table that the primary key constraint will be removed from.
+ * @return string the SQL statement for removing a primary key constraint from an existing table.
+ * @throws NotSupportedException this is not supported by SQLite *
+ */
+ public function dropPrimaryKey($name, $table)
+ {
+ throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
+ }
+}
diff --git a/framework/yii/db/sqlite/Schema.php b/framework/yii/db/sqlite/Schema.php
new file mode 100644
index 0000000..bca26c1
--- /dev/null
+++ b/framework/yii/db/sqlite/Schema.php
@@ -0,0 +1,189 @@
+
+ * @since 2.0
+ */
+class Schema extends \yii\db\Schema
+{
+ /**
+ * @var array mapping from physical column types (keys) to abstract column types (values)
+ */
+ public $typeMap = array(
+ 'tinyint' => self::TYPE_SMALLINT,
+ 'bit' => self::TYPE_SMALLINT,
+ 'smallint' => self::TYPE_SMALLINT,
+ 'mediumint' => self::TYPE_INTEGER,
+ 'int' => self::TYPE_INTEGER,
+ 'integer' => self::TYPE_INTEGER,
+ 'bigint' => self::TYPE_BIGINT,
+ 'float' => self::TYPE_FLOAT,
+ 'double' => self::TYPE_FLOAT,
+ 'real' => self::TYPE_FLOAT,
+ 'decimal' => self::TYPE_DECIMAL,
+ 'numeric' => self::TYPE_DECIMAL,
+ 'tinytext' => self::TYPE_TEXT,
+ 'mediumtext' => self::TYPE_TEXT,
+ 'longtext' => self::TYPE_TEXT,
+ 'text' => self::TYPE_TEXT,
+ 'varchar' => self::TYPE_STRING,
+ 'string' => self::TYPE_STRING,
+ 'char' => self::TYPE_STRING,
+ 'datetime' => self::TYPE_DATETIME,
+ 'year' => self::TYPE_DATE,
+ 'date' => self::TYPE_DATE,
+ 'time' => self::TYPE_TIME,
+ 'timestamp' => self::TYPE_TIMESTAMP,
+ 'enum' => self::TYPE_STRING,
+ );
+
+ /**
+ * Creates a query builder for the MySQL database.
+ * This method may be overridden by child classes to create a DBMS-specific query builder.
+ * @return QueryBuilder query builder instance
+ */
+ public function createQueryBuilder()
+ {
+ return new QueryBuilder($this->db);
+ }
+
+ /**
+ * Returns all table names in the database.
+ * @param string $schema the schema of the tables. Defaults to empty string, meaning the current or default schema.
+ * If not empty, the returned table names will be prefixed with the schema name.
+ * @return array all table names in the database.
+ */
+ protected function findTableNames($schema = '')
+ {
+ $sql = "SELECT DISTINCT tbl_name FROM sqlite_master WHERE tbl_name<>'sqlite_sequence'";
+ return $this->db->createCommand($sql)->queryColumn();
+ }
+
+ /**
+ * Loads the metadata for the specified table.
+ * @param string $name table name
+ * @return TableSchema driver dependent table metadata. Null if the table does not exist.
+ */
+ protected function loadTableSchema($name)
+ {
+ $table = new TableSchema;
+ $table->name = $name;
+
+ if ($this->findColumns($table)) {
+ $this->findConstraints($table);
+ return $table;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Collects the table column metadata.
+ * @param TableSchema $table the table metadata
+ * @return boolean whether the table exists in the database
+ */
+ protected function findColumns($table)
+ {
+ $sql = "PRAGMA table_info(" . $this->quoteSimpleTableName($table->name) . ')';
+ $columns = $this->db->createCommand($sql)->queryAll();
+ if (empty($columns)) {
+ return false;
+ }
+
+ foreach ($columns as $info) {
+ $column = $this->loadColumnSchema($info);
+ $table->columns[$column->name] = $column;
+ if ($column->isPrimaryKey) {
+ $table->primaryKey[] = $column->name;
+ }
+ }
+ if (count($table->primaryKey) === 1 && !strncasecmp($table->columns[$table->primaryKey[0]]->dbType, 'int', 3)) {
+ $table->sequenceName = '';
+ $table->columns[$table->primaryKey[0]]->autoIncrement = true;
+ }
+
+ return true;
+ }
+
+ /**
+ * Collects the foreign key column details for the given table.
+ * @param TableSchema $table the table metadata
+ */
+ protected function findConstraints($table)
+ {
+ $sql = "PRAGMA foreign_key_list(" . $this->quoteSimpleTableName($table->name) . ')';
+ $keys = $this->db->createCommand($sql)->queryAll();
+ foreach ($keys as $key) {
+ $id = (int)$key['id'];
+ if (!isset($table->foreignKeys[$id])) {
+ $table->foreignKeys[$id] = array($key['table'], $key['from'] => $key['to']);
+ } else {
+ // composite FK
+ $table->foreignKeys[$id][$key['from']] = $key['to'];
+ }
+ }
+ }
+
+ /**
+ * Loads the column information into a [[ColumnSchema]] object.
+ * @param array $info column information
+ * @return ColumnSchema the column schema object
+ */
+ protected function loadColumnSchema($info)
+ {
+ $column = new ColumnSchema;
+ $column->name = $info['name'];
+ $column->allowNull = !$info['notnull'];
+ $column->isPrimaryKey = $info['pk'] != 0;
+
+ $column->dbType = $info['type'];
+ $column->unsigned = strpos($column->dbType, 'unsigned') !== false;
+
+ $column->type = self::TYPE_STRING;
+ if (preg_match('/^(\w+)(?:\(([^\)]+)\))?/', $column->dbType, $matches)) {
+ $type = $matches[1];
+ if (isset($this->typeMap[$type])) {
+ $column->type = $this->typeMap[$type];
+ }
+
+ if (!empty($matches[2])) {
+ $values = explode(',', $matches[2]);
+ $column->size = $column->precision = (int)$values[0];
+ if (isset($values[1])) {
+ $column->scale = (int)$values[1];
+ }
+ if ($column->size === 1 && ($type === 'tinyint' || $type === 'bit')) {
+ $column->type = 'boolean';
+ } elseif ($type === 'bit') {
+ if ($column->size > 32) {
+ $column->type = 'bigint';
+ } elseif ($column->size === 32) {
+ $column->type = 'integer';
+ }
+ }
+ }
+ }
+ $column->phpType = $this->getColumnPhpType($column);
+
+ $value = $info['dflt_value'];
+ if ($column->type === 'string') {
+ $column->defaultValue = trim($value, "'\"");
+ } else {
+ $column->defaultValue = $column->typecast(strcasecmp($value, 'null') ? $value : null);
+ }
+
+ return $column;
+ }
+}
diff --git a/framework/yii/debug/DebugAsset.php b/framework/yii/debug/DebugAsset.php
new file mode 100644
index 0000000..4f3ee89
--- /dev/null
+++ b/framework/yii/debug/DebugAsset.php
@@ -0,0 +1,25 @@
+
+ * @since 2.0
+ */
+class DebugAsset extends AssetBundle
+{
+ public $sourcePath = '@yii/debug/assets';
+ public $css = array(
+ 'main.css',
+ );
+ public $depends = array(
+ 'yii\web\YiiAsset',
+ 'yii\bootstrap\BootstrapAsset',
+ );
+}
diff --git a/framework/yii/debug/LogTarget.php b/framework/yii/debug/LogTarget.php
new file mode 100644
index 0000000..30392f0
--- /dev/null
+++ b/framework/yii/debug/LogTarget.php
@@ -0,0 +1,105 @@
+
+ * @since 2.0
+ */
+class LogTarget extends Target
+{
+ /**
+ * @var Module
+ */
+ public $module;
+ public $tag;
+
+ /**
+ * @param \yii\debug\Module $module
+ * @param array $config
+ */
+ public function __construct($module, $config = array())
+ {
+ parent::__construct($config);
+ $this->module = $module;
+ $this->tag = uniqid();
+ }
+
+ /**
+ * Exports log messages to a specific destination.
+ * Child classes must implement this method.
+ */
+ public function export()
+ {
+ $path = $this->module->dataPath;
+ if (!is_dir($path)) {
+ mkdir($path);
+ }
+ $indexFile = "$path/index.data";
+ if (!is_file($indexFile)) {
+ $manifest = array();
+ } else {
+ $manifest = unserialize(file_get_contents($indexFile));
+ }
+ $request = Yii::$app->getRequest();
+ $manifest[$this->tag] = $summary = array(
+ 'tag' => $this->tag,
+ 'url' => $request->getAbsoluteUrl(),
+ 'ajax' => $request->getIsAjax(),
+ 'method' => $request->getMethod(),
+ 'ip' => $request->getUserIP(),
+ 'time' => time(),
+ );
+ $this->gc($manifest);
+
+ $dataFile = "$path/{$this->tag}.data";
+ $data = array();
+ foreach ($this->module->panels as $id => $panel) {
+ $data[$id] = $panel->save();
+ }
+ $data['summary'] = $summary;
+ file_put_contents($dataFile, serialize($data));
+ file_put_contents($indexFile, serialize($manifest));
+ }
+
+ /**
+ * Processes the given log messages.
+ * This method will filter the given messages with [[levels]] and [[categories]].
+ * And if requested, it will also export the filtering result to specific medium (e.g. email).
+ * @param array $messages log messages to be processed. See [[Logger::messages]] for the structure
+ * of each message.
+ * @param boolean $final whether this method is called at the end of the current application
+ */
+ public function collect($messages, $final)
+ {
+ $this->messages = array_merge($this->messages, $messages);
+ if ($final) {
+ $this->export($this->messages);
+ }
+ }
+
+ protected function gc(&$manifest)
+ {
+ if (count($manifest) > $this->module->historySize + 10) {
+ $n = count($manifest) - $this->module->historySize;
+ foreach (array_keys($manifest) as $tag) {
+ $file = $this->module->dataPath . "/$tag.data";
+ @unlink($file);
+ unset($manifest[$tag]);
+ if (--$n <= 0) {
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/framework/yii/debug/Module.php b/framework/yii/debug/Module.php
new file mode 100644
index 0000000..260a981
--- /dev/null
+++ b/framework/yii/debug/Module.php
@@ -0,0 +1,132 @@
+
+ * @since 2.0
+ */
+class Module extends \yii\base\Module
+{
+ /**
+ * @var array the list of IPs that are allowed to access this module.
+ * Each array element represents a single IP filter which can be either an IP address
+ * or an address with wildcard (e.g. 192.168.0.*) to represent a network segment.
+ * The default value is `array('127.0.0.1', '::1')`, which means the module can only be accessed
+ * by localhost.
+ */
+ public $allowedIPs = array('127.0.0.1', '::1');
+ /**
+ * @var string the namespace that controller classes are in.
+ */
+ public $controllerNamespace = 'yii\debug\controllers';
+ /**
+ * @var LogTarget
+ */
+ public $logTarget;
+ /**
+ * @var array|Panel[]
+ */
+ public $panels = array();
+ /**
+ * @var string the directory storing the debugger data files. This can be specified using a path alias.
+ */
+ public $dataPath = '@runtime/debug';
+ /**
+ * @var integer the maximum number of debug data files to keep. If there are more files generated,
+ * the oldest ones will be removed.
+ */
+ public $historySize = 50;
+
+
+ public function init()
+ {
+ parent::init();
+ $this->dataPath = Yii::getAlias($this->dataPath);
+ $this->logTarget = Yii::$app->getLog()->targets['debug'] = new LogTarget($this);
+ // do not initialize view component before application is ready (needed when debug in preload)
+ Yii::$app->on(Application::EVENT_BEFORE_ACTION, function() {
+ Yii::$app->getView()->on(View::EVENT_END_BODY, array($this, 'renderToolbar'));
+ });
+
+ foreach (array_merge($this->corePanels(), $this->panels) as $id => $config) {
+ $config['module'] = $this;
+ $config['id'] = $id;
+ $this->panels[$id] = Yii::createObject($config);
+ }
+ }
+
+ public function beforeAction($action)
+ {
+ Yii::$app->getView()->off(View::EVENT_END_BODY, array($this, 'renderToolbar'));
+ unset(Yii::$app->getLog()->targets['debug']);
+ $this->logTarget = null;
+
+ if ($this->checkAccess($action)) {
+ return parent::beforeAction($action);
+ } elseif ($action->id === 'toolbar') {
+ return false;
+ } else {
+ throw new HttpException(403, 'You are not allowed to access this page.');
+ }
+ }
+
+ public function renderToolbar($event)
+ {
+ if (!$this->checkAccess()) {
+ return;
+ }
+ $url = Yii::$app->getUrlManager()->createUrl($this->id . '/default/toolbar', array(
+ 'tag' => $this->logTarget->tag,
+ ));
+ echo '
';
+ /** @var View $view */
+ $view = $event->sender;
+ echo '';
+ echo '';
+ }
+
+ protected function checkAccess()
+ {
+ $ip = Yii::$app->getRequest()->getUserIP();
+ foreach ($this->allowedIPs as $filter) {
+ if ($filter === '*' || $filter === $ip || (($pos = strpos($filter, '*')) !== false && !strncmp($ip, $filter, $pos))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ protected function corePanels()
+ {
+ return array(
+ 'config' => array(
+ 'class' => 'yii\debug\panels\ConfigPanel',
+ ),
+ 'request' => array(
+ 'class' => 'yii\debug\panels\RequestPanel',
+ ),
+ 'log' => array(
+ 'class' => 'yii\debug\panels\LogPanel',
+ ),
+ 'profiling' => array(
+ 'class' => 'yii\debug\panels\ProfilingPanel',
+ ),
+ 'db' => array(
+ 'class' => 'yii\debug\panels\DbPanel',
+ ),
+ );
+ }
+}
diff --git a/framework/yii/debug/Panel.php b/framework/yii/debug/Panel.php
new file mode 100644
index 0000000..6782264
--- /dev/null
+++ b/framework/yii/debug/Panel.php
@@ -0,0 +1,85 @@
+
+ * @since 2.0
+ */
+class Panel extends Component
+{
+ public $id;
+ public $tag;
+ /**
+ * @var Module
+ */
+ public $module;
+ public $data;
+
+ /**
+ * @return string name of the panel
+ */
+ public function getName()
+ {
+ return '';
+ }
+
+ /**
+ * @return string content that is displayed at debug toolbar
+ */
+ public function getSummary()
+ {
+ return '';
+ }
+
+ /**
+ * @return string content that is displayed in debugger detail view
+ */
+ public function getDetail()
+ {
+ return '';
+ }
+
+ /**
+ * Saves data to be later used in debugger detail view.
+ * This method is called on every page where debugger is enabled.
+ *
+ * @return mixed data to be saved
+ */
+ public function save()
+ {
+ return null;
+ }
+
+ public function load($data)
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * @return string URL pointing to panel detail view
+ */
+ public function getUrl()
+ {
+ return Yii::$app->getUrlManager()->createUrl($this->module->id . '/default/view', array(
+ 'panel' => $this->id,
+ 'tag' => $this->tag,
+ ));
+ }
+}
diff --git a/framework/yii/debug/assets/main.css b/framework/yii/debug/assets/main.css
new file mode 100644
index 0000000..7953873
--- /dev/null
+++ b/framework/yii/debug/assets/main.css
@@ -0,0 +1,153 @@
+body {
+ padding-top: 60px;
+}
+
+#yii-debug-toolbar {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ margin: 0 0 20px 0;
+ padding: 0;
+ z-index: 1000000;
+ font: 11px Verdana, Arial, sans-serif;
+ text-align: left;
+ height: 40px;
+ border-bottom: 1px solid #e4e4e4;
+ background: rgb(237,237,237);
+ background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2VkZWRlZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUzJSIgc3RvcC1jb2xvcj0iI2Y2ZjZmNiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
+ background: -moz-linear-gradient(top, rgba(237,237,237,1) 0%, rgba(246,246,246,1) 53%, rgba(255,255,255,1) 100%);
+ background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,rgba(237,237,237,1)), color-stop(53%,rgba(246,246,246,1)), color-stop(100%,rgba(255,255,255,1)));
+ background: -webkit-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%);
+ background: -o-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%);
+ background: -ms-linear-gradient(top, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%);
+ background: linear-gradient(to bottom, rgba(237,237,237,1) 0%,rgba(246,246,246,1) 53%,rgba(255,255,255,1) 100%);
+ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ededed', endColorstr='#ffffff',GradientType=0 );
+}
+
+.yii-debug-toolbar-block {
+ float: left;
+ margin: 0;
+ border-right: 1px solid #e4e4e4;
+ padding: 4px 8px;
+ line-height: 32px;
+}
+
+.yii-debug-toolbar-block.title {
+ font-size: 1.4em;
+}
+
+.yii-debug-toolbar-block a {
+ text-decoration: none;
+ color: black;
+}
+
+.yii-debug-toolbar-block span {
+}
+
+.yii-debug-toolbar-block img {
+ vertical-align: middle;
+}
+
+#yii-debug-toolbar .label {
+ display: inline-block;
+ padding: 2px 4px;
+ font-size: 11.844px;
+ font-weight: normal;
+ line-height: 14px;
+ color: #ffffff;
+ text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
+ white-space: nowrap;
+ vertical-align: baseline;
+ background-color: #999999;
+}
+
+#yii-debug-toolbar .label {
+ -webkit-border-radius: 3px;
+ -moz-border-radius: 3px;
+ border-radius: 3px;
+}
+
+#yii-debug-toolbar .label:empty {
+ display: none;
+}
+
+#yii-debug-toolbar a.label:hover,
+#yii-debug-toolbar a.label:focus {
+ color: #ffffff;
+ text-decoration: none;
+ cursor: pointer;
+}
+
+#yii-debug-toolbar .label-important {
+ background-color: #b94a48;
+}
+
+#yii-debug-toolbar .label-important[href] {
+ background-color: #953b39;
+}
+
+#yii-debug-toolbar .label-warning,
+#yii-debug-toolbar .badge-warning {
+ background-color: #f89406;
+}
+
+#yii-debug-toolbar .label-warning[href] {
+ background-color: #c67605;
+}
+
+#yii-debug-toolbar .label-success {
+ background-color: #468847;
+}
+
+#yii-debug-toolbar .label-success[href] {
+ background-color: #356635;
+}
+
+#yii-debug-toolbar .label-info {
+ background-color: #3a87ad;
+}
+
+#yii-debug-toolbar .label-info[href] {
+ background-color: #2d6987;
+}
+
+#yii-debug-toolbar .label-inverse,
+#yii-debug-toolbar .badge-inverse {
+ background-color: #333333;
+}
+
+#yii-debug-toolbar .label-inverse[href],
+#yii-debug-toolbar .badge-inverse[href] {
+ background-color: #1a1a1a;
+}
+span.indent {
+ color: #ccc;
+}
+
+ul.trace {
+ font-size: 12px;
+ color: #999;
+ margin: 2px 0 0 0;
+ padding: 0;
+ list-style: none;
+ white-space: normal;
+}
+
+.callout-danger {
+ background-color: #fcf2f2;
+ border-color: #dFb5b4;
+}
+.callout {
+ margin: 0 0 10px 0;
+ padding: 5px;
+}
+
+.list-group .glyphicon {
+ float: right;
+}
+
+td, th {
+ white-space: pre;
+ word-wrap: break-word;
+}
diff --git a/framework/yii/debug/controllers/DefaultController.php b/framework/yii/debug/controllers/DefaultController.php
new file mode 100644
index 0000000..2026dc7
--- /dev/null
+++ b/framework/yii/debug/controllers/DefaultController.php
@@ -0,0 +1,107 @@
+
+ * @since 2.0
+ */
+class DefaultController extends Controller
+{
+ public $layout = 'main';
+ /**
+ * @var \yii\debug\Module
+ */
+ public $module;
+ /**
+ * @var array the summary data (e.g. URL, time)
+ */
+ public $summary;
+
+ public function actionIndex()
+ {
+ return $this->render('index', array(
+ 'manifest' => $this->getManifest(),
+ ));
+ }
+
+ public function actionView($tag = null, $panel = null)
+ {
+ if ($tag === null) {
+ $tags = array_keys($this->getManifest());
+ $tag = reset($tags);
+ }
+ $this->loadData($tag);
+ if (isset($this->module->panels[$panel])) {
+ $activePanel = $this->module->panels[$panel];
+ } else {
+ $activePanel = $this->module->panels['request'];
+ }
+ return $this->render('view', array(
+ 'tag' => $tag,
+ 'summary' => $this->summary,
+ 'manifest' => $this->getManifest(),
+ 'panels' => $this->module->panels,
+ 'activePanel' => $activePanel,
+ ));
+ }
+
+ public function actionToolbar($tag)
+ {
+ $this->loadData($tag);
+ return $this->renderPartial('toolbar', array(
+ 'tag' => $tag,
+ 'panels' => $this->module->panels,
+ ));
+ }
+
+ public function actionPhpinfo()
+ {
+ phpinfo();
+ }
+
+ private $_manifest;
+
+ protected function getManifest()
+ {
+ if ($this->_manifest === null) {
+ $indexFile = $this->module->dataPath . '/index.php';
+ if (is_file($indexFile)) {
+ $this->_manifest = array_reverse(unserialize(file_get_contents($indexFile)), true);
+ } else {
+ $this->_manifest = array();
+ }
+ }
+ return $this->_manifest;
+ }
+
+ protected function loadData($tag)
+ {
+ $manifest = $this->getManifest();
+ if (isset($manifest[$tag])) {
+ $dataFile = $this->module->dataPath . "/$tag.php";
+ $data = unserialize(file_get_contents($dataFile));
+ foreach ($this->module->panels as $id => $panel) {
+ if (isset($data[$id])) {
+ $panel->tag = $tag;
+ $panel->load($data[$id]);
+ } else {
+ // remove the panel since it has not received any data
+ unset($this->module->panels[$id]);
+ }
+ }
+ $this->summary = $data['summary'];
+ } else {
+ throw new HttpException(404, "Unable to find debug data tagged with '$tag'.");
+ }
+ }
+}
diff --git a/framework/yii/debug/panels/ConfigPanel.php b/framework/yii/debug/panels/ConfigPanel.php
new file mode 100644
index 0000000..eb0d325
--- /dev/null
+++ b/framework/yii/debug/panels/ConfigPanel.php
@@ -0,0 +1,115 @@
+
+ * @since 2.0
+ */
+class ConfigPanel extends Panel
+{
+ public function getName()
+ {
+ return 'Configuration';
+ }
+
+ public static function getYiiLogo()
+ {
+ return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB0AAAAeCAYAAADQBxWhAAAACXBIWXMAAAsTAAALEwEAmpwYAAAKT2lDQ1BQaG90b3Nob3AgSUNDIHByb2ZpbGUAAHjanVNnVFPpFj333vRCS4iAlEtvUhUIIFJCi4AUkSYqIQkQSoghodkVUcERRUUEG8igiAOOjoCMFVEsDIoK2AfkIaKOg6OIisr74Xuja9a89+bN/rXXPues852zzwfACAyWSDNRNYAMqUIeEeCDx8TG4eQuQIEKJHAAEAizZCFz/SMBAPh+PDwrIsAHvgABeNMLCADATZvAMByH/w/qQplcAYCEAcB0kThLCIAUAEB6jkKmAEBGAYCdmCZTAKAEAGDLY2LjAFAtAGAnf+bTAICd+Jl7AQBblCEVAaCRACATZYhEAGg7AKzPVopFAFgwABRmS8Q5ANgtADBJV2ZIALC3AMDOEAuyAAgMADBRiIUpAAR7AGDIIyN4AISZABRG8lc88SuuEOcqAAB4mbI8uSQ5RYFbCC1xB1dXLh4ozkkXKxQ2YQJhmkAuwnmZGTKBNA/g88wAAKCRFRHgg/P9eM4Ors7ONo62Dl8t6r8G/yJiYuP+5c+rcEAAAOF0ftH+LC+zGoA7BoBt/qIl7gRoXgugdfeLZrIPQLUAoOnaV/Nw+H48PEWhkLnZ2eXk5NhKxEJbYcpXff5nwl/AV/1s+X48/Pf14L7iJIEyXYFHBPjgwsz0TKUcz5IJhGLc5o9H/LcL//wd0yLESWK5WCoU41EScY5EmozzMqUiiUKSKcUl0v9k4t8s+wM+3zUAsGo+AXuRLahdYwP2SycQWHTA4vcAAPK7b8HUKAgDgGiD4c93/+8//UegJQCAZkmScQAAXkQkLlTKsz/HCAAARKCBKrBBG/TBGCzABhzBBdzBC/xgNoRCJMTCQhBCCmSAHHJgKayCQiiGzbAdKmAv1EAdNMBRaIaTcA4uwlW4Dj1wD/phCJ7BKLyBCQRByAgTYSHaiAFiilgjjggXmYX4IcFIBBKLJCDJiBRRIkuRNUgxUopUIFVIHfI9cgI5h1xGupE7yAAygvyGvEcxlIGyUT3UDLVDuag3GoRGogvQZHQxmo8WoJvQcrQaPYw2oefQq2gP2o8+Q8cwwOgYBzPEbDAuxsNCsTgsCZNjy7EirAyrxhqwVqwDu4n1Y8+xdwQSgUXACTYEd0IgYR5BSFhMWE7YSKggHCQ0EdoJNwkDhFHCJyKTqEu0JroR+cQYYjIxh1hILCPWEo8TLxB7iEPENyQSiUMyJ7mQAkmxpFTSEtJG0m5SI+ksqZs0SBojk8naZGuyBzmULCAryIXkneTD5DPkG+Qh8lsKnWJAcaT4U+IoUspqShnlEOU05QZlmDJBVaOaUt2ooVQRNY9aQq2htlKvUYeoEzR1mjnNgxZJS6WtopXTGmgXaPdpr+h0uhHdlR5Ol9BX0svpR+iX6AP0dwwNhhWDx4hnKBmbGAcYZxl3GK+YTKYZ04sZx1QwNzHrmOeZD5lvVVgqtip8FZHKCpVKlSaVGyovVKmqpqreqgtV81XLVI+pXlN9rkZVM1PjqQnUlqtVqp1Q61MbU2epO6iHqmeob1Q/pH5Z/YkGWcNMw09DpFGgsV/jvMYgC2MZs3gsIWsNq4Z1gTXEJrHN2Xx2KruY/R27iz2qqaE5QzNKM1ezUvOUZj8H45hx+Jx0TgnnKKeX836K3hTvKeIpG6Y0TLkxZVxrqpaXllirSKtRq0frvTau7aedpr1Fu1n7gQ5Bx0onXCdHZ4/OBZ3nU9lT3acKpxZNPTr1ri6qa6UbobtEd79up+6Ynr5egJ5Mb6feeb3n+hx9L/1U/W36p/VHDFgGswwkBtsMzhg8xTVxbzwdL8fb8VFDXcNAQ6VhlWGX4YSRudE8o9VGjUYPjGnGXOMk423GbcajJgYmISZLTepN7ppSTbmmKaY7TDtMx83MzaLN1pk1mz0x1zLnm+eb15vft2BaeFostqi2uGVJsuRaplnutrxuhVo5WaVYVVpds0atna0l1rutu6cRp7lOk06rntZnw7Dxtsm2qbcZsOXYBtuutm22fWFnYhdnt8Wuw+6TvZN9un2N/T0HDYfZDqsdWh1+c7RyFDpWOt6azpzuP33F9JbpL2dYzxDP2DPjthPLKcRpnVOb00dnF2e5c4PziIuJS4LLLpc+Lpsbxt3IveRKdPVxXeF60vWdm7Obwu2o26/uNu5p7ofcn8w0nymeWTNz0MPIQ+BR5dE/C5+VMGvfrH5PQ0+BZ7XnIy9jL5FXrdewt6V3qvdh7xc+9j5yn+M+4zw33jLeWV/MN8C3yLfLT8Nvnl+F30N/I/9k/3r/0QCngCUBZwOJgUGBWwL7+Hp8Ib+OPzrbZfay2e1BjKC5QRVBj4KtguXBrSFoyOyQrSH355jOkc5pDoVQfujW0Adh5mGLw34MJ4WHhVeGP45wiFga0TGXNXfR3ENz30T6RJZE3ptnMU85ry1KNSo+qi5qPNo3ujS6P8YuZlnM1VidWElsSxw5LiquNm5svt/87fOH4p3iC+N7F5gvyF1weaHOwvSFpxapLhIsOpZATIhOOJTwQRAqqBaMJfITdyWOCnnCHcJnIi/RNtGI2ENcKh5O8kgqTXqS7JG8NXkkxTOlLOW5hCepkLxMDUzdmzqeFpp2IG0yPTq9MYOSkZBxQqohTZO2Z+pn5mZ2y6xlhbL+xW6Lty8elQfJa7OQrAVZLQq2QqboVFoo1yoHsmdlV2a/zYnKOZarnivN7cyzytuQN5zvn//tEsIS4ZK2pYZLVy0dWOa9rGo5sjxxedsK4xUFK4ZWBqw8uIq2Km3VT6vtV5eufr0mek1rgV7ByoLBtQFr6wtVCuWFfevc1+1dT1gvWd+1YfqGnRs+FYmKrhTbF5cVf9go3HjlG4dvyr+Z3JS0qavEuWTPZtJm6ebeLZ5bDpaql+aXDm4N2dq0Dd9WtO319kXbL5fNKNu7g7ZDuaO/PLi8ZafJzs07P1SkVPRU+lQ27tLdtWHX+G7R7ht7vPY07NXbW7z3/T7JvttVAVVN1WbVZftJ+7P3P66Jqun4lvttXa1ObXHtxwPSA/0HIw6217nU1R3SPVRSj9Yr60cOxx++/p3vdy0NNg1VjZzG4iNwRHnk6fcJ3/ceDTradox7rOEH0x92HWcdL2pCmvKaRptTmvtbYlu6T8w+0dbq3nr8R9sfD5w0PFl5SvNUyWna6YLTk2fyz4ydlZ19fi753GDborZ752PO32oPb++6EHTh0kX/i+c7vDvOXPK4dPKy2+UTV7hXmq86X23qdOo8/pPTT8e7nLuarrlca7nuer21e2b36RueN87d9L158Rb/1tWeOT3dvfN6b/fF9/XfFt1+cif9zsu72Xcn7q28T7xf9EDtQdlD3YfVP1v+3Njv3H9qwHeg89HcR/cGhYPP/pH1jw9DBY+Zj8uGDYbrnjg+OTniP3L96fynQ89kzyaeF/6i/suuFxYvfvjV69fO0ZjRoZfyl5O/bXyl/erA6xmv28bCxh6+yXgzMV70VvvtwXfcdx3vo98PT+R8IH8o/2j5sfVT0Kf7kxmTk/8EA5jz/GMzLdsAAAAgY0hSTQAAeiUAAICDAAD5/wAAgOkAAHUwAADqYAAAOpgAABdvkl/FRgAADcZJREFUeAEAtg1J8gHaKRUAAP8AAAEGAQACDgMAAQgDAAIIAQABCQMAAQgEAAIKAwACCAQAAQgCAAIJBQACCQQAAgkFAAIJBwAQCwkA0hgAANAO+gAM/AEAAQABAPn++wD2/PkA+f38Of3+/Wb+//9S/v/+sQABAV4DAAEAAAAAAAQAAAAAAAAAAAD/AAAA/wAAAP8AGwD/ABoA/wAAAP8A5gD/AOUA/wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AAAAAAB+BgIEgf8BAwD9//4A/v/9Xfz+/hEAAABNAAAAAAQA/wAAAP8AMgADAGEAAwE7AQUBJwAGAgoBBQIAAQUB9gADAdoAAQDFAP//sQD//7sAAAAAAAAAAAAAAAAAAAAA/wD/AP///wD//wAA/wD/AAAAAH4HAwWBAP8AAP7//wD9//4A/f8DAAIB/Uz9//1mAQECmgHaKRUAAAIApwMPBFgCDAQAAQsCAAIJAwABBwMAAgkDAAEHAwABBQIAAQYDAAEFA8cCCAOEAggFtgIKBwAQCgkAzhsBANQO+wAP+wEA/gD/QP///78AAAAA/gD+AP7+/wD9//4A/P/+AP39/gD7//zp/gD/GAQCCAIAAAQA5wAJAgABCAIAAQcCAAEGAwACCAIAAAcCAAEHAgABBgQAAgcEAAIGAjkABAK1AAEAnwD//2f8AP77FPwHABACAwAEBAAAAP/+jgD//wD/AAAA/f/+AP4B/gD9//4AAv79APwB/QAA/f8X/P7+PgQCCgMAAAIBzgAGAQABBgEAAgUCAAIGAQABBgIAAQYDAAIFBAAABwQAAQcCAAEGAwABBQUAAQQCYQEDAiv6Af9HFvgD8A/+AQD2A/4hBf4CMQAAAQD/AP4A/v//AP7+/gD8//4AAgECAAL/AAAB/wAAAgD+RgQACwMAAP8AwwIFAQABBgIAAQYCAAAHAwABBgMAAQUDAAEHAwABBgIAAgYDAAEGBQACBgQAAgUEAAAFAjb9AwG+CPz+ORv6BfndDgMsBvsBAAAAAAD/AP4A/v/+APwB/gAC//0AAv4CAAL+AAAAAwEAAAH8FAICBgEAAgYA4QAEAscBBQIAAQYCAAEFAgAABAIAAQUDAAEFAwACBgMAAQYFAAIGBAABBwQAAAgEAAIHBQACCAYx/gMBpR7zAAAP/wbaBAUHAAcEBQAGAwYABgMGAAcDBQAFAwUABAMDAAQCBQAFAgMABAED/wICDAQAAgwFAAIGAngBAwEAAAUCAAEDAQACBQIAAQUCAAEFAgABBQQAAQYDAAEHBAACBgQAAgUDAAEGAwACBwUA/wn+U/0FHlULABjZBQX74AYDBwAGBAUABQMFAAUDBAAGAgUABQIEAAUCAwAEAQQABAID6AIABQEAAAYBAAAEAcIAAwGZAQMBAAAEAgAABAMAAgUCAAEEAgABBAIAAgQDAAEEAwABBQIAAQYDAAIHBQACBgQAAwYEAP8KAKIHAhEABwQChgYEBQAGAgUABwMFAAUCBQADAgMABQIEAAMCAwADAgMAAwIEugIA/wAAAP8AAAD+/wAAAABoAAMBqgIEAgABBAIAAAMBAAEEAwAABAMAAQUDAAEFAgAABAMAAgUEAAEFBAABBgUAAAcKAAUG8QgH/A93B/4amwYF/f8FAwYABAIDAAUDBAAEAgMAAwIDAAMBAgACAQHkBQIDxwIAAAAAAAAAAAAAAAAAAAAAAQABVwACAnsBAwH0AQMCAAEEAgABBAIAAAMCAAEDAgACBAMAAQUDAAEEAwABBQQAAgcFAP4FBQADAPqABfwaAQQDBbEEAwUAAwMFAAMCAwAEAgMAAwECAAMBAgACAQKaBAIDAAIAAAAAAAAAAAAAAAAAAAAAAAAAAP8A/4YAAAAvAQIBhQABAcoBAgIAAgMCAAEDAgABBAMAAAMDAAEEAwABBQQAAAcCAPwECwD9AgAIAf8LUQQBEaYGAwEAAwIEAAICAgACAgIAAQECAAECAvEDAgOTBAIDAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADeAAAAfAABADcAAgAx/wIAdwACArUAAgL3AQICAAEDAwABAwMAAAYCAPkCDgD8AgoA/QAIbP//Ec0EBAD7AgECAAIBAgACAAIAAgABAAEAAXEEAgPwBQIFAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADQAAAAigAAAEwBAAAxAAIBYgACArMDA/v8AAXzAPcADwD9AgkA/gIJQf//BBsCAfrZAf8CAAAAAAAAAAAA/wAAuAEBAp8FAgUABAIEAAIA//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP8AAwAHAffZBgHtnwQD8k4ABPQp1vVFpvYCFgANCPUA/QIIAPr9Eyb8/AOx/wH7AP///wD+//7nAQEAWQUCBAAEAgQABAIEAAT98esAAQYJAAMLEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD/AAIACQH3AAME7AD4AigA3/0sANPtLAD5Ag3c5AE5GxcK8QAzEsgAFAnzAPH7ESMC/ATg/v/1AP3+AP7///9PAQAB8AIBAQAAAAAAAAAAAAOGorAA+9zPAPvg1wADFBQAAgYCAAEGAwAABQYACwT4AA0F9AAIB/UA8QIXANf8LgCp+WIAtvNKAOP3GwDu/BmLEAPuWvT8CgDh9iYABwX+ABUN+PD8++EL+/zuNP3/A08A//+//wD/AAAAAAAAAAAAAAAAAAH///8A+ubdAOdzRQD/7t8AESYZAA0UCAACCPwA8A4iANsJLwDRBC8A2P0rAN37IgAIAfYABv70AA0LBkERCwe+BwQCAAkHAAAAAwkA+wMRADEZ7N0qCYfF9/jR0/4CFoz///wA/f3+AAAAAAAAAAAAAAAAAAH///8A/gAAAPXn4QD90bsA58y1APH38wAIEAkApA5sANICMgD//QAACQD1AA0C8wD//wAABAICEQsIBN4IBgQQBwUCAAkGAwAJBgIAAwQGAP0DFgAuEqk+FQbDw/j+GAD///0A/v7+AAAAAAAAAAAAAAAAAAH///8A+vv7AP4FBQAIAAAAlL7hAJC+6AAZEgwA/gACAAr/9AABAAAAAQD/AP8AAQD+//8ADQgFqw0IBlQIBQMACAYDAAgFBAAHBgMACgYBAAYFBP8BBA0XAwH+6/8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAQAAAP729gAACgYA6/T4AOf1+gAQDPYAA//8APIA/wAAAAEAAAAAAP3//wAGAAE5BQECVAEA+wD8/v8A/f7/AP7+/wD9//4A/v8EAAr+/OYD/fLr/v8CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAA/v0AOx0HAIkA+ADf7/sABgMBAAAAAAAAAAAAAAAAAP4A/wAMBAR+AP8AAP3//wD8/v8A/f7+AP7+/wD9/v8A/v7/AP//BLgC/P4A//8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAD+/P4ANyENABgPAwDh4/QAAAsEAAAAAAAAAAAAAP8AAAAAABTyCAVI/f//ABX//gAO/v4A/v4EAP3+/wD8//8A/v//AP8AAJICAQEA////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAD2+v0ADxAIAEUlEACc0+0AAAwEAAD+AAAAAAAAAAEAAAD//1b49/oA5P3/APn+/wAW/v8AD/3/AP3+/wD8/v4A/f4Fofn8/dL//v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAD/AAAAAPv9AE4oEQCcCwQAAP3/AAACAQAAAAAAAAAAAAABATAAAP8A/fr7AOj9/gD3/gYAGP//ABH+/gAK/QTfBgMCZwEAAgD9/v8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAABAAAA8fH5AE0zFwA3MRYAAOv3AAAAAAAA/v8AAP7/AAD/ABgAAgEAAAMBAAD9/QDx8/gA4fT5AOX3+tv4/P4/AwEB+QMCAQADAgEAAwIBAAMCAQADAgEAAwIBAAMCAQADAgEAAwIBAAIAAAAAAQEBAAAAAAAAAAAA9/7/AAAAAACGRB0AAAQDAAD5/wAA/gEAAP4BAAAAAA4A/gAAAPr/AAD4/wAC+fwA+Pb4qfH7/jgDAgHjAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT///8G/Pz8+gAAAAD///8AAgAAAPHt9wBCKBEAdFIfAMbZ7AARCwYADQkCAM7d9xzg6foABQ0D8SkVA7spHA+grNnxtfv8/gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAT9/f3+////AQQEBAEAAAABBAQE/f0BAQAABQcASiMNAN3g5wAbDQQADAf/AOgNAXosEgkMAQgAsA4GAe4SEAUA/P8BAAQBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAP//vNz1LVdvDhUAAAAASUVORK5CYII=';
+ }
+
+ public function getSummary()
+ {
+ $yiiLogo = $this->getYiiLogo();
+ $url = $this->getUrl();
+ $phpUrl = Yii::$app->getUrlManager()->createUrl($this->module->id . '/default/phpinfo');
+ return <<
+
+
+ {$this->data['application']['yii']}
+
+
+' . Html::a('Show phpinfo »', array('phpinfo'), array('class' => 'btn btn-primary')) . "
\n";
+ }
+
+ protected function renderData($caption, $values)
+ {
+ if (empty($values)) {
+ return "Empty.
";
+ }
+ $rows = array();
+ foreach ($values as $name => $value) {
+ $rows[] = '