From 103fa975a1864374d89e7e9b339b81435462dc41 Mon Sep 17 00:00:00 2001 From: Vlasta Ramik Date: Mon, 26 Sep 2016 12:34:46 +0200 Subject: [PATCH 01/38] Resolve warnings while building the effective model --- adapters/saml/undertow/pom.xml | 6 --- distribution/demo-dist/pom.xml | 44 ++++++++----------- .../as7-eap6-adapter/as7-modules/pom.xml | 4 -- 3 files changed, 19 insertions(+), 35 deletions(-) diff --git a/adapters/saml/undertow/pom.xml b/adapters/saml/undertow/pom.xml index 3e3a4d003d8..82ac42c9d2c 100755 --- a/adapters/saml/undertow/pom.xml +++ b/adapters/saml/undertow/pom.xml @@ -31,12 +31,6 @@ - - org.jboss.logging - jboss-logging - ${jboss.logging.version} - provided - org.keycloak keycloak-saml-core diff --git a/distribution/demo-dist/pom.xml b/distribution/demo-dist/pom.xml index 71a5a1a1ed8..f6ac44dcf6c 100755 --- a/distribution/demo-dist/pom.xml +++ b/distribution/demo-dist/pom.xml @@ -151,6 +151,24 @@ + + unpack + compile + + unpack + + + + + org.keycloak + keycloak-wildfly-server-subsystem + ${project.version} + jar + default-config/*.xml + + + + @@ -202,31 +220,7 @@ - - - org.apache.maven.plugins - maven-dependency-plugin - - - unpack - compile - - unpack - - - - - org.keycloak - keycloak-wildfly-server-subsystem - ${project.version} - jar - default-config/*.xml - - - - - - + diff --git a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml index 56e20d21f54..6e45491559b 100755 --- a/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml +++ b/distribution/saml-adapters/as7-eap6-adapter/as7-modules/pom.xml @@ -79,10 +79,6 @@ org.keycloak keycloak-tomcat-adapter-spi - - org.keycloak - keycloak-jboss-adapter-core - org.bouncycastle bcprov-jdk15on From abb0bbd815ddd61c2025f1a0386a133fc90944ed Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Thu, 22 Sep 2016 22:19:45 -0400 Subject: [PATCH 02/38] Angular 2: Added an extension to the Http class to automatically retrieve Keycloak tokens --- .../src/main/webapp/app/app.component.ts | 26 +---- .../src/main/webapp/app/app.module.ts | 14 ++- .../src/main/webapp/app/keycloak.http.ts | 110 ++++++++++++++++++ 3 files changed, 128 insertions(+), 22 deletions(-) create mode 100644 examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts index a6955e71f13..e085032846d 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts @@ -39,27 +39,11 @@ export class AppComponent { } reloadData() { - //angular dont have http interceptor yet + //angular don't have http interceptor yet - this.kc.getToken() - .then(token => { - let headers = new Headers({ - 'Accept': 'application/json', - 'Authorization': 'Bearer ' + token - }); - - let options = new RequestOptions({ headers }); - - this.http.get('/database/products', options) - .map(res => res.json()) - .subscribe(prods => this.products = prods, - error => console.log(error)); - }) - .catch(error => console.log(error)); - } - - private handleError(error: Response) { - console.error(error); - return Observable.throw(error.json().error || 'Server error'); + this.http.get('/database/products') + .map(res => res.json()) + .subscribe(prods => this.products = prods, + error => console.log(error)); } } diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts index 6d2891f9357..b7474892bb3 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts @@ -1,8 +1,9 @@ import {NgModule} from "@angular/core"; import {BrowserModule} from "@angular/platform-browser"; -import {HttpModule} from "@angular/http"; +import {HttpModule, Http, XHRBackend, RequestOptions} from '@angular/http'; import {KeycloakService} from "./keycloak.service"; import {AppComponent} from "./app.component"; +import {KeycloakHttp} from "./keycloak.http"; @NgModule({ imports: [ @@ -14,6 +15,17 @@ import {AppComponent} from "./app.component"; ], providers: [ KeycloakService, + + { + provide: Http, + useFactory: + ( + backend: XHRBackend, + defaultOptions: RequestOptions, + keycloakService: KeycloakService + ) => new KeycloakHttp(backend, defaultOptions, keycloakService), + deps: [XHRBackend, RequestOptions, KeycloakService] + } ], bootstrap: [ AppComponent ] }) diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts new file mode 100644 index 00000000000..c1ef439005f --- /dev/null +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts @@ -0,0 +1,110 @@ +import {Injectable} from "@angular/core"; +import {Http, Request, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response} from "@angular/http"; + +import {KeycloakService} from "./keycloak.service"; +import {Observable} from 'rxjs/Observable'; + +/** + * This provides a wrapper over the ng2 Http class that insures tokens are refreshed on each request. + */ +@Injectable() +export class KeycloakHttp extends Http { + constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private _keycloakService:KeycloakService) { + super(_backend, _defaultOptions); + } + + private setToken(options: RequestOptionsArgs) { + if (options == null || KeycloakService.auth == null || KeycloakService.auth.authz == null || KeycloakService.auth.authz.token == null) { + console.log("Need a token, but no token is available, not setting bearer token."); + return; + } + + options.headers.set('Authorization', 'Bearer ' + KeycloakService.auth.authz.token); + } + + private configureRequest(f:Function, url:string | Request, options:RequestOptionsArgs, body?: any):Observable { + let tokenPromise:Promise = this._keycloakService.getToken(); + let tokenObservable:Observable = Observable.fromPromise(tokenPromise); + let tokenUpdateObservable:Observable = Observable.create((observer) => { + this.setToken(options); + observer.next(); + observer.complete(); + }); + let requestObservable:Observable = Observable.create((observer) => { + let result; + if (body) { + result = f.apply(this, [url, body, options]); + } else { + result = f.apply(this, [url, options]); + } + + result.subscribe((response) => { + observer.next(response); + observer.complete(); + }); + }); + + return >Observable + .merge(tokenObservable, tokenUpdateObservable, requestObservable) + .filter((response) => response instanceof Response); + } + + /** + * Performs any type of http request. First argument is required, and can either be a url or + * a {@link Request} instance. If the first argument is a url, an optional {@link RequestOptions} + * object can be provided as the 2nd argument. The options object will be merged with the values + * of {@link BaseRequestOptions} before performing the request. + */ + request(url: string | Request, options?: RequestOptionsArgs): Observable { + return this.configureRequest(super.request, url, options); + } + + /** + * Performs a request with `get` http method. + */ + get(url: string, options?: RequestOptionsArgs): Observable { + return this.configureRequest(super.get, url, options); + } + + /** + * Performs a request with `post` http method. + */ + post(url: string, body: any, options?: RequestOptionsArgs): Observable { + return this.configureRequest(super.post, url, options, body); + } + + /** + * Performs a request with `put` http method. + */ + put(url: string, body: any, options?: RequestOptionsArgs): Observable { + return this.configureRequest(super.put, url, options, body); + } + + /** + * Performs a request with `delete` http method. + */ + delete(url: string, options?: RequestOptionsArgs): Observable { + return this.configureRequest(super.delete, url, options); + } + + /** + * Performs a request with `patch` http method. + */ + patch(url: string, body: any, options?: RequestOptionsArgs): Observable { + return this.configureRequest(super.patch, url, options, body); + } + + /** + * Performs a request with `head` http method. + */ + head(url: string, options?: RequestOptionsArgs): Observable { + return this.configureRequest(super.head, url, options); + } + + /** + * Performs a request with `options` http method. + */ + options(url: string, options?: RequestOptionsArgs): Observable { + return this.configureRequest(super.options, url, options); + } +} \ No newline at end of file From e2094065b2b06a8b1e703e066ede0dc30681e858 Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Fri, 30 Sep 2016 21:09:50 -0400 Subject: [PATCH 03/38] Switched to observable import that contains other operators --- .../angular2-product-app/src/main/webapp/app/keycloak.http.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts index c1ef439005f..84e09b1d570 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts @@ -2,7 +2,7 @@ import {Injectable} from "@angular/core"; import {Http, Request, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response} from "@angular/http"; import {KeycloakService} from "./keycloak.service"; -import {Observable} from 'rxjs/Observable'; +import {Observable} from 'rxjs/Rx'; /** * This provides a wrapper over the ng2 Http class that insures tokens are refreshed on each request. @@ -107,4 +107,4 @@ export class KeycloakHttp extends Http { options(url: string, options?: RequestOptionsArgs): Observable { return this.configureRequest(super.options, url, options); } -} \ No newline at end of file +} From 5b1b63e364f43796989eb4766fe40adc8f4335f6 Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Mon, 3 Oct 2016 17:57:08 -0400 Subject: [PATCH 04/38] Removed irrelevant comment --- .../angular2-product-app/src/main/webapp/app/app.component.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts index e085032846d..cd8ba04a1f3 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.component.ts @@ -39,8 +39,6 @@ export class AppComponent { } reloadData() { - //angular don't have http interceptor yet - this.http.get('/database/products') .map(res => res.json()) .subscribe(prods => this.products = prods, From cab9740221235c0b52bfea509a25c0155d70ac57 Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Mon, 3 Oct 2016 18:08:36 -0400 Subject: [PATCH 05/38] Fixed token handling for cases where options is null --- .../src/main/webapp/app/app.module.ts | 2 +- .../src/main/webapp/app/keycloak.http.ts | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts index b7474892bb3..fd6075fbd91 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/app.module.ts @@ -29,4 +29,4 @@ import {KeycloakHttp} from "./keycloak.http"; ], bootstrap: [ AppComponent ] }) -export class AppModule {} +export class AppModule {} \ No newline at end of file diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts index 84e09b1d570..40e2f63bb3d 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts @@ -1,5 +1,5 @@ import {Injectable} from "@angular/core"; -import {Http, Request, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response} from "@angular/http"; +import {Http, Request, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response, Headers} from "@angular/http"; import {KeycloakService} from "./keycloak.service"; import {Observable} from 'rxjs/Rx'; @@ -14,6 +14,7 @@ export class KeycloakHttp extends Http { } private setToken(options: RequestOptionsArgs) { + if (options == null || KeycloakService.auth == null || KeycloakService.auth.authz == null || KeycloakService.auth.authz.token == null) { console.log("Need a token, but no token is available, not setting bearer token."); return; @@ -26,6 +27,11 @@ export class KeycloakHttp extends Http { let tokenPromise:Promise = this._keycloakService.getToken(); let tokenObservable:Observable = Observable.fromPromise(tokenPromise); let tokenUpdateObservable:Observable = Observable.create((observer) => { + if (options == null) { + let headers = new Headers(); + options = new RequestOptions({ headers: headers }); + } + this.setToken(options); observer.next(); observer.complete(); From cfbc9cf14b8938553ba08022c533d58354b07608 Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Wed, 5 Oct 2016 13:39:14 +0200 Subject: [PATCH 06/38] KEYCLOAK-3655: Fix for unexpected server error when adding duplicate auth flow --- .../admin/AuthenticationManagementResource.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java index 4e1d7a43999..20bf7387a3a 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AuthenticationManagementResource.java @@ -290,7 +290,7 @@ public class AuthenticationManagementResource { String newName = data.get("newName"); if (realm.getFlowByAlias(newName) != null) { - return Response.status(Response.Status.CONFLICT).build(); + return ErrorResponse.exists("New flow alias name already exists"); } AuthenticationFlowModel flow = realm.getFlowByAlias(flowAlias); @@ -310,7 +310,7 @@ public class AuthenticationManagementResource { data.put("id", copy.getId()); adminEvent.operation(OperationType.CREATE).resourcePath(uriInfo).representation(data).success(); - return Response.status(201).build(); + return Response.status(Response.Status.CREATED).build(); } @@ -344,12 +344,12 @@ public class AuthenticationManagementResource { @POST @NoCache @Consumes(MediaType.APPLICATION_JSON) - public void addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map data) { + public Response addExecutionFlow(@PathParam("flowAlias") String flowAlias, Map data) { auth.requireManage(); AuthenticationFlowModel parentFlow = realm.getFlowByAlias(flowAlias); if (parentFlow == null) { - throw new BadRequestException("Parent flow doesn't exists"); + return ErrorResponse.error("Parent flow doesn't exists", Response.Status.BAD_REQUEST); } String alias = data.get("alias"); String type = data.get("type"); @@ -359,7 +359,7 @@ public class AuthenticationManagementResource { AuthenticationFlowModel newFlow = realm.getFlowByAlias(alias); if (newFlow != null) { - throw new BadRequestException("New flow alias name already exists"); + return ErrorResponse.exists("New flow alias name already exists"); } newFlow = new AuthenticationFlowModel(); newFlow.setAlias(alias); @@ -377,6 +377,8 @@ public class AuthenticationManagementResource { data.put("id", execution.getId()); adminEvent.operation(OperationType.CREATE).resource(ResourceType.AUTH_EXECUTION_FLOW).resourcePath(uriInfo).representation(data).success(); + + return Response.status(Response.Status.CREATED).build(); } private int getNextPriority(AuthenticationFlowModel parentFlow) { From 4bd38bb9c141a4b862ae16c45b72e5e4fa62edab Mon Sep 17 00:00:00 2001 From: zschwarz Date: Wed, 5 Oct 2016 12:04:17 +0200 Subject: [PATCH 07/38] KEYLOAK-3663 OIDC servlet filter --- .../DeploymentArchiveProcessor.java | 24 ++++++++--- .../org/keycloak/testsuite/util/IOUtil.java | 23 ++++++---- .../AbstractDemoFilterServletAdapterTest.java | 42 +++++++++++++++++++ .../adapter/WildflyOIDCFilterAdapterTest.java | 12 ++++++ 4 files changed, 86 insertions(+), 15 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java create mode 100644 testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCFilterAdapterTest.java diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java index d610957c297..fe3bce8269d 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/DeploymentArchiveProcessor.java @@ -49,9 +49,9 @@ import static org.keycloak.testsuite.util.IOUtil.loadJson; import static org.keycloak.testsuite.util.IOUtil.loadXML; import static org.keycloak.testsuite.util.IOUtil.modifyDocElementAttribute; import static org.keycloak.testsuite.util.IOUtil.modifyDocElementValue; -import static org.keycloak.testsuite.util.IOUtil.removeElementFromDoc; +import static org.keycloak.testsuite.util.IOUtil.removeElementsFromDoc; + -; /** * @author tkyjovsk @@ -97,7 +97,7 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor { if (archive.contains(adapterConfigPath)) { log.info("Modifying adapter config " + adapterConfigPath + " in " + archive.getName()); if (adapterConfigPath.equals(SAML_ADAPTER_CONFIG_PATH)) { // SAML adapter config - log.info("Modyfying saml adapter config in " + archive.getName()); + log.info("Modifying saml adapter config in " + archive.getName()); Document doc = loadXML(archive.get("WEB-INF/keycloak-saml.xml").getAsset().openStream()); if (authServerSslRequired) { @@ -148,7 +148,17 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor { } catch (IOException ex) { log.log(Level.FATAL, "Cannot serialize adapter config to JSON.", ex); } + + log.info("Adding OIDCFilter dependencies to " + archive.getName()); + ((WebArchive) archive).addAsLibraries(KeycloakDependenciesResolver.resolveDependencies("org.keycloak:keycloak-servlet-filter-adapter:" + System.getProperty("project.version"))); + } + + } else if (archive.getName().equals("customer-portal-subsystem.war")) { + + log.info("Adding OIDCFilter dependencies to " + archive.getName()); + ((WebArchive) archive).addAsLibraries(KeycloakDependenciesResolver.resolveDependencies("org.keycloak:keycloak-servlet-filter-adapter:" + System.getProperty("project.version"))); + } } @@ -210,9 +220,11 @@ public class DeploymentArchiveProcessor implements ApplicationArchiveProcessor { appendChildInDocument(webXmlDoc, "web-app", filterMapping); //finally we need to remove all keycloak related configuration from web.xml - removeElementFromDoc(webXmlDoc, "web-app", "security-constraint"); - removeElementFromDoc(webXmlDoc, "web-app", "login-config"); - removeElementFromDoc(webXmlDoc, "web-app", "security-role"); + removeElementsFromDoc(webXmlDoc, "web-app", "security-constraint"); + removeElementsFromDoc(webXmlDoc, "web-app", "login-config"); + removeElementsFromDoc(webXmlDoc, "web-app", "security-role"); + + } diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java index 015910bf4ee..345e77eb8f5 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/util/IOUtil.java @@ -129,7 +129,7 @@ public class IOUtil { node.setTextContent(node.getTextContent().replace(regex, replacement)); } - public static void removeElementFromDoc(Document doc, String parentTag, String removeNode) { + public static void removeElementsFromDoc(Document doc, String parentTag, String removeNode) { NodeList nodes = doc.getElementsByTagName(parentTag); if (nodes.getLength() != 1) { log.warn("Not able or ambiguous to find element: " + parentTag); @@ -143,18 +143,23 @@ public class IOUtil { } NodeList removeNodes = parentElement.getElementsByTagName(removeNode); - if (removeNodes.getLength() != 1) { - log.warn("Not able or ambiguous to find element: " + removeNode + " within node " + parentTag); - return; - } - - Element removeElement = (Element) removeNodes.item(0); - if (removeElement == null) { + if (removeNodes == null) { log.warn("Not able to find element: " + removeNode + " within node " + parentTag); return; } - parentElement.removeChild(removeElement); + for (int i = 0; i < removeNodes.getLength();){ + Element removeElement = (Element) removeNodes.item(i); + if (removeElement == null) { + log.warn("Not able to find element: " + removeNode + " within node " + parentTag); + return; + } + + log.info("Removing node " + removeNode); + parentElement.removeChild(removeElement); + + } + } public static String getElementTextContent(Document doc, String path) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java new file mode 100644 index 00000000000..b53fdcc7bcc --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/servlet/AbstractDemoFilterServletAdapterTest.java @@ -0,0 +1,42 @@ +package org.keycloak.testsuite.adapter.servlet; + +import org.junit.Ignore; +import org.junit.Test; +import org.keycloak.testsuite.arquillian.annotation.UseServletFilter; + +/** + * Created by zschwarz on 9/14/16. + */ + +@UseServletFilter(filterName = "oidc-filter", filterClass = "org.keycloak.adapters.servlet.KeycloakOIDCFilter") +public abstract class AbstractDemoFilterServletAdapterTest extends AbstractDemoServletsAdapterTest { + + + @Test + @Override + @Ignore + public void testCustomerPortalWithSubsystemSettings() { + + } + + @Test + @Override + @Ignore + public void testAuthenticated() { + + } + + @Test + @Override + @Ignore + public void testOIDCParamsForwarding() { + + } + + @Test + @Override + @Ignore + public void testClientWithJwksUri() { + + } +} diff --git a/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCFilterAdapterTest.java b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCFilterAdapterTest.java new file mode 100644 index 00000000000..3427f33a317 --- /dev/null +++ b/testsuite/integration-arquillian/tests/other/adapters/jboss/wildfly/src/test/java/org/keycloak/testsuite/adapter/WildflyOIDCFilterAdapterTest.java @@ -0,0 +1,12 @@ +package org.keycloak.testsuite.adapter; + +import org.keycloak.testsuite.adapter.servlet.AbstractDemoFilterServletAdapterTest; +import org.keycloak.testsuite.arquillian.annotation.AppServerContainer; + +/** + * Created by zschwarz on 9/14/16. + */ + +@AppServerContainer("app-server-wildfly") +public class WildflyOIDCFilterAdapterTest extends AbstractDemoFilterServletAdapterTest{ +} From c4f0053dd943e5d2d0c9c870d0dd9d07e734cd9e Mon Sep 17 00:00:00 2001 From: Mohit Suman Date: Wed, 7 Sep 2016 14:54:36 +0530 Subject: [PATCH 08/38] add pagination custom css --- .../theme/keycloak/admin/resources/css/styles.css | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css index 9253c8c839b..daa55f51874 100755 --- a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css +++ b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css @@ -379,4 +379,14 @@ h1 i { .ace_editor { height: 600px; width: 100%; +} + +.kc-pagination { + border: 1px solid #d1d1d1; + font-size: 12px; + height: 23px; + margin-right: 10px; + padding-right: 10px; + text-align: right; + width: 30px; } \ No newline at end of file From 0e33e4035f0c9a73dabbcf8d2174c9aed36a5b9f Mon Sep 17 00:00:00 2001 From: Mohit Suman Date: Wed, 7 Sep 2016 14:50:44 +0530 Subject: [PATCH 09/38] Pagination Directive for clients and roles in admin console --- .../theme/base/admin/resources/js/app.js | 41 +++++++++++++++++++ .../admin/resources/js/controllers/clients.js | 12 +++++- .../admin/resources/js/controllers/realm.js | 12 +++++- .../admin/resources/partials/client-list.html | 13 ++++-- .../admin/resources/partials/role-list.html | 13 ++++-- .../admin/resources/templates/kc-paging.html | 19 +++++++++ 6 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index c3b25ecb275..eeae412e9fa 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -2678,3 +2678,44 @@ module.directive('kcOnReadFile', function ($parse) { } }; }); + +module.controller('PagingCtrl', function ($scope) { + $scope.isLastPage = function() + { + if ($scope.currentPage === $scope.numberOfPages) { + return true; + } + return false; + }; + + $scope.isFirstPage = function() + { + if ($scope.currentPage === 1) { + return true; + } + return false; + }; +}); + +module.directive('kcPaging', function () { + return { + scope: { + currentPage: '=', + numberOfPages: '=' + }, + restrict: 'A', + replace: true, + controller: 'PagingCtrl', + templateUrl: resourceUrl + '/templates/kc-paging.html' + } +}); + +module.filter('startFrom', function () { + return function (input, start) { + if (input) { + start = +start; + return input.slice(start); + } + return []; + }; +}); \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index 4b33b14cb6f..d81882d7970 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -731,9 +731,19 @@ module.controller('ClientImportCtrl', function($scope, $location, $upload, realm }); -module.controller('ClientListCtrl', function($scope, realm, clients, Client, serverInfo, $route, Dialog, Notifications) { +module.controller('ClientListCtrl', function($scope, realm, clients, Client, serverInfo, $route, Dialog, Notifications, filterFilter) { $scope.realm = realm; $scope.clients = clients; + $scope.currentPage = 1; + $scope.pageSize = 20; + $scope.numberOfPages = Math.ceil($scope.clients.length/$scope.pageSize); + + $scope.$watch('search', function (newVal, oldVal) { + $scope.filtered = filterFilter($scope.clients, newVal); + $scope.totalItems = $scope.filtered.length; + $scope.numberOfPages = Math.ceil($scope.totalItems/$scope.pageSize); + $scope.currentPage = 1; + }, true); $scope.removeClient = function(client) { Dialog.confirmDelete(client.clientId, 'client', function() { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 1589ffdff08..a42ef3c5e44 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -1223,9 +1223,19 @@ module.controller('RealmRevocationCtrl', function($scope, Realm, RealmPushRevoca }); -module.controller('RoleListCtrl', function($scope, $route, Dialog, Notifications, realm, roles, RoleById) { +module.controller('RoleListCtrl', function($scope, $route, Dialog, Notifications, realm, roles, RoleById, filterFilter) { $scope.realm = realm; $scope.roles = roles; + $scope.currentPage = 1; + $scope.pageSize = 20; + $scope.numberOfPages = Math.ceil($scope.roles.length/$scope.pageSize); + + $scope.$watch('searchQuery', function (newVal, oldVal) { + $scope.filtered = filterFilter($scope.roles, newVal); + $scope.totalItems = $scope.filtered.length; + $scope.numberOfPages = Math.ceil($scope.totalItems/$scope.pageSize); + $scope.currentPage = 1; + }, true); $scope.removeRole = function (role) { Dialog.confirmDelete(role.name, 'role', function () { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html index 7f796e99601..50c71dcb57c 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html @@ -32,8 +32,15 @@ {{:: 'actions' | translate}} + + + +
+ + + - + {{client.clientId}} @@ -45,8 +52,8 @@ {{:: 'delete' | translate}} - {{:: 'no-results' | translate}} - {{:: 'no-clients-available' | translate}} + {{:: 'no-results' | translate}} + {{:: 'no-clients-available' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html index 8b2bd265664..ca274396224 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html @@ -33,8 +33,15 @@ {{:: 'actions' | translate}} + + + +
+ + + - + {{role.name}} {{role.description}} @@ -42,8 +49,8 @@ {{:: 'delete' | translate}} - {{:: 'no-results' | translate}} - {{:: 'no-realm-roles-available' | translate}} + {{:: 'no-results' | translate}} + {{:: 'no-realm-roles-available' | translate}} diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html new file mode 100644 index 00000000000..d875c72d1cd --- /dev/null +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html @@ -0,0 +1,19 @@ +
+
    +
  • + +
  • +
  • + +
  • +
  • + of  {{numberOfPages}} +
  • +
  • + +
  • +
  • + +
  • +
+
\ No newline at end of file From f5a5fc34583bb20021b1740744c036788835ff1c Mon Sep 17 00:00:00 2001 From: sebastienblanc Date: Tue, 11 Oct 2016 15:11:14 +0200 Subject: [PATCH 10/38] KEYCLOAK-3683: Remove trustore and trustore-password check --- .../extension/KeycloakSubsystemParser.java | 14 --- .../adapter/extension/RealmAddHandler.java | 4 - .../extension/SharedAttributeDefinitons.java | 19 ---- .../extension/RealmDefinitionTestCase.java | 86 ------------------- 4 files changed, 123 deletions(-) delete mode 100755 adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java index 2a6e4d3833f..d4ddc02e3da 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakSubsystemParser.java @@ -85,11 +85,6 @@ class KeycloakSubsystemParser implements XMLStreamConstants, XMLElementReader
  • true if the attributes are valid, false otherwise. - */ - public static boolean validateTruststoreSetIfRequired(ModelNode attributes) { - if (isSet(attributes, DISABLE_TRUST_MANAGER)) { - return true; - } - - if (isSet(attributes, SSL_REQUIRED) && attributes.get(SSL_REQUIRED.getName()).asString().equals("none")) { - return true; - } - - return isSet(attributes, TRUSTSTORE) && isSet(attributes, TRUSTSTORE_PASSWORD); - } - private static boolean isSet(ModelNode attributes, SimpleAttributeDefinition def) { ModelNode attribute = attributes.get(def.getName()); diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java b/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java deleted file mode 100755 index e938d4892e5..00000000000 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/test/java/org/keycloak/subsystem/adapter/extension/RealmDefinitionTestCase.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * 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. - */ -package org.keycloak.subsystem.adapter.extension; - -import org.jboss.dmr.ModelNode; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -/** - * - * @author Stan Silvert ssilvert@redhat.com (C) 2013 Red Hat Inc. - */ -public class RealmDefinitionTestCase { - - private ModelNode model; - - @Before - public void setUp() { - model = new ModelNode(); - model.get("realm").set("demo"); - model.get("resource").set("customer-portal"); - model.get("realm-public-key").set("MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"); - model.get("auth-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/login"); - model.get("code-url").set("http://localhost:8080/auth-server/rest/realms/demo/protocol/openid-connect/access/codes"); - model.get("expose-token").set(true); - ModelNode credential = new ModelNode(); - credential.get("password").set("password"); - model.get("credentials").set(credential); - } - - @Test - public void testIsTruststoreSetIfRequired() throws Exception { - model.get("ssl-required").set("none"); - model.get("disable-trust-manager").set(true); - Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); - - model.get("ssl-required").set("none"); - model.get("disable-trust-manager").set(false); - Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); - - model.get("ssl-required").set("all"); - model.get("disable-trust-manager").set(true); - Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); - - model.get("ssl-required").set("all"); - model.get("disable-trust-manager").set(false); - Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); - - model.get("ssl-required").set("external"); - model.get("disable-trust-manager").set(false); - Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); - - model.get("ssl-required").set("all"); - model.get("disable-trust-manager").set(false); - model.get("truststore").set("foo"); - Assert.assertFalse(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); - - model.get("ssl-required").set("all"); - model.get("disable-trust-manager").set(false); - model.get("truststore").set("foo"); - model.get("truststore-password").set("password"); - Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); - - model.get("ssl-required").set("external"); - model.get("disable-trust-manager").set(false); - model.get("truststore").set("foo"); - model.get("truststore-password").set("password"); - Assert.assertTrue(SharedAttributeDefinitons.validateTruststoreSetIfRequired(model)); - } - -} From 03cf9bad2e0d7babd5c5d82aec0e47f3f832b2ac Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Mon, 10 Oct 2016 09:47:03 +0200 Subject: [PATCH 11/38] KEYCLOAK-2964 - Fix groups not applied for authentication of admin operations --- .../group/GroupLDAPFederationMapper.java | 6 + .../role/RoleLDAPFederationMapper.java | 3 +- .../models/cache/infinispan/UserAdapter.java | 2 +- .../org/keycloak/models/jpa/UserAdapter.java | 3 +- .../mongo/keycloak/adapters/UserAdapter.java | 3 +- .../org/keycloak/models/RoleMapperModel.java | 16 ++ .../models/utils/KeycloakModelUtils.java | 38 ++++ .../storage/adapter/AbstractUserAdapter.java | 3 +- .../AbstractUserAdapterFederatedStorage.java | 3 +- .../testsuite/AbstractKeycloakTest.java | 8 + .../testsuite/admin/group/GroupTest.java | 169 +++++++++++++++--- .../keycloak/testsuite/util/GroupBuilder.java | 85 +++++++++ 12 files changed, 309 insertions(+), 30 deletions(-) create mode 100644 testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/GroupBuilder.java diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java index e27f3efd48f..460ab62ced6 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/group/GroupLDAPFederationMapper.java @@ -48,6 +48,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import org.keycloak.models.RoleModel; /** * @author Marek Posolda @@ -560,6 +561,11 @@ public class GroupLDAPFederationMapper extends AbstractLDAPFederationMapper impl this.ldapUser = ldapUser; } + @Override + public boolean hasRole(RoleModel role) { + return super.hasRole(role) || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true); + } + @Override public Set getGroups() { Set ldapGroupMappings = getLDAPGroupMappingsConverted(); diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java index 2a23001fcb8..9d6e2a1282e 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/membership/role/RoleLDAPFederationMapper.java @@ -348,7 +348,8 @@ public class RoleLDAPFederationMapper extends AbstractLDAPFederationMapper imple @Override public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); - return KeycloakModelUtils.hasRole(roles, role); + return KeycloakModelUtils.hasRole(roles, role) + || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true); } @Override diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java index edbc1863b56..6030a66e8ed 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java @@ -301,7 +301,7 @@ public class UserAdapter implements CachedUserModel { for (RoleModel mapping: mappings) { if (mapping.hasRole(role)) return true; } - return false; + return KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index 7c5fe1ff0cc..5ee5490282e 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -352,7 +352,8 @@ public class UserAdapter implements UserModel, JpaModel { @Override public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); - return KeycloakModelUtils.hasRole(roles, role); + return KeycloakModelUtils.hasRole(roles, role) + || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true); } protected TypedQuery getUserRoleMappingEntityTypedQuery(RoleModel role) { diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java index 5453c0b4eec..972f4409b77 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java @@ -268,7 +268,8 @@ public class UserAdapter extends AbstractMongoAdapter implement @Override public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); - return KeycloakModelUtils.hasRole(roles, role); + return KeycloakModelUtils.hasRole(roles, role) + || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true); } @Override diff --git a/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java b/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java index fd25372d7ee..85f1fd3dfa4 100755 --- a/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RoleMapperModel.java @@ -28,8 +28,24 @@ public interface RoleMapperModel { Set getClientRoleMappings(ClientModel app); + /** + * Returns {@code true} if this object is directly or indirectly assigned the given role, {@code false} otherwise. + *

    + * For example, {@code true} is returned for hasRole(R) if: + *

      + *
    • R is directly assigned to this object
    • + *
    • R is not assigned to this object but this object belongs to a group G which is assigned the role R
    • + *
    • R is not assigned to this object but this object belongs to a group G, and G belongs to group H which is assigned the role R
    • + *
    + * @param role + * @return see description + */ boolean hasRole(RoleModel role); + /** + * Grants the given role to this object. + * @param role + */ void grantRole(RoleModel role); Set getRoleMappings(); diff --git a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index c291c42bb61..e4051e01a24 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -69,6 +69,7 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.stream.StreamSupport; /** * Set of helper methods, which are useful in various model implementations. @@ -328,6 +329,43 @@ public final class KeycloakModelUtils { return false; } + /** + * Checks whether the {@code targetRole} is contained in the given group or its parents + * (if requested) + * @param group Group to check role for + * @param targetRole + * @param checkParentGroup When {@code true}, also parent group is recursively checked for role + * @return true if targetRole is in roles (directly or indirectly via composite role) + */ + public static boolean hasRoleFromGroup(GroupModel group, RoleModel targetRole, boolean checkParentGroup) { + if (group.hasRole(targetRole)) + return true; + + if (checkParentGroup) { + GroupModel parent = group.getParent(); + return parent != null && hasRoleFromGroup(parent, targetRole, true); + } + + return false; + } + + /** + * Checks whether the {@code targetRole} is contained in any of the {@code groups} or their parents + * (if requested) + * @param groups + * @param targetRole + * @param checkParentGroup When {@code true}, also parent group is recursively checked for role + * @return true if targetRole is in roles (directly or indirectly via composite role) + */ + public static boolean hasRoleFromGroup(Iterable groups, RoleModel targetRole, boolean checkParentGroup) { + if (groups == null) { + return false; + } + + return StreamSupport.stream(groups.spliterator(), false) + .anyMatch(group -> hasRoleFromGroup(group, targetRole, checkParentGroup)); + } + /** * * @param groups diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java index 49d22889edf..df6c37ab2ab 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapter.java @@ -172,7 +172,8 @@ public abstract class AbstractUserAdapter implements UserModel { @Override public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); - return KeycloakModelUtils.hasRole(roles, role); + return KeycloakModelUtils.hasRole(roles, role) + || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true); } @Override diff --git a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java index 68e068998b2..ed8759bd081 100644 --- a/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java +++ b/server-spi/src/main/java/org/keycloak/storage/adapter/AbstractUserAdapterFederatedStorage.java @@ -177,7 +177,8 @@ public abstract class AbstractUserAdapterFederatedStorage implements UserModel { @Override public boolean hasRole(RoleModel role) { Set roles = getRoleMappings(); - return KeycloakModelUtils.hasRole(roles, role); + return KeycloakModelUtils.hasRole(roles, role) + || KeycloakModelUtils.hasRoleFromGroup(getGroups(), role, true); } @Override diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java index 5bd733cfdf6..676a40f052f 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/AbstractKeycloakTest.java @@ -278,6 +278,14 @@ public abstract class AbstractKeycloakTest { } } + /** + * Creates a user in the given realm and returns its ID. + * @param realm Realm name + * @param username Username + * @param password Password + * @param requiredActions + * @return ID of the newly created user + */ public String createUser(String realm, String username, String password, String ... requiredActions) { List requiredUserActions = Arrays.asList(requiredActions); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java index f44cc2e6687..7e61f511db0 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/group/GroupTest.java @@ -45,20 +45,30 @@ import javax.ws.rs.core.Response; import java.io.IOException; import java.net.URI; import java.util.Collections; -import java.util.HashMap; import java.util.LinkedList; import java.util.List; -import java.util.Map; +import java.util.UUID; +import javax.ws.rs.ClientErrorException; +import static org.hamcrest.Matchers.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.*; +import org.junit.Rule; +import org.junit.rules.ExpectedException; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.models.AdminRoles; import static org.keycloak.testsuite.Assert.assertNames; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.keycloak.testsuite.auth.page.AuthRealm; +import org.keycloak.testsuite.util.GroupBuilder; /** * @author Marko Strukelj */ public class GroupTest extends AbstractGroupTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Override public void addTestRealms(List testRealms) { RealmRepresentation testRealmRep = loadTestRealm(testRealms); @@ -293,26 +303,24 @@ public class GroupTest extends AbstractGroupTest { @Test public void updateGroup() { RealmResource realm = adminClient.realms().realm("test"); + final String groupName = "group-" + UUID.randomUUID(); - GroupRepresentation group = new GroupRepresentation(); - group.setName("group"); - - Map> attrs = new HashMap<>(); - attrs.put("attr1", Collections.singletonList("attrval1")); - attrs.put("attr2", Collections.singletonList("attrval2")); - group.setAttributes(attrs); + GroupRepresentation group = GroupBuilder.create() + .name(groupName) + .singleAttribute("attr1", "attrval1") + .singleAttribute("attr2", "attrval2") + .build(); createGroup(realm, group); - group = realm.getGroupByPath("/group"); + group = realm.getGroupByPath("/" + groupName); Assert.assertNotNull(group); - assertEquals("group", group.getName()); - assertEquals(2, group.getAttributes().size()); - assertEquals(1, group.getAttributes().get("attr1").size()); - assertEquals("attrval1", group.getAttributes().get("attr1").get(0)); - assertEquals(1, group.getAttributes().get("attr2").size()); - assertEquals("attrval2", group.getAttributes().get("attr2").get(0)); + assertThat(group.getName(), is(groupName)); + assertThat(group.getAttributes().keySet(), containsInAnyOrder("attr1", "attr2")); + assertThat(group.getAttributes(), hasEntry(is("attr1"), contains("attrval1"))); + assertThat(group.getAttributes(), hasEntry(is("attr2"), contains("attrval2"))); - group.setName("group-new"); + final String groupNewName = "group-" + UUID.randomUUID(); + group.setName(groupNewName); group.getAttributes().remove("attr1"); group.getAttributes().get("attr2").add("attrval2-2"); @@ -321,12 +329,12 @@ public class GroupTest extends AbstractGroupTest { realm.groups().group(group.getId()).update(group); assertAdminEvents.assertEvent("test", OperationType.UPDATE, AdminEventPaths.groupPath(group.getId()), group, ResourceType.GROUP); - group = realm.getGroupByPath("/group-new"); + group = realm.getGroupByPath("/" + groupNewName); - assertEquals("group-new", group.getName()); - assertEquals(2, group.getAttributes().size()); - assertEquals(2, group.getAttributes().get("attr2").size()); - assertEquals(1, group.getAttributes().get("attr3").size()); + assertThat(group.getName(), is(groupNewName)); + assertThat(group.getAttributes().keySet(), containsInAnyOrder("attr2", "attr3")); + assertThat(group.getAttributes(), hasEntry(is("attr2"), containsInAnyOrder("attrval2", "attrval2-2"))); + assertThat(group.getAttributes(), hasEntry(is("attr3"), contains("attrval2"))); } @Test @@ -457,4 +465,117 @@ public class GroupTest extends AbstractGroupTest { assertNames(roles.clientLevel(clientId).listAll(), "client-composite"); } + + /** + * Verifies that the user does not have access to Keycloak Admin endpoint when role is not + * assigned to that user. + * @link https://issues.jboss.org/browse/KEYCLOAK-2964 + */ + @Test + public void noAdminEndpointAccessWhenNoRoleAssigned() { + String userName = "user-" + UUID.randomUUID(); + final String realmName = AuthRealm.MASTER; + createUser(realmName, userName, "pwd"); + + Keycloak userClient = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", + realmName, userName, "pwd", Constants.ADMIN_CLI_CLIENT_ID); + + expectedException.expect(ClientErrorException.class); + expectedException.expectMessage(String.valueOf(Response.Status.FORBIDDEN.getStatusCode())); + userClient.realms().findAll(); // Any admin operation will do + } + + /** + * Verifies that the role assigned to a user is correctly handled by Keycloak Admin endpoint. + * @link https://issues.jboss.org/browse/KEYCLOAK-2964 + */ + @Test + public void adminEndpointAccessibleWhenAdminRoleAssignedToUser() { + String userName = "user-" + UUID.randomUUID(); + + final String realmName = AuthRealm.MASTER; + RealmResource realm = adminClient.realms().realm(realmName); + RoleRepresentation adminRole = realm.roles().get(AdminRoles.ADMIN).toRepresentation(); + assertThat(adminRole, notNullValue()); + assertThat(adminRole.getId(), notNullValue()); + + String userId = createUser(realmName, userName, "pwd"); + assertThat(userId, notNullValue()); + + RoleMappingResource mappings = realm.users().get(userId).roles(); + mappings.realmLevel().add(Collections.singletonList(adminRole)); + + Keycloak userClient = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", + realmName, userName, "pwd", Constants.ADMIN_CLI_CLIENT_ID); + + assertThat(userClient.realms().findAll(), // Any admin operation will do + not(empty())); + } + + /** + * Verifies that the role assigned to a user's group is correctly handled by Keycloak Admin endpoint. + * @link https://issues.jboss.org/browse/KEYCLOAK-2964 + */ + @Test + public void adminEndpointAccessibleWhenAdminRoleAssignedToGroup() { + String userName = "user-" + UUID.randomUUID(); + String groupName = "group-" + UUID.randomUUID(); + + final String realmName = AuthRealm.MASTER; + RealmResource realm = adminClient.realms().realm(realmName); + RoleRepresentation adminRole = realm.roles().get(AdminRoles.ADMIN).toRepresentation(); + assertThat(adminRole, notNullValue()); + assertThat(adminRole.getId(), notNullValue()); + + String userId = createUser(realmName, userName, "pwd"); + GroupRepresentation group = GroupBuilder.create().name(groupName).build(); + Response response = realm.groups().add(group); + String groupId = ApiUtil.getCreatedId(response); + response.close(); + + RoleMappingResource mappings = realm.groups().group(groupId).roles(); + mappings.realmLevel().add(Collections.singletonList(adminRole)); + + realm.users().get(userId).joinGroup(groupId); + + Keycloak userClient = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", + realmName, userName, "pwd", Constants.ADMIN_CLI_CLIENT_ID); + + assertThat(userClient.realms().findAll(), // Any admin operation will do + not(empty())); + } + + + /** + * Verifies that the role assigned to a user's group is correctly handled by Keycloak Admin endpoint. + * @link https://issues.jboss.org/browse/KEYCLOAK-2964 + */ + @Test + public void adminEndpointAccessibleWhenAdminRoleAssignedToGroupAfterUserJoinedIt() { + String userName = "user-" + UUID.randomUUID(); + String groupName = "group-" + UUID.randomUUID(); + + final String realmName = AuthRealm.MASTER; + RealmResource realm = adminClient.realms().realm(realmName); + RoleRepresentation adminRole = realm.roles().get(AdminRoles.ADMIN).toRepresentation(); + assertThat(adminRole, notNullValue()); + assertThat(adminRole.getId(), notNullValue()); + + String userId = createUser(realmName, userName, "pwd"); + GroupRepresentation group = GroupBuilder.create().name(groupName).build(); + Response response = realm.groups().add(group); + String groupId = ApiUtil.getCreatedId(response); + response.close(); + + realm.users().get(userId).joinGroup(groupId); + + RoleMappingResource mappings = realm.groups().group(groupId).roles(); + mappings.realmLevel().add(Collections.singletonList(adminRole)); + + Keycloak userClient = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", + realmName, userName, "pwd", Constants.ADMIN_CLI_CLIENT_ID); + + assertThat(userClient.realms().findAll(), // Any admin operation will do + not(empty())); + } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/GroupBuilder.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/GroupBuilder.java new file mode 100644 index 00000000000..0968ead4753 --- /dev/null +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/util/GroupBuilder.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * 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. + */ + +package org.keycloak.testsuite.util; + +import java.util.List; +import java.util.Map; +import org.keycloak.representations.idm.GroupRepresentation; + +/** + * + * @author Hynek Mlnarik + */ +public class GroupBuilder { + + private final GroupRepresentation rep; + + public static GroupBuilder create() { + final GroupRepresentation rep = new GroupRepresentation(); + return new GroupBuilder(rep); + } + + private GroupBuilder(GroupRepresentation rep) { + this.rep = rep; + } + + public GroupRepresentation build() { + return rep; + } + + public GroupBuilder id(String id) { + rep.setId(id); + return this; + } + + public GroupBuilder name(String name) { + rep.setName(name); + return this; + } + + public GroupBuilder path(String path) { + rep.setPath(path); + return this; + } + + public GroupBuilder realmRoles(List realmRoles) { + rep.setRealmRoles(realmRoles); + return this; + } + + public GroupBuilder clientRoles(Map> clientRoles) { + rep.setClientRoles(clientRoles); + return this; + } + + public GroupBuilder attributes(Map> attributes) { + rep.setAttributes(attributes); + return this; + } + + public GroupBuilder singleAttribute(String name, String value) { + rep.singleAttribute(name, value); + return this; + } + + public GroupBuilder subGroups(List subGroups) { + rep.setSubGroups(subGroups); + return this; + } + +} From 635e20a510d9fc19b09f8bbbc0fdbc10e6036e57 Mon Sep 17 00:00:00 2001 From: Jess Sightler Date: Thu, 13 Oct 2016 19:08:45 -0400 Subject: [PATCH 12/38] Fixed a concurrency bug that would cause periodic timeouts --- .../angular2-product-app/src/main/webapp/app/keycloak.http.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts index 40e2f63bb3d..a7af71ae3c2 100644 --- a/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts +++ b/examples/demo-template/angular2-product-app/src/main/webapp/app/keycloak.http.ts @@ -51,7 +51,7 @@ export class KeycloakHttp extends Http { }); return >Observable - .merge(tokenObservable, tokenUpdateObservable, requestObservable) + .merge(tokenObservable, tokenUpdateObservable, requestObservable, 1) // Insure no concurrency in the merged Observables .filter((response) => response instanceof Response); } From 422805b51101d99c826da5f6e9457217b2b76868 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Fri, 14 Oct 2016 10:36:17 +0200 Subject: [PATCH 13/38] Updated labels for java keystore provider config --- .../org/keycloak/keys/JavaKeystoreKeyProviderFactory.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java index 069160758bb..518d3217839 100644 --- a/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java +++ b/services/src/main/java/org/keycloak/keys/JavaKeystoreKeyProviderFactory.java @@ -43,10 +43,10 @@ public class JavaKeystoreKeyProviderFactory extends AbstractRsaKeyProviderFactor public static ProviderConfigProperty KEYSTORE_PASSWORD_PROPERTY = new ProviderConfigProperty(KEYSTORE_PASSWORD_KEY, "Keystore Password", "Password for the keys", STRING_TYPE, null, true); public static String KEY_ALIAS_KEY = "keyAlias"; - public static ProviderConfigProperty KEY_ALIAS_PROPERTY = new ProviderConfigProperty(KEY_ALIAS_KEY, "Private Key Alias", "Alias for the private key", STRING_TYPE, null); + public static ProviderConfigProperty KEY_ALIAS_PROPERTY = new ProviderConfigProperty(KEY_ALIAS_KEY, "Key Alias", "Alias for the private key", STRING_TYPE, null); public static String KEY_PASSWORD_KEY = "keyPassword"; - public static ProviderConfigProperty KEY_PASSWORD_PROPERTY = new ProviderConfigProperty(KEY_PASSWORD_KEY, "Private Key password", "Password for the private key", STRING_TYPE, null, true); + public static ProviderConfigProperty KEY_PASSWORD_PROPERTY = new ProviderConfigProperty(KEY_PASSWORD_KEY, "Key Password", "Password for the private key", STRING_TYPE, null, true); private static final String HELP_TEXT = "Loads keys from a Java keys file"; From e08725e730332adf98626df4ff45663addf6bd1f Mon Sep 17 00:00:00 2001 From: mhajas Date: Fri, 14 Oct 2016 12:47:31 +0200 Subject: [PATCH 14/38] KEYCLOAK-3654 Fix jwks-url in integration-arquillian --- .../testsuite/adapter/AbstractAdapterTest.java | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterTest.java index 63821b3bcc0..43173179086 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/adapter/AbstractAdapterTest.java @@ -70,6 +70,7 @@ public abstract class AbstractAdapterTest extends AbstractAuthTest { modifyClientUrls(tr, "^(/.*)", appServerContextRootPage.toString() + "$1"); modifySamlMasterURLs(tr, "8080", System.getProperty("auth.server.http.port", null)); modifySAMLClientsAttributes(tr, "8080", System.getProperty("app.server.http.port", "8280")); + modifyClientJWKSUrl(tr, "^(/.*)", appServerContextRootPage.toString() + "$1"); } if ("true".equals(System.getProperty("auth.server.ssl.required"))) { tr.setSslRequired("all"); @@ -77,6 +78,16 @@ public abstract class AbstractAdapterTest extends AbstractAuthTest { } } + private void modifyClientJWKSUrl(RealmRepresentation realm, String regex, String replacement) { + if (realm.getClients() != null) { + realm.getClients().stream().filter(client -> "client-jwt".equals(client.getClientAuthenticatorType())).forEach(client -> { + Map attr = client.getAttributes(); + attr.put("jwks.url", attr.get("jwks.url").replaceFirst(regex, replacement)); + client.setAttributes(attr); + }); + } + } + public abstract void addAdapterTestRealms(List testRealms); public boolean isRelative() { From 3d47ab3665f5fcfff98d31877209ca820f61e9df Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Fri, 14 Oct 2016 08:31:27 +0200 Subject: [PATCH 15/38] KEYCLOAK-3698: Add creation of DB changelog to SQL script Creation of database table DatabaseChangeLog was omitted from SQL script which prevented creation of the database from scratch. The fix adds DDL commands to create the table to the output SQL script in case of empty database initialization. Please note that DatabaseChangeLogLock is intentionally omitted. It is created automatically before the update is even executed because a lock is acquired (thus the table is properly created if it does not exist) before check for up-to-dateness of database and potential migration in KeycloakApplication constructor. --- .../LiquibaseJpaUpdaterProvider.java | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java index 957c79a328a..c2c3cfd8c07 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java @@ -33,11 +33,19 @@ import org.keycloak.models.KeycloakSession; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import java.io.Writer; import java.lang.reflect.Method; import java.sql.Connection; import java.util.List; import java.util.Set; import liquibase.LabelExpression; +import liquibase.database.Database; +import liquibase.exception.DatabaseException; +import liquibase.executor.Executor; +import liquibase.executor.ExecutorService; +import liquibase.executor.LoggingExecutor; +import liquibase.statement.core.CreateDatabaseChangeLogTableStatement; +import liquibase.util.StreamUtil; /** * @author Stian Thorgersen @@ -110,7 +118,12 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { } if (exportFile != null) { - liquibase.update((Contexts) null, new FileWriter(exportFile)); + try (Writer exportWriter = new FileWriter(exportFile)) { + if (ranChangeSets.isEmpty()) { + outputChangeLogTableCreationScript(liquibase, exportWriter); + } + liquibase.update((Contexts) null, new LabelExpression(), exportWriter, false); + } } else { liquibase.update((Contexts) null); } @@ -125,6 +138,27 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { } } + private void outputChangeLogTableCreationScript(Liquibase liquibase, final Writer exportWriter) throws DatabaseException { + Database database = liquibase.getDatabase(); + + Executor oldTemplate = ExecutorService.getInstance().getExecutor(database); + LoggingExecutor executor = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), exportWriter, database); + ExecutorService.getInstance().setExecutor(database, executor); + + executor.comment("*********************************************************************"); + executor.comment("* Keycloak database creation script - apply this script to empty DB *"); + executor.comment("*********************************************************************" + StreamUtil.getLineSeparator()); + + executor.execute(new CreateDatabaseChangeLogTableStatement()); + // DatabaseChangeLogLockTable is created before this code is executed and recreated if it does not exist automatically + // in org.keycloak.connections.jpa.updater.liquibase.lock.CustomLockService.init() called indirectly from + // KeycloakApplication constructor (search for waitForLock() call). Hence it is not included in the creation script. + + executor.comment("*********************************************************************" + StreamUtil.getLineSeparator()); + + ExecutorService.getInstance().setExecutor(database, oldTemplate); + } + @Override public Status validate(Connection connection, String defaultSchema) { logger.debug("Validating if database is updated"); From f256e2b10272dbf5b68e0d00b471e3b203b8506d Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Fri, 14 Oct 2016 14:09:44 +0200 Subject: [PATCH 16/38] KEYCLOAK-3588: DB up-to-date check should not modify DB The DB up-to-date check uses Liquibase.listUnrunChangeSets() that in its available variants unconditionally creates a DatabaseChangeLog tables. Until the variant of listUnrunChangeSets() that suppresses this behaviour is made public [1] (currently it is protected), workaround has been implemented that invokes less invasive variant of listUnrunChangeSets() via reflection. [1] https://liquibase.jira.com/browse/CORE-2919 --- .../liquibase/LiquibaseJpaUpdaterProvider.java | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java index 957c79a328a..8ecba4f51cd 100755 --- a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/LiquibaseJpaUpdaterProvider.java @@ -96,7 +96,7 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { protected void updateChangeSet(Liquibase liquibase, String changelog, File exportFile) throws LiquibaseException, IOException { - List changeSets = liquibase.listUnrunChangeSets((Contexts) null, new LabelExpression()); + List changeSets = getChangeSets(liquibase); if (!changeSets.isEmpty()) { List ranChangeSets = liquibase.getDatabase().getRanChangeSetList(); if (ranChangeSets.isEmpty()) { @@ -160,7 +160,8 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { } protected Status validateChangeSet(Liquibase liquibase, String changelog) throws LiquibaseException { - List changeSets = liquibase.listUnrunChangeSets((Contexts) null, new LabelExpression()); + List changeSets = getChangeSets(liquibase); + if (!changeSets.isEmpty()) { if (changeSets.size() == liquibase.getDatabaseChangeLog().getChangeSets().size()) { return Status.EMPTY; @@ -174,6 +175,14 @@ public class LiquibaseJpaUpdaterProvider implements JpaUpdaterProvider { } } + @SuppressWarnings("unchecked") + private List getChangeSets(Liquibase liquibase) { + // TODO: When https://liquibase.jira.com/browse/CORE-2919 is resolved, replace the following two lines with: + // List changeSets = liquibase.listUnrunChangeSets((Contexts) null, new LabelExpression(), false); + Method listUnrunChangeSets = Reflections.findDeclaredMethod(Liquibase.class, "listUnrunChangeSets", Contexts.class, LabelExpression.class, boolean.class); + return Reflections.invokeMethod(true, listUnrunChangeSets, List.class, liquibase, (Contexts) null, new LabelExpression(), false); + } + private Liquibase getLiquibaseForKeycloakUpdate(Connection connection, String defaultSchema) throws LiquibaseException { LiquibaseConnectionProvider liquibaseProvider = session.getProvider(LiquibaseConnectionProvider.class); return liquibaseProvider.getLiquibase(connection, defaultSchema); From 7a6324e02c55e651ea6407c09e43c84140cc45b4 Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Fri, 14 Oct 2016 08:35:46 -0400 Subject: [PATCH 17/38] KEYCLOAK-3507: Pagination for clients and roles in admin console --- .../theme/base/admin/resources/js/app.js | 65 +++++++++++---- .../admin/resources/js/controllers/clients.js | 2 + .../admin/resources/js/controllers/realm.js | 4 +- .../admin/resources/partials/client-list.html | 82 +++++++++---------- .../admin/resources/partials/role-list.html | 72 ++++++++-------- .../admin/resources/templates/kc-paging.html | 40 +++++---- .../keycloak/admin/resources/css/styles.css | 10 --- 7 files changed, 150 insertions(+), 125 deletions(-) diff --git a/themes/src/main/resources/theme/base/admin/resources/js/app.js b/themes/src/main/resources/theme/base/admin/resources/js/app.js index eeae412e9fa..adb0c5ea41e 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/app.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/app.js @@ -2680,20 +2680,38 @@ module.directive('kcOnReadFile', function ($parse) { }); module.controller('PagingCtrl', function ($scope) { - $scope.isLastPage = function() - { - if ($scope.currentPage === $scope.numberOfPages) { - return true; - } - return false; + $scope.currentPageInput = 1; + + $scope.firstPage = function() { + if (!$scope.hasPrevious()) return; + $scope.currentPage = 1; + $scope.currentPageInput = 1; }; - - $scope.isFirstPage = function() - { - if ($scope.currentPage === 1) { - return true; - } - return false; + + $scope.lastPage = function() { + if (!$scope.hasNext()) return; + $scope.currentPage = $scope.numberOfPages; + $scope.currentPageInput = $scope.numberOfPages; + }; + + $scope.previousPage = function() { + if (!$scope.hasPrevious()) return; + $scope.currentPage--; + $scope.currentPageInput = $scope.currentPage; + }; + + $scope.nextPage = function() { + if (!$scope.hasNext()) return; + $scope.currentPage++; + $scope.currentPageInput = $scope.currentPage; + }; + + $scope.hasNext = function() { + return $scope.currentPage < $scope.numberOfPages; + }; + + $scope.hasPrevious = function() { + return $scope.currentPage > 1; }; }); @@ -2701,15 +2719,34 @@ module.directive('kcPaging', function () { return { scope: { currentPage: '=', + currentPageInput: '=', numberOfPages: '=' }, - restrict: 'A', + restrict: 'E', replace: true, controller: 'PagingCtrl', templateUrl: resourceUrl + '/templates/kc-paging.html' } }); +// Tests the page number input from currentPageInput to see +// if it represents a valid page. If so, the current page is changed. +module.directive('kcValidPage', function() { + return { + require: 'ngModel', + link: function(scope, element, attrs, ctrl) { + ctrl.$validators.inRange = function(modelValue, viewValue) { + if (viewValue >= 1 && viewValue <= scope.numberOfPages) { + scope.currentPage = viewValue; + } + + return true; + } + } + } +}); + +// filter used for paged tables module.filter('startFrom', function () { return function (input, start) { if (input) { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js index d81882d7970..7d8de51ead5 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/clients.js @@ -735,6 +735,7 @@ module.controller('ClientListCtrl', function($scope, realm, clients, Client, ser $scope.realm = realm; $scope.clients = clients; $scope.currentPage = 1; + $scope.currentPageInput = 1; $scope.pageSize = 20; $scope.numberOfPages = Math.ceil($scope.clients.length/$scope.pageSize); @@ -743,6 +744,7 @@ module.controller('ClientListCtrl', function($scope, realm, clients, Client, ser $scope.totalItems = $scope.filtered.length; $scope.numberOfPages = Math.ceil($scope.totalItems/$scope.pageSize); $scope.currentPage = 1; + $scope.currentPageInput = 1; }, true); $scope.removeClient = function(client) { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index a42ef3c5e44..d7909ac5095 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -1227,14 +1227,16 @@ module.controller('RoleListCtrl', function($scope, $route, Dialog, Notifications $scope.realm = realm; $scope.roles = roles; $scope.currentPage = 1; + $scope.currentPageInput = 1; $scope.pageSize = 20; $scope.numberOfPages = Math.ceil($scope.roles.length/$scope.pageSize); $scope.$watch('searchQuery', function (newVal, oldVal) { - $scope.filtered = filterFilter($scope.roles, newVal); + $scope.filtered = filterFilter($scope.roles, {name: newVal}); $scope.totalItems = $scope.filtered.length; $scope.numberOfPages = Math.ceil($scope.totalItems/$scope.pageSize); $scope.currentPage = 1; + $scope.currentPageInput = 1; }, true); $scope.removeRole = function (role) { diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html index 50c71dcb57c..51aaa2009b4 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/client-list.html @@ -4,59 +4,53 @@ {{:: 'clients.tooltip' | translate}} - +
    - - + - - - - - - - - - - - + - + + + + + + + - - - - - - - - - - - - + + + + + + + + + + + + + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html b/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html index ca274396224..5c0d47304ef 100755 --- a/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/role-list.html @@ -6,54 +6,48 @@
  • {{:: 'default-roles' | translate}}
  • - +
    - - + - - - - - - - - - - - + - + + + + + + + - - - - - - - - - - - + + + + + + + + + + + + \ No newline at end of file diff --git a/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html b/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html index d875c72d1cd..653e4a58c91 100644 --- a/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html +++ b/themes/src/main/resources/theme/base/admin/resources/templates/kc-paging.html @@ -1,19 +1,25 @@ -
    -
      -
    • - -
    • -
    • - -
    • -
    • - of  {{numberOfPages}} -
    • -
    • - -
    • -
    • - + +
    \ No newline at end of file diff --git a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css index daa55f51874..9253c8c839b 100755 --- a/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css +++ b/themes/src/main/resources/theme/keycloak/admin/resources/css/styles.css @@ -379,14 +379,4 @@ h1 i { .ace_editor { height: 600px; width: 100%; -} - -.kc-pagination { - border: 1px solid #d1d1d1; - font-size: 12px; - height: 23px; - margin-right: 10px; - padding-right: 10px; - text-align: right; - width: 30px; } \ No newline at end of file From 8bdd8f4274fb0d77cc75354209ada7f40f38023a Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Fri, 14 Oct 2016 17:51:35 +0200 Subject: [PATCH 18/38] KEYCLOAK-3639 Drop default value that prevents MSSQL update --- model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml index b1f98879a80..e7c1a3558f7 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml @@ -31,6 +31,7 @@ + From 8a7983bf5aa991e6616234f32f0211afa0a3ee8b Mon Sep 17 00:00:00 2001 From: Stan Silvert Date: Fri, 14 Oct 2016 15:11:50 -0400 Subject: [PATCH 19/38] KEYCLOAK-2295: Flow selection forgotten when clicking to a sibling tab. --- .../admin/resources/js/controllers/realm.js | 27 +++++++++++-------- .../theme/base/admin/resources/js/services.js | 5 ++++ 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 8237749e00c..f766f81ed61 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -1874,14 +1874,12 @@ module.controller('CreateFlowCtrl', function($scope, realm, }; }); -module.controller('CreateExecutionFlowCtrl', function($scope, realm, topFlow, parentFlow, formProviders, +module.controller('CreateExecutionFlowCtrl', function($scope, realm, parentFlow, formProviders, CreateExecutionFlow, Notifications, $location) { $scope.realm = realm; $scope.formProviders = formProviders; - var returnToTopFlow = parentFlow.topLevel ? parentFlow.alias : topFlow; - var defaultFlowType = parentFlow.providerId == 'client-flow' ? 'client-flow' : 'basic-flow'; $scope.flow = { alias: "", @@ -1896,23 +1894,21 @@ module.controller('CreateExecutionFlowCtrl', function($scope, realm, topFlow, pa $scope.save = function() { $scope.flow.provider = $scope.provider.id; CreateExecutionFlow.save({realm: realm.realm, alias: parentFlow.alias}, $scope.flow, function() { - $location.url("/realms/" + realm.realm + "/authentication/flows/" + returnToTopFlow); + $location.url("/realms/" + realm.realm + "/authentication/flows"); Notifications.success("Flow Created."); }) } $scope.cancel = function() { - $location.url("/realms/" + realm.realm + "/authentication/flows/" + returnToTopFlow); + $location.url("/realms/" + realm.realm + "/authentication/flows"); }; }); -module.controller('CreateExecutionCtrl', function($scope, realm, topFlow, parentFlow, formActionProviders, authenticatorProviders, clientAuthenticatorProviders, +module.controller('CreateExecutionCtrl', function($scope, realm, parentFlow, formActionProviders, authenticatorProviders, clientAuthenticatorProviders, CreateExecution, Notifications, $location) { $scope.realm = realm; $scope.parentFlow = parentFlow; - var returnToTopFlow = parentFlow.topLevel ? parentFlow.alias : topFlow; - if (parentFlow.providerId == 'form-flow') { $scope.providers = formActionProviders; } else if (parentFlow.providerId == 'client-flow') { @@ -1931,23 +1927,32 @@ module.controller('CreateExecutionCtrl', function($scope, realm, topFlow, parent provider: $scope.provider.id } CreateExecution.save({realm: realm.realm, alias: parentFlow.alias}, execution, function() { - $location.url("/realms/" + realm.realm + "/authentication/flows/" + returnToTopFlow); + $location.url("/realms/" + realm.realm + "/authentication/flows"); Notifications.success("Execution Created."); }) } $scope.cancel = function() { - $location.url("/realms/" + realm.realm + "/authentication/flows/" + returnToTopFlow); + $location.url("/realms/" + realm.realm + "/authentication/flows"); }; }); -module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flows, selectedFlow, +module.controller('AuthenticationFlowsCtrl', function($scope, $route, realm, flows, selectedFlow, LastFlowSelected, AuthenticationFlows, AuthenticationFlowsCopy, AuthenticationFlowExecutions, AuthenticationExecution, AuthenticationExecutionRaisePriority, AuthenticationExecutionLowerPriority, $modal, Notifications, CopyDialog, $location) { $scope.realm = realm; $scope.flows = flows; + + if (selectedFlow !== null) { + LastFlowSelected.alias = selectedFlow; + } + + if (selectedFlow === null && LastFlowSelected.alias !== null) { + selectedFlow = LastFlowSelected.alias; + } + if (flows.length > 0) { $scope.flow = flows[0]; if (selectedFlow) { diff --git a/themes/src/main/resources/theme/base/admin/resources/js/services.js b/themes/src/main/resources/theme/base/admin/resources/js/services.js index 4e1c4369d87..cb89cb88ea9 100755 --- a/themes/src/main/resources/theme/base/admin/resources/js/services.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/services.js @@ -360,6 +360,11 @@ module.service('UserSearchState', function() { }; }); +// Service tracks the last flow selected in Authentication-->Flows tab +module.service('LastFlowSelected', function() { + this.alias = null; +}); + module.factory('UserFederationInstances', function($resource) { return $resource(authUrl + '/admin/realms/:realm/user-federation/instances/:instance', { realm : '@realm', From 4a19d4cdc19df909d53eaa80ea9ac31a1abd4d2e Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Mon, 17 Oct 2016 09:19:44 +0200 Subject: [PATCH 20/38] KEYCLOAK-3664 Fix for NPE in subsystem when secure-deployment is undefined for a particular deployment --- .../subsystem/as7/KeycloakAdapterConfigService.java | 7 ++++++- .../wf8/extension/KeycloakAdapterConfigService.java | 7 ++++++- .../adapter/extension/KeycloakAdapterConfigService.java | 7 ++++++- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java b/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java index 210473c28f2..b0bd654ba3f 100755 --- a/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java +++ b/adapters/oidc/as7-eap6/as7-subsystem/src/main/java/org/keycloak/subsystem/as7/KeycloakAdapterConfigService.java @@ -165,6 +165,9 @@ public final class KeycloakAdapterConfigService { protected boolean isDeploymentConfigured(DeploymentUnit deploymentUnit) { ModelNode deployment = getSecureDeployment(deploymentUnit); + if (! deployment.isDefined()) { + return false; + } ModelNode resource = deployment.get(SecureDeploymentDefinition.RESOURCE.getName()); return resource.isDefined(); } @@ -202,7 +205,9 @@ public final class KeycloakAdapterConfigService { private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) { String deploymentName = preferredDeploymentName(deploymentUnit); - return this.secureDeployments.get(deploymentName); + return this.secureDeployments.containsKey(deploymentName) + ? this.secureDeployments.get(deploymentName) + : new ModelNode(); } // KEYCLOAK-3273: prefer module name if available diff --git a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java index 8214c3f7580..1ccd2a07d96 100755 --- a/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java +++ b/adapters/oidc/wildfly/wf8-subsystem/src/main/java/org/keycloak/subsystem/wf8/extension/KeycloakAdapterConfigService.java @@ -164,6 +164,9 @@ public final class KeycloakAdapterConfigService { protected boolean isDeploymentConfigured(DeploymentUnit deploymentUnit) { ModelNode deployment = getSecureDeployment(deploymentUnit); + if (! deployment.isDefined()) { + return false; + } ModelNode resource = deployment.get(SecureDeploymentDefinition.RESOURCE.getName()); return resource.isDefined(); } @@ -201,7 +204,9 @@ public final class KeycloakAdapterConfigService { private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) { String deploymentName = preferredDeploymentName(deploymentUnit); - return this.secureDeployments.get(deploymentName); + return this.secureDeployments.containsKey(deploymentName) + ? this.secureDeployments.get(deploymentName) + : new ModelNode(); } // KEYCLOAK-3273: prefer module name if available diff --git a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java index c30e3ed3398..e96a5e51f8e 100755 --- a/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java +++ b/adapters/oidc/wildfly/wildfly-subsystem/src/main/java/org/keycloak/subsystem/adapter/extension/KeycloakAdapterConfigService.java @@ -164,6 +164,9 @@ public final class KeycloakAdapterConfigService { protected boolean isDeploymentConfigured(DeploymentUnit deploymentUnit) { ModelNode deployment = getSecureDeployment(deploymentUnit); + if (! deployment.isDefined()) { + return false; + } ModelNode resource = deployment.get(SecureDeploymentDefinition.RESOURCE.getName()); return resource.isDefined(); } @@ -201,7 +204,9 @@ public final class KeycloakAdapterConfigService { private ModelNode getSecureDeployment(DeploymentUnit deploymentUnit) { String deploymentName = preferredDeploymentName(deploymentUnit); - return this.secureDeployments.get(deploymentName); + return this.secureDeployments.containsKey(deploymentName) + ? this.secureDeployments.get(deploymentName) + : new ModelNode(); } // KEYCLOAK-3273: prefer module name if available From b0448d1b6f1ce6efc3856806300b9caf420eef36 Mon Sep 17 00:00:00 2001 From: Vlasta Ramik Date: Thu, 13 Oct 2016 14:01:29 +0200 Subject: [PATCH 21/38] KEYCLOAK-3589 Add support for manual upgrade of database schema to testsuite --- testsuite/integration-arquillian/pom.xml | 19 ++++ .../jboss/common/migration-strategy.xsl | 57 ++++++++++++ .../servers/auth-server/jboss/pom.xml | 54 +++++++++++ .../arquillian/AuthServerTestEnricher.java | 5 +- .../testsuite/migration/MigrationTest.java | 90 ++++++++++--------- .../integration-arquillian/tests/pom.xml | 13 ++- 6 files changed, 187 insertions(+), 51 deletions(-) create mode 100644 testsuite/integration-arquillian/servers/auth-server/jboss/common/migration-strategy.xsl diff --git a/testsuite/integration-arquillian/pom.xml b/testsuite/integration-arquillian/pom.xml index d63f76be330..49c960495ad 100644 --- a/testsuite/integration-arquillian/pom.xml +++ b/testsuite/integration-arquillian/pom.xml @@ -49,6 +49,10 @@ 2.2.2 1.0.0.Alpha2 + + 2.2.1.Final + 1.9.8.Final + 1.8 1.8 @@ -128,5 +132,20 @@ servers tests + + + + test-project-migration + + ${migration.project.version} + + + + test-product-migration + + ${migration.product.version} + + + diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/common/migration-strategy.xsl b/testsuite/integration-arquillian/servers/auth-server/jboss/common/migration-strategy.xsl new file mode 100644 index 00000000000..5bc33b86aab --- /dev/null +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/common/migration-strategy.xsl @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml index e62c79e4b33..dc6587037d1 100644 --- a/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml +++ b/testsuite/integration-arquillian/servers/auth-server/jboss/pom.xml @@ -571,6 +571,60 @@ + + + migration-manual + + + migration.mode + manual + + + + + + + org.codehaus.mojo + xml-maven-plugin + + + set-manual-migration-strategy + process-resources + + transform + + + + + + ${auth.server.home}/standalone/configuration + + standalone.xml + + ${common.resources}/migration-strategy.xsl + ${auth.server.home}/standalone/configuration + + + migration.strategy + manual + + + initialize.empty + false + + + + + + + + + + + + + + auth-server-apply-patch diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java index 9542eaae294..bdeda57d902 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/arquillian/AuthServerTestEnricher.java @@ -64,7 +64,8 @@ public class AuthServerTestEnricher { private static final String AUTH_SERVER_CLUSTER_PROPERTY = "auth.server.cluster"; public static final boolean AUTH_SERVER_CLUSTER = Boolean.parseBoolean(System.getProperty(AUTH_SERVER_CLUSTER_PROPERTY, "false")); - private static final Boolean AUTO_MIGRATION_ENABLED = "auto".equals(System.getProperty("migration.mode")); + private static final Boolean START_MIGRATION_CONTAINER = "auto".equals(System.getProperty("migration.mode")) || + "manual".equals(System.getProperty("migration.mode")); @Inject @SuiteScoped @@ -130,7 +131,7 @@ public class AuthServerTestEnricher { throw new RuntimeException(String.format("No auth server container matching '%sN' found in arquillian.xml.", authServerBackend)); } - if (AUTO_MIGRATION_ENABLED) { + if (START_MIGRATION_CONTAINER) { // init migratedAuthServerInfo for (ContainerInfo container : suiteContext.getContainers()) { // migrated auth server diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java index 9889d1307e5..d9c041c14da 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/migration/MigrationTest.java @@ -36,10 +36,8 @@ import org.keycloak.models.utils.DefaultAuthenticationFlows; import org.keycloak.representations.idm.AuthenticationExecutionExportRepresentation; import org.keycloak.representations.idm.AuthenticationFlowRepresentation; import org.keycloak.representations.idm.ClientRepresentation; -import org.keycloak.representations.idm.GroupRepresentation; import org.keycloak.representations.idm.RequiredActionProviderRepresentation; import org.keycloak.representations.idm.RoleRepresentation; -import org.keycloak.representations.idm.UserRepresentation; import static org.keycloak.testsuite.Assert.*; import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; @@ -49,6 +47,8 @@ import static org.keycloak.testsuite.auth.page.AuthRealm.MASTER; public class MigrationTest extends AbstractKeycloakTest { private final String MIGRATION = "Migration"; + private RealmResource migrationRealm; + private RealmResource masterRealm; @Override public void addTestRealms(List testRealms) { @@ -57,66 +57,74 @@ public class MigrationTest extends AbstractKeycloakTest { @Before public void beforeMigrationTest() { + migrationRealm = adminClient.realms().realm(MIGRATION); + masterRealm = adminClient.realms().realm(MASTER); + //add migration realm to testRealmReps to make the migration removed after test testRealmReps.add(adminClient.realms().realm(MIGRATION).toRepresentation()); } @Test @Migration(versionFrom = "1.9.8.Final") - public void migration198Test() { - RealmResource migrationRealm = adminClient.realms().realm(MIGRATION); - RealmResource masterRealm = adminClient.realms().realm(MASTER); - - testMigratedMasterData(masterRealm); - testMigratedMigrationData(migrationRealm); - - // 2.0.0 - org.keycloak.migration.migrators.MigrateTo2_0_0 - testAuthorizationServices(masterRealm, migrationRealm); - - // 2.1.0 - org.keycloak.migration.migrators.MigrateTo2_1_0 - testNameOfOTPRequiredAction(masterRealm, migrationRealm); - //there is no migration of RolePolicies (MigrateTo2_1_0#migrateRolePolicies) between 1.9.8 to current version (2.3.0-SNAPSHOT) - - // 2.2.0 - org.keycloak.migration.migrators.MigrateTo2_2_0 - testIdentityProviderAuthenticator(masterRealm, migrationRealm); + public void migration1_9_8Test() { + testMigratedData(); + testMigrationTo2_0_0(); + testMigrationTo2_1_0(); + testMigrationTo2_2_0(); } @Test @Migration(versionFrom = "2.2.1.Final") - public void migration221Test() { - RealmResource migrationRealm = adminClient.realms().realm(MIGRATION); - RealmResource masterRealm = adminClient.realms().realm(MASTER); - - testMigratedMasterData(masterRealm); - testMigratedMigrationData(migrationRealm); - - // so far nothing else + public void migration2_2_1Test() { + testMigratedData(); } - private void testMigratedMasterData(RealmResource master) { - assertNames(master.roles().list(), "offline_access", "uma_authorization", "create-realm", "master-test-realm-role", "admin"); - assertNames(master.clients().findAll(), "admin-cli", "security-admin-console", "broker", "account", + private void testMigratedData() { + //master realm + assertNames(masterRealm.roles().list(), "offline_access", "uma_authorization", "create-realm", "master-test-realm-role", "admin"); + assertNames(masterRealm.clients().findAll(), "admin-cli", "security-admin-console", "broker", "account", "master-realm", "master-test-client", "Migration-realm"); - String id = master.clients().findByClientId("master-test-client").get(0).getId(); - assertNames(master.clients().get(id).roles().list(), "master-test-client-role"); - assertNames(master.users().search("", 0, 5), "admin", "master-test-user"); - assertNames(master.groups().groups(), "master-test-group"); + String id = masterRealm.clients().findByClientId("master-test-client").get(0).getId(); + assertNames(masterRealm.clients().get(id).roles().list(), "master-test-client-role"); + assertNames(masterRealm.users().search("", 0, 5), "admin", "master-test-user"); + assertNames(masterRealm.groups().groups(), "master-test-group"); + + //migrationRealm + assertNames(migrationRealm.roles().list(), "offline_access", "uma_authorization", "migration-test-realm-role"); + assertNames(migrationRealm.clients().findAll(), "account", "admin-cli", "broker", "migration-test-client", "realm-management", "security-admin-console"); + String id2 = migrationRealm.clients().findByClientId("migration-test-client").get(0).getId(); + assertNames(migrationRealm.clients().get(id2).roles().list(), "migration-test-client-role"); + assertNames(migrationRealm.users().search("", 0, 5), "migration-test-user"); + assertNames(migrationRealm.groups().groups(), "migration-test-group"); } - private void testMigratedMigrationData(RealmResource migration) { - assertNames(migration.roles().list(), "offline_access", "uma_authorization", "migration-test-realm-role"); - assertNames(migration.clients().findAll(), "account", "admin-cli", "broker", "migration-test-client", "realm-management", "security-admin-console"); - String id = migration.clients().findByClientId("migration-test-client").get(0).getId(); - assertNames(migration.clients().get(id).roles().list(), "migration-test-client-role"); - assertNames(migration.users().search("", 0, 5), "migration-test-user"); - assertNames(migration.groups().groups(), "migration-test-group"); + /** + * @see org.keycloak.migration.migrators.MigrateTo2_0_0 + */ + private void testMigrationTo2_0_0() { + testAuthorizationServices(masterRealm, migrationRealm); + } + + /** + * @see org.keycloak.migration.migrators.MigrateTo2_1_0 + */ + private void testMigrationTo2_1_0() { + testNameOfOTPRequiredAction(masterRealm, migrationRealm); + } + + /** + * @see org.keycloak.migration.migrators.MigrateTo2_2_0 + */ + private void testMigrationTo2_2_0() { + testIdentityProviderAuthenticator(masterRealm, migrationRealm); + //MigrateTo2_2_0#migrateRolePolicies is not relevant any more } private void testAuthorizationServices(RealmResource... realms) { for (RealmResource realm : realms) { //test setup of authorization services for (String roleName : Constants.AUTHZ_DEFAULT_AUTHORIZATION_ROLES) { - RoleResource role = realm.roles().get(roleName); //throw javax.ws.rs.NotFoundException + RoleResource role = realm.roles().get(roleName); //throws javax.ws.rs.NotFoundException if not found assertFalse("Role's scopeParamRequired should be false.", role.toRepresentation().isScopeParamRequired()); assertFalse("Role shouldn't be composite should be false.", role.toRepresentation().isComposite()); diff --git a/testsuite/integration-arquillian/tests/pom.xml b/testsuite/integration-arquillian/tests/pom.xml index 1683bc9b65b..d30721ca6ef 100755 --- a/testsuite/integration-arquillian/tests/pom.xml +++ b/testsuite/integration-arquillian/tests/pom.xml @@ -405,16 +405,10 @@ - + - migration-auto - - - migration.mode - auto - - + auth-server-migration -Dkeycloak.migration.action=import @@ -438,6 +432,9 @@ migrated.auth.server.version + + migration.mode + From 5732b2c58f6419705650948753f6b9d382cea934 Mon Sep 17 00:00:00 2001 From: mposolda Date: Mon, 17 Oct 2016 12:22:33 +0200 Subject: [PATCH 22/38] KEYCLOAK-3716 Unable to start Keycloak on wildfly --- .../main/java/org/keycloak/models/KeycloakSession.java | 2 ++ .../java/org/keycloak/models/utils/ComponentUtil.java | 6 ++---- .../org/keycloak/services/DefaultKeycloakSession.java | 5 +++++ .../keycloak/services/DefaultKeycloakSessionFactory.java | 9 +++++++++ ...ciesTest.java => ClientRegistrationPoliciesTest.java} | 2 +- 5 files changed, 19 insertions(+), 5 deletions(-) rename testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/{OIDCClientRegistrationPoliciesTest.java => ClientRegistrationPoliciesTest.java} (99%) diff --git a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java index 1a8d9740627..cb1089192d1 100755 --- a/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java +++ b/server-spi/src/main/java/org/keycloak/models/KeycloakSession.java @@ -73,6 +73,8 @@ public interface KeycloakSession { Set getAllProviders(Class clazz); + Class getProviderClass(String providerClassName); + Object getAttribute(String attribute); Object removeAttribute(String attribute); void setAttribute(String name, Object value); diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java b/server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java index a0d915edd70..507bfffb1d0 100644 --- a/server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/ComponentUtil.java @@ -47,10 +47,8 @@ public class ComponentUtil { } public static ComponentFactory getComponentFactory(KeycloakSession session, ComponentModel component) { - Class provider; - try { - provider = (Class) Class.forName(component.getProviderType()); - } catch (ClassNotFoundException e) { + Class provider = session.getProviderClass(component.getProviderType()); + if (provider == null) { throw new RuntimeException("Invalid provider type '" + component.getProviderType() + "'"); } diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index c26dceda0de..7fbd7a3d12b 100644 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -227,6 +227,11 @@ public class DefaultKeycloakSession implements KeycloakSession { return providers; } + @Override + public Class getProviderClass(String providerClassName) { + return factory.getProviderClass(providerClassName); + } + @Override public RealmProvider realms() { if (model == null) { diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java index caf281a0464..d891f2ca86f 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java @@ -339,6 +339,15 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory, Pr return ids; } + Class getProviderClass(String providerClassName) { + for (Class clazz : factoriesMap.keySet()) { + if (clazz.getName().equals(providerClassName)) { + return clazz; + } + } + return null; + } + public void close() { ProviderManagerRegistry.SINGLETON.setDeployer(null); for (Map factories : factoriesMap.values()) { diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationPoliciesTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationPoliciesTest.java similarity index 99% rename from testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationPoliciesTest.java rename to testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationPoliciesTest.java index 12d24ed65d8..0f0b7cc81a9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/OIDCClientRegistrationPoliciesTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/client/ClientRegistrationPoliciesTest.java @@ -63,7 +63,7 @@ import static org.junit.Assert.assertTrue; /** * @author Marek Posolda */ -public class OIDCClientRegistrationPoliciesTest extends AbstractClientRegistrationTest { +public class ClientRegistrationPoliciesTest extends AbstractClientRegistrationTest { private static final String PRIVATE_KEY = "MIICXAIBAAKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQABAoGAfmO8gVhyBxdqlxmIuglbz8bcjQbhXJLR2EoS8ngTXmN1bo2L90M0mUKSdc7qF10LgETBzqL8jYlQIbt+e6TH8fcEpKCjUlyq0Mf/vVbfZSNaVycY13nTzo27iPyWQHK5NLuJzn1xvxxrUeXI6A2WFpGEBLbHjwpx5WQG9A+2scECQQDvdn9NE75HPTVPxBqsEd2z10TKkl9CZxu10Qby3iQQmWLEJ9LNmy3acvKrE3gMiYNWb6xHPKiIqOR1as7L24aTAkEAtyvQOlCvr5kAjVqrEKXalj0Tzewjweuxc0pskvArTI2Oo070h65GpoIKLc9jf+UA69cRtquwP93aZKtW06U8dQJAF2Y44ks/mK5+eyDqik3koCI08qaC8HYq2wVl7G2QkJ6sbAaILtcvD92ToOvyGyeE0flvmDZxMYlvaZnaQ0lcSQJBAKZU6umJi3/xeEbkJqMfeLclD27XGEFoPeNrmdx0q10Azp4NfJAY+Z8KRyQCR2BEG+oNitBOZ+YXF9KCpH3cdmECQHEigJhYg+ykOvr1aiZUMFT72HU0jnmQe2FVekuG+LJUt2Tm7GtMjTFoGpf0JwrVuZN39fOYAlo+nTixgeW7X8Y="; private static final String PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCrVrCuTtArbgaZzL1hvh0xtL5mc7o0NqPVnYXkLvgcwiC3BjLGw1tGEGoJaXDuSaRllobm53JBhjx33UNv+5z/UMG4kytBWxheNVKnL6GgqlNabMaFfPLPCF8kAgKnsi79NMo+n6KnSY8YeUmec/p2vjO2NjsSAVcWEQMVhJ31LwIDAQAB"; From c2684a2b36f199d2936015d5a21ba37c9a94f129 Mon Sep 17 00:00:00 2001 From: Hynek Mlnarik Date: Mon, 17 Oct 2016 12:17:46 +0200 Subject: [PATCH 23/38] KEYCLOAK-3574 Add missing check for changes in RealmPasswordPolicyCtrl --- .../base/admin/resources/js/controllers/realm.js | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js index 568ebdf5911..1915e3c5104 100644 --- a/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js +++ b/themes/src/main/resources/theme/base/admin/resources/js/controllers/realm.js @@ -458,6 +458,7 @@ module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $htt var value; if (policyToken.indexOf('(') == -1) { id = policyToken.trim(); + value = null; } else { id = policyToken.substring(0, policyToken.indexOf('(')); value = policyToken.substring(policyToken.indexOf('(') + 1, policyToken.indexOf(')')).trim(); @@ -492,7 +493,14 @@ module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $htt $scope.realm = realm; $scope.serverInfo = serverInfo; - $scope.changed = false; $scope.policy = parse(realm.passwordPolicy); + + $scope.changed = false; + $scope.policy = parse(realm.passwordPolicy); + var oldCopy = angular.copy($scope.policy); + + $scope.$watch('policy', function() { + $scope.changed = ! angular.equals($scope.policy, oldCopy); + }, true); $scope.addPolicy = function(policy){ policy.value = policy.defaultValue; @@ -500,21 +508,18 @@ module.controller('RealmPasswordPolicyCtrl', function($scope, Realm, realm, $htt $scope.policy = []; } $scope.policy.push(policy); - $scope.changed = true; } $scope.removePolicy = function(index){ $scope.policy.splice(index, 1); - $scope.changed = true; } $scope.save = function() { - $scope.changed = false; $scope.realm.passwordPolicy = toString($scope.policy); console.debug($scope.realm.passwordPolicy); Realm.update($scope.realm, function () { - $location.url("/realms/" + realm.realm + "/authentication/password-policy"); + $route.reload(); Notifications.success("Your changes have been saved to the realm."); }); }; From 160e26b699b0a037a612b1fee587dffd8db47c85 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 17 Oct 2016 13:47:29 +0200 Subject: [PATCH 24/38] KEYCLOAK-3665 Remove theme module and make built-in theme resources read-only --- .../keycloak-services/main/module.xml | 5 --- .../keycloak/keycloak-themes/main/module.xml | 36 ------------------- .../main/module.xml | 1 - distribution/server-dist/assembly.xml | 9 +++++ 4 files changed, 9 insertions(+), 42 deletions(-) delete mode 100755 distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-themes/main/module.xml diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml index 8cf1cde89b9..4f351fd7c09 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-services/main/module.xml @@ -27,11 +27,6 @@ - - - - - diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-themes/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-themes/main/module.xml deleted file mode 100755 index a0f6e1d553d..00000000000 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-themes/main/module.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-extensions/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-extensions/main/module.xml index 3f0470cd61f..d785cb7fe37 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-extensions/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-wildfly-extensions/main/module.xml @@ -29,7 +29,6 @@ - diff --git a/distribution/server-dist/assembly.xml b/distribution/server-dist/assembly.xml index 9e245677337..0e58c833446 100755 --- a/distribution/server-dist/assembly.xml +++ b/distribution/server-dist/assembly.xml @@ -46,6 +46,7 @@ appclient/** copyright.txt README.txt + themes/** @@ -56,6 +57,14 @@ 0755 + + target/${project.build.finalName} + + + themes/** + + 0444 + src/main/welcome-content welcome-content From 95f62c6aeb73aff3953e20e788a68cd6e4e1a8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Geir=20Ole=20Hi=C3=A5sen=20Stevning?= Date: Thu, 6 Oct 2016 12:43:25 +0200 Subject: [PATCH 25/38] KEYCLOAK-3626 - CreatedDate and lastUpdatedDate on user consent --- .../idm/UserConsentRepresentation.java | 20 ++++++++++++++++ .../cache/infinispan/UserCacheSession.java | 2 ++ .../entities/CachedUserConsent.java | 12 ++++++++++ .../keycloak/models/jpa/JpaUserProvider.java | 9 ++++++++ .../jpa/entities/UserConsentEntity.java | 23 ++++++++++++++++++- .../META-INF/jpa-changelog-2.3.0.xml | 6 ++++- .../keycloak/adapters/MongoUserProvider.java | 8 +++++++ .../keycloak/entities/UserConsentEntity.java | 18 +++++++++++++++ .../org/keycloak/models/UserConsentModel.java | 17 ++++++++++++++ .../models/utils/ModelToRepresentation.java | 2 ++ .../models/utils/RepresentationToModel.java | 2 ++ .../resources/admin/UsersResource.java | 2 ++ .../testsuite/model/UserConsentModelTest.java | 7 ++++++ .../messages/admin-messages_en.properties | 2 ++ .../messages/admin-messages_no.properties | 2 ++ .../resources/partials/user-consents.html | 4 ++++ 16 files changed, 134 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/keycloak/representations/idm/UserConsentRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserConsentRepresentation.java index a394b3c5a91..681a03bbab9 100644 --- a/core/src/main/java/org/keycloak/representations/idm/UserConsentRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/UserConsentRepresentation.java @@ -35,6 +35,10 @@ public class UserConsentRepresentation { // Key is clientId, Value is list of granted roles of this client protected Map> grantedClientRoles; + private Long createdDate; + + private Long lastUpdatedDate; + public String getClientId() { return clientId; } @@ -66,4 +70,20 @@ public class UserConsentRepresentation { public void setGrantedClientRoles(Map> grantedClientRoles) { this.grantedClientRoles = grantedClientRoles; } + + public void setCreatedDate(Long createdDate) { + this.createdDate = createdDate; + } + + public Long getCreatedDate() { + return createdDate; + } + + public void setLastUpdatedDate(Long lastUpdatedDate) { + this.lastUpdatedDate = lastUpdatedDate; + } + + public Long getLastUpdatedDate() { + return lastUpdatedDate; + } } diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java index 16987d493d6..cdb79a71ca5 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserCacheSession.java @@ -555,6 +555,8 @@ public class UserCacheSession implements UserCache { } UserConsentModel consentModel = new UserConsentModel(client); + consentModel.setCreatedDate(cachedConsent.getCreatedDate()); + consentModel.setLastUpdatedDate(cachedConsent.getLastUpdatedDate()); for (String roleId : cachedConsent.getRoleIds()) { RoleModel role = session.realms().getRoleById(roleId, realm); diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsent.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsent.java index e57d456bff1..4b24bd12ab3 100644 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsent.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/entities/CachedUserConsent.java @@ -32,6 +32,8 @@ public class CachedUserConsent { private final String clientDbId; private final Set protocolMappers = new HashSet<>(); private final Set roleIds = new HashSet<>(); + private final Long createdDate; + private final Long lastUpdatedDate; public CachedUserConsent(UserConsentModel consentModel) { this.clientDbId = consentModel.getClient().getId(); @@ -39,6 +41,8 @@ public class CachedUserConsent { for (RoleModel role : consentModel.getGrantedRoles()) { this.roleIds.add(role.getId()); } + this.createdDate = consentModel.getCreatedDate(); + this.lastUpdatedDate = consentModel.getLastUpdatedDate(); } public String getClientDbId() { @@ -52,4 +56,12 @@ public class CachedUserConsent { public Set getRoleIds() { return roleIds; } + + public Long getCreatedDate() { + return createdDate; + } + + public Long getLastUpdatedDate() { + return lastUpdatedDate; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 6f85e38d0f6..3aa71f0059a 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -18,6 +18,7 @@ package org.keycloak.models.jpa; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; import org.keycloak.credential.CredentialModel; import org.keycloak.credential.UserCredentialStore; @@ -201,10 +202,14 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore { throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]"); } + long currentTime = Time.currentTimeMillis(); + consentEntity = new UserConsentEntity(); consentEntity.setId(KeycloakModelUtils.generateId()); consentEntity.setUser(em.getReference(UserEntity.class, user.getId())); consentEntity.setClientId(clientId); + consentEntity.setCreatedDate(currentTime); + consentEntity.setLastUpdatedDate(currentTime); em.persist(consentEntity); em.flush(); @@ -277,6 +282,8 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore { throw new ModelException("Client with id " + entity.getClientId() + " is not available"); } UserConsentModel model = new UserConsentModel(client); + model.setCreatedDate(entity.getCreatedDate()); + model.setLastUpdatedDate(entity.getLastUpdatedDate()); Collection grantedRoleEntities = entity.getGrantedRoles(); if (grantedRoleEntities != null) { @@ -346,6 +353,8 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore { em.remove(toRemove); } + consentEntity.setLastUpdatedDate(Time.currentTimeMillis()); + em.flush(); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java index 34c272a5b3d..c2b7b021ef2 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserConsentEntity.java @@ -68,6 +68,12 @@ public class UserConsentEntity { @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "userConsent") Collection grantedProtocolMappers = new ArrayList(); + @Column(name = "CREATED_DATE") + private Long createdDate; + + @Column(name = "LAST_UPDATED_DATE") + private Long lastUpdatedDate; + public String getId() { return id; } @@ -108,6 +114,22 @@ public class UserConsentEntity { this.grantedProtocolMappers = grantedProtocolMappers; } + public Long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Long createdDate) { + this.createdDate = createdDate; + } + + public Long getLastUpdatedDate() { + return lastUpdatedDate; + } + + public void setLastUpdatedDate(Long lastUpdatedDate) { + this.lastUpdatedDate = lastUpdatedDate; + } + @Override public boolean equals(Object o) { if (this == o) return true; @@ -125,5 +147,4 @@ public class UserConsentEntity { public int hashCode() { return id.hashCode(); } - } diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml index bc2ef137366..2f377c3bb7e 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.3.0.xml @@ -47,7 +47,11 @@ + + + + + - \ No newline at end of file diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java index 529aabe8990..af1b6bec6ae 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java @@ -21,6 +21,7 @@ import com.mongodb.BasicDBObject; import com.mongodb.DBObject; import com.mongodb.QueryBuilder; import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.common.util.Time; import org.keycloak.component.ComponentModel; import org.keycloak.connections.mongo.api.MongoStore; import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext; @@ -524,9 +525,13 @@ public class MongoUserProvider implements UserProvider, UserCredentialStore { throw new ModelDuplicateException("Consent already exists for client [" + clientId + "] and user [" + user.getId() + "]"); } + long currentTime = Time.currentTimeMillis(); + MongoUserConsentEntity consentEntity = new MongoUserConsentEntity(); consentEntity.setUserId(user.getId()); consentEntity.setClientId(clientId); + consentEntity.setCreatedDate(currentTime); + consentEntity.setLastUpdatedDate(currentTime); fillEntityFromModel(consent, consentEntity); getMongoStore().insertEntity(consentEntity, invocationContext); } @@ -568,6 +573,8 @@ public class MongoUserProvider implements UserProvider, UserCredentialStore { throw new ModelException("Client with id " + entity.getClientId() + " is not available"); } UserConsentModel model = new UserConsentModel(client); + model.setCreatedDate(entity.getCreatedDate()); + model.setLastUpdatedDate(entity.getLastUpdatedDate()); for (String roleId : entity.getGrantedRoles()) { RoleModel roleModel = realm.getRoleById(roleId); @@ -596,6 +603,7 @@ public class MongoUserProvider implements UserProvider, UserCredentialStore { protMapperIds.add(protMapperModel.getId()); } consentEntity.setGrantedProtocolMappers(protMapperIds); + consentEntity.setLastUpdatedDate(Time.currentTimeMillis()); } @Override diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserConsentEntity.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserConsentEntity.java index c60faee81a0..1ca9e64f525 100644 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserConsentEntity.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/entities/UserConsentEntity.java @@ -29,6 +29,8 @@ public class UserConsentEntity extends AbstractIdentifiableEntity { private String clientId; private List grantedRoles = new ArrayList(); private List grantedProtocolMappers = new ArrayList(); + private Long createdDate; + private Long lastUpdatedDate; public String getUserId() { return userId; @@ -61,4 +63,20 @@ public class UserConsentEntity extends AbstractIdentifiableEntity { public void setGrantedProtocolMappers(List grantedProtocolMappers) { this.grantedProtocolMappers = grantedProtocolMappers; } + + public Long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Long createdDate) { + this.createdDate = createdDate; + } + + public Long getLastUpdatedDate() { + return lastUpdatedDate; + } + + public void setLastUpdatedDate(Long lastUpdatedDate) { + this.lastUpdatedDate = lastUpdatedDate; + } } diff --git a/server-spi/src/main/java/org/keycloak/models/UserConsentModel.java b/server-spi/src/main/java/org/keycloak/models/UserConsentModel.java index d3c295b4ab6..847696d575a 100644 --- a/server-spi/src/main/java/org/keycloak/models/UserConsentModel.java +++ b/server-spi/src/main/java/org/keycloak/models/UserConsentModel.java @@ -28,6 +28,8 @@ public class UserConsentModel { private final ClientModel client; private Set protocolMappers = new HashSet(); private Set roles = new HashSet(); + private Long createdDate; + private Long lastUpdatedDate; public UserConsentModel(ClientModel client) { this.client = client; @@ -67,4 +69,19 @@ public class UserConsentModel { return false; } + public Long getCreatedDate() { + return createdDate; + } + + public void setCreatedDate(Long createdDate) { + this.createdDate = createdDate; + } + + public Long getLastUpdatedDate() { + return lastUpdatedDate; + } + + public void setLastUpdatedDate(Long lastUpdatedDate) { + this.lastUpdatedDate = lastUpdatedDate; + } } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 8703a055cd3..9846d087c01 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -710,6 +710,8 @@ public class ModelToRepresentation { consentRep.setGrantedProtocolMappers(grantedProtocolMappers); consentRep.setGrantedRealmRoles(grantedRealmRoles); consentRep.setGrantedClientRoles(grantedClientRoles); + consentRep.setCreatedDate(model.getCreatedDate()); + consentRep.setLastUpdatedDate(model.getLastUpdatedDate()); return consentRep; } diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index ec516b621c0..c5e55a53df8 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -1571,6 +1571,8 @@ public class RepresentationToModel { } UserConsentModel consentModel = new UserConsentModel(client); + consentModel.setCreatedDate(consentRep.getCreatedDate()); + consentModel.setLastUpdatedDate(consentRep.getLastUpdatedDate()); if (consentRep.getGrantedRealmRoles() != null) { for (String roleName : consentRep.getGrantedRealmRoles()) { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java index 2b06c4ccb5f..5325d8b4cae 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java @@ -538,6 +538,8 @@ public class UsersResource { currentRep.put("grantedProtocolMappers", (rep==null ? Collections.emptyMap() : rep.getGrantedProtocolMappers())); currentRep.put("grantedRealmRoles", (rep==null ? Collections.emptyList() : rep.getGrantedRealmRoles())); currentRep.put("grantedClientRoles", (rep==null ? Collections.emptyMap() : rep.getGrantedClientRoles())); + currentRep.put("createdDate", (rep==null ? null : rep.getCreatedDate())); + currentRep.put("lastUpdatedDate", (rep==null ? null : rep.getLastUpdatedDate())); List> additionalGrants = new LinkedList<>(); if (hasOfflineToken) { diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java index 4335c52fc77..c65fee13ee4 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserConsentModelTest.java @@ -105,12 +105,16 @@ public class UserConsentModelTest extends AbstractModelTest { Assert.assertTrue(isRoleGranted(realm, "realm-role", johnFooConsent)); Assert.assertTrue(isRoleGranted(barClient, "bar-client-role", johnFooConsent)); Assert.assertTrue(isMapperGranted(fooClient, "foo", johnFooConsent)); + Assert.assertNotNull("Created Date should be set", johnFooConsent.getCreatedDate()); + Assert.assertNotNull("Last Updated Date should be set", johnFooConsent.getLastUpdatedDate()); UserConsentModel johnBarConsent = realmManager.getSession().users().getConsentByClient(realm, john, barClient.getId()); Assert.assertEquals(johnBarConsent.getGrantedRoles().size(), 1); Assert.assertEquals(johnBarConsent.getGrantedProtocolMappers().size(), 1); Assert.assertTrue(isRoleGranted(realm, "realm-role", johnBarConsent)); Assert.assertTrue(isMapperGranted(barClient, "bar", johnBarConsent)); + Assert.assertNotNull("Created Date should be set", johnBarConsent.getCreatedDate()); + Assert.assertNotNull("Last Updated Date should be set", johnBarConsent.getLastUpdatedDate()); UserConsentModel maryConsent = realmManager.getSession().users().getConsentByClient(realm, mary, fooClient.getId()); Assert.assertEquals(maryConsent.getGrantedRoles().size(), 1); @@ -118,6 +122,8 @@ public class UserConsentModelTest extends AbstractModelTest { Assert.assertTrue(isRoleGranted(realm, "realm-role", maryConsent)); Assert.assertFalse(isRoleGranted(barClient, "bar-client-role", maryConsent)); Assert.assertTrue(isMapperGranted(fooClient, "foo", maryConsent)); + Assert.assertNotNull("Created Date should be set", maryConsent.getCreatedDate()); + Assert.assertNotNull("Last Updated Date should be set", maryConsent.getLastUpdatedDate()); Assert.assertNull(realmManager.getSession().users().getConsentByClient(realm, mary, barClient.getId())); } @@ -176,6 +182,7 @@ public class UserConsentModelTest extends AbstractModelTest { Assert.assertFalse(isRoleGranted(realm, "realm-role", johnConsent)); Assert.assertTrue(isRoleGranted(realm, "new-realm-role", johnConsent)); Assert.assertFalse(isMapperGranted(fooClient, "foo", johnConsent)); + Assert.assertTrue("Created date should be less than last updated date", johnConsent.getCreatedDate() < johnConsent.getLastUpdatedDate()); } @Test diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties index ef27439102b..ef8ef749c5d 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_en.properties @@ -896,6 +896,8 @@ spi=SPI granted-roles=Granted Roles granted-protocol-mappers=Granted Protocol Mappers additional-grants=Additional Grants +consent-created-date=Created +consent-last-updated-date=Last updated revoke=Revoke new-password=New Password password-confirmation=Password Confirmation diff --git a/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties index 574eb9156aa..9b8a6cb28fd 100644 --- a/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties +++ b/themes/src/main/resources/theme/base/admin/messages/admin-messages_no.properties @@ -863,6 +863,8 @@ spi=SPI granted-roles=Tildelte roller granted-protocol-mappers=Innvilgede protokollmappere additional-grants=Tillegsrettigheter +consent-created-date=Opprettet +consent-last-updated-date=Sist oppdatert revoke=Opphev new-password=Nytt passord password-confirmation=Passord bekreftelse diff --git a/themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html b/themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html index 63bfa7e528e..670d7081f77 100644 --- a/themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html +++ b/themes/src/main/resources/theme/base/admin/resources/partials/user-consents.html @@ -13,6 +13,8 @@ {{:: 'granted-roles' | translate}} {{:: 'granted-protocol-mappers' | translate}} {{:: 'additional-grants' | translate}} + {{:: 'consent-created-date' | translate}} + {{:: 'consent-last-updated-date' | translate}} {{:: 'action' | translate}} @@ -41,6 +43,8 @@ , {{additionalGrant.key}} + {{consent.createdDate | date :'short'}} + {{consent.lastUpdatedDate | date :'short'}} {{:: 'revoke' | translate}} From e61191edb5401a2d6ac6309e62e7abd369a2d3d9 Mon Sep 17 00:00:00 2001 From: Stian Thorgersen Date: Mon, 17 Oct 2016 14:47:55 +0200 Subject: [PATCH 26/38] KEYCLOAK-3570 Reduce the size of themes --- .../main/resources/theme/base/admin/index.ftl | 2 +- .../resources/lib/angular/angular-mocks.js | 2433 - .../resources/lib/angular/angular-scenario.js | 40024 ---------------- .../angular-translate-storage-cookie.js | 71 - .../angular/jstd-scenario-adapter-config.js | 6 - .../lib/angular/jstd-scenario-adapter.js | 185 - .../common/resources/lib/angular/version.txt | 1 - .../font-awesome/fonts/FontAwesome.otf | Bin 93888 -> 0 bytes .../angular-file-upload-html5-shim.js | 25 - .../fileupload/angular-file-upload-shim.js | 215 - .../lib/fileupload/angular-file-upload.js | 156 - .../lib/patternfly/css/patternfly.min.css | 10 - .../resources/lib/patternfly/img/brand-lg.png | Bin 2726 -> 0 bytes .../resources/lib/patternfly/img/brand.png | Bin 1483 -> 0 bytes .../resources/lib/patternfly/img/brand.svg | 87 - .../resources/lib/patternfly/img/favicon.ico | Bin 6518 -> 0 bytes .../resources/lib/patternfly/img/logo.png | Bin 2601 -> 0 bytes .../resources/lib/patternfly/img/logo.svg | 22 - .../resources/lib/patternfly/js/patternfly.js | 212 - .../lib/patternfly/js/patternfly.min.js | 1 - .../resources/lib/select2-3.4.1/LICENSE | 18 - .../resources/lib/select2-3.4.1/README.md | 83 - .../lib/select2-3.4.1/component.json | 8 - .../resources/lib/select2-3.4.1/release.sh | 69 - .../lib/select2-3.4.1/select2.jquery.json | 36 - .../resources/lib/select2-3.4.1/select2.js | 3137 -- .../lib/select2-3.4.1/select2_locale_ar.js | 17 - .../lib/select2-3.4.1/select2_locale_ca.js | 17 - .../lib/select2-3.4.1/select2_locale_cs.js | 49 - .../lib/select2-3.4.1/select2_locale_da.js | 17 - .../lib/select2-3.4.1/select2_locale_de.js | 15 - .../lib/select2-3.4.1/select2_locale_el.js | 17 - .../select2_locale_en.js.template | 17 - .../lib/select2-3.4.1/select2_locale_es.js | 15 - .../lib/select2-3.4.1/select2_locale_et.js | 17 - .../lib/select2-3.4.1/select2_locale_eu.js | 43 - .../lib/select2-3.4.1/select2_locale_fi.js | 28 - .../lib/select2-3.4.1/select2_locale_fr.js | 15 - .../lib/select2-3.4.1/select2_locale_gl.js | 43 - .../lib/select2-3.4.1/select2_locale_he.js | 17 - .../lib/select2-3.4.1/select2_locale_hr.js | 42 - .../lib/select2-3.4.1/select2_locale_hu.js | 15 - .../lib/select2-3.4.1/select2_locale_id.js | 17 - .../lib/select2-3.4.1/select2_locale_is.js | 16 - .../lib/select2-3.4.1/select2_locale_it.js | 15 - .../lib/select2-3.4.1/select2_locale_ja.js | 15 - .../lib/select2-3.4.1/select2_locale_ko.js | 17 - .../lib/select2-3.4.1/select2_locale_lt.js | 29 - .../lib/select2-3.4.1/select2_locale_lv.js | 16 - .../lib/select2-3.4.1/select2_locale_mk.js | 17 - .../lib/select2-3.4.1/select2_locale_nl.js | 15 - .../lib/select2-3.4.1/select2_locale_no.js | 18 - .../lib/select2-3.4.1/select2_locale_pl.js | 37 - .../lib/select2-3.4.1/select2_locale_pt-BR.js | 15 - .../lib/select2-3.4.1/select2_locale_pt-PT.js | 15 - .../lib/select2-3.4.1/select2_locale_ro.js | 15 - .../lib/select2-3.4.1/select2_locale_ru.js | 15 - .../lib/select2-3.4.1/select2_locale_sk.js | 48 - .../lib/select2-3.4.1/select2_locale_sv.js | 17 - .../lib/select2-3.4.1/select2_locale_tr.js | 17 - .../lib/select2-3.4.1/select2_locale_ua.js | 17 - .../lib/select2-3.4.1/select2_locale_vi.js | 18 - .../lib/select2-3.4.1/select2_locale_zh-CN.js | 14 - .../lib/select2-3.4.1/select2_locale_zh-TW.js | 14 - .../resources/lib/select2-3.4.1/select2x2.png | Bin 845 -> 0 bytes .../common/resources/lib/ui-ace/LICENSE | 24 - .../common/resources/lib/ui-ace/README.md | 21 - 67 files changed, 1 insertion(+), 47646 deletions(-) delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-mocks.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-scenario.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-translate-storage-cookie.js delete mode 100755 themes/src/main/resources/theme/keycloak/common/resources/lib/angular/jstd-scenario-adapter-config.js delete mode 100755 themes/src/main/resources/theme/keycloak/common/resources/lib/angular/jstd-scenario-adapter.js delete mode 100755 themes/src/main/resources/theme/keycloak/common/resources/lib/angular/version.txt delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/components/font-awesome/fonts/FontAwesome.otf delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/fileupload/angular-file-upload-html5-shim.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/fileupload/angular-file-upload-shim.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/fileupload/angular-file-upload.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/patternfly/css/patternfly.min.css delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/patternfly/img/brand-lg.png delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/patternfly/img/brand.png delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/patternfly/img/brand.svg delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/patternfly/img/favicon.ico delete mode 100755 themes/src/main/resources/theme/keycloak/common/resources/lib/patternfly/img/logo.png delete mode 100755 themes/src/main/resources/theme/keycloak/common/resources/lib/patternfly/img/logo.svg delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/patternfly/js/patternfly.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/patternfly/js/patternfly.min.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/LICENSE delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/README.md delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/component.json delete mode 100755 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/release.sh delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2.jquery.json delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_ar.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_ca.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_cs.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_da.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_de.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_el.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_en.js.template delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_es.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_et.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_eu.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_fi.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_fr.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_gl.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_he.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_hr.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_hu.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_id.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_is.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_it.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_ja.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_ko.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_lt.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_lv.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_mk.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_nl.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_no.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_pl.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_pt-BR.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_pt-PT.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_ro.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_ru.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_sk.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_sv.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_tr.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_ua.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_vi.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_zh-CN.js delete mode 100755 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2_locale_zh-TW.js delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/select2-3.4.1/select2x2.png delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/ui-ace/LICENSE delete mode 100644 themes/src/main/resources/theme/keycloak/common/resources/lib/ui-ace/README.md diff --git a/themes/src/main/resources/theme/base/admin/index.ftl b/themes/src/main/resources/theme/base/admin/index.ftl index 76286b17b8c..7ab0bc947ee 100755 --- a/themes/src/main/resources/theme/base/admin/index.ftl +++ b/themes/src/main/resources/theme/base/admin/index.ftl @@ -22,7 +22,7 @@ - + diff --git a/themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-mocks.js b/themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-mocks.js deleted file mode 100644 index 8b7c1f29b8f..00000000000 --- a/themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-mocks.js +++ /dev/null @@ -1,2433 +0,0 @@ -/** - * @license AngularJS v1.4.4 - * (c) 2010-2015 Google, Inc. http://angularjs.org - * License: MIT - */ -(function(window, angular, undefined) { - -'use strict'; - -/** - * @ngdoc object - * @name angular.mock - * @description - * - * Namespace from 'angular-mocks.js' which contains testing related code. - */ -angular.mock = {}; - -/** - * ! This is a private undocumented service ! - * - * @name $browser - * - * @description - * This service is a mock implementation of {@link ng.$browser}. It provides fake - * implementation for commonly used browser apis that are hard to test, e.g. setTimeout, xhr, - * cookies, etc... - * - * The api of this service is the same as that of the real {@link ng.$browser $browser}, except - * that there are several helper methods available which can be used in tests. - */ -angular.mock.$BrowserProvider = function() { - this.$get = function() { - return new angular.mock.$Browser(); - }; -}; - -angular.mock.$Browser = function() { - var self = this; - - this.isMock = true; - self.$$url = "http://server/"; - self.$$lastUrl = self.$$url; // used by url polling fn - self.pollFns = []; - - // TODO(vojta): remove this temporary api - self.$$completeOutstandingRequest = angular.noop; - self.$$incOutstandingRequestCount = angular.noop; - - - // register url polling fn - - self.onUrlChange = function(listener) { - self.pollFns.push( - function() { - if (self.$$lastUrl !== self.$$url || self.$$state !== self.$$lastState) { - self.$$lastUrl = self.$$url; - self.$$lastState = self.$$state; - listener(self.$$url, self.$$state); - } - } - ); - - return listener; - }; - - self.$$applicationDestroyed = angular.noop; - self.$$checkUrlChange = angular.noop; - - self.deferredFns = []; - self.deferredNextId = 0; - - self.defer = function(fn, delay) { - delay = delay || 0; - self.deferredFns.push({time:(self.defer.now + delay), fn:fn, id: self.deferredNextId}); - self.deferredFns.sort(function(a, b) { return a.time - b.time;}); - return self.deferredNextId++; - }; - - - /** - * @name $browser#defer.now - * - * @description - * Current milliseconds mock time. - */ - self.defer.now = 0; - - - self.defer.cancel = function(deferId) { - var fnIndex; - - angular.forEach(self.deferredFns, function(fn, index) { - if (fn.id === deferId) fnIndex = index; - }); - - if (fnIndex !== undefined) { - self.deferredFns.splice(fnIndex, 1); - return true; - } - - return false; - }; - - - /** - * @name $browser#defer.flush - * - * @description - * Flushes all pending requests and executes the defer callbacks. - * - * @param {number=} number of milliseconds to flush. See {@link #defer.now} - */ - self.defer.flush = function(delay) { - if (angular.isDefined(delay)) { - self.defer.now += delay; - } else { - if (self.deferredFns.length) { - self.defer.now = self.deferredFns[self.deferredFns.length - 1].time; - } else { - throw new Error('No deferred tasks to be flushed'); - } - } - - while (self.deferredFns.length && self.deferredFns[0].time <= self.defer.now) { - self.deferredFns.shift().fn(); - } - }; - - self.$$baseHref = '/'; - self.baseHref = function() { - return this.$$baseHref; - }; -}; -angular.mock.$Browser.prototype = { - -/** - * @name $browser#poll - * - * @description - * run all fns in pollFns - */ - poll: function poll() { - angular.forEach(this.pollFns, function(pollFn) { - pollFn(); - }); - }, - - url: function(url, replace, state) { - if (angular.isUndefined(state)) { - state = null; - } - if (url) { - this.$$url = url; - // Native pushState serializes & copies the object; simulate it. - this.$$state = angular.copy(state); - return this; - } - - return this.$$url; - }, - - state: function() { - return this.$$state; - }, - - notifyWhenNoOutstandingRequests: function(fn) { - fn(); - } -}; - - -/** - * @ngdoc provider - * @name $exceptionHandlerProvider - * - * @description - * Configures the mock implementation of {@link ng.$exceptionHandler} to rethrow or to log errors - * passed to the `$exceptionHandler`. - */ - -/** - * @ngdoc service - * @name $exceptionHandler - * - * @description - * Mock implementation of {@link ng.$exceptionHandler} that rethrows or logs errors passed - * to it. See {@link ngMock.$exceptionHandlerProvider $exceptionHandlerProvider} for configuration - * information. - * - * - * ```js - * describe('$exceptionHandlerProvider', function() { - * - * it('should capture log messages and exceptions', function() { - * - * module(function($exceptionHandlerProvider) { - * $exceptionHandlerProvider.mode('log'); - * }); - * - * inject(function($log, $exceptionHandler, $timeout) { - * $timeout(function() { $log.log(1); }); - * $timeout(function() { $log.log(2); throw 'banana peel'; }); - * $timeout(function() { $log.log(3); }); - * expect($exceptionHandler.errors).toEqual([]); - * expect($log.assertEmpty()); - * $timeout.flush(); - * expect($exceptionHandler.errors).toEqual(['banana peel']); - * expect($log.log.logs).toEqual([[1], [2], [3]]); - * }); - * }); - * }); - * ``` - */ - -angular.mock.$ExceptionHandlerProvider = function() { - var handler; - - /** - * @ngdoc method - * @name $exceptionHandlerProvider#mode - * - * @description - * Sets the logging mode. - * - * @param {string} mode Mode of operation, defaults to `rethrow`. - * - * - `log`: Sometimes it is desirable to test that an error is thrown, for this case the `log` - * mode stores an array of errors in `$exceptionHandler.errors`, to allow later - * assertion of them. See {@link ngMock.$log#assertEmpty assertEmpty()} and - * {@link ngMock.$log#reset reset()} - * - `rethrow`: If any errors are passed to the handler in tests, it typically means that there - * is a bug in the application or test, so this mock will make these tests fail. - * For any implementations that expect exceptions to be thrown, the `rethrow` mode - * will also maintain a log of thrown errors. - */ - this.mode = function(mode) { - - switch (mode) { - case 'log': - case 'rethrow': - var errors = []; - handler = function(e) { - if (arguments.length == 1) { - errors.push(e); - } else { - errors.push([].slice.call(arguments, 0)); - } - if (mode === "rethrow") { - throw e; - } - }; - handler.errors = errors; - break; - default: - throw new Error("Unknown mode '" + mode + "', only 'log'/'rethrow' modes are allowed!"); - } - }; - - this.$get = function() { - return handler; - }; - - this.mode('rethrow'); -}; - - -/** - * @ngdoc service - * @name $log - * - * @description - * Mock implementation of {@link ng.$log} that gathers all logged messages in arrays - * (one array per logging level). These arrays are exposed as `logs` property of each of the - * level-specific log function, e.g. for level `error` the array is exposed as `$log.error.logs`. - * - */ -angular.mock.$LogProvider = function() { - var debug = true; - - function concat(array1, array2, index) { - return array1.concat(Array.prototype.slice.call(array2, index)); - } - - this.debugEnabled = function(flag) { - if (angular.isDefined(flag)) { - debug = flag; - return this; - } else { - return debug; - } - }; - - this.$get = function() { - var $log = { - log: function() { $log.log.logs.push(concat([], arguments, 0)); }, - warn: function() { $log.warn.logs.push(concat([], arguments, 0)); }, - info: function() { $log.info.logs.push(concat([], arguments, 0)); }, - error: function() { $log.error.logs.push(concat([], arguments, 0)); }, - debug: function() { - if (debug) { - $log.debug.logs.push(concat([], arguments, 0)); - } - } - }; - - /** - * @ngdoc method - * @name $log#reset - * - * @description - * Reset all of the logging arrays to empty. - */ - $log.reset = function() { - /** - * @ngdoc property - * @name $log#log.logs - * - * @description - * Array of messages logged using {@link ng.$log#log `log()`}. - * - * @example - * ```js - * $log.log('Some Log'); - * var first = $log.log.logs.unshift(); - * ``` - */ - $log.log.logs = []; - /** - * @ngdoc property - * @name $log#info.logs - * - * @description - * Array of messages logged using {@link ng.$log#info `info()`}. - * - * @example - * ```js - * $log.info('Some Info'); - * var first = $log.info.logs.unshift(); - * ``` - */ - $log.info.logs = []; - /** - * @ngdoc property - * @name $log#warn.logs - * - * @description - * Array of messages logged using {@link ng.$log#warn `warn()`}. - * - * @example - * ```js - * $log.warn('Some Warning'); - * var first = $log.warn.logs.unshift(); - * ``` - */ - $log.warn.logs = []; - /** - * @ngdoc property - * @name $log#error.logs - * - * @description - * Array of messages logged using {@link ng.$log#error `error()`}. - * - * @example - * ```js - * $log.error('Some Error'); - * var first = $log.error.logs.unshift(); - * ``` - */ - $log.error.logs = []; - /** - * @ngdoc property - * @name $log#debug.logs - * - * @description - * Array of messages logged using {@link ng.$log#debug `debug()`}. - * - * @example - * ```js - * $log.debug('Some Error'); - * var first = $log.debug.logs.unshift(); - * ``` - */ - $log.debug.logs = []; - }; - - /** - * @ngdoc method - * @name $log#assertEmpty - * - * @description - * Assert that all of the logging methods have no logged messages. If any messages are present, - * an exception is thrown. - */ - $log.assertEmpty = function() { - var errors = []; - angular.forEach(['error', 'warn', 'info', 'log', 'debug'], function(logLevel) { - angular.forEach($log[logLevel].logs, function(log) { - angular.forEach(log, function(logItem) { - errors.push('MOCK $log (' + logLevel + '): ' + String(logItem) + '\n' + - (logItem.stack || '')); - }); - }); - }); - if (errors.length) { - errors.unshift("Expected $log to be empty! Either a message was logged unexpectedly, or " + - "an expected log message was not checked and removed:"); - errors.push(''); - throw new Error(errors.join('\n---------\n')); - } - }; - - $log.reset(); - return $log; - }; -}; - - -/** - * @ngdoc service - * @name $interval - * - * @description - * Mock implementation of the $interval service. - * - * Use {@link ngMock.$interval#flush `$interval.flush(millis)`} to - * move forward by `millis` milliseconds and trigger any functions scheduled to run in that - * time. - * - * @param {function()} fn A function that should be called repeatedly. - * @param {number} delay Number of milliseconds between each function call. - * @param {number=} [count=0] Number of times to repeat. If not set, or 0, will repeat - * indefinitely. - * @param {boolean=} [invokeApply=true] If set to `false` skips model dirty checking, otherwise - * will invoke `fn` within the {@link ng.$rootScope.Scope#$apply $apply} block. - * @param {...*=} Pass additional parameters to the executed function. - * @returns {promise} A promise which will be notified on each iteration. - */ -angular.mock.$IntervalProvider = function() { - this.$get = ['$browser', '$rootScope', '$q', '$$q', - function($browser, $rootScope, $q, $$q) { - var repeatFns = [], - nextRepeatId = 0, - now = 0; - - var $interval = function(fn, delay, count, invokeApply) { - var hasParams = arguments.length > 4, - args = hasParams ? Array.prototype.slice.call(arguments, 4) : [], - iteration = 0, - skipApply = (angular.isDefined(invokeApply) && !invokeApply), - deferred = (skipApply ? $$q : $q).defer(), - promise = deferred.promise; - - count = (angular.isDefined(count)) ? count : 0; - promise.then(null, null, (!hasParams) ? fn : function() { - fn.apply(null, args); - }); - - promise.$$intervalId = nextRepeatId; - - function tick() { - deferred.notify(iteration++); - - if (count > 0 && iteration >= count) { - var fnIndex; - deferred.resolve(iteration); - - angular.forEach(repeatFns, function(fn, index) { - if (fn.id === promise.$$intervalId) fnIndex = index; - }); - - if (fnIndex !== undefined) { - repeatFns.splice(fnIndex, 1); - } - } - - if (skipApply) { - $browser.defer.flush(); - } else { - $rootScope.$apply(); - } - } - - repeatFns.push({ - nextTime:(now + delay), - delay: delay, - fn: tick, - id: nextRepeatId, - deferred: deferred - }); - repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;}); - - nextRepeatId++; - return promise; - }; - /** - * @ngdoc method - * @name $interval#cancel - * - * @description - * Cancels a task associated with the `promise`. - * - * @param {promise} promise A promise from calling the `$interval` function. - * @returns {boolean} Returns `true` if the task was successfully cancelled. - */ - $interval.cancel = function(promise) { - if (!promise) return false; - var fnIndex; - - angular.forEach(repeatFns, function(fn, index) { - if (fn.id === promise.$$intervalId) fnIndex = index; - }); - - if (fnIndex !== undefined) { - repeatFns[fnIndex].deferred.reject('canceled'); - repeatFns.splice(fnIndex, 1); - return true; - } - - return false; - }; - - /** - * @ngdoc method - * @name $interval#flush - * @description - * - * Runs interval tasks scheduled to be run in the next `millis` milliseconds. - * - * @param {number=} millis maximum timeout amount to flush up until. - * - * @return {number} The amount of time moved forward. - */ - $interval.flush = function(millis) { - now += millis; - while (repeatFns.length && repeatFns[0].nextTime <= now) { - var task = repeatFns[0]; - task.fn(); - task.nextTime += task.delay; - repeatFns.sort(function(a, b) { return a.nextTime - b.nextTime;}); - } - return millis; - }; - - return $interval; - }]; -}; - - -/* jshint -W101 */ -/* The R_ISO8061_STR regex is never going to fit into the 100 char limit! - * This directive should go inside the anonymous function but a bug in JSHint means that it would - * not be enacted early enough to prevent the warning. - */ -var R_ISO8061_STR = /^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?:\:?(\d\d)(?:\:?(\d\d)(?:\.(\d{3}))?)?)?(Z|([+-])(\d\d):?(\d\d)))?$/; - -function jsonStringToDate(string) { - var match; - if (match = string.match(R_ISO8061_STR)) { - var date = new Date(0), - tzHour = 0, - tzMin = 0; - if (match[9]) { - tzHour = toInt(match[9] + match[10]); - tzMin = toInt(match[9] + match[11]); - } - date.setUTCFullYear(toInt(match[1]), toInt(match[2]) - 1, toInt(match[3])); - date.setUTCHours(toInt(match[4] || 0) - tzHour, - toInt(match[5] || 0) - tzMin, - toInt(match[6] || 0), - toInt(match[7] || 0)); - return date; - } - return string; -} - -function toInt(str) { - return parseInt(str, 10); -} - -function padNumber(num, digits, trim) { - var neg = ''; - if (num < 0) { - neg = '-'; - num = -num; - } - num = '' + num; - while (num.length < digits) num = '0' + num; - if (trim) { - num = num.substr(num.length - digits); - } - return neg + num; -} - - -/** - * @ngdoc type - * @name angular.mock.TzDate - * @description - * - * *NOTE*: this is not an injectable instance, just a globally available mock class of `Date`. - * - * Mock of the Date type which has its timezone specified via constructor arg. - * - * The main purpose is to create Date-like instances with timezone fixed to the specified timezone - * offset, so that we can test code that depends on local timezone settings without dependency on - * the time zone settings of the machine where the code is running. - * - * @param {number} offset Offset of the *desired* timezone in hours (fractions will be honored) - * @param {(number|string)} timestamp Timestamp representing the desired time in *UTC* - * - * @example - * !!!! WARNING !!!!! - * This is not a complete Date object so only methods that were implemented can be called safely. - * To make matters worse, TzDate instances inherit stuff from Date via a prototype. - * - * We do our best to intercept calls to "unimplemented" methods, but since the list of methods is - * incomplete we might be missing some non-standard methods. This can result in errors like: - * "Date.prototype.foo called on incompatible Object". - * - * ```js - * var newYearInBratislava = new TzDate(-1, '2009-12-31T23:00:00Z'); - * newYearInBratislava.getTimezoneOffset() => -60; - * newYearInBratislava.getFullYear() => 2010; - * newYearInBratislava.getMonth() => 0; - * newYearInBratislava.getDate() => 1; - * newYearInBratislava.getHours() => 0; - * newYearInBratislava.getMinutes() => 0; - * newYearInBratislava.getSeconds() => 0; - * ``` - * - */ -angular.mock.TzDate = function(offset, timestamp) { - var self = new Date(0); - if (angular.isString(timestamp)) { - var tsStr = timestamp; - - self.origDate = jsonStringToDate(timestamp); - - timestamp = self.origDate.getTime(); - if (isNaN(timestamp)) { - throw { - name: "Illegal Argument", - message: "Arg '" + tsStr + "' passed into TzDate constructor is not a valid date string" - }; - } - } else { - self.origDate = new Date(timestamp); - } - - var localOffset = new Date(timestamp).getTimezoneOffset(); - self.offsetDiff = localOffset * 60 * 1000 - offset * 1000 * 60 * 60; - self.date = new Date(timestamp + self.offsetDiff); - - self.getTime = function() { - return self.date.getTime() - self.offsetDiff; - }; - - self.toLocaleDateString = function() { - return self.date.toLocaleDateString(); - }; - - self.getFullYear = function() { - return self.date.getFullYear(); - }; - - self.getMonth = function() { - return self.date.getMonth(); - }; - - self.getDate = function() { - return self.date.getDate(); - }; - - self.getHours = function() { - return self.date.getHours(); - }; - - self.getMinutes = function() { - return self.date.getMinutes(); - }; - - self.getSeconds = function() { - return self.date.getSeconds(); - }; - - self.getMilliseconds = function() { - return self.date.getMilliseconds(); - }; - - self.getTimezoneOffset = function() { - return offset * 60; - }; - - self.getUTCFullYear = function() { - return self.origDate.getUTCFullYear(); - }; - - self.getUTCMonth = function() { - return self.origDate.getUTCMonth(); - }; - - self.getUTCDate = function() { - return self.origDate.getUTCDate(); - }; - - self.getUTCHours = function() { - return self.origDate.getUTCHours(); - }; - - self.getUTCMinutes = function() { - return self.origDate.getUTCMinutes(); - }; - - self.getUTCSeconds = function() { - return self.origDate.getUTCSeconds(); - }; - - self.getUTCMilliseconds = function() { - return self.origDate.getUTCMilliseconds(); - }; - - self.getDay = function() { - return self.date.getDay(); - }; - - // provide this method only on browsers that already have it - if (self.toISOString) { - self.toISOString = function() { - return padNumber(self.origDate.getUTCFullYear(), 4) + '-' + - padNumber(self.origDate.getUTCMonth() + 1, 2) + '-' + - padNumber(self.origDate.getUTCDate(), 2) + 'T' + - padNumber(self.origDate.getUTCHours(), 2) + ':' + - padNumber(self.origDate.getUTCMinutes(), 2) + ':' + - padNumber(self.origDate.getUTCSeconds(), 2) + '.' + - padNumber(self.origDate.getUTCMilliseconds(), 3) + 'Z'; - }; - } - - //hide all methods not implemented in this mock that the Date prototype exposes - var unimplementedMethods = ['getUTCDay', - 'getYear', 'setDate', 'setFullYear', 'setHours', 'setMilliseconds', - 'setMinutes', 'setMonth', 'setSeconds', 'setTime', 'setUTCDate', 'setUTCFullYear', - 'setUTCHours', 'setUTCMilliseconds', 'setUTCMinutes', 'setUTCMonth', 'setUTCSeconds', - 'setYear', 'toDateString', 'toGMTString', 'toJSON', 'toLocaleFormat', 'toLocaleString', - 'toLocaleTimeString', 'toSource', 'toString', 'toTimeString', 'toUTCString', 'valueOf']; - - angular.forEach(unimplementedMethods, function(methodName) { - self[methodName] = function() { - throw new Error("Method '" + methodName + "' is not implemented in the TzDate mock"); - }; - }); - - return self; -}; - -//make "tzDateInstance instanceof Date" return true -angular.mock.TzDate.prototype = Date.prototype; -/* jshint +W101 */ - -angular.mock.animate = angular.module('ngAnimateMock', ['ng']) - - .config(['$provide', function($provide) { - - $provide.factory('$$forceReflow', function() { - function reflowFn() { - reflowFn.totalReflows++; - } - reflowFn.totalReflows = 0; - return reflowFn; - }); - - $provide.decorator('$animate', ['$delegate', '$timeout', '$browser', '$$rAF', '$$forceReflow', - function($delegate, $timeout, $browser, $$rAF, $$forceReflow) { - - var animate = { - queue: [], - cancel: $delegate.cancel, - get reflows() { - return $$forceReflow.totalReflows; - }, - enabled: $delegate.enabled, - triggerCallbackEvents: function() { - $$rAF.flush(); - }, - triggerCallbackPromise: function() { - $timeout.flush(0); - }, - triggerCallbacks: function() { - this.triggerCallbackEvents(); - this.triggerCallbackPromise(); - } - }; - - angular.forEach( - ['animate','enter','leave','move','addClass','removeClass','setClass'], function(method) { - animate[method] = function() { - animate.queue.push({ - event: method, - element: arguments[0], - options: arguments[arguments.length - 1], - args: arguments - }); - return $delegate[method].apply($delegate, arguments); - }; - }); - - return animate; - }]); - - }]); - - -/** - * @ngdoc function - * @name angular.mock.dump - * @description - * - * *NOTE*: this is not an injectable instance, just a globally available function. - * - * Method for serializing common angular objects (scope, elements, etc..) into strings, useful for - * debugging. - * - * This method is also available on window, where it can be used to display objects on debug - * console. - * - * @param {*} object - any object to turn into string. - * @return {string} a serialized string of the argument - */ -angular.mock.dump = function(object) { - return serialize(object); - - function serialize(object) { - var out; - - if (angular.isElement(object)) { - object = angular.element(object); - out = angular.element('
    '); - angular.forEach(object, function(element) { - out.append(angular.element(element).clone()); - }); - out = out.html(); - } else if (angular.isArray(object)) { - out = []; - angular.forEach(object, function(o) { - out.push(serialize(o)); - }); - out = '[ ' + out.join(', ') + ' ]'; - } else if (angular.isObject(object)) { - if (angular.isFunction(object.$eval) && angular.isFunction(object.$apply)) { - out = serializeScope(object); - } else if (object instanceof Error) { - out = object.stack || ('' + object.name + ': ' + object.message); - } else { - // TODO(i): this prevents methods being logged, - // we should have a better way to serialize objects - out = angular.toJson(object, true); - } - } else { - out = String(object); - } - - return out; - } - - function serializeScope(scope, offset) { - offset = offset || ' '; - var log = [offset + 'Scope(' + scope.$id + '): {']; - for (var key in scope) { - if (Object.prototype.hasOwnProperty.call(scope, key) && !key.match(/^(\$|this)/)) { - log.push(' ' + key + ': ' + angular.toJson(scope[key])); - } - } - var child = scope.$$childHead; - while (child) { - log.push(serializeScope(child, offset + ' ')); - child = child.$$nextSibling; - } - log.push('}'); - return log.join('\n' + offset); - } -}; - -/** - * @ngdoc service - * @name $httpBackend - * @description - * Fake HTTP backend implementation suitable for unit testing applications that use the - * {@link ng.$http $http service}. - * - * *Note*: For fake HTTP backend implementation suitable for end-to-end testing or backend-less - * development please see {@link ngMockE2E.$httpBackend e2e $httpBackend mock}. - * - * During unit testing, we want our unit tests to run quickly and have no external dependencies so - * we don’t want to send [XHR](https://developer.mozilla.org/en/xmlhttprequest) or - * [JSONP](http://en.wikipedia.org/wiki/JSONP) requests to a real server. All we really need is - * to verify whether a certain request has been sent or not, or alternatively just let the - * application make requests, respond with pre-trained responses and assert that the end result is - * what we expect it to be. - * - * This mock implementation can be used to respond with static or dynamic responses via the - * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). - * - * When an Angular application needs some data from a server, it calls the $http service, which - * sends the request to a real server using $httpBackend service. With dependency injection, it is - * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify - * the requests and respond with some testing data without sending a request to a real server. - * - * There are two ways to specify what test data should be returned as http responses by the mock - * backend when the code under test makes http requests: - * - * - `$httpBackend.expect` - specifies a request expectation - * - `$httpBackend.when` - specifies a backend definition - * - * - * # Request Expectations vs Backend Definitions - * - * Request expectations provide a way to make assertions about requests made by the application and - * to define responses for those requests. The test will fail if the expected requests are not made - * or they are made in the wrong order. - * - * Backend definitions allow you to define a fake backend for your application which doesn't assert - * if a particular request was made or not, it just returns a trained response if a request is made. - * The test will pass whether or not the request gets made during testing. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    Request expectationsBackend definitions
    Syntax.expect(...).respond(...).when(...).respond(...)
    Typical usagestrict unit testsloose (black-box) unit testing
    Fulfills multiple requestsNOYES
    Order of requests mattersYESNO
    Request requiredYESNO
    Response requiredoptional (see below)YES
    - * - * In cases where both backend definitions and request expectations are specified during unit - * testing, the request expectations are evaluated first. - * - * If a request expectation has no response specified, the algorithm will search your backend - * definitions for an appropriate response. - * - * If a request didn't match any expectation or if the expectation doesn't have the response - * defined, the backend definitions are evaluated in sequential order to see if any of them match - * the request. The response from the first matched definition is returned. - * - * - * # Flushing HTTP requests - * - * The $httpBackend used in production always responds to requests asynchronously. If we preserved - * this behavior in unit testing, we'd have to create async unit tests, which are hard to write, - * to follow and to maintain. But neither can the testing mock respond synchronously; that would - * change the execution of the code under test. For this reason, the mock $httpBackend has a - * `flush()` method, which allows the test to explicitly flush pending requests. This preserves - * the async api of the backend, while allowing the test to execute synchronously. - * - * - * # Unit testing with mock $httpBackend - * The following code shows how to setup and use the mock backend when unit testing a controller. - * First we create the controller under test: - * - ```js - // The module code - angular - .module('MyApp', []) - .controller('MyController', MyController); - - // The controller code - function MyController($scope, $http) { - var authToken; - - $http.get('/auth.py').success(function(data, status, headers) { - authToken = headers('A-Token'); - $scope.user = data; - }); - - $scope.saveMessage = function(message) { - var headers = { 'Authorization': authToken }; - $scope.status = 'Saving...'; - - $http.post('/add-msg.py', message, { headers: headers } ).success(function(response) { - $scope.status = ''; - }).error(function() { - $scope.status = 'ERROR!'; - }); - }; - } - ``` - * - * Now we setup the mock backend and create the test specs: - * - ```js - // testing controller - describe('MyController', function() { - var $httpBackend, $rootScope, createController, authRequestHandler; - - // Set up the module - beforeEach(module('MyApp')); - - beforeEach(inject(function($injector) { - // Set up the mock http service responses - $httpBackend = $injector.get('$httpBackend'); - // backend definition common for all tests - authRequestHandler = $httpBackend.when('GET', '/auth.py') - .respond({userId: 'userX'}, {'A-Token': 'xxx'}); - - // Get hold of a scope (i.e. the root scope) - $rootScope = $injector.get('$rootScope'); - // The $controller service is used to create instances of controllers - var $controller = $injector.get('$controller'); - - createController = function() { - return $controller('MyController', {'$scope' : $rootScope }); - }; - })); - - - afterEach(function() { - $httpBackend.verifyNoOutstandingExpectation(); - $httpBackend.verifyNoOutstandingRequest(); - }); - - - it('should fetch authentication token', function() { - $httpBackend.expectGET('/auth.py'); - var controller = createController(); - $httpBackend.flush(); - }); - - - it('should fail authentication', function() { - - // Notice how you can change the response even after it was set - authRequestHandler.respond(401, ''); - - $httpBackend.expectGET('/auth.py'); - var controller = createController(); - $httpBackend.flush(); - expect($rootScope.status).toBe('Failed...'); - }); - - - it('should send msg to server', function() { - var controller = createController(); - $httpBackend.flush(); - - // now you don’t care about the authentication, but - // the controller will still send the request and - // $httpBackend will respond without you having to - // specify the expectation and response for this request - - $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); - $rootScope.saveMessage('message content'); - expect($rootScope.status).toBe('Saving...'); - $httpBackend.flush(); - expect($rootScope.status).toBe(''); - }); - - - it('should send auth header', function() { - var controller = createController(); - $httpBackend.flush(); - - $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { - // check if the header was sent, if it wasn't the expectation won't - // match the request and the test will fail - return headers['Authorization'] == 'xxx'; - }).respond(201, ''); - - $rootScope.saveMessage('whatever'); - $httpBackend.flush(); - }); - }); - ``` - */ -angular.mock.$HttpBackendProvider = function() { - this.$get = ['$rootScope', '$timeout', createHttpBackendMock]; -}; - -/** - * General factory function for $httpBackend mock. - * Returns instance for unit testing (when no arguments specified): - * - passing through is disabled - * - auto flushing is disabled - * - * Returns instance for e2e testing (when `$delegate` and `$browser` specified): - * - passing through (delegating request to real backend) is enabled - * - auto flushing is enabled - * - * @param {Object=} $delegate Real $httpBackend instance (allow passing through if specified) - * @param {Object=} $browser Auto-flushing enabled if specified - * @return {Object} Instance of $httpBackend mock - */ -function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) { - var definitions = [], - expectations = [], - responses = [], - responsesPush = angular.bind(responses, responses.push), - copy = angular.copy; - - function createResponse(status, data, headers, statusText) { - if (angular.isFunction(status)) return status; - - return function() { - return angular.isNumber(status) - ? [status, data, headers, statusText] - : [200, status, data, headers]; - }; - } - - // TODO(vojta): change params to: method, url, data, headers, callback - function $httpBackend(method, url, data, callback, headers, timeout, withCredentials) { - var xhr = new MockXhr(), - expectation = expectations[0], - wasExpected = false; - - function prettyPrint(data) { - return (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp) - ? data - : angular.toJson(data); - } - - function wrapResponse(wrapped) { - if (!$browser && timeout) { - timeout.then ? timeout.then(handleTimeout) : $timeout(handleTimeout, timeout); - } - - return handleResponse; - - function handleResponse() { - var response = wrapped.response(method, url, data, headers); - xhr.$$respHeaders = response[2]; - callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(), - copy(response[3] || '')); - } - - function handleTimeout() { - for (var i = 0, ii = responses.length; i < ii; i++) { - if (responses[i] === handleResponse) { - responses.splice(i, 1); - callback(-1, undefined, ''); - break; - } - } - } - } - - if (expectation && expectation.match(method, url)) { - if (!expectation.matchData(data)) { - throw new Error('Expected ' + expectation + ' with different data\n' + - 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data); - } - - if (!expectation.matchHeaders(headers)) { - throw new Error('Expected ' + expectation + ' with different headers\n' + - 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + - prettyPrint(headers)); - } - - expectations.shift(); - - if (expectation.response) { - responses.push(wrapResponse(expectation)); - return; - } - wasExpected = true; - } - - var i = -1, definition; - while ((definition = definitions[++i])) { - if (definition.match(method, url, data, headers || {})) { - if (definition.response) { - // if $browser specified, we do auto flush all requests - ($browser ? $browser.defer : responsesPush)(wrapResponse(definition)); - } else if (definition.passThrough) { - $delegate(method, url, data, callback, headers, timeout, withCredentials); - } else throw new Error('No response defined !'); - return; - } - } - throw wasExpected ? - new Error('No response defined !') : - new Error('Unexpected request: ' + method + ' ' + url + '\n' + - (expectation ? 'Expected ' + expectation : 'No more request expected')); - } - - /** - * @ngdoc method - * @name $httpBackend#when - * @description - * Creates a new backend definition. - * - * @param {string} method HTTP method. - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives - * data string and returns true if the data is as expected. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current definition. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - * - * - respond – - * `{function([status,] data[, headers, statusText]) - * | function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can - * return an array containing response status (number), response data (string), response - * headers (Object), and the text for the status (string). The respond method returns the - * `requestHandler` object for possible overrides. - */ - $httpBackend.when = function(method, url, data, headers) { - var definition = new MockHttpExpectation(method, url, data, headers), - chain = { - respond: function(status, data, headers, statusText) { - definition.passThrough = undefined; - definition.response = createResponse(status, data, headers, statusText); - return chain; - } - }; - - if ($browser) { - chain.passThrough = function() { - definition.response = undefined; - definition.passThrough = true; - return chain; - }; - } - - definitions.push(definition); - return chain; - }; - - /** - * @ngdoc method - * @name $httpBackend#whenGET - * @description - * Creates a new backend definition for GET requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenHEAD - * @description - * Creates a new backend definition for HEAD requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenDELETE - * @description - * Creates a new backend definition for DELETE requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenPOST - * @description - * Creates a new backend definition for POST requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives - * data string and returns true if the data is as expected. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenPUT - * @description - * Creates a new backend definition for PUT requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp|function(string))=} data HTTP request body or function that receives - * data string and returns true if the data is as expected. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#whenJSONP - * @description - * Creates a new backend definition for JSONP requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - createShortMethods('when'); - - - /** - * @ngdoc method - * @name $httpBackend#expect - * @description - * Creates a new request expectation. - * - * @param {string} method HTTP method. - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that - * receives data string and returns true if the data is as expected, or Object if request body - * is in JSON format. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current expectation. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - * - * - respond – - * `{function([status,] data[, headers, statusText]) - * | function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can - * return an array containing response status (number), response data (string), response - * headers (Object), and the text for the status (string). The respond method returns the - * `requestHandler` object for possible overrides. - */ - $httpBackend.expect = function(method, url, data, headers) { - var expectation = new MockHttpExpectation(method, url, data, headers), - chain = { - respond: function(status, data, headers, statusText) { - expectation.response = createResponse(status, data, headers, statusText); - return chain; - } - }; - - expectations.push(expectation); - return chain; - }; - - - /** - * @ngdoc method - * @name $httpBackend#expectGET - * @description - * Creates a new request expectation for GET requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. See #expect for more info. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectHEAD - * @description - * Creates a new request expectation for HEAD requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectDELETE - * @description - * Creates a new request expectation for DELETE requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectPOST - * @description - * Creates a new request expectation for POST requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that - * receives data string and returns true if the data is as expected, or Object if request body - * is in JSON format. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectPUT - * @description - * Creates a new request expectation for PUT requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that - * receives data string and returns true if the data is as expected, or Object if request body - * is in JSON format. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectPATCH - * @description - * Creates a new request expectation for PATCH requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp|function(string)|Object)=} data HTTP request body or function that - * receives data string and returns true if the data is as expected, or Object if request body - * is in JSON format. - * @param {Object=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - - /** - * @ngdoc method - * @name $httpBackend#expectJSONP - * @description - * Creates a new request expectation for JSONP requests. For more info see `expect()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives an url - * and returns true if the url matches the current definition. - * @returns {requestHandler} Returns an object with `respond` method that controls how a matched - * request is handled. You can save this object for later use and invoke `respond` again in - * order to change how a matched request is handled. - */ - createShortMethods('expect'); - - - /** - * @ngdoc method - * @name $httpBackend#flush - * @description - * Flushes all pending requests using the trained responses. - * - * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, - * all pending requests will be flushed. If there are no pending requests when the flush method - * is called an exception is thrown (as this typically a sign of programming error). - */ - $httpBackend.flush = function(count, digest) { - if (digest !== false) $rootScope.$digest(); - if (!responses.length) throw new Error('No pending request to flush !'); - - if (angular.isDefined(count) && count !== null) { - while (count--) { - if (!responses.length) throw new Error('No more pending request to flush !'); - responses.shift()(); - } - } else { - while (responses.length) { - responses.shift()(); - } - } - $httpBackend.verifyNoOutstandingExpectation(digest); - }; - - - /** - * @ngdoc method - * @name $httpBackend#verifyNoOutstandingExpectation - * @description - * Verifies that all of the requests defined via the `expect` api were made. If any of the - * requests were not made, verifyNoOutstandingExpectation throws an exception. - * - * Typically, you would call this method following each test case that asserts requests using an - * "afterEach" clause. - * - * ```js - * afterEach($httpBackend.verifyNoOutstandingExpectation); - * ``` - */ - $httpBackend.verifyNoOutstandingExpectation = function(digest) { - if (digest !== false) $rootScope.$digest(); - if (expectations.length) { - throw new Error('Unsatisfied requests: ' + expectations.join(', ')); - } - }; - - - /** - * @ngdoc method - * @name $httpBackend#verifyNoOutstandingRequest - * @description - * Verifies that there are no outstanding requests that need to be flushed. - * - * Typically, you would call this method following each test case that asserts requests using an - * "afterEach" clause. - * - * ```js - * afterEach($httpBackend.verifyNoOutstandingRequest); - * ``` - */ - $httpBackend.verifyNoOutstandingRequest = function() { - if (responses.length) { - throw new Error('Unflushed requests: ' + responses.length); - } - }; - - - /** - * @ngdoc method - * @name $httpBackend#resetExpectations - * @description - * Resets all request expectations, but preserves all backend definitions. Typically, you would - * call resetExpectations during a multiple-phase test when you want to reuse the same instance of - * $httpBackend mock. - */ - $httpBackend.resetExpectations = function() { - expectations.length = 0; - responses.length = 0; - }; - - return $httpBackend; - - - function createShortMethods(prefix) { - angular.forEach(['GET', 'DELETE', 'JSONP', 'HEAD'], function(method) { - $httpBackend[prefix + method] = function(url, headers) { - return $httpBackend[prefix](method, url, undefined, headers); - }; - }); - - angular.forEach(['PUT', 'POST', 'PATCH'], function(method) { - $httpBackend[prefix + method] = function(url, data, headers) { - return $httpBackend[prefix](method, url, data, headers); - }; - }); - } -} - -function MockHttpExpectation(method, url, data, headers) { - - this.data = data; - this.headers = headers; - - this.match = function(m, u, d, h) { - if (method != m) return false; - if (!this.matchUrl(u)) return false; - if (angular.isDefined(d) && !this.matchData(d)) return false; - if (angular.isDefined(h) && !this.matchHeaders(h)) return false; - return true; - }; - - this.matchUrl = function(u) { - if (!url) return true; - if (angular.isFunction(url.test)) return url.test(u); - if (angular.isFunction(url)) return url(u); - return url == u; - }; - - this.matchHeaders = function(h) { - if (angular.isUndefined(headers)) return true; - if (angular.isFunction(headers)) return headers(h); - return angular.equals(headers, h); - }; - - this.matchData = function(d) { - if (angular.isUndefined(data)) return true; - if (data && angular.isFunction(data.test)) return data.test(d); - if (data && angular.isFunction(data)) return data(d); - if (data && !angular.isString(data)) { - return angular.equals(angular.fromJson(angular.toJson(data)), angular.fromJson(d)); - } - return data == d; - }; - - this.toString = function() { - return method + ' ' + url; - }; -} - -function createMockXhr() { - return new MockXhr(); -} - -function MockXhr() { - - // hack for testing $http, $httpBackend - MockXhr.$$lastInstance = this; - - this.open = function(method, url, async) { - this.$$method = method; - this.$$url = url; - this.$$async = async; - this.$$reqHeaders = {}; - this.$$respHeaders = {}; - }; - - this.send = function(data) { - this.$$data = data; - }; - - this.setRequestHeader = function(key, value) { - this.$$reqHeaders[key] = value; - }; - - this.getResponseHeader = function(name) { - // the lookup must be case insensitive, - // that's why we try two quick lookups first and full scan last - var header = this.$$respHeaders[name]; - if (header) return header; - - name = angular.lowercase(name); - header = this.$$respHeaders[name]; - if (header) return header; - - header = undefined; - angular.forEach(this.$$respHeaders, function(headerVal, headerName) { - if (!header && angular.lowercase(headerName) == name) header = headerVal; - }); - return header; - }; - - this.getAllResponseHeaders = function() { - var lines = []; - - angular.forEach(this.$$respHeaders, function(value, key) { - lines.push(key + ': ' + value); - }); - return lines.join('\n'); - }; - - this.abort = angular.noop; -} - - -/** - * @ngdoc service - * @name $timeout - * @description - * - * This service is just a simple decorator for {@link ng.$timeout $timeout} service - * that adds a "flush" and "verifyNoPendingTasks" methods. - */ - -angular.mock.$TimeoutDecorator = ['$delegate', '$browser', function($delegate, $browser) { - - /** - * @ngdoc method - * @name $timeout#flush - * @description - * - * Flushes the queue of pending tasks. - * - * @param {number=} delay maximum timeout amount to flush up until - */ - $delegate.flush = function(delay) { - $browser.defer.flush(delay); - }; - - /** - * @ngdoc method - * @name $timeout#verifyNoPendingTasks - * @description - * - * Verifies that there are no pending tasks that need to be flushed. - */ - $delegate.verifyNoPendingTasks = function() { - if ($browser.deferredFns.length) { - throw new Error('Deferred tasks to flush (' + $browser.deferredFns.length + '): ' + - formatPendingTasksAsString($browser.deferredFns)); - } - }; - - function formatPendingTasksAsString(tasks) { - var result = []; - angular.forEach(tasks, function(task) { - result.push('{id: ' + task.id + ', ' + 'time: ' + task.time + '}'); - }); - - return result.join(', '); - } - - return $delegate; -}]; - -angular.mock.$RAFDecorator = ['$delegate', function($delegate) { - var queue = []; - var rafFn = function(fn) { - var index = queue.length; - queue.push(fn); - return function() { - queue.splice(index, 1); - }; - }; - - rafFn.supported = $delegate.supported; - - rafFn.flush = function() { - if (queue.length === 0) { - throw new Error('No rAF callbacks present'); - } - - var length = queue.length; - for (var i = 0; i < length; i++) { - queue[i](); - } - - queue = queue.slice(i); - }; - - return rafFn; -}]; - -/** - * - */ -angular.mock.$RootElementProvider = function() { - this.$get = function() { - return angular.element('
    '); - }; -}; - -/** - * @ngdoc service - * @name $controller - * @description - * A decorator for {@link ng.$controller} with additional `bindings` parameter, useful when testing - * controllers of directives that use {@link $compile#-bindtocontroller- `bindToController`}. - * - * - * ## Example - * - * ```js - * - * // Directive definition ... - * - * myMod.directive('myDirective', { - * controller: 'MyDirectiveController', - * bindToController: { - * name: '@' - * } - * }); - * - * - * // Controller definition ... - * - * myMod.controller('MyDirectiveController', ['log', function($log) { - * $log.info(this.name); - * })]; - * - * - * // In a test ... - * - * describe('myDirectiveController', function() { - * it('should write the bound name to the log', inject(function($controller, $log) { - * var ctrl = $controller('MyDirective', { /* no locals */ }, { name: 'Clark Kent' }); - * expect(ctrl.name).toEqual('Clark Kent'); - * expect($log.info.logs).toEqual(['Clark Kent']); - * }); - * }); - * - * ``` - * - * @param {Function|string} constructor If called with a function then it's considered to be the - * controller constructor function. Otherwise it's considered to be a string which is used - * to retrieve the controller constructor using the following steps: - * - * * check if a controller with given name is registered via `$controllerProvider` - * * check if evaluating the string on the current scope returns a constructor - * * if $controllerProvider#allowGlobals, check `window[constructor]` on the global - * `window` object (not recommended) - * - * The string can use the `controller as property` syntax, where the controller instance is published - * as the specified property on the `scope`; the `scope` must be injected into `locals` param for this - * to work correctly. - * - * @param {Object} locals Injection locals for Controller. - * @param {Object=} bindings Properties to add to the controller before invoking the constructor. This is used - * to simulate the `bindToController` feature and simplify certain kinds of tests. - * @return {Object} Instance of given controller. - */ -angular.mock.$ControllerDecorator = ['$delegate', function($delegate) { - return function(expression, locals, later, ident) { - if (later && typeof later === 'object') { - var create = $delegate(expression, locals, true, ident); - angular.extend(create.instance, later); - return create(); - } - return $delegate(expression, locals, later, ident); - }; -}]; - - -/** - * @ngdoc module - * @name ngMock - * @packageName angular-mocks - * @description - * - * # ngMock - * - * The `ngMock` module provides support to inject and mock Angular services into unit tests. - * In addition, ngMock also extends various core ng services such that they can be - * inspected and controlled in a synchronous manner within test code. - * - * - *
    - * - */ -angular.module('ngMock', ['ng']).provider({ - $browser: angular.mock.$BrowserProvider, - $exceptionHandler: angular.mock.$ExceptionHandlerProvider, - $log: angular.mock.$LogProvider, - $interval: angular.mock.$IntervalProvider, - $httpBackend: angular.mock.$HttpBackendProvider, - $rootElement: angular.mock.$RootElementProvider -}).config(['$provide', function($provide) { - $provide.decorator('$timeout', angular.mock.$TimeoutDecorator); - $provide.decorator('$$rAF', angular.mock.$RAFDecorator); - $provide.decorator('$rootScope', angular.mock.$RootScopeDecorator); - $provide.decorator('$controller', angular.mock.$ControllerDecorator); -}]); - -/** - * @ngdoc module - * @name ngMockE2E - * @module ngMockE2E - * @packageName angular-mocks - * @description - * - * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. - * Currently there is only one mock present in this module - - * the {@link ngMockE2E.$httpBackend e2e $httpBackend} mock. - */ -angular.module('ngMockE2E', ['ng']).config(['$provide', function($provide) { - $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); -}]); - -/** - * @ngdoc service - * @name $httpBackend - * @module ngMockE2E - * @description - * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of - * applications that use the {@link ng.$http $http service}. - * - * *Note*: For fake http backend implementation suitable for unit testing please see - * {@link ngMock.$httpBackend unit-testing $httpBackend mock}. - * - * This implementation can be used to respond with static or dynamic responses via the `when` api - * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the - * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch - * templates from a webserver). - * - * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application - * is being developed with the real backend api replaced with a mock, it is often desirable for - * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch - * templates or static files from the webserver). To configure the backend with this behavior - * use the `passThrough` request handler of `when` instead of `respond`. - * - * Additionally, we don't want to manually have to flush mocked out requests like we do during unit - * testing. For this reason the e2e $httpBackend flushes mocked out requests - * automatically, closely simulating the behavior of the XMLHttpRequest object. - * - * To setup the application to run with this http backend, you have to create a module that depends - * on the `ngMockE2E` and your application modules and defines the fake backend: - * - * ```js - * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); - * myAppDev.run(function($httpBackend) { - * phones = [{name: 'phone1'}, {name: 'phone2'}]; - * - * // returns the current list of phones - * $httpBackend.whenGET('/phones').respond(phones); - * - * // adds a new phone to the phones array - * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { - * var phone = angular.fromJson(data); - * phones.push(phone); - * return [200, phone, {}]; - * }); - * $httpBackend.whenGET(/^\/templates\//).passThrough(); - * //... - * }); - * ``` - * - * Afterwards, bootstrap your app with this new module. - */ - -/** - * @ngdoc method - * @name $httpBackend#when - * @module ngMockE2E - * @description - * Creates a new backend definition. - * - * @param {string} method HTTP method. - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header - * object and returns true if the headers match the current definition. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - * - * - respond – - * `{function([status,] data[, headers, statusText]) - * | function(function(method, url, data, headers)}` - * – The respond method takes a set of static data to be returned or a function that can return - * an array containing response status (number), response data (string), response headers - * (Object), and the text for the status (string). - * - passThrough – `{function()}` – Any request matching a backend definition with - * `passThrough` handler will be passed through to the real backend (an XHR request will be made - * to the server.) - * - Both methods return the `requestHandler` object for possible overrides. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenGET - * @module ngMockE2E - * @description - * Creates a new backend definition for GET requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenHEAD - * @module ngMockE2E - * @description - * Creates a new backend definition for HEAD requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenDELETE - * @module ngMockE2E - * @description - * Creates a new backend definition for DELETE requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenPOST - * @module ngMockE2E - * @description - * Creates a new backend definition for POST requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenPUT - * @module ngMockE2E - * @description - * Creates a new backend definition for PUT requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenPATCH - * @module ngMockE2E - * @description - * Creates a new backend definition for PATCH requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @param {(string|RegExp)=} data HTTP request body. - * @param {(Object|function(Object))=} headers HTTP headers. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ - -/** - * @ngdoc method - * @name $httpBackend#whenJSONP - * @module ngMockE2E - * @description - * Creates a new backend definition for JSONP requests. For more info see `when()`. - * - * @param {string|RegExp|function(string)} url HTTP url or function that receives a url - * and returns true if the url matches the current definition. - * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that - * control how a matched request is handled. You can save this object for later use and invoke - * `respond` or `passThrough` again in order to change how a matched request is handled. - */ -angular.mock.e2e = {}; -angular.mock.e2e.$httpBackendDecorator = - ['$rootScope', '$timeout', '$delegate', '$browser', createHttpBackendMock]; - - -/** - * @ngdoc type - * @name $rootScope.Scope - * @module ngMock - * @description - * {@link ng.$rootScope.Scope Scope} type decorated with helper methods useful for testing. These - * methods are automatically available on any {@link ng.$rootScope.Scope Scope} instance when - * `ngMock` module is loaded. - * - * In addition to all the regular `Scope` methods, the following helper methods are available: - */ -angular.mock.$RootScopeDecorator = ['$delegate', function($delegate) { - - var $rootScopePrototype = Object.getPrototypeOf($delegate); - - $rootScopePrototype.$countChildScopes = countChildScopes; - $rootScopePrototype.$countWatchers = countWatchers; - - return $delegate; - - // ------------------------------------------------------------------------------------------ // - - /** - * @ngdoc method - * @name $rootScope.Scope#$countChildScopes - * @module ngMock - * @description - * Counts all the direct and indirect child scopes of the current scope. - * - * The current scope is excluded from the count. The count includes all isolate child scopes. - * - * @returns {number} Total number of child scopes. - */ - function countChildScopes() { - // jshint validthis: true - var count = 0; // exclude the current scope - var pendingChildHeads = [this.$$childHead]; - var currentScope; - - while (pendingChildHeads.length) { - currentScope = pendingChildHeads.shift(); - - while (currentScope) { - count += 1; - pendingChildHeads.push(currentScope.$$childHead); - currentScope = currentScope.$$nextSibling; - } - } - - return count; - } - - - /** - * @ngdoc method - * @name $rootScope.Scope#$countWatchers - * @module ngMock - * @description - * Counts all the watchers of direct and indirect child scopes of the current scope. - * - * The watchers of the current scope are included in the count and so are all the watchers of - * isolate child scopes. - * - * @returns {number} Total number of watchers. - */ - function countWatchers() { - // jshint validthis: true - var count = this.$$watchers ? this.$$watchers.length : 0; // include the current scope - var pendingChildHeads = [this.$$childHead]; - var currentScope; - - while (pendingChildHeads.length) { - currentScope = pendingChildHeads.shift(); - - while (currentScope) { - count += currentScope.$$watchers ? currentScope.$$watchers.length : 0; - pendingChildHeads.push(currentScope.$$childHead); - currentScope = currentScope.$$nextSibling; - } - } - - return count; - } -}]; - - -if (window.jasmine || window.mocha) { - - var currentSpec = null, - annotatedFunctions = [], - isSpecRunning = function() { - return !!currentSpec; - }; - - angular.mock.$$annotate = angular.injector.$$annotate; - angular.injector.$$annotate = function(fn) { - if (typeof fn === 'function' && !fn.$inject) { - annotatedFunctions.push(fn); - } - return angular.mock.$$annotate.apply(this, arguments); - }; - - - (window.beforeEach || window.setup)(function() { - annotatedFunctions = []; - currentSpec = this; - }); - - (window.afterEach || window.teardown)(function() { - var injector = currentSpec.$injector; - - annotatedFunctions.forEach(function(fn) { - delete fn.$inject; - }); - - angular.forEach(currentSpec.$modules, function(module) { - if (module && module.$$hashKey) { - module.$$hashKey = undefined; - } - }); - - currentSpec.$injector = null; - currentSpec.$modules = null; - currentSpec = null; - - if (injector) { - injector.get('$rootElement').off(); - } - - // clean up jquery's fragment cache - angular.forEach(angular.element.fragments, function(val, key) { - delete angular.element.fragments[key]; - }); - - MockXhr.$$lastInstance = null; - - angular.forEach(angular.callbacks, function(val, key) { - delete angular.callbacks[key]; - }); - angular.callbacks.counter = 0; - }); - - /** - * @ngdoc function - * @name angular.mock.module - * @description - * - * *NOTE*: This function is also published on window for easy access.
    - * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha - * - * This function registers a module configuration code. It collects the configuration information - * which will be used when the injector is created by {@link angular.mock.inject inject}. - * - * See {@link angular.mock.inject inject} for usage example - * - * @param {...(string|Function|Object)} fns any number of modules which are represented as string - * aliases or as anonymous module initialization functions. The modules are used to - * configure the injector. The 'ng' and 'ngMock' modules are automatically loaded. If an - * object literal is passed they will be registered as values in the module, the key being - * the module name and the value being what is returned. - */ - window.module = angular.mock.module = function() { - var moduleFns = Array.prototype.slice.call(arguments, 0); - return isSpecRunning() ? workFn() : workFn; - ///////////////////// - function workFn() { - if (currentSpec.$injector) { - throw new Error('Injector already created, can not register a module!'); - } else { - var modules = currentSpec.$modules || (currentSpec.$modules = []); - angular.forEach(moduleFns, function(module) { - if (angular.isObject(module) && !angular.isArray(module)) { - modules.push(function($provide) { - angular.forEach(module, function(value, key) { - $provide.value(key, value); - }); - }); - } else { - modules.push(module); - } - }); - } - } - }; - - /** - * @ngdoc function - * @name angular.mock.inject - * @description - * - * *NOTE*: This function is also published on window for easy access.
    - * *NOTE*: This function is declared ONLY WHEN running tests with jasmine or mocha - * - * The inject function wraps a function into an injectable function. The inject() creates new - * instance of {@link auto.$injector $injector} per test, which is then used for - * resolving references. - * - * - * ## Resolving References (Underscore Wrapping) - * Often, we would like to inject a reference once, in a `beforeEach()` block and reuse this - * in multiple `it()` clauses. To be able to do this we must assign the reference to a variable - * that is declared in the scope of the `describe()` block. Since we would, most likely, want - * the variable to have the same name of the reference we have a problem, since the parameter - * to the `inject()` function would hide the outer variable. - * - * To help with this, the injected parameters can, optionally, be enclosed with underscores. - * These are ignored by the injector when the reference name is resolved. - * - * For example, the parameter `_myService_` would be resolved as the reference `myService`. - * Since it is available in the function body as _myService_, we can then assign it to a variable - * defined in an outer scope. - * - * ``` - * // Defined out reference variable outside - * var myService; - * - * // Wrap the parameter in underscores - * beforeEach( inject( function(_myService_){ - * myService = _myService_; - * })); - * - * // Use myService in a series of tests. - * it('makes use of myService', function() { - * myService.doStuff(); - * }); - * - * ``` - * - * See also {@link angular.mock.module angular.mock.module} - * - * ## Example - * Example of what a typical jasmine tests looks like with the inject method. - * ```js - * - * angular.module('myApplicationModule', []) - * .value('mode', 'app') - * .value('version', 'v1.0.1'); - * - * - * describe('MyApp', function() { - * - * // You need to load modules that you want to test, - * // it loads only the "ng" module by default. - * beforeEach(module('myApplicationModule')); - * - * - * // inject() is used to inject arguments of all given functions - * it('should provide a version', inject(function(mode, version) { - * expect(version).toEqual('v1.0.1'); - * expect(mode).toEqual('app'); - * })); - * - * - * // The inject and module method can also be used inside of the it or beforeEach - * it('should override a version and test the new version is injected', function() { - * // module() takes functions or strings (module aliases) - * module(function($provide) { - * $provide.value('version', 'overridden'); // override version here - * }); - * - * inject(function(version) { - * expect(version).toEqual('overridden'); - * }); - * }); - * }); - * - * ``` - * - * @param {...Function} fns any number of functions which will be injected using the injector. - */ - - - - var ErrorAddingDeclarationLocationStack = function(e, errorForStack) { - this.message = e.message; - this.name = e.name; - if (e.line) this.line = e.line; - if (e.sourceId) this.sourceId = e.sourceId; - if (e.stack && errorForStack) - this.stack = e.stack + '\n' + errorForStack.stack; - if (e.stackArray) this.stackArray = e.stackArray; - }; - ErrorAddingDeclarationLocationStack.prototype.toString = Error.prototype.toString; - - window.inject = angular.mock.inject = function() { - var blockFns = Array.prototype.slice.call(arguments, 0); - var errorForStack = new Error('Declaration Location'); - return isSpecRunning() ? workFn.call(currentSpec) : workFn; - ///////////////////// - function workFn() { - var modules = currentSpec.$modules || []; - var strictDi = !!currentSpec.$injectorStrict; - modules.unshift('ngMock'); - modules.unshift('ng'); - var injector = currentSpec.$injector; - if (!injector) { - if (strictDi) { - // If strictDi is enabled, annotate the providerInjector blocks - angular.forEach(modules, function(moduleFn) { - if (typeof moduleFn === "function") { - angular.injector.$$annotate(moduleFn); - } - }); - } - injector = currentSpec.$injector = angular.injector(modules, strictDi); - currentSpec.$injectorStrict = strictDi; - } - for (var i = 0, ii = blockFns.length; i < ii; i++) { - if (currentSpec.$injectorStrict) { - // If the injector is strict / strictDi, and the spec wants to inject using automatic - // annotation, then annotate the function here. - injector.annotate(blockFns[i]); - } - try { - /* jshint -W040 *//* Jasmine explicitly provides a `this` object when calling functions */ - injector.invoke(blockFns[i] || angular.noop, this); - /* jshint +W040 */ - } catch (e) { - if (e.stack && errorForStack) { - throw new ErrorAddingDeclarationLocationStack(e, errorForStack); - } - throw e; - } finally { - errorForStack = null; - } - } - } - }; - - - angular.mock.inject.strictDi = function(value) { - value = arguments.length ? !!value : true; - return isSpecRunning() ? workFn() : workFn; - - function workFn() { - if (value !== currentSpec.$injectorStrict) { - if (currentSpec.$injector) { - throw new Error('Injector already created, can not modify strict annotations'); - } else { - currentSpec.$injectorStrict = value; - } - } - } - }; -} - - -})(window, window.angular); diff --git a/themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-scenario.js b/themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-scenario.js deleted file mode 100644 index 705d70e65e4..00000000000 --- a/themes/src/main/resources/theme/keycloak/common/resources/lib/angular/angular-scenario.js +++ /dev/null @@ -1,40024 +0,0 @@ -/*! - * jQuery JavaScript Library v2.1.1 - * http://jquery.com/ - * - * Includes Sizzle.js - * http://sizzlejs.com/ - * - * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2014-05-01T17:11Z - */ - -(function( global, factory ) {'use strict'; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - // For CommonJS and CommonJS-like environments where a proper window is present, - // execute the factory and get jQuery - // For environments that do not inherently posses a window with a document - // (such as Node.js), expose a jQuery-making factory as module.exports - // This accentuates the need for the creation of a real window - // e.g. var jQuery = require("jquery")(window); - // See ticket #14549 for more info - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Can't do this because several apps including ASP.NET trace -// the stack via arguments.caller.callee and Firefox dies if -// you try to trace through "use strict" call chains. (#13335) -// Support: Firefox 18+ -// - -var arr = []; - -var slice = arr.slice; - -var concat = arr.concat; - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var support = {}; - - - -var - // Use the correct document accordingly with window argument (sandbox) - document = window.document, - - version = "2.1.1", - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }, - - // Support: Android<4.1 - // Make sure we trim BOM and NBSP - rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, - - // Matches dashed string for camelizing - rmsPrefix = /^-ms-/, - rdashAlpha = /-([\da-z])/gi, - - // Used by jQuery.camelCase as callback to replace() - fcamelCase = function( all, letter ) { - return letter.toUpperCase(); - }; - -jQuery.fn = jQuery.prototype = { - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // Start with an empty selector - selector: "", - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - return num != null ? - - // Return just the one element from the set - ( num < 0 ? this[ num + this.length ] : this[ num ] ) : - - // Return all the elements in a clean array - slice.call( this ); - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - ret.context = this.context; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - // (You can seed the arguments with an array of args, but this is - // only used internally.) - each: function( callback, args ) { - return jQuery.each( this, callback, args ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map(this, function( elem, i ) { - return callback.call( elem, i, elem ); - })); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(null); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[0] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !jQuery.isFunction(target) ) { - target = {}; - } - - // extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - // Only deal with non-null/undefined values - if ( (options = arguments[ i ]) != null ) { - // Extend the base object - for ( name in options ) { - src = target[ name ]; - copy = options[ name ]; - - // Prevent never-ending loop - if ( target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { - if ( copyIsArray ) { - copyIsArray = false; - clone = src && jQuery.isArray(src) ? src : []; - - } else { - clone = src && jQuery.isPlainObject(src) ? src : {}; - } - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend({ - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - // See test/unit/core.js for details concerning isFunction. - // Since version 1.3, DOM methods and functions like alert - // aren't supported. They return false on IE (#2968). - isFunction: function( obj ) { - return jQuery.type(obj) === "function"; - }, - - isArray: Array.isArray, - - isWindow: function( obj ) { - return obj != null && obj === obj.window; - }, - - isNumeric: function( obj ) { - // parseFloat NaNs numeric-cast false positives (null|true|false|"") - // ...but misinterprets leading-number strings, particularly hex literals ("0x...") - // subtraction forces infinities to NaN - return !jQuery.isArray( obj ) && obj - parseFloat( obj ) >= 0; - }, - - isPlainObject: function( obj ) { - // Not plain objects: - // - Any object or value whose internal [[Class]] property is not "[object Object]" - // - DOM nodes - // - window - if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { - return false; - } - - if ( obj.constructor && - !hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) { - return false; - } - - // If the function hasn't returned already, we're confident that - // |obj| is a plain object, created by {} or constructed with new Object - return true; - }, - - isEmptyObject: function( obj ) { - var name; - for ( name in obj ) { - return false; - } - return true; - }, - - type: function( obj ) { - if ( obj == null ) { - return obj + ""; - } - // Support: Android < 4.0, iOS < 6 (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call(obj) ] || "object" : - typeof obj; - }, - - // Evaluates a script in a global context - globalEval: function( code ) { - var script, - indirect = eval; - - code = jQuery.trim( code ); - - if ( code ) { - // If the code includes a valid, prologue position - // strict mode pragma, execute code by injecting a - // script tag into the document. - if ( code.indexOf("use strict") === 1 ) { - script = document.createElement("script"); - script.text = code; - document.head.appendChild( script ).parentNode.removeChild( script ); - } else { - // Otherwise, avoid the DOM node creation, insertion - // and removal by using an indirect global eval - indirect( code ); - } - } - }, - - // Convert dashed to camelCase; used by the css and data modules - // Microsoft forgot to hump their vendor prefix (#9572) - camelCase: function( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); - }, - - nodeName: function( elem, name ) { - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - }, - - // args is for internal usage only - each: function( obj, callback, args ) { - var value, - i = 0, - length = obj.length, - isArray = isArraylike( obj ); - - if ( args ) { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.apply( obj[ i ], args ); - - if ( value === false ) { - break; - } - } - } - - // A special, fast, case for the most common use of each - } else { - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } else { - for ( i in obj ) { - value = callback.call( obj[ i ], i, obj[ i ] ); - - if ( value === false ) { - break; - } - } - } - } - - return obj; - }, - - // Support: Android<4.1 - trim: function( text ) { - return text == null ? - "" : - ( text + "" ).replace( rtrim, "" ); - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArraylike( Object(arr) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var value, - i = 0, - length = elems.length, - isArray = isArraylike( elems ), - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArray ) { - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return concat.apply( [], ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // Bind a function to a context, optionally partially applying any - // arguments. - proxy: function( fn, context ) { - var tmp, args, proxy; - - if ( typeof context === "string" ) { - tmp = fn[ context ]; - context = fn; - fn = tmp; - } - - // Quick check to determine if target is callable, in the spec - // this throws a TypeError, but we will just return undefined. - if ( !jQuery.isFunction( fn ) ) { - return undefined; - } - - // Simulated bind - args = slice.call( arguments, 2 ); - proxy = function() { - return fn.apply( context || this, args.concat( slice.call( arguments ) ) ); - }; - - // Set the guid of unique handler to the same of original handler, so it can be removed - proxy.guid = fn.guid = fn.guid || jQuery.guid++; - - return proxy; - }, - - now: Date.now, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -}); - -// Populate the class2type map -jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); -}); - -function isArraylike( obj ) { - var length = obj.length, - type = jQuery.type( obj ); - - if ( type === "function" || jQuery.isWindow( obj ) ) { - return false; - } - - if ( obj.nodeType === 1 && length ) { - return true; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} -var Sizzle = -/*! - * Sizzle CSS Selector Engine v1.10.19 - * http://sizzlejs.com/ - * - * Copyright 2013 jQuery Foundation, Inc. and other contributors - * Released under the MIT license - * http://jquery.org/license - * - * Date: 2014-04-18 - */ -(function( window ) { - -var i, - support, - Expr, - getText, - isXML, - tokenize, - compile, - select, - outermostContext, - sortInput, - hasDuplicate, - - // Local document vars - setDocument, - document, - docElem, - documentIsHTML, - rbuggyQSA, - rbuggyMatches, - matches, - contains, - - // Instance-specific data - expando = "sizzle" + -(new Date()), - preferredDoc = window.document, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - // General-purpose constants - strundefined = typeof undefined, - MAX_NEGATIVE = 1 << 31, - - // Instance methods - hasOwn = ({}).hasOwnProperty, - arr = [], - pop = arr.pop, - push_native = arr.push, - push = arr.push, - slice = arr.slice, - // Use a stripped-down indexOf if we can't use a native one - indexOf = arr.indexOf || function( elem ) { - var i = 0, - len = this.length; - for ( ; i < len; i++ ) { - if ( this[i] === elem ) { - return i; - } - } - return -1; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace - whitespace = "[\\x20\\t\\r\\n\\f]", - // http://www.w3.org/TR/css3-syntax/#characters - characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", - - // Loosely modeled on CSS identifier characters - // An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors - // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier - identifier = characterEncoding.replace( "w", "w#" ), - - // Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace + - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace + - "*\\]", - - pseudos = ":(" + characterEncoding + ")(?:\\((" + - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ), - - rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - "ID": new RegExp( "^#(" + characterEncoding + ")" ), - "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ), - "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ), - "ATTR": new RegExp( "^" + attributes ), - "PSEUDO": new RegExp( "^" + pseudos ), - "CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace + - "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace + - "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - "bool": new RegExp( "^(?:" + booleans + ")$", "i" ), - // For use in libraries implementing .is() - // We use this for POS matching in `select` - "needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + - whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - rnative = /^[^{]+\{\s*\[native \w/, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - rescape = /'|\\/g, - - // CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ), - funescape = function( _, escaped, escapedWhitespace ) { - var high = "0x" + escaped - 0x10000; - // NaN means non-codepoint - // Support: Firefox<24 - // Workaround erroneous numeric interpretation of +"0x" - return high !== high || escapedWhitespace ? - escaped : - high < 0 ? - // BMP codepoint - String.fromCharCode( high + 0x10000 ) : - // Supplemental Plane codepoint (surrogate pair) - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }; - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - (arr = slice.call( preferredDoc.childNodes )), - preferredDoc.childNodes - ); - // Support: Android<4.0 - // Detect silently failing push.apply - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { apply: arr.length ? - - // Leverage slice if possible - function( target, els ) { - push_native.apply( target, slice.call(els) ); - } : - - // Support: IE<9 - // Otherwise append directly - function( target, els ) { - var j = target.length, - i = 0; - // Can't trust NodeList.length - while ( (target[j++] = els[i++]) ) {} - target.length = j - 1; - } - }; -} - -function Sizzle( selector, context, results, seed ) { - var match, elem, m, nodeType, - // QSA vars - i, groups, old, nid, newContext, newSelector; - - if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) { - setDocument( context ); - } - - context = context || document; - results = results || []; - - if ( !selector || typeof selector !== "string" ) { - return results; - } - - if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) { - return []; - } - - if ( documentIsHTML && !seed ) { - - // Shortcuts - if ( (match = rquickExpr.exec( selector )) ) { - // Speed-up: Sizzle("#ID") - if ( (m = match[1]) ) { - if ( nodeType === 9 ) { - elem = context.getElementById( m ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document (jQuery #6963) - if ( elem && elem.parentNode ) { - // Handle the case where IE, Opera, and Webkit return items - // by name instead of ID - if ( elem.id === m ) { - results.push( elem ); - return results; - } - } else { - return results; - } - } else { - // Context is not a document - if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) && - contains( context, elem ) && elem.id === m ) { - results.push( elem ); - return results; - } - } - - // Speed-up: Sizzle("TAG") - } else if ( match[2] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Speed-up: Sizzle(".CLASS") - } else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) { - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // QSA path - if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) { - nid = old = expando; - newContext = context; - newSelector = nodeType === 9 && selector; - - // qSA works strangely on Element-rooted queries - // We can work around this by specifying an extra ID on the root - // and working up from there (Thanks to Andrew Dupont for the technique) - // IE 8 doesn't work on object elements - if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) { - groups = tokenize( selector ); - - if ( (old = context.getAttribute("id")) ) { - nid = old.replace( rescape, "\\$&" ); - } else { - context.setAttribute( "id", nid ); - } - nid = "[id='" + nid + "'] "; - - i = groups.length; - while ( i-- ) { - groups[i] = nid + toSelector( groups[i] ); - } - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context; - newSelector = groups.join(","); - } - - if ( newSelector ) { - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch(qsaError) { - } finally { - if ( !old ) { - context.removeAttribute("id"); - } - } - } - } - } - - // All others - return select( selector.replace( rtrim, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {Function(string, Object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - // Use (key + " ") to avoid collision with native prototype properties (see Issue #157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return (cache[ key + " " ] = value); - } - return cache; -} - -/** - * Mark a function for special use by Sizzle - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created div and expects a boolean result - */ -function assert( fn ) { - var div = document.createElement("div"); - - try { - return !!fn( div ); - } catch (e) { - return false; - } finally { - // Remove from its parent by default - if ( div.parentNode ) { - div.parentNode.removeChild( div ); - } - // release memory in IE - div = null; - } -} - -/** - * Adds the same handler for all of the specified attrs - * @param {String} attrs Pipe-separated list of attributes - * @param {Function} handler The method that will be applied - */ -function addHandle( attrs, handler ) { - var arr = attrs.split("|"), - i = attrs.length; - - while ( i-- ) { - Expr.attrHandle[ arr[i] ] = handler; - } -} - -/** - * Checks document order of two siblings - * @param {Element} a - * @param {Element} b - * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b - */ -function siblingCheck( a, b ) { - var cur = b && a, - diff = cur && a.nodeType === 1 && b.nodeType === 1 && - ( ~b.sourceIndex || MAX_NEGATIVE ) - - ( ~a.sourceIndex || MAX_NEGATIVE ); - - // Use IE sourceIndex if available on both nodes - if ( diff ) { - return diff; - } - - // Check if b follows a - if ( cur ) { - while ( (cur = cur.nextSibling) ) { - if ( cur === b ) { - return -1; - } - } - } - - return a ? 1 : -1; -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - var name = elem.nodeName.toLowerCase(); - return (name === "input" || name === "button") && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction(function( argument ) { - argument = +argument; - return markFunction(function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ (j = matchIndexes[i]) ] ) { - seed[j] = !(matches[j] = seed[j]); - } - } - }); - }); -} - -/** - * Checks a node for validity as a Sizzle context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== strundefined && context; -} - -// Expose support vars for convenience -support = Sizzle.support = {}; - -/** - * Detects XML nodes - * @param {Element|Object} elem An element or a document - * @returns {Boolean} True iff elem is a non-HTML XML node - */ -isXML = Sizzle.isXML = function( elem ) { - // documentElement is verified for cases where it doesn't yet exist - // (such as loading iframes in IE - #4833) - var documentElement = elem && (elem.ownerDocument || elem).documentElement; - return documentElement ? documentElement.nodeName !== "HTML" : false; -}; - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [doc] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -setDocument = Sizzle.setDocument = function( node ) { - var hasCompare, - doc = node ? node.ownerDocument || node : preferredDoc, - parent = doc.defaultView; - - // If no document and documentElement is available, return - if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Set our document - document = doc; - docElem = doc.documentElement; - - // Support tests - documentIsHTML = !isXML( doc ); - - // Support: IE>8 - // If iframe document is assigned to "document" variable and if iframe has been reloaded, - // IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936 - // IE6-8 do not support the defaultView property so parent will be undefined - if ( parent && parent !== parent.top ) { - // IE11 does not have attachEvent, so all must suffer - if ( parent.addEventListener ) { - parent.addEventListener( "unload", function() { - setDocument(); - }, false ); - } else if ( parent.attachEvent ) { - parent.attachEvent( "onunload", function() { - setDocument(); - }); - } - } - - /* Attributes - ---------------------------------------------------------------------- */ - - // Support: IE<8 - // Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans) - support.attributes = assert(function( div ) { - div.className = "i"; - return !div.getAttribute("className"); - }); - - /* getElement(s)By* - ---------------------------------------------------------------------- */ - - // Check if getElementsByTagName("*") returns only elements - support.getElementsByTagName = assert(function( div ) { - div.appendChild( doc.createComment("") ); - return !div.getElementsByTagName("*").length; - }); - - // Check if getElementsByClassName can be trusted - support.getElementsByClassName = rnative.test( doc.getElementsByClassName ) && assert(function( div ) { - div.innerHTML = "
    "; - - // Support: Safari<4 - // Catch class over-caching - div.firstChild.className = "i"; - // Support: Opera<10 - // Catch gEBCN failure to find non-leading classes - return div.getElementsByClassName("i").length === 2; - }); - - // Support: IE<10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert(function( div ) { - docElem.appendChild( div ).id = expando; - return !doc.getElementsByName || !doc.getElementsByName( expando ).length; - }); - - // ID find and filter - if ( support.getById ) { - Expr.find["ID"] = function( id, context ) { - if ( typeof context.getElementById !== strundefined && documentIsHTML ) { - var m = context.getElementById( id ); - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - return m && m.parentNode ? [ m ] : []; - } - }; - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute("id") === attrId; - }; - }; - } else { - // Support: IE6/7 - // getElementById is not reliable as a find shortcut - delete Expr.find["ID"]; - - Expr.filter["ID"] = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id"); - return node && node.value === attrId; - }; - }; - } - - // Tag - Expr.find["TAG"] = support.getElementsByTagName ? - function( tag, context ) { - if ( typeof context.getElementsByTagName !== strundefined ) { - return context.getElementsByTagName( tag ); - } - } : - function( tag, context ) { - var elem, - tmp = [], - i = 0, - results = context.getElementsByTagName( tag ); - - // Filter out possible comments - if ( tag === "*" ) { - while ( (elem = results[i++]) ) { - if ( elem.nodeType === 1 ) { - tmp.push( elem ); - } - } - - return tmp; - } - return results; - }; - - // Class - Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) { - if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - // matchesSelector(:active) reports false when true (IE9/Opera 11.5) - rbuggyMatches = []; - - // qSa(:focus) reports false when true (Chrome 21) - // We allow this because of a bug in IE8/9 that throws an error - // whenever `document.activeElement` is accessed on an iframe - // So, we allow :focus to pass through QSA all the time to avoid the IE error - // See http://bugs.jquery.com/ticket/13378 - rbuggyQSA = []; - - if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) { - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert(function( div ) { - // Select is set to empty string on purpose - // This is to test IE's treatment of not explicitly - // setting a boolean content attribute, - // since its presence should be enough - // http://bugs.jquery.com/ticket/12359 - div.innerHTML = ""; - - // Support: IE8, Opera 11-12.16 - // Nothing should be selected when empty strings follow ^= or $= or *= - // The test attribute must be unknown in Opera but "safe" for WinRT - // http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section - if ( div.querySelectorAll("[msallowclip^='']").length ) { - rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" ); - } - - // Support: IE8 - // Boolean attributes and "value" are not treated correctly - if ( !div.querySelectorAll("[selected]").length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Webkit/Opera - :checked should return selected option elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":checked").length ) { - rbuggyQSA.push(":checked"); - } - }); - - assert(function( div ) { - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - var input = doc.createElement("input"); - input.setAttribute( "type", "hidden" ); - div.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE8 - // Enforce case-sensitivity of name attribute - if ( div.querySelectorAll("[name=d]").length ) { - rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" ); - } - - // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled) - // IE8 throws error here and will not see later tests - if ( !div.querySelectorAll(":enabled").length ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Opera 10-11 does not throw on post-comma invalid pseudos - div.querySelectorAll("*,:x"); - rbuggyQSA.push(",.*:"); - }); - } - - if ( (support.matchesSelector = rnative.test( (matches = docElem.matches || - docElem.webkitMatchesSelector || - docElem.mozMatchesSelector || - docElem.oMatchesSelector || - docElem.msMatchesSelector) )) ) { - - assert(function( div ) { - // Check to see if it's possible to do matchesSelector - // on a disconnected node (IE 9) - support.disconnectedMatch = matches.call( div, "div" ); - - // This should fail with an exception - // Gecko does not error, returns false instead - matches.call( div, "[s!='']:x" ); - rbuggyMatches.push( "!=", pseudos ); - }); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") ); - rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") ); - - /* Contains - ---------------------------------------------------------------------- */ - hasCompare = rnative.test( docElem.compareDocumentPosition ); - - // Element contains another - // Purposefully does not implement inclusive descendent - // As in, an element does not contain itself - contains = hasCompare || rnative.test( docElem.contains ) ? - function( a, b ) { - var adown = a.nodeType === 9 ? a.documentElement : a, - bup = b && b.parentNode; - return a === bup || !!( bup && bup.nodeType === 1 && ( - adown.contains ? - adown.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - )); - } : - function( a, b ) { - if ( b ) { - while ( (b = b.parentNode) ) { - if ( b === a ) { - return true; - } - } - } - return false; - }; - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = hasCompare ? - function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - (!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) { - - // Choose the first element that is related to our preferred document - if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) { - return -1; - } - if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - } : - function( a, b ) { - // Exit early if the nodes are identical - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - var cur, - i = 0, - aup = a.parentNode, - bup = b.parentNode, - ap = [ a ], - bp = [ b ]; - - // Parentless nodes are either documents or disconnected - if ( !aup || !bup ) { - return a === doc ? -1 : - b === doc ? 1 : - aup ? -1 : - bup ? 1 : - sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : - 0; - - // If the nodes are siblings, we can do a quick check - } else if ( aup === bup ) { - return siblingCheck( a, b ); - } - - // Otherwise we need full lists of their ancestors for comparison - cur = a; - while ( (cur = cur.parentNode) ) { - ap.unshift( cur ); - } - cur = b; - while ( (cur = cur.parentNode) ) { - bp.unshift( cur ); - } - - // Walk down the tree looking for a discrepancy - while ( ap[i] === bp[i] ) { - i++; - } - - return i ? - // Do a sibling check if the nodes have a common ancestor - siblingCheck( ap[i], bp[i] ) : - - // Otherwise nodes in our document sort first - ap[i] === preferredDoc ? -1 : - bp[i] === preferredDoc ? 1 : - 0; - }; - - return doc; -}; - -Sizzle.matches = function( expr, elements ) { - return Sizzle( expr, null, null, elements ); -}; - -Sizzle.matchesSelector = function( elem, expr ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - // Make sure that attribute selectors are quoted - expr = expr.replace( rattributeQuotes, "='$1']" ); - - if ( support.matchesSelector && documentIsHTML && - ( !rbuggyMatches || !rbuggyMatches.test( expr ) ) && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch(e) {} - } - - return Sizzle( expr, document, null, [ elem ] ).length > 0; -}; - -Sizzle.contains = function( context, elem ) { - // Set document vars if needed - if ( ( context.ownerDocument || context ) !== document ) { - setDocument( context ); - } - return contains( context, elem ); -}; - -Sizzle.attr = function( elem, name ) { - // Set document vars if needed - if ( ( elem.ownerDocument || elem ) !== document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - // Don't get fooled by Object.prototype properties (jQuery #13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - return val !== undefined ? - val : - support.attributes || !documentIsHTML ? - elem.getAttribute( name ) : - (val = elem.getAttributeNode(name)) && val.specified ? - val.value : - null; -}; - -Sizzle.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -Sizzle.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - hasDuplicate = !support.detectDuplicates; - sortInput = !support.sortStable && results.slice( 0 ); - results.sort( sortOrder ); - - if ( hasDuplicate ) { - while ( (elem = results[i++]) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - results.splice( duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -/** - * Utility function for retrieving the text value of an array of DOM nodes - * @param {Array|Element} elem - */ -getText = Sizzle.getText = function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - // If no nodeType, this is expected to be an array - while ( (node = elem[i++]) ) { - // Do not traverse comment nodes - ret += getText( node ); - } - } else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { - // Use textContent for elements - // innerText usage removed for consistency of new lines (jQuery #11153) - if ( typeof elem.textContent === "string" ) { - return elem.textContent; - } else { - // Traverse its children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - ret += getText( elem ); - } - } - } else if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - // Do not include comment or processing instruction nodes - - return ret; -}; - -Expr = Sizzle.selectors = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - "ATTR": function( match ) { - match[1] = match[1].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape ); - - if ( match[2] === "~=" ) { - match[3] = " " + match[3] + " "; - } - - return match.slice( 0, 4 ); - }, - - "CHILD": function( match ) { - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[1] = match[1].toLowerCase(); - - if ( match[1].slice( 0, 3 ) === "nth" ) { - // nth-* requires argument - if ( !match[3] ) { - Sizzle.error( match[0] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) ); - match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" ); - - // other types prohibit arguments - } else if ( match[3] ) { - Sizzle.error( match[0] ); - } - - return match; - }, - - "PSEUDO": function( match ) { - var excess, - unquoted = !match[6] && match[2]; - - if ( matchExpr["CHILD"].test( match[0] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[3] ) { - match[2] = match[4] || match[5] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - // Get excess from tokenize (recursively) - (excess = tokenize( unquoted, true )) && - // advance to the next closing parenthesis - (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) { - - // excess is a negative index - match[0] = match[0].slice( 0, excess ); - match[2] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - "TAG": function( nodeNameSelector ) { - var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { return true; } : - function( elem ) { - return elem.nodeName && elem.nodeName.toLowerCase() === nodeName; - }; - }, - - "CLASS": function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - (pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) && - classCache( className, function( elem ) { - return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" ); - }); - }, - - "ATTR": function( name, operator, check ) { - return function( elem ) { - var result = Sizzle.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - return operator === "=" ? result === check : - operator === "!=" ? result !== check : - operator === "^=" ? check && result.indexOf( check ) === 0 : - operator === "*=" ? check && result.indexOf( check ) > -1 : - operator === "$=" ? check && result.slice( -check.length ) === check : - operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 : - operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" : - false; - }; - }, - - "CHILD": function( type, what, argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, context, xml ) { - var cache, outerCache, node, diff, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( (node = node[ dir ]) ) { - if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) { - return false; - } - } - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || (parent[ expando ] = {}); - cache = outerCache[ type ] || []; - nodeIndex = cache[0] === dirruns && cache[1]; - diff = cache[0] === dirruns && cache[2]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( (node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - (diff = nodeIndex = 0) || start.pop()) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - // Use previously-cached element index if available - } else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) { - diff = cache[1]; - - // xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...) - } else { - // Use the same loop as above to seek `elem` from the start - while ( (node = ++nodeIndex && node && node[ dir ] || - (diff = nodeIndex = 0) || start.pop()) ) { - - if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) { - // Cache the index of each encountered element - if ( useCache ) { - (node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - "PSEUDO": function( pseudo, argument ) { - // pseudo-class names are case-insensitive - // http://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - Sizzle.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as Sizzle does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction(function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf.call( seed, matched[i] ); - seed[ idx ] = !( matches[ idx ] = matched[i] ); - } - }) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - // Potentially complex pseudos - "not": markFunction(function( selector ) { - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrim, "$1" ) ); - - return matcher[ expando ] ? - markFunction(function( seed, matches, context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( (elem = unmatched[i]) ) { - seed[i] = !(matches[i] = elem); - } - } - }) : - function( elem, context, xml ) { - input[0] = elem; - matcher( input, null, xml, results ); - return !results.pop(); - }; - }), - - "has": markFunction(function( selector ) { - return function( elem ) { - return Sizzle( selector, elem ).length > 0; - }; - }), - - "contains": markFunction(function( text ) { - return function( elem ) { - return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1; - }; - }), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // http://www.w3.org/TR/selectors/#lang-pseudo - "lang": markFunction( function( lang ) { - // lang value must be a valid identifier - if ( !ridentifier.test(lang || "") ) { - Sizzle.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( (elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( (elem = elem.parentNode) && elem.nodeType === 1 ); - return false; - }; - }), - - // Miscellaneous - "target": function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - "root": function( elem ) { - return elem === docElem; - }, - - "focus": function( elem ) { - return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex); - }, - - // Boolean properties - "enabled": function( elem ) { - return elem.disabled === false; - }, - - "disabled": function( elem ) { - return elem.disabled === true; - }, - - "checked": function( elem ) { - // In CSS3, :checked should return both checked and selected elements - // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - var nodeName = elem.nodeName.toLowerCase(); - return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected); - }, - - "selected": function( elem ) { - // Accessing this property makes selected-by-default - // options in Safari work properly - if ( elem.parentNode ) { - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - "empty": function( elem ) { - // http://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - "parent": function( elem ) { - return !Expr.pseudos["empty"]( elem ); - }, - - // Element/input types - "header": function( elem ) { - return rheader.test( elem.nodeName ); - }, - - "input": function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - "button": function( elem ) { - var name = elem.nodeName.toLowerCase(); - return name === "input" && elem.type === "button" || name === "button"; - }, - - "text": function( elem ) { - var attr; - return elem.nodeName.toLowerCase() === "input" && - elem.type === "text" && - - // Support: IE<8 - // New HTML5 attribute values (e.g., "search") appear with elem.type === "text" - ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - "first": createPositionalPseudo(function() { - return [ 0 ]; - }), - - "last": createPositionalPseudo(function( matchIndexes, length ) { - return [ length - 1 ]; - }), - - "eq": createPositionalPseudo(function( matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - }), - - "even": createPositionalPseudo(function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "odd": createPositionalPseudo(function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "lt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }), - - "gt": createPositionalPseudo(function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - }) - } -}; - -Expr.pseudos["nth"] = Expr.pseudos["eq"]; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -tokenize = Sizzle.tokenize = function( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || (match = rcomma.exec( soFar )) ) { - if ( match ) { - // Don't consume trailing commas as valid - soFar = soFar.slice( match[0].length ) || soFar; - } - groups.push( (tokens = []) ); - } - - matched = false; - - // Combinators - if ( (match = rcombinators.exec( soFar )) ) { - matched = match.shift(); - tokens.push({ - value: matched, - // Cast descendant combinators to space - type: match[0].replace( rtrim, " " ) - }); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] || - (match = preFilters[ type ]( match ))) ) { - matched = match.shift(); - tokens.push({ - value: matched, - type: type, - matches: match - }); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - return parseOnly ? - soFar.length : - soFar ? - Sizzle.error( selector ) : - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -}; - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[i].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - checkNonElements = base && dir === "parentNode", - doneName = done++; - - return combinator.first ? - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching - if ( xml ) { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( (elem = elem[ dir ]) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || (elem[ expando ] = {}); - if ( (oldCache = outerCache[ dir ]) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return (newCache[ 2 ] = oldCache[ 2 ]); - } else { - // Reuse newcache so results back-propagate to previous elements - outerCache[ dir ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) { - return true; - } - } - } - } - } - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[i]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[0]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - Sizzle( selector, contexts[i], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( (elem = unmatched[i]) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction(function( seed, results, context, xml ) { - var temp, i, elem, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems, - - matcherOut = matcher ? - // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results, - postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results : - matcherIn; - - // Find primary matches - if ( matcher ) { - matcher( matcherIn, matcherOut, context, xml ); - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( (elem = temp[i]) ) { - matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) ) { - // Restore matcherIn since elem is not yet a final match - temp.push( (matcherIn[i] = elem) ); - } - } - postFinder( null, (matcherOut = []), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( (elem = matcherOut[i]) && - (temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) { - - seed[temp] = !(results[temp] = elem); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - }); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[0].type ], - implicitRelative = leadingRelative || Expr.relative[" "], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - return ( !leadingRelative && ( xml || context !== outermostContext ) ) || ( - (checkContext = context).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - } ]; - - for ( ; i < len; i++ ) { - if ( (matcher = Expr.relative[ tokens[i].type ]) ) { - matchers = [ addCombinator(elementMatcher( matchers ), matcher) ]; - } else { - matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[j].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" }) - ).replace( rtrim, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( (tokens = tokens.slice( j )) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find["TAG"]( "*", outermost ), - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1), - len = elems.length; - - if ( outermost ) { - outermostContext = context !== document && context; - } - - // Add elements passing elementMatchers directly to results - // Keep `i` a string if there are no elements so `matchedCount` will be "00" below - // Support: IE<9, Safari - // Tolerate NodeList properties (IE: "length"; Safari: ) matching elements by id - for ( ; i !== len && (elem = elems[i]) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - while ( (matcher = elementMatchers[j++]) ) { - if ( matcher( elem, context, xml ) ) { - results.push( elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - // They will have gone through all possible matchers - if ( (elem = !matcher && elem) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // Apply set filters to unmatched elements - matchedCount += i; - if ( bySet && i !== matchedCount ) { - j = 0; - while ( (matcher = setMatchers[j++]) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !(unmatched[i] || setMatched[i]) ) { - setMatched[i] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - Sizzle.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[i] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -}; - -/** - * A low-level selection function that works with Sizzle's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with Sizzle.compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -select = Sizzle.select = function( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( (selector = compiled.selector || selector) ); - - results = results || []; - - // Try to minimize operations if there is no seed and only one group - if ( match.length === 1 ) { - - // Take a shortcut and set the context if the root selector is an ID - tokens = match[0] = match[0].slice( 0 ); - if ( tokens.length > 2 && (token = tokens[0]).type === "ID" && - support.getById && context.nodeType === 9 && documentIsHTML && - Expr.relative[ tokens[1].type ] ) { - - context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[i]; - - // Abort if we hit a combinator - if ( Expr.relative[ (type = token.type) ] ) { - break; - } - if ( (find = Expr.find[ type ]) ) { - // Search, expanding context for leading sibling combinators - if ( (seed = find( - token.matches[0].replace( runescape, funescape ), - rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context - )) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -}; - -// One-time assignments - -// Sort stability -support.sortStable = expando.split("").sort( sortOrder ).join("") === expando; - -// Support: Chrome<14 -// Always assume duplicates if they aren't passed to the comparison function -support.detectDuplicates = !!hasDuplicate; - -// Initialize against the default document -setDocument(); - -// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27) -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert(function( div1 ) { - // Should return 1, but returns 4 (following) - return div1.compareDocumentPosition( document.createElement("div") ) & 1; -}); - -// Support: IE<8 -// Prevent attribute/property "interpolation" -// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx -if ( !assert(function( div ) { - div.innerHTML = ""; - return div.firstChild.getAttribute("href") === "#" ; -}) ) { - addHandle( "type|href|height|width", function( elem, name, isXML ) { - if ( !isXML ) { - return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 ); - } - }); -} - -// Support: IE<9 -// Use defaultValue in place of getAttribute("value") -if ( !support.attributes || !assert(function( div ) { - div.innerHTML = ""; - div.firstChild.setAttribute( "value", "" ); - return div.firstChild.getAttribute( "value" ) === ""; -}) ) { - addHandle( "value", function( elem, name, isXML ) { - if ( !isXML && elem.nodeName.toLowerCase() === "input" ) { - return elem.defaultValue; - } - }); -} - -// Support: IE<9 -// Use getAttributeNode to fetch booleans when getAttribute lies -if ( !assert(function( div ) { - return div.getAttribute("disabled") == null; -}) ) { - addHandle( booleans, function( elem, name, isXML ) { - var val; - if ( !isXML ) { - return elem[ name ] === true ? name.toLowerCase() : - (val = elem.getAttributeNode( name )) && val.specified ? - val.value : - null; - } - }); -} - -return Sizzle; - -})( window ); - - - -jQuery.find = Sizzle; -jQuery.expr = Sizzle.selectors; -jQuery.expr[":"] = jQuery.expr.pseudos; -jQuery.unique = Sizzle.uniqueSort; -jQuery.text = Sizzle.getText; -jQuery.isXMLDoc = Sizzle.isXML; -jQuery.contains = Sizzle.contains; - - - -var rneedsContext = jQuery.expr.match.needsContext; - -var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/); - - - -var risSimple = /^.[^:#\[\.,]*$/; - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( jQuery.isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - /* jshint -W018 */ - return !!qualifier.call( elem, i, elem ) !== not; - }); - - } - - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - }); - - } - - if ( typeof qualifier === "string" ) { - if ( risSimple.test( qualifier ) ) { - return jQuery.filter( qualifier, elements, not ); - } - - qualifier = jQuery.filter( qualifier, elements ); - } - - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) >= 0 ) !== not; - }); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - return elems.length === 1 && elem.nodeType === 1 ? - jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] : - jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - })); -}; - -jQuery.fn.extend({ - find: function( selector ) { - var i, - len = this.length, - ret = [], - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter(function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - }) ); - } - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - // Needed because $( selector, context ) becomes $( context ).find( selector ) - ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret ); - ret.selector = this.selector ? this.selector + " " + selector : selector; - return ret; - }, - filter: function( selector ) { - return this.pushStack( winnow(this, selector || [], false) ); - }, - not: function( selector ) { - return this.pushStack( winnow(this, selector || [], true) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -}); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (#9521) - // Strict HTML recognition (#11290: must start with <) - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, - - init = jQuery.fn.init = function( selector, context ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) { - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && (match[1] || !context) ) { - - // HANDLE: $(html) -> $(array) - if ( match[1] ) { - context = context instanceof jQuery ? context[0] : context; - - // scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[1], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - // Properties of context are called as methods if possible - if ( jQuery.isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[2] ); - - // Check parentNode to catch when Blackberry 4.6 returns - // nodes that are no longer in the document #6963 - if ( elem && elem.parentNode ) { - // Inject the element directly into the jQuery object - this.length = 1; - this[0] = elem; - } - - this.context = document; - this.selector = selector; - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || rootjQuery ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this.context = this[0] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( jQuery.isFunction( selector ) ) { - return typeof rootjQuery.ready !== "undefined" ? - rootjQuery.ready( selector ) : - // Execute immediately if ready is not present - selector( jQuery ); - } - - if ( selector.selector !== undefined ) { - this.selector = selector.selector; - this.context = selector.context; - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - // methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.extend({ - dir: function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; - }, - - sibling: function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; - } -}); - -jQuery.fn.extend({ - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter(function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[i] ) ) { - return true; - } - } - }); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ? - jQuery( selectors, context || this.context ) : - 0; - - for ( ; i < l; i++ ) { - for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) { - // Always skip document fragments - if ( cur.nodeType < 11 && (pos ? - pos.index(cur) > -1 : - - // Don't pass non-elements to Sizzle - cur.nodeType === 1 && - jQuery.find.matchesSelector(cur, selectors)) ) { - - matched.push( cur ); - break; - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched ); - }, - - // Determine the position of an element within - // the matched set of elements - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.unique( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter(selector) - ); - } -}); - -function sibling( cur, dir ) { - while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each({ - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return jQuery.dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, i, until ) { - return jQuery.dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return jQuery.dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return jQuery.dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, i, until ) { - return jQuery.dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, i, until ) { - return jQuery.dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return jQuery.sibling( elem.firstChild ); - }, - contents: function( elem ) { - return elem.contentDocument || jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.unique( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -}); -var rnotwhite = (/\S+/g); - - - -// String to Object options format cache -var optionsCache = {}; - -// Convert String-formatted options into Object-formatted ones and store in cache -function createOptions( options ) { - var object = optionsCache[ options ] = {}; - jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) { - object[ flag ] = true; - }); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - ( optionsCache[ options ] || createOptions( options ) ) : - jQuery.extend( {}, options ); - - var // Last fire value (for non-forgettable lists) - memory, - // Flag to know if list was already fired - fired, - // Flag to know if list is currently firing - firing, - // First callback to fire (used internally by add and fireWith) - firingStart, - // End of the loop when firing - firingLength, - // Index of currently firing callback (modified by remove if needed) - firingIndex, - // Actual callback list - list = [], - // Stack of fire calls for repeatable lists - stack = !options.once && [], - // Fire callbacks - fire = function( data ) { - memory = options.memory && data; - fired = true; - firingIndex = firingStart || 0; - firingStart = 0; - firingLength = list.length; - firing = true; - for ( ; list && firingIndex < firingLength; firingIndex++ ) { - if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) { - memory = false; // To prevent further calls using add - break; - } - } - firing = false; - if ( list ) { - if ( stack ) { - if ( stack.length ) { - fire( stack.shift() ); - } - } else if ( memory ) { - list = []; - } else { - self.disable(); - } - } - }, - // Actual Callbacks object - self = { - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - // First, we save the current length - var start = list.length; - (function add( args ) { - jQuery.each( args, function( _, arg ) { - var type = jQuery.type( arg ); - if ( type === "function" ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && type !== "string" ) { - // Inspect recursively - add( arg ); - } - }); - })( arguments ); - // Do we need to add the callbacks to the - // current firing batch? - if ( firing ) { - firingLength = list.length; - // With memory, if we're not firing then - // we should call right away - } else if ( memory ) { - firingStart = start; - fire( memory ); - } - } - return this; - }, - // Remove a callback from the list - remove: function() { - if ( list ) { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - // Handle firing indexes - if ( firing ) { - if ( index <= firingLength ) { - firingLength--; - } - if ( index <= firingIndex ) { - firingIndex--; - } - } - } - }); - } - return this; - }, - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length ); - }, - // Remove all callbacks from the list - empty: function() { - list = []; - firingLength = 0; - return this; - }, - // Have the list do nothing anymore - disable: function() { - list = stack = memory = undefined; - return this; - }, - // Is it disabled? - disabled: function() { - return !list; - }, - // Lock the list in its current state - lock: function() { - stack = undefined; - if ( !memory ) { - self.disable(); - } - return this; - }, - // Is it locked? - locked: function() { - return !stack; - }, - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( list && ( !fired || stack ) ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - if ( firing ) { - stack.push( args ); - } else { - fire( args ); - } - } - return this; - }, - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -jQuery.extend({ - - Deferred: function( func ) { - var tuples = [ - // action, add listener, listener list, final state - [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ], - [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ], - [ "notify", "progress", jQuery.Callbacks("memory") ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - then: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - return jQuery.Deferred(function( newDefer ) { - jQuery.each( tuples, function( i, tuple ) { - var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ]; - // deferred[ done | fail | progress ] for forwarding actions to newDefer - deferred[ tuple[1] ](function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && jQuery.isFunction( returned.promise ) ) { - returned.promise() - .done( newDefer.resolve ) - .fail( newDefer.reject ) - .progress( newDefer.notify ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments ); - } - }); - }); - fns = null; - }).promise(); - }, - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Keep pipe for back-compat - promise.pipe = promise.then; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 3 ]; - - // promise[ done | fail | progress ] = list.add - promise[ tuple[1] ] = list.add; - - // Handle state - if ( stateString ) { - list.add(function() { - // state = [ resolved | rejected ] - state = stateString; - - // [ reject_list | resolve_list ].disable; progress_list.lock - }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); - } - - // deferred[ resolve | reject | notify ] - deferred[ tuple[0] ] = function() { - deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments ); - return this; - }; - deferred[ tuple[0] + "With" ] = list.fireWith; - }); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( subordinate /* , ..., subordinateN */ ) { - var i = 0, - resolveValues = slice.call( arguments ), - length = resolveValues.length, - - // the count of uncompleted subordinates - remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0, - - // the master Deferred. If resolveValues consist of only a single Deferred, just use that. - deferred = remaining === 1 ? subordinate : jQuery.Deferred(), - - // Update function for both resolve and progress values - updateFunc = function( i, contexts, values ) { - return function( value ) { - contexts[ i ] = this; - values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( values === progressValues ) { - deferred.notifyWith( contexts, values ); - } else if ( !( --remaining ) ) { - deferred.resolveWith( contexts, values ); - } - }; - }, - - progressValues, progressContexts, resolveContexts; - - // add listeners to Deferred subordinates; treat others as resolved - if ( length > 1 ) { - progressValues = new Array( length ); - progressContexts = new Array( length ); - resolveContexts = new Array( length ); - for ( ; i < length; i++ ) { - if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) { - resolveValues[ i ].promise() - .done( updateFunc( i, resolveContexts, resolveValues ) ) - .fail( deferred.reject ) - .progress( updateFunc( i, progressContexts, progressValues ) ); - } else { - --remaining; - } - } - } - - // if we're not waiting on anything, resolve the master - if ( !remaining ) { - deferred.resolveWith( resolveContexts, resolveValues ); - } - - return deferred.promise(); - } -}); - - -// The deferred used on DOM ready -var readyList; - -jQuery.fn.ready = function( fn ) { - // Add the callback - jQuery.ready.promise().done( fn ); - - return this; -}; - -jQuery.extend({ - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See #6781 - readyWait: 1, - - // Hold (or release) the ready event - holdReady: function( hold ) { - if ( hold ) { - jQuery.readyWait++; - } else { - jQuery.ready( true ); - } - }, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - - // Trigger any bound ready events - if ( jQuery.fn.triggerHandler ) { - jQuery( document ).triggerHandler( "ready" ); - jQuery( document ).off( "ready" ); - } - } -}); - -/** - * The ready event handler and self cleanup method - */ -function completed() { - document.removeEventListener( "DOMContentLoaded", completed, false ); - window.removeEventListener( "load", completed, false ); - jQuery.ready(); -} - -jQuery.ready.promise = function( obj ) { - if ( !readyList ) { - - readyList = jQuery.Deferred(); - - // Catch cases where $(document).ready() is called after the browser event has already occurred. - // we once tried to use readyState "interactive" here, but it caused issues like the one - // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 - if ( document.readyState === "complete" ) { - // Handle it asynchronously to allow scripts the opportunity to delay ready - setTimeout( jQuery.ready ); - - } else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed, false ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed, false ); - } - } - return readyList.promise( obj ); -}; - -// Kick off the DOM ready check even if the user does not -jQuery.ready.promise(); - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( jQuery.type( key ) === "object" ) { - chainable = true; - for ( i in key ) { - jQuery.access( elems, fn, i, key[i], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !jQuery.isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) ); - } - } - } - - return chainable ? - elems : - - // Gets - bulk ? - fn.call( elems ) : - len ? fn( elems[0], key ) : emptyGet; -}; - - -/** - * Determines whether an object can have data - */ -jQuery.acceptData = function( owner ) { - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - /* jshint -W018 */ - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - -function Data() { - // Support: Android < 4, - // Old WebKit does not have Object.preventExtensions/freeze method, - // return new empty object instead with no [[set]] accessor - Object.defineProperty( this.cache = {}, 0, { - get: function() { - return {}; - } - }); - - this.expando = jQuery.expando + Math.random(); -} - -Data.uid = 1; -Data.accepts = jQuery.acceptData; - -Data.prototype = { - key: function( owner ) { - // We can accept data for non-element nodes in modern browsers, - // but we should not, see #8335. - // Always return the key for a frozen object. - if ( !Data.accepts( owner ) ) { - return 0; - } - - var descriptor = {}, - // Check if the owner object already has a cache key - unlock = owner[ this.expando ]; - - // If not, create one - if ( !unlock ) { - unlock = Data.uid++; - - // Secure it in a non-enumerable, non-writable property - try { - descriptor[ this.expando ] = { value: unlock }; - Object.defineProperties( owner, descriptor ); - - // Support: Android < 4 - // Fallback to a less secure definition - } catch ( e ) { - descriptor[ this.expando ] = unlock; - jQuery.extend( owner, descriptor ); - } - } - - // Ensure the cache object - if ( !this.cache[ unlock ] ) { - this.cache[ unlock ] = {}; - } - - return unlock; - }, - set: function( owner, data, value ) { - var prop, - // There may be an unlock assigned to this node, - // if there is no entry for this "owner", create one inline - // and set the unlock as though an owner entry had always existed - unlock = this.key( owner ), - cache = this.cache[ unlock ]; - - // Handle: [ owner, key, value ] args - if ( typeof data === "string" ) { - cache[ data ] = value; - - // Handle: [ owner, { properties } ] args - } else { - // Fresh assignments by object are shallow copied - if ( jQuery.isEmptyObject( cache ) ) { - jQuery.extend( this.cache[ unlock ], data ); - // Otherwise, copy the properties one-by-one to the cache object - } else { - for ( prop in data ) { - cache[ prop ] = data[ prop ]; - } - } - } - return cache; - }, - get: function( owner, key ) { - // Either a valid cache is found, or will be created. - // New caches will be created and the unlock returned, - // allowing direct access to the newly created - // empty data object. A valid owner object must be provided. - var cache = this.cache[ this.key( owner ) ]; - - return key === undefined ? - cache : cache[ key ]; - }, - access: function( owner, key, value ) { - var stored; - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ((key && typeof key === "string") && value === undefined) ) { - - stored = this.get( owner, key ); - - return stored !== undefined ? - stored : this.get( owner, jQuery.camelCase(key) ); - } - - // [*]When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, name, camel, - unlock = this.key( owner ), - cache = this.cache[ unlock ]; - - if ( key === undefined ) { - this.cache[ unlock ] = {}; - - } else { - // Support array or space separated string of keys - if ( jQuery.isArray( key ) ) { - // If "name" is an array of keys... - // When data is initially created, via ("key", "val") signature, - // keys will be converted to camelCase. - // Since there is no way to tell _how_ a key was added, remove - // both plain key and camelCase key. #12786 - // This will only penalize the array argument path. - name = key.concat( key.map( jQuery.camelCase ) ); - } else { - camel = jQuery.camelCase( key ); - // Try the string as a key before any manipulation - if ( key in cache ) { - name = [ key, camel ]; - } else { - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - name = camel; - name = name in cache ? - [ name ] : ( name.match( rnotwhite ) || [] ); - } - } - - i = name.length; - while ( i-- ) { - delete cache[ name[ i ] ]; - } - } - }, - hasData: function( owner ) { - return !jQuery.isEmptyObject( - this.cache[ owner[ this.expando ] ] || {} - ); - }, - discard: function( owner ) { - if ( owner[ this.expando ] ) { - delete this.cache[ owner[ this.expando ] ]; - } - } -}; -var data_priv = new Data(); - -var data_user = new Data(); - - - -/* - Implementation Summary - - 1. Enforce API surface and semantic compatibility with 1.9.x branch - 2. Improve the module's maintainability by reducing the storage - paths to a single mechanism. - 3. Use the same single mechanism to support "private" and "user" data. - 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) - 5. Avoid exposing implementation details on user objects (eg. expando properties) - 6. Provide a clear path for implementation upgrade to WeakMap in 2014 -*/ -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /([A-Z])/g; - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = data === "true" ? true : - data === "false" ? false : - data === "null" ? null : - // Only convert to a number if it doesn't change the string - +data + "" === data ? +data : - rbrace.test( data ) ? jQuery.parseJSON( data ) : - data; - } catch( e ) {} - - // Make sure we set the data so it isn't changed later - data_user.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend({ - hasData: function( elem ) { - return data_user.hasData( elem ) || data_priv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return data_user.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - data_user.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to data_priv methods, these can be deprecated. - _data: function( elem, name, data ) { - return data_priv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - data_priv.remove( elem, name ); - } -}); - -jQuery.fn.extend({ - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = data_user.get( elem ); - - if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE11+ - // The attrs elements can be null (#14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = jQuery.camelCase( name.slice(5) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - data_priv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each(function() { - data_user.set( this, key ); - }); - } - - return access( this, function( value ) { - var data, - camelKey = jQuery.camelCase( key ); - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - // Attempt to get data from the cache - // with the key as-is - data = data_user.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to get data from the cache - // with the key camelized - data = data_user.get( elem, camelKey ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, camelKey, undefined ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each(function() { - // First, attempt to store a copy or reference of any - // data that might've been store with a camelCased key. - var data = data_user.get( this, camelKey ); - - // For HTML5 data-* attribute interop, we have to - // store property names with dashes in a camelCase form. - // This might not apply to all properties...* - data_user.set( this, camelKey, value ); - - // *... In the case of properties that might _actually_ - // have dashes, we need to also store a copy of that - // unchanged property. - if ( key.indexOf("-") !== -1 && data !== undefined ) { - data_user.set( this, key, value ); - } - }); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each(function() { - data_user.remove( this, key ); - }); - } -}); - - -jQuery.extend({ - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = data_priv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || jQuery.isArray( data ) ) { - queue = data_priv.access( elem, type, jQuery.makeArray(data) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // not intended for public consumption - generates a queueHooks object, or returns the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return data_priv.get( elem, key ) || data_priv.access( elem, key, { - empty: jQuery.Callbacks("once memory").add(function() { - data_priv.remove( elem, [ type + "queue", key ] ); - }) - }); - } -}); - -jQuery.fn.extend({ - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[0], type ); - } - - return data === undefined ? - this : - this.each(function() { - var queue = jQuery.queue( this, type, data ); - - // ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[0] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - }); - }, - dequeue: function( type ) { - return this.each(function() { - jQuery.dequeue( this, type ); - }); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = data_priv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -}); -var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source; - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var isHidden = function( elem, el ) { - // isHidden might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem ); - }; - -var rcheckableType = (/^(?:checkbox|radio)$/i); - - - -(function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // #11217 - WebKit loses check when the name is after the checked attribute - // Support: Windows Web Apps (WWA) - // `name` and `type` need .setAttribute for WWA - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Safari 5.1, iOS 5.1, Android 4.x, Android 2.3 - // old WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Make sure textarea (and checkbox) defaultValue is properly cloned - // Support: IE9-IE11+ - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; -})(); -var strundefined = typeof undefined; - - - -support.focusinBubbles = "onfocusin" in window; - - -var - rkeyEvent = /^key/, - rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/, - rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - rtypenamespace = /^([^.]*)(?:\.(.+)|)$/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = data_priv.get( elem ); - - // Don't attach events to noData or text/comment nodes (but allow plain objects) - if ( !elemData ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !(events = elemData.events) ) { - events = elemData.events = {}; - } - if ( !(eventHandle = elemData.handle) ) { - eventHandle = elemData.handle = function( e ) { - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend({ - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join(".") - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !(handlers = events[ type ]) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle, false ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = data_priv.hasData( elem ) && data_priv.get( elem ); - - if ( !elemData || !(events = elemData.events) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnotwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[t] ) || []; - type = origType = tmp[1]; - namespaces = ( tmp[2] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - delete elemData.handle; - data_priv.remove( elem, "events" ); - } - }, - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : []; - - cur = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf(".") >= 0 ) { - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split("."); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf(":") < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join("."); - event.namespace_re = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (#9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (#9724) - if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === (elem.ownerDocument || document) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) { - - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && jQuery.acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) && - jQuery.acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name name as the event. - // Don't do default actions on window, that's where global variables be (#6170) - if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - elem[ type ](); - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - dispatch: function( event ) { - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( event ); - - var i, j, ret, matched, handleObj, - handlerQueue = [], - args = slice.call( arguments ), - handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[0] = event; - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) { - - // Triggered event must either 1) have no namespace, or - // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace). - if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) - .apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( (event.result = ret) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, matches, sel, handleObj, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - // Black-hole SVG instance trees (#13180) - // Avoid non-left-click bubbling in Firefox (#3861) - if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't process clicks on disabled elements (#6911, #8165, #11382, #11764) - if ( cur.disabled !== true || event.type !== "click" ) { - matches = []; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (#13203) - sel = handleObj.selector + " "; - - if ( matches[ sel ] === undefined ) { - matches[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) >= 0 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matches[ sel ] ) { - matches.push( handleObj ); - } - } - if ( matches.length ) { - handlerQueue.push({ elem: cur, handlers: matches }); - } - } - } - } - - // Add the remaining (directly-bound) handlers - if ( delegateCount < handlers.length ) { - handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) }); - } - - return handlerQueue; - }, - - // Includes some event props shared by KeyEvent and MouseEvent - props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), - - fixHooks: {}, - - keyHooks: { - props: "char charCode key keyCode".split(" "), - filter: function( event, original ) { - - // Add which for key events - if ( event.which == null ) { - event.which = original.charCode != null ? original.charCode : original.keyCode; - } - - return event; - } - }, - - mouseHooks: { - props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), - filter: function( event, original ) { - var eventDoc, doc, body, - button = original.button; - - // Calculate pageX/Y if missing and clientX/Y available - if ( event.pageX == null && original.clientX != null ) { - eventDoc = event.target.ownerDocument || document; - doc = eventDoc.documentElement; - body = eventDoc.body; - - event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); - event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); - } - - // Add which for click: 1 === left; 2 === middle; 3 === right - // Note: button is not normalized, so don't use it - if ( !event.which && button !== undefined ) { - event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); - } - - return event; - } - }, - - fix: function( event ) { - if ( event[ jQuery.expando ] ) { - return event; - } - - // Create a writable copy of the event object and normalize some properties - var i, prop, copy, - type = event.type, - originalEvent = event, - fixHook = this.fixHooks[ type ]; - - if ( !fixHook ) { - this.fixHooks[ type ] = fixHook = - rmouseEvent.test( type ) ? this.mouseHooks : - rkeyEvent.test( type ) ? this.keyHooks : - {}; - } - copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; - - event = new jQuery.Event( originalEvent ); - - i = copy.length; - while ( i-- ) { - prop = copy[ i ]; - event[ prop ] = originalEvent[ prop ]; - } - - // Support: Cordova 2.5 (WebKit) (#13255) - // All events should have a target; Cordova deviceready doesn't - if ( !event.target ) { - event.target = document; - } - - // Support: Safari 6.0+, Chrome < 28 - // Target should not be a text node (#504, #13143) - if ( event.target.nodeType === 3 ) { - event.target = event.target.parentNode; - } - - return fixHook.filter ? fixHook.filter( event, originalEvent ) : event; - }, - - special: { - load: { - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - focus: { - // Fire native event if possible so blur/focus sequence is correct - trigger: function() { - if ( this !== safeActiveElement() && this.focus ) { - this.focus(); - return false; - } - }, - delegateType: "focusin" - }, - blur: { - trigger: function() { - if ( this === safeActiveElement() && this.blur ) { - this.blur(); - return false; - } - }, - delegateType: "focusout" - }, - click: { - // For checkbox, fire native event so checked state will be right - trigger: function() { - if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) { - this.click(); - return false; - } - }, - - // For cross-browser consistency, don't fire native .click() on links - _default: function( event ) { - return jQuery.nodeName( event.target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - }, - - simulate: function( type, elem, event, bubble ) { - // Piggyback on a donor event to simulate a different one. - // Fake originalEvent to avoid donor's stopPropagation, but if the - // simulated event prevents default then we do the same on the donor. - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true, - originalEvent: {} - } - ); - if ( bubble ) { - jQuery.event.trigger( e, null, elem ); - } else { - jQuery.event.dispatch.call( elem, e ); - } - if ( e.isDefaultPrevented() ) { - event.preventDefault(); - } - } -}; - -jQuery.removeEvent = function( elem, type, handle ) { - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle, false ); - } -}; - -jQuery.Event = function( src, props ) { - // Allow instantiation without the 'new' keyword - if ( !(this instanceof jQuery.Event) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - // Support: Android < 4.0 - src.returnValue === false ? - returnTrue : - returnFalse; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || jQuery.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && e.preventDefault ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && e.stopPropagation ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && e.stopImmediatePropagation ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Create mouseenter/leave events using mouseover/out and event-time checks -// Support: Chrome 15+ -jQuery.each({ - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mousenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || (related !== target && !jQuery.contains( target, related )) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -}); - -// Create "bubbling" focus and blur events -// Support: Firefox, Chrome, Safari -if ( !support.focusinBubbles ) { - jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { - - // Attach a single capturing handler on the document while someone wants focusin/focusout - var handler = function( event ) { - jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); - }; - - jQuery.event.special[ fix ] = { - setup: function() { - var doc = this.ownerDocument || this, - attaches = data_priv.access( doc, fix ); - - if ( !attaches ) { - doc.addEventListener( orig, handler, true ); - } - data_priv.access( doc, fix, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this, - attaches = data_priv.access( doc, fix ) - 1; - - if ( !attaches ) { - doc.removeEventListener( orig, handler, true ); - data_priv.remove( doc, fix ); - - } else { - data_priv.access( doc, fix, attaches ); - } - } - }; - }); -} - -jQuery.fn.extend({ - - on: function( types, selector, data, fn, /*INTERNAL*/ one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - this.on( type, selector, data, types[ type ], one ); - } - return this; - } - - if ( data == null && fn == null ) { - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return this; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return this.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - }); - }, - one: function( types, selector, data, fn ) { - return this.on( types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each(function() { - jQuery.event.remove( this, types, fn, selector ); - }); - }, - - trigger: function( type, data ) { - return this.each(function() { - jQuery.event.trigger( type, data, this ); - }); - }, - triggerHandler: function( type, data ) { - var elem = this[0]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -}); - - -var - rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, - rtagName = /<([\w:]+)/, - rhtml = /<|&#?\w+;/, - rnoInnerhtml = /<(?:script|style|link)/i, - // checked="checked" or checked - rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, - rscriptType = /^$|\/(?:java|ecma)script/i, - rscriptTypeMasked = /^true\/(.*)/, - rcleanScript = /^\s*\s*$/g, - - // We have to close these tags to support XHTML (#13200) - wrapMap = { - - // Support: IE 9 - option: [ 1, "" ], - - thead: [ 1, "", "
    " ], - col: [ 2, "", "
    " ], - tr: [ 2, "", "
    " ], - td: [ 3, "", "
    " ], - - _default: [ 0, "", "" ] - }; - -// Support: IE 9 -wrapMap.optgroup = wrapMap.option; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: 1.x compatibility -// Manipulating tables requires a tbody -function manipulationTarget( elem, content ) { - return jQuery.nodeName( elem, "table" ) && - jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ? - - elem.getElementsByTagName("tbody")[0] || - elem.appendChild( elem.ownerDocument.createElement("tbody") ) : - elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - var match = rscriptTypeMasked.exec( elem.type ); - - if ( match ) { - elem.type = match[ 1 ]; - } else { - elem.removeAttribute("type"); - } - - return elem; -} - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - data_priv.set( - elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" ) - ); - } -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( data_priv.hasData( src ) ) { - pdataOld = data_priv.access( src ); - pdataCur = data_priv.set( dest, pdataOld ); - events = pdataOld.events; - - if ( events ) { - delete pdataCur.handle; - pdataCur.events = {}; - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( data_user.hasData( src ) ) { - udataOld = data_user.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - data_user.set( dest, udataCur ); - } -} - -function getAll( context, tag ) { - var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) : - context.querySelectorAll ? context.querySelectorAll( tag || "*" ) : - []; - - return tag === undefined || tag && jQuery.nodeName( context, tag ) ? - jQuery.merge( [ context ], ret ) : - ret; -} - -// Support: IE >= 9 -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -jQuery.extend({ - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = jQuery.contains( elem.ownerDocument, elem ); - - // Support: IE >= 9 - // Fix Cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - buildFragment: function( elems, context, scripts, selection ) { - var elem, tmp, tag, wrap, contains, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( jQuery.type( elem ) === "object" ) { - // Support: QtWebKit - // jQuery.merge because push.apply(_, arraylike) throws - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement("div") ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1>" ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: QtWebKit - // jQuery.merge because push.apply(_, arraylike) throws - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Fixes #12346 - // Support: Webkit, IE - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( (elem = nodes[ i++ ]) ) { - - // #4087 - If origin and destination elements are the same, and this is - // that element, do not do anything - if ( selection && jQuery.inArray( elem, selection ) !== -1 ) { - continue; - } - - contains = jQuery.contains( elem.ownerDocument, elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( contains ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( (elem = tmp[ j++ ]) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; - }, - - cleanData: function( elems ) { - var data, elem, type, key, - special = jQuery.event.special, - i = 0; - - for ( ; (elem = elems[ i ]) !== undefined; i++ ) { - if ( jQuery.acceptData( elem ) ) { - key = elem[ data_priv.expando ]; - - if ( key && (data = data_priv.cache[ key ]) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - if ( data_priv.cache[ key ] ) { - // Discard any remaining `private` data - delete data_priv.cache[ key ]; - } - } - } - // Discard any remaining `user` data - delete data_user.cache[ elem[ data_user.expando ] ]; - } - } -}); - -jQuery.fn.extend({ - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each(function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - }); - }, null, value, arguments.length ); - }, - - append: function() { - return this.domManip( arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - }); - }, - - prepend: function() { - return this.domManip( arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - }); - }, - - before: function() { - return this.domManip( arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - }); - }, - - after: function() { - return this.domManip( arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - }); - }, - - remove: function( selector, keepData /* Internal Use Only */ ) { - var elem, - elems = selector ? jQuery.filter( selector, this ) : this, - i = 0; - - for ( ; (elem = elems[i]) != null; i++ ) { - if ( !keepData && elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem ) ); - } - - if ( elem.parentNode ) { - if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) { - setGlobalEval( getAll( elem, "script" ) ); - } - elem.parentNode.removeChild( elem ); - } - } - - return this; - }, - - empty: function() { - var elem, - i = 0; - - for ( ; (elem = this[i]) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map(function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - }); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = value.replace( rxhtmlTag, "<$1>" ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var arg = arguments[ 0 ]; - - // Make the changes, replacing each context element with the new content - this.domManip( arguments, function( elem ) { - arg = this.parentNode; - - jQuery.cleanData( getAll( this ) ); - - if ( arg ) { - arg.replaceChild( elem, this ); - } - }); - - // Force removal if there was no new content (e.g., from empty arguments) - return arg && (arg.length || arg.nodeType) ? this : this.remove(); - }, - - detach: function( selector ) { - return this.remove( selector, true ); - }, - - domManip: function( args, callback ) { - - // Flatten any nested arrays - args = concat.apply( [], args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = this.length, - set = this, - iNoClone = l - 1, - value = args[ 0 ], - isFunction = jQuery.isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( isFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return this.each(function( index ) { - var self = set.eq( index ); - if ( isFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - self.domManip( args, callback ); - }); - } - - if ( l ) { - fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - if ( first ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item instead of the first because it can end up - // being emptied incorrectly in certain situations (#8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - // Support: QtWebKit - // jQuery.merge because push.apply(_, arraylike) throws - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( this[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Reenable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) { - - if ( node.src ) { - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl ) { - jQuery._evalUrl( node.src ); - } - } else { - jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) ); - } - } - } - } - } - } - - return this; - } -}); - -jQuery.each({ - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: QtWebKit - // .get() because push.apply(_, arraylike) throws - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -}); - - -var iframe, - elemdisplay = {}; - -/** - * Retrieve the actual display of a element - * @param {String} name nodeName of the element - * @param {Object} doc Document object - */ -// Called only from within defaultDisplay -function actualDisplay( name, doc ) { - var style, - elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ), - - // getDefaultComputedStyle might be reliably used only on attached element - display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ? - - // Use of this method is a temporary fix (more like optmization) until something better comes along, - // since it was removed from specification and supported only in FF - style.display : jQuery.css( elem[ 0 ], "display" ); - - // We don't have any data stored on the element, - // so use "detach" method as fast way to get rid of the element - elem.detach(); - - return display; -} - -/** - * Try to determine the default display value of an element - * @param {String} nodeName - */ -function defaultDisplay( nodeName ) { - var doc = document, - display = elemdisplay[ nodeName ]; - - if ( !display ) { - display = actualDisplay( nodeName, doc ); - - // If the simple way fails, read from inside an iframe - if ( display === "none" || !display ) { - - // Use the already-created iframe if possible - iframe = (iframe || jQuery( "