diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/100.json b/.changelog/github/repos/bolt-design-system/bolt/issues/100.json new file mode 100644 index 0000000000..0cd8be4941 --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/100.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/100", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/100/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/100/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/100/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/100", + "id": 251768234, + "number": 100, + "title": "docs: font-family readme doc update", + "user": { + "login": "mikemai2awesome", + "id": 3027663, + "avatar_url": "https://avatars2.githubusercontent.com/u/3027663?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mikemai2awesome", + "html_url": "https://github.com/mikemai2awesome", + "followers_url": "https://api.github.com/users/mikemai2awesome/followers", + "following_url": "https://api.github.com/users/mikemai2awesome/following{/other_user}", + "gists_url": "https://api.github.com/users/mikemai2awesome/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mikemai2awesome/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mikemai2awesome/subscriptions", + "organizations_url": "https://api.github.com/users/mikemai2awesome/orgs", + "repos_url": "https://api.github.com/users/mikemai2awesome/repos", + "events_url": "https://api.github.com/users/mikemai2awesome/events{/privacy}", + "received_events_url": "https://api.github.com/users/mikemai2awesome/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2017-08-21T20:54:04Z", + "updated_at": "2017-08-22T14:20:32Z", + "closed_at": "2017-08-21T21:38:03Z", + "author_association": "COLLABORATOR", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/100", + "html_url": "https://github.com/bolt-design-system/bolt/pull/100", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/100.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/100.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/102.json b/.changelog/github/repos/bolt-design-system/bolt/issues/102.json new file mode 100644 index 0000000000..118678eac7 --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/102.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/102", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/102/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/102/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/102/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/102", + "id": 251786289, + "number": 102, + "title": "Feature/settings breakpoints docs", + "user": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2017-08-21T22:19:33Z", + "updated_at": "2017-08-22T14:28:37Z", + "closed_at": "2017-08-22T14:28:37Z", + "author_association": "OWNER", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/102", + "html_url": "https://github.com/bolt-design-system/bolt/pull/102", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/102.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/102.patch" + }, + "body": "https://github.com/bolt-design-system/bolt/blob/5c012eb81363d2b4a88890a028863786e1ba2239/packages/bolt-core/01-settings/settings-breakpoints/README.md\r\n\r\nCc @mikemai2awesome @theSadowski ", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/104.json b/.changelog/github/repos/bolt-design-system/bolt/issues/104.json new file mode 100644 index 0000000000..f912d8f029 --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/104.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/104", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/104/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/104/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/104/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/104", + "id": 252005155, + "number": 104, + "title": "Feature/spacing scale docs", + "user": { + "login": "mikemai2awesome", + "id": 3027663, + "avatar_url": "https://avatars2.githubusercontent.com/u/3027663?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mikemai2awesome", + "html_url": "https://github.com/mikemai2awesome", + "followers_url": "https://api.github.com/users/mikemai2awesome/followers", + "following_url": "https://api.github.com/users/mikemai2awesome/following{/other_user}", + "gists_url": "https://api.github.com/users/mikemai2awesome/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mikemai2awesome/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mikemai2awesome/subscriptions", + "organizations_url": "https://api.github.com/users/mikemai2awesome/orgs", + "repos_url": "https://api.github.com/users/mikemai2awesome/repos", + "events_url": "https://api.github.com/users/mikemai2awesome/events{/privacy}", + "received_events_url": "https://api.github.com/users/mikemai2awesome/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2017-08-22T15:51:42Z", + "updated_at": "2017-08-28T22:35:24Z", + "closed_at": "2017-08-28T22:35:23Z", + "author_association": "COLLABORATOR", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/104", + "html_url": "https://github.com/bolt-design-system/bolt/pull/104", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/104.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/104.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/105.json b/.changelog/github/repos/bolt-design-system/bolt/issues/105.json new file mode 100644 index 0000000000..dd6f0baf09 --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/105.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/105", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/105/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/105/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/105/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/105", + "id": 252042346, + "number": 105, + "title": "Feature/tools font family docs", + "user": { + "login": "mikemai2awesome", + "id": 3027663, + "avatar_url": "https://avatars2.githubusercontent.com/u/3027663?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mikemai2awesome", + "html_url": "https://github.com/mikemai2awesome", + "followers_url": "https://api.github.com/users/mikemai2awesome/followers", + "following_url": "https://api.github.com/users/mikemai2awesome/following{/other_user}", + "gists_url": "https://api.github.com/users/mikemai2awesome/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mikemai2awesome/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mikemai2awesome/subscriptions", + "organizations_url": "https://api.github.com/users/mikemai2awesome/orgs", + "repos_url": "https://api.github.com/users/mikemai2awesome/repos", + "events_url": "https://api.github.com/users/mikemai2awesome/events{/privacy}", + "received_events_url": "https://api.github.com/users/mikemai2awesome/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2017-08-22T18:06:20Z", + "updated_at": "2017-08-28T22:35:41Z", + "closed_at": "2017-08-28T22:35:41Z", + "author_association": "COLLABORATOR", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/105", + "html_url": "https://github.com/bolt-design-system/bolt/pull/105", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/105.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/105.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/106.json b/.changelog/github/repos/bolt-design-system/bolt/issues/106.json new file mode 100644 index 0000000000..0c93688f28 --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/106.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/106", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/106/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/106/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/106/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/106", + "id": 252055720, + "number": 106, + "title": "Feature/tools font kerning docs", + "user": { + "login": "mikemai2awesome", + "id": 3027663, + "avatar_url": "https://avatars2.githubusercontent.com/u/3027663?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mikemai2awesome", + "html_url": "https://github.com/mikemai2awesome", + "followers_url": "https://api.github.com/users/mikemai2awesome/followers", + "following_url": "https://api.github.com/users/mikemai2awesome/following{/other_user}", + "gists_url": "https://api.github.com/users/mikemai2awesome/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mikemai2awesome/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mikemai2awesome/subscriptions", + "organizations_url": "https://api.github.com/users/mikemai2awesome/orgs", + "repos_url": "https://api.github.com/users/mikemai2awesome/repos", + "events_url": "https://api.github.com/users/mikemai2awesome/events{/privacy}", + "received_events_url": "https://api.github.com/users/mikemai2awesome/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2017-08-22T18:56:16Z", + "updated_at": "2017-08-28T22:35:55Z", + "closed_at": "2017-08-28T22:35:55Z", + "author_association": "COLLABORATOR", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/106", + "html_url": "https://github.com/bolt-design-system/bolt/pull/106", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/106.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/106.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/107.json b/.changelog/github/repos/bolt-design-system/bolt/issues/107.json new file mode 100644 index 0000000000..c6a3af680a --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/107.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/107", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/107/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/107/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/107/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/107", + "id": 252058593, + "number": 107, + "title": "Feature/tools font weight docs", + "user": { + "login": "mikemai2awesome", + "id": 3027663, + "avatar_url": "https://avatars2.githubusercontent.com/u/3027663?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mikemai2awesome", + "html_url": "https://github.com/mikemai2awesome", + "followers_url": "https://api.github.com/users/mikemai2awesome/followers", + "following_url": "https://api.github.com/users/mikemai2awesome/following{/other_user}", + "gists_url": "https://api.github.com/users/mikemai2awesome/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mikemai2awesome/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mikemai2awesome/subscriptions", + "organizations_url": "https://api.github.com/users/mikemai2awesome/orgs", + "repos_url": "https://api.github.com/users/mikemai2awesome/repos", + "events_url": "https://api.github.com/users/mikemai2awesome/events{/privacy}", + "received_events_url": "https://api.github.com/users/mikemai2awesome/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2017-08-22T19:07:25Z", + "updated_at": "2017-08-22T20:47:10Z", + "closed_at": "2017-08-22T20:47:10Z", + "author_association": "COLLABORATOR", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/107", + "html_url": "https://github.com/bolt-design-system/bolt/pull/107", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/107.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/107.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/111.json b/.changelog/github/repos/bolt-design-system/bolt/issues/111.json new file mode 100644 index 0000000000..df31543639 --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/111.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/111", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/111/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/111/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/111/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/111", + "id": 253434819, + "number": 111, + "title": "elements-headings, settings-global, generic-reset, generic-shared: build and docs", + "user": { + "login": "mikemai2awesome", + "id": 3027663, + "avatar_url": "https://avatars2.githubusercontent.com/u/3027663?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mikemai2awesome", + "html_url": "https://github.com/mikemai2awesome", + "followers_url": "https://api.github.com/users/mikemai2awesome/followers", + "following_url": "https://api.github.com/users/mikemai2awesome/following{/other_user}", + "gists_url": "https://api.github.com/users/mikemai2awesome/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mikemai2awesome/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mikemai2awesome/subscriptions", + "organizations_url": "https://api.github.com/users/mikemai2awesome/orgs", + "repos_url": "https://api.github.com/users/mikemai2awesome/repos", + "events_url": "https://api.github.com/users/mikemai2awesome/events{/privacy}", + "received_events_url": "https://api.github.com/users/mikemai2awesome/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 1, + "created_at": "2017-08-28T19:45:04Z", + "updated_at": "2017-08-29T19:28:28Z", + "closed_at": "2017-08-29T19:27:51Z", + "author_association": "COLLABORATOR", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/111", + "html_url": "https://github.com/bolt-design-system/bolt/pull/111", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/111.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/111.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/112.json b/.changelog/github/repos/bolt-design-system/bolt/issues/112.json new file mode 100644 index 0000000000..145ec01204 --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/112.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/112", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/112/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/112/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/112/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/112", + "id": 253776982, + "number": 112, + "title": "Merging into Develop for v0.2 Release", + "user": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 0, + "created_at": "2017-08-29T19:29:19Z", + "updated_at": "2017-08-29T21:03:34Z", + "closed_at": "2017-08-29T21:03:34Z", + "author_association": "OWNER", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/112", + "html_url": "https://github.com/bolt-design-system/bolt/pull/112", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/112.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/112.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/96.json b/.changelog/github/repos/bolt-design-system/bolt/issues/96.json new file mode 100644 index 0000000000..a724b6f67f --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/96.json @@ -0,0 +1,109 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/96", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/96/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/96/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/96/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/96", + "id": 248859905, + "number": 96, + "title": "Adding POC Detailed Documentation for Font Sizes", + "user": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + }, + "labels": [ + { + "id": 626834834, + "url": "https://api.github.com/repos/bolt-design-system/bolt/labels/Documentation", + "name": "Documentation", + "color": "09929d", + "default": false + } + ], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/milestones/2", + "html_url": "https://github.com/bolt-design-system/bolt/milestone/2", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/milestones/2/labels", + "id": 2584062, + "number": 2, + "title": "v0.2", + "description": null, + "creator": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + }, + "open_issues": 0, + "closed_issues": 1, + "state": "open", + "created_at": "2017-06-15T01:34:40Z", + "updated_at": "2017-08-22T12:14:20Z", + "due_on": null, + "closed_at": null + }, + "comments": 9, + "created_at": "2017-08-08T22:02:50Z", + "updated_at": "2017-08-22T14:20:51Z", + "closed_at": "2017-08-22T12:14:20Z", + "author_association": "OWNER", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/96", + "html_url": "https://github.com/bolt-design-system/bolt/pull/96", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/96.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/96.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/97.json b/.changelog/github/repos/bolt-design-system/bolt/issues/97.json new file mode 100644 index 0000000000..592b1afe95 --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/97.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/97", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/97/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/97/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/97/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/97", + "id": 250039223, + "number": 97, + "title": "Create ITCSS vs Atomic Design.md", + "user": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 7, + "created_at": "2017-08-14T13:59:08Z", + "updated_at": "2017-08-22T12:25:39Z", + "closed_at": "2017-08-22T12:25:24Z", + "author_association": "OWNER", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/97", + "html_url": "https://github.com/bolt-design-system/bolt/pull/97", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/97.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/97.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/repos/bolt-design-system/bolt/issues/99.json b/.changelog/github/repos/bolt-design-system/bolt/issues/99.json new file mode 100644 index 0000000000..80bd6db65e --- /dev/null +++ b/.changelog/github/repos/bolt-design-system/bolt/issues/99.json @@ -0,0 +1,67 @@ +{ + "url": "https://api.github.com/repos/bolt-design-system/bolt/issues/99", + "repository_url": "https://api.github.com/repos/bolt-design-system/bolt", + "labels_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/99/labels{/name}", + "comments_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/99/comments", + "events_url": "https://api.github.com/repos/bolt-design-system/bolt/issues/99/events", + "html_url": "https://github.com/bolt-design-system/bolt/pull/99", + "id": 251768109, + "number": 99, + "title": "docs: font weight readme update", + "user": { + "login": "mikemai2awesome", + "id": 3027663, + "avatar_url": "https://avatars2.githubusercontent.com/u/3027663?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mikemai2awesome", + "html_url": "https://github.com/mikemai2awesome", + "followers_url": "https://api.github.com/users/mikemai2awesome/followers", + "following_url": "https://api.github.com/users/mikemai2awesome/following{/other_user}", + "gists_url": "https://api.github.com/users/mikemai2awesome/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mikemai2awesome/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mikemai2awesome/subscriptions", + "organizations_url": "https://api.github.com/users/mikemai2awesome/orgs", + "repos_url": "https://api.github.com/users/mikemai2awesome/repos", + "events_url": "https://api.github.com/users/mikemai2awesome/events{/privacy}", + "received_events_url": "https://api.github.com/users/mikemai2awesome/received_events", + "type": "User", + "site_admin": false + }, + "labels": [], + "state": "closed", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2017-08-21T20:53:25Z", + "updated_at": "2017-08-21T21:49:37Z", + "closed_at": "2017-08-21T21:49:31Z", + "author_association": "COLLABORATOR", + "pull_request": { + "url": "https://api.github.com/repos/bolt-design-system/bolt/pulls/99", + "html_url": "https://github.com/bolt-design-system/bolt/pull/99", + "diff_url": "https://github.com/bolt-design-system/bolt/pull/99.diff", + "patch_url": "https://github.com/bolt-design-system/bolt/pull/99.patch" + }, + "body": "", + "closed_by": { + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false + } +} diff --git a/.changelog/github/users/mikemai2awesome.json b/.changelog/github/users/mikemai2awesome.json new file mode 100644 index 0000000000..84dca174bd --- /dev/null +++ b/.changelog/github/users/mikemai2awesome.json @@ -0,0 +1,32 @@ +{ + "login": "mikemai2awesome", + "id": 3027663, + "avatar_url": "https://avatars2.githubusercontent.com/u/3027663?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/mikemai2awesome", + "html_url": "https://github.com/mikemai2awesome", + "followers_url": "https://api.github.com/users/mikemai2awesome/followers", + "following_url": "https://api.github.com/users/mikemai2awesome/following{/other_user}", + "gists_url": "https://api.github.com/users/mikemai2awesome/gists{/gist_id}", + "starred_url": "https://api.github.com/users/mikemai2awesome/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/mikemai2awesome/subscriptions", + "organizations_url": "https://api.github.com/users/mikemai2awesome/orgs", + "repos_url": "https://api.github.com/users/mikemai2awesome/repos", + "events_url": "https://api.github.com/users/mikemai2awesome/events{/privacy}", + "received_events_url": "https://api.github.com/users/mikemai2awesome/received_events", + "type": "User", + "site_admin": false, + "name": "Mike Mai", + "company": "Mike Mai Network", + "blog": "http://mikemai.net", + "location": "Boston, MA", + "email": "boss@mikemai.net", + "hireable": null, + "bio": null, + "public_repos": 0, + "public_gists": 2, + "followers": 0, + "following": 0, + "created_at": "2012-12-12T18:37:55Z", + "updated_at": "2017-08-01T00:46:56Z" +} diff --git a/.changelog/github/users/sghoweri.json b/.changelog/github/users/sghoweri.json new file mode 100644 index 0000000000..84a540d50f --- /dev/null +++ b/.changelog/github/users/sghoweri.json @@ -0,0 +1,32 @@ +{ + "login": "sghoweri", + "id": 1617209, + "avatar_url": "https://avatars2.githubusercontent.com/u/1617209?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/sghoweri", + "html_url": "https://github.com/sghoweri", + "followers_url": "https://api.github.com/users/sghoweri/followers", + "following_url": "https://api.github.com/users/sghoweri/following{/other_user}", + "gists_url": "https://api.github.com/users/sghoweri/gists{/gist_id}", + "starred_url": "https://api.github.com/users/sghoweri/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/sghoweri/subscriptions", + "organizations_url": "https://api.github.com/users/sghoweri/orgs", + "repos_url": "https://api.github.com/users/sghoweri/repos", + "events_url": "https://api.github.com/users/sghoweri/events{/privacy}", + "received_events_url": "https://api.github.com/users/sghoweri/received_events", + "type": "User", + "site_admin": false, + "name": "Salem", + "company": "Pegasystems", + "blog": "salemghoweri.com", + "location": "Boston, MA", + "email": "me@salemghoweri.com", + "hireable": null, + "bio": "Senior Front-end Architect, Design Systems at Pega. \r\nPattern Lab, Design Systems, CSS Architecture, Twig, and Web Performance nut.", + "public_repos": 90, + "public_gists": 3, + "followers": 11, + "following": 2, + "created_at": "2012-04-06T00:49:25Z", + "updated_at": "2017-08-12T09:59:24Z" +} diff --git a/.eslintignore b/.eslintignore index 26e3e97aec..55116fa853 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,7 +2,7 @@ **/vendor/** **/sandbox/** /www/** -/example-integrations/drupal-lab/** +/apps/drupal-lab/** /apps/pw-site/www/** packages/uikit-workshop/** **/dist/** diff --git a/.gitignore b/.gitignore index 4360bd5965..244e60b4b9 100755 --- a/.gitignore +++ b/.gitignore @@ -16,9 +16,6 @@ node_modules bower_components vendor -# nightwatch.js test results when run locally -/tests_output/** - /www # Logs diff --git a/.travis.yml b/.travis.yml index bb6306eb8f..bc3801ea30 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,7 @@ addons: before_install: - nvm install # version lifted from `.nvmrc` -- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.9.4 +- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.7.0 - export PATH="$HOME/.yarn/bin:$PATH" before_script: @@ -26,7 +26,7 @@ before_script: # Can't do deploy on `after_success` b/c if deploy fails, the build still reports success. Can't use `deploy` step b/c Travis skips that on PRs. script: - bash ./travis.sh # time command already run inside the travis.sh script -- npx nightwatch@0.9.20 --config nightwatch.js --env chrome,ie11 +- npx nightwatch --config nightwatch.js --env chrome,ie11 cache: apt: true diff --git a/LICENSE b/LICENSE index 2c0cbe094c..88a4dd3c57 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2018 Pegasystems +Copyright (c) 2017 Pegasystems Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/apps/bolt-site/.boltrc.js b/apps/bolt-site/.boltrc.js index 83a98037e8..4f2be0e038 100644 --- a/apps/bolt-site/.boltrc.js +++ b/apps/bolt-site/.boltrc.js @@ -1,28 +1,10 @@ -const path = require('path'); -const resolve = require('resolve'); -const argv = require('yargs').argv; - -const config = { - lang: ['en'], - renderingService: true, // starts PHP service for rendering Twig templates - openServerAtStart: false, - webpackDevServer: true, +module.exports = { // Environmental variable / preset to use + lang: ['en'], env: 'static', - startPath: '/', buildDir: '../../www/build/', - srcDir: './pages', + srcDir: './content', wwwDir: '../../www', - extraTwigNamespaces: { - 'bolt-assets': { - recursive: true, - paths: ['../../www/build'], - }, - bolt: { - recursive: true, - paths: ['templates'], - }, - }, images: { sets: [ { @@ -34,72 +16,33 @@ const config = { }, components: { global: [ + '@bolt/core', '@bolt/global', - '@bolt/internal-schema-form', - '@bolt/components-placeholder', + '@bolt/components-page-footer', + '@bolt/components-page-header', + '@bolt/components-site', '@bolt/components-action-blocks', - '@bolt/components-dropdown', - '@bolt/components-background', - '@bolt/components-background-shapes', '@bolt/components-band', - '@bolt/components-block-list', '@bolt/components-blockquote', - '@bolt/components-breadcrumb', '@bolt/components-button', '@bolt/components-button-group', '@bolt/components-card', '@bolt/components-chip', '@bolt/components-chip-list', - '@bolt/components-code-snippet', - '@bolt/components-copy-to-clipboard', - '@bolt/components-device-viewer', - '@bolt/components-figure', - '@bolt/components-form', - '@bolt/components-headline', '@bolt/components-icon', - '@bolt/components-image', + '@bolt/components-headline', '@bolt/components-link', - '@bolt/components-nav-indicator', - '@bolt/components-nav-priority', - '@bolt/components-navbar', - '@bolt/components-navlink', '@bolt/components-logo', - '@bolt/components-ordered-list', - '@bolt/components-page-footer', - '@bolt/components-page-header', - '@bolt/components-pagination', - '@bolt/components-share', - '@bolt/components-search-filter', - '@bolt/components-site', - '@bolt/components-smooth-scroll', - '@bolt/components-sticky', '@bolt/components-teaser', - '@bolt/components-text', - '@bolt/components-tooltip', - '@bolt/components-unordered-list', - '@bolt/components-video', - resolve.sync('./index.scss'), - resolve.sync('./index.js'), - ], - individual: [ - '@bolt/components-critical-fonts', - '@bolt/components-critical-css-vars', + '@bolt/components-image', + './style.scss', ], + individual: [], }, - copy: [ - { - from: `./assets/**/*`, - to: `../../www/assets`, - flatten: true, - }, - { - from: `${path.dirname( - resolve.sync('@bolt/global/package.json'), - )}/favicons/bolt`, - to: `../../www`, - flatten: true, + extraTwigNamespaces: { + 'bolt-assets': { + recursive: true, + paths: ['../../www/build'], }, - ], + }, }; - -module.exports = config; diff --git a/apps/bolt-site/assets/bolt-sketch.zip b/apps/bolt-site/assets/bolt-sketch.zip deleted file mode 100644 index 488d67ae9c..0000000000 Binary files a/apps/bolt-site/assets/bolt-sketch.zip and /dev/null differ diff --git a/apps/bolt-site/components/animated-logo/animated-logo.scss b/apps/bolt-site/components/animated-logo/animated-logo.scss deleted file mode 100644 index a091e0a5f2..0000000000 --- a/apps/bolt-site/components/animated-logo/animated-logo.scss +++ /dev/null @@ -1,36 +0,0 @@ -@keyframes bds-logo-fade { - 0% { - opacity: 0; - transform: translate3d(0, 0, 0) scale(0.4, 0.4); - } - 100% { - opacity: 1; - transform: translate3d(0, 0, 0) scale(0.6, 0.6); - } -} - -@keyframes bds-logo-scale-in-out { - 0%, - 100% { - transform: translate3d(0, 0, 0) scale(1); - } - 50% { - transform: translate3d(0, 0, 0) scale(0.9); - } -} - -.c-bds-logo { - overflow: visible; // so the logo doesn't get chopped off - position: relative; - animation-name: bds-logo-fade; - animation-delay: 0.2s; - animation-duration: 1s; - animation-timing-function: cubic-bezier(0.22, 1, 0.32, 1); - animation-fill-mode: forwards; - opacity: 0; - perspective: 1000px; -} - -.c-bds-logo__inner { - animation: bds-logo-scale-in-out 24s ease-in-out infinite; -} \ No newline at end of file diff --git a/apps/bolt-site/components/animated-logo/animated-logo.twig b/apps/bolt-site/components/animated-logo/animated-logo.twig deleted file mode 100644 index 0aa6c7ee82..0000000000 --- a/apps/bolt-site/components/animated-logo/animated-logo.twig +++ /dev/null @@ -1,9 +0,0 @@ - \ No newline at end of file diff --git a/apps/bolt-site/components/animated-shapes/animated-shapes.scss b/apps/bolt-site/components/animated-shapes/animated-shapes.scss deleted file mode 100644 index 149d1fa6ca..0000000000 --- a/apps/bolt-site/components/animated-shapes/animated-shapes.scss +++ /dev/null @@ -1,185 +0,0 @@ -@keyframes bds-shapes-fade { - 0% { - opacity: 0; - transform: translate3d(0, 0, 0) rotate(calc(var(--rotate) * 2)); - } - 10% { - opacity: 0; - transform: translate3d(0, 0, 0) rotate(calc(var(--rotate) * 2)); - } -} - -@keyframes bds-shapes-drift { - 0% { - transform: translate3d(100vw, 0, 0); - } - to { - transform: translate3d(-30vw, 0, 0); - } -} - -.c-bds-shapes { - overflow: hidden; - position: fixed; - height: 100vh; - width: 100vw; - pointer-events: none; - top: bolt-v-spacing(medium); // offset the top just a tad - left: 0; - z-index: 1; - animation: bds-shapes-fade 0.5s ease-out; - opacity: 1; -} - -.c-bds-shapes__shape-inner { - width: var(--size); - height: var(--size); - transform: translate3d(0, calc(var(--criterion, 0) * -2 * var(--size)), 0) - rotate(calc((1 - var(--criterion, 0) * -2) * var(--rotate))); - will-change: transform; -} - - - - - -.c-bds-shapes__animation { - left: 0; - position: absolute; - top: 0; -} - -.c-bds-shapes__animation--1 { - animation-iteration-count: infinite; - animation-name: bds-shapes-drift; - will-change: transform; - animation-delay: -12s; - animation-duration: 55s; - animation-timing-function: cubic-bezier(0.47, 0, 0.745, 0.715); - filter: blur(1px); - height: 10vh; - opacity: 0.1; - top: 0; - width: 10vh; - z-index: 8; -} - -.c-bds-shapes__animation--2 { - animation-iteration-count: infinite; - animation-name: bds-shapes-drift; - will-change: transform; - animation-delay: -38s; - animation-duration: 58s; - animation-timing-function: cubic-bezier(0.55, 0.085, 0.68, 0.53); - filter: blur(3px); - height: 3vh; - opacity: 1; - top: 8vh; - width: 3vh; - z-index: 7; -} - -.c-bds-shapes__animation--3 { - animation-iteration-count: infinite; - animation-name: bds-shapes-drift; - will-change: transform; - animation-delay: -60s; - animation-duration: 76s; - animation-timing-function: linear; - filter: blur(5px); - height: 4vh; - opacity: 0.5; - top: 20vh; - width: 4vh; - z-index: 1; -} - -.c-bds-shapes__animation--4 { - animation-iteration-count: infinite; - animation-name: bds-shapes-drift; - will-change: transform; - animation-delay: -29s; - animation-duration: 51s; - animation-timing-function: ease-in; - filter: blur(5px); - height: 7vh; - opacity: 0.8; - top: 33vh; - width: 7vh; - z-index: 6; -} - -.c-bds-shapes__animation--5 { - animation-iteration-count: infinite; - animation-name: bds-shapes-drift; - will-change: transform; - animation-delay: -5s; - animation-duration: 67s; - animation-timing-function: linear; - filter: blur(4px); - height: 4vh; - opacity: 0.45; - top: 40vh; - width: 4vh; - z-index: 4; -} - -.c-bds-shapes__animation--6 { - animation-iteration-count: infinite; - animation-name: bds-shapes-drift; - will-change: transform; - animation-delay: -31s; - animation-duration: 64s; - animation-timing-function: cubic-bezier(0.55, 0.085, 0.68, 0.53); - filter: blur(3px); - height: 6vh; - opacity: 0.7; - top: 68vh; - width: 6vh; - z-index: 5; -} - -.c-bds-shapes__animation--7 { - animation-iteration-count: infinite; - animation-name: bds-shapes-drift; - will-change: transform; - animation-delay: -42s; - animation-duration: 70s; - animation-timing-function: linear; - filter: blur(1px); - height: 2vh; - opacity: 0.6; - top: 82vh; - width: 2vh; - z-index: 3; -} - -.c-bds-shapes__animation--8 { - animation-iteration-count: infinite; - animation-name: bds-shapes-drift; - will-change: transform; - animation-delay: -55s; - animation-duration: 73s; - animation-timing-function: ease-in; - filter: blur(6px); - height: 2vh; - opacity: 0.55; - top: 90vh; - width: 2vh; - z-index: 2; -} - -.c-bds-shapes__animation--9 { - animation-iteration-count: infinite; - animation-name: bds-shapes-drift; - will-change: transform; - animation-delay: -25s; - animation-duration: 40s; - animation-timing-function: ease-in; - filter: blur(6px); - height: 3vh; - opacity: 0.95; - top: 60vh; - width: 3vh; - z-index: 2; -} diff --git a/apps/bolt-site/components/animated-shapes/animated-shapes.twig b/apps/bolt-site/components/animated-shapes/animated-shapes.twig deleted file mode 100644 index a9192511a7..0000000000 --- a/apps/bolt-site/components/animated-shapes/animated-shapes.twig +++ /dev/null @@ -1,133 +0,0 @@ -
- - - - -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
-
\ No newline at end of file diff --git a/apps/bolt-site/components/hamburger-button/hamburger-button.scss b/apps/bolt-site/components/hamburger-button/hamburger-button.scss deleted file mode 100644 index 39f6f3cb8f..0000000000 --- a/apps/bolt-site/components/hamburger-button/hamburger-button.scss +++ /dev/null @@ -1,55 +0,0 @@ -.c-bds-hamburger { - position: fixed; - bottom: bolt-spacing(xsmall); - right: bolt-spacing(xsmall); - z-index: bolt-z-index('fab'); -} - -.c-bds-hamburger__inner { - position: relative; -} - -.c-bds-hamburger__button, -.c-bds-hamburger__button bolt-icon { - transition: all 0.4s ease; -} - -.c-bds-hamburger__button--open { - opacity: 1; - z-index: 11; -} - -.c-bds-hamburger__button--close { - z-index: 10; - pointer-events: none; - // visibility: hidden; - position: absolute; - top: 0; - right: 0; - opacity: 0; -} - - -.c-bds-layout__sidebar:target ~ .c-bds-hamburger > .c-bds-hamburger__inner .c-bds-hamburger__button--close { - opacity: 1; - // visibility: visible; - pointer-events: auto; - z-index: 11; - - bolt-icon { - transform: rotate(1turn) scale(1); - } -} - -.c-bds-layout__sidebar:target ~ .c-bds-hamburger > .c-bds-hamburger__inner .c-bds-hamburger__button--open { - opacity: 0; - // visibility: hidden; - pointer-events: none; - - bolt-icon { - transform: rotate(1turn) scale(0); - } -} - - - diff --git a/apps/bolt-site/components/hamburger-button/hamburger-button.twig b/apps/bolt-site/components/hamburger-button/hamburger-button.twig deleted file mode 100644 index 91a75fe4d4..0000000000 --- a/apps/bolt-site/components/hamburger-button/hamburger-button.twig +++ /dev/null @@ -1,35 +0,0 @@ -
-
-
- {% include "@bolt-components-button/button.twig" with { - text: 'Open Menu', - iconOnly: true, - url: "#menu", - style: "primary", - size: "small", - rounded: "rounded", - icon: { - name: "menu", - size: "medium", - position: "after" - } - } %} -
- -
- {% include "@bolt-components-button/button.twig" with { - text: 'Close Menu', - iconOnly: true, - url: "#", - style: "primary", - size: "small", - rounded: "rounded", - icon: { - name: "close", - size: "medium", - position: "after" - } - } %} -
-
-
\ No newline at end of file diff --git a/apps/bolt-site/components/layout/layout.scss b/apps/bolt-site/components/layout/layout.scss deleted file mode 100644 index 4041908bef..0000000000 --- a/apps/bolt-site/components/layout/layout.scss +++ /dev/null @@ -1,280 +0,0 @@ -@import '@bolt/core'; - -$bds-sidebar-width: 320px; - -.c-bds-layout { - grid-template-columns: auto 1fr auto; - height: 100vh; - - @include bolt-mq(medium) { - &.c-bds-layout--has-sidebar { - grid-template-columns: $bds-sidebar-width auto 1fr auto; - } - } -} - -.c-bds-layout__wrapper { - align-self: stretch; - justify-self: stretch; - display: flex; - flex-direction: column; - grid-column-start: 1; - grid-column-end: 1; -} - -.c-bds-layout__title { - padding: 15px 20px; - - span { - vertical-align: middle; - } - - .c-bds-layout__content--has-sidebar & { - width: 100%; - } -} - -.c-bds-layout__header { - grid-column-start: 1; - grid-column-end: 3; - - @include bolt-mq(medium) { - .c-bds-layout--has-sidebar & { - grid-column-start: 1; - grid-column-end: 4; - } - } -} - -.c-bds-layout__header__nav { - padding: 15px 20px; - text-align: right; - - @include bolt-mq($until: 599px) { - text-align: center; - width: 100%; - } - - a { - color: #fff; - padding: 6px 12px; - - &.active, - &.active-path { - background: rgba(0, 0, 0, 0.1); - border: 1px solid #fff; - border-radius: 3px; - box-shadow: 0 1px 3px rgba(21, 22, 25, 0.12), - 0 1px 2px rgba(21, 22, 25, 0.24); - display: none; // we need more room at mobile and this is the item which makes most sense to exclude since we're already there! - - @include bolt-mq(small) { - display: initial; - } - - &.active-path { - text-decoration: none; - } - } - - &.c-bds-layout__hamburger { - color: bolt-color(yellow); - - > span { - display: none; - - @include bolt-mq(xsmall) { - display: initial; - } - } - } - } -} - -.c-bds-layout__header__link { - text-decoration: none; -} - -.c-bds-layout__sidebar__overlay { - position: fixed; - top: 0; - left: 0; - right: 0; - bottom: 0; - pointer-events: none; - z-index: bolt-z-index('modalBG'); - background-color: bolt-color(black); - opacity: 0; - visibility: hidden; - transition: opacity 0.3s cubic-bezier(0.23, 1, 0.32, 1), - visibility 0.3s cubic-bezier(0.23, 1, 0.32, 1); -} - -.c-bds-layout__sidebar { - width: $bds-sidebar-width; - overflow: auto; - -webkit-overflow-scrolling: touch; - border-right: 1px solid #e0e2eb; - padding: 0; - position: fixed; - top: 0; - bottom: 0; - transform: translate3d(-$bds-sidebar-width, 0, 0); - background-color: bolt-color(gray, xlight); - z-index: bolt-z-index('modal'); - transition: transform 0.3s cubic-bezier(0.23, 1, 0.32, 1); - - &:target { - transform: translate3d(0, 0, 0); - - & ~ .c-bds-layout__sidebar__overlay { - visibility: visible; - opacity: 0.2; - pointer-events: auto; - - @include bolt-mq(medium) { - visibility: hidden; - opacity: 0; - pointer-events: none; - } - } - } - - @include bolt-mq(medium) { - transform: translate3d(0, 0, 0); - position: relative; - - @include bolt-mq(medium) { - .c-bds-layout--has-sidebar & { - grid-column-start: 1; - grid-column-end: 2; - - grid-row-start: 2; - grid-row-end: 4; - } - } - } - - .c-bds-offcanvas { - position: relative; - - > h2 { - color: #fff; - background-color: bolt-color(indigo); - border-bottom: 1px solid #fff; - padding: 10px 20px; - margin: 0; - font-size: 1.11rem; - } - - .c-bds-layout__close-menu { - position: absolute; - top: 8px; - right: 10px; - color: bolt-color(yellow); - } - } -} - -.c-bds-layout__-docs { - // Styling for markdown content (classes not available) - article { - h2 { - font-size: 1.33rem; - margin-bottom: 0.9rem; - } - - h3 { - margin-bottom: 0.65rem; - } - } -} - -.c-bds-layout__content { - grid-column-start: 1; - grid-column-end: 3; - overflow: auto; - display: grid; - grid-template-columns: auto; - grid-template-rows: auto; - // display: flex; - // flex-direction: column; - // justify-content: space-between; - - @include bolt-mq(medium) { - .c-bds-layout--has-sidebar & { - grid-template-columns: auto; - grid-column-start: 2; - grid-column-end: 4; - } - } - - @include bolt-mq(1400px + 320px) { - .c-bds-layout--has-sidebar & { - grid-template-columns: auto 320px; - grid-column-start: 2; - grid-column-end: 4; - } - } -} - -.c-bds-layout__footer { - position: relative; // Fallback positioning - bottom: 0; - right: 0; - left: 0; - background-color: #fff; - align-self: end; - width: 100%; - - grid-column-start: 1; - grid-column-end: 1; -} - -// Remove default padding of highlightJS when used inside of Bolt Global style defaults -// pre > .hljs, -// pre > code { -// /** -// * 1. Workaround to offset default spacing -// */ -// background-color: bolt-color(gray, xlight); -// margin-left: bolt-spacing(small) * -1; /* [1] */ -// margin-right: bolt-spacing(small) * -1; /* [1] */ -// @include bolt-padding(small); -// } - -// .u-bolt-margin-right-auto { -// margin-right: auto !important; -// } - -// .u-bolt-margin-right-auto\@small { -// @include bolt-mq(small) { -// margin-right: auto !important; -// } -// } - -// .u-bolt-hide { -// display: none !important; -// } - -// .u-bolt-hide\@small { -// @include bolt-mq(small) { -// display: none !important; -// } -// } - -// .u-bolt-hide\@medium { -// @include bolt-mq(medium) { -// display: none !important; -// } -// } - -// .u-bolt-show\@small { -// @include bolt-mq(small) { -// display: block !important; -// } -// } - -// .u-bolt-padding-top-none { -// padding-top: 0 !important; -// } diff --git a/apps/bolt-site/components/off-canvas-nav/off-canvas-nav.scss b/apps/bolt-site/components/off-canvas-nav/off-canvas-nav.scss deleted file mode 100644 index b78f634822..0000000000 --- a/apps/bolt-site/components/off-canvas-nav/off-canvas-nav.scss +++ /dev/null @@ -1,134 +0,0 @@ -.c-bds-offcanvas { - @include bolt-padding(large medium); -} - -.c-bds-offcanvas__list { - @include bolt-margin-left(0); - @include bolt-margin-bottom(0); - list-style: none; -} - -.c-bds-offcanvas__link { - @include bolt-padding-top(xxsmall); - @include bolt-padding-bottom(xxsmall); - @include bolt-font-size(small); - text-decoration: none; - - &.is-active { - @include bolt-font-weight(bold); - } -} - -.c-bds-offcanvas { - // border-top: 1px solid #E0E2EB; - // list-style-type: none; - // margin: 0; - - &__link { - // font-size: 1.11rem; - // line-height: 1.25; - // font-weight: 300; - - &--active-path { - // color: #545DA6; - // font-weight: 600; - &+.c-bds-offcanvas__list--depth-3 { - // border-left: 2px solid #535ca6; - } - } - &--active { - // font-weight: bolder; - // text-decoration: none; - } - &--parent { - // background: #e0e2eb; - display: block; - // border-top: 1px solid #FFF; - // border-bottom: 1px solid #FFF; - // padding: 0.6rem 1.7rem; - // color: #000; - - &.c-bds-offcanvas__link--active-path { - // background: #545DA6; - // color: #FFF; - // text-decoration: none; - } - } - .c-bds-offcanvas__list--depth-1 & { - // font-size: 1.25rem; - } - .c-bds-offcanvas__list--depth-2 & { - // font-size: 0.9rem; - display: block; - // margin: 0.5rem 0; - - &.c-bds-offcanvas__link--active { - // color: #545DA6; - // font-style: italic; - // font-weight: 600; - } - } - } - - &__list { - &--depth-3, - &--depth-4 { - // margin-top: 0.25rem; - // margin-bottom: 0.25rem; - // border-left: 2px solid transparent; - @include bolt-margin-left(small); - // padding-left: 2rem; - } - } - - // > ul { - // margin-left: 0; - // padding: 0; - // li:first-child a.c-bds-offcanvas__link--parent { - // border-top: none; - // } - // } - - // li { - // list-style: none; - // } -} - - -.c-bds-offcanvas__collapsible { - @include bolt-margin-bottom(0); - overflow: hidden; - outline: none; -} - -.c-bds-offcanvas__collapsible-header { - @include bolt-margin-bottom(0); - @include bolt-padding-top(xxsmall); - @include bolt-padding-bottom(xxsmall); - @include bolt-font-size(small); - display: flex; - flex-direction: row-reverse; - align-items: center; - justify-content: space-between; - position: relative; - outline: none; - - &:before { - content: ''; - position: absolute; - left: 0; - top: 100%; - bottom: -999px; - width: 1px; - background-color: currentColor; - opacity: 0.2; - } -} - -// .c-bds-offcanvas__collapsible-body { -// @include bolt-margin-bottom(0); -// display: flex; -// flex-direction: row-reverse; -// align-items: center; -// justify-content: space-between; -// } \ No newline at end of file diff --git a/apps/bolt-site/components/off-canvas-nav/off-canvas-nav.twig b/apps/bolt-site/components/off-canvas-nav/off-canvas-nav.twig deleted file mode 100644 index 465ea72296..0000000000 --- a/apps/bolt-site/components/off-canvas-nav/off-canvas-nav.twig +++ /dev/null @@ -1,79 +0,0 @@ -{% macro menu(items, depth, currentUrl) %} - {% import _self as macros %} - -{% endmacro %} - -{% import _self as macros %} -{% set currentUrl = page.url %} - - diff --git a/apps/bolt-site/composer.json b/apps/bolt-site/composer.json index 26370c4ff4..b068336435 100644 --- a/apps/bolt-site/composer.json +++ b/apps/bolt-site/composer.json @@ -1,25 +1,22 @@ { - "name": "bolt/website--storefront", - "type": "project", - "license": "MIT", - "minimum-stability": "dev", - "repositories": [ - { - "type": "path", - "url": "../../packages/core-php" - }, - { - "type": "path", - "url": "../../packages/drupal-stubs" + "name": "bolt/website--storefront", + "type": "project", + "license": "MIT", + "minimum-stability": "dev", + "repositories": [ + { + "type": "path", + "url": "../../packages/core-php" + }, + { + "type": "path", + "url": "../../packages/drupal-stubs" + } + ], + "require": { + "bolt-design-system/core-php": "*", + "bolt-design-system/drupal-stubs": "*", + "twig/twig": "^1.0", + "pattern-lab/drupal-twig-extensions": "^0.2.0" } - ], - "require": { - "bolt-design-system/core-php": "*", - "bolt-design-system/drupal-stubs": "*", - "twig/twig": "^1.0", - "pattern-lab/drupal-twig-extensions": "^0.2.0", - "webmozart/path-util": "*", - "symfony/finder": "*", - "wa72/html-pretty-min": "^0.2.0" - } } diff --git a/apps/bolt-site/composer.lock b/apps/bolt-site/composer.lock index d1ceb0cee9..dc0f81991a 100644 --- a/apps/bolt-site/composer.lock +++ b/apps/bolt-site/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "content-hash": "e0fda144b1c61388acf30e39218a7b59", + "content-hash": "63dd261d2a003ddaaad05ff095fecb61", "packages": [ { "name": "asm89/twig-lint", @@ -59,16 +59,16 @@ }, { "name": "basaltinc/twig-tools", - "version": "v1.4.2", + "version": "v1.4.1", "source": { "type": "git", "url": "https://github.com/basaltinc/twig-tools.git", - "reference": "06d174c63aa0b2bfbc0aad2887349e11f9a60ef8" + "reference": "15aaf150034623c7dffc8d5b1903dde9fe43ccae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/basaltinc/twig-tools/zipball/06d174c63aa0b2bfbc0aad2887349e11f9a60ef8", - "reference": "06d174c63aa0b2bfbc0aad2887349e11f9a60ef8", + "url": "https://api.github.com/repos/basaltinc/twig-tools/zipball/15aaf150034623c7dffc8d5b1903dde9fe43ccae", + "reference": "15aaf150034623c7dffc8d5b1903dde9fe43ccae", "shasum": "" }, "require": { @@ -94,15 +94,15 @@ "email": "evanlovely@gmail.com" } ], - "time": "2018-08-30T21:00:44+00:00" + "time": "2018-03-30T23:48:12+00:00" }, { "name": "bolt-design-system/core-php", - "version": "1.8.3", + "version": "1.1.2", "dist": { "type": "path", "url": "../../packages/core-php", - "reference": "b1ad3d20bafed6b9993bb4c67188ce80ab0f5f71", + "reference": "77f49643322b780f77cbe9e055fb40a997075f33", "shasum": null }, "require": { @@ -180,16 +180,16 @@ }, { "name": "drupal/core-render", - "version": "8.7.x-dev", + "version": "8.6.x-dev", "source": { "type": "git", "url": "https://github.com/drupal/core-render.git", - "reference": "088f75280b4357930dc5136c5323cc1641772514" + "reference": "10f38eca14d0486b54e239950ce5e99df62a3052" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core-render/zipball/088f75280b4357930dc5136c5323cc1641772514", - "reference": "088f75280b4357930dc5136c5323cc1641772514", + "url": "https://api.github.com/repos/drupal/core-render/zipball/10f38eca14d0486b54e239950ce5e99df62a3052", + "reference": "10f38eca14d0486b54e239950ce5e99df62a3052", "shasum": "" }, "require": { @@ -211,28 +211,26 @@ "keywords": [ "drupal" ], - "time": "2018-05-08 20:55:21" + "time": "2018-02-01T21:34:33+00:00" }, { "name": "drupal/core-utility", - "version": "8.7.x-dev", + "version": "8.6.x-dev", "source": { "type": "git", "url": "https://github.com/drupal/core-utility.git", - "reference": "490bbe8248aa88468674aa8a9ef216a1a388ea3a" + "reference": "e50124d0bb70560342a349a11c8661603f089f27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/drupal/core-utility/zipball/490bbe8248aa88468674aa8a9ef216a1a388ea3a", - "reference": "490bbe8248aa88468674aa8a9ef216a1a388ea3a", + "url": "https://api.github.com/repos/drupal/core-utility/zipball/e50124d0bb70560342a349a11c8661603f089f27", + "reference": "e50124d0bb70560342a349a11c8661603f089f27", "shasum": "" }, "require": { "drupal/core-render": "^8.2", "paragonie/random_compat": "^1.0|^2.0", - "php": ">=5.5.9", - "symfony/polyfill-iconv": "~1.0", - "symfony/polyfill-mbstring": "~1.0" + "php": ">=5.5.9" }, "type": "library", "autoload": { @@ -249,7 +247,7 @@ "keywords": [ "drupal" ], - "time": "2018-05-11 09:46:46" + "time": "2018-04-06T11:45:50+00:00" }, { "name": "fzaninotto/faker", @@ -257,12 +255,12 @@ "source": { "type": "git", "url": "https://github.com/fzaninotto/Faker.git", - "reference": "10cc47a818c0fb5a911ab4274c0a8354245af3e7" + "reference": "65fbcca41437baff58a5c0e8f08fae617531dd0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/10cc47a818c0fb5a911ab4274c0a8354245af3e7", - "reference": "10cc47a818c0fb5a911ab4274c0a8354245af3e7", + "url": "https://api.github.com/repos/fzaninotto/Faker/zipball/65fbcca41437baff58a5c0e8f08fae617531dd0d", + "reference": "65fbcca41437baff58a5c0e8f08fae617531dd0d", "shasum": "" }, "require": { @@ -276,7 +274,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.8-dev" } }, "autoload": { @@ -299,7 +297,7 @@ "faker", "fixtures" ], - "time": "2018-09-18 12:17:14" + "time": "2018-03-15T16:58:08+00:00" }, { "name": "gregwar/cache", @@ -553,70 +551,18 @@ ], "time": "2018-01-15T00:49:33+00:00" }, - { - "name": "mrclay/jsmin-php", - "version": "2.3.2", - "source": { - "type": "git", - "url": "https://github.com/mrclay/jsmin-php.git", - "reference": "932c9633c35b390beb2cfdea69a41ea7dbc8d759" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/mrclay/jsmin-php/zipball/932c9633c35b390beb2cfdea69a41ea7dbc8d759", - "reference": "932c9633c35b390beb2cfdea69a41ea7dbc8d759", - "shasum": "" - }, - "require": { - "ext-pcre": "*", - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "4.2" - }, - "type": "library", - "autoload": { - "psr-0": { - "JSMin\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Stephen Clay", - "email": "steve@mrclay.org", - "role": "Developer" - }, - { - "name": "Ryan Grove", - "email": "ryan@wonko.com", - "role": "Developer" - } - ], - "description": "Provides a modified port of Douglas Crockford's jsmin.c, which removes unnecessary whitespace from JavaScript files.", - "homepage": "https://github.com/mrclay/jsmin-php/", - "keywords": [ - "compress", - "jsmin", - "minify" - ], - "time": "2015-03-30T15:04:42+00:00" - }, { "name": "paragonie/random_compat", - "version": "v2.0.17", + "version": "v2.0.12", "source": { "type": "git", "url": "https://github.com/paragonie/random_compat.git", - "reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d" + "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/random_compat/zipball/29af24f25bab834fcbb38ad2a69fa93b867e070d", - "reference": "29af24f25bab834fcbb38ad2a69fa93b867e070d", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/258c89a6b97de7dfaf5b8c7607d0478e236b04fb", + "reference": "258c89a6b97de7dfaf5b8c7607d0478e236b04fb", "shasum": "" }, "require": { @@ -648,11 +594,10 @@ "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", "keywords": [ "csprng", - "polyfill", "pseudorandom", "random" ], - "time": "2018-07-04T16:31:37+00:00" + "time": "2018-04-04T21:24:14+00:00" }, { "name": "pattern-lab/drupal-twig-extensions", @@ -783,12 +728,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "2fdbba7fe6d979f53512e2eb852837c991fbe3d8" + "reference": "d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/2fdbba7fe6d979f53512e2eb852837c991fbe3d8", - "reference": "2fdbba7fe6d979f53512e2eb852837c991fbe3d8", + "url": "https://api.github.com/repos/symfony/console/zipball/d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf", + "reference": "d4bb70fa24d540c309d88a9d6e43fb2d339b1fbf", "shasum": "" }, "require": { @@ -809,7 +754,7 @@ "symfony/process": "~3.3|~4.0" }, "suggest": { - "psr/log-implementation": "For using the console logger", + "psr/log": "For using the console logger", "symfony/event-dispatcher": "", "symfony/lock": "", "symfony/process": "" @@ -844,7 +789,7 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2018-09-16 11:50:16" + "time": "2018-04-03T05:22:50+00:00" }, { "name": "symfony/debug", @@ -852,12 +797,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "9374383ad212676d943b3df2e181071d11582f79" + "reference": "c2c7c28603091b76499089fac34b8b68d58b4b81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/9374383ad212676d943b3df2e181071d11582f79", - "reference": "9374383ad212676d943b3df2e181071d11582f79", + "url": "https://api.github.com/repos/symfony/debug/zipball/c2c7c28603091b76499089fac34b8b68d58b4b81", + "reference": "c2c7c28603091b76499089fac34b8b68d58b4b81", "shasum": "" }, "require": { @@ -873,7 +818,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.1-dev" } }, "autoload": { @@ -900,7 +845,7 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2018-09-09 09:23:23" + "time": "2018-04-05T17:04:06+00:00" }, { "name": "symfony/finder", @@ -908,12 +853,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "7d3bd180970e61a14ad2408484acbc8397cb8583" + "reference": "bd14efe8b1fabc4de82bf50dce62f05f9a102433" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/7d3bd180970e61a14ad2408484acbc8397cb8583", - "reference": "7d3bd180970e61a14ad2408484acbc8397cb8583", + "url": "https://api.github.com/repos/symfony/finder/zipball/bd14efe8b1fabc4de82bf50dce62f05f9a102433", + "reference": "bd14efe8b1fabc4de82bf50dce62f05f9a102433", "shasum": "" }, "require": { @@ -949,178 +894,7 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2018-09-02 17:11:59" - }, - { - "name": "symfony/options-resolver", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "353fde224412a47a8da55a7a2344760cc206d511" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/353fde224412a47a8da55a7a2344760cc206d511", - "reference": "353fde224412a47a8da55a7a2344760cc206d511", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.2-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony OptionsResolver Component", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "time": "2018-09-18 16:38:25" - }, - { - "name": "symfony/polyfill-ctype", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, - { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" - } - ], - "description": "Symfony polyfill for ctype functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "ctype", - "polyfill", - "portable" - ], - "time": "2018-08-06 14:22:27" - }, - { - "name": "symfony/polyfill-iconv", - "version": "dev-master", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "bcc0cd69185b8a5d8b4a5400c489ed3333bf9bb2" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/bcc0cd69185b8a5d8b4a5400c489ed3333bf9bb2", - "reference": "bcc0cd69185b8a5d8b4a5400c489ed3333bf9bb2", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-iconv": "For best performance" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Iconv\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Iconv extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "iconv", - "polyfill", - "portable", - "shim" - ], - "time": "2018-08-06 14:22:27" + "time": "2018-04-04T05:07:11+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -1128,12 +902,12 @@ "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8" + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/d0cd638f4634c16d8df4508e847f14e9e43168b8", - "reference": "d0cd638f4634c16d8df4508e847f14e9e43168b8", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/78be803ce01e55d3491c1397cf1c64beb9c1b63b", + "reference": "78be803ce01e55d3491c1397cf1c64beb9c1b63b", "shasum": "" }, "require": { @@ -1145,7 +919,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.7-dev" } }, "autoload": { @@ -1179,7 +953,7 @@ "portable", "shim" ], - "time": "2018-08-06 14:22:27" + "time": "2018-01-30T19:27:44+00:00" }, { "name": "symfony/yaml", @@ -1187,17 +961,16 @@ "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "84fa64edb72e90a5421391f0c5206b4c90788750" + "reference": "033cfa61ef06ee0847e056e530201842b6e926c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/84fa64edb72e90a5421391f0c5206b4c90788750", - "reference": "84fa64edb72e90a5421391f0c5206b4c90788750", + "url": "https://api.github.com/repos/symfony/yaml/zipball/033cfa61ef06ee0847e056e530201842b6e926c3", + "reference": "033cfa61ef06ee0847e056e530201842b6e926c3", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/polyfill-ctype": "~1.8" + "php": "^5.5.9|>=7.0.8" }, "conflict": { "symfony/console": "<3.4" @@ -1238,7 +1011,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-09-02 17:11:59" + "time": "2018-04-08T08:21:29+00:00" }, { "name": "tooleks/php-avg-color-picker", @@ -1292,76 +1065,22 @@ ], "time": "2017-07-07T11:41:14+00:00" }, - { - "name": "tubalmartin/cssmin", - "version": "v4.1.1", - "source": { - "type": "git", - "url": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port.git", - "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/tubalmartin/YUI-CSS-compressor-PHP-port/zipball/3cbf557f4079d83a06f9c3ff9b957c022d7805cf", - "reference": "3cbf557f4079d83a06f9c3ff9b957c022d7805cf", - "shasum": "" - }, - "require": { - "ext-pcre": "*", - "php": ">=5.3.2" - }, - "require-dev": { - "cogpowered/finediff": "0.3.*", - "phpunit/phpunit": "4.8.*" - }, - "bin": [ - "cssmin" - ], - "type": "library", - "autoload": { - "psr-4": { - "tubalmartin\\CssMin\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Túbal Martín", - "homepage": "http://tubalmartin.me/" - } - ], - "description": "A PHP port of the YUI CSS compressor", - "homepage": "https://github.com/tubalmartin/YUI-CSS-compressor-PHP-port", - "keywords": [ - "compress", - "compressor", - "css", - "cssmin", - "minify", - "yui" - ], - "time": "2018-01-15T15:26:51+00:00" - }, { "name": "twig/twig", "version": "1.x-dev", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "82515f6339fd223a6a6a40a432e6e61b8d6d269f" + "reference": "3bc9686dc531dc2127ea90273352180261a4049e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/82515f6339fd223a6a6a40a432e6e61b8d6d269f", - "reference": "82515f6339fd223a6a6a40a432e6e61b8d6d269f", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/3bc9686dc531dc2127ea90273352180261a4049e", + "reference": "3bc9686dc531dc2127ea90273352180261a4049e", "shasum": "" }, "require": { - "php": ">=5.3.3", - "symfony/polyfill-ctype": "^1.8" + "php": ">=5.3.3" }, "require-dev": { "psr/container": "^1.0", @@ -1371,7 +1090,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.36-dev" + "dev-master": "1.35-dev" } }, "autoload": { @@ -1400,58 +1119,16 @@ }, { "name": "Twig Team", - "homepage": "https://twig.symfony.com/contributors", + "homepage": "http://twig.sensiolabs.org/contributors", "role": "Contributors" } ], "description": "Twig, the flexible, fast, and secure template language for PHP", - "homepage": "https://twig.symfony.com", + "homepage": "http://twig.sensiolabs.org", "keywords": [ "templating" ], - "time": "2018-08-03 05:53:41" - }, - { - "name": "wa72/html-pretty-min", - "version": "v0.2.0", - "source": { - "type": "git", - "url": "https://github.com/wasinger/html-pretty-min.git", - "reference": "3b4eca03559dab8c178ec7a80f3043c279f5e90e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/wasinger/html-pretty-min/zipball/3b4eca03559dab8c178ec7a80f3043c279f5e90e", - "reference": "3b4eca03559dab8c178ec7a80f3043c279f5e90e", - "shasum": "" - }, - "require": { - "mrclay/jsmin-php": "^2.3", - "php": ">=5.4", - "symfony/options-resolver": ">=2.3", - "tubalmartin/cssmin": "^4" - }, - "require-dev": { - "phpunit/phpunit": "^4|^5|^6|^7" - }, - "type": "library", - "autoload": { - "psr-4": { - "Wa72\\HtmlPrettymin\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christoph Singer", - "email": "singer@webagentur72.de" - } - ], - "description": "HTML minifier and indenter that works on the DOM tree", - "time": "2018-03-08T20:39:03+00:00" + "time": "2018-04-01T18:24:34+00:00" }, { "name": "webmozart/assert", @@ -1459,12 +1136,12 @@ "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "53927dddf3afa2088b355188e143bba42159bf5d" + "reference": "0df1908962e7a3071564e857d86874dad1ef204a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/53927dddf3afa2088b355188e143bba42159bf5d", - "reference": "53927dddf3afa2088b355188e143bba42159bf5d", + "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", + "reference": "0df1908962e7a3071564e857d86874dad1ef204a", "shasum": "" }, "require": { @@ -1501,7 +1178,7 @@ "check", "validate" ], - "time": "2018-05-29 14:25:02" + "time": "2018-01-29T19:49:41+00:00" }, { "name": "webmozart/path-util", diff --git a/apps/bolt-site/content/00-index.md b/apps/bolt-site/content/00-index.md new file mode 100644 index 0000000000..88ced22b95 --- /dev/null +++ b/apps/bolt-site/content/00-index.md @@ -0,0 +1,6 @@ +--- +title: Home +subtitle: Bolt provides tools, patterns, services, and guidelines that systematically improve quality, timeliness, and consistency to the Pega ecosystem so that you can focus on what really matters. +nav: main +layout: home +--- diff --git a/apps/bolt-site/content/20-docs/00-index.md b/apps/bolt-site/content/20-docs/00-index.md new file mode 100644 index 0000000000..6537ebc62c --- /dev/null +++ b/apps/bolt-site/content/20-docs/00-index.md @@ -0,0 +1,28 @@ +--- +title: Docs +nav: main +--- + +## Welcome to Bolt Contributor docs + +**Here you'll learn how to get a local environment spun up and the correct methods for contributing to the platform** + +
    ///////|\\\\\\
+   ///˜˜˜˜/|˜˜˜˜\\\
+  ///    //|     \\\     Bolt-CLI
+ ///    ///|____  \\\
+///    /////////   \\\   Welcome to the Bolt CLI ⚡ Have fun!
+\\\   /////////    ///
+ \\\      |///    ///    Usage: `bolt <command> [options ...]`
+  \\\     |//    ///     Help: `bolt --help` or `bolt <command> --help`
+   \\\____|/____///
+    \\\\\\|///////
+    
+Verbosity: 1
+✔ Cleaned files in 134ms
+✔ Built WebPack bundle in 10.1s
+✔ Built Pattern Lab in 3.8s
+✔ Processed images in 25ms
+
+ +**[Let's get started!](/docs/getting-started/index.html)** diff --git a/apps/bolt-site/content/20-docs/05-principles/00-index.md b/apps/bolt-site/content/20-docs/05-principles/00-index.md new file mode 100644 index 0000000000..880820918c --- /dev/null +++ b/apps/bolt-site/content/20-docs/05-principles/00-index.md @@ -0,0 +1,21 @@ +--- +title: Design Principles +--- + +The Digital team at Pegasystems creates, maintains and continuously improves the websites that help people learn, build with, sell and buy Pega applications. The 5+ digital properties we work on span internal and external audiences across offices in North America, Europe, and the Asia Pacific region. The following principles help us deliver effective, thoughtful design for the Bolt Design System. + +## Clarity > Cleverness. + +When information isn't clear, it doesn't matter how clever it is. Use plain language and avoid jargon, especially in helper text. Use animation cautiously - it should assist user interaction, rather than obfuscating it. + +## Accessibility is not optional. + +Thinking about accessibility is more than just caring about screen readers. Designing for accessibility helps people with low vision, color blindness and other minor visual issues best interact with our work. [It also helps with SEO](https://webaim.org/blog/web-accessibility-and-seo/). + +## Consider the ecosystem. + +Everything we create fits into a broader digital ecosystem. Understand where the pieces we are working on fit within that ecosystem, and how we can reuse elements to create visual and UI consistency. + +## Make it shine. + +Usability isn't just about functionality or interaction design. If pages load slowly, or the experience lacks visual polish, the user experience suffers. Everything we create should take care to represent Pega in the best possible light. \ No newline at end of file diff --git a/apps/bolt-site/pages/20-docs/25-design-principles/05-accessibility.md b/apps/bolt-site/content/20-docs/05-principles/05-accessibility.md similarity index 100% rename from apps/bolt-site/pages/20-docs/25-design-principles/05-accessibility.md rename to apps/bolt-site/content/20-docs/05-principles/05-accessibility.md diff --git a/apps/bolt-site/pages/20-docs/25-design-principles/10-interface-copy.md b/apps/bolt-site/content/20-docs/05-principles/10-interface-copy.md similarity index 100% rename from apps/bolt-site/pages/20-docs/25-design-principles/10-interface-copy.md rename to apps/bolt-site/content/20-docs/05-principles/10-interface-copy.md diff --git a/apps/bolt-site/content/20-docs/10-getting-started/00-index.md b/apps/bolt-site/content/20-docs/10-getting-started/00-index.md new file mode 100644 index 0000000000..a04894b7b5 --- /dev/null +++ b/apps/bolt-site/content/20-docs/10-getting-started/00-index.md @@ -0,0 +1,100 @@ +--- +title: Getting Started +--- + +## First Time Setup + +### Create bolt config file + +Create a file called `.boltrc.js` with: + +```js +module.exports = { + buildDir: 'www/build', + components: { + global: [ + ], + individual: [ + ], + }, +}; +``` + +### Install Build Tools + +Ensure you have a `package.json` file, if not, run `npm init`. + +```bash +npm install --save @bolt/build-tools +``` + +Add this to your `package.json`: + +```diff +"scripts": { ++ "build": "bolt build", ++ "build:prod": "NODE_ENV=production bolt build", ++ "start": "bolt start" +} +``` + +### Consider adding global styling + +All global styles are kept in a single package, if you'd like it, run: + +```bash +npm install --save @bolt/global +``` + +Then add it to `.boltrc.js`: + +```diff +module.exports = { + buildDir: 'www/build', + components: { + global: [ ++ '@bolt/global', + ], + individual: [ + ], + }, +}; +``` + +### Install Components + +Install any Bolt Component via `npm` as it's docs suggest. If you were going to install the Card, you'd run: + +```bash +npm install --save @bolt/components-card +``` + +Then add it to `.boltrc.js`: + +```diff +module.exports = { + buildDir: 'www/build', + components: { + global: [ + '@bolt/global', ++ '@bolt/components-card', + ], + individual: [ + ], + }, +}; +``` + +Continue to do so with as many components as you'd like. + +### Build It + +Run this to build: + +```bash +npm run build +``` + +You can optionally run `npm run build:prod` for smaller files sizes - though it does take longer. CI should run this command. + +All files will build to the directory you've configured as your `buildDir`. diff --git a/apps/bolt-site/content/20-docs/10-getting-started/05-prerequisites.md b/apps/bolt-site/content/20-docs/10-getting-started/05-prerequisites.md new file mode 100644 index 0000000000..fb787f7152 --- /dev/null +++ b/apps/bolt-site/content/20-docs/10-getting-started/05-prerequisites.md @@ -0,0 +1,55 @@ +--- +title: Prerequisites +--- + +## Overview + +- Node v8.9 + +- Yarn v1 + +- Composer v1 + + - Prestissimo +- Yarn v1 + +- imagemagick & graphicsmagick +- PHP v7.1 + + +All package installation command below assume a Mac with Homebrew (`brew`) installed. If it's Linux, then it's *probably* just `apt-get`, if it's Windows, then emulate a Linux environment. + +## Node.js + +We use the latest [Long Term Support](https://github.com/nodejs/Release) Node version; 8.9.0 (Codename Carbon), which was released Oct 2017 & will be supported by them until April 2019. This ensures things are fast without overhead of polyfills in our build tools. + +### How to install + +```bash +brew install nvm +nvm install lts/carbon # v8.9 + +nvm alias default lts/carbon +``` + +If you don't have a Mac, just [read the node docs on installing](https://nodejs.org). + +## Install PHP Dependencies + +PHP 7.x or higher and Composer v1+ is required. + +``` +brew install composer +composer global require hirak/prestissimo +``` + +### GD and Imagick + +Used for responsive images / image optimization. + +``` +brew install imagemagick +brew install graphicsmagick +``` + +### Yarn + +Yarn v1.x or higher is required. Yarn caches every package it downloads so it never needs to download it again. It also parallelizes operations to maximize resource utilization so install times are faster than ever. + +``` +brew install yarn +``` diff --git a/apps/bolt-site/pages/20-docs/50-guides/10-browser-support.md b/apps/bolt-site/content/20-docs/10-getting-started/10-browser-support.md similarity index 100% rename from apps/bolt-site/pages/20-docs/50-guides/10-browser-support.md rename to apps/bolt-site/content/20-docs/10-getting-started/10-browser-support.md diff --git a/apps/bolt-site/pages/20-docs/_15-whats-new-in-bolt-v1.md b/apps/bolt-site/content/20-docs/10-getting-started/15-whats-new-in-bolt-v1.md similarity index 100% rename from apps/bolt-site/pages/20-docs/_15-whats-new-in-bolt-v1.md rename to apps/bolt-site/content/20-docs/10-getting-started/15-whats-new-in-bolt-v1.md diff --git a/apps/bolt-site/content/20-docs/20-visual-language/00-index.md b/apps/bolt-site/content/20-docs/20-visual-language/00-index.md new file mode 100644 index 0000000000..1adc006b7e --- /dev/null +++ b/apps/bolt-site/content/20-docs/20-visual-language/00-index.md @@ -0,0 +1,3 @@ +--- +title: Visual Language +--- \ No newline at end of file diff --git a/apps/bolt-site/pages/20-docs/40-visual-language/10-colors.md b/apps/bolt-site/content/20-docs/20-visual-language/10-colors.md similarity index 84% rename from apps/bolt-site/pages/20-docs/40-visual-language/10-colors.md rename to apps/bolt-site/content/20-docs/20-visual-language/10-colors.md index b11eaf42d0..a5acfa4bb3 100644 --- a/apps/bolt-site/pages/20-docs/40-visual-language/10-colors.md +++ b/apps/bolt-site/content/20-docs/20-visual-language/10-colors.md @@ -16,9 +16,7 @@ Pega websites need to to meet all web accessibility standards, including the min Whenever possible, lead with Indigo or White, and use the accent colors primarily as “pops” of color to add visual variety. Select colors, like orange and yellow, may also be used as calls to action or interactive elements, such as a Share button. - - - +![Pega's brand colors default to Base; use shades to create visual hierarchy where needed.](../../../images/color_brand.png) ## Color themes @@ -46,4 +44,4 @@ For body copy, such as articles, always use the x-light theme. Use status colors to indicate system information, success or error, or provide system warnings. Avoid using status colors in other contexts. - +![Use these colors only for system messaging or informational copy.](../../../images/color_messaging.png) \ No newline at end of file diff --git a/apps/bolt-site/pages/20-docs/40-visual-language/20-typography.md b/apps/bolt-site/content/20-docs/20-visual-language/20-typography.md similarity index 100% rename from apps/bolt-site/pages/20-docs/40-visual-language/20-typography.md rename to apps/bolt-site/content/20-docs/20-visual-language/20-typography.md diff --git a/apps/bolt-site/pages/20-docs/40-visual-language/30-iconography.md b/apps/bolt-site/content/20-docs/20-visual-language/30-iconography.md similarity index 100% rename from apps/bolt-site/pages/20-docs/40-visual-language/30-iconography.md rename to apps/bolt-site/content/20-docs/20-visual-language/30-iconography.md diff --git a/apps/bolt-site/pages/20-docs/40-visual-language/40-spacing.md b/apps/bolt-site/content/20-docs/20-visual-language/40-spacing.md similarity index 100% rename from apps/bolt-site/pages/20-docs/40-visual-language/40-spacing.md rename to apps/bolt-site/content/20-docs/20-visual-language/40-spacing.md diff --git a/apps/bolt-site/content/20-docs/30-patterns/00-index.md b/apps/bolt-site/content/20-docs/30-patterns/00-index.md new file mode 100644 index 0000000000..76e69cb5f6 --- /dev/null +++ b/apps/bolt-site/content/20-docs/30-patterns/00-index.md @@ -0,0 +1,5 @@ +--- +title: Patterns +--- + +Our [reusable components](/pattern-lab/index.html) provide flexible combinations of interface elements that enable designers and content authors to create web pages. Patterns are consistent combinations of components and elements that are frequently used across Pega websites. \ No newline at end of file diff --git a/apps/bolt-site/pages/20-docs/30-ui-patterns/bands.md b/apps/bolt-site/content/20-docs/30-patterns/bands.md similarity index 100% rename from apps/bolt-site/pages/20-docs/30-ui-patterns/bands.md rename to apps/bolt-site/content/20-docs/30-patterns/bands.md diff --git a/apps/bolt-site/pages/20-docs/30-ui-patterns/blockquotes.md b/apps/bolt-site/content/20-docs/30-patterns/blockquotes.md similarity index 100% rename from apps/bolt-site/pages/20-docs/30-ui-patterns/blockquotes.md rename to apps/bolt-site/content/20-docs/30-patterns/blockquotes.md diff --git a/apps/bolt-site/pages/20-docs/30-ui-patterns/buttons.md b/apps/bolt-site/content/20-docs/30-patterns/buttons.md similarity index 100% rename from apps/bolt-site/pages/20-docs/30-ui-patterns/buttons.md rename to apps/bolt-site/content/20-docs/30-patterns/buttons.md diff --git a/apps/bolt-site/pages/20-docs/30-ui-patterns/cards.md b/apps/bolt-site/content/20-docs/30-patterns/cards.md similarity index 100% rename from apps/bolt-site/pages/20-docs/30-ui-patterns/cards.md rename to apps/bolt-site/content/20-docs/30-patterns/cards.md diff --git a/apps/bolt-site/pages/20-docs/30-ui-patterns/forms.md b/apps/bolt-site/content/20-docs/30-patterns/forms.md similarity index 91% rename from apps/bolt-site/pages/20-docs/30-ui-patterns/forms.md rename to apps/bolt-site/content/20-docs/30-patterns/forms.md index a4df5ac2cc..1fe6f8ada5 100644 --- a/apps/bolt-site/pages/20-docs/30-ui-patterns/forms.md +++ b/apps/bolt-site/content/20-docs/30-patterns/forms.md @@ -39,7 +39,7 @@ Below are the most common elements and their considerations. Use a text input for a single line of text that doesn’t require formatting. Input data displays as plain text. Example uses include asking for names or inputting the title of a post.
- +
A static input contains helper text within the field.
@@ -53,7 +53,7 @@ Use a text input for a single line of text that doesn’t require formatting. In Use a textarea for longer passages of text, such as blog posts, descriptions or comments. Textareas generally support WYSIWYG editors, in-line images and standard formatting such as bold or italic text.
- +
Static input
@@ -82,7 +82,7 @@ Use a select list whenever you have a list longer than 7 items to choose from. B When disabled, form fields take on the `xlight` color as their background and lose the ability to interact with it.
- +
Form fields that are disabled take on a light background.
diff --git a/apps/bolt-site/content/20-docs/40-config/00-index.md b/apps/bolt-site/content/20-docs/40-config/00-index.md new file mode 100644 index 0000000000..2e90250377 --- /dev/null +++ b/apps/bolt-site/content/20-docs/40-config/00-index.md @@ -0,0 +1,17 @@ +--- +title: Configuration +--- + +Welcome to the home of the config! + +## Where does config come from? + +The order goes like this, as this list increases, the later override the earlier +Order inspired by https://www.npmjs.com/package/rc#standards + +1. Each property has a default +2. `userConfig` from `.boltrc.js` that's in same cwd as where `bolt` was ran, unless they use `--config-file path/to/.boltrc.js` +3. Env Vars with `bolt_` prefix; `bolt_verbosity=1` will override `config.verbosity` - case matters! +4. Certain command line options like `bolt build --verbosity 5` - not every config option is overridable this way. Run `bolt --help` or `bolt build --help` to see options. + +For both 3 & 4, it doesn't support deep merges, so only top level properties. diff --git a/apps/bolt-site/content/20-docs/40-config/10-basics.md b/apps/bolt-site/content/20-docs/40-config/10-basics.md new file mode 100644 index 0000000000..29608e389a --- /dev/null +++ b/apps/bolt-site/content/20-docs/40-config/10-basics.md @@ -0,0 +1,22 @@ +--- +title: Basics +--- + +These values can be set in `.boltrc.js` files to configure the build tools: + +``` +buildDir: + type: string + description: The buildDir config specifies where Bolt's compiled files are saved after every build. These are the generated scripts which will be requested by the browser. The build directory should be relative to the wwwDir setting (i.e. inside it). + +wwwDir: + type: string + title: Path to server root + description: "Where static files are served from. The wwwDir config specifies the public web distribution directory. This directory is commonly the root directory for a server, where all static files can be served. This directory is built and rebuilt directly from the source files. Note: We recommend this directory is not committed to a repository." + +prod: + type: boolean + description: Production build, will compress assets. + default: false + +``` diff --git a/apps/bolt-site/pages/20-docs/50-guides/30-custom-configuration/20-components.md b/apps/bolt-site/content/20-docs/40-config/20-components.md similarity index 100% rename from apps/bolt-site/pages/20-docs/50-guides/30-custom-configuration/20-components.md rename to apps/bolt-site/content/20-docs/40-config/20-components.md diff --git a/apps/bolt-site/pages/20-docs/50-guides/30-custom-configuration/30-envs.md b/apps/bolt-site/content/20-docs/40-config/30-envs.md similarity index 100% rename from apps/bolt-site/pages/20-docs/50-guides/30-custom-configuration/30-envs.md rename to apps/bolt-site/content/20-docs/40-config/30-envs.md diff --git a/apps/bolt-site/pages/20-docs/50-guides/30-custom-configuration/_40-extras.md b/apps/bolt-site/content/20-docs/40-config/40-extras.md similarity index 100% rename from apps/bolt-site/pages/20-docs/50-guides/30-custom-configuration/_40-extras.md rename to apps/bolt-site/content/20-docs/40-config/40-extras.md diff --git a/apps/bolt-site/content/20-docs/50-concepts/00-index.md b/apps/bolt-site/content/20-docs/50-concepts/00-index.md new file mode 100644 index 0000000000..dc0e40f98b --- /dev/null +++ b/apps/bolt-site/content/20-docs/50-concepts/00-index.md @@ -0,0 +1,3 @@ +--- +title: Concepts +--- diff --git a/apps/bolt-site/content/20-docs/50-concepts/10-standards/00-frontend-architecture-principles.md b/apps/bolt-site/content/20-docs/50-concepts/10-standards/00-frontend-architecture-principles.md new file mode 100644 index 0000000000..83c70defb4 --- /dev/null +++ b/apps/bolt-site/content/20-docs/50-concepts/10-standards/00-frontend-architecture-principles.md @@ -0,0 +1,273 @@ +# Bolt Frontend Architecture Principles + +**Part 1. Component Reuse and Composition** +1. Frequently used components should be the easiest ones to reuse and extend. +2. JavaScript components (and any underlying functionality) should be sharable and extendable. +3. Solve problems ~once~. Components rarely have to solve problems that other components / UI patterns don’t also have to solve. + +**Part 2. Component Consistency, Maintainability, & Emerging Standards** +1. Components should be authored consistently. +2. Emerging patterns should be discussed and shared. +3. Components should visually render and —whenever possible— include basic functionality and interactivity when JavaScript is disabled. +4. Components should support being used via a Twig include (which pulls in the web component’s custom element) or via the web component’s custom element directly. +5. Components should be encapsulated. If a totally separate component gets an API change, your component shouldn’t care. +6. Component composition > component inheritance. +7. Don’t repeat yourself. +8. Use the web component rendering engine best suited for a component’s needs (Preact vs HyperHTML) + +**Part 3. People-friendly API + Reasonable Defaults** +1. Components should work with the smallest number of config options — ideally none if possible! +2. Use appropriate default prop values for different situations. +3. Batch together “either/or” props that shouldn’t be mixed and matched. +4. Component props that aren’t unique should be broken down and shared. +5. Use consistent, easy to remember prop names. +6. Rarely used component props < utility classes which do the exact same thing. + +**Part 4. Design System Feedback Loop** +1. Capture and discuss reoccurring pain-points. +2. Identify gaps in the Design System. +3. Refactor, Release, or Merge and Iterate? + + +--- + +## Part 1. Component Reuse and Composition + +### Frequently used components should be the easiest ones to reuse and extend. + +* Is it reasonable to assume that this a lower-level “core” component that’ll get frequently composed with (or functionality extended by) other higher level components in the design system? +* Or does this component primarily live on it’s own (limited composition expected) and/or doesn’t include underlying functionality or behavior that would reasonably need to get reused or extended by other components? + +The more frequent a component is expected to be reused (as a whole + reusing and sharing the underlying pieces / functionality that make up that component), the greater the importance of making a component can get easily reused and extended! + + +### JavaScript components (and any underlying functionality) should be sharable and extendable. + +Where does the majority of a component’s logic live? In the render method? In external helper functions? In exported functions that other components could pull in? + +- Can the component’s logic and behavior be easily extended / shared via one of the following methods? + - A. Extending the component’s base Class (logic primarily exists as standalone methods that are NOT baked into the render method) + - B. Importing component-specific functions that are exported as standalone JS standalone functions (functionality worth sharing isn’t directly baked into the component) + - C. Importing helper functions used by the component (but aren’t exclusive to the component itself) + + +### Solve problems ~once~. Components rarely have to solve problems that other components / UI patterns don’t also have to solve. + +- Does this component have logic that ONLY applies to this one component or is there any core functionality, behavior, or logic that applies more broadly to a range of components in the design system (especially components that already exist)? + +--- + +## Part 2. Component Consistency, Maintainability, & Emerging Standards + +### Components should be authored consistently. +- Does this component feel right at home with other recently authored components? +- Are the approaches, coding style, libraries, architectural patterns, etc in line with work that has been done elsewhere in the design system? + +### Emerging patterns should be discussed and shared. +- Or does anything (new technique, different / alternative approach, unexplored territory, experimental work, etc) stand out? +- If so, those things should get spelled out, documented and demoed with the team — not to get buy-in mind you, but to educate on how the system is evolving and growing! + +### Components should visually render and —whenever possible— include basic functionality and interactivity when JavaScript is disabled. +- The more essential and highly visible a component is, the more important the component looks —and when appropriate, behaves— when JavaScript is disabled, takes a long time to load or is unexpectedly broken. + +### Components should support being used via a Twig include (which pulls in the web component’s custom element) or via the web component’s custom element directly. +- Components being pre-rendered in Twig should automatically hydrate using the initial data passed along by the server and take over once the JavaScript kicks in. + +### Components should be encapsulated. If a totally separate component gets an API change, your component shouldn’t care. +- Is this component tied at the hip to one or more (“related but technically standalone”) nested components / “behaviors” in the design system OR does this component “just work” if any nested components have their APIs updated? + +> As a gut check, let’s say we added a new “isFancy” boolean prop to one of the component’s “related but technically separate” components, say, an icon. Do we need to update this component’s API every time the API of a nested component (the icon in this case) changes? If so, this means our two components are + +Examples we should be looking out for include icons, links, text and buttons — all of which are commonly used together but are nonetheless separate standalone components / component behaviors with their own separate API. + + +### Component composition > component inheritance. + +A component’s API needs to *primarily* focus on passing along data to the component itself (which can include how nested components are positioned / behave) + whatever nested children should get passed along. + +Shorthand API config options to nested components are ok for frequently nested subcomponents *however* aren’t a replacement for the full “longhand” version of nesting something. + + +- In components that include a shorthand way to pre-configure nested subcomponents and behavior (ex. nested icons or linkable behavior), how are we handling additional subcomponent options that are out of scope for what a ‘shorthand” API should reasonably handle? + + +*Probably* Reasonable: +``` + + + + + Hello world + + + + + + Hello world + +``` + +*Probably* **Not** Reasonable: +``` + + + Hello world + + + + + + Hello world + + + +``` + + +> When in doubt, it’s better to avoid including a shorthand API for a nested sub-component entirely if it’ll mean having a component with a smaller, more consistent, easier to maintainable API. + + +### Don’t repeat yourself. +Look at the component’s Twig, Sass and JavaScript files independently. + +- Are there any patterns or logic that stick out as occurring multiple times? +- Could a loop or helper function *significantly* cut back on the amount of code getting written? +- Does adding a new value to a list of already available options involve more than updating an array? +- Does adding a new prop type require copying and pasting the same couple lines of code over and over again? + + + +### Use the web component rendering engine best suited for a component’s needs (Preact vs HyperHTML) +Currently there are two different component rendering engines available in Bolt to handle different use cases (each with their pros and cons — see below), Preact (JSX) and HyperHTML (Template Literals). + +While both are great choices and would both work great in many situations (and in those cases, which engine to use is really up to the author’s personal preference), there are 2 important use cases that must get considered when settling on one renderer over the other. + + +**1. Dynamic Template Tags** +Do you need dynamic template tags in your HTML (ex. dynamically switch between an `

` or a `

` depending on a prop passed along)? + +If so, currently **only** Preact has this use case figured out (but this could change down the road). Currently, the only known way to have dynamic tags in HyperHTML involves lots of “if / else” statements and manually doing the work yourself. + +**Dynamic Support** +On the flip side, does the component need to support conditionally rendered `` tags in the template based on native Shadow DOM support? If not, would a heavy handed `this.innerHTML` JavaScript call potentially break any event bindings? + +If so, currently **only** HyperHTML has this use case figured out (however as with Dynamic Tags, this could ultimately change down the road). + + +### Preact vs HyperHTML Renderers + +**Option A. [Preact](https://github.com/bolt-design-system/bolt/blob/master/packages/core/renderers/renderer-preact.js)** +- **Pros** + - JSX templates = POWERFUL + - **Tons** of examples out there for Preact / React + - Relatively straightforward to port React components over from NPM + - Ability to import and nest JSX components in other components (ex. ``; + buttonElement = this.hyper.wire(this)` + + `; } - buttonElement = renderInnerSlots(buttonElement); - + // Add inline `; } @@ -32,21 +36,21 @@ export function withHyperHtml(Base = HTMLElement) { slot(name) { if (this.useShadow && hasNativeShadowDomSupport) { if (name === 'default') { - return wire(this)` + return hyper.wire(this)` `; } else { - return wire(this)` + return hyper.wire(this)` `; } } else { if (name === 'default') { - return wire(this)` + return hyper.wire(this)` ${this.slots.default} `; } else if (this.slots[name]) { - return wire(this)` + return hyper.wire(this)` ${this.slots[name]} `; } else { diff --git a/packages/core/renderers/renderer-lit-html.js b/packages/core/renderers/renderer-lit-html.js deleted file mode 100644 index 89533acf09..0000000000 --- a/packages/core/renderers/renderer-lit-html.js +++ /dev/null @@ -1,58 +0,0 @@ -import { html, render } from 'lit-html'; - -import { - withComponent, - shadow, - props, - hasNativeShadowDomSupport, - findParentTag, -} from '../utils'; -import { BoltBase } from './bolt-base'; - -export { html, render } from 'lit-html'; - -export function withLitHtml(Base = HTMLElement) { - return class extends withComponent(BoltBase(Base)) { - static props = { - onClick: props.string, - onClickTarget: props.string, - }; - - constructor(...args) { - super(...args); - } - - renderStyles(styles) { - if (styles) { - return html``; - } - } - - slot(name) { - if (typeof this.slots[name] === 'undefined') { - this.slots[name] = []; - } - - if (this.useShadow && hasNativeShadowDomSupport) { - if (name === 'default') { - return html``; - } else { - return html``; - } - } else { - if (name === 'default') { - return html`${this.slots.default}`; - } else if (this.slots[name] && this.slots[name] !== []) { - return html`${this.slots[name]}`; - } else { - return ''; // No slots assigned so don't return any markup. - console.log(`The ${name} slot doesn't appear to exist...`); - } - } - } - - renderer(root, call) { - render(call(), root); - } - }; -} diff --git a/packages/core/renderers/renderer-preact.js b/packages/core/renderers/renderer-preact.js index dc97350c14..cbd294c869 100644 --- a/packages/core/renderers/renderer-preact.js +++ b/packages/core/renderers/renderer-preact.js @@ -2,7 +2,10 @@ // Temp working version of @skatejs/renderer-preact till SkateJS fixes this upstream in the SkateJS monorepo import { name, withComponent, shadow, props } from 'skatejs'; -import preact, { h, render } from 'preact'; +import preact, { h, render, Component } from 'preact'; +import html from 'preact-html'; +import { hasNativeShadowDomSupport } from '../utils/environment'; +import { findParentTag } from '../utils/find-parent-tag'; import { BoltBase } from './bolt-base'; // TODO make this a Symbol() when it's supported. @@ -34,8 +37,6 @@ function teardownPreact() { preact.options.vnode = oldVnode; } -export { h } from 'preact'; - export function withPreact(Base = HTMLElement) { return class extends withComponent(BoltBase(Base)) { get props() { @@ -48,6 +49,9 @@ export function withPreact(Base = HTMLElement) { constructor(...args) { super(...args); + super.setupShadow(); + + this.html = html; } renderStyles(styles) { diff --git a/packages/core/sandbox/bolt-component.js b/packages/core/sandbox/bolt-component.js new file mode 100644 index 0000000000..05552e81e7 --- /dev/null +++ b/packages/core/sandbox/bolt-component.js @@ -0,0 +1,23 @@ +import { Component, h, render, unmountComponentAtNode } from "preact"; + +export default function BoltComponent(ComponentClass, CSSString) { + class BoltComponentClass extends Component { + setup(node) { + if (!node) { + console.warn(`BoltComponent failed to create shadow dom for ${ComponentClass.displayName || "component"}, because node was falsy.`); + return; + } + + this.shadow = node.createShadowRoot(); + this._component = render(, this.shadow); + this.shadow.innerHTML += ``; + } + + render() { + return

; + } + } + BoltComponentClass.displayName = `BoltComponent(${ComponentClass.displayName})`; + + return BoltComponentClass; +} diff --git a/packages/core/sandbox/component.js b/packages/core/sandbox/component.js new file mode 100644 index 0000000000..635fb3f7a8 --- /dev/null +++ b/packages/core/sandbox/component.js @@ -0,0 +1,52 @@ +// Example taken from recent SkateJS 5.0.0-beta.4 site example + +import { html } from 'lit-html/lib/lit-extended'; +import { define, props, withComponent } from 'skatejs/dist/esnext'; +import withLitHtml from '@skatejs/renderer-lit-html/dist/node'; +import { value } from 'yocss'; + +// export const Component = class extends withComponent(withLitHtml()) { +// $ = html; +// get $style() { +// return style(this.context.style, value(...Object.values(this.css || {}))); +// } +// }; + +export function style(...css) { + return html``; +} + +export const withLoadable = (props: Object) => + define( + class extends Component { + static is = props.is; + props: { + format: any, + loader: any, + loading: any, + useShadowRoot: boolean + }; + props = props; + get renderRoot() { + return props.useShadowRoot ? super.renderRoot : this; + } + connecting() { + const loaded = this.loading; + if (loaded) { + this.state = { loaded }; + } + if (this.loader) { + this.loader().then(r => { + const loaded = r.default || r; + if (loaded) { + this.state = { loaded }; + } + }); + } + } + render() { + const { loaded } = this.state; + return this.$`${typeof loaded === 'function' ? new loaded() : loaded}`; + } + } + ); \ No newline at end of file diff --git a/packages/core/sandbox/events.js b/packages/core/sandbox/events.js new file mode 100644 index 0000000000..28aeb36122 --- /dev/null +++ b/packages/core/sandbox/events.js @@ -0,0 +1,271 @@ +/* eslint func-names: ["error", "never"] */ + +// Heavily inspired by https://css-tricks.com/managing-state-css-reusable-javascript-functions-part-2/ + +// CLOSEST PARENT HELPER FUNCTION +// ---------------------------------------------- + +export function closestParent(child, match) { + if (!child || child === document) return null; + if (child.classList.contains(match) || child.nodeName.toLowerCase() === match) return child; + return closestParent(child.parentNode, match); +}; + +// REUSABLE FUNCTION +// ---------------------------------------------- + +// Change function +export function eventHandler(elem) { + + console.log('bolt eventHandler!'); + + let dataState; + let dataStateBehaviour; + let dataStateScope; + let dataStateElement; + + // Grab data-state list and convert to array + dataState = elem.getAttribute(`data-state`); + dataState = dataState.split(`, `); + + // Grab data-state-behaviour list if present and convert to array + if (elem.getAttribute(`data-state-behaviour`)) { + dataStateBehaviour = elem.getAttribute(`data-state-behaviour`); + dataStateBehaviour = dataStateBehaviour.split(`, `); + } + + // Grab data-scope list if present and convert to array + if (elem.getAttribute(`data-state-scope`)) { + dataStateScope = elem.getAttribute(`data-state-scope`); + dataStateScope = dataStateScope.split(`, `); + } + + // Grab data-state-element list and convert to array + // If data-state-element isn't found, pass self, set scope to self if none is present, essentially replicating "this" + if (elem.getAttribute(`data-state-element`)) { + dataStateElement = elem.getAttribute(`data-state-element`); + dataStateElement = dataStateElement.split(`, `); + } else { + dataStateElement = []; + dataStateElement.push(elem.classList[0]); + if (!dataStateScope) { + dataStateScope = dataStateElement; + } + } + + // Find out which has the biggest length between states and elements and use that length as loop number + /** This is to make sure situations where we have one data-state-element value and many data-state values + * are correctly setup + */ + const dataLength = Math.max(dataStateElement.length, dataState.length); + + // Loop + for (let b = 0; b < dataLength; b++) { + let dataStateElementValue; + let cachedScope; + let elemRef; + let elemState; + let elemBehaviour; + + // If a data-state-element value isn't found, use last valid one + if (dataStateElement[b] !== undefined) { + dataStateElementValue = dataStateElement[b]; + } + + // If scope isn't found, use last valid one + if (dataStateScope && dataStateScope[b] !== undefined) { + cachedScope = dataStateScope[b]; + } else if (cachedScope) { + dataStateScope[b] = cachedScope; + } + + // Grab elem references, apply scope if found + if (dataStateScope && dataStateScope[b] !== `false`) { + // Grab parent + const elemParent = closestParent(elem, dataStateScope[b]); + + // Grab all matching child elements of parent + elemRef = elemParent.querySelectorAll(`.${dataStateElementValue}`); + + // Convert to array + elemRef = Array.prototype.slice.call(elemRef); + + // Add parent if it matches the data-state-element and fits within scope + if (elemParent.classList.contains(dataStateElementValue)) { + elemRef.unshift(elemParent); + } + } else { + elemRef = document.querySelectorAll(`.${dataStateElementValue}`); + } + + // Grab state we will add + // If one isn't found, keep last valid one + if (dataState[b] !== undefined) { + elemState = dataState[b]; + } + + // Grab behaviour if any exists + // If one isn't found, keep last valid one + if (dataStateBehaviour && dataStateBehaviour[b] !== undefined) { + elemBehaviour = dataStateBehaviour[b]; + } + + // Do + for (let c = 0; c < elemRef.length; c++) { + // Find out if we're manipulating aria-attributes or classes + let toggleAttr; + if (elemRef[c].getAttribute(elemState)) { + toggleAttr = true; + } else { + toggleAttr = false; + } + + if (elemBehaviour === `add`) { + if (toggleAttr) { + elemRef[c].setAttribute(elemState, true); + } else { + elemRef[c].classList.add(elemState); + } + } else if (elemBehaviour === `remove`) { + if (toggleAttr) { + elemRef[c].setAttribute(elemState, false); + } else { + elemRef[c].classList.remove(elemState); + } + } else if (toggleAttr) { + if (elemRef[c].getAttribute(elemState) === `true`) { + elemRef[c].setAttribute(elemState, false); + } else { + elemRef[c].setAttribute(elemState, true); + } + } else { + elemRef[c].classList.toggle(elemState); + } + } + } +} + +// Init function +export function initDataState(elem) { + // Detect data-swipe attribute before we do anything, as it's optional + // if (elem.getAttribute(`data-state-swipe`)) { + // // Grab swipe specific data from data-state-swipe + // let elemSwipe = elem.getAttribute(`data-state-swipe`).split(`, `); + // let direction = elemSwipe[0]; + // let elemSwipeBool = elemSwipe[1]; + // let currentElem = elem; + + // // If the behaviour flag is set to "false", or not set at all, then assign our click event + // if (elemSwipeBool === `false` || !elemSwipeBool) { + // // Assign click event + // elem.addEventListener(`click`, function (e) { + // // Prevent default action of element + // e.preventDefault(); + // // Run state function + // processChange(this); + // }); + // } + // // Use our swipeDetect helper function to determine if the swipe direction matches our desired direction + // swipeDetect(elem, (swipedir) => { + // // Run state function + // if (swipedir === direction) processChange(currentElem); + // }); + // } else { + // Assign click event + elem.addEventListener(`click`, function (e) { + // Prevent default action of element + e.preventDefault(); + // Run state function + eventHandler(this); + }); + // } + // Add keyboard event for enter key or space to mimic anchor functionality + elem.addEventListener(`keypress`, function (e) { + if (e.which !== 13 && e.which !== 32) return; + // Prevent default action of element + e.preventDefault(); + // Run state function + eventHandler(this); + }); +} + +// Run when DOM has finished loading +// document.addEventListener(`DOMContentLoaded`, () => { +// // Grab all elements with required attributes +// const elems = document.querySelectorAll(`[data-state]`); + +// // Loop through our matches and add click events +// for (let a = 0; a < elems.length; a++) initDataState(elems[a]); + +// // Setup mutation observer to track changes for matching elements added after initial DOM render +// const observer = new MutationObserver(((mutations) => { +// mutations.forEach((mutation) => { +// for (let d = 0; d < mutation.addedNodes.length; d++) { +// // Check if we're dealing with an element node +// if (typeof mutation.addedNodes[d].getAttribute === `function` && mutation.addedNodes[d].getAttribute(`data-state`)) { +// initDataState(mutation.addedNodes[d]); +// } +// } +// }); +// })); + +// // Define type of change our observer will watch out for +// observer.observe(document.body, { +// childList: true, +// subtree: true, +// }); +// }); +// }()); + + + + + +// SWIPE DETECT HELPER +// ---------------------------------------------- + +// http://www.javascriptkit.com/javatutors/touchevents2.shtml +// const swipeDetect = function (el, callback) { +// const touchsurface = el; +// let swipedir; +// let startX; +// let startY; +// let distX; +// let distY; +// const threshold = 100; // required min distance traveled to be considered swipe +// const restraint = 100; // maximum distance allowed at the same time in perpendicular direction +// const allowedTime = 300; // maximum time allowed to travel that distance +// let elapsedTime; +// let startTime; +// let eventObj; +// const handleswipe = callback || function (swipedir, eventObj) {}; + +// touchsurface.addEventListener(`touchstart`, (e) => { +// const touchobj = e.changedTouches[0]; +// swipedir = `none`; +// startX = touchobj.pageX; +// startY = touchobj.pageY; +// startTime = Date.now(); // record time when finger first makes contact with surface +// eventObj = e; +// }, false); + +// touchsurface.addEventListener(`touchend`, (e) => { +// const touchobj = e.changedTouches[0]; +// distX = touchobj.pageX - startX; // get horizontal dist traveled by finger while in contact with surface +// distY = touchobj.pageY - startY; // get vertical dist traveled by finger while in contact with surface +// elapsedTime = Date.now() - startTime; // get time elapsed + +// if (elapsedTime <= allowedTime) { +// // first condition for awipe met +// if (Math.abs(distX) >= threshold && Math.abs(distY) <= restraint) { +// // 2nd condition for horizontal swipe met +// swipedir = distX < 0 ? `left` : `right`; // if dist traveled is negative, it indicates left swipe +// } else if (Math.abs(distY) >= threshold && Math.abs(distX) <= restraint) { +// // 2nd condition for vertical swipe met +// swipedir = distY < 0 ? `up` : `down`; // if dist traveled is negative, it indicates up swipe +// } +// } + +// handleswipe(swipedir, eventObj); +// }, false); +// }; diff --git a/packages/core/sandbox/get-meta.js b/packages/core/sandbox/get-meta.js new file mode 100644 index 0000000000..944c1c6aa8 --- /dev/null +++ b/packages/core/sandbox/get-meta.js @@ -0,0 +1,20 @@ +/* eslint-disable */ +// getMeta function: get a meta tag by name +// NOTE: meta tag must be in the HTML source before this script is included in order to guarantee it'll be found + +var Meta = { + get: function (metaname) { + var metas = window.document.getElementsByTagName('meta'), + meta; + + for (var i = 0; i < metas.length; i++) { + if (metas[i].name && metas[i].name === metaname) { + meta = metas[i]; + break; + } + } + return meta; + } +}; + +module.exports = Meta; \ No newline at end of file diff --git a/packages/core/sandbox/sizes.js b/packages/core/sandbox/sizes.js new file mode 100644 index 0000000000..9ce3f03c19 --- /dev/null +++ b/packages/core/sandbox/sizes.js @@ -0,0 +1,13 @@ +const sizes = { + xsmall: 'xsmall', + small: 'small', + medium: 'medium', + large: 'large', + xlarge: 'xlarge' +}; +module.exports.sizes = sizes; + +function cssClassForSize( size, prefix ) { + return size ? `${prefix}${sizes.size}` : null; +} +module.exports.cssClassForSize = cssClassForSize; \ No newline at end of file diff --git a/packages/core/sandbox/skate-component.js b/packages/core/sandbox/skate-component.js new file mode 100644 index 0000000000..63803c1bc3 --- /dev/null +++ b/packages/core/sandbox/skate-component.js @@ -0,0 +1,105 @@ +import { h as preactH } from 'preact'; +import { define, props, withComponent, withUpdate } from 'skatejs'; +import withPreact from '@skatejs/renderer-preact'; +import hasNativeShadowDomSupport from './environment'; + + +// Compatiblity layer for renames. +export const Component = class extends withComponent(withPreact()) { + childrenDidUpdate() { + if (this.childrenChangedCallback) { + return this.childrenChangedCallback(...args); + } + if (super.childrenDidUpdate) { + return super.childrenDidUpdate(...args); + } + } + willUpdate(...args) { + if (this.propsSetCallback) { + return this.propsSetCallback(...args); + } + if (super.willUpdate) { + return super.willUpdate(...args); + } + } + shouldUpdate(...args) { + if (this.propsUpdatedCallback) { + return this.propsUpdatedCallback(...args); + } + if (super.shouldUpdate) { + return super.shouldUpdate(...args); + } + } + didUpdate(...args) { + if (this.propsChangedCallback) { + return this.propsChangedCallback(...args); + } + if (super.didUpdate) { + return super.didUpdate(...args); + } + } + render(...args) { + if (this.renderCallback) { + return this.renderCallback(...args); + } + if (super.render) { + return super.render(...args); + } + } + renderCallback(...args) { + if (super.renderCallback) { + return super.renderCallback(...args); + } + if (super.render) { + return super.render(...args); + } + } + renderer(...args) { + if (this.rendererCallback) { + return this.rendererCallback(...args); + } + if (super.renderer) { + return super.renderer(...args); + } + } + didRender(...args) { + if (this.renderedCallback) { + return this.renderedCallback(...args); + } + if (super.didRender) { + return super.didRender(...args); + } + } +}; + +function args(fn) { + return fn + .toString() + .match(/\(([^)]*)\)/)[1] + .split(',') + .map(name => name.split('=')[0].trim()); +} + +export function component(render) { + const fnArgs = args(render); + class Comp extends Component { + static props = fnArgs.reduce((prev, curr) => { + prev[curr] = { attribute: { source: true } }; + return prev; + }, {}); + render() { + return render.call(this, ...fnArgs.map(n => this[n])); + } + } + + // Allows the component to have a tag name hint based off the render function + // name. + Object.defineProperty(Comp, 'name', { + configurable: true, + value: render.name + }); + + return define(Comp); +} + +export const h = preactH; \ No newline at end of file diff --git a/packages/core/sandbox/skate-shadow.js b/packages/core/sandbox/skate-shadow.js new file mode 100644 index 0000000000..85e2949b76 --- /dev/null +++ b/packages/core/sandbox/skate-shadow.js @@ -0,0 +1,169 @@ +const { + document, + DocumentFragment, + HTMLElement, + NodeFilter, + NodeList +} = window; +const { SHOW_ELEMENT } = NodeFilter; +const parser = document.createElement('div'); +const _childNodes = Symbol(); +const _scopeName = Symbol(); +const _scopeExists = Symbol(); + +function createTreeWalker(root) { + return document.createTreeWalker(root, SHOW_ELEMENT); +} + +function doIfIndex(host, refNode, callback, otherwise) { + const chren = host.childNodes; + const index = chren.indexOf(refNode); + + if (index > -1) { + callback(index, chren); + } else if (otherwise) { + otherwise(chren); + } +} + +function makeNodeList(nodeList = []) { + if (nodeList instanceof NodeList) { + nodeList = Array.from(nodeList); + } + nodeList.item = function (index) { + return this[index]; + }; + return nodeList; +} + +function ensureArray(refNode) { + return refNode instanceof DocumentFragment + ? Array.from(refNode.childNodes) + : [refNode]; +} + +function reParentOne(refNode, newHost) { + Object.defineProperty(refNode, 'parentNode', { + configurable: true, + value: newHost + }); + return refNode; +} + +function reParentAll(nodeList, newHost) { + return nodeList.map(n => reParentOne(n, newHost)); +} + +function getScopeName(host) { + return ( + host[_scopeName] || + (host[_scopeName] = + 'scoped-' + + Math.random() + .toString(36) + .substring(2, 8)) + ); +} + +export const Shadow = (Base = HTMLElement) => + class extends Base { + get children() { + return this.childNodes.filter(n => n.nodeType === 1); + } + get firstChild() { + return this.childNodes[0] || null; + } + get lastChild() { + const chs = this.childNodes; + return chs[chs.length - 1] || null; + } + get innerHTML() { + return this.childNodes.map(n => n.innerHTML || n.textContent).join(''); + } + set innerHTML(innerHTML) { + parser.innerHTML = innerHTML; + this.childNodes = reParentAll(makeNodeList(parser.childNodes), this); + } + get outerHTML() { + const { attributes, localName } = this; + const attrsAsString = Array.from(attributes).map( + a => ` ${a.name}="${a.value}"` + ); + return `<${localName}${attrsAsString}>${this.innerHTML}`; + } + set outerHTML(outerHTML) { + // TODO get attributes and apply to custom element host. + parser.outerHTML = outerHTML; + this.childNodes = reParentAll(makeNodeList(parser.childNodes), this); + } + get textContent() { + return this.childNodes.map(n => n.textContent).join(''); + } + set textContent(textContent) { + this.childNodes = reParentAll( + makeNodeList([document.createTextNode(textContent)]), + this + ); + } + appendChild(newNode) { + this.childNodes = this.childNodes.concat( + reParentAll(ensureArray(newNode), this) + ); + return newNode; + } + insertBefore(newNode, refNode) { + newNode = reParentAll(ensureArray(newNode), this); + doIfIndex( + this, + refNode, + (index, chren) => { + this.childNodes = chren.concat( + chren.slice(0, index + 1), + newNode, + chren.slice(index) + ); + }, + chren => { + this.childNodes = chren.concat(newNode); + } + ); + return newNode; + } + removeChild(refNode) { + doIfIndex(this, refNode, (index, chren) => { + reParentOne(refNode, null); + this.childNodes = chren.splice(index, 1).concat(); + }); + return refNode; + } + replaceChild(newNode, refNode) { + doIfIndex(this, refNode, (index, chren) => { + reParentOne(refNode, null); + this.childNodes = chren.concat( + chren.slice(0, index), + reParentAll(ensureArray(newNode)), + chren.slice(index) + ); + }); + return refNode; + } + attachShadow({ mode }) { + // Currently we just use an extra element. Working around this involves + // using a proxy element that modifies the host, but re-parents each node + // to look like the parent is the shadow root. + const shadowRoot = document.createElement('shadowroot'); + + // Remove existing content and add the shadow root to append to. Appending + // the shadow root isn't necessary if using the proxy as noted above, but + // resetting the innerHTML is. + super.innerHTML = ''; + super.appendChild(shadowRoot); + + // Emulate native { mode }. + if (mode === 'open') { + Object.defineProperty(this, 'shadowRoot', { value: shadowRoot }); + } + + return shadowRoot; + } + }; \ No newline at end of file diff --git a/packages/core/sandbox/skate-style.js b/packages/core/sandbox/skate-style.js new file mode 100644 index 0000000000..3cdb14bf0a --- /dev/null +++ b/packages/core/sandbox/skate-style.js @@ -0,0 +1,109 @@ +// Special thanks to Jason Miller (@_developit) for this idea. +// Link: https://jsfiddle.net/developit/vLzdhcg0/ + +import { h } from './component'; +import { isBrowser } from './environment'; + +const _mo = Symbol(); +const _scopeExists = Symbol(); +const _scopeName = Symbol(); +const _scopes = Symbol(); + +const scopes = {}; + +function walk(root, call) { + const chs = root.children; + const chsLen = chs.length; + call(root); + for (let a = 0; a < chsLen; a++) { + walk(chs[a], call); + } +} + +function getScopeName(css) { + return ( + scopes[css] || + (scopes[css] = Math.random() + .toString(36) + .substring(2, 8)) + ); +} + +function scopeHost(host, scopeNames) { + scopeNames.forEach(scopeName => host.setAttribute(`host-${scopeName}`, '')); +} + +function scopeSlot(node, scopeNames) { + if (node.nodeName === 'SLOT') { + scopeNames.forEach(scopeName => node.setAttribute(`slot-${scopeName}`, '')); + } +} + +export function scopeCss(host, css) { + if (isBrowser) { + return css; + } + + css = '' + css; + const scopeName = getScopeName(css); + const hostSelector = `[host-${scopeName}]`; + const slotSelector = `[slot-${scopeName}]`; + return css + .replace(/:host/g, hostSelector) + .replace(/::slotted\(([^)]+)\)/g, slotSelector) + .replace(/(\.[^{]+)/g, `${hostSelector} $1`); +} + +export function scopeNode(host, css) { + if (isBrowser) { + return; + } + + const scopeName = getScopeName(css); + + if (!host[_scopes]) { + host[_scopes] = []; + } + + if (host[_scopes].indexOf(scopeName) === -1) { + host[_scopes].push(scopeName); + } + + // Only add a single mutation observer to scope a shadow root. + if (!host[_mo]) { + host[_mo] = new MutationObserver(muts => { + muts.addedNodes && + muts.addedNodes.forEach(node => scopeSlot(node, host[_scopes])); + }); + host[_mo].observe(host.shadowRoot, { + childList: true, + subtree: true + }); + } + + // Apply initial scoping. + scopeHost(host, host[_scopes]); + walk(host.shadowRoot, node => scopeSlot(node, host[_scopes])); +} + +export function style(host, css) { + // No host means global styles. + if (!host.nodeType) { + css = host; + host = null; + } + + // Simply return if running client side or dedupe head styles and only scope if not global if on the server. + if (host && isBrowser) { + return ; + } else { + const newStyle = document.createElement('style'); + newStyle.textContent = host ? scopeCss(host, css) : css; + document.head.appendChild(newStyle); + } + + // Only scope if there's a host. + if (host) { + scopeNode(host, css); + } +} \ No newline at end of file diff --git a/packages/core/sandbox/style.js b/packages/core/sandbox/style.js new file mode 100644 index 0000000000..eef3889031 --- /dev/null +++ b/packages/core/sandbox/style.js @@ -0,0 +1,39 @@ +import { h } from 'skatejs'; + +const $template = Symbol(); + +/** + * Applies the stylesheet to the given element. Used in the `renderCallback` + * + * @example + * import style from 'x-global-header/src/util/style.js'; + * import css from './style.css'; + * + * export class Foo extends Component { + * ... + * + * renderCallback() { + * return ( + *
+ * { style(this, css) } + *

+ * Hello, world! + *

+ *
+ * ); + * } + * } + * + * @param {HTMLElement} elem the element to apply the styles to + * @param {string} css the stylesheet to add to the element + * @returns {HTMLElement|undefined} might return a `style` tag, or nothing at all + */ +export default function style(elem, css) { + if (ShadyCSS.nativeShadow) { + return (); + } + + const template = elem[$template] || (elem[$template] = document.createElement('template')); + template.innerHTML = ``; + ShadyCSS.prepareTemplate(template, elem.localName); +} diff --git a/packages/core/sandbox/styled-mixin.js b/packages/core/sandbox/styled-mixin.js new file mode 100644 index 0000000000..72bd62104f --- /dev/null +++ b/packages/core/sandbox/styled-mixin.js @@ -0,0 +1,74 @@ +// const { nativeShadow } = ShadyCSS; +import { hasNativeShadowDomSupport } from './environment'; +const $template = Symbol(); + + +/** + * Generate a scoped style element for the given element + * + * @see https://github.com/skatejs/skatejs/issues/1027#issuecomment-277382741 + * @private + * @param {Component} elem the element to generate a style for + * @param {string} css the stylesheet to scope + * @return {HTMLElement} the style elememnt to add to the component + */ +function style(elem, css) { + if (hasNativeShadowDomSupport) { + const styleElement = document.createElement('style'); + const styleContent = document.createTextNode(css); + + styleElement.appendChild(styleContent); + + return styleElement; + } else { + const template = elem[$template] || (elem[$template] = document.createElement('template')); + + template.innerHTML = ``; + window.ShadyCSS.prepareTemplate(template, elem.localName); + + return template; + } +} + +/** + * Mixin to add automatic styling to a Skatejs component + * + * @param {skatejs~Component} superclass the superclass to add features to + * @returns {skatejs~Component} the new class, with added features + */ +export default function StyledMixin(superclass) { + return class extends superclass { + /** + * By setting the `styleSheet` attribute on a class, a new custom + * element can be created that will automatically have the styles + * injected into the component. + * + * In browsers that have native support for the ShadowDOM, the + * stylesheet will be added such that the styles are all scoped to + * that custom element. + * + * In browsers that do not have native support for the ShadowDOM, + * ShadyCSS will be used to scope the styles to the element. Note + * that ShadyCSS can only scope `:host` and class definitions, so + * styles should only be defined that use those techniques. + * + * @property {string} styleSheet the stylesheet for this element + * @abstract + */ + // static styleSheet = '' + + /** + * Adds a ` + + + - - + + /*! loadCSS rel=preload polyfill. [c]2017 Filament Group, Inc. MIT License */ + !function(t){"use strict";t.loadCSS||(t.loadCSS=function(){});var e=loadCSS.relpreload={};if(e.support=function(){var e;try{e=t.document.createElement("link").relList.supports("preload")}catch(t){e=!1}return function(){return e}}(),e.bindMediaToggle=function(t){function e(){t.media=a}var a=t.media||"all";t.addEventListener?t.addEventListener("load",e):t.attachEvent&&t.attachEvent("onload",e),setTimeout(function(){t.rel="stylesheet",t.media="only x"}),setTimeout(e,3e3)},e.poly=function(){if(!e.support())for(var a=t.document.getElementsByTagName("link"),n=0;n + + @@ -42,30 +45,162 @@ +
- + + + + + +
-
+
@@ -372,81 +507,62 @@

{{ title }}

- + - + - - - + + + - - + + + + + + diff --git a/packages/uikit-workshop/dist/splash-screen.html b/packages/uikit-workshop/dist/splash-screen.html new file mode 100644 index 0000000000..bd8323676e --- /dev/null +++ b/packages/uikit-workshop/dist/splash-screen.html @@ -0,0 +1,146 @@ + + + + + + + + + Initializing First Bolt Build... + + + + + +
+
+
Initializing First Build
+
+ + + +
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/packages/uikit-workshop/dist/styleguide/css/pattern-lab--iframe-loader.css b/packages/uikit-workshop/dist/styleguide/css/pattern-lab--iframe-loader.css deleted file mode 100644 index 8d32d4ec72..0000000000 --- a/packages/uikit-workshop/dist/styleguide/css/pattern-lab--iframe-loader.css +++ /dev/null @@ -1 +0,0 @@ -@-webkit-keyframes animateIn{from{-webkit-transform:translate3d(-50%,-100%,0);transform:translate3d(-50%,-100%,0);opacity:0}to{opacity:1;-webkit-transform:translate3d(-50%,calc(3rem - 50%),0);transform:translate3d(-50%,calc(3rem - 50%),0)}}@keyframes animateIn{from{-webkit-transform:translate3d(-50%,-100%,0);transform:translate3d(-50%,-100%,0);opacity:0}to{opacity:1;-webkit-transform:translate3d(-50%,calc(3rem - 50%),0);transform:translate3d(-50%,calc(3rem - 50%),0)}}@-webkit-keyframes rotate{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes rotate{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(360deg);transform:rotate(360deg)}}.pl-c-loader{z-index:1000;position:absolute;top:0;left:50%;margin:auto;max-width:25rem;width:calc(90vw - 2rem);border-radius:3px;background:rgba(0,0,0,.9);-webkit-transform:translate3d(-50%,-100%,0);transform:translate3d(-50%,-100%,0);-webkit-transition:opacity .3s ease,-webkit-transform .3s ease;transition:opacity .3s ease,-webkit-transform .3s ease;transition:opacity .3s ease,transform .3s ease;transition:opacity .3s ease,transform .3s ease,-webkit-transform .3s ease;pointer-events:none;opacity:0;-webkit-animation:animateIn ease .3s forwards;animation:animateIn ease .3s forwards}.pl-c-loader__content{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;pointer-events:auto}.pl-c-loader__message{-webkit-box-flex:1;-ms-flex:1;flex:1;padding:1rem;font-size:.85rem;color:#fff}.pl-c-loader__spinner{position:relative;display:inline-block;width:4rem;height:2rem}.pl-c-loader-svg:not(:root){overflow:hidden}.pl-c-loader-svg{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-animation:rotate 1s linear infinite;animation:rotate 1s linear infinite}.pl-c-loader-svg__outer-circle{fill:none;stroke:#fff;stroke-width:15;stroke-miterlimit:10}.pl-c-loader-svg__electron,.pl-c-loader-svg__inner-circle{fill:#ccc} \ No newline at end of file diff --git a/packages/uikit-workshop/dist/styleguide/css/pattern-lab.critical.css b/packages/uikit-workshop/dist/styleguide/css/pattern-lab.critical.css deleted file mode 100644 index 88cd6b3b02..0000000000 --- a/packages/uikit-workshop/dist/styleguide/css/pattern-lab.critical.css +++ /dev/null @@ -1 +0,0 @@ -.pl-c-body *{-webkit-box-sizing:border-box;box-sizing:border-box}.pl-c-body{margin:0;padding:0;background-color:#f6f6f9;-webkit-text-size-adjust:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;height:100vh}.pl-c-header{position:fixed;top:0;left:0;z-index:4;display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;background:#000;color:grey;font-family:HelveticaNeue,Helvetica,Arial,sans-serif;font-size:.85rem}.pl-c-header__nav-toggle{background:#000;color:grey;text-decoration:none;line-height:1;padding:.7rem .5rem;border:0;text-align:left;border:0}@media all and (min-width:42em){.pl-c-header__nav-toggle{display:none}}@media all and (max-width:42em){.pl-c-nav{position:absolute;top:100%;width:100%;overflow:hidden;max-height:0;background:#000;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}}@media all and (min-width:42em){.pl-c-nav{display:-webkit-box;display:-ms-flexbox;display:flex}}.pl-c-nav__list{z-index:1;margin:0;padding:0;list-style:none}@media all and (max-width:42em){.pl-c-nav__list{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}}@media all and (min-width:42em){.pl-c-nav__list{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-negative:0;flex-shrink:0}}.pl-c-controls{margin-left:auto;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.pl-c-viewport{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;position:relative;margin-top:2rem;bottom:0;left:0;right:0;z-index:0;overflow:hidden;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.pl-c-viewport__cover{width:100%;height:100%;position:absolute;z-index:20;display:none;top:0;left:0;bottom:0;right:0}.pl-c-viewport__iframe-wrapper{height:100%;position:relative;margin:0 auto;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-overflow-scrolling:touch;overflow-y:auto;overflow-x:hidden;width:100%}.pl-c-viewport__iframe{position:absolute;height:100%;width:100%;border:0;padding:0;margin:0;top:0;bottom:0;left:0;right:0;background-color:#fff;max-width:100vw}.pl-c-viewport__resizer{position:absolute;right:0;top:0;bottom:0;width:14px;margin:0;height:100%}.pl-c-viewport__resizer-handle{margin:0;width:100%;height:100%;background:#ccc}.pl-c-modal__cover{width:100%;height:100%;display:none;position:absolute;z-index:20}.pl-c-header .pl-c-typeahead{border:0;background:#222!important;color:grey;width:100%;right:0;padding:.61rem .5rem;font-size:inherit}@media all and (min-width:48em){.pl-c-body--theme-sidebar .pl-c-viewport{top:0;left:14rem;width:auto}.pl-c-body--theme-sidebar .pl-c-viewport{top:0;left:14rem;width:auto}} \ No newline at end of file diff --git a/packages/uikit-workshop/dist/styleguide/css/pattern-lab.css b/packages/uikit-workshop/dist/styleguide/css/pattern-lab.css index 67d63ae9a4..59ba24d8db 100755 --- a/packages/uikit-workshop/dist/styleguide/css/pattern-lab.css +++ b/packages/uikit-workshop/dist/styleguide/css/pattern-lab.css @@ -1 +1,2590 @@ -.pl-c-body *{-webkit-box-sizing:border-box;box-sizing:border-box}.pl-c-body{margin:0;padding:0;background-color:#f6f6f9;-webkit-text-size-adjust:100%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;height:100vh}.pl-c-header{position:fixed;top:0;left:0;z-index:4;display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;background:#000;color:grey;font-family:HelveticaNeue,Helvetica,Arial,sans-serif;font-size:.85rem}.pl-c-header__nav-toggle{background:#000;color:grey;text-decoration:none;line-height:1;padding:.7rem .5rem;border:0;text-align:left;-webkit-transition:background .1s ease-out,color .1s ease-out;transition:background .1s ease-out,color .1s ease-out;border:0}.pl-c-header__nav-toggle:hover{color:#fff;background:#222}.pl-c-header__nav-toggle.active,.pl-c-header__nav-toggle:focus{color:#fff;background:#222;outline:1px dotted grey;outline-offset:-1px}.pl-c-body--theme-light .pl-c-header__nav-toggle{background:#fff;color:#4d4c4c}.pl-c-body--theme-density-cozy .pl-c-header__nav-toggle{font-size:.85rem;padding:1.2rem .8rem}.pl-c-body--theme-density-comfortable .pl-c-header__nav-toggle{font-size:.85rem;padding:1.5rem 1rem}@media all and (min-width:42em){.pl-c-header__nav-toggle{display:none}}.pl-c-logo{max-width:2rem;margin:0 1rem;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}.pl-c-logo:focus{outline:1px dotted grey;outline-offset:-1px}.pl-c-logo__img{display:block;max-width:100%;height:auto}@media all and (max-width:42em){.pl-c-nav{position:absolute;top:100%;width:100%;overflow:hidden;max-height:0;background:#000;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-transition:max-height .1s ease-out;transition:max-height .1s ease-out}.pl-c-nav.pl-is-active{max-height:calc(100vh - 1rem);overflow:auto;-ms-overflow-style:-ms-autohiding-scrollbar}.pl-c-nav.pl-is-active::-webkit-scrollbar{width:0!important}}@media all and (min-width:42em){.pl-c-nav{display:-webkit-box;display:-ms-flexbox;display:flex}}.pl-c-nav__list{z-index:1;margin:0;padding:0;list-style:none}@media all and (max-width:42em){.pl-c-nav__list{-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}}@media all and (min-width:42em){.pl-c-nav__list{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-negative:0;flex-shrink:0}}.pl-c-nav__item{cursor:pointer;position:relative}@media all and (min-width:42em){.pl-c-nav__sublist>.pl-c-nav__item:last-child{overflow:hidden;border-bottom-left-radius:6px;border-bottom-right-radius:6px}}.pl-c-nav__link{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;background:#000;color:grey;text-decoration:none;line-height:1;padding:.7rem .5rem;border:0;text-align:left;-webkit-transition:background .1s ease-out,color .1s ease-out;transition:background .1s ease-out,color .1s ease-out}.pl-c-nav__link:hover{color:#fff;background:#222}.pl-c-nav__link.active,.pl-c-nav__link:focus{color:#fff;background:#222;outline:1px dotted grey;outline-offset:-1px}.pl-c-body--theme-light .pl-c-nav__link{background:#fff;color:#4d4c4c}.pl-c-body--theme-density-cozy .pl-c-nav__link{font-size:.85rem;padding:1.2rem .8rem}.pl-c-body--theme-density-comfortable .pl-c-nav__link{font-size:.85rem;padding:1.5rem 1rem}.pl-c-nav__link--sublink{text-transform:none;padding-left:1rem}.pl-c-nav__link--dropdown:after{content:'\25bc';color:rgba(255,255,255,.25);display:inline-block;font-size:7px;position:relative;top:1px;right:-2px;-webkit-transition:all .1s ease-out;transition:all .1s ease-out}.pl-c-nav__link--dropdown:focus:after,.pl-c-nav__link--dropdown:hover:after{color:grey}.pl-c-nav__link--dropdown.pl-is-active{color:#fff;background:#222}.pl-c-nav__link--dropdown.pl-is-active:after{color:grey;-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.pl-c-nav__sublist{list-style:none;margin:0;padding:0}@media all and (min-width:42em){.pl-c-nav__sublist{position:absolute;top:100%;left:0;min-width:10rem;overflow:hidden;border-bottom-left-radius:6px;border-bottom-right-radius:6px}}.pl-c-nav__sublist--dropdown,.pl-c-nav__subsublist--dropdown{list-style:none;margin:0;padding:0;overflow:hidden;max-height:0;-webkit-transition:max-height .1s ease-out;transition:max-height .1s ease-out}.pl-c-nav__sublist--dropdown.pl-is-active,.pl-c-nav__subsublist--dropdown.pl-is-active{max-height:none;overflow:auto;-ms-overflow-style:-ms-autohiding-scrollbar}.pl-c-nav__sublist--dropdown.pl-is-active::-webkit-scrollbar,.pl-c-nav__subsublist--dropdown.pl-is-active::-webkit-scrollbar{width:0!important}@media all and (min-width:42em){.pl-c-nav__sublist--dropdown.pl-is-active,.pl-c-nav__subsublist--dropdown.pl-is-active{max-height:calc(100vh - 3rem)}}.pl-c-nav__subsublist{list-style:none;margin:0;padding:0}.pl-c-viewport-size{margin:0;border:0;padding:.3rem .5rem .4rem;line-height:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.pl-c-viewport-size__input{padding:.1rem;margin:0;border:0;border-radius:3px;background:0 0;font-size:inherit;color:grey;width:3em;text-align:right;-webkit-transition:all .1s ease-out;transition:all .1s ease-out}.pl-c-viewport-size__input::-moz-focus-inner{padding:0;border:0}.pl-c-viewport-size__input:hover{color:#fff;background:#222}.pl-c-viewport-size__input:active,.pl-c-viewport-size__input:focus{color:#fff;background:#222;outline:1px dotted grey;outline-offset:-1px}.pl-c-viewport-size__label{display:block;margin:0;padding:0}.pl-c-size-list{display:none;list-style:none;margin:0;padding:0}@media all and (min-width:53em){.pl-c-size-list{display:block;display:-webkit-box;display:-ms-flexbox;display:flex}}.pl-c-size-list__action{background:#000;color:grey;text-decoration:none;line-height:1;padding:.7rem .5rem;border:0;text-align:left;-webkit-transition:background .1s ease-out,color .1s ease-out;transition:background .1s ease-out,color .1s ease-out}.pl-c-size-list__action:hover{color:#fff;background:#222}.pl-c-size-list__action.active,.pl-c-size-list__action:focus{color:#fff;background:#222;outline:1px dotted grey;outline-offset:-1px}.pl-c-body--theme-light .pl-c-size-list__action{background:#fff;color:#4d4c4c}.pl-c-body--theme-density-cozy .pl-c-size-list__action{font-size:.85rem;padding:1.2rem .8rem}.pl-c-body--theme-density-comfortable .pl-c-size-list__action{font-size:.85rem;padding:1.5rem 1rem}.pl-c-controls{margin-left:auto;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.pl-c-controls__list{list-style:none;margin:0;padding:0;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-wrap:nowrap;flex-wrap:nowrap}.pl-c-tools{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex}.pl-c-tools__toggle{background:#000;color:grey;text-decoration:none;line-height:1;padding:.7rem .5rem;border:0;text-align:left;-webkit-transition:background .1s ease-out,color .1s ease-out;transition:background .1s ease-out,color .1s ease-out;margin:0;padding-top:.6rem;padding-bottom:.5rem;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;position:relative;min-width:30px}.pl-c-tools__toggle:hover{color:#fff;background:#222}.pl-c-tools__toggle.active,.pl-c-tools__toggle:focus{color:#fff;background:#222;outline:1px dotted grey;outline-offset:-1px}.pl-c-body--theme-light .pl-c-tools__toggle{background:#fff;color:#4d4c4c}.pl-c-body--theme-density-cozy .pl-c-tools__toggle{font-size:.85rem;padding:1.2rem .8rem}.pl-c-body--theme-density-comfortable .pl-c-tools__toggle{font-size:.85rem;padding:1.5rem 1rem}.pl-c-tools__toggle-icon{position:absolute}.pl-c-tools__list{list-style:none;margin:0;padding:0;overflow:hidden;max-height:0;-webkit-transition:max-height .1s ease-out;transition:max-height .1s ease-out;position:absolute;top:100%;right:0;z-index:1;width:10rem;border-bottom-left-radius:6px;border-bottom-right-radius:6px}.pl-c-tools__list.pl-is-active{max-height:none;overflow:auto;-ms-overflow-style:-ms-autohiding-scrollbar}.pl-c-tools__list.pl-is-active::-webkit-scrollbar{width:0!important}@media all and (min-width:42em){.pl-c-tools__list.pl-is-active{max-height:calc(100vh - 3rem)}}.pl-c-tools__action{background:#000;color:grey;text-decoration:none;line-height:1;padding:.7rem .5rem;border:0;text-align:left;-webkit-transition:background .1s ease-out,color .1s ease-out;transition:background .1s ease-out,color .1s ease-out;display:block;width:100%;margin:0}.pl-c-tools__action:hover{color:#fff;background:#222}.pl-c-tools__action.active,.pl-c-tools__action:focus{color:#fff;background:#222;outline:1px dotted grey;outline-offset:-1px}.pl-c-body--theme-light .pl-c-tools__action{background:#fff;color:#4d4c4c}.pl-c-body--theme-density-cozy .pl-c-tools__action{font-size:.85rem;padding:1.2rem .8rem}.pl-c-body--theme-density-comfortable .pl-c-tools__action{font-size:.85rem;padding:1.5rem 1rem}.pl-c-viewport{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%;position:relative;margin-top:2rem;bottom:0;left:0;right:0;z-index:0;overflow:hidden;-webkit-box-flex:1;-ms-flex-positive:1;flex-grow:1}.pl-c-viewport__cover{width:100%;height:100%;position:absolute;z-index:20;cursor:move;display:none;top:0;left:0;bottom:0;right:0}.pl-c-viewport__iframe-wrapper{height:100%;position:relative;margin:0 auto;-webkit-box-flex:1;-ms-flex:1;flex:1;-webkit-overflow-scrolling:touch;overflow-y:auto;overflow-x:hidden;width:100%}.pl-c-viewport__iframe-wrapper.hay-mode{-webkit-transition:all 40s linear;transition:all 40s linear}.pl-c-viewport__iframe{position:absolute;height:100%;width:100%;border:0;padding:0;margin:0;top:0;bottom:0;left:0;right:0;background-color:#fff;max-width:100vw}.pl-c-viewport__iframe.hay-mode{-webkit-transition:all 40s linear;transition:all 40s linear}.pl-c-viewport__resizer{position:absolute;right:0;top:0;bottom:0;width:14px;margin:0;height:100%;cursor:ew-resize}.pl-c-viewport__resizer-handle{margin:0;width:100%;height:100%;background:#ccc;-webkit-transition:background .1s ease-out;transition:background .1s ease-out}.pl-c-viewport__resizer-handle:hover{background:grey}.pl-c-viewport__resizer-handle:active{cursor:move;background:#4d4c4c}.vp-animate{-webkit-transition:width .8s ease-out;transition:width .8s ease-out}.pl-c-pattern{margin-bottom:2rem;position:relative;clear:both}.pl-c-pattern__header{position:relative;padding:.5rem 0 0;line-height:1.3;font-size:90%;color:grey}.pl-c-pattern__header:empty{padding:0}.pl-c-pattern__title{font-family:HelveticaNeue,Helvetica,Arial,sans-serif!important;font-size:.85rem!important;line-height:1!important;font-weight:400!important;margin:0!important;padding:0!important;text-transform:capitalize!important}.pl-c-pattern__title-link{display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:start;-ms-flex-align:start;align-items:flex-start;padding:1rem 0 .3rem;color:grey!important;text-decoration:none;cursor:pointer}.pl-c-pattern__title-link:focus,.pl-c-pattern__title-link:hover{color:#000!important}.pl-c-pattern__extra-toggle{font-size:9px;position:absolute;bottom:-1px;right:0;z-index:1;padding:.65em .65em .5em;line-height:1;color:grey;background:0 0;font-weight:400;border:1px solid #ddd;border-top-left-radius:6px;border-top-right-radius:6px;-webkit-transition:background .1s ease-out;transition:background .1s ease-out}.pl-c-pattern__extra-toggle .pl-c-pattern__toggle-icon{display:inline-block}.pl-c-pattern__extra-toggle.pl-is-active,.pl-c-pattern__extra-toggle:focus,.pl-c-pattern__extra-toggle:hover{background:#fafafa;color:#000}.pl-c-pattern__extra-toggle:focus{outline:1px dotted #4d4c4c}.pl-c-pattern__extra-toggle.pl-is-active{border-bottom-color:#fafafa}.pl-c-pattern__extra-toggle.pl-is-active .pl-c-pattern__toggle-icon{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.pl-c-pattern__extra{background:#fafafa;border-top:1px solid #ddd;margin-bottom:1rem;overflow:hidden;max-height:1px;position:relative;-webkit-transition:all .1s ease-out;transition:all .1s ease-out}.pl-c-pattern__extra.pl-is-active{border:1px solid #ddd;border-radius:6px;border-top-right-radius:0;max-height:150rem}.pl-c-category{margin-top:6rem;font:HelveticaNeue,Helvetica,Arial,sans-serif!important}.pl-c-category:first-of-type{margin-top:2rem}.pl-c-category__title{font-size:1.4rem!important;color:#222!important;margin:0 0 .2rem;text-transform:capitalize}.pl-c-category__title-link{-webkit-transition:color .1s ease-out;transition:color .1s ease-out}.pl-c-category__description{font-size:.85rem;line-height:1.5;max-width:30rem}.pl-c-category__description:empty{display:none}@media all and (min-width:53em){.pl-c-pattern-info{display:-webkit-box;display:-ms-flexbox;display:flex}}.pl-c-pattern .pl-c-pattern-info{max-height:20rem;overflow:scroll;-ms-overflow-style:-ms-autohiding-scrollbar}.pl-c-pattern .pl-c-pattern-info::-webkit-scrollbar{width:0!important}@media all and (min-width:53em){.pl-c-pattern .pl-c-pattern-info{max-height:none;height:18rem;overflow:visible}}.pl-c-modal .pl-c-pattern-info{position:absolute;top:0;right:0;bottom:0;left:0;overflow:scroll;-ms-overflow-style:-ms-autohiding-scrollbar}.pl-c-modal .pl-c-pattern-info::-webkit-scrollbar{width:0!important}@media all and (min-width:53em){.pl-c-modal .pl-c-pattern-info{position:static;overflow:visible}}.pl-c-pattern-info__panel{padding:1rem}@media all and (min-width:53em){.pl-c-pattern-info__panel{-webkit-box-flex:1;-ms-flex:auto;flex:auto;position:absolute;top:0;bottom:0;left:0;right:0}}@media all and (min-width:62em){.pl-c-modal .pl-c-pattern-info__panel{padding-left:2rem}}.pl-c-pattern-info__panel--info{padding-top:2rem}@media all and (min-width:53em){.pl-c-pattern-info__panel--info{left:0;right:50%;overflow:scroll;-ms-overflow-style:-ms-autohiding-scrollbar}.pl-c-pattern-info__panel--info::-webkit-scrollbar{width:0!important}}@media all and (min-width:62em){.pl-c-pattern-info__panel--info{right:55%}}@media all and (min-width:53em){.pl-c-pattern-info__panel--info+.pl-c-pattern-info__panel--code{right:0;left:50%;top:1.2rem}}@media all and (min-width:62em){.pl-c-pattern-info__panel--info+.pl-c-pattern-info__panel--code{left:45%}}.pl-c-pattern-info__header{margin-bottom:.5rem}.pl-c-pattern-info__title{font-size:1.4rem!important;font-weight:400;margin-top:0;margin-bottom:0;color:inherit;text-transform:capitalize;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center}.pl-c-pattern-info__description{border-bottom-color:grey}.pl-c-annotations{border-top-color:grey}.pl-c-pattern-state{display:inline-block;width:5px;height:5px;margin-left:10px;position:relative;top:5px;left:0;border-radius:50%;background:#02a4d5;line-height:4px;text-indent:10px}.pl-c-pattern-state--complete{background:#03790f}.pl-c-pattern-state--inreview{background:#c7a118}.pl-c-pattern-state--deprecated{background:#b00b02}.complete:before{color:#03790f!important}.pl-c-lineage{font-size:.85rem;line-height:1.7;margin-top:0}.pl-c-lineage__link{font-style:italic;color:grey;text-decoration:underline;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-transition:opacity .1s ease;transition:opacity .1s ease}.pl-c-lineage__link:focus,.pl-c-lineage__link:hover{opacity:.8}.pl-c-breadcrumb{list-style:none;margin:0;padding:0;margin-bottom:.5rem;display:-webkit-box;display:-ms-flexbox;display:flex;font-size:.7rem;color:grey;text-transform:capitalize}.pl-c-breadcrumb__item:after{content:"\25b6";opacity:.4;font-size:6px;display:inline-block;margin:0 .2rem;position:relative;top:-1px}.pl-c-tabs{padding:0 .5rem .5rem;background:#fff;border:1px solid #ddd;border-radius:6px;font-family:HelveticaNeue,Helvetica,Arial,sans-serif;position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;overflow:hidden}@media all and (min-width:53em){.pl-c-pattern-info__panel--code .pl-c-tabs{position:absolute;top:1rem;bottom:1rem;left:1rem;right:1rem}}@media all and (min-width:62em){.pl-c-modal .pl-c-tabs{right:2rem;left:2rem}}.pl-c-tabs__list{display:-webkit-box;display:-ms-flexbox;display:flex;width:100%;list-style:none;margin:0;padding:.5rem 0;background:#fff}.pl-c-tabs__link{display:block;line-height:1;padding:.2rem .4rem;border:1px solid transparent;border-radius:6px;background:#fff;color:grey;cursor:pointer;text-decoration:none;text-transform:lowercase;-webkit-transition:all .1s ease-out;transition:all .1s ease-out}.pl-c-tabs__link:hover{color:#222}.pl-c-tabs__link.pl-is-active-tab{color:#222;background:#eee;border:1px solid #ddd}.pl-c-tabs__content{-ms-overflow-style:-ms-autohiding-scrollbar;-webkit-box-flex:1;-ms-flex:1 0 auto;flex:1 0 auto;overflow:scroll;-webkit-overflow-scrolling:touch;height:100%;padding-top:.5rem}.pl-c-tabs__content::-webkit-scrollbar{width:0!important}.pl-c-modal .pl-c-tabs__content{border:0}.pl-c-tabs__panel{display:none;min-height:12rem}.pl-c-tabs__panel.pl-is-active-tab{display:block}.pl-c-tabs__panel :not(pre)>code[class*=language-],.pl-c-tabs__panel pre[class*=language-]{background:0 0;margin:0;padding:0;border:0;display:block}.pl-c-tabs__panel code[class*=language-]{background:0 0;margin:0}.pl-c-tools{position:relative;display:-webkit-box;display:-ms-flexbox;display:flex}.pl-c-tools__toggle{background:#000;color:grey;text-decoration:none;line-height:1;padding:.7rem .5rem;border:0;text-align:left;-webkit-transition:background .1s ease-out,color .1s ease-out;transition:background .1s ease-out,color .1s ease-out;margin:0;padding-top:.6rem;padding-bottom:.5rem;display:-webkit-inline-box;display:-ms-inline-flexbox;display:inline-flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;position:relative;min-width:30px}.pl-c-tools__toggle:hover{color:#fff;background:#222}.pl-c-tools__toggle.active,.pl-c-tools__toggle:focus{color:#fff;background:#222;outline:1px dotted grey;outline-offset:-1px}.pl-c-body--theme-light .pl-c-tools__toggle{background:#fff;color:#4d4c4c}.pl-c-body--theme-density-cozy .pl-c-tools__toggle{font-size:.85rem;padding:1.2rem .8rem}.pl-c-body--theme-density-comfortable .pl-c-tools__toggle{font-size:.85rem;padding:1.5rem 1rem}.pl-c-tools__toggle-icon{position:absolute}.pl-c-tools__list{list-style:none;margin:0;padding:0;overflow:hidden;max-height:0;-webkit-transition:max-height .1s ease-out;transition:max-height .1s ease-out;position:absolute;top:100%;right:0;z-index:1;width:10rem;border-bottom-left-radius:6px;border-bottom-right-radius:6px}.pl-c-tools__list.pl-is-active{max-height:none;overflow:auto;-ms-overflow-style:-ms-autohiding-scrollbar}.pl-c-tools__list.pl-is-active::-webkit-scrollbar{width:0!important}@media all and (min-width:42em){.pl-c-tools__list.pl-is-active{max-height:calc(100vh - 3rem)}}.pl-c-tools__action{background:#000;color:grey;text-decoration:none;line-height:1;padding:.7rem .5rem;border:0;text-align:left;-webkit-transition:background .1s ease-out,color .1s ease-out;transition:background .1s ease-out,color .1s ease-out;display:block;width:100%;margin:0}.pl-c-tools__action:hover{color:#fff;background:#222}.pl-c-tools__action.active,.pl-c-tools__action:focus{color:#fff;background:#222;outline:1px dotted grey;outline-offset:-1px}.pl-c-body--theme-light .pl-c-tools__action{background:#fff;color:#4d4c4c}.pl-c-body--theme-density-cozy .pl-c-tools__action{font-size:.85rem;padding:1.2rem .8rem}.pl-c-body--theme-density-comfortable .pl-c-tools__action{font-size:.85rem;padding:1.5rem 1rem}.pl-has-annotation{cursor:help!important;outline:1px dotted grey;outline-offset:-4px;-webkit-transition:-webkit-box-shadow .1s ease;transition:-webkit-box-shadow .1s ease;transition:box-shadow .1s ease;transition:box-shadow .1s ease,-webkit-box-shadow .1s ease}.pl-has-annotation a,.pl-has-annotation input{cursor:help!important}.pl-has-annotation:hover{-webkit-box-shadow:0 0 3px grey;box-shadow:0 0 3px grey}.pl-has-annotation.active{-webkit-box-shadow:inset 0 0 6px #4d4c4c;box-shadow:inset 0 0 6px #4d4c4c;outline:1px dotted grey;outline-offset:-1px}.pl-c-annotation-tip{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:24px!important;height:24px!important;margin-top:6px!important;margin-left:6px!important;border-radius:50%!important;background:#222!important;color:#fff!important;font-size:16px!important;position:absolute;z-index:100}.pl-c-annotations{margin:1rem 0}.pl-c-annotations__title{font-size:1.2rem!important;margin:0 0 .5rem}.pl-c-annotations .pl-c-annotations__list{counter-reset:the-count;padding:0;margin:0;list-style:none}.pl-c-annotations__item{position:relative;padding-left:1.5rem;margin-bottom:1rem;border-radius:6px;-webkit-transition:background .1s ease;transition:background .1s ease}.pl-c-annotations__item:before{content:counter(the-count);counter-increment:the-count;font-size:85%;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;width:14px;height:14px;border-radius:50%;padding:2px;text-align:center;background:grey;color:#fff;position:absolute;top:4px;left:0}.pl-c-annotations__item.pl-is-active{outline:1px dotted grey;outline-offset:-1px}.pl-c-annotations .pl-c-annotations__item-title{margin-bottom:0}.pl-c-modal{font-family:HelveticaNeue,Helvetica,Arial,sans-serif;background:#000;color:#ccc;position:relative;top:auto;bottom:0;left:0;z-index:5;width:100%;height:50%;-webkit-transition:max-height .3s ease,-webkit-transform .3s ease;transition:max-height .3s ease,-webkit-transform .3s ease;transition:transform .3s ease,max-height .3s ease;transition:transform .3s ease,max-height .3s ease,-webkit-transform .3s ease;-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0);height:50vh;overflow:hidden;border-top-left-radius:12px;border-top-right-radius:12px;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;bottom:0;max-height:0}.pl-c-modal.pl-is-active{-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0);max-height:9999px}.pl-c-modal__toolbar{display:-webkit-box;display:-ms-flexbox;display:flex}.pl-c-modal__close-btn{font-size:70%;background:#000;color:grey;border:0;border-radius:6px;display:inline-block;padding:.5rem .5rem .3rem;margin:0;text-decoration:none;cursor:pointer;z-index:2;-webkit-transition:all .1s ease-out;transition:all .1s ease-out}.pl-c-modal__close-btn:focus,.pl-c-modal__close-btn:hover{background:#222;color:#fff}.pl-c-modal__close-btn:active,.pl-c-modal__close-btn:focus{outline:1px dotted grey;outline-offset:-2px}.pl-c-modal.pl-is-active .pl-c-modal__close-btn{bottom:100%}.pl-c-modal__cover{width:100%;height:100%;display:none;position:absolute;z-index:20;cursor:move}.pl-c-modal__resizer{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;left:0;height:14px;width:100%;background:#000;z-index:2;cursor:ns-resize}.pl-c-modal__resizer:after{content:"";height:3px;width:50px;border-top:1px solid grey;border-bottom:1px solid grey;-webkit-transition:border-color .1s ease-out;transition:border-color .1s ease-out}.pl-c-modal__resizer:focus:after,.pl-c-modal__resizer:hover:after{border-color:#fff}.pl-c-modal__close-btn-icon{width:12px;height:12px;color:currentColor;fill:currentColor;-webkit-transition:fill .1s ease-out;transition:fill .1s ease-out}.pl-c-code-copy-btn{display:inline-block;position:absolute;top:.5rem;right:.5rem;padding:.2rem .4rem;background:#eee;color:#222;border:1px solid #ddd;border-radius:6px;font-family:HelveticaNeue,Helvetica,Arial,sans-serif;font-size:1rem;text-transform:lowercase;line-height:1;cursor:pointer;z-index:2;-webkit-transition:background .1s ease-out;transition:background .1s ease-out}.pl-c-code-copy-btn:focus,.pl-c-code-copy-btn:hover{background:#ccc}.pl-c-text-passage{font-size:.85rem;line-height:1.7}.pl-c-text-passage p{margin-top:0;margin-bottom:1rem}.pl-c-text-passage a{color:grey;text-decoration:underline;-webkit-transition:opacity .1s ease;transition:opacity .1s ease}.pl-c-text-passage a:focus,.pl-c-text-passage a:hover{opacity:.8}.pl-c-text-passage code[class*=language-],.pl-c-text-passage pre[class*=language-]{color:inherit}.pl-c-text-passage blockquote{padding-left:.8rem;border-left:3px solid inherit}.pl-c-text-passage hr{height:1px;background:grey;margin:2rem 0;border:0}.pl-c-text-passage h1{margin-bottom:1rem;font-weight:400}.pl-c-text-passage h2{margin:1rem 0 1rem;font-weight:400}.pl-c-text-passage h3{margin:1rem 0 1rem;font-weight:400}.pl-c-text-passage h4{margin:1rem 0 1rem;font-weight:400}.pl-c-text-passage h5{margin:1rem 0 1rem;font-weight:400}.pl-c-text-passage h6{margin:1rem 0 1rem;font-weight:400}.pl-c-text-passage ul{list-style:square;margin-left:.9rem;margin-bottom:1rem}.pl-c-text-passage ul li:last-child{margin-bottom:0}.pl-c-text-passage ol{list-style:decimal;margin-left:.9rem;margin-bottom:1rem}.pl-c-text-passage ol li:last-child{margin-bottom:0}.pl-c-text-passage li{margin-bottom:.5rem}.is-vishidden{position:absolute!important;overflow:hidden;width:1px;height:1px;padding:0;border:0;clip:rect(1px,1px,1px,1px)}.pl-c-header .pl-c-typeahead{border:0;background:#222!important;color:grey;width:100%;right:0;padding:.61rem .5rem;font-size:inherit}.pl-c-header .pl-c-typeahead:focus{background:grey;color:#fff}.pl-c-header .twitter-typeahead{display:-webkit-box!important;display:-ms-flexbox!important;display:flex!important;-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2;width:100%}.pl-c-header .tt-input{background:grey;color:#fff}.pl-c-header .tt-input:hover{color:#fff;background:#222!important}.pl-c-header .tt-input:hover::-webkit-input-placeholder{color:#fff}.pl-c-header .tt-input:hover::-moz-input-placeholder{color:#fff}.pl-c-header .tt-input:focus{border-radius:0;text-transform:lowercase;color:#fff;background:#222;outline:1px dotted grey;outline-offset:-1px}.pl-c-header .tt-hint{text-transform:lowercase}.pl-c-header .tt-dropdown-menu{text-transform:lowercase;background-color:grey;width:100%;border-bottom-right-radius:6px;border-bottom-left-radius:6px}.pl-c-header .tt-suggestion{color:#eee;padding:.8em}.pl-c-header .tt-suggestion.tt-cursor{color:#fff;background:rgba(255,255,255,.25)}.pl-c-header .tt-suggestion p{margin:0}code[class*=language-],pre[class*=language-]{color:#000;text-shadow:0 1px #fff;font-family:Consolas,Monaco,'Andale Mono',monospace;direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-ms-hyphens:none;hyphens:none}code[class*=language-] ::-moz-selection,code[class*=language-]::-moz-selection,pre[class*=language-] ::-moz-selection,pre[class*=language-]::-moz-selection{text-shadow:none;background:#b3d4fc}code[class*=language-] ::selection,code[class*=language-]::selection,pre[class*=language-] ::selection,pre[class*=language-]::selection{text-shadow:none;background:#b3d4fc}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#f5f2f0}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#708090}.token.punctuation{color:#999}.namespace{opacity:.7}.token.boolean,.token.constant,.token.deleted,.token.number,.token.property,.token.symbol,.token.tag{color:#905}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#690}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url{color:#a67f59;background:rgba(255,255,255,.5)}.token.atrule,.token.attr-value,.token.keyword{color:#07a}.token.function{color:#dd4a68}.token.important,.token.regex,.token.variable{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}pre.line-numbers{position:relative;padding-left:3.8em;counter-reset:linenumber}pre.line-numbers>code{position:relative}.line-numbers .line-numbers-rows{position:absolute;pointer-events:none;top:0;font-size:100%;left:-3.8em;width:3em;letter-spacing:-1px;border-right:1px solid #999;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.line-numbers-rows>span{pointer-events:none;display:block;counter-increment:linenumber}.line-numbers-rows>span:before{content:counter(linenumber);color:#999;display:block;padding-right:.8em;text-align:right}.token a{color:inherit}.pl-c-body--theme-light .pl-c-header{background:#fff;border-bottom:1px solid #ccc}.pl-c-body--theme-light .pl-c-nav__sublist{background:0 0}.pl-c-body--theme-light .pl-c-tools__list.pl-is-active{border-bottom:1px solid #ccc;border-left:1px solid #ccc}.pl-c-body--theme-light .pl-c-nav__link--dropdown{color:#4d4c4c;background:#fff}.pl-c-body--theme-light .pl-c-nav__link--dropdown:after{color:#ccc}.pl-c-body--theme-light .pl-c-nav__sublist .pl-c-nav__link{border-left:1px solid #ccc;border-right:1px solid #ccc}@media all and (min-width:42em){.pl-c-body--theme-light .pl-c-nav__sublist>.pl-c-nav__item:last-child .pl-c-nav__link{border-bottom-left-radius:6px;border-bottom-right-radius:6px;border-bottom:1px solid #ccc}}.pl-c-body--theme-light .pl-c-viewport-size__input{color:#4d4c4c}.pl-c-body--theme-light .pl-c-viewport-size__input:focus,.pl-c-body--theme-light .pl-c-viewport-size__input:hover{background:#ddd}.pl-c-body--theme-light .typeahead{background:#ddd!important}.pl-c-body--theme-light .pl-c-typeahead{background:#ddd!important;color:#4d4c4c!important}.pl-c-body--theme-light .tt-input{background:#eee!important;color:#4d4c4c!important}.pl-c-body--theme-light .tt-input:hover{color:#222;background:#ddd!important}.pl-c-body--theme-light .tt-input:hover::-webkit-input-placeholder{color:#222}.pl-c-body--theme-light .tt-input:hover::-moz-input-placeholder{color:#222}.pl-c-body--theme-light .pl-c-modal{background:#fff;color:#4d4c4c;border-top:1px solid #ccc}.pl-c-body--theme-light .pl-c-modal__close-btn{background:#fff;border-top:1px solid #ccc;border-left:1px solid #ccc}.pl-c-body--theme-light .pl-c-modal__close-btn:focus,.pl-c-body--theme-light .pl-c-modal__close-btn:hover{background:#fafafa;color:#4d4c4c}.pl-c-body--theme-density-cozy .pl-c-header{font-size:.85rem}.pl-c-body--theme-density-cozy .pl-c-viewport-size__input{width:44px}.pl-c-body--theme-density-cozy .pl-c-typeahead{padding:.9rem .8rem}@media all and (max-width:78em){.pl-c-body--theme-density-cozy .pl-c-size-list{display:none}}@media all and (max-width:78em){.pl-c-body--theme-density-cozy .pl-c-viewport-size{display:none}}.pl-c-body--theme-density-cozy .pl-c-tools__toggle{min-width:44px}.pl-c-body--theme-density-cozy .pl-c-viewport{top:3.28rem}.pl-c-body--theme-density-comfortable .pl-c-header{font-size:.85rem}.pl-c-body--theme-density-comfortable .pl-c-logo{max-width:4rem}.pl-c-body--theme-density-comfortable .pl-c-header .tt-suggestion{padding:1.5rem 1rem}.pl-c-body--theme-density-comfortable .pl-c-viewport-size__input{width:44px}.pl-c-body--theme-density-comfortable .pl-c-typeahead{padding:.9rem 1rem}@media all and (max-width:78em){.pl-c-body--theme-density-comfortable .pl-c-size-list{display:none}}@media all and (max-width:78em){.pl-c-body--theme-density-comfortable .pl-c-viewport-size{display:none}}.pl-c-body--theme-density-comfortable .pl-c-tools__toggle{min-width:44px}.pl-c-body--theme-density-comfortable .pl-c-viewport{top:3.8rem}@media all and (min-width:48em){.pl-c-body--theme-sidebar .pl-c-header{width:14rem;height:100vh;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;border-bottom:0;padding:1rem;overflow:auto;-ms-overflow-style:-ms-autohiding-scrollbar}.pl-c-body--theme-sidebar .pl-c-header::-webkit-scrollbar{width:0!important}.pl-c-body--theme-sidebar.pl-c-body--theme-light .pl-c-header{border-right:1px solid #ccc}.pl-c-body--theme-sidebar .pl-c-logo{max-width:7rem;margin:0 auto 1rem}.pl-c-body--theme-sidebar .pl-c-nav{display:block;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.pl-c-body--theme-sidebar .pl-c-nav__list{-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;-webkit-box-ordinal-group:3;-ms-flex-order:2;order:2}.pl-c-body--theme-sidebar .pl-c-nav__link{font-size:.85rem;padding-left:0;padding-right:0}.pl-c-body--theme-sidebar .pl-c-nav__sublist{position:relative;border-radius:0}.pl-c-body--theme-sidebar .pl-c-nav__sublist .pl-c-nav__link{padding-left:1rem}.pl-c-body--theme-sidebar .pl-c-nav__sublist--dropdown.pl-is-active{border:0;border-left:1px solid #4d4c4c}.pl-c-body--theme-sidebar.pl-c-body--theme-light .pl-c-nav__sublist--dropdown.pl-is-active{border-left-color:#eee}}@media all and (min-width:48em) and (min-width:42em){.pl-c-body--theme-sidebar .pl-c-nav__sublist--dropdown.pl-is-active{height:auto}}@media all and (min-width:48em){.pl-c-body--theme-sidebar .pl-c-nav__subsublist{border-left:1px solid #4d4c4c;margin-left:1rem}.pl-c-body--theme-sidebar.pl-c-body--theme-light .pl-c-nav__subsublist{border-left-color:#eee}.pl-c-body--theme-sidebar .pl-c-nav__sublist .pl-c-nav__link{border-left:0;border-right:0}}@media all and (min-width:48em) and (min-width:42em){.pl-c-body--theme-sidebar .pl-c-nav__sublist>.pl-c-nav__item:last-child .pl-c-nav__link{border-bottom-left-radius:0;border-bottom-right-radius:0;border-bottom:0}}@media all and (min-width:48em){.pl-c-body--theme-sidebar .pl-c-controls{margin-top:auto;margin-left:0;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}.pl-c-body--theme-sidebar .pl-c-size-list{display:none}.pl-c-body--theme-sidebar .pl-c-viewport-size{display:none}.pl-c-body--theme-sidebar .pl-c-typeahead{border-radius:6px}.pl-c-body--theme-sidebar .pl-c-header .twitter-typeahead{display:block!important;margin-bottom:.5rem}.pl-c-body--theme-sidebar .pl-c-tools__toggle{display:none}.pl-c-body--theme-sidebar .pl-c-tools__list{max-height:none;overflow:visible;position:relative;border-radius:0;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column;width:100%}.pl-c-body--theme-sidebar .pl-c-tools__action{padding-left:0;padding-right:0}.pl-c-body--theme-sidebar .pl-c-viewport{top:0;left:14rem;width:auto}.pl-c-body--theme-sidebar .pl-c-viewport__iframe-wrapper{width:100%!important}.pl-c-body--theme-sidebar .pl-c-viewport__iframe{width:100%!important}.pl-c-body--theme-sidebar .pl-c-viewport{top:0;left:14rem;width:auto}.pl-c-body--theme-sidebar .pl-c-modal{left:14rem;right:0;width:auto}} \ No newline at end of file +/*------------------------------------*\ + #PATTERN LAB STYLES +\*------------------------------------*/ +/** +* NOTES: +* 1) These styles are specific to Pattern Lab and should not be modified. +* All project styles should be modified in /source/css/ +* +* 2) Any !important declarations are to prevent brand styles from overriding +* pattern lab-specific styles +*/ +/*------------------------------------*\ + #TABLE OF CONTENTS +\*------------------------------------*/ +/** + * ABSTRACTS.................Variables + * BASE......................Reset & Base elements + * COMPONENTS................Components + * UTILITIES.................Helper classes + * VENDOR....................Styles out of PL control + */ +/*------------------------------------*\ + #ABSTRACTS +\*------------------------------------*/ +/*------------------------------------*\ + #VARIABLES +\*------------------------------------*/ +/** +* These variables are specific to the Pattern Lab shell and exist +* indepenedently of any project-specific styles +*/ +/*------------------------------------*\ + #VARIABLES +\*------------------------------------*/ +/** + * List Reset + */ +/** + * Hide scrollbar + * 1) This hides scrollbars on Windows devices + */ +/** + * Header Link Style + */ +/** + * Accordion panel + */ +/*------------------------------------*\ + #BASE +\*------------------------------------*/ +/*------------------------------------*\ + #RESET +\*------------------------------------*/ +/** + * Apply border-box to all elements + * + * 1) This is a broadly-applied style that affects every + * element on the screen. This can bleed into user's styles + * but since it's been a best practice for years now we're + * going to default to this. + */ +.pl-c-body * { + -webkit-box-sizing: border-box; + box-sizing: border-box; } + +/*------------------------------------*\ + #PATTERN LAB BODY +\*------------------------------------*/ +/** +* The HTML and body elements for the Pattern Lab shell. +* 1) These exist indepenedent of any project-specific styles +* 2) Styled as IDs to avoid collisions with user tag +*/ +.pl-c-body { + margin: 0; + padding: 0; + background: #ddd; + -webkit-text-size-adjust: 100%; } + +/*------------------------------------*\ + #COMPONENTS +\*------------------------------------*/ +/** + * Pattern Lab Header + */ +/*------------------------------------*\ + #HEADER +\*------------------------------------*/ +/** +* 1) Pattern Lab's header is fixed across the top of the viewport and +* contains the primary pattern navigation, viewport resizing items, +* and tools. +* 2) Display nav and controls horizontally +*/ +.pl-c-header { + position: fixed; + top: 0; + left: 0; + z-index: 4; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + /* 2 */ + width: 100%; + background: #000; + color: #808080; + font-family: "HelveticaNeue", "Helvetica", "Arial", sans-serif; + font-size: 0.85rem; } + +/** + * Nav toggle button + * 1) Styles for the general nav toggle button, which + * only appears on small screens + */ +.pl-c-header__nav-toggle { + background: #000; + color: #808080; + text-decoration: none; + line-height: 1; + padding: 0.7rem 0.5rem; + border: 0; + text-align: left; + -webkit-transition: background 0.1s ease-out, color 0.1s ease-out; + transition: background 0.1s ease-out, color 0.1s ease-out; + /** + * Header link styles inside light theme + */ + /** + * Header link styles inside cozy theme + */ + /** + * Header link styles inside comfortable theme + */ + border: 0; } + .pl-c-header__nav-toggle:hover { + color: #fff; + background: #222; } + .pl-c-header__nav-toggle:focus, .pl-c-header__nav-toggle.active { + color: #fff; + background: #222; + outline: 1px dotted #808080; + outline-offset: -1px; } + .pl-c-body--theme-light .pl-c-header__nav-toggle { + background: #fff; + color: #4d4c4c; } + .pl-c-body--theme-density-cozy .pl-c-header__nav-toggle { + font-size: 0.85rem; + padding: 1.2rem 0.8rem; } + .pl-c-body--theme-density-comfortable .pl-c-header__nav-toggle { + font-size: 0.85rem; + padding: 1.5rem 1rem; } + @media all and (min-width: 42em) { + .pl-c-header__nav-toggle { + display: none; } } + +/*------------------------------------*\ + #LOGO +\*------------------------------------*/ +/** + * 1) An optional logo that lives in PL's header. + * 2) Displayed as a link + */ +.pl-c-logo { + max-width: 2rem; + margin: 0 1rem; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; } + .pl-c-logo:focus { + outline: 1px dotted #808080; + outline-offset: -1px; } + +.pl-c-logo__img { + display: block; + max-width: 100%; + height: auto; } + +/*------------------------------------*\ + #NAVIGATION +\*------------------------------------*/ +/** + * Navigation container + * 1) Collapse height on small screens. Menu trigger button + * activates nav + */ +@media all and (max-width: 42em) { + .pl-c-nav { + position: absolute; + top: 100%; + width: 100%; + overflow: hidden; + max-height: 0; + /* 1 */ + background: #000; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + -webkit-transition: max-height 0.1s ease-out; + transition: max-height 0.1s ease-out; + /** + * Active navigaiton + * 1) Slide + * 2) Set the height to the vierport height minus the height + * of the header + */ } + .pl-c-nav.pl-is-active { + max-height: calc(100vh - 1rem); + /* 2 */ + overflow: auto; + -ms-overflow-style: -ms-autohiding-scrollbar; } + .pl-c-nav.pl-is-active::-webkit-scrollbar { + width: 0 !important; } } + +@media all and (min-width: 42em) { + .pl-c-nav { + display: -webkit-box; + display: -ms-flexbox; + display: flex; } } + +/** + * Nav list + * 1) appears as an
    + * 2) display as a horizontal list on larger screens + * 3) On small screens, move the nav list after the typeahead form field + */ +.pl-c-nav__list { + z-index: 1; + margin: 0; + padding: 0; + list-style: none; } + @media all and (max-width: 42em) { + .pl-c-nav__list { + -webkit-box-ordinal-group: 3; + -ms-flex-order: 2; + order: 2; + /* 3 */ } } + @media all and (min-width: 42em) { + .pl-c-nav__list { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + /* 2 */ + -ms-flex-negative: 0; + flex-shrink: 0; } } + +/** + * Nav list item + */ +.pl-c-nav__item { + cursor: pointer; + position: relative; } + +/** + * Last sublist item + */ +@media all and (min-width: 42em) { + .pl-c-nav__sublist > .pl-c-nav__item:last-child { + overflow: hidden; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; } } + +/** + * Nav link + */ +.pl-c-nav__link { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + background: #000; + color: #808080; + text-decoration: none; + line-height: 1; + padding: 0.7rem 0.5rem; + border: 0; + text-align: left; + -webkit-transition: background 0.1s ease-out, color 0.1s ease-out; + transition: background 0.1s ease-out, color 0.1s ease-out; + /** + * Header link styles inside light theme + */ + /** + * Header link styles inside cozy theme + */ + /** + * Header link styles inside comfortable theme + */ } + .pl-c-nav__link:hover { + color: #fff; + background: #222; } + .pl-c-nav__link:focus, .pl-c-nav__link.active { + color: #fff; + background: #222; + outline: 1px dotted #808080; + outline-offset: -1px; } + .pl-c-body--theme-light .pl-c-nav__link { + background: #fff; + color: #4d4c4c; } + .pl-c-body--theme-density-cozy .pl-c-nav__link { + font-size: 0.85rem; + padding: 1.2rem 0.8rem; } + .pl-c-body--theme-density-comfortable .pl-c-nav__link { + font-size: 0.85rem; + padding: 1.5rem 1rem; } + +/** + * Nav sublink + * 1) Visually differentiate sub-item links from + * the other links. Creates better hierarchy. + */ +.pl-c-nav__link--sublink { + text-transform: none; + padding-left: 1rem; } + +/** + * Nav link + */ +.pl-c-nav__link--dropdown { + /** + * Dropdown caret after accordion handle + */ + /** + * Active dropdown + */ } + .pl-c-nav__link--dropdown:after { + content: '\25bc'; + color: rgba(255, 255, 255, 0.25); + display: inline-block; + font-size: 7px; + position: relative; + top: 1px; + right: -2px; + -webkit-transition: all 0.1s ease-out; + transition: all 0.1s ease-out; } + .pl-c-nav__link--dropdown:hover:after, .pl-c-nav__link--dropdown:focus:after { + color: #808080; } + .pl-c-nav__link--dropdown.pl-is-active { + color: #fff; + background: #222; + /** + * Caret rotation and positioning in active dropdown + */ } + .pl-c-nav__link--dropdown.pl-is-active:after { + color: #808080; + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); } + +/** + * Nav sublist + * 1) On larger screens, display as dropdowns that + * hang over the header + */ +.pl-c-nav__sublist { + list-style: none; + margin: 0; + padding: 0; } + @media all and (min-width: 42em) { + .pl-c-nav__sublist { + position: absolute; + top: 100%; + /* 1 */ + left: 0; + min-width: 10rem; + overflow: hidden; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; } } + +/** + * Dropdown sublist + */ +.pl-c-nav__sublist--dropdown, .pl-c-nav__subsublist--dropdown { + list-style: none; + margin: 0; + padding: 0; + overflow: hidden; + max-height: 0; + -webkit-transition: max-height 0.1s ease-out; + transition: max-height 0.1s ease-out; + /** + * Active is when accordion panel is open + */ } + .pl-c-nav__sublist--dropdown.pl-is-active, .pl-c-nav__subsublist--dropdown.pl-is-active { + max-height: none; + overflow: auto; + -ms-overflow-style: -ms-autohiding-scrollbar; } + .pl-c-nav__sublist--dropdown.pl-is-active::-webkit-scrollbar, .pl-c-nav__subsublist--dropdown.pl-is-active::-webkit-scrollbar { + width: 0 !important; } + @media all and (min-width: 42em) { + .pl-c-nav__sublist--dropdown.pl-is-active, .pl-c-nav__subsublist--dropdown.pl-is-active { + max-height: calc(100vh - 3rem); } } + +/** + * Dropdown sublist + * 1) Set the height to the viewport height minus the height of the header + */ +/** + * Sub-navigation + * 1) Third-level links are stylistically different + * than first and second nav links. + */ +.pl-c-nav__subsublist { + list-style: none; + margin: 0; + padding: 0; } + +/*------------------------------------*\ + #ISH SIZING +\*------------------------------------*/ +/** + * Viewport size form + * 1) This is the form for the form that houses the current + * viewport size in px and em + */ +.pl-c-viewport-size { + margin: 0; + border: 0; + padding: 0.3rem 0.5rem 0.4rem; + line-height: 1; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; } + +/** + * Size input fields + */ +.pl-c-viewport-size__input { + padding: 0.1rem; + margin: 0; + border: 0; + border-radius: 3px; + background: transparent; + font-size: inherit; + color: #808080; + width: 3em; + text-align: right; + -webkit-transition: all 0.1s ease-out; + transition: all 0.1s ease-out; } + .pl-c-viewport-size__input::-moz-focus-inner { + padding: 0; + border: 0; } + .pl-c-viewport-size__input:hover { + color: #fff; + background: #222; } + .pl-c-viewport-size__input:active, .pl-c-viewport-size__input:focus { + color: #fff; + background: #222; + outline: 1px dotted #808080; + outline-offset: -1px; } + +/** + * Size input labels + */ +.pl-c-viewport-size__label { + display: block; + margin: 0; + padding: 0; } + +/** + * Size options + * 1) This holds the S, M, L, Rand, Disco links + * 2) Depending on the config, these number of options may be + * larger or smaller. + */ +.pl-c-size-list { + display: none; + list-style: none; + margin: 0; + padding: 0; } + @media all and (min-width: 53em) { + .pl-c-size-list { + display: block; + display: -webkit-box; + display: -ms-flexbox; + display: flex; } } + +/** + * Size actions + * 1) These are the buttons that control the viewport resizing + */ +.pl-c-size-list__action { + background: #000; + color: #808080; + text-decoration: none; + line-height: 1; + padding: 0.7rem 0.5rem; + border: 0; + text-align: left; + -webkit-transition: background 0.1s ease-out, color 0.1s ease-out; + transition: background 0.1s ease-out, color 0.1s ease-out; + /** + * Header link styles inside light theme + */ + /** + * Header link styles inside cozy theme + */ + /** + * Header link styles inside comfortable theme + */ } + .pl-c-size-list__action:hover { + color: #fff; + background: #222; } + .pl-c-size-list__action:focus, .pl-c-size-list__action.active { + color: #fff; + background: #222; + outline: 1px dotted #808080; + outline-offset: -1px; } + .pl-c-body--theme-light .pl-c-size-list__action { + background: #fff; + color: #4d4c4c; } + .pl-c-body--theme-density-cozy .pl-c-size-list__action { + font-size: 0.85rem; + padding: 1.2rem 0.8rem; } + .pl-c-body--theme-density-comfortable .pl-c-size-list__action { + font-size: 0.85rem; + padding: 1.5rem 1rem; } + +/*------------------------------------*\ + #CONTROLS +\*------------------------------------*/ +/** + * 1) Controls contains viewport resizer and tools dropdown + * 2) Right-align inside of header + */ +.pl-c-controls { + margin-left: auto; + /* 2 */ + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; } + +/** +* Control list +*/ +.pl-c-controls__list { + list-style: none; + margin: 0; + padding: 0; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -ms-flex-wrap: nowrap; + flex-wrap: nowrap; } + +/*------------------------------------*\ + #TOOLS +\*------------------------------------*/ +/** + * The tools dropdown contains more utilities such as show/hide + * pattern info and pattern search, and also links to open in a + * new window and view the documentation + */ +.pl-c-tools { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; } + +/** + * Tools menu button + * 1) This is the button that contains the toggle and + * triggers the tools dropdown list + */ +.pl-c-tools__toggle { + background: #000; + color: #808080; + text-decoration: none; + line-height: 1; + padding: 0.7rem 0.5rem; + border: 0; + text-align: left; + -webkit-transition: background 0.1s ease-out, color 0.1s ease-out; + transition: background 0.1s ease-out, color 0.1s ease-out; + /** + * Header link styles inside light theme + */ + /** + * Header link styles inside cozy theme + */ + /** + * Header link styles inside comfortable theme + */ + margin: 0; + padding-top: 0.6rem; + padding-bottom: 0.5rem; + display: -webkit-inline-box; + display: -ms-inline-flexbox; + display: inline-flex; + -webkit-box-align: center; + -ms-flex-align: center; + align-items: center; + -webkit-box-pack: center; + -ms-flex-pack: center; + justify-content: center; + position: relative; + min-width: 30px; } + .pl-c-tools__toggle:hover { + color: #fff; + background: #222; } + .pl-c-tools__toggle:focus, .pl-c-tools__toggle.active { + color: #fff; + background: #222; + outline: 1px dotted #808080; + outline-offset: -1px; } + .pl-c-body--theme-light .pl-c-tools__toggle { + background: #fff; + color: #4d4c4c; } + .pl-c-body--theme-density-cozy .pl-c-tools__toggle { + font-size: 0.85rem; + padding: 1.2rem 0.8rem; } + .pl-c-body--theme-density-comfortable .pl-c-tools__toggle { + font-size: 0.85rem; + padding: 1.5rem 1rem; } + +/** + * Tools Toggle SVG icon + * 1) Cog icon + * 2) Set the width and height of the icon to be the same height of font + */ +.pl-c-tools__toggle-icon { + position: absolute; } + +/** + * Tools dropdown list + */ +.pl-c-tools__list { + list-style: none; + margin: 0; + padding: 0; + overflow: hidden; + max-height: 0; + -webkit-transition: max-height 0.1s ease-out; + transition: max-height 0.1s ease-out; + /** + * Active is when accordion panel is open + */ + position: absolute; + top: 100%; + right: 0; + z-index: 1; + width: 10rem; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; } + .pl-c-tools__list.pl-is-active { + max-height: none; + overflow: auto; + -ms-overflow-style: -ms-autohiding-scrollbar; } + .pl-c-tools__list.pl-is-active::-webkit-scrollbar { + width: 0 !important; } + @media all and (min-width: 42em) { + .pl-c-tools__list.pl-is-active { + max-height: calc(100vh - 3rem); } } + +/** + * Tools dropdown actions + * 1) Links and buttons inside of the tools dropdown + */ +.pl-c-tools__action { + background: #000; + color: #808080; + text-decoration: none; + line-height: 1; + padding: 0.7rem 0.5rem; + border: 0; + text-align: left; + -webkit-transition: background 0.1s ease-out, color 0.1s ease-out; + transition: background 0.1s ease-out, color 0.1s ease-out; + /** + * Header link styles inside light theme + */ + /** + * Header link styles inside cozy theme + */ + /** + * Header link styles inside comfortable theme + */ + display: block; + width: 100%; + margin: 0; } + .pl-c-tools__action:hover { + color: #fff; + background: #222; } + .pl-c-tools__action:focus, .pl-c-tools__action.active { + color: #fff; + background: #222; + outline: 1px dotted #808080; + outline-offset: -1px; } + .pl-c-body--theme-light .pl-c-tools__action { + background: #fff; + color: #4d4c4c; } + .pl-c-body--theme-density-cozy .pl-c-tools__action { + font-size: 0.85rem; + padding: 1.2rem 0.8rem; } + .pl-c-body--theme-density-comfortable .pl-c-tools__action { + font-size: 0.85rem; + padding: 1.5rem 1rem; } + +/** + * Viewport + */ +/*------------------------------------*\ + #VIEWPORT +\*------------------------------------*/ +/** +* To keep user code and PL code separate, and to make +* resizing the viewport possible, PL contains an iframe +* that houses all user code. +*/ +/** +* Viewport +* 1) This wrapper div occupies all remaining viewport space after PL's header +*/ +.pl-c-viewport { + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + height: calc(100vh - 2rem); + width: 100%; + position: relative; + top: 2rem; + bottom: 0; + left: 0; + right: 0; + z-index: 0; + overflow: hidden; } + +/** +* Cover +* 1) This is an invisible div that sits above the iframe and is +* used in JS for manual viewport resizing purposes. +*/ +.pl-c-viewport__cover { + width: 100%; + height: 100%; + display: none; + position: absolute; + z-index: 20; + cursor: move; } + +/** +* Viewport iframe wrapper +* 1) This is the container that houses the +* iframe and the manual resize handle +*/ +.pl-c-viewport__iframe-wrapper { + height: 100%; + position: relative; + margin: 0 auto; + -webkit-box-flex: 1; + -ms-flex: 1; + flex: 1; + -webkit-overflow-scrolling: touch; + overflow-y: auto; + overflow-x: hidden; + width: 100%; } + .pl-c-viewport__iframe-wrapper.hay-mode { + -webkit-transition: all 40s linear; + transition: all 40s linear; } + +/** +* Viewport iframe +* 1) this is the actual + + + + + +
    -
    +
    -
+
\ No newline at end of file diff --git a/packages/uikit-workshop/src/js/styleguide.js b/packages/uikit-workshop/src/js/styleguide.js index 1edc1cce01..d5a9330dd6 100755 --- a/packages/uikit-workshop/src/js/styleguide.js +++ b/packages/uikit-workshop/src/js/styleguide.js @@ -548,7 +548,7 @@ } // Open in new window link - if (document.querySelector('.pl-js-open-new-window')) { + if (document.querySelector('.pl-js-open-new-window') !== undefined) { // Set value of href to the path to the pattern document .querySelector('.pl-js-open-new-window') diff --git a/packages/uikit-workshop/src/js/url-handler.js b/packages/uikit-workshop/src/js/url-handler.js index 8aeffe4c55..21d95bc310 100755 --- a/packages/uikit-workshop/src/js/url-handler.js +++ b/packages/uikit-workshop/src/js/url-handler.js @@ -187,7 +187,7 @@ var urlHandler = { document.getElementById('title').innerHTML = 'Pattern Lab - ' + pattern; // Open in new window link - if (document.querySelector('.pl-js-open-new-window')) { + if (document.querySelector('.pl-js-open-new-window') !== undefined) { // Set value of href to the path to the pattern document .querySelector('.pl-js-open-new-window') diff --git a/packages/uikit-workshop/src/sass/pattern-lab--iframe-loader.scss b/packages/uikit-workshop/src/sass/pattern-lab--iframe-loader.scss deleted file mode 100644 index acef6edda7..0000000000 --- a/packages/uikit-workshop/src/sass/pattern-lab--iframe-loader.scss +++ /dev/null @@ -1,85 +0,0 @@ -@import 'scss/abstracts/variables'; -@import 'scss/abstracts/mixins'; - -@keyframes animateIn { - from { - transform: translate3d(-50%, -100%, 0px); - opacity: 0; - } - to { - opacity: 1; - transform: translate3d(-50%, calc(3rem - 50%), 0px); - } -} - -@keyframes rotate { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -.pl-c-loader { - z-index: 1000; - position: absolute; - top: 0; - left: 50%; - margin: auto; - max-width: $pl-space * 25; - width: calc(90vw - #{$pl-doublespace}); - border-radius: $pl-border-radius; - background: rgba($pl-color-black, 0.9); - transform: translate3d(-50%, -100%, 0px); - transition: opacity 0.3s ease, transform 0.3s ease; - pointer-events: none; - opacity: 0; - animation: animateIn ease 0.3s forwards; -} - -.pl-c-loader__content { - display: flex; - align-items: center; - pointer-events: auto; -} - -.pl-c-loader__message { - flex: 1; - padding: $pl-space; - font-size: $pl-font-size-sm-2; - color: $pl-color-white; -} - -.pl-c-loader__spinner { - position: relative; - display: inline-block; - width: $pl-doublespace * 2; - height: $pl-doublespace; -} - -.pl-c-loader-svg:not(:root) { - overflow: hidden; -} - -.pl-c-loader-svg { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - transform: translateZ(0); - animation: rotate 1s linear infinite; -} - -.pl-c-loader-svg__outer-circle { - fill: none; - stroke: $pl-color-white; - stroke-width: 15; - stroke-miterlimit: 10; -} - -.pl-c-loader-svg__inner-circle, -.pl-c-loader-svg__electron { - fill: $pl-color-gray-20; -} diff --git a/packages/uikit-workshop/src/sass/scss/base/_body.scss b/packages/uikit-workshop/src/sass/scss/base/_body.scss index c8b6dcf3da..67fc00a391 100755 --- a/packages/uikit-workshop/src/sass/scss/base/_body.scss +++ b/packages/uikit-workshop/src/sass/scss/base/_body.scss @@ -10,12 +10,6 @@ .pl-c-body { margin: 0; padding: 0; - // background: $pl-color-gray-13; - background-color: #F6F6F9; + background: $pl-color-gray-13; -webkit-text-size-adjust: 100%; - - // So the height-adjustable panel + viewport wrapper take up the space available automatically - display: flex; - flex-direction: column; - height: 100vh; // workaround to IE 11 flex-bug causing children's height to collapse in PL } \ No newline at end of file diff --git a/packages/uikit-workshop/src/sass/scss/components/_modal.scss b/packages/uikit-workshop/src/sass/scss/components/_modal.scss index 30d3e2ed88..3fa1934b5a 100755 --- a/packages/uikit-workshop/src/sass/scss/components/_modal.scss +++ b/packages/uikit-workshop/src/sass/scss/components/_modal.scss @@ -7,6 +7,7 @@ * "show pattern info" is selected on the pattern detail screen. */ .pl-c-modal { + display: none; font-family: $pl-font; background: $pl-color-black; color: $pl-color-gray-20; @@ -17,23 +18,15 @@ z-index: 5; width: 100%; height: 50%; - transition: transform 0.3s ease, max-height 0.3s ease; - transform: translate3d(0, 100%, 0); - height: 50vh; // overwritten by inline height from JS - overflow: hidden; - border-top-left-radius: 12px; // Double the inner border radius - border-top-right-radius: 12px; // Double the inner border radius - display: flex; - flex-direction: column; - bottom: 0; - max-height: 0; + transition: bottom 0.3s ease-out; /** * Active modal */ &.pl-is-active { - transform: translate3d(0, 0%, 0); - max-height: 9999px; + display: flex; + flex-direction: column; + bottom: 0; } } @@ -50,7 +43,7 @@ background: $pl-color-black; color: $pl-color-gray-50; border: 0; - border-radius: $pl-border-radius-med; + border-radius: $pl-border-radius-med $pl-border-radius-med 0 0; display: inline-block; padding: 0.5rem 0.5rem 0.3rem; margin: 0; diff --git a/packages/uikit-workshop/src/sass/scss/components/_navigation.scss b/packages/uikit-workshop/src/sass/scss/components/_navigation.scss old mode 100644 new mode 100755 index 204942468d..9c86a9e92c --- a/packages/uikit-workshop/src/sass/scss/components/_navigation.scss +++ b/packages/uikit-workshop/src/sass/scss/components/_navigation.scss @@ -21,12 +21,13 @@ /** * Active navigaiton - * 1) Slide + * 1) Slide * 2) Set the height to the vierport height minus the height * of the header */ &.pl-is-active { max-height: calc(100vh - #{$offset-top - 1rem}); /* 2 */ + // max-height: 50rem; /* 1 */ overflow: auto; @include hideScrollBar(); } @@ -174,6 +175,7 @@ */ .pl-c-nav__sublist--dropdown.pl-is-active { @media all and (min-width: $pl-bp-med) { + // height: calc(100vh - #{$offset-top}); /* 1 */ } } diff --git a/packages/uikit-workshop/src/sass/scss/components/_tabs.scss b/packages/uikit-workshop/src/sass/scss/components/_tabs.scss index 48e5fbd6bb..2efb2534f9 100755 --- a/packages/uikit-workshop/src/sass/scss/components/_tabs.scss +++ b/packages/uikit-workshop/src/sass/scss/components/_tabs.scss @@ -90,10 +90,9 @@ * 1) Tab content contains the tab panels */ .pl-c-tabs__content { - @include hideScrollBar; flex: 1 0 auto; overflow: scroll; - -webkit-overflow-scrolling: touch; + @include hideScrollBar(); height: 100%; padding-top: 0.5rem; diff --git a/packages/uikit-workshop/src/sass/scss/components/_viewport.scss b/packages/uikit-workshop/src/sass/scss/components/_viewport.scss index 821b229326..ad5a5430c8 100755 --- a/packages/uikit-workshop/src/sass/scss/components/_viewport.scss +++ b/packages/uikit-workshop/src/sass/scss/components/_viewport.scss @@ -15,15 +15,15 @@ .pl-c-viewport { display: flex; flex-direction: column; + height: calc(100vh - #{$offset-top}); // fixes the extra bit of scroll if set to 100vh width: 100%; position: relative; - margin-top: $offset-top; + top: $offset-top; bottom: 0; left: 0; right: 0; z-index: 0; overflow: hidden; - flex-grow: 1; // Share any available vertical space } /** @@ -34,16 +34,10 @@ .pl-c-viewport__cover { width: 100%; height: 100%; + display: none; position: absolute; z-index: 20; cursor: move; - // this needs to be set to display: none by default since the PL JavaScript that sets this inline to - // display none / block only fires when resizing the viewport or code drawer - display: none; - top: 0; - left: 0; - bottom: 0; - right: 0; } /** @@ -82,7 +76,6 @@ left: 0; right: 0; background-color: $pl-color-white; - max-width: 100vw; /** * Hay Mode transition diff --git a/sandbox/bolt-bolt/bolt.scss b/sandbox/bolt-bolt/bolt.scss new file mode 100644 index 0000000000..102d941ec6 --- /dev/null +++ b/sandbox/bolt-bolt/bolt.scss @@ -0,0 +1,12 @@ +// @todo Everything in here should be considered for inclusion in correct spot or deleted + + + +/* + * SHAME + */ + +// Workaround Brightcove Video Player's Hapyak CSS in a ratio component +bolt-ratio .hapyak-player:not(.hapyak-minimal-css) { + position: absolute !important; +} diff --git a/sandbox/grav/.gitignore b/sandbox/grav/.gitignore new file mode 100644 index 0000000000..644e5978d1 --- /dev/null +++ b/sandbox/grav/.gitignore @@ -0,0 +1,4 @@ +.idea +.DS_Store +cache +#logs diff --git a/sandbox/grav/.htaccess b/sandbox/grav/.htaccess new file mode 100644 index 0000000000..ef79a4bc26 --- /dev/null +++ b/sandbox/grav/.htaccess @@ -0,0 +1,75 @@ + + +RewriteEngine On + +## Begin RewriteBase +# If you are getting 500 or 404 errors on subpages, you may have to uncomment the RewriteBase entry +# You should change the '/' to your appropriate subfolder. For example if you have +# your Grav install at the root of your site '/' should work, else it might be something +# along the lines of: RewriteBase / +## + +# RewriteBase / + +## End - RewriteBase + +## Begin - X-Forwarded-Proto +# In some hosted or load balanced environments, SSL negotiation happens upstream. +# In order for Grav to recognize the connection as secure, you need to uncomment +# the following lines. +# +# RewriteCond %{HTTP:X-Forwarded-Proto} https +# RewriteRule .* - [E=HTTPS:on] +# +## End - X-Forwarded-Proto + +## Begin - Exploits +# If you experience problems on your site block out the operations listed below +# This attempts to block the most common type of exploit `attempts` to Grav +# +# Block out any script trying to base64_encode data within the URL. +RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR] +# Block out any script that includes a + + + +Hello + + + + + + + diff --git a/sandbox/grav/index.php b/sandbox/grav/index.php new file mode 100644 index 0000000000..68c9049824 --- /dev/null +++ b/sandbox/grav/index.php @@ -0,0 +1,56 @@ +bin/grav install"); +} + +if (PHP_SAPI == 'cli-server') { + if (!isset($_SERVER['PHP_CLI_ROUTER'])) { + die("PHP webserver requires a router to run Grav, please use:
php -S {$_SERVER["SERVER_NAME"]}:{$_SERVER["SERVER_PORT"]} system/router.php
"); + } +} + +use Grav\Common\Grav; +use RocketTheme\Toolbox\Event\Event; + +if (version_compare($ver = PHP_VERSION, $req = GRAV_PHP_MIN, '<')) { + die(sprintf('You are running PHP %s, but Grav needs at least PHP %s to run.', $ver, $req)); +} + +// Register the auto-loader. +$loader = require_once $autoload; + +// Set timezone to default, falls back to system if php.ini not set +date_default_timezone_set(@date_default_timezone_get()); + +// Set internal encoding if mbstring loaded +if (!extension_loaded('mbstring')) { + die("'mbstring' extension is not loaded. This is required for Grav to run correctly"); +} +mb_internal_encoding('UTF-8'); + +// Get the Grav instance +$grav = Grav::instance( + array( + 'loader' => $loader + ) +); + +// Process the page +try { + $grav->process(); +} catch (\Exception $e) { + $grav->fireEvent('onFatalException', new Event(array('exception' => $e))); + throw $e; +} diff --git a/sandbox/grav/logs/.gitkeep b/sandbox/grav/logs/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sandbox/grav/logs/grav.log b/sandbox/grav/logs/grav.log new file mode 100644 index 0000000000..58afda61ec --- /dev/null +++ b/sandbox/grav/logs/grav.log @@ -0,0 +1,54 @@ +[2017-09-16 14:34:04] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:34:05] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:34:55] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:34:56] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:34:57] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:34:57] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:35:06] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:35:07] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:35:51] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:35:51] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:37:20] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:37:21] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:38:18] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:38:19] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:38:22] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:38:23] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:40:41] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:40:42] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:40:43] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 {main} [] [] +[2017-09-16 14:40:43] grav.CRITICAL: Login Plugin failed to load. Composer dependencies not met. - Trace: #0 [internal function]: Grav\Plugin\LoginPlugin->initializeSession(Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onPluginsInitia...', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/PluginsProcessor.php(19): Grav\Common\Grav->fireEvent('onPluginsInitia...') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\PluginsProcessor->process() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #8 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('plugins', 'Plugins', Object(Closure)) #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #11 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #12 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #13 {main} [] [] +[2017-09-16 14:55:08] grav.CRITICAL: Template "@bolt/metadata.html.twig" is not defined in "_layouts/base.html.twig" at line 8. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('html') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 {main} [] [] +[2017-09-16 14:55:08] grav.CRITICAL: Template "error.ico.twig" is not defined. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('ico') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #10 {main} [] [] +[2017-09-16 14:55:48] grav.CRITICAL: Class 'Symfony\Component\Finder\Finder' not found - Trace: #0 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #9 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #12 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #13 {main} [] [] +[2017-09-16 14:55:49] grav.CRITICAL: Class 'Symfony\Component\Finder\Finder' not found - Trace: #0 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #9 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #12 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #13 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #14 {main} [] [] +[2017-09-16 14:55:50] grav.CRITICAL: Class 'Symfony\Component\Finder\Finder' not found - Trace: #0 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #9 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #12 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #13 {main} [] [] +[2017-09-16 14:55:51] grav.CRITICAL: Class 'Symfony\Component\Finder\Finder' not found - Trace: #0 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #1 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #3 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #9 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #10 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #12 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #13 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #14 {main} [] [] +[2017-09-16 15:00:00] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 {main} [] [] +[2017-09-16 15:00:01] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #15 {main} [] [] +[2017-09-16 15:00:01] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 {main} [] [] +[2017-09-16 15:00:02] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #15 {main} [] [] +[2017-09-16 15:01:30] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 {main} [] [] +[2017-09-16 15:01:30] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #15 {main} [] [] +[2017-09-16 15:01:32] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 {main} [] [] +[2017-09-16 15:01:32] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #15 {main} [] [] +[2017-09-16 15:01:46] grav.CRITICAL: Template "@bolt/metadata.html.twig" is not defined in "_layouts/base.html.twig" at line 8. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('html') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 {main} [] [] +[2017-09-16 15:01:46] grav.CRITICAL: Template "error.ico.twig" is not defined. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('ico') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #10 {main} [] [] +[2017-09-16 15:03:13] grav.CRITICAL: Template "@bolt/buttons.twig" is not defined in "_layouts/base.html.twig" at line 37. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('html') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 {main} [] [] +[2017-09-16 15:03:14] grav.CRITICAL: Template "error.ico.twig" is not defined. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('ico') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #10 {main} [] [] +[2017-09-16 15:04:15] grav.CRITICAL: Template "error.ico.twig" is not defined. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('ico') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #10 {main} [] [] +[2017-09-16 15:04:42] grav.CRITICAL: Template "error.ico.twig" is not defined. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('ico') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #10 {main} [] [] +[2017-09-16 22:39:36] grav.CRITICAL: Template "error.ico.twig" is not defined. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('ico') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #10 {main} [] [] +[2017-09-16 22:40:42] grav.CRITICAL: Template "error.ico.twig" is not defined. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('ico') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #10 {main} [] [] +[2017-09-16 22:45:30] grav.CRITICAL: Template "error.ico.twig" is not defined. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('ico') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #10 {main} [] [] +[2017-09-16 23:29:37] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 {main} [] [] +[2017-09-16 23:29:37] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #15 {main} [] [] +[2017-09-16 23:29:43] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 {main} [] [] +[2017-09-16 23:29:43] grav.CRITICAL: The "/Users/sghoweri/sites/bolt/www/user/themes/bolt/pattern-lab/source/_patterns" directory does not exist. - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(54): Symfony\Component\Finder\Finder->in('/Users/sghoweri...') #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #15 {main} [] [] +[2017-09-16 23:31:00] grav.CRITICAL: Undefined variable: theme_dir - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(53): Whoops\Run->handleError(8, 'Undefined varia...', '/Users/sghoweri...', 53, Array) #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 {main} [] [] +[2017-09-16 23:31:00] grav.CRITICAL: Undefined variable: theme_dir - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(53): Whoops\Run->handleError(8, 'Undefined varia...', '/Users/sghoweri...', 53, Array) #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #15 {main} [] [] +[2017-09-16 23:31:50] grav.CRITICAL: Undefined variable: patternLabPatternsDir - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(56): Whoops\Run->handleError(8, 'Undefined varia...', '/Users/sghoweri...', 56, Array) #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 {main} [] [] +[2017-09-16 23:31:50] grav.CRITICAL: Undefined variable: patternLabPatternsDir - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(56): Whoops\Run->handleError(8, 'Undefined varia...', '/Users/sghoweri...', 56, Array) #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #15 {main} [] [] +[2017-09-16 23:32:08] grav.CRITICAL: Undefined variable: patternLabSourceDir - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(73): Whoops\Run->handleError(8, 'Undefined varia...', '/Users/sghoweri...', 73, Array) #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 {main} [] [] +[2017-09-16 23:32:08] grav.CRITICAL: Undefined variable: patternLabSourceDir - Trace: #0 /Users/sghoweri/sites/bolt/www/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php(73): Whoops\Run->handleError(8, 'Undefined varia...', '/Users/sghoweri...', 73, Array) #1 [internal function]: Grav\Plugin\PatternLabTwigNamespacesPlugin->onTwigLoader(Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #2 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(184): call_user_func(Array, Object(RocketTheme\Toolbox\Event\Event), 'onTwigLoader', Object(RocketTheme\Toolbox\Event\EventDispatcher)) #3 /Users/sghoweri/sites/bolt/www/vendor/symfony/event-dispatcher/EventDispatcher.php(46): Symfony\Component\EventDispatcher\EventDispatcher->doDispatch(Array, 'onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #4 /Users/sghoweri/sites/bolt/www/vendor/rockettheme/toolbox/Event/src/EventDispatcher.php(23): Symfony\Component\EventDispatcher\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #5 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(288): RocketTheme\Toolbox\Event\EventDispatcher->dispatch('onTwigLoader', Object(RocketTheme\Toolbox\Event\Event)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Twig/Twig.php(102): Grav\Common\Grav->fireEvent('onTwigLoader') #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/TwigProcessor.php(18): Grav\Common\Twig\Twig->init() #8 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\TwigProcessor->process() #9 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #10 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('twig', 'Twig', Object(Closure)) #11 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #12 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #13 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #14 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #15 {main} [] [] +[2017-09-16 23:32:32] grav.CRITICAL: Template "error.ico.twig" is not defined. - Trace: #0 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Service/OutputServiceProvider.php(27): Grav\Common\Twig\Twig->processSite('ico') #1 /Users/sghoweri/sites/bolt/www/vendor/pimple/pimple/src/Pimple/Container.php(118): Grav\Common\Service\OutputServiceProvider->Grav\Common\Service\{closure}(Object(Grav\Common\Grav)) #2 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Processors/RenderProcessor.php(19): Pimple\Container->offsetGet('output') #3 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(132): Grav\Common\Processors\RenderProcessor->process() #4 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(379): Grav\Common\Grav->Grav\Common\{closure}() #5 [internal function]: Grav\Common\Grav::Grav\Common\{closure}('render', 'Render', Object(Closure)) #6 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(355): call_user_func_array(Object(Closure), Array) #7 /Users/sghoweri/sites/bolt/www/system/src/Grav/Common/Grav.php(133): Grav\Common\Grav->__call('measureTime', Array) #8 /Users/sghoweri/sites/bolt/www/index.php(52): Grav\Common\Grav->process() #9 /Users/sghoweri/sites/bolt/www/system/router.php(24): require('/Users/sghoweri...') #10 {main} [] [] diff --git a/sandbox/grav/logs/popularity/daily.json b/sandbox/grav/logs/popularity/daily.json new file mode 100644 index 0000000000..2eddabd3be --- /dev/null +++ b/sandbox/grav/logs/popularity/daily.json @@ -0,0 +1 @@ +{"16-09-2017":6} \ No newline at end of file diff --git a/sandbox/grav/logs/popularity/monthly.json b/sandbox/grav/logs/popularity/monthly.json new file mode 100644 index 0000000000..d57c852448 --- /dev/null +++ b/sandbox/grav/logs/popularity/monthly.json @@ -0,0 +1 @@ +{"09-2017":6} \ No newline at end of file diff --git a/sandbox/grav/logs/popularity/totals.json b/sandbox/grav/logs/popularity/totals.json new file mode 100644 index 0000000000..aac99c953e --- /dev/null +++ b/sandbox/grav/logs/popularity/totals.json @@ -0,0 +1 @@ +{"\/":6} \ No newline at end of file diff --git a/sandbox/grav/logs/popularity/visitors.json b/sandbox/grav/logs/popularity/visitors.json new file mode 100644 index 0000000000..dc79b2830c --- /dev/null +++ b/sandbox/grav/logs/popularity/visitors.json @@ -0,0 +1 @@ +{"UNKNOWN":1505604752} \ No newline at end of file diff --git a/sandbox/grav/pattern-kit-core/.pk-config.yml b/sandbox/grav/pattern-kit-core/.pk-config.yml new file mode 100644 index 0000000000..bd42a5709a --- /dev/null +++ b/sandbox/grav/pattern-kit-core/.pk-config.yml @@ -0,0 +1,36 @@ +title: Pattern Kit + + +namespaces: + includes: source/_includes + layouts: source/_layouts + patterns: source/_patterns + bolt: public/templates +paths: + data: + - ../../../bolt-website/pattern-lab/data + schemas: + - ../../../bolt-website/pattern-lab/schemas + templates: + - ../../../bolt-website/pattern-lab/templates + docs: + - ../../../bolt-website/pattern-lab/docs + sg: + - ../../../bolt-website/pattern-lab/sg + +extensions: + data: .data.json + schemas: .schema.json + templates: .twig + docs: .docs.md + sg: .sg.md + +categories: + - Pattern + - Component + - Layout +assets: + css: + - /source/styles/style.css + js: + footer_js: diff --git a/sandbox/grav/pattern-kit-core/composer.json b/sandbox/grav/pattern-kit-core/composer.json new file mode 100644 index 0000000000..56ecf822b2 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/composer.json @@ -0,0 +1,53 @@ +{ + "name": "pattern-lab/edition-drupal-standard", + "description": "Standard Edition of Pattern Lab for Drupal.", + "keywords": ["pattern lab", "drupal"], + "homepage": "http://patternlab.io", + "license": "GPL-2.0+", + "type": "project", + "authors": [ + { + "name": "Dave Olsen", + "email": "dmolsen@gmail.com", + "homepage": "http://dmolsen.com", + "role": "Lead Developer" + }, + { + "name": "Evan Lovely", + "homepage": "http://evanlovely.com", + "role": "Developer" + } + ], + "support": { + "issues": "https://github.com/pattern-lab/edition-drupal-standard/issues", + "wiki": "http://patternlab.io/docs/", + "source": "https://github.com/pattern-lab/edition-drupal-standard/releases" + }, + "autoload": { + "psr-0": { + "PatternLab": "core/src/" + } + }, + "repositories": [ + { + "type": "path", + "url": "./../schemas/pattern-kit" + } + ], + "minimum-stability": "dev", + "require": { + "bolt/pattern-kit": "*" + }, + "config": { + "process-timeout": 0, + "github-oauth": { + "github.com": "99b8ce633d5fe3c5db9a4855f6a7146c3793885e" + } + }, + "scripts": { + "server": "php core/console --server", + "generate": "php core/console --generate", + "watch": "php core/console --watch", + "start": "php core/console --server --quiet & php core/console --watch" + } +} diff --git a/sandbox/grav/pattern-kit-core/composer.lock b/sandbox/grav/pattern-kit-core/composer.lock new file mode 100644 index 0000000000..6712073a8b --- /dev/null +++ b/sandbox/grav/pattern-kit-core/composer.lock @@ -0,0 +1,1191 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "81e48496a53f2d235365238774fbd3bd", + "packages": [ + { + "name": "bolt/pattern-kit", + "version": "dev-feature/v0.2-rc1", + "dist": { + "type": "path", + "url": "./../schemas/pattern-kit", + "reference": "335ff419548068776e1789c4812333db126e6f77", + "shasum": null + }, + "require": { + "deralex/yaml-config-service-provider": "@dev", + "erusev/parsedown": "^1.6", + "justinrainbow/json-schema": "~1.4", + "mnapoli/front-yaml": "^1.5", + "monolog/monolog": "~1.6", + "nesbot/carbon": "~1.6", + "php": ">=5.3.3", + "psr/log": "^1.0", + "silex/silex": "=1.2.5", + "symfony/twig-bridge": "^2.3", + "symfony/yaml": "*", + "twig/twig": "1.24.*" + }, + "type": "library", + "autoload": { + "psr-0": { + "PatternKit\\": "src" + } + }, + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Micah Godbolt", + "email": "micahgodbolt@gmail.com" + }, + { + "name": "Salem Ghoweri", + "url": "https://github.com/sghoweri" + } + ] + }, + { + "name": "deralex/yaml-config-service-provider", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/deralex/YamlConfigServiceProvider.git", + "reference": "20857a6f34a6276429c548ab67be6fa138457f1a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/deralex/YamlConfigServiceProvider/zipball/20857a6f34a6276429c548ab67be6fa138457f1a", + "reference": "20857a6f34a6276429c548ab67be6fa138457f1a", + "shasum": "" + }, + "require": { + "silex/silex": ">=1.0 <=1.3", + "symfony/yaml": "~2.4" + }, + "require-dev": { + "phpspec/phpspec": "2.0.*@dev" + }, + "suggest": { + "symfony/yaml": "~2.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-0": { + "DerAlex\\Silex": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Alexander Kluth", + "email": "contact@alexanderkluth.com" + } + ], + "description": "Silex ServiceProvider for using YAML configuration files", + "keywords": [ + "silex" + ], + "time": "2015-03-10 15:57:39" + }, + { + "name": "erusev/parsedown", + "version": "1.6.3", + "source": { + "type": "git", + "url": "https://github.com/erusev/parsedown.git", + "reference": "728952b90a333b5c6f77f06ea9422b94b585878d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/728952b90a333b5c6f77f06ea9422b94b585878d", + "reference": "728952b90a333b5c6f77f06ea9422b94b585878d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "autoload": { + "psr-0": { + "Parsedown": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Emanuil Rusev", + "email": "hello@erusev.com", + "homepage": "http://erusev.com" + } + ], + "description": "Parser for Markdown.", + "homepage": "http://parsedown.org", + "keywords": [ + "markdown", + "parser" + ], + "time": "2017-05-14T14:47:48+00:00" + }, + { + "name": "justinrainbow/json-schema", + "version": "1.6.1", + "source": { + "type": "git", + "url": "https://github.com/justinrainbow/json-schema.git", + "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/justinrainbow/json-schema/zipball/cc84765fb7317f6b07bd8ac78364747f95b86341", + "reference": "cc84765fb7317f6b07bd8ac78364747f95b86341", + "shasum": "" + }, + "require": { + "php": ">=5.3.29" + }, + "require-dev": { + "json-schema/json-schema-test-suite": "1.1.0", + "phpdocumentor/phpdocumentor": "~2", + "phpunit/phpunit": "~3.7" + }, + "bin": [ + "bin/validate-json" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.6.x-dev" + } + }, + "autoload": { + "psr-4": { + "JsonSchema\\": "src/JsonSchema/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Bruno Prieto Reis", + "email": "bruno.p.reis@gmail.com" + }, + { + "name": "Justin Rainbow", + "email": "justin.rainbow@gmail.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + }, + { + "name": "Robert Schönthal", + "email": "seroscho@googlemail.com" + } + ], + "description": "A library to validate a json schema.", + "homepage": "https://github.com/justinrainbow/json-schema", + "keywords": [ + "json", + "schema" + ], + "time": "2016-01-25T15:43:01+00:00" + }, + { + "name": "mnapoli/front-yaml", + "version": "1.5.2", + "source": { + "type": "git", + "url": "https://github.com/mnapoli/FrontYAML.git", + "reference": "f10c1dfee1604d15c2b0ab6826eecc1111d65543" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/mnapoli/FrontYAML/zipball/f10c1dfee1604d15c2b0ab6826eecc1111d65543", + "reference": "f10c1dfee1604d15c2b0ab6826eecc1111d65543", + "shasum": "" + }, + "require": { + "erusev/parsedown": "~1.0", + "symfony/yaml": "~2.1|^3.0" + }, + "require-dev": { + "league/commonmark": "~0.7", + "phpunit/phpunit": "~4.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Mni\\FrontYAML\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "time": "2016-10-01T11:06:51+00:00" + }, + { + "name": "monolog/monolog", + "version": "1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "reference": "fd8c787753b3a2ad11bc60c063cff1358a32a3b4", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "jakub-onderka/php-parallel-lint": "0.9", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpunit/phpunit": "~4.5", + "phpunit/phpunit-mock-objects": "2.3.0", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "time": "2017-06-19 01:22:40" + }, + { + "name": "nesbot/carbon", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/briannesbitt/Carbon.git", + "reference": "926aee5ab38c2868816aa760f862a85ad01cb61a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/926aee5ab38c2868816aa760f862a85ad01cb61a", + "reference": "926aee5ab38c2868816aa760f862a85ad01cb61a", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "symfony/translation": "~2.6 || ~3.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "~2", + "phpunit/phpunit": "~4.0 || ~5.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.23-dev" + } + }, + "autoload": { + "psr-4": { + "Carbon\\": "src/Carbon/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Brian Nesbitt", + "email": "brian@nesbot.com", + "homepage": "http://nesbot.com" + } + ], + "description": "A simple API extension for DateTime.", + "homepage": "http://carbon.nesbot.com", + "keywords": [ + "date", + "datetime", + "time" + ], + "time": "2017-02-06 22:02:47" + }, + { + "name": "pimple/pimple", + "version": "1.1.x-dev", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Pimple.git", + "reference": "cae373ff3d87f8763fe78557312ec7f47f5c745c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/cae373ff3d87f8763fe78557312ec7f47f5c745c", + "reference": "cae373ff3d87f8763fe78557312ec7f47f5c745c", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-0": { + "Pimple": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", + "homepage": "http://pimple.sensiolabs.org", + "keywords": [ + "container", + "dependency injection" + ], + "time": "2016-09-10 19:35:52" + }, + { + "name": "psr/log", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "reference": "4ebe3a8bf773a19edfe0a84b6585ba3d401b724d", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2016-10-10 12:19:37" + }, + { + "name": "silex/silex", + "version": "v1.2.5", + "source": { + "type": "git", + "url": "https://github.com/silexphp/Silex.git", + "reference": "ce75b98d82d4c509802e63005c618392db17afef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/silexphp/Silex/zipball/ce75b98d82d4c509802e63005c618392db17afef", + "reference": "ce75b98d82d4c509802e63005c618392db17afef", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "pimple/pimple": "~1.0", + "symfony/event-dispatcher": "~2.3,<2.7", + "symfony/http-foundation": "~2.3,<2.7", + "symfony/http-kernel": "~2.3,<2.7", + "symfony/routing": "~2.3,<2.7" + }, + "require-dev": { + "doctrine/dbal": "~2.2", + "monolog/monolog": "~1.4,>=1.4.1", + "swiftmailer/swiftmailer": "5.*", + "symfony/browser-kit": "~2.3,<2.7", + "symfony/config": "~2.3,<2.7", + "symfony/css-selector": "~2.3,<2.7", + "symfony/debug": "~2.3,<2.7", + "symfony/dom-crawler": "~2.3,<2.7", + "symfony/finder": "~2.3,<2.7", + "symfony/form": "~2.3,<2.7", + "symfony/locale": "~2.3,<2.7", + "symfony/monolog-bridge": "~2.3,<2.7", + "symfony/options-resolver": "~2.3,<2.7", + "symfony/process": "~2.3,<2.7", + "symfony/security": "~2.3,<2.7", + "symfony/serializer": "~2.3,<2.7", + "symfony/translation": "~2.3,<2.7", + "symfony/twig-bridge": "~2.3,<2.7", + "symfony/validator": "~2.3,<2.7", + "twig/twig": ">=1.8.0,<2.0-dev" + }, + "suggest": { + "symfony/browser-kit": "~2.3", + "symfony/css-selector": "~2.3", + "symfony/dom-crawler": "~2.3", + "symfony/form": "~2.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.2.x-dev" + } + }, + "autoload": { + "psr-0": { + "Silex": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "The PHP micro-framework based on the Symfony2 Components", + "homepage": "http://silex.sensiolabs.org", + "keywords": [ + "microframework" + ], + "time": "2015-06-04T21:24:58+00:00" + }, + { + "name": "symfony/debug", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/debug.git", + "reference": "efc9656dcb227e1459905d5aa51e43dfec76e752" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/debug/zipball/efc9656dcb227e1459905d5aa51e43dfec76e752", + "reference": "efc9656dcb227e1459905d5aa51e43dfec76e752", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "psr/log": "~1.0" + }, + "conflict": { + "symfony/http-kernel": ">=2.3,<2.3.24|~2.4.0|>=2.5,<2.5.9|>=2.6,<2.6.2" + }, + "require-dev": { + "symfony/class-loader": "~2.2|~3.0.0", + "symfony/http-kernel": "~2.3.24|~2.5.9|^2.6.2|~3.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Debug\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Debug Component", + "homepage": "https://symfony.com", + "time": "2017-08-27 14:29:03" + }, + { + "name": "symfony/event-dispatcher", + "version": "2.6.x-dev", + "target-dir": "Symfony/Component/EventDispatcher", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "672593bc4b0043a0acf91903bb75a1c82d8f2e02" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/672593bc4b0043a0acf91903bb75a1c82d8f2e02", + "reference": "672593bc4b0043a0acf91903bb75a1c82d8f2e02", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.0,>=2.0.5", + "symfony/dependency-injection": "~2.6", + "symfony/expression-language": "~2.6", + "symfony/phpunit-bridge": "~2.7", + "symfony/stopwatch": "~2.3" + }, + "suggest": { + "symfony/dependency-injection": "", + "symfony/http-kernel": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony EventDispatcher Component", + "homepage": "https://symfony.com", + "time": "2015-05-02 15:18:45" + }, + { + "name": "symfony/http-foundation", + "version": "2.6.x-dev", + "target-dir": "Symfony/Component/HttpFoundation", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-foundation.git", + "reference": "e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", + "reference": "e8fd1b73ac1c3de1f76c73801ddf1a8ecb1c1c9c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "symfony/expression-language": "~2.4", + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpFoundation\\": "" + }, + "classmap": [ + "Symfony/Component/HttpFoundation/Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpFoundation Component", + "homepage": "https://symfony.com", + "time": "2015-07-22 10:08:40" + }, + { + "name": "symfony/http-kernel", + "version": "2.6.x-dev", + "target-dir": "Symfony/Component/HttpKernel", + "source": { + "type": "git", + "url": "https://github.com/symfony/http-kernel.git", + "reference": "cdd991d304fed833514dc44d6aafcf19397c26cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/cdd991d304fed833514dc44d6aafcf19397c26cb", + "reference": "cdd991d304fed833514dc44d6aafcf19397c26cb", + "shasum": "" + }, + "require": { + "php": ">=5.3.3", + "psr/log": "~1.0", + "symfony/debug": "~2.6,>=2.6.2", + "symfony/event-dispatcher": "~2.6,>=2.6.7", + "symfony/http-foundation": "~2.5,>=2.5.4" + }, + "require-dev": { + "symfony/browser-kit": "~2.3", + "symfony/class-loader": "~2.1", + "symfony/config": "~2.0,>=2.0.5", + "symfony/console": "~2.3", + "symfony/css-selector": "~2.0,>=2.0.5", + "symfony/dependency-injection": "~2.2", + "symfony/dom-crawler": "~2.0,>=2.0.5", + "symfony/expression-language": "~2.4", + "symfony/finder": "~2.0,>=2.0.5", + "symfony/phpunit-bridge": "~2.7", + "symfony/process": "~2.0,>=2.0.5", + "symfony/routing": "~2.2", + "symfony/stopwatch": "~2.3", + "symfony/templating": "~2.2", + "symfony/translation": "~2.0,>=2.0.5", + "symfony/var-dumper": "~2.6" + }, + "suggest": { + "symfony/browser-kit": "", + "symfony/class-loader": "", + "symfony/config": "", + "symfony/console": "", + "symfony/dependency-injection": "", + "symfony/finder": "", + "symfony/var-dumper": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\HttpKernel\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony HttpKernel Component", + "homepage": "https://symfony.com", + "time": "2016-01-14 10:11:16" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "dev-master", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "reference": "7c8fae0ac1d216eb54349e6a8baa57d515fe8803", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.5-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + }, + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "time": "2017-06-14 15:44:48" + }, + { + "name": "symfony/routing", + "version": "2.6.x-dev", + "target-dir": "Symfony/Component/Routing", + "source": { + "type": "git", + "url": "https://github.com/symfony/Routing.git", + "reference": "0a1764d41bbb54f3864808c50569ac382b44d128" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/Routing/zipball/0a1764d41bbb54f3864808c50569ac382b44d128", + "reference": "0a1764d41bbb54f3864808c50569ac382b44d128", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "doctrine/annotations": "~1.0", + "doctrine/common": "~2.2", + "psr/log": "~1.0", + "symfony/config": "~2.2", + "symfony/expression-language": "~2.4", + "symfony/http-foundation": "~2.3", + "symfony/phpunit-bridge": "~2.7", + "symfony/yaml": "~2.0,>=2.0.5" + }, + "suggest": { + "doctrine/annotations": "For using the annotation loader", + "symfony/config": "For using the all-in-one router or any loader", + "symfony/expression-language": "For using expression matching", + "symfony/yaml": "For using the YAML loader" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "psr-0": { + "Symfony\\Component\\Routing\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Routing Component", + "homepage": "https://symfony.com", + "keywords": [ + "router", + "routing", + "uri", + "url" + ], + "time": "2015-07-09 16:02:48" + }, + { + "name": "symfony/translation", + "version": "3.2.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "df36a48672b929bf3995eb62c58d83004b1d0d50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/df36a48672b929bf3995eb62c58d83004b1d0d50", + "reference": "df36a48672b929bf3995eb62c58d83004b1d0d50", + "shasum": "" + }, + "require": { + "php": ">=5.5.9", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/config": "<2.8" + }, + "require-dev": { + "psr/log": "~1.0", + "symfony/config": "~2.8|~3.0", + "symfony/intl": "^2.8.18|^3.2.5", + "symfony/yaml": "~2.8|~3.0" + }, + "suggest": { + "psr/log": "To use logging capability in translator", + "symfony/config": "", + "symfony/yaml": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Translation\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Translation Component", + "homepage": "https://symfony.com", + "time": "2017-06-24 16:45:17" + }, + { + "name": "symfony/twig-bridge", + "version": "v2.8.11", + "source": { + "type": "git", + "url": "https://github.com/symfony/twig-bridge.git", + "reference": "5e9679f7085e99adb5248e07b4677494b8f884b5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/5e9679f7085e99adb5248e07b4677494b8f884b5", + "reference": "5e9679f7085e99adb5248e07b4677494b8f884b5", + "shasum": "" + }, + "require": { + "php": ">=5.3.9", + "twig/twig": "~1.23|~2.0" + }, + "require-dev": { + "symfony/asset": "~2.7|~3.0.0", + "symfony/console": "~2.8|~3.0.0", + "symfony/expression-language": "~2.4|~3.0.0", + "symfony/finder": "~2.3|~3.0.0", + "symfony/form": "~2.8.4", + "symfony/http-kernel": "~2.8|~3.0.0", + "symfony/polyfill-intl-icu": "~1.0", + "symfony/routing": "~2.2|~3.0.0", + "symfony/security": "~2.6|~3.0.0", + "symfony/security-acl": "~2.6|~3.0.0", + "symfony/stopwatch": "~2.2|~3.0.0", + "symfony/templating": "~2.1|~3.0.0", + "symfony/translation": "~2.7|~3.0.0", + "symfony/var-dumper": "~2.7.16|~2.8.9|~3.0.9", + "symfony/yaml": "~2.0,>=2.0.5|~3.0.0" + }, + "suggest": { + "symfony/asset": "For using the AssetExtension", + "symfony/expression-language": "For using the ExpressionExtension", + "symfony/finder": "", + "symfony/form": "For using the FormExtension", + "symfony/http-kernel": "For using the HttpKernelExtension", + "symfony/routing": "For using the RoutingExtension", + "symfony/security": "For using the SecurityExtension", + "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/templating": "For using the TwigEngine", + "symfony/translation": "For using the TranslationExtension", + "symfony/var-dumper": "For using the DumpExtension", + "symfony/yaml": "For using the YamlExtension" + }, + "type": "symfony-bridge", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Bridge\\Twig\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Twig Bridge", + "homepage": "https://symfony.com", + "time": "2016-09-06T10:55:00+00:00" + }, + { + "name": "symfony/yaml", + "version": "2.8.x-dev", + "source": { + "type": "git", + "url": "https://github.com/symfony/yaml.git", + "reference": "4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/yaml/zipball/4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5", + "reference": "4c29dec8d489c4e37cf87ccd7166cd0b0e6a45c5", + "shasum": "" + }, + "require": { + "php": ">=5.3.9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.8-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Yaml\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony Yaml Component", + "homepage": "https://symfony.com", + "time": "2017-06-01 20:52:29" + }, + { + "name": "twig/twig", + "version": "v1.24.2", + "source": { + "type": "git", + "url": "https://github.com/twigphp/Twig.git", + "reference": "33093f6e310e6976baeac7b14f3a6ec02f2d79b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/33093f6e310e6976baeac7b14f3a6ec02f2d79b7", + "reference": "33093f6e310e6976baeac7b14f3a6ec02f2d79b7", + "shasum": "" + }, + "require": { + "php": ">=5.2.7" + }, + "require-dev": { + "symfony/debug": "~2.7", + "symfony/phpunit-bridge": "~2.7" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.24-dev" + } + }, + "autoload": { + "psr-0": { + "Twig_": "lib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com", + "homepage": "http://fabien.potencier.org", + "role": "Lead Developer" + }, + { + "name": "Armin Ronacher", + "email": "armin.ronacher@active-4.com", + "role": "Project Founder" + }, + { + "name": "Twig Team", + "homepage": "http://twig.sensiolabs.org/contributors", + "role": "Contributors" + } + ], + "description": "Twig, the flexible, fast, and secure template language for PHP", + "homepage": "http://twig.sensiolabs.org", + "keywords": [ + "templating" + ], + "time": "2016-09-01T17:50:53+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "dev", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/sandbox/grav/pattern-kit-core/index.php b/sandbox/grav/pattern-kit-core/index.php new file mode 100644 index 0000000000..b10a24a47d --- /dev/null +++ b/sandbox/grav/pattern-kit-core/index.php @@ -0,0 +1,6 @@ +run(); diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/.gitignore b/sandbox/grav/pattern-kit-core/pattern-kit/.gitignore new file mode 100644 index 0000000000..a156070537 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/.gitignore @@ -0,0 +1,5 @@ +.DS_Store +.idea +storage/logs/*.log +composer.lock +vendor diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/.pk-config.yml b/sandbox/grav/pattern-kit-core/pattern-kit/.pk-config.yml new file mode 100644 index 0000000000..59f60f0fa8 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/.pk-config.yml @@ -0,0 +1,34 @@ +title: Pattern Kit + +paths: + data: + - /resources/fixtures + test_data: + - /resources/fixtures + schemas: + - /resources/fixtures + templates: + - /resources/fixtures + docs: + - /resources/fixtures + sg: + - /resources/fixtures +extensions: + data: .docs.json + test_data: .test.json + schemas: .json + templates: .twig + docs: .docs.md + sg: .sg.md +categories: + - Pattern + - Sub Pattern + - Layout + - Component + - Atom +assets: + css: + js: + footer_js: +dev: true + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/CHANGELOG-1.x.md b/sandbox/grav/pattern-kit-core/pattern-kit/CHANGELOG-1.x.md new file mode 100644 index 0000000000..4e3448a05e --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/CHANGELOG-1.x.md @@ -0,0 +1,24 @@ +## 1.2 Minor style tweaks and functionality additions (2017-03-09) + +Tag: [1.2](https://github.com/PatternBuilder/pattern-kit/releases/tag/V1.2) + +- Change rendering of the styleguide documentation to lean on bootstrap column grid +- Add logic to render only the tabs that work for a particular atom based on what data is present +- Add alert messaging for atoms in the preview area to flag that it will not render +- Reduce and organize CSS with section comments for clarity +- Add color to the nesting of the schema editor +- Increase size of buttons for better accessibility for those of us with poor vision :D +- Add ability to designate order on documentation in the markdown and render the secondary nav in that order +- Update jQuery package to reference CDN for consistency + + +## 1.0 support-at-data-and-yaml (2016-08-01) + +Tag: [1.0](https://github.com/PatternBuilder/pattern-kit/tree/V1.1) + +Now supporting YAML files for docs using the same basename as JSON. Also supporting @data to reference other component's doc data file. So @component would import component.docs.json or component.docs.yaml into your data file. + + +## 1.0 Initial Release (2016-06-13) + +Tag: [1.0](https://github.com/PatternBuilder/pattern-kit/tree/V1.0) diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/README.md b/sandbox/grav/pattern-kit-core/pattern-kit/README.md new file mode 100755 index 0000000000..1bdb5a82ba --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/README.md @@ -0,0 +1,94 @@ +# Pattern Kit + +Pattern Kit is an application that lets you preview your library of templates and manipulate their content by interacting with a form built from the schemas. It is both a development tool and a public facing pattern library. + +For a demo check out [Pattern Kit Demo](http://patternkit.info/sg/). + +# Installation + +Note, by following these instructions you do _not_ need to clone this git repository. + +## Create composer.json at pattern library root and require pattern kit + +``` +"require": { + "pattern-builder/pattern-kit": "@dev" +}, +"repositories": [ + { + "type": "vcs", + "url": "https://github.com/PatternBuilder/pattern-kit" + } +] +``` + +## Add index.php at pattern library root +``` +run(); + +``` + +## Add .pk-config.yml at pattern library root + +- Create arrays of paths to your data, schema, template, docs and styleguide files (relative to config) +- Set the file extensions for each file type +- List categories in order you'd like them to appear in navigation +- Create arrays of assets for css, js and footer js (including live reload if necessary) + +``` + +title: Project Title + +paths: # relative to your pattern library root + data: + - path/to/sample/data + schemas: + - path/to/schemas + templates: + - path/to/templates + docs: + - path/to/schemas-docs + sg: + - path/to/stylelguide/docs +extensions: + data: .docs.json + schemas: .json + templates: .twig + docs: .docs.md + sg: .sg.md +categories: + - Pattern + - Sub Pattern + - Layout + - Component + - Atom +assets: + css: + - path/to/css + - path/to/othercss + js: + - path/to/js + - path/to/otherjs + footer_js: + - path/to/footer_js + - path/to/otherfooter_js + - //localhost:1336/livereload.js +``` + +In your terminal, + +``` +$ cd [pattern library root] +$ composer install +``` + +# Use Pattern Kit + +Point MAMP or local PHP server at your index.php file + +php -S 0:9001 -t ./ diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/composer.json b/sandbox/grav/pattern-kit-core/pattern-kit/composer.json new file mode 100755 index 0000000000..80c9ea4aeb --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/composer.json @@ -0,0 +1,33 @@ +{ + "name": "bolt/pattern-kit", + "authors": [ + { + "name": "Micah Godbolt", + "email": "micahgodbolt@gmail.com" + }, + { + "name": "Salem Ghoweri", + "url": "https://github.com/sghoweri" + } + ], + "license": "MIT", + "require": { + "php": ">=5.3.3", + "psr/log": "^1.0", + "twig/twig": "1.24.*", + "silex/silex": "=1.2.5", + "justinrainbow/json-schema": "~1.4", + "deralex/yaml-config-service-provider": "@dev", + "monolog/monolog": "~1.6", + "nesbot/Carbon": "~1.6", + "erusev/parsedown": "^1.6", + "mnapoli/front-yaml": "^1.5", + "symfony/twig-bridge": "^2.3", + "symfony/yaml": "*" + }, + "autoload": { + "psr-0": { + "PatternKit\\": "src" + } + } +} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/index.php b/sandbox/grav/pattern-kit-core/pattern-kit/index.php new file mode 100644 index 0000000000..d3cee04004 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/index.php @@ -0,0 +1,6 @@ +run(); diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/cat.jpg b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/cat.jpg new file mode 100644 index 0000000000..37edc8ab57 Binary files /dev/null and b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/cat.jpg differ diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/company-colors.sg.md b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/company-colors.sg.md new file mode 100644 index 0000000000..56cad14ff8 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/company-colors.sg.md @@ -0,0 +1,7 @@ +--- +title: Brand Colors +--- + +`$brand-primary` +
`#c00` +
diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/foo.docs.yaml b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/foo.docs.yaml new file mode 100644 index 0000000000..be2a6eb85e --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/foo.docs.yaml @@ -0,0 +1,2 @@ +bar: abc +baz: def diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/foo2.docs.json b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/foo2.docs.json new file mode 100644 index 0000000000..9809f3bfe2 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/foo2.docs.json @@ -0,0 +1,4 @@ +{ + "bar": "123", + "baz": "456" +} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/get-started.sg.md b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/get-started.sg.md new file mode 100644 index 0000000000..bb7fec7b27 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/get-started.sg.md @@ -0,0 +1,5 @@ +--- +title: How to get set up +--- + +This is how to get set up diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/image.docs.yaml b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/image.docs.yaml new file mode 100644 index 0000000000..1fd924f609 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/image.docs.yaml @@ -0,0 +1,2 @@ +src: /resources/fixtures/cat.jpg +alt: This is a photo diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/image.json b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/image.json new file mode 100644 index 0000000000..3f480cca93 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/image.json @@ -0,0 +1,24 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Image", + "category": "atom", + "type": "object", + "format": "grid", + "properties": { + "name": { + "type": "string", + "default": "image", + "options": { + "hidden": true + } + }, + "src": { + "type": "string" + }, + "alt": { + "type": "string" + } + }, + "required": ["name"], + "additionalProperties": false +} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/image.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/image.twig new file mode 100644 index 0000000000..229abf64f9 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/image.twig @@ -0,0 +1 @@ +{{alt}} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/index.sg.md b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/index.sg.md new file mode 100644 index 0000000000..6dfc925b20 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/index.sg.md @@ -0,0 +1,5 @@ +--- +title: Get Started +--- + +This is the getting started Docs diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.docs.md b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.docs.md new file mode 100644 index 0000000000..02ba79c2d5 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.docs.md @@ -0,0 +1,5 @@ +--- +--- + +## Docs +hello there diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.docs.yaml b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.docs.yaml new file mode 100644 index 0000000000..1594fa60bd --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.docs.yaml @@ -0,0 +1,9 @@ +name: test +quotation: We think of Red Hat as one of just a handful of superior engineering and support organizations in the United States. +attribution_name: John Defeo +attribution_title: President of Infrastructure, CIGNA +image: @image +foo: + - @foo + - @foo2 + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.json b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.json new file mode 100644 index 0000000000..6a6bb8ff55 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.json @@ -0,0 +1,50 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Quote", + "category": "component", + "type": "object", + "format": "grid", + "properties": { + "name": { + "type": "string", + "default": "test", + "options": { + "hidden": true + } + }, + "quotation": { + "title": "Quote Text", + "type": "string", + "format": "textarea" + }, + "attribution_name": { + "title": "Attribution", + "description": "Who said it?", + "type": "string" + }, + "attribution_title": { + "title": "Attribution Title", + "description": "What is their job title?", + "type": "string" + }, + "image": { + "$ref": "image.json" + }, + "foo": { + "type": "array", + "items": { + "type": "object", + "properties": { + "bar": { + "type": "string" + }, + "baz": { + "type": "string" + } + } + } + } + }, + "required": ["name", "quotation", "attribution_name"], + "additionalProperties": false +} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.twig new file mode 100644 index 0000000000..9cd041987b --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/fixtures/test.twig @@ -0,0 +1,15 @@ +
+ {% include 'image.twig' with image only %} +
+

{{quotation}}

+
+ {{attribution_name}}{% if attribution_title %}, {{attribution_title}}{% endif %} +
+
+
+ + +{% for item in foo %} + {{item.bar}}
+ {{item.baz}}

+{% endfor %} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/_pattern-kit-app.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/_pattern-kit-app.twig new file mode 100644 index 0000000000..6b3a9ac4da --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/_pattern-kit-app.twig @@ -0,0 +1,62 @@ +{% if app_config.dev == true %} +{% set vendor_path = "/" %} +{% else %} +{% set vendor_path = "/vendor/pattern-builder/pattern-kit/" %} +{% endif %} + + + + + + + + + + + + + + + + + + + + + + + + + + +{% block navigation %} +{% endblock %} + +
+
+ {% if block( 'secondary_nav' ) %} + + {% endif %} + + {% if block( 'left' ) %} +
+ {% block left %}{% endblock %} +
+ {% endif %} + + {% if block( 'right' ) %} +
+ {% block right %}{% endblock %} +
+ {% endif %} + +
+
+ +{% block footer %}{% endblock %} + + + + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/basic.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/basic.twig new file mode 100644 index 0000000000..6eefb845cb --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/basic.twig @@ -0,0 +1,27 @@ + + + + + + + {% for css in app_config.assets.css %} + + {% endfor %} + + {% for js in app_config.assets.js %} + + {% endfor %} + + +
+ {% if (name) or (template) %} + {% include template ?: name ~ '.twig' %} + {% else %} +

Data does not include required "name" property

+ {% endif %} +
+{% for js in app_config.assets.footer_js %} + +{% endfor %} + + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/display-schema.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/display-schema.twig new file mode 100644 index 0000000000..492907f4e5 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/display-schema.twig @@ -0,0 +1,97 @@ +{% extends '_pattern-kit-app.twig' %} + + + + +{% block navigation %} + {% include 'navigation.twig' with nav only %} +{% endblock %} + +{% block left %} + {% include 'iframe-holder.twig' %} +{% endblock %} + +{% block right %} + {% set json_data %} +
+

Data

+
+

+        
+ {% endset %} + + {% set twig_template %} +
{{ template_markup }}
+ {% endset %} + + {% set docs %} + {% include 'docs.twig' with { + "meta": docs_yaml, + "content": docs_content, + "data": docs_json + } only %} + {% endset %} + + {% set tabs_data = {} %} + {% if raw_schema.category == "atom" %} + {% if template_markup %} + {% set twig = { + 'title': 'Twig template', + 'content': twig_template, + 'active': false + } %} + {% endif %} + {% if json_data %} + {% set json = { + 'title': 'JSON data', + 'content': json_data, + 'active': false + } %} + {% endif %} + {% set tabs_data = { + 'tabs': [{ + 'title': 'Docs', + 'content': docs, + 'active': true + }, { + 'title': 'Schema editor', + 'content': '
', + 'active': false + }] | merge([ twig ]) | merge([ json_data ]) + } %} + {% else %} + {% set tabs_data = { + 'tabs': [{ + 'title': 'Docs', + 'content': docs, + 'active': true + },{ + 'title': 'Schema editor', + 'content': '
', + 'active': false + },{ + 'title': 'JSON data', + 'content': json_data, + 'active': false + },{ + 'title': 'Twig template', + 'content': twig_template, + 'active': false + }] + } %} + {% endif %} + + {% include 'editor-tabs.twig' with tabs_data %} + + +{% endblock %} + +{% block footer %} + +{% endblock %} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/display-sg.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/display-sg.twig new file mode 100644 index 0000000000..7b9ab94dc5 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/display-sg.twig @@ -0,0 +1,23 @@ +{% extends '_pattern-kit-app.twig' %} + + +{% block navigation %} + {% include 'navigation.twig' with nav only %} +{% endblock %} + +{% block secondary_nav %} + {% include 'secondary-nav.twig' %} +{% endblock %} + +{% block left %} +
+

{{sg_yaml.title}}

+ + {{sg_content|raw}} +
+{% endblock %} + + +{% block footer %} + +{% endblock %} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/docs.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/docs.twig new file mode 100644 index 0000000000..5e84b2d246 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/docs.twig @@ -0,0 +1 @@ +
{{ content|raw }}
diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/editor-accordion.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/editor-accordion.twig new file mode 100644 index 0000000000..156b381492 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/editor-accordion.twig @@ -0,0 +1,21 @@ +
+{% for tab in tabs %} +
+ +
+
+ {{tab.content|raw}} +
+
+
+{% endfor %} +
+ + + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/editor-tabs.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/editor-tabs.twig new file mode 100644 index 0000000000..e6d648cfb9 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/editor-tabs.twig @@ -0,0 +1,17 @@ +
+ + + + +
+ {% for tab in tabs %} +
+ {{ tab.content|raw }} +
+ {% endfor %} +
+
diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/iframe-holder.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/iframe-holder.twig new file mode 100644 index 0000000000..7b1b6d40bc --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/iframe-holder.twig @@ -0,0 +1,21 @@ +
+ +
+{% if raw_schema.category != "atom" %} +
+
+ +
+
+
+
+
+
+{% else %} +
+

Atoms do not contain styles or often markup and thus cannot be previewed in this space.

+
+{% endif %} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/navigation.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/navigation.twig new file mode 100644 index 0000000000..d028b54d87 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/navigation.twig @@ -0,0 +1,36 @@ + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/secondary-nav.twig b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/secondary-nav.twig new file mode 100644 index 0000000000..ffc808a75f --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/resources/templates/secondary-nav.twig @@ -0,0 +1,31 @@ + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/ApiControllerProvider.php b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/ApiControllerProvider.php new file mode 100644 index 0000000000..22ad2573f5 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/ApiControllerProvider.php @@ -0,0 +1,134 @@ +post('/render/{target}', function (Request $request, $target) use ($app) { + + if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) { + + $contents = json_decode($request->getContent(), true); + + if (isset($app['config'])) { + $contents["app_config"] = $app['config']; + } + + if ($target == "page") { + if ($contents['name'] || $contents['template']) { + return $app['twig']->render("basic.twig", $contents); + } + else { + return "sorry"; + } + } + + if (!empty($contents["template"])) { + return $app['twig']->render($contents["template"], $contents); + } + else { + return $app['twig']->render($contents["name"] . '.twig', $contents); + } + } + }); + + + + $controllers->post('/validate', function (Request $request) use ($app) { + + if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) { + + function traverse($data, &$to_test, $i=0, $path="root") { + foreach ($data as $key => &$value) { + if (is_array($value)) { + $array_name = $key; + foreach ($value as $key=>$item) { + if (is_object($item)) { + if ($item->name) { + $location = $path . "." . $array_name . "." . $key; + $to_test[] = array("depth" => $i, "obj" => $item, "path" => $location); + } + traverse($item, $to_test, $i+1, $location); + } + } + } + } + usort($to_test, function($a, $b) { + return $b['depth'] - $a['depth']; + }); + } + + function test($data, &$reply) { + $retriever = new \JsonSchema\Uri\UriRetriever; + $refResolver = new \JsonSchema\RefResolver($retriever); + $refResolver::$maxDepth = 9999; + $validator = new \JsonSchema\Validator(); + $valid = true; + foreach ($data as $item) { + $path = get_asset_path($item['obj']->name, 'schemas'); + $schema = $retriever->retrieve('file://' . realpath($path)); + $refResolver->resolve($schema); + + //Validate + $validator->check($item['obj'], $schema); + + if (!$validator->isValid()) { + $valid = false; + foreach ($validator->getErrors() as $error) { + $path = $item['path']; + $name = $item['obj']->name; + $property = $error['property']; + $message = $error['message']; + $reply .= sprintf("Error at %s:
%s [%s] %s\n

", $path, $name, $property, $message); + } + break; + } + } + } + $to_test = array(); + $reply = ""; + $retriever = new \JsonSchema\Uri\UriRetriever; + $refResolver = new \JsonSchema\RefResolver($retriever); + $refResolver::$maxDepth = 9999; + $validator = new \JsonSchema\Validator(); + + $data = (object) json_decode($request->getContent()); + + + $path = get_asset_path($data->name, 'schemas'); + + $schema = $retriever->retrieve('file://' . realpath($path)); + + $refResolver->resolve($schema); + + //Validate + $validator->check($data, $schema); + + if ($validator->isValid()) { + $reply = "The supplied JSON validates against the schema.\n"; + } else { + $to_test[] = array("depth" => 0, "obj" => $data, "path" => "root"); + traverse($data, $to_test); + test($to_test, $reply); + } + + return $reply; + + } + + }); + + + return $controllers; + } +} +?> diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/Controllers/SchemaController.php b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/Controllers/SchemaController.php new file mode 100755 index 0000000000..7213904745 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/Controllers/SchemaController.php @@ -0,0 +1,58 @@ +schemaService = $service; + // } + + // public function getAll() + // { + // return new JsonResponse($this->schemaService->getAll()); + // } + + // public function save(Request $request) + // { + + // $note = $this->getDataFromRequest($request); + // return new JsonResponse(array("id" => $this->schemaService->save($note))); + + // } + + // public function update($id, Request $request) + // { + // $note = $this->getDataFromRequest($request); + // $this->schemaService->update($id, $note); + // return new JsonResponse($note); + + // } + + // public function delete($id) + // { + + // return new JsonResponse($this->schemaService->delete($id)); + + // } + + // public function getDataFromRequest(Request $request) + // { + // return $note = array( + // "note" => $request->request->get("note") + // ); + // } +} diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/RoutesLoader.php b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/RoutesLoader.php new file mode 100755 index 0000000000..30696d308b --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/RoutesLoader.php @@ -0,0 +1,34 @@ +app = $app; + $this->instantiateControllers(); + + } + + private function instantiateControllers() + { + $this->app['schema.controller'] = $this->app->share(function () { + return new Controllers\SchemaController(); + }); + } + + public function bindRoutesToControllers() + { + $api = $this->app["controllers_factory"]; + + $api->get('/tests/{name}/{data_array}', "schema.controller:getTests"); + + $this->app->mount('/', $api); + } +} + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/SchemaControllerProvider.php b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/SchemaControllerProvider.php new file mode 100644 index 0000000000..3f7ebf6626 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/SchemaControllerProvider.php @@ -0,0 +1,91 @@ +get('/{pattern}', function ($pattern) use ($app) { + + $retriever = new \JsonSchema\Uri\UriRetriever; + $path = get_asset_path($pattern, 'schemas'); + $seed_path = get_asset_path($pattern, 'data'); + $template_path = get_asset_path($pattern, 'templates'); + $docs_path = get_asset_path($pattern, 'docs'); + $data = array(); + + + + $schema = $retriever->retrieve('file://' . realpath($path)); + + // Navigation + $data['nav']= getNav($pattern); + if (array_key_exists('sg', $app["config"]["paths"])) { + $data['nav']['sg_active'] = true; + } + // end navigation + + if ($seed_path) { + + $seed_file = file_get_contents('file://' . realpath($seed_path)); + if (($pathinfo = pathinfo($seed_path)) && isset($pathinfo['extension']) && $pathinfo['extension'] == 'yaml') { + + $seed_data = Yaml::parse($seed_file); + + } + elseif (!empty($seed_file)) { + $seed_data = json_decode($seed_file, true); + } + else { + $seed_data = array(); + } + data_replace($seed_data); + } + else $seed_data = array(); + + + $refResolver = new \JsonSchema\RefResolver($retriever); + $refResolver::$maxDepth = 9999; + $refResolver->resolve($schema); + + if (isset($app['config'])) { + $data["app_config"] = $app['config']; + } + + + $docs_file = file_get_contents('file://' . realpath($docs_path)); + + $parser = new \Mni\FrontYAML\Parser;; + + $docs_data = $parser->parse($docs_file); + + $data['docs_yaml'] = $docs_data->getYAML(); + $data['docs_content'] = $docs_data->getContent(); + + $data['schema'] = json_encode($schema); + $data['docs_json'] = (array) $seed_data; + $data['starting'] = json_encode($seed_data); + $data['raw_schema'] = (array) json_decode(file_get_contents($path), true); + if ($template_path) { + $template_file = file_get_contents('file://' . realpath($template_path)); + $data['template_markup'] = $template_file; + + } + + return $app['twig']->render("display-schema.twig", $data); + + })->bind('schema'); + + return $controllers; + } +} +?> diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/StyleGuideControllerProvider.php b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/StyleGuideControllerProvider.php new file mode 100644 index 0000000000..fa7d296949 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/StyleGuideControllerProvider.php @@ -0,0 +1,71 @@ +get('/', function ($pattern) use ($app) { + + + $sg_path = get_asset_path($pattern, 'sg'); + + $sg_file = file_get_contents('file://' . realpath($sg_path)); + + $parser = new Parser(); + + $sg_data = $parser->parse($sg_file); + + if (isset($app['config'])) { + $data["app_config"] = $app['config']; + } + + + $data['secondary_nav'] = getDocNav($pattern); + $data['nav']= getNav($pattern); + $data['sg_yaml'] = $sg_data->getYAML(); + $data['sg_content'] = $sg_data->getContent(); + + return $app['twig']->render("display-sg.twig", $data); + })->value('pattern', "index")->bind('styleguide-home'); + + $controllers->get('/{pattern}', function ($pattern) use ($app) { + + + $sg_path = get_asset_path($pattern, 'sg'); + + $sg_file = file_get_contents('file://' . realpath($sg_path)); + + $parser = new Parser(); + + $sg_data = $parser->parse($sg_file); + + if (isset($app['config'])) { + $data["app_config"] = $app['config']; + } + + + $data['secondary_nav'] = getDocNav($pattern); + $data['nav']= getNav($pattern); + $data['sg_yaml'] = $sg_data->getYAML(); + $data['sg_content'] = $sg_data->getContent(); + + return $app['twig']->render("display-sg.twig", $data); + })->bind('styleguide'); + + + return $controllers; + } +} +?> + + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/TestsControllerProvider.php b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/TestsControllerProvider.php new file mode 100644 index 0000000000..567f1a03d0 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/src/PatternKit/TestsControllerProvider.php @@ -0,0 +1,51 @@ +get('/{name}/{data_array}', function ($name, $data_array) use ($app) { + + $data_path = get_asset_path($name, "test_data"); + + if (file_exists($data_path)) { + $file_data = json_decode(file_get_contents($data_path), true); + } + else { + trigger_error($name . " is missing an associated data file. Create " . $name . ".tests.json in the " . $name . "/library folder.

"); + exit; + } + + // Test if array of tests data + if (array_keys($file_data) == range(0, count($file_data) - 1)) { + $file_data = $file_data[$data_array]["data"]; + } + + if ($file_data['name'] || $file_data['template']) { + if (isset($app['config'])) { + $file_data["app_config"] = $app['config']; + } + return $app['twig']->render("basic.twig", $file_data); + } + else { + trigger_error($name . ".tests.json is missing a name or template value.

"); + exit; + } + + }) + ->value('data_array', 0); + + return $controllers; + } +} +?> + + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/src/app.php b/sandbox/grav/pattern-kit-core/pattern-kit/src/app.php new file mode 100644 index 0000000000..7d022002a0 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/src/app.php @@ -0,0 +1,280 @@ +register(new YamlConfigServiceProvider("./.pk-config.yml")); +$app->register(new Silex\Provider\UrlGeneratorServiceProvider()); + + +//handling CORS preflight request +$app->before(function (Request $request) { + if ($request->getMethod() === "OPTIONS") { + $response = new Response(); + $response->headers->set("Access-Control-Allow-Origin","*"); + $response->headers->set("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS"); + $response->headers->set("Access-Control-Allow-Headers","Content-Type"); + $response->setStatusCode(200); + return $response->send(); + } +}, Application::EARLY_EVENT); + +//handling CORS respons with right headers +$app->after(function (Request $request, Response $response) { + $response->headers->set("Access-Control-Allow-Origin","*"); + $response->headers->set("Access-Control-Allow-Methods","GET,POST,PUT,DELETE,OPTIONS"); +}); + +// //accepting JSON +// $app->before(function (Request $request) { +// if (0 === strpos($request->headers->get('Content-Type'), 'application/json')) { +// $data = json_decode($request->getContent(), true); +// $request->request->replace(is_array($data) ? $data : array()); +// } +// }); + +$app->register(new ServiceControllerServiceProvider()); + +$app->register(new HttpCacheServiceProvider(), array("http_cache.cache_dir" => ROOT_PATH . "/storage/cache",)); + +// $app->register(new MonologServiceProvider(), array( +// "monolog.logfile" => ROOT_PATH . "/storage/logs/" . Carbon::now('America/Los_Angeles')->format("Y-m-d") . ".log", +// "monolog.name" => "application" +// )); + +$app['debug'] = true; + + +// Set Up Twig + +//// Twig Template Paths +$twig_template_paths = array(); + +array_push($twig_template_paths, ROOT_PATH . '/resources/templates'); + +if (is_string($app['config']['paths']['templates'])) { + array_push($twig_template_paths, realpath('./' . $app['config']['paths']['templates']) ); +} +elseif (is_array($app['config']['paths']['templates'])) { + foreach ($app['config']['paths']['templates'] as $value) { + array_push($twig_template_paths, realpath('./' . $value)); + } +} + +//// Register Twig + +$app->register(new Silex\Provider\TwigServiceProvider(), array( + 'twig.path' => $twig_template_paths, + 'twig.options' => array( + 'strict_variables' => false + ), +)); + + +// Custom Functions + + +function data_replace(&$data) { + if (is_array($data)) { + foreach ($data as &$value) { + if (is_array($value) ) { + data_replace($value); + } + elseif (is_string($value) && $value[0] == '@') { + $file_path = 'file://' . realpath(get_asset_path(substr($value, 1), 'data')); + if (($pathinfo = pathinfo($file_path)) && isset($pathinfo['extension']) && $pathinfo['extension'] == 'yaml') { + $data_replace_with = Yaml::parse(file_get_contents($file_path)); + } + else { + $data_replace_with = json_decode(file_get_contents($file_path), true); + } + + $value = data_replace($data_replace_with); + } + } + } + return $data; +} + +//// Get path to matching asset +function get_asset_path($name, $type) { + global $app; + + if (in_array($type, array("templates", "data", "schemas", "docs", "sg", "test_data"))) { + $return = NULL; + $paths = $app['config']['paths'][$type]; + if (is_array($paths)) { + $paths = array_reverse($paths); + } + if ($paths) { + foreach ($paths as $path) { + $extension = $app['config']['extensions'][$type]; + $yaml_extension = str_replace('.json', '.yaml', $extension); + $dir = './' . $path; + $file_path = "{$dir}/{$name}{$extension}"; + $yaml_file_path = "{$dir}/{$name}{$yaml_extension}"; + if (is_dir($dir) && is_readable($file_path)) { + $return = $file_path; + break; + } + else if (is_dir($dir) && is_readable($yaml_file_path)) { + $return = $yaml_file_path; + break; + } + } + } + + return $return; + } + else { + throw new Exception($type . ' is not equal to template, data or schema'); + } +} + +//// Create Primary Navigation for pattern library + +function getNav($pattern) { + global $app; + $categories = $app['config']['categories']; + $schema_paths = array(); + $nav = array(); + $nav['title'] = $app['config']['title']; + + foreach ($app['config']['paths']['schemas'] as $path) { + $files = scandir("./" . $path); + $schema_paths[] = array( + 'location' => $path, + 'files' => $files + ); + } + + if ($categories) { + foreach ($categories as $category) { + $value = strtolower(str_replace(' ', '_', $category)); + $nav['categories'][$value] = array(); + $nav['categories'][$value]['title'] = $category; + $nav['categories'][$value]['path'] = '/' . $value; + } + } + + + foreach ($schema_paths as $path) { + foreach ($path['files'] as $file) { + if (strpos($file, 'json') !== false) { + $nav_item = array(); + $contents = json_decode(file_get_contents('./' . $path['location'] . "/" . $file), true); + $contents['name'] = substr($file, 0, -5); + $category = isset($contents['category']) ? $contents['category'] : false; + $nav_item['title'] = isset($contents['title']) ? $contents['title'] : $contents['name']; + $nav_item['path'] = $contents['name']; + if ($contents['name'] == $pattern) { + $nav_item['active'] = true; + } + if ($category) { + $nav['categories'][$category]['items'][] = $nav_item; + } + + } + } + } + return $nav; +} + + +//// Create secondary navigation for styleguide + +function getDocNav($pattern) { + global $app; + $nav = array(); + $parser = new Parser(); + + foreach ($app['config']['paths']['sg'] as $path) { + $files = glob('./' . $path .'/*' . $app['config']['extensions']['sg']); + foreach ($files as $value) { + $value_parts = str_split(basename($value), strpos(basename($value), ".")); + $nav_item = array(); + $sg_file = file_get_contents($value); + $sg_data = $parser->parse($sg_file); + $data['sg_yaml'] = $sg_data->getYAML(); + $nav_item['title'] = $data['sg_yaml']['title']; + $nav_item['order'] = $data['sg_yaml']['order']; + $nav_item['path'] = $value_parts[0]; + if ($value_parts[0] == $pattern) { + $nav_item['active'] = true; + } + if ($value_parts[0] == 'index') { + $nav_item['path'] = NULL; + array_unshift($nav, $nav_item); + } + else { + $nav[] = $nav_item; + } + } + } + return $nav; +} + + +// Mount Routes + +$app->mount('/schema', new PatternKit\SchemaControllerProvider()); +$app->mount('/api', new PatternKit\ApiControllerProvider()); +$app->mount('/tests', new PatternKit\TestsControllerProvider()); +$app->mount('/sg', new PatternKit\StyleGuideControllerProvider()); + + +$app->get('/', function () use ($app) { + $data = array(); + $data['nav'] = getNav('/'); + return $app['twig']->render("display-schema.twig", $data); +}); + + +// $app->get('/{category}', function ($category) use ($app) { + +// if (in_array($category, $app['config']['categories']) ) { +// foreach ($app['config']['paths']['schemas'] as $path) { +// $files = scandir("./" . $path); +// foreach ($files as $file) { +// $contents = json_decode(file_get_contents('./' . $path . "/" . $file), true); + +// } +// } +// } + +// return $app['twig']->render("display-schema.twig", $data); +// } + + + + + +// $app->error(function (\Exception $e, $code) use ($app) { +// $app['monolog']->addError($e->getMessage()); +// $app['monolog']->addError($e->getTraceAsString()); +// return new JsonResponse(array("statusCode" => $code, "message" => $e->getMessage(), "stacktrace" => $e->getTraceAsString())); +// }); + +return $app; + + + + +?> diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/web/css/json-editor.css b/sandbox/grav/pattern-kit-core/pattern-kit/web/css/json-editor.css new file mode 100644 index 0000000000..19047d9253 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/web/css/json-editor.css @@ -0,0 +1,430 @@ +/* Define Overpass font */ +@font-face { + font-family: Overpass; + src: url("/resources/fixtures/fonts/overpass_regular-web.eot"); + src: url("/resources/fixtures/fonts/overpass_regular-web.eot?#iefix") format("eot"), + url("/resources/fixtures/fonts/overpass_regular-web.woff") format("woff"), + url("/resources/fixtures/fonts/overpass_regular-web.ttf") format("truetype"), + url("/resources/fixtures/fonts/overpass_regular-web.svg#webfontLTZe4IYH") format("svg"); + font-weight: 600; + font-style: normal; +} + +@font-face { + font-family: Overpass; + src: url("/resources/fixtures/fonts/overpass_bold-web.eot"); + src: url("/resources/fixtures/fonts/overpass_bold-web.eot?#iefix") format("eot"), + url("/resources/fixtures/fonts/overpass_bold-web.woff") format("woff"), + url("/resources/fixtures/fonts/overpass_bold-web.ttf") format("truetype"), + url("/resources/fixtures/fonts/overpass_bold-web.svg#webfontzAU82Ltw") format("svg"); + font-weight: 800; + font-style: normal; +} + +@font-face { + font-family: Overpass; + src: url("/resources/fixtures/fonts/overpass_light-webfont.eot"); + src: url("/resources/fixtures/fonts/overpass_light-webfont.eot?#iefix") format("eot"), + url("/resources/fixtures/fonts/overpass_light-webfont.woff") format("woff"), + url("/resources/fixtures/fonts/overpass_light-webfont.ttf") format("truetype"), + url("/resources/fixtures/fonts/overpass_light-webfont.svg#webfontzAU82Ltw") format("svg"); + font-weight: 400; + font-style: normal; +} +body { + font-size: 16px; + font-family: "Overpass", Overpass, Helvetica, helvetica, arial, sans-serif; + font-weight: 100; +} + +h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { + font-family: "Overpass", Overpass, Helvetica, helvetica, arial, sans-serif; + color: #017EA3; + font-weight: 400; +} +h1, .h1 { + color: #02546d; +} +h1, .h1, h2, .h2 { + font-weight: 600; +} +a { + color: #017EA3; +} +a:hover, a:focus { + color: #0eb3e4; +} + + +.resize-overlay { + pointer-events: none; + position: relative; +} +/* Styles for the navbar */ +.navbar-inverse .navbar-nav > li > a { + color: #fff; + font-weight: 400; + font-size: 16px; +} +.navbar-inverse .navbar-header .navbar-brand:hover { + cursor: default; + color: #999; +} + +/* Styles for the pattern preview tool */ +#schema_holder .page-header { + padding-left: 60px; +} +#schema_holder + .alert { + margin: 20px 60px; +} +#display_holder { + width: 100%; + border: 0; +} +.sg-snippet-preview { + position: relative; + margin: 20px auto; +} + +.sg-snippet-resize-handle { + height: 100px; + width: 30px; + margin-top: -50px; + position: absolute; + top: 50%; + -webkit-transition: all .4s; + -moz-transition: all .4s; + transition: all .4s; +} + +.sg-snippet-resize-handle:before { + background: url('../img/icon-drag.svg') no-repeat; + position: absolute; + top: 50%; + content: ''; + height: 17px; + margin-top: -8px; + width: 11px +} + +.sg-snippet-resize-handle.pos-right { + border-right: 1px solid #ccc; + left: -40px; + cursor: e-resize +} + +.sg-snippet-resize-handle.pos-right:before { + left: 10px +} + +.sg-snippet-resize-handle.pos-left { + border-left: 1px solid #ccc; + cursor: w-resize; + right: -40px +} + +.sg-snippet-resize-handle.pos-left:before { + right: 10px +} + +.sg-snippet:hover .sg-snippet-resize-handle { + opacity: 1; + visibility: visible +} + + +@-webkit-keyframes androidBugfix { from { padding: 0; } to { padding: 0; } } +body { -webkit-animation: androidBugfix infinite 1s; } +/* Layout styles for extra large browsers, side-by-side preview + data */ +@media (min-width:1800px) { + body, html { + height: 100%; + } + .left { + width: 50%; + float: left; + height: 100%; + overflow: scroll; + } + .right { + padding: 0 1%; + height: 100%; + left: 50%; + overflow: scroll; + } +} + +/* Styles specific to the schema content */ +.right { + background: white; + padding-top: 10px; +} +.right > * { + margin-left: 10px; + margin-right: 10px; + margin-bottom: 10px; +} +.right .nav-tabs { + margin-bottom: 30px; + padding: 0 80px; +} +@media (min-width: 1800px) { + .right > .container-fluid { + margin-top: 50px; + } + .right .nav-tabs { + padding-left: 10px; + } +} + +/* Styles specific to the interactive schema preview */ +code { + color: #000000; + background-color: #f3e3e8; +} +#editor_holder { + font-size: 14px; +} + +#editor_holder .btn { + font-size: 12px; + padding: 4px 8px; +} + +#editor_holder .well > .form-control { + background-color: #fff !important; + font-size: 1.2em; + color: #017EA3; /* aqua */ + font-weight: bold !important; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + + +#editor_holder .list-group-item { + padding: 6px 6px; +} + +#editor_holder .panel-title { + float: left; +} + +.page-header { + padding-bottom: 9px; + margin: 40px 10px 0px; + border-bottom: 1px solid #eee; +} + +#editor_holder h1 { + font-size: 24px; + margin: 5px 0; +} + +#editor_holder h2 { + font-size: 20px; + margin: 8px 0; +} + +#editor_holder h3 { + font-size: 15px; + margin: 5px 0; +} + +#editor_holder p { + margin-bottom: 5px; +} +.help-block { + color: #3e3e3e; +} + +.json-editor-btn-collapse { + background: transparent; + border: 0; + padding: 1px 3px; + line-height: 0; +} +.json-editor-btn-collapse:hover { + background: transparent; +} +#editor_holder [data-schematype="array"]:hover > h3 .btn-group .json-editor-btn-collapse i , +#editor_holder [data-schematype="object"]:hover > h3 .btn-group .json-editor-btn-collapse i , +.json-editor-btn-collapse:hover i:before { + color: #017EA3; /* #276edc;*/ +} +.json-editor-btn-collapse i { + font-size: 1.7em; +} + + +#editor_holder .well { + margin-bottom: 10px; +} +#editor_holder [data-schematype="object"] > .well, +#editor_holder [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well, +#editor_holder [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well, +#editor_holder [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well { + background-color: #e6ecf0; /* soft sky */ +} +#editor_holder [data-schematype="object"] > .well [data-schematype="object"] > .well, +#editor_holder [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well, +#editor_holder [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well .well, +#editor_holder [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well [data-schematype="object"] > .well { + background-color: #fff; +} +#editor_holder [data-schematype="array"] .well { + background: transparent;; +} + +#editor_holder [data-schematype="array"] > .well { + padding: 0; + border: 0; +} +#editor_holder [data-schematype="array"] > .well > div > .well { + margin: 0 0 15px; + padding-bottom: 10px !important; + border: 3px solid #FFF; + border-radius: 0; + background-color: rgba(205, 233, 219, 0.3); /* sage */ +} +#editor_holder [data-schematype="array"] > .well > div > .well:last-child { + padding-bottom: 0 !important; +} +#editor_holder [data-schematype="array"] > .well > div > .well label { + font-size: 14px; +} +#editor_holder [data-schematype="array"] > .well > div > .well > .btn-group { + margin: 10px 10px 10px 0; +} +#editor_holder [data-schematype="array"] > .well > .btn-group { + margin: 10px; +} +#editor_holder [data-schematype="object"] > h3 span { + font-weight: 800; + color: #212121; +} +#editor_holder [data-schematype="object"]:hover > h3 > span, +#editor_holder [data-schematype="object"] .well .well .well:hover h3 span { + color: #017EA3; /* aqua */ +} +#editor_holder [data-schematype="array"] > h3 span { + font-weight: 800; + color: #3E4551; +}, +#editor_holder [data-schematype="array"] label { + font-weight: 600; +} +#editor_holder [data-schematype="array"] > .well > div > [data-schematype="object"] { + border-bottom: 2px solid #fff; +} +#editor_holder [data-schematype="array"] > .well > div > [data-schematype="object"] > h3 { + font-size: 14px; + padding-left: 10px; + padding-top: 5px; +} +#editor_holder [data-schematype="array"] > .well > div > [data-schematype="object"] > h3 span { + display: inline-block !important; +} +#editor_holder [data-schematype="array"] > .well > div > [data-schematype="object"] > .well { + margin-bottom: 0; +} + +#json_holder { + display: block; + max-width: 1400px; + margin: 0 auto; + +} +#json_holder pre { + font-size: 14px; + font-family: monospace; +} + +#json_holder .valid { + font-size: 14px; + margin-bottom: 5px; +} +#json_holder .valid.alert-danger > br:first-child { + display: none; +} +.ace_editor { + height: 300px !important; +} +.sceditor-container *{ + box-sizing: content-box; + -moz-box-sizing: border-box; +} +.background { + display: none; + position: fixed; + top: 0; + width: 100%; + height: 100%; + z-index: 10; + background: rgba(255,255,255,.5); +} +.well .table-bordered { + border: 0px; +} +.well .table-bordered>thead>tr>th, +.well .table-bordered>tbody>tr>td { + border: 0px; +} + +.well .table-bordered>thead>tr>th , +.well .table-bordered>tbody>tr~tr { + border-top: 1px #dedede solid; +} +.navbar { + margin-bottom: 0; +} +.navbar + .container-fluid { + margin-right: 15px; + margin-left: 15px; + margin-top: 15px; +} + +/* Styles specific to styleguide / documentation */ +.docs-wrapper { + font-size: 1.1em; + max-width: 800px; +} +.right, .left { + overflow: auto; +} +.docs-wrapper a { + text-decoration: underline; +} +.docs-wrapper li { + margin-top: 3px; + margin-bottom: 8px; +} +.docs-wrapper li li { + margin-top: 3px; + margin-bottom: 1px; +} +.secondary-nav { + max-width: 300px; +} +.secondary-nav + .left > *:first-child { + margin-top: 0; +} +.secondary-nav .nav>li { + margin: 0; +} +.secondary-nav .nav>li>a { + border-bottom: 1px solid #ddd; + padding: 7px 3px; +} +code { + white-space: normal; +} +a[href="#top"] { + margin-top: -50px; + float: right; +} +hr { + border-top: 1px solid #bbb; + margin-top: 30px; + margin-bottom: 30px; +} \ No newline at end of file diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/web/img/icon-drag.svg b/sandbox/grav/pattern-kit-core/pattern-kit/web/img/icon-drag.svg new file mode 100644 index 0000000000..12df3c1ade --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/web/img/icon-drag.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/web/js/json-editor.js b/sandbox/grav/pattern-kit-core/pattern-kit/web/js/json-editor.js new file mode 100644 index 0000000000..b82b7e9ccb --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/web/js/json-editor.js @@ -0,0 +1,7774 @@ +/*! JSON Editor v0.7.23 - JSON Schema -> HTML Editor + * By Jeremy Dorn - https://github.com/jdorn/json-editor/ + * Released under the MIT license + * + * Date: 2015-09-27 + */ + +/** + * See README.md for requirements and usage info + */ + +(function() { + +/*jshint loopfunc: true */ +/* Simple JavaScript Inheritance + * By John Resig http://ejohn.org/ + * MIT Licensed. + */ +// Inspired by base2 and Prototype +var Class; +(function(){ + var initializing = false, fnTest = /xyz/.test(function(){window.postMessage("xyz");}) ? /\b_super\b/ : /.*/; + + // The base Class implementation (does nothing) + Class = function(){}; + + // Create a new Class that inherits from this class + Class.extend = function(prop) { + var _super = this.prototype; + + // Instantiate a base class (but only create the instance, + // don't run the init constructor) + initializing = true; + var prototype = new this(); + initializing = false; + + // Copy the properties over onto the new prototype + for (var name in prop) { + // Check if we're overwriting an existing function + prototype[name] = typeof prop[name] == "function" && + typeof _super[name] == "function" && fnTest.test(prop[name]) ? + (function(name, fn){ + return function() { + var tmp = this._super; + + // Add a new ._super() method that is the same method + // but on the super-class + this._super = _super[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]) : + prop[name]; + } + + // The dummy class constructor + function Class() { + // All construction is actually done in the init method + if ( !initializing && this.init ) + this.init.apply(this, arguments); + } + + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.prototype.constructor = Class; + + // And make this class extendable + Class.extend = arguments.callee; + + return Class; + }; + + return Class; +})(); + +// CustomEvent constructor polyfill +// From MDN +(function () { + function CustomEvent ( event, params ) { + params = params || { bubbles: false, cancelable: false, detail: undefined }; + var evt = document.createEvent( 'CustomEvent' ); + evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail ); + return evt; + } + + CustomEvent.prototype = window.Event.prototype; + + window.CustomEvent = CustomEvent; +})(); + +// requestAnimationFrame polyfill by Erik Möller. fixes from Paul Irish and Tino Zijdel +// MIT license +(function() { + var lastTime = 0; + var vendors = ['ms', 'moz', 'webkit', 'o']; + for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { + window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; + window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] || + window[vendors[x]+'CancelRequestAnimationFrame']; + } + + if (!window.requestAnimationFrame) + window.requestAnimationFrame = function(callback, element) { + var currTime = new Date().getTime(); + var timeToCall = Math.max(0, 16 - (currTime - lastTime)); + var id = window.setTimeout(function() { callback(currTime + timeToCall); }, + timeToCall); + lastTime = currTime + timeToCall; + return id; + }; + + if (!window.cancelAnimationFrame) + window.cancelAnimationFrame = function(id) { + clearTimeout(id); + }; +}()); + +// Array.isArray polyfill +// From MDN +(function() { + if(!Array.isArray) { + Array.isArray = function(arg) { + return Object.prototype.toString.call(arg) === '[object Array]'; + }; + } +}()); +/** + * Taken from jQuery 2.1.3 + * + * @param obj + * @returns {boolean} + */ +var $isplainobject = function( obj ) { + // Not plain objects: + // - Any object or value whose internal [[Class]] property is not "[object Object]" + // - DOM nodes + // - window + if (typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) { + return false; + } + + if (obj.constructor && !Object.prototype.hasOwnProperty.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; +}; + +var $extend = function(destination) { + var source, i,property; + for(i=1; i 0 && (obj.length - 1) in obj)) { + for(i=0; i= waiting && !callback_fired) { + callback_fired = true; + callback(); + } + }); + } + // Request failed + else { + window.console.log(r); + throw "Failed to fetch ref via ajax- "+url; + } + }; + r.send(); + }); + + if(!waiting) { + callback(); + } + }, + expandRefs: function(schema) { + schema = $extend({},schema); + + while (schema.$ref) { + var ref = schema.$ref; + delete schema.$ref; + + if(!this.refs[ref]) ref = decodeURIComponent(ref); + + schema = this.extendSchemas(schema,this.refs[ref]); + } + return schema; + }, + expandSchema: function(schema) { + var self = this; + var extended = $extend({},schema); + var i; + + // Version 3 `type` + if(typeof schema.type === 'object') { + // Array of types + if(Array.isArray(schema.type)) { + $each(schema.type, function(key,value) { + // Schema + if(typeof value === 'object') { + schema.type[key] = self.expandSchema(value); + } + }); + } + // Schema + else { + schema.type = self.expandSchema(schema.type); + } + } + // Version 3 `disallow` + if(typeof schema.disallow === 'object') { + // Array of types + if(Array.isArray(schema.disallow)) { + $each(schema.disallow, function(key,value) { + // Schema + if(typeof value === 'object') { + schema.disallow[key] = self.expandSchema(value); + } + }); + } + // Schema + else { + schema.disallow = self.expandSchema(schema.disallow); + } + } + // Version 4 `anyOf` + if(schema.anyOf) { + $each(schema.anyOf, function(key,value) { + schema.anyOf[key] = self.expandSchema(value); + }); + } + // Version 4 `dependencies` (schema dependencies) + if(schema.dependencies) { + $each(schema.dependencies,function(key,value) { + if(typeof value === "object" && !(Array.isArray(value))) { + schema.dependencies[key] = self.expandSchema(value); + } + }); + } + // Version 4 `not` + if(schema.not) { + schema.not = this.expandSchema(schema.not); + } + + // allOf schemas should be merged into the parent + if(schema.allOf) { + for(i=0; i= schema.maximum) { + errors.push({ + path: path, + property: 'maximum', + message: this.translate('error_maximum_excl', [schema.maximum]) + }); + } + else if(!schema.exclusiveMaximum && value > schema.maximum) { + errors.push({ + path: path, + property: 'maximum', + message: this.translate('error_maximum_incl', [schema.maximum]) + }); + } + } + + // `minimum` + if(schema.hasOwnProperty('minimum')) { + if(schema.exclusiveMinimum && value <= schema.minimum) { + errors.push({ + path: path, + property: 'minimum', + message: this.translate('error_minimum_excl', [schema.minimum]) + }); + } + else if(!schema.exclusiveMinimum && value < schema.minimum) { + errors.push({ + path: path, + property: 'minimum', + message: this.translate('error_minimum_incl', [schema.minimum]) + }); + } + } + } + // String specific validation + else if(typeof value === "string") { + // `maxLength` + if(schema.maxLength) { + if((value+"").length > schema.maxLength) { + errors.push({ + path: path, + property: 'maxLength', + message: this.translate('error_maxLength', [schema.maxLength]) + }); + } + } + + // `minLength` + if(schema.minLength) { + if((value+"").length < schema.minLength) { + errors.push({ + path: path, + property: 'minLength', + message: this.translate((schema.minLength===1?'error_notempty':'error_minLength'), [schema.minLength]) + }); + } + } + + // `pattern` + if(schema.pattern) { + if(!(new RegExp(schema.pattern)).test(value)) { + errors.push({ + path: path, + property: 'pattern', + message: this.translate('error_pattern') + }); + } + } + } + // Array specific validation + else if(typeof value === "object" && value !== null && Array.isArray(value)) { + // `items` and `additionalItems` + if(schema.items) { + // `items` is an array + if(Array.isArray(schema.items)) { + for(i=0; i schema.maxItems) { + errors.push({ + path: path, + property: 'maxItems', + message: this.translate('error_maxItems', [schema.maxItems]) + }); + } + } + + // `minItems` + if(schema.minItems) { + if(value.length < schema.minItems) { + errors.push({ + path: path, + property: 'minItems', + message: this.translate('error_minItems', [schema.minItems]) + }); + } + } + + // `uniqueItems` + if(schema.uniqueItems) { + var seen = {}; + for(i=0; i schema.maxProperties) { + errors.push({ + path: path, + property: 'maxProperties', + message: this.translate('error_maxProperties', [schema.maxProperties]) + }); + } + } + + // `minProperties` + if(schema.minProperties) { + valid = 0; + for(i in value) { + if(!value.hasOwnProperty(i)) continue; + valid++; + } + if(valid < schema.minProperties) { + errors.push({ + path: path, + property: 'minProperties', + message: this.translate('error_minProperties', [schema.minProperties]) + }); + } + } + + // Version 4 `required` + if(schema.required && Array.isArray(schema.required)) { + for(i=0; i=0) { + holder = this.theme.getBlockLinkHolder(); + + link = this.theme.getBlockLink(); + link.setAttribute('target','_blank'); + + var media = document.createElement(type); + media.setAttribute('controls','controls'); + + this.theme.createMediaLink(holder,link,media); + + // When a watched field changes, update the url + this.link_watchers.push(function(vars) { + var url = href(vars); + link.setAttribute('href',url); + link.textContent = data.rel || url; + media.setAttribute('src',url); + }); + } + // Text links + else { + holder = this.theme.getBlockLink(); + holder.setAttribute('target','_blank'); + holder.textContent = data.rel; + + // When a watched field changes, update the url + this.link_watchers.push(function(vars) { + var url = href(vars); + holder.setAttribute('href',url); + holder.textContent = data.rel || url; + }); + } + + return holder; + }, + refreshWatchedFieldValues: function() { + if(!this.watched_values) return; + var watched = {}; + var changed = false; + var self = this; + + if(this.watched) { + var val,editor; + for(var name in this.watched) { + if(!this.watched.hasOwnProperty(name)) continue; + editor = self.jsoneditor.getEditor(this.watched[name]); + val = editor? editor.getValue() : null; + if(self.watched_values[name] !== val) changed = true; + watched[name] = val; + } + } + + watched.self = this.getValue(); + if(this.watched_values.self !== watched.self) changed = true; + + this.watched_values = watched; + + return changed; + }, + getWatchedFieldValues: function() { + return this.watched_values; + }, + updateHeaderText: function() { + if(this.header) { + // If the header has children, only update the text node's value + if(this.header.children.length) { + for(var i=0; i -1; + else if(this.jsoneditor.options.required_by_default) return true; + else return false; + }, + getDisplayText: function(arr) { + var disp = []; + var used = {}; + + // Determine how many times each attribute name is used. + // This helps us pick the most distinct display text for the schemas. + $each(arr,function(i,el) { + if(el.title) { + used[el.title] = used[el.title] || 0; + used[el.title]++; + } + if(el.description) { + used[el.description] = used[el.description] || 0; + used[el.description]++; + } + if(el.format) { + used[el.format] = used[el.format] || 0; + used[el.format]++; + } + if(el.type) { + used[el.type] = used[el.type] || 0; + used[el.type]++; + } + }); + + // Determine display text for each element of the array + $each(arr,function(i,el) { + var name; + + // If it's a simple string + if(typeof el === "string") name = el; + // Object + else if(el.title && used[el.title]<=1) name = el.title; + else if(el.format && used[el.format]<=1) name = el.format; + else if(el.type && used[el.type]<=1) name = el.type; + else if(el.description && used[el.description]<=1) name = el.descripton; + else if(el.title) name = el.title; + else if(el.format) name = el.format; + else if(el.type) name = el.type; + else if(el.description) name = el.description; + else if(JSON.stringify(el).length < 50) name = JSON.stringify(el); + else name = "type"; + + disp.push(name); + }); + + // Replace identical display text with "text 1", "text 2", etc. + var inc = {}; + $each(disp,function(i,name) { + inc[name] = inc[name] || 0; + inc[name]++; + + if(used[name] > 1) disp[i] = name + " " + inc[name]; + }); + + return disp; + }, + getOption: function(key) { + try { + throw "getOption is deprecated"; + } + catch(e) { + window.console.error(e); + } + + return this.options[key]; + }, + showValidationErrors: function(errors) { + + } +}); + +JSONEditor.defaults.editors["null"] = JSONEditor.AbstractEditor.extend({ + getValue: function() { + return null; + }, + setValue: function() { + this.onChange(); + }, + getNumColumns: function() { + return 2; + } +}); + +JSONEditor.defaults.editors.string = JSONEditor.AbstractEditor.extend({ + register: function() { + this._super(); + if(!this.input) return; + this.input.setAttribute('name',this.formname); + }, + unregister: function() { + this._super(); + if(!this.input) return; + this.input.removeAttribute('name'); + }, + setValue: function(value,initial,from_template) { + var self = this; + + if(this.template && !from_template) { + return; + } + + if(value === null || typeof value === 'undefined') value = ""; + else if(typeof value === "object") value = JSON.stringify(value); + else if(typeof value !== "string") value = ""+value; + + if(value === this.serialized) return; + + // Sanitize value before setting it + var sanitized = this.sanitize(value); + + if(this.input.value === sanitized) { + return; + } + + this.input.value = sanitized; + + // If using SCEditor, update the WYSIWYG + if(this.sceditor_instance) { + this.sceditor_instance.val(sanitized); + } + else if(this.epiceditor) { + this.epiceditor.importFile(null,sanitized); + } + else if(this.ace_editor) { + this.ace_editor.setValue(sanitized); + } + + var changed = from_template || this.getValue() !== value; + + this.refreshValue(); + + if(initial) this.is_dirty = false; + else if(this.jsoneditor.options.show_errors === "change") this.is_dirty = true; + + if(this.adjust_height) this.adjust_height(this.input); + + // Bubble this setValue to parents if the value changed + this.onChange(changed); + }, + getNumColumns: function() { + var min = Math.ceil(Math.max(this.getTitle().length,this.schema.maxLength||0,this.schema.minLength||0)/5); + var num; + + if(this.input_type === 'textarea') num = 6; + else if(['text','email'].indexOf(this.input_type) >= 0) num = 4; + else num = 2; + + return Math.min(12,Math.max(min,num)); + }, + build: function() { + var self = this, i; + if(!this.options.compact) this.header = this.label = this.theme.getFormInputLabel(this.getTitle()); + if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description); + + this.format = this.schema.format; + if(!this.format && this.schema.media && this.schema.media.type) { + this.format = this.schema.media.type.replace(/(^(application|text)\/(x-)?(script\.)?)|(-source$)/g,''); + } + if(!this.format && this.options.default_format) { + this.format = this.options.default_format; + } + if(this.options.format) { + this.format = this.options.format; + } + + // Specific format + if(this.format) { + // Text Area + if(this.format === 'textarea') { + this.input_type = 'textarea'; + this.input = this.theme.getTextareaInput(); + } + // Range Input + else if(this.format === 'range') { + this.input_type = 'range'; + var min = this.schema.minimum || 0; + var max = this.schema.maximum || Math.max(100,min+1); + var step = 1; + if(this.schema.multipleOf) { + if(min%this.schema.multipleOf) min = Math.ceil(min/this.schema.multipleOf)*this.schema.multipleOf; + if(max%this.schema.multipleOf) max = Math.floor(max/this.schema.multipleOf)*this.schema.multipleOf; + step = this.schema.multipleOf; + } + + this.input = this.theme.getRangeInput(min,max,step); + } + // Source Code + else if([ + 'actionscript', + 'batchfile', + 'bbcode', + 'c', + 'c++', + 'cpp', + 'coffee', + 'csharp', + 'css', + 'dart', + 'django', + 'ejs', + 'erlang', + 'golang', + 'handlebars', + 'haskell', + 'haxe', + 'html', + 'ini', + 'jade', + 'java', + 'javascript', + 'json', + 'less', + 'lisp', + 'lua', + 'makefile', + 'markdown', + 'matlab', + 'mysql', + 'objectivec', + 'pascal', + 'perl', + 'pgsql', + 'php', + 'python', + 'r', + 'ruby', + 'sass', + 'scala', + 'scss', + 'smarty', + 'sql', + 'stylus', + 'svg', + 'twig', + 'vbscript', + 'xml', + 'yaml' + ].indexOf(this.format) >= 0 + ) { + this.input_type = this.format; + this.source_code = true; + + this.input = this.theme.getTextareaInput(); + } + // HTML5 Input type + else { + this.input_type = this.format; + this.input = this.theme.getFormInputField(this.input_type); + } + } + // Normal text input + else { + this.input_type = 'text'; + this.input = this.theme.getFormInputField(this.input_type); + } + + // minLength, maxLength, and pattern + if(typeof this.schema.maxLength !== "undefined") this.input.setAttribute('maxlength',this.schema.maxLength); + if(typeof this.schema.pattern !== "undefined") this.input.setAttribute('pattern',this.schema.pattern); + else if(typeof this.schema.minLength !== "undefined") this.input.setAttribute('pattern','.{'+this.schema.minLength+',}'); + + if(this.options.compact) { + this.container.className += ' compact'; + } + else { + if(this.options.input_width) this.input.style.width = this.options.input_width; + } + + if(this.schema.readOnly || this.schema.readonly || this.schema.template) { + this.always_disabled = true; + this.input.disabled = true; + } + + this.input + .addEventListener('change',function(e) { + e.preventDefault(); + e.stopPropagation(); + + // Don't allow changing if this field is a template + if(self.schema.template) { + this.value = self.value; + return; + } + + var val = this.value; + + // sanitize value + var sanitized = self.sanitize(val); + if(val !== sanitized) { + this.value = sanitized; + } + + self.is_dirty = true; + + self.refreshValue(); + self.onChange(true); + }); + + if(this.options.input_height) this.input.style.height = this.options.input_height; + if(this.options.expand_height) { + this.adjust_height = function(el) { + if(!el) return; + var i, ch=el.offsetHeight; + // Input too short + if(el.offsetHeight < el.scrollHeight) { + i=0; + while(el.offsetHeight < el.scrollHeight+3) { + if(i>100) break; + i++; + ch++; + el.style.height = ch+'px'; + } + } + else { + i=0; + while(el.offsetHeight >= el.scrollHeight+3) { + if(i>100) break; + i++; + ch--; + el.style.height = ch+'px'; + } + el.style.height = (ch+1)+'px'; + } + }; + + this.input.addEventListener('keyup',function(e) { + self.adjust_height(this); + }); + this.input.addEventListener('change',function(e) { + self.adjust_height(this); + }); + this.adjust_height(); + } + + if(this.format) this.input.setAttribute('data-schemaformat',this.format); + + this.control = this.theme.getFormControl(this.label, this.input, this.description); + this.container.appendChild(this.control); + + // Any special formatting that needs to happen after the input is added to the dom + window.requestAnimationFrame(function() { + // Skip in case the input is only a temporary editor, + // otherwise, in the case of an ace_editor creation, + // it will generate an error trying to append it to the missing parentNode + if(self.input.parentNode) self.afterInputReady(); + if(self.adjust_height) self.adjust_height(self.input); + }); + + // Compile and store the template + if(this.schema.template) { + this.template = this.jsoneditor.compileTemplate(this.schema.template, this.template_engine); + this.refreshValue(); + } + else { + this.refreshValue(); + } + }, + enable: function() { + if(!this.always_disabled) { + this.input.disabled = false; + // TODO: WYSIWYG and Markdown editors + } + this._super(); + }, + disable: function() { + this.input.disabled = true; + // TODO: WYSIWYG and Markdown editors + this._super(); + }, + afterInputReady: function() { + var self = this, options; + + // Code editor + if(this.source_code) { + // WYSIWYG html and bbcode editor + if(this.options.wysiwyg && + ['html','bbcode'].indexOf(this.input_type) >= 0 && + window.jQuery && window.jQuery.fn && window.jQuery.fn.sceditor + ) { + options = $extend({},{ + plugins: self.input_type==='html'? 'xhtml' : 'bbcode', + emoticonsEnabled: false, + width: '100%', + height: 300 + },JSONEditor.plugins.sceditor,self.options.sceditor_options||{}); + + window.jQuery(self.input).sceditor(options); + + self.sceditor_instance = window.jQuery(self.input).sceditor('instance'); + + self.sceditor_instance.blur(function() { + // Get editor's value + var val = window.jQuery("
"+self.sceditor_instance.val()+"
"); + // Remove sceditor spans/divs + window.jQuery('#sceditor-start-marker,#sceditor-end-marker,.sceditor-nlf',val).remove(); + // Set the value and update + self.input.value = val.html(); + self.value = self.input.value; + self.is_dirty = true; + self.onChange(true); + }); + } + // EpicEditor for markdown (if it's loaded) + else if (this.input_type === 'markdown' && window.EpicEditor) { + this.epiceditor_container = document.createElement('div'); + this.input.parentNode.insertBefore(this.epiceditor_container,this.input); + this.input.style.display = 'none'; + + options = $extend({},JSONEditor.plugins.epiceditor,{ + container: this.epiceditor_container, + clientSideStorage: false + }); + + this.epiceditor = new window.EpicEditor(options).load(); + + this.epiceditor.importFile(null,this.getValue()); + + this.epiceditor.on('update',function() { + var val = self.epiceditor.exportFile(); + self.input.value = val; + self.value = val; + self.is_dirty = true; + self.onChange(true); + }); + } + // ACE editor for everything else + else if(window.ace) { + var mode = this.input_type; + // aliases for c/cpp + if(mode === 'cpp' || mode === 'c++' || mode === 'c') { + mode = 'c_cpp'; + } + + this.ace_container = document.createElement('div'); + this.ace_container.style.width = '100%'; + this.ace_container.style.position = 'relative'; + this.ace_container.style.height = '400px'; + this.input.parentNode.insertBefore(this.ace_container,this.input); + this.input.style.display = 'none'; + this.ace_editor = window.ace.edit(this.ace_container); + + this.ace_editor.setValue(this.getValue()); + + // The theme + if(JSONEditor.plugins.ace.theme) this.ace_editor.setTheme('ace/theme/'+JSONEditor.plugins.ace.theme); + // The mode + mode = window.ace.require("ace/mode/"+mode); + if(mode) this.ace_editor.getSession().setMode(new mode.Mode()); + + // Listen for changes + this.ace_editor.on('change',function() { + var val = self.ace_editor.getValue(); + self.input.value = val; + self.refreshValue(); + self.is_dirty = true; + self.onChange(true); + }); + } + } + + self.theme.afterInputReady(self.input); + }, + refreshValue: function() { + this.value = this.input.value; + if(typeof this.value !== "string") this.value = ''; + this.serialized = this.value; + }, + destroy: function() { + // If using SCEditor, destroy the editor instance + if(this.sceditor_instance) { + this.sceditor_instance.destroy(); + } + else if(this.epiceditor) { + this.epiceditor.unload(); + } + else if(this.ace_editor) { + this.ace_editor.destroy(); + } + + + this.template = null; + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label); + if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); + + this._super(); + }, + /** + * This is overridden in derivative editors + */ + sanitize: function(value) { + return value; + }, + /** + * Re-calculates the value if needed + */ + onWatchedFieldChange: function() { + var self = this, vars, j; + + // If this editor needs to be rendered by a macro template + if(this.template) { + vars = this.getWatchedFieldValues(); + this.setValue(this.template(vars),false,true); + } + + this._super(); + }, + showValidationErrors: function(errors) { + var self = this; + + if(this.jsoneditor.options.show_errors === "always") {} + else if(!this.is_dirty && this.previous_error_setting===this.jsoneditor.options.show_errors) return; + + this.previous_error_setting = this.jsoneditor.options.show_errors; + + var messages = []; + $each(errors,function(i,error) { + if(error.path === self.path) { + messages.push(error.message); + } + }); + + if(messages.length) { + this.theme.addInputError(this.input, messages.join('. ')+'.'); + } + else { + this.theme.removeInputError(this.input); + } + } +}); + +JSONEditor.defaults.editors.number = JSONEditor.defaults.editors.string.extend({ + sanitize: function(value) { + return (value+"").replace(/[^0-9\.\-eE]/g,''); + }, + getNumColumns: function() { + return 2; + }, + getValue: function() { + return this.value*1; + } +}); + +JSONEditor.defaults.editors.integer = JSONEditor.defaults.editors.number.extend({ + sanitize: function(value) { + value = value + ""; + return value.replace(/[^0-9\-]/g,''); + }, + getNumColumns: function() { + return 2; + } +}); + +JSONEditor.defaults.editors.object = JSONEditor.AbstractEditor.extend({ + getDefault: function() { + return $extend({},this.schema["default"] || {}); + }, + getChildEditors: function() { + return this.editors; + }, + register: function() { + this._super(); + if(this.editors) { + for(var i in this.editors) { + if(!this.editors.hasOwnProperty(i)) continue; + this.editors[i].register(); + } + } + }, + unregister: function() { + this._super(); + if(this.editors) { + for(var i in this.editors) { + if(!this.editors.hasOwnProperty(i)) continue; + this.editors[i].unregister(); + } + } + }, + getNumColumns: function() { + return Math.max(Math.min(12,this.maxwidth),3); + }, + enable: function() { + if(this.editjson_button) this.editjson_button.disabled = false; + if(this.addproperty_button) this.addproperty_button.disabled = false; + + this._super(); + if(this.editors) { + for(var i in this.editors) { + if(!this.editors.hasOwnProperty(i)) continue; + this.editors[i].enable(); + } + } + }, + disable: function() { + if(this.editjson_button) this.editjson_button.disabled = true; + if(this.addproperty_button) this.addproperty_button.disabled = true; + this.hideEditJSON(); + + this._super(); + if(this.editors) { + for(var i in this.editors) { + if(!this.editors.hasOwnProperty(i)) continue; + this.editors[i].disable(); + } + } + }, + layoutEditors: function() { + var self = this, i, j; + + if(!this.row_container) return; + + // Sort editors by propertyOrder + this.property_order = Object.keys(this.editors); + this.property_order = this.property_order.sort(function(a,b) { + var ordera = self.editors[a].schema.propertyOrder; + var orderb = self.editors[b].schema.propertyOrder; + if(typeof ordera !== "number") ordera = 1000; + if(typeof orderb !== "number") orderb = 1000; + + return ordera - orderb; + }); + + var container; + + if(this.format === 'grid') { + var rows = []; + $each(this.property_order, function(j,key) { + var editor = self.editors[key]; + if(editor.property_removed) return; + var found = false; + var width = editor.options.hidden? 0 : (editor.options.grid_columns || editor.getNumColumns()); + var height = editor.options.hidden? 0 : editor.container.offsetHeight; + // See if the editor will fit in any of the existing rows first + for(var i=0; i height)) { + found = i; + } + } + } + + // If there isn't a spot in any of the existing rows, start a new row + if(found === false) { + rows.push({ + width: 0, + minh: 999999, + maxh: 0, + editors: [] + }); + found = rows.length-1; + } + + rows[found].editors.push({ + key: key, + //editor: editor, + width: width, + height: height + }); + rows[found].width += width; + rows[found].minh = Math.min(rows[found].minh,height); + rows[found].maxh = Math.max(rows[found].maxh,height); + }); + + // Make almost full rows width 12 + // Do this by increasing all editors' sizes proprotionately + // Any left over space goes to the biggest editor + // Don't touch rows with a width of 6 or less + for(i=0; i rows[i].editors[biggest].width) biggest = j; + rows[i].editors[j].width *= 12/rows[i].width; + rows[i].editors[j].width = Math.floor(rows[i].editors[j].width); + new_width += rows[i].editors[j].width; + } + if(new_width < 12) rows[i].editors[biggest].width += 12-new_width; + rows[i].width = 12; + } + } + + // layout hasn't changed + if(this.layout === JSON.stringify(rows)) return false; + this.layout = JSON.stringify(rows); + + // Layout the form + container = document.createElement('div'); + for(i=0; i= this.schema.maxProperties); + + if(this.addproperty_checkboxes) { + this.addproperty_list.innerHTML = ''; + } + this.addproperty_checkboxes = {}; + + // Check for which editors can't be removed or added back + for(i in this.cached_editors) { + if(!this.cached_editors.hasOwnProperty(i)) continue; + + this.addPropertyCheckbox(i); + + if(this.isRequired(this.cached_editors[i]) && i in this.editors) { + this.addproperty_checkboxes[i].disabled = true; + } + + if(typeof this.schema.minProperties !== "undefined" && num_props <= this.schema.minProperties) { + this.addproperty_checkboxes[i].disabled = this.addproperty_checkboxes[i].checked; + if(!this.addproperty_checkboxes[i].checked) show_modal = true; + } + else if(!(i in this.editors)) { + if(!can_add && !this.schema.properties.hasOwnProperty(i)) { + this.addproperty_checkboxes[i].disabled = true; + } + else { + this.addproperty_checkboxes[i].disabled = false; + show_modal = true; + } + } + else { + show_modal = true; + can_remove = true; + } + } + + if(this.canHaveAdditionalProperties()) { + show_modal = true; + } + + // Additional addproperty checkboxes not tied to a current editor + for(i in this.schema.properties) { + if(!this.schema.properties.hasOwnProperty(i)) continue; + if(this.cached_editors[i]) continue; + show_modal = true; + this.addPropertyCheckbox(i); + } + + // If no editors can be added or removed, hide the modal button + if(!show_modal) { + this.hideAddProperty(); + this.addproperty_controls.style.display = 'none'; + } + // If additional properties are disabled + else if(!this.canHaveAdditionalProperties()) { + this.addproperty_add.style.display = 'none'; + this.addproperty_input.style.display = 'none'; + } + // If no new properties can be added + else if(!can_add) { + this.addproperty_add.disabled = true; + } + // If new properties can be added + else { + this.addproperty_add.disabled = false; + } + }, + isRequired: function(editor) { + if(typeof editor.schema.required === "boolean") return editor.schema.required; + else if(Array.isArray(this.schema.required)) return this.schema.required.indexOf(editor.key) > -1; + else if(this.jsoneditor.options.required_by_default) return true; + else return false; + }, + setValue: function(value, initial) { + var self = this; + value = value || {}; + + if(typeof value !== "object" || Array.isArray(value)) value = {}; + + // First, set the values for all of the defined properties + $each(this.cached_editors, function(i,editor) { + // Value explicitly set + if(typeof value[i] !== "undefined") { + self.addObjectProperty(i); + editor.setValue(value[i],initial); + } + // Otherwise, remove value unless this is the initial set or it's required + else if(!initial && !self.isRequired(editor)) { + self.removeObjectProperty(i); + } + // Otherwise, set the value to the default + else { + editor.setValue(editor.getDefault(),initial); + } + }); + + $each(value, function(i,val) { + if(!self.cached_editors[i]) { + self.addObjectProperty(i); + if(self.editors[i]) self.editors[i].setValue(val,initial); + } + }); + + this.refreshValue(); + this.layoutEditors(); + this.onChange(); + }, + showValidationErrors: function(errors) { + var self = this; + + // Get all the errors that pertain to this editor + var my_errors = []; + var other_errors = []; + $each(errors, function(i,error) { + if(error.path === self.path) { + my_errors.push(error); + } + else { + other_errors.push(error); + } + }); + + // Show errors for this editor + if(this.error_holder) { + if(my_errors.length) { + var message = []; + this.error_holder.innerHTML = ''; + this.error_holder.style.display = ''; + $each(my_errors, function(i,error) { + self.error_holder.appendChild(self.theme.getErrorMessage(error.message)); + }); + } + // Hide error area + else { + this.error_holder.style.display = 'none'; + } + } + + // Show error for the table row if this is inside a table + if(this.options.table_row) { + if(my_errors.length) { + this.theme.addTableRowError(this.container); + } + else { + this.theme.removeTableRowError(this.container); + } + } + + // Show errors for child editors + $each(this.editors, function(i,editor) { + editor.showValidationErrors(other_errors); + }); + } +}); + +JSONEditor.defaults.editors.array = JSONEditor.AbstractEditor.extend({ + getDefault: function() { + return this.schema["default"] || []; + }, + register: function() { + this._super(); + if(this.rows) { + for(var i=0; i= this.schema.items.length) { + if(this.schema.additionalItems===true) { + return {}; + } + else if(this.schema.additionalItems) { + return $extend({},this.schema.additionalItems); + } + } + else { + return $extend({},this.schema.items[i]); + } + } + else if(this.schema.items) { + return $extend({},this.schema.items); + } + else { + return {}; + } + }, + getItemInfo: function(i) { + var schema = this.getItemSchema(i); + + // Check if it's cached + this.item_info = this.item_info || {}; + var stringified = JSON.stringify(schema); + if(typeof this.item_info[stringified] !== "undefined") return this.item_info[stringified]; + + // Get the schema for this item + schema = this.jsoneditor.expandRefs(schema); + + this.item_info[stringified] = { + title: schema.title || "item", + 'default': schema["default"], + width: 12, + child_editors: schema.properties || schema.items + }; + + return this.item_info[stringified]; + }, + getElementEditor: function(i) { + var item_info = this.getItemInfo(i); + var schema = this.getItemSchema(i); + schema = this.jsoneditor.expandRefs(schema); + schema.title = item_info.title+' '+(i+1); + + var editor = this.jsoneditor.getEditorClass(schema); + + var holder; + if(this.tabs_holder) { + holder = this.theme.getTabContent(); + } + else if(item_info.child_editors) { + holder = this.theme.getChildEditorHolder(); + } + else { + holder = this.theme.getIndentedPanel(); + } + + this.row_holder.appendChild(holder); + + var ret = this.jsoneditor.createEditor(editor,{ + jsoneditor: this.jsoneditor, + schema: schema, + container: holder, + path: this.path+'.'+i, + parent: this, + required: true + }); + ret.preBuild(); + ret.build(); + ret.postBuild(); + + if(!ret.title_controls) { + ret.array_controls = this.theme.getButtonHolder(); + holder.appendChild(ret.array_controls); + } + + return ret; + }, + destroy: function() { + this.empty(true); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); + if(this.row_holder && this.row_holder.parentNode) this.row_holder.parentNode.removeChild(this.row_holder); + if(this.controls && this.controls.parentNode) this.controls.parentNode.removeChild(this.controls); + if(this.panel && this.panel.parentNode) this.panel.parentNode.removeChild(this.panel); + + this.rows = this.row_cache = this.title = this.description = this.row_holder = this.panel = this.controls = null; + + this._super(); + }, + empty: function(hard) { + if(!this.rows) return; + var self = this; + $each(this.rows,function(i,row) { + if(hard) { + if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab); + self.destroyRow(row,true); + self.row_cache[i] = null; + } + self.rows[i] = null; + }); + self.rows = []; + if(hard) self.row_cache = []; + }, + destroyRow: function(row,hard) { + var holder = row.container; + if(hard) { + row.destroy(); + if(holder.parentNode) holder.parentNode.removeChild(holder); + if(row.tab && row.tab.parentNode) row.tab.parentNode.removeChild(row.tab); + } + else { + if(row.tab) row.tab.style.display = 'none'; + holder.style.display = 'none'; + row.unregister(); + } + }, + getMax: function() { + if((Array.isArray(this.schema.items)) && this.schema.additionalItems === false) { + return Math.min(this.schema.items.length,this.schema.maxItems || Infinity); + } + else { + return this.schema.maxItems || Infinity; + } + }, + refreshTabs: function(refresh_headers) { + var self = this; + $each(this.rows, function(i,row) { + if(!row.tab) return; + + if(refresh_headers) { + row.tab_text.textContent = row.getHeaderText(); + } + else { + if(row.tab === self.active_tab) { + self.theme.markTabActive(row.tab); + row.container.style.display = ''; + } + else { + self.theme.markTabInactive(row.tab); + row.container.style.display = 'none'; + } + } + }); + }, + setValue: function(value, initial) { + // Update the array's value, adding/removing rows when necessary + value = value || []; + + if(!(Array.isArray(value))) value = [value]; + + var serialized = JSON.stringify(value); + if(serialized === this.serialized) return; + + // Make sure value has between minItems and maxItems items in it + if(this.schema.minItems) { + while(value.length < this.schema.minItems) { + value.push(this.getItemInfo(value.length)["default"]); + } + } + if(this.getMax() && value.length > this.getMax()) { + value = value.slice(0,this.getMax()); + } + + var self = this; + $each(value,function(i,val) { + if(self.rows[i]) { + // TODO: don't set the row's value if it hasn't changed + self.rows[i].setValue(val,initial); + } + else if(self.row_cache[i]) { + self.rows[i] = self.row_cache[i]; + self.rows[i].setValue(val,initial); + self.rows[i].container.style.display = ''; + if(self.rows[i].tab) self.rows[i].tab.style.display = ''; + self.rows[i].register(); + } + else { + self.addRow(val,initial); + } + }); + + for(var j=value.length; j= this.rows.length; + + $each(this.rows,function(i,editor) { + // Hide the move down button for the last row + if(editor.movedown_button) { + if(i === self.rows.length - 1) { + editor.movedown_button.style.display = 'none'; + } + else { + editor.movedown_button.style.display = ''; + } + } + + // Hide the delete button if we have minItems items + if(editor.delete_button) { + if(minItems) { + editor.delete_button.style.display = 'none'; + } + else { + editor.delete_button.style.display = ''; + } + } + + // Get the value for this editor + self.value[i] = editor.getValue(); + }); + + var controls_needed = false; + + if(!this.value.length) { + this.delete_last_row_button.style.display = 'none'; + this.remove_all_rows_button.style.display = 'none'; + } + else if(this.value.length === 1) { + this.remove_all_rows_button.style.display = 'none'; + + // If there are minItems items in the array, hide the delete button beneath the rows + if(minItems || this.hide_delete_buttons) { + this.delete_last_row_button.style.display = 'none'; + } + else { + this.delete_last_row_button.style.display = ''; + controls_needed = true; + } + } + else { + // If there are minItems items in the array, hide the delete button beneath the rows + if(minItems || this.hide_delete_buttons) { + this.delete_last_row_button.style.display = 'none'; + this.remove_all_rows_button.style.display = 'none'; + } + else { + this.delete_last_row_button.style.display = ''; + this.remove_all_rows_button.style.display = ''; + controls_needed = true; + } + } + + // If there are maxItems in the array, hide the add button beneath the rows + if((this.getMax() && this.getMax() <= this.rows.length) || this.hide_add_button){ + this.add_row_button.style.display = 'none'; + } + else { + this.add_row_button.style.display = ''; + controls_needed = true; + } + + if(!this.collapsed && controls_needed) { + this.controls.style.display = 'inline-block'; + } + else { + this.controls.style.display = 'none'; + } + } + }, + addRow: function(value, initial) { + var self = this; + var i = this.rows.length; + + self.rows[i] = this.getElementEditor(i); + self.row_cache[i] = self.rows[i]; + + if(self.tabs_holder) { + self.rows[i].tab_text = document.createElement('span'); + self.rows[i].tab_text.textContent = self.rows[i].getHeaderText(); + self.rows[i].tab = self.theme.getTab(self.rows[i].tab_text); + self.rows[i].tab.addEventListener('click', function(e) { + self.active_tab = self.rows[i].tab; + self.refreshTabs(); + e.preventDefault(); + e.stopPropagation(); + }); + + self.theme.addTab(self.tabs_holder, self.rows[i].tab); + } + + var controls_holder = self.rows[i].title_controls || self.rows[i].array_controls; + + // Buttons to delete row, move row up, and move row down + if(!self.hide_delete_buttons) { + self.rows[i].delete_button = this.getButton(self.getItemTitle(),'delete','Delete '+self.getItemTitle()); + self.rows[i].delete_button.className += ' delete'; + self.rows[i].delete_button.setAttribute('data-i',i); + self.rows[i].delete_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + var value = self.getValue(); + + var newval = []; + var new_active_tab = null; + $each(value,function(j,row) { + if(j===i) { + // If the one we're deleting is the active tab + if(self.rows[j].tab === self.active_tab) { + // Make the next tab active if there is one + // Note: the next tab is going to be the current tab after deletion + if(self.rows[j+1]) new_active_tab = self.rows[j].tab; + // Otherwise, make the previous tab active if there is one + else if(j) new_active_tab = self.rows[j-1].tab; + } + + return; // If this is the one we're deleting + } + newval.push(row); + }); + self.setValue(newval); + if(new_active_tab) { + self.active_tab = new_active_tab; + self.refreshTabs(); + } + + self.onChange(true); + }); + + if(controls_holder) { + controls_holder.appendChild(self.rows[i].delete_button); + } + } + + if(i && !self.hide_move_buttons) { + self.rows[i].moveup_button = this.getButton('','moveup','Move up'); + self.rows[i].moveup_button.className += ' moveup'; + self.rows[i].moveup_button.setAttribute('data-i',i); + self.rows[i].moveup_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + if(i<=0) return; + var rows = self.getValue(); + var tmp = rows[i-1]; + rows[i-1] = rows[i]; + rows[i] = tmp; + + self.setValue(rows); + self.active_tab = self.rows[i-1].tab; + self.refreshTabs(); + + self.onChange(true); + }); + + if(controls_holder) { + controls_holder.appendChild(self.rows[i].moveup_button); + } + } + + if(!self.hide_move_buttons) { + self.rows[i].movedown_button = this.getButton('','movedown','Move down'); + self.rows[i].movedown_button.className += ' movedown'; + self.rows[i].movedown_button.setAttribute('data-i',i); + self.rows[i].movedown_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + var rows = self.getValue(); + if(i>=rows.length-1) return; + var tmp = rows[i+1]; + rows[i+1] = rows[i]; + rows[i] = tmp; + + self.setValue(rows); + self.active_tab = self.rows[i+1].tab; + self.refreshTabs(); + self.onChange(true); + }); + + if(controls_holder) { + controls_holder.appendChild(self.rows[i].movedown_button); + } + } + + if(value) self.rows[i].setValue(value, initial); + self.refreshTabs(); + }, + addControls: function() { + var self = this; + + this.collapsed = false; + this.toggle_button = this.getButton('','collapse','Collapse'); + this.title_controls.appendChild(this.toggle_button); + var row_holder_display = self.row_holder.style.display; + var controls_display = self.controls.style.display; + this.toggle_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + if(self.collapsed) { + self.collapsed = false; + if(self.panel) self.panel.style.display = ''; + self.row_holder.style.display = row_holder_display; + if(self.tabs_holder) self.tabs_holder.style.display = ''; + self.controls.style.display = controls_display; + self.setButtonText(this,'','collapse','Collapse'); + } + else { + self.collapsed = true; + self.row_holder.style.display = 'none'; + if(self.tabs_holder) self.tabs_holder.style.display = 'none'; + self.controls.style.display = 'none'; + if(self.panel) self.panel.style.display = 'none'; + self.setButtonText(this,'','expand','Expand'); + } + }); + + // If it should start collapsed + if(this.options.collapsed) { + $trigger(this.toggle_button,'click'); + } + + // Collapse button disabled + if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") { + if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none'; + } + else if(this.jsoneditor.options.disable_collapse) { + this.toggle_button.style.display = 'none'; + } + + // Add "new row" and "delete last" buttons below editor + this.add_row_button = this.getButton(this.getItemTitle(),'add','Add '+this.getItemTitle()); + + this.add_row_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = self.rows.length; + if(self.row_cache[i]) { + self.rows[i] = self.row_cache[i]; + self.rows[i].setValue(self.rows[i].getDefault()); + self.rows[i].container.style.display = ''; + if(self.rows[i].tab) self.rows[i].tab.style.display = ''; + self.rows[i].register(); + } + else { + self.addRow(); + } + self.active_tab = self.rows[i].tab; + self.refreshTabs(); + self.refreshValue(); + self.onChange(true); + }); + self.controls.appendChild(this.add_row_button); + + this.delete_last_row_button = this.getButton('Last '+this.getItemTitle(),'delete','Delete Last '+this.getItemTitle()); + this.delete_last_row_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var rows = self.getValue(); + + var new_active_tab = null; + if(self.rows.length > 1 && self.rows[self.rows.length-1].tab === self.active_tab) new_active_tab = self.rows[self.rows.length-2].tab; + + rows.pop(); + self.setValue(rows); + if(new_active_tab) { + self.active_tab = new_active_tab; + self.refreshTabs(); + } + self.onChange(true); + }); + self.controls.appendChild(this.delete_last_row_button); + + this.remove_all_rows_button = this.getButton('All','delete','Delete All'); + this.remove_all_rows_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + self.setValue([]); + self.onChange(true); + }); + self.controls.appendChild(this.remove_all_rows_button); + + if(self.tabs) { + this.add_row_button.style.width = '100%'; + this.add_row_button.style.textAlign = 'left'; + this.add_row_button.style.marginBottom = '3px'; + + this.delete_last_row_button.style.width = '100%'; + this.delete_last_row_button.style.textAlign = 'left'; + this.delete_last_row_button.style.marginBottom = '3px'; + + this.remove_all_rows_button.style.width = '100%'; + this.remove_all_rows_button.style.textAlign = 'left'; + this.remove_all_rows_button.style.marginBottom = '3px'; + } + }, + showValidationErrors: function(errors) { + var self = this; + + // Get all the errors that pertain to this editor + var my_errors = []; + var other_errors = []; + $each(errors, function(i,error) { + if(error.path === self.path) { + my_errors.push(error); + } + else { + other_errors.push(error); + } + }); + + // Show errors for this editor + if(this.error_holder) { + if(my_errors.length) { + var message = []; + this.error_holder.innerHTML = ''; + this.error_holder.style.display = ''; + $each(my_errors, function(i,error) { + self.error_holder.appendChild(self.theme.getErrorMessage(error.message)); + }); + } + // Hide error area + else { + this.error_holder.style.display = 'none'; + } + } + + // Show errors for child editors + $each(this.rows, function(i,row) { + row.showValidationErrors(other_errors); + }); + } +}); + +JSONEditor.defaults.editors.table = JSONEditor.defaults.editors.array.extend({ + register: function() { + this._super(); + if(this.rows) { + for(var i=0; i this.schema.maxItems) { + value = value.slice(0,this.schema.maxItems); + } + + var serialized = JSON.stringify(value); + if(serialized === this.serialized) return; + + var numrows_changed = false; + + var self = this; + $each(value,function(i,val) { + if(self.rows[i]) { + // TODO: don't set the row's value if it hasn't changed + self.rows[i].setValue(val); + } + else { + self.addRow(val); + numrows_changed = true; + } + }); + + for(var j=value.length; j= this.rows.length; + + var need_row_buttons = false; + $each(this.rows,function(i,editor) { + // Hide the move down button for the last row + if(editor.movedown_button) { + if(i === self.rows.length - 1) { + editor.movedown_button.style.display = 'none'; + } + else { + need_row_buttons = true; + editor.movedown_button.style.display = ''; + } + } + + // Hide the delete button if we have minItems items + if(editor.delete_button) { + if(minItems) { + editor.delete_button.style.display = 'none'; + } + else { + need_row_buttons = true; + editor.delete_button.style.display = ''; + } + } + + if(editor.moveup_button) { + need_row_buttons = true; + } + }); + + // Show/hide controls column in table + $each(this.rows,function(i,editor) { + if(need_row_buttons) { + editor.controls_cell.style.display = ''; + } + else { + editor.controls_cell.style.display = 'none'; + } + }); + if(need_row_buttons) { + this.controls_header_cell.style.display = ''; + } + else { + this.controls_header_cell.style.display = 'none'; + } + + var controls_needed = false; + + if(!this.value.length) { + this.delete_last_row_button.style.display = 'none'; + this.remove_all_rows_button.style.display = 'none'; + this.table.style.display = 'none'; + } + else if(this.value.length === 1 || this.hide_delete_buttons) { + this.table.style.display = ''; + this.remove_all_rows_button.style.display = 'none'; + + // If there are minItems items in the array, hide the delete button beneath the rows + if(minItems || this.hide_delete_buttons) { + this.delete_last_row_button.style.display = 'none'; + } + else { + this.delete_last_row_button.style.display = ''; + controls_needed = true; + } + } + else { + this.table.style.display = ''; + // If there are minItems items in the array, hide the delete button beneath the rows + if(minItems || this.hide_delete_buttons) { + this.delete_last_row_button.style.display = 'none'; + this.remove_all_rows_button.style.display = 'none'; + } + else { + this.delete_last_row_button.style.display = ''; + this.remove_all_rows_button.style.display = ''; + controls_needed = true; + } + } + + // If there are maxItems in the array, hide the add button beneath the rows + if((this.schema.maxItems && this.schema.maxItems <= this.rows.length) || this.hide_add_button) { + this.add_row_button.style.display = 'none'; + } + else { + this.add_row_button.style.display = ''; + controls_needed = true; + } + + if(!controls_needed) { + this.controls.style.display = 'none'; + } + else { + this.controls.style.display = ''; + } + }, + refreshValue: function() { + var self = this; + this.value = []; + + $each(this.rows,function(i,editor) { + // Get the value for this editor + self.value[i] = editor.getValue(); + }); + this.serialized = JSON.stringify(this.value); + }, + addRow: function(value) { + var self = this; + var i = this.rows.length; + + self.rows[i] = this.getElementEditor(i); + + var controls_holder = self.rows[i].table_controls; + + // Buttons to delete row, move row up, and move row down + if(!this.hide_delete_buttons) { + self.rows[i].delete_button = this.getButton('','delete','Delete'); + self.rows[i].delete_button.className += ' delete'; + self.rows[i].delete_button.setAttribute('data-i',i); + self.rows[i].delete_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + var value = self.getValue(); + + var newval = []; + $each(value,function(j,row) { + if(j===i) return; // If this is the one we're deleting + newval.push(row); + }); + self.setValue(newval); + self.onChange(true); + }); + controls_holder.appendChild(self.rows[i].delete_button); + } + + + if(i && !this.hide_move_buttons) { + self.rows[i].moveup_button = this.getButton('','moveup','Move up'); + self.rows[i].moveup_button.className += ' moveup'; + self.rows[i].moveup_button.setAttribute('data-i',i); + self.rows[i].moveup_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + + if(i<=0) return; + var rows = self.getValue(); + var tmp = rows[i-1]; + rows[i-1] = rows[i]; + rows[i] = tmp; + + self.setValue(rows); + self.onChange(true); + }); + controls_holder.appendChild(self.rows[i].moveup_button); + } + + if(!this.hide_move_buttons) { + self.rows[i].movedown_button = this.getButton('','movedown','Move down'); + self.rows[i].movedown_button.className += ' movedown'; + self.rows[i].movedown_button.setAttribute('data-i',i); + self.rows[i].movedown_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + var i = this.getAttribute('data-i')*1; + var rows = self.getValue(); + if(i>=rows.length-1) return; + var tmp = rows[i+1]; + rows[i+1] = rows[i]; + rows[i] = tmp; + + self.setValue(rows); + self.onChange(true); + }); + controls_holder.appendChild(self.rows[i].movedown_button); + } + + if(value) self.rows[i].setValue(value); + }, + addControls: function() { + var self = this; + + this.collapsed = false; + this.toggle_button = this.getButton('','collapse','Collapse'); + if(this.title_controls) { + this.title_controls.appendChild(this.toggle_button); + this.toggle_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + + if(self.collapsed) { + self.collapsed = false; + self.panel.style.display = ''; + self.setButtonText(this,'','collapse','Collapse'); + } + else { + self.collapsed = true; + self.panel.style.display = 'none'; + self.setButtonText(this,'','expand','Expand'); + } + }); + + // If it should start collapsed + if(this.options.collapsed) { + $trigger(this.toggle_button,'click'); + } + + // Collapse button disabled + if(this.schema.options && typeof this.schema.options.disable_collapse !== "undefined") { + if(this.schema.options.disable_collapse) this.toggle_button.style.display = 'none'; + } + else if(this.jsoneditor.options.disable_collapse) { + this.toggle_button.style.display = 'none'; + } + } + + // Add "new row" and "delete last" buttons below editor + this.add_row_button = this.getButton(this.getItemTitle(),'add','Add '+this.getItemTitle()); + this.add_row_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + + self.addRow(); + self.refreshValue(); + self.refreshRowButtons(); + self.onChange(true); + }); + self.controls.appendChild(this.add_row_button); + + this.delete_last_row_button = this.getButton('Last '+this.getItemTitle(),'delete','Delete Last '+this.getItemTitle()); + this.delete_last_row_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + + var rows = self.getValue(); + rows.pop(); + self.setValue(rows); + self.onChange(true); + }); + self.controls.appendChild(this.delete_last_row_button); + + this.remove_all_rows_button = this.getButton('All','delete','Delete All'); + this.remove_all_rows_button.addEventListener('click',function(e) { + e.preventDefault(); + e.stopPropagation(); + + self.setValue([]); + self.onChange(true); + }); + self.controls.appendChild(this.remove_all_rows_button); + } +}); + +// Multiple Editor (for when `type` is an array) +JSONEditor.defaults.editors.multiple = JSONEditor.AbstractEditor.extend({ + register: function() { + if(this.editors) { + for(var i=0; inull'; + } + // Array or Object + else if(typeof el === "object") { + // TODO: use theme + var ret = ''; + + $each(el,function(i,child) { + var html = self.getHTML(child); + + // Add the keys to object children + if(!(Array.isArray(el))) { + // TODO: use theme + html = '
'+i+': '+html+'
'; + } + + // TODO: use theme + ret += '
  • '+html+'
  • '; + }); + + if(Array.isArray(el)) ret = '
      '+ret+'
    '; + else ret = "
      "+ret+'
    '; + + return ret; + } + // Boolean + else if(typeof el === "boolean") { + return el? 'true' : 'false'; + } + // String + else if(typeof el === "string") { + return el.replace(/&/g,'&').replace(//g,'>'); + } + // Number + else { + return el; + } + }, + setValue: function(val) { + if(this.value !== val) { + this.value = val; + this.refreshValue(); + this.onChange(); + } + }, + destroy: function() { + if(this.display_area && this.display_area.parentNode) this.display_area.parentNode.removeChild(this.display_area); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.switcher && this.switcher.parentNode) this.switcher.parentNode.removeChild(this.switcher); + + this._super(); + } +}); + +JSONEditor.defaults.editors.select = JSONEditor.AbstractEditor.extend({ + setValue: function(value,initial) { + value = this.typecast(value||''); + + // Sanitize value before setting it + var sanitized = value; + if(this.enum_values.indexOf(sanitized) < 0) { + sanitized = this.enum_values[0]; + } + + if(this.value === sanitized) { + return; + } + + this.input.value = this.enum_options[this.enum_values.indexOf(sanitized)]; + if(this.select2) this.select2.select2('val',this.input.value); + this.value = sanitized; + this.onChange(); + }, + register: function() { + this._super(); + if(!this.input) return; + this.input.setAttribute('name',this.formname); + }, + unregister: function() { + this._super(); + if(!this.input) return; + this.input.removeAttribute('name'); + }, + getNumColumns: function() { + if(!this.enum_options) return 3; + var longest_text = this.getTitle().length; + for(var i=0; i 2 || (this.enum_options.length && this.enumSource))) { + var options = $extend({},JSONEditor.plugins.select2); + if(this.schema.options && this.schema.options.select2_options) options = $extend(options,this.schema.options.select2_options); + this.select2 = window.jQuery(this.input).select2(options); + var self = this; + this.select2.on('select2-blur',function() { + self.input.value = self.select2.select2('val'); + self.onInputChange(); + }); + this.select2.on('change',function() { + self.input.value = self.select2.select2('val'); + self.onInputChange(); + }); + } + else { + this.select2 = null; + } + }, + postBuild: function() { + this._super(); + this.theme.afterInputReady(this.input); + this.setupSelect2(); + }, + onWatchedFieldChange: function() { + var self = this, vars, j; + + // If this editor uses a dynamic select box + if(this.enumSource) { + vars = this.getWatchedFieldValues(); + var select_options = []; + var select_titles = []; + + for(var i=0; i= 2 || (this.enum_options.length && this.enumSource))) { + var options = $extend({},JSONEditor.plugins.selectize); + if(this.schema.options && this.schema.options.selectize_options) options = $extend(options,this.schema.options.selectize_options); + this.selectize = window.jQuery(this.input).selectize($extend(options, + { + create: true, + onChange : function() { + self.onInputChange(); + } + })); + } + else { + this.selectize = null; + } + }, + postBuild: function() { + this._super(); + this.theme.afterInputReady(this.input); + this.setupSelectize(); + }, + onWatchedFieldChange: function() { + var self = this, vars, j; + + // If this editor uses a dynamic select box + if(this.enumSource) { + vars = this.getWatchedFieldValues(); + var select_options = []; + var select_titles = []; + + for(var i=0; iSize: '+Math.floor((this.value.length-this.value.split(',')[0].length-1)/1.33333)+' bytes'; + if(mime.substr(0,5)==="image") { + this.preview.innerHTML += '
    '; + var img = document.createElement('img'); + img.style.maxWidth = '100%'; + img.style.maxHeight = '100px'; + img.src = this.value; + this.preview.appendChild(img); + } + } + }, + enable: function() { + if(this.uploader) this.uploader.disabled = false; + this._super(); + }, + disable: function() { + if(this.uploader) this.uploader.disabled = true; + this._super(); + }, + setValue: function(val) { + if(this.value !== val) { + this.value = val; + this.input.value = this.value; + this.refreshPreview(); + this.onChange(); + } + }, + destroy: function() { + if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader); + + this._super(); + } +}); + +JSONEditor.defaults.editors.upload = JSONEditor.AbstractEditor.extend({ + getNumColumns: function() { + return 4; + }, + build: function() { + var self = this; + this.title = this.header = this.label = this.theme.getFormInputLabel(this.getTitle()); + + // Input that holds the base64 string + this.input = this.theme.getFormInputField('hidden'); + this.container.appendChild(this.input); + + // Don't show uploader if this is readonly + if(!this.schema.readOnly && !this.schema.readonly) { + + if(!this.jsoneditor.options.upload) throw "Upload handler required for upload editor"; + + // File uploader + this.uploader = this.theme.getFormInputField('file'); + + this.uploader.addEventListener('change',function(e) { + e.preventDefault(); + e.stopPropagation(); + + if(this.files && this.files.length) { + var fr = new FileReader(); + fr.onload = function(evt) { + self.preview_value = evt.target.result; + self.refreshPreview(); + self.onChange(true); + fr = null; + }; + fr.readAsDataURL(this.files[0]); + } + }); + } + + var description = this.schema.description; + if (!description) description = ''; + + this.preview = this.theme.getFormInputDescription(description); + this.container.appendChild(this.preview); + + this.control = this.theme.getFormControl(this.label, this.uploader||this.input, this.preview); + this.container.appendChild(this.control); + }, + refreshPreview: function() { + if(this.last_preview === this.preview_value) return; + this.last_preview = this.preview_value; + + this.preview.innerHTML = ''; + + if(!this.preview_value) return; + + var self = this; + + var mime = this.preview_value.match(/^data:([^;,]+)[;,]/); + if(mime) mime = mime[1]; + if(!mime) mime = 'unknown'; + + var file = this.uploader.files[0]; + + this.preview.innerHTML = 'Type: '+mime+', Size: '+file.size+' bytes'; + if(mime.substr(0,5)==="image") { + this.preview.innerHTML += '
    '; + var img = document.createElement('img'); + img.style.maxWidth = '100%'; + img.style.maxHeight = '100px'; + img.src = this.preview_value; + this.preview.appendChild(img); + } + + this.preview.innerHTML += '
    '; + var uploadButton = this.getButton('Upload', 'upload', 'Upload'); + this.preview.appendChild(uploadButton); + uploadButton.addEventListener('click',function(event) { + event.preventDefault(); + + uploadButton.setAttribute("disabled", "disabled"); + self.theme.removeInputError(self.uploader); + + if (self.theme.getProgressBar) { + self.progressBar = self.theme.getProgressBar(); + self.preview.appendChild(self.progressBar); + } + + self.jsoneditor.options.upload(self.path, file, { + success: function(url) { + self.setValue(url); + + if(self.parent) self.parent.onChildEditorChange(self); + else self.jsoneditor.onChange(); + + if (self.progressBar) self.preview.removeChild(self.progressBar); + uploadButton.removeAttribute("disabled"); + }, + failure: function(error) { + self.theme.addInputError(self.uploader, error); + if (self.progressBar) self.preview.removeChild(self.progressBar); + uploadButton.removeAttribute("disabled"); + }, + updateProgress: function(progress) { + if (self.progressBar) { + if (progress) self.theme.updateProgressBar(self.progressBar, progress); + else self.theme.updateProgressBarUnknown(self.progressBar); + } + } + }); + }); + }, + enable: function() { + if(this.uploader) this.uploader.disabled = false; + this._super(); + }, + disable: function() { + if(this.uploader) this.uploader.disabled = true; + this._super(); + }, + setValue: function(val) { + if(this.value !== val) { + this.value = val; + this.input.value = this.value; + this.onChange(); + } + }, + destroy: function() { + if(this.preview && this.preview.parentNode) this.preview.parentNode.removeChild(this.preview); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + if(this.uploader && this.uploader.parentNode) this.uploader.parentNode.removeChild(this.uploader); + + this._super(); + } +}); + +JSONEditor.defaults.editors.checkbox = JSONEditor.AbstractEditor.extend({ + setValue: function(value,initial) { + this.value = !!value; + this.input.checked = this.value; + this.onChange(); + }, + register: function() { + this._super(); + if(!this.input) return; + this.input.setAttribute('name',this.formname); + }, + unregister: function() { + this._super(); + if(!this.input) return; + this.input.removeAttribute('name'); + }, + getNumColumns: function() { + return Math.min(12,Math.max(this.getTitle().length/7,2)); + }, + build: function() { + var self = this; + if(!this.options.compact) { + this.label = this.header = this.theme.getCheckboxLabel(this.getTitle()); + } + if(this.schema.description) this.description = this.theme.getFormInputDescription(this.schema.description); + if(this.options.compact) this.container.className += ' compact'; + + this.input = this.theme.getCheckbox(); + this.control = this.theme.getFormControl(this.label, this.input, this.description); + + if(this.schema.readOnly || this.schema.readonly) { + this.always_disabled = true; + this.input.disabled = true; + } + + this.input.addEventListener('change',function(e) { + e.preventDefault(); + e.stopPropagation(); + self.value = this.checked; + self.onChange(true); + }); + + this.container.appendChild(this.control); + }, + enable: function() { + if(!this.always_disabled) { + this.input.disabled = false; + } + this._super(); + }, + disable: function() { + this.input.disabled = true; + this._super(); + }, + destroy: function() { + if(this.label && this.label.parentNode) this.label.parentNode.removeChild(this.label); + if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + this._super(); + } +}); + +JSONEditor.defaults.editors.arraySelectize = JSONEditor.AbstractEditor.extend({ + build: function() { + this.title = this.theme.getFormInputLabel(this.getTitle()); + + this.title_controls = this.theme.getHeaderButtonHolder(); + this.title.appendChild(this.title_controls); + this.error_holder = document.createElement('div'); + + if(this.schema.description) { + this.description = this.theme.getDescription(this.schema.description); + } + + this.input = document.createElement('select'); + this.input.setAttribute('multiple', 'multiple'); + + var group = this.theme.getFormControl(this.title, this.input, this.description); + + this.container.appendChild(group); + this.container.appendChild(this.error_holder); + + window.jQuery(this.input).selectize({ + delimiter: false, + createOnBlur: true, + create: true + }); + }, + postBuild: function() { + var self = this; + this.input.selectize.on('change', function(event) { + self.refreshValue(); + self.onChange(true); + }); + }, + destroy: function() { + this.empty(true); + if(this.title && this.title.parentNode) this.title.parentNode.removeChild(this.title); + if(this.description && this.description.parentNode) this.description.parentNode.removeChild(this.description); + if(this.input && this.input.parentNode) this.input.parentNode.removeChild(this.input); + + this._super(); + }, + empty: function(hard) {}, + setValue: function(value, initial) { + var self = this; + // Update the array's value, adding/removing rows when necessary + value = value || []; + if(!(Array.isArray(value))) value = [value]; + + this.input.selectize.clearOptions(); + this.input.selectize.clear(true); + + value.forEach(function(item) { + self.input.selectize.addOption({text: item, value: item}); + }); + this.input.selectize.setValue(value); + + this.refreshValue(initial); + }, + refreshValue: function(force) { + this.value = this.input.selectize.getValue(); + }, + showValidationErrors: function(errors) { + var self = this; + + // Get all the errors that pertain to this editor + var my_errors = []; + var other_errors = []; + $each(errors, function(i,error) { + if(error.path === self.path) { + my_errors.push(error); + } + else { + other_errors.push(error); + } + }); + + // Show errors for this editor + if(this.error_holder) { + + if(my_errors.length) { + var message = []; + this.error_holder.innerHTML = ''; + this.error_holder.style.display = ''; + $each(my_errors, function(i,error) { + self.error_holder.appendChild(self.theme.getErrorMessage(error.message)); + }); + } + // Hide error area + else { + this.error_holder.style.display = 'none'; + } + } + } +}); + +var matchKey = (function () { + var elem = document.documentElement; + + if (elem.matches) return 'matches'; + else if (elem.webkitMatchesSelector) return 'webkitMatchesSelector'; + else if (elem.mozMatchesSelector) return 'mozMatchesSelector'; + else if (elem.msMatchesSelector) return 'msMatchesSelector'; + else if (elem.oMatchesSelector) return 'oMatchesSelector'; +})(); + +JSONEditor.AbstractTheme = Class.extend({ + getContainer: function() { + return document.createElement('div'); + }, + getFloatRightLinkHolder: function() { + var el = document.createElement('div'); + el.style = el.style || {}; + el.style.cssFloat = 'right'; + el.style.marginLeft = '10px'; + return el; + }, + getModal: function() { + var el = document.createElement('div'); + el.style.backgroundColor = 'white'; + el.style.border = '1px solid black'; + el.style.boxShadow = '3px 3px black'; + el.style.position = 'absolute'; + el.style.zIndex = '10'; + el.style.display = 'none'; + return el; + }, + getGridContainer: function() { + var el = document.createElement('div'); + return el; + }, + getGridRow: function() { + var el = document.createElement('div'); + el.className = 'row'; + return el; + }, + getGridColumn: function() { + var el = document.createElement('div'); + return el; + }, + setGridColumnSize: function(el,size) { + + }, + getLink: function(text) { + var el = document.createElement('a'); + el.setAttribute('href','#'); + el.appendChild(document.createTextNode(text)); + return el; + }, + disableHeader: function(header) { + header.style.color = '#ccc'; + }, + disableLabel: function(label) { + label.style.color = '#ccc'; + }, + enableHeader: function(header) { + header.style.color = ''; + }, + enableLabel: function(label) { + label.style.color = ''; + }, + getFormInputLabel: function(text) { + var el = document.createElement('label'); + el.appendChild(document.createTextNode(text)); + return el; + }, + getCheckboxLabel: function(text) { + var el = this.getFormInputLabel(text); + el.style.fontWeight = 'normal'; + return el; + }, + getHeader: function(text) { + var el = document.createElement('h3'); + if(typeof text === "string") { + el.textContent = text; + } + else { + el.appendChild(text); + } + + return el; + }, + getCheckbox: function() { + var el = this.getFormInputField('checkbox'); + el.style.display = 'inline-block'; + el.style.width = 'auto'; + return el; + }, + getMultiCheckboxHolder: function(controls,label,description) { + var el = document.createElement('div'); + + if(label) { + label.style.display = 'block'; + el.appendChild(label); + } + + for(var i in controls) { + if(!controls.hasOwnProperty(i)) continue; + controls[i].style.display = 'inline-block'; + controls[i].style.marginRight = '20px'; + el.appendChild(controls[i]); + } + + if(description) el.appendChild(description); + + return el; + }, + getSelectInput: function(options) { + var select = document.createElement('select'); + if(options) this.setSelectOptions(select, options); + return select; + }, + getSwitcher: function(options) { + var switcher = this.getSelectInput(options); + switcher.style.backgroundColor = 'transparent'; + switcher.style.display = 'inline-block'; + switcher.style.fontStyle = 'italic'; + switcher.style.fontWeight = 'normal'; + switcher.style.height = 'auto'; + switcher.style.marginBottom = 0; + switcher.style.marginLeft = '5px'; + switcher.style.padding = '0 0 0 3px'; + switcher.style.width = 'auto'; + return switcher; + }, + getSwitcherOptions: function(switcher) { + return switcher.getElementsByTagName('option'); + }, + setSwitcherOptions: function(switcher, options, titles) { + this.setSelectOptions(switcher, options, titles); + }, + setSelectOptions: function(select, options, titles) { + titles = titles || []; + select.innerHTML = ''; + for(var i=0; i'); + input.errmsg = input.parentNode.getElementsByClassName('error')[0]; + } + else { + input.errmsg.style.display = ''; + } + + input.errmsg.textContent = text; + }, + removeInputError: function(input) { + if(!input.errmsg) return; + input.group.className = input.group.className.replace(/ error/g,''); + input.errmsg.style.display = 'none'; + }, + getProgressBar: function() { + var progressBar = document.createElement('div'); + progressBar.className = 'progress'; + + var meter = document.createElement('span'); + meter.className = 'meter'; + meter.style.width = '0%'; + progressBar.appendChild(meter); + return progressBar; + }, + updateProgressBar: function(progressBar, progress) { + if (!progressBar) return; + progressBar.firstChild.style.width = progress + '%'; + }, + updateProgressBarUnknown: function(progressBar) { + if (!progressBar) return; + progressBar.firstChild.style.width = '100%'; + } +}); + +// Foundation 3 Specific Theme +JSONEditor.defaults.themes.foundation3 = JSONEditor.defaults.themes.foundation.extend({ + getHeaderButtonHolder: function() { + var el = this._super(); + el.style.fontSize = '.6em'; + return el; + }, + getFormInputLabel: function(text) { + var el = this._super(text); + el.style.fontWeight = 'bold'; + return el; + }, + getTabHolder: function() { + var el = document.createElement('div'); + el.className = 'row'; + el.innerHTML = "
    "; + return el; + }, + setGridColumnSize: function(el,size) { + var sizes = ['zero','one','two','three','four','five','six','seven','eight','nine','ten','eleven','twelve']; + el.className = 'columns '+sizes[size]; + }, + getTab: function(text) { + var el = document.createElement('dd'); + var a = document.createElement('a'); + a.setAttribute('href','#'); + a.appendChild(text); + el.appendChild(a); + return el; + }, + getTabContentHolder: function(tab_holder) { + return tab_holder.children[1]; + }, + getTabContent: function() { + var el = document.createElement('div'); + el.className = 'content active'; + el.style.paddingLeft = '5px'; + return el; + }, + markTabActive: function(tab) { + tab.className += ' active'; + }, + markTabInactive: function(tab) { + tab.className = tab.className.replace(/\s*active/g,''); + }, + addTab: function(holder, tab) { + holder.children[0].appendChild(tab); + } +}); + +// Foundation 4 Specific Theme +JSONEditor.defaults.themes.foundation4 = JSONEditor.defaults.themes.foundation.extend({ + getHeaderButtonHolder: function() { + var el = this._super(); + el.style.fontSize = '.6em'; + return el; + }, + setGridColumnSize: function(el,size) { + el.className = 'columns large-'+size; + }, + getFormInputDescription: function(text) { + var el = this._super(text); + el.style.fontSize = '.8rem'; + return el; + }, + getFormInputLabel: function(text) { + var el = this._super(text); + el.style.fontWeight = 'bold'; + return el; + } +}); + +// Foundation 5 Specific Theme +JSONEditor.defaults.themes.foundation5 = JSONEditor.defaults.themes.foundation.extend({ + getFormInputDescription: function(text) { + var el = this._super(text); + el.style.fontSize = '.8rem'; + return el; + }, + setGridColumnSize: function(el,size) { + el.className = 'columns medium-'+size; + }, + getButton: function(text, icon, title) { + var el = this._super(text,icon,title); + el.className = el.className.replace(/\s*small/g,'') + ' tiny'; + return el; + }, + getTabHolder: function() { + var el = document.createElement('div'); + el.innerHTML = "
    "; + return el; + }, + getTab: function(text) { + var el = document.createElement('dd'); + var a = document.createElement('a'); + a.setAttribute('href','#'); + a.appendChild(text); + el.appendChild(a); + return el; + }, + getTabContentHolder: function(tab_holder) { + return tab_holder.children[1]; + }, + getTabContent: function() { + var el = document.createElement('div'); + el.className = 'content active'; + el.style.paddingLeft = '5px'; + return el; + }, + markTabActive: function(tab) { + tab.className += ' active'; + }, + markTabInactive: function(tab) { + tab.className = tab.className.replace(/\s*active/g,''); + }, + addTab: function(holder, tab) { + holder.children[0].appendChild(tab); + } +}); + +JSONEditor.defaults.themes.html = JSONEditor.AbstractTheme.extend({ + getFormInputLabel: function(text) { + var el = this._super(text); + el.style.display = 'block'; + el.style.marginBottom = '3px'; + el.style.fontWeight = 'bold'; + return el; + }, + getFormInputDescription: function(text) { + var el = this._super(text); + el.style.fontSize = '.8em'; + el.style.margin = 0; + el.style.display = 'inline-block'; + el.style.fontStyle = 'italic'; + return el; + }, + getIndentedPanel: function() { + var el = this._super(); + el.style.border = '1px solid #ddd'; + el.style.padding = '5px'; + el.style.margin = '5px'; + el.style.borderRadius = '3px'; + return el; + }, + getChildEditorHolder: function() { + var el = this._super(); + el.style.marginBottom = '8px'; + return el; + }, + getHeaderButtonHolder: function() { + var el = this.getButtonHolder(); + el.style.display = 'inline-block'; + el.style.marginLeft = '10px'; + el.style.fontSize = '.8em'; + el.style.verticalAlign = 'middle'; + return el; + }, + getTable: function() { + var el = this._super(); + el.style.borderBottom = '1px solid #ccc'; + el.style.marginBottom = '5px'; + return el; + }, + addInputError: function(input, text) { + input.style.borderColor = 'red'; + + if(!input.errmsg) { + var group = this.closest(input,'.form-control'); + input.errmsg = document.createElement('div'); + input.errmsg.setAttribute('class','errmsg'); + input.errmsg.style = input.errmsg.style || {}; + input.errmsg.style.color = 'red'; + group.appendChild(input.errmsg); + } + else { + input.errmsg.style.display = 'block'; + } + + input.errmsg.innerHTML = ''; + input.errmsg.appendChild(document.createTextNode(text)); + }, + removeInputError: function(input) { + input.style.borderColor = ''; + if(input.errmsg) input.errmsg.style.display = 'none'; + }, + getProgressBar: function() { + var max = 100, start = 0; + + var progressBar = document.createElement('progress'); + progressBar.setAttribute('max', max); + progressBar.setAttribute('value', start); + return progressBar; + }, + updateProgressBar: function(progressBar, progress) { + if (!progressBar) return; + progressBar.setAttribute('value', progress); + }, + updateProgressBarUnknown: function(progressBar) { + if (!progressBar) return; + progressBar.removeAttribute('value'); + } +}); + +JSONEditor.defaults.themes.jqueryui = JSONEditor.AbstractTheme.extend({ + getTable: function() { + var el = this._super(); + el.setAttribute('cellpadding',5); + el.setAttribute('cellspacing',0); + return el; + }, + getTableHeaderCell: function(text) { + var el = this._super(text); + el.className = 'ui-state-active'; + el.style.fontWeight = 'bold'; + return el; + }, + getTableCell: function() { + var el = this._super(); + el.className = 'ui-widget-content'; + return el; + }, + getHeaderButtonHolder: function() { + var el = this.getButtonHolder(); + el.style.marginLeft = '10px'; + el.style.fontSize = '.6em'; + el.style.display = 'inline-block'; + return el; + }, + getFormInputDescription: function(text) { + var el = this.getDescription(text); + el.style.marginLeft = '10px'; + el.style.display = 'inline-block'; + return el; + }, + getFormControl: function(label, input, description) { + var el = this._super(label,input,description); + if(input.type === 'checkbox') { + el.style.lineHeight = '25px'; + + el.style.padding = '3px 0'; + } + else { + el.style.padding = '4px 0 8px 0'; + } + return el; + }, + getDescription: function(text) { + var el = document.createElement('span'); + el.style.fontSize = '.8em'; + el.style.fontStyle = 'italic'; + el.textContent = text; + return el; + }, + getButtonHolder: function() { + var el = document.createElement('div'); + el.className = 'ui-buttonset'; + el.style.fontSize = '.7em'; + return el; + }, + getFormInputLabel: function(text) { + var el = document.createElement('label'); + el.style.fontWeight = 'bold'; + el.style.display = 'block'; + el.textContent = text; + return el; + }, + getButton: function(text, icon, title) { + var button = document.createElement("button"); + button.className = 'ui-button ui-widget ui-state-default ui-corner-all'; + + // Icon only + if(icon && !text) { + button.className += ' ui-button-icon-only'; + icon.className += ' ui-button-icon-primary ui-icon-primary'; + button.appendChild(icon); + } + // Icon and Text + else if(icon) { + button.className += ' ui-button-text-icon-primary'; + icon.className += ' ui-button-icon-primary ui-icon-primary'; + button.appendChild(icon); + } + // Text only + else { + button.className += ' ui-button-text-only'; + } + + var el = document.createElement('span'); + el.className = 'ui-button-text'; + el.textContent = text||title||"."; + button.appendChild(el); + + button.setAttribute('title',title); + + return button; + }, + setButtonText: function(button,text, icon, title) { + button.innerHTML = ''; + button.className = 'ui-button ui-widget ui-state-default ui-corner-all'; + + // Icon only + if(icon && !text) { + button.className += ' ui-button-icon-only'; + icon.className += ' ui-button-icon-primary ui-icon-primary'; + button.appendChild(icon); + } + // Icon and Text + else if(icon) { + button.className += ' ui-button-text-icon-primary'; + icon.className += ' ui-button-icon-primary ui-icon-primary'; + button.appendChild(icon); + } + // Text only + else { + button.className += ' ui-button-text-only'; + } + + var el = document.createElement('span'); + el.className = 'ui-button-text'; + el.textContent = text||title||"."; + button.appendChild(el); + + button.setAttribute('title',title); + }, + getIndentedPanel: function() { + var el = document.createElement('div'); + el.className = 'ui-widget-content ui-corner-all'; + el.style.padding = '1em 1.4em'; + el.style.marginBottom = '20px'; + return el; + }, + afterInputReady: function(input) { + if(input.controls) return; + input.controls = this.closest(input,'.form-control'); + }, + addInputError: function(input,text) { + if(!input.controls) return; + if(!input.errmsg) { + input.errmsg = document.createElement('div'); + input.errmsg.className = 'ui-state-error'; + input.controls.appendChild(input.errmsg); + } + else { + input.errmsg.style.display = ''; + } + + input.errmsg.textContent = text; + }, + removeInputError: function(input) { + if(!input.errmsg) return; + input.errmsg.style.display = 'none'; + }, + markTabActive: function(tab) { + tab.className = tab.className.replace(/\s*ui-widget-header/g,'')+' ui-state-active'; + }, + markTabInactive: function(tab) { + tab.className = tab.className.replace(/\s*ui-state-active/g,'')+' ui-widget-header'; + } +}); + +JSONEditor.AbstractIconLib = Class.extend({ + mapping: { + collapse: '', + expand: '', + "delete": '', + edit: '', + add: '', + cancel: '', + save: '', + moveup: '', + movedown: '' + }, + icon_prefix: '', + getIconClass: function(key) { + if(this.mapping[key]) return this.icon_prefix+this.mapping[key]; + else return null; + }, + getIcon: function(key) { + var iconclass = this.getIconClass(key); + + if(!iconclass) return null; + + var i = document.createElement('i'); + i.className = iconclass; + return i; + } +}); + +JSONEditor.defaults.iconlibs.bootstrap2 = JSONEditor.AbstractIconLib.extend({ + mapping: { + collapse: 'chevron-down', + expand: 'chevron-up', + "delete": 'trash', + edit: 'pencil', + add: 'plus', + cancel: 'ban-circle', + save: 'ok', + moveup: 'arrow-up', + movedown: 'arrow-down' + }, + icon_prefix: 'icon-' +}); + +JSONEditor.defaults.iconlibs.bootstrap3 = JSONEditor.AbstractIconLib.extend({ + mapping: { + collapse: 'chevron-down', + expand: 'chevron-right', + "delete": 'remove', + edit: 'pencil', + add: 'plus', + cancel: 'floppy-remove', + save: 'floppy-saved', + moveup: 'arrow-up', + movedown: 'arrow-down' + }, + icon_prefix: 'glyphicon glyphicon-' +}); + +JSONEditor.defaults.iconlibs.fontawesome3 = JSONEditor.AbstractIconLib.extend({ + mapping: { + collapse: 'chevron-down', + expand: 'chevron-right', + "delete": 'remove', + edit: 'pencil', + add: 'plus', + cancel: 'ban-circle', + save: 'save', + moveup: 'arrow-up', + movedown: 'arrow-down' + }, + icon_prefix: 'icon-' +}); + +JSONEditor.defaults.iconlibs.fontawesome4 = JSONEditor.AbstractIconLib.extend({ + mapping: { + collapse: 'caret-square-o-down', + expand: 'caret-square-o-right', + "delete": 'times', + edit: 'pencil', + add: 'plus', + cancel: 'ban', + save: 'save', + moveup: 'arrow-up', + movedown: 'arrow-down' + }, + icon_prefix: 'fa fa-' +}); + +JSONEditor.defaults.iconlibs.foundation2 = JSONEditor.AbstractIconLib.extend({ + mapping: { + collapse: 'minus', + expand: 'plus', + "delete": 'remove', + edit: 'edit', + add: 'add-doc', + cancel: 'error', + save: 'checkmark', + moveup: 'up-arrow', + movedown: 'down-arrow' + }, + icon_prefix: 'foundicon-' +}); + +JSONEditor.defaults.iconlibs.foundation3 = JSONEditor.AbstractIconLib.extend({ + mapping: { + collapse: 'minus', + expand: 'plus', + "delete": 'x', + edit: 'pencil', + add: 'page-add', + cancel: 'x-circle', + save: 'save', + moveup: 'arrow-up', + movedown: 'arrow-down' + }, + icon_prefix: 'fi-' +}); + +JSONEditor.defaults.iconlibs.jqueryui = JSONEditor.AbstractIconLib.extend({ + mapping: { + collapse: 'triangle-1-s', + expand: 'triangle-1-e', + "delete": 'trash', + edit: 'pencil', + add: 'plusthick', + cancel: 'closethick', + save: 'disk', + moveup: 'arrowthick-1-n', + movedown: 'arrowthick-1-s' + }, + icon_prefix: 'ui-icon ui-icon-' +}); + +JSONEditor.defaults.templates["default"] = function() { + return { + compile: function(template) { + var matches = template.match(/{{\s*([a-zA-Z0-9\-_ \.]+)\s*}}/g); + var l = matches && matches.length; + + // Shortcut if the template contains no variables + if(!l) return function() { return template; }; + + // Pre-compute the search/replace functions + // This drastically speeds up template execution + var replacements = []; + var get_replacement = function(i) { + var p = matches[i].replace(/[{}]+/g,'').trim().split('.'); + var n = p.length; + var func; + + if(n > 1) { + var cur; + func = function(vars) { + cur = vars; + for(i=0; i= 0) { + // For enumerated strings, number, or integers + if(schema.items.enum) { + return 'multiselect'; + } + // For non-enumerated strings (tag editor) + else if(JSONEditor.plugins.selectize.enable && schema.items.type === "string") { + return 'arraySelectize'; + } + } +}); +// Use the multiple editor for schemas with `oneOf` set +JSONEditor.defaults.resolvers.unshift(function(schema) { + // If this schema uses `oneOf` + if(schema.oneOf) return "multiple"; +}); + +/** + * This is a small wrapper for using JSON Editor like a typical jQuery plugin. + */ +(function() { + if(window.jQuery || window.Zepto) { + var $ = window.jQuery || window.Zepto; + $.jsoneditor = JSONEditor.defaults; + + $.fn.jsoneditor = function(options) { + var self = this; + var editor = this.data('jsoneditor'); + if(options === 'value') { + if(!editor) throw "Must initialize jsoneditor before getting/setting the value"; + + // Set value + if(arguments.length > 1) { + editor.setValue(arguments[1]); + } + // Get value + else { + return editor.getValue(); + } + } + else if(options === 'validate') { + if(!editor) throw "Must initialize jsoneditor before validating"; + + // Validate a specific value + if(arguments.length > 1) { + return editor.validate(arguments[1]); + } + // Validate current value + else { + return editor.validate(); + } + } + else if(options === 'destroy') { + if(editor) { + editor.destroy(); + this.data('jsoneditor',null); + } + } + else { + // Destroy first + if(editor) { + editor.destroy(); + } + + // Create editor + editor = new JSONEditor(this.get(0),options); + this.data('jsoneditor',editor); + + // Setup event listeners + editor.on('change',function() { + self.trigger('change'); + }); + editor.on('ready',function() { + self.trigger('ready'); + }); + } + + return this; + }; + } +})(); + + window.JSONEditor = JSONEditor; +})(); + +//# sourceMappingURL=jsoneditor.js.map diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/web/js/lzstring.js b/sandbox/grav/pattern-kit-core/pattern-kit/web/js/lzstring.js new file mode 100644 index 0000000000..8dc9a20ed1 --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/web/js/lzstring.js @@ -0,0 +1,3 @@ + + var LZString={_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",_f:String.fromCharCode,compressToBase64:function(e){if(e==null)return"";var t="";var n,r,i,s,o,u,a;var f=0;e=LZString.compress(e);while(f>8;r=e.charCodeAt(f/2)&255;if(f/2+1>8;else i=NaN}else{n=e.charCodeAt((f-1)/2)&255;if((f+1)/2>8;i=e.charCodeAt((f+1)/2)&255}else r=i=NaN}f+=3;s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+LZString._keyStr.charAt(s)+LZString._keyStr.charAt(o)+LZString._keyStr.charAt(u)+LZString._keyStr.charAt(a)}return t},decompressFromBase64:function(e){if(e==null)return"";var t="",n=0,r,i,s,o,u,a,f,l,c=0,h=LZString._f;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(c>4;s=(a&15)<<4|f>>2;o=(f&3)<<6|l;if(n%2==0){r=i<<8;if(f!=64){t+=h(r|s)}if(l!=64){r=o<<8}}else{t=t+h(r|i);if(f!=64){r=s<<8}if(l!=64){t+=h(r|o)}}n+=3}return LZString.decompress(t)},compressToUTF16:function(e){if(e==null)return"";var t="",n,r,i,s=0,o=LZString._f;e=LZString.compress(e);for(n=0;n>1)+32);i=(r&1)<<14;break;case 1:t+=o(i+(r>>2)+32);i=(r&3)<<13;break;case 2:t+=o(i+(r>>3)+32);i=(r&7)<<12;break;case 3:t+=o(i+(r>>4)+32);i=(r&15)<<11;break;case 4:t+=o(i+(r>>5)+32);i=(r&31)<<10;break;case 5:t+=o(i+(r>>6)+32);i=(r&63)<<9;break;case 6:t+=o(i+(r>>7)+32);i=(r&127)<<8;break;case 7:t+=o(i+(r>>8)+32);i=(r&255)<<7;break;case 8:t+=o(i+(r>>9)+32);i=(r&511)<<6;break;case 9:t+=o(i+(r>>10)+32);i=(r&1023)<<5;break;case 10:t+=o(i+(r>>11)+32);i=(r&2047)<<4;break;case 11:t+=o(i+(r>>12)+32);i=(r&4095)<<3;break;case 12:t+=o(i+(r>>13)+32);i=(r&8191)<<2;break;case 13:t+=o(i+(r>>14)+32);i=(r&16383)<<1;break;case 14:t+=o(i+(r>>15)+32,(r&32767)+32);s=0;break}}return t+o(i+32)},decompressFromUTF16:function(e){if(e==null)return"";var t="",n,r,i=0,s=0,o=LZString._f;while(s>14);n=(r&16383)<<2;break;case 2:t+=o(n|r>>13);n=(r&8191)<<3;break;case 3:t+=o(n|r>>12);n=(r&4095)<<4;break;case 4:t+=o(n|r>>11);n=(r&2047)<<5;break;case 5:t+=o(n|r>>10);n=(r&1023)<<6;break;case 6:t+=o(n|r>>9);n=(r&511)<<7;break;case 7:t+=o(n|r>>8);n=(r&255)<<8;break;case 8:t+=o(n|r>>7);n=(r&127)<<9;break;case 9:t+=o(n|r>>6);n=(r&63)<<10;break;case 10:t+=o(n|r>>5);n=(r&31)<<11;break;case 11:t+=o(n|r>>4);n=(r&15)<<12;break;case 12:t+=o(n|r>>3);n=(r&7)<<13;break;case 13:t+=o(n|r>>2);n=(r&3)<<14;break;case 14:t+=o(n|r>>1);n=(r&1)<<15;break;case 15:t+=o(n|r);i=0;break}s++}return LZString.decompress(t)},compressToUint8Array:function(e){var t=LZString.compress(e);var n=new Uint8Array(t.length*2);for(var r=0,i=t.length;r>>8;n[r*2+1]=s%256}return n},decompressFromUint8Array:function(e){if(e===null||e===undefined){return LZString.decompress(e)}else{var t=new Array(e.length/2);for(var n=0,r=t.length;n>1}}else{n=1;for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}r[o]=f++;u=String(s)}}if(u!==""){if(Object.prototype.hasOwnProperty.call(i,u)){if(u.charCodeAt(0)<256){for(t=0;t>1}}else{n=1;for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}delete i[u]}else{n=r[u];for(t=0;t>1}}a--;if(a==0){a=Math.pow(2,l);l++}}n=2;for(t=0;t>1}while(true){h=h<<1;if(p==15){c+=v(h);break}else p++}return c},decompress:function(e){if(e==null)return"";if(e=="")return null;var t=[],n,r=4,i=4,s=3,o="",u="",a,f,l,c,h,p,d,v=LZString._f,m={string:e,val:e.charCodeAt(0),position:32768,index:1};for(a=0;a<3;a+=1){t[a]=a}l=0;h=Math.pow(2,2);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(n=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}d=v(l);break;case 2:return""}t[3]=d;f=u=d;while(true){if(m.index>m.string.length){return""}l=0;h=Math.pow(2,s);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}switch(d=l){case 0:l=0;h=Math.pow(2,8);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 1:l=0;h=Math.pow(2,16);p=1;while(p!=h){c=m.val&m.position;m.position>>=1;if(m.position==0){m.position=32768;m.val=m.string.charCodeAt(m.index++)}l|=(c>0?1:0)*p;p<<=1}t[i++]=v(l);d=i-1;r--;break;case 2:return u}if(r==0){r=Math.pow(2,s);s++}if(t[d]){o=t[d]}else{if(d===i){o=f+f.charAt(0)}else{return null}}u+=o;t[i++]=f+o.charAt(0);r--;f=o;if(r==0){r=Math.pow(2,s);s++}}}};if(typeof module!=="undefined"&&module!=null){module.exports=LZString} + diff --git a/sandbox/grav/pattern-kit-core/pattern-kit/web/js/schema_editor.js b/sandbox/grav/pattern-kit-core/pattern-kit/web/js/schema_editor.js new file mode 100644 index 0000000000..295e31309a --- /dev/null +++ b/sandbox/grav/pattern-kit-core/pattern-kit/web/js/schema_editor.js @@ -0,0 +1,157 @@ +handleHeights = function () { + var iframes = $("iframe"), + len = iframes.length, + index, + snippet, + overflow, + overflowData, + height; + + for (index = 0; index < len; index++) { + snippet = $(iframes[index]).contents().find("#snippet"); // element for height measurement + overflow = snippet.css("overflow"); + overflowData = snippet.attr("data-default-overflow"); + if (overflowData !== undefined && overflowData !== "") { + overflow = overflowData; + } else { + snippet.attr("data-default-overflow", overflow); //sets default after first check, so temp value does not get picked on resize iterations + } + snippet.css("overflow", "scroll"); // sets temp value for measuring + height = snippet.get(0).offsetHeight; + snippet.css("overflow", overflow); // sets styling value + + $(iframes[index]).height(height); + } +}; + + +var $preview = $(".js-snippet-preview"), + $previewSource = $preview.find("iframe"), + //viewport + $handleLeft = $(".js-snippet-resize-handle-left"), + $handleRight = $(".js-snippet-resize-handle-right"), + $resizeLength = $(".js-resize-length"), + //data + snippetSource = $(".js-snippet-source"); + +(function() { + var windowWidth = $(".left").width(), + width = 1024; + + + if ((width) + 100 > windowWidth) { + width = (windowWidth - 100); + } + $preview.css('width', width); + $resizeLength.css('width', parseInt(width / 2, 10)); +})(); + +interact('.js-resize-length') + .resizable({ + edges: { + left: ".js-snippet-resize-handle-right", + right: ".js-snippet-resize-handle-left", + bottom: false, + top: false + }, + onmove: function (e) { + + var width = e.rect.width, + windowWidth = $(".left").width(); + + if (width < 160) { + width = 160; + } else if ((width * 2) + 100 > windowWidth) { + width = (windowWidth - 100) / 2; + } + + $preview + .find(snippetSource) + .addClass('resize-overlay'); + $preview[0].style.width = (width * 2) + 'px'; + $resizeLength[0].style.width = width + 'px'; + handleHeights(); + }, + onend: function () { + $preview + .find(snippetSource) + .removeClass('resize-overlay'); + handleHeights(); + } + }); + + + + + + + +var editor_update = function(markup, json) { + $("#display_holder").attr('srcdoc', markup); + $("#json_holder pre").text(JSON.stringify(json,null,2)); + $("#twig_holder").text(JSON.stringify(json,null,2)); + updateDirectLink(); + $('#display_holder').load(function(){ + handleHeights(); + }); + +} + + +var updateDirectLink = function() { + var url = window.location.href.replace(/\?.*/,''); + + url += '?data='+LZString.compressToBase64(JSON.stringify(editor.getValue())); + document.getElementById('direct_link').href = url; +}; + +if(window.location.href.match('[?&]data=([^&]+)')) { + try { + data.starting = JSON.parse(LZString.decompressFromBase64(window.location.href.match('[?&]data=([^&]+)')[1])); + } + catch(e) { + console.log('invalid starting data'); + } +} +if (data.starting.name) { + JSONEditor.defaults.options.startval = data.starting; +}; + + // Initialize the editor with a JSON schema + var editor = new JSONEditor(document.getElementById('editor_holder'),{ + schema: data.schema, + theme: 'bootstrap3', + iconlib: 'fontawesome4', + keep_oneof_values: false + }); + + + JSONEditor.plugins.sceditor.emoticonsEnabled = false; + JSONEditor.plugins.ace.theme = 'twilight'; + + + editor.on('change',function() { + var json = editor.getValue(); + $.ajax({ + url: "/api/validate", + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(json,null,2) + }).success(function(response) { + if ( response.trim() === "The supplied JSON validates against the schema." ) { + $('.valid').removeClass('alert-danger').addClass('alert-success'); + } else if ( response.includes( "The supplied JSON validates against the schema." ) ) { + $('.valid').removeClass('alert-danger').addClass('alert-warning'); + } + $('.valid').html(response); + $.ajax({ + url: "/api/render/page", + method: 'POST', + contentType: 'application/json', + data: JSON.stringify(json,null,2) + }).done(function(markup) { + editor_update(markup, json); + }); + + }); + }); diff --git a/sandbox/grav/robots.txt b/sandbox/grav/robots.txt new file mode 100644 index 0000000000..3b558d6356 --- /dev/null +++ b/sandbox/grav/robots.txt @@ -0,0 +1,11 @@ +User-agent: * +Disallow: /backup/ +Disallow: /bin/ +Disallow: /cache/ +Disallow: /grav/ +Disallow: /logs/ +Disallow: /system/ +Disallow: /vendor/ +Disallow: /user/ +Allow: /user/pages/ +Allow: /user/themes/ diff --git a/sandbox/grav/system/assets/debugger.css b/sandbox/grav/system/assets/debugger.css new file mode 100644 index 0000000000..556da6a0a1 --- /dev/null +++ b/sandbox/grav/system/assets/debugger.css @@ -0,0 +1,54 @@ +div.phpdebugbar { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +} + +.phpdebugbar pre { + padding: 1rem; +} + +.phpdebugbar div.phpdebugbar-header > div > * { + padding: 5px 15px; +} + +.phpdebugbar div.phpdebugbar-header > div.phpdebugbar-header-right > * { + padding: 5px 8px; +} + +.phpdebugbar div.phpdebugbar-header, .phpdebugbar a.phpdebugbar-restore-btn { + background-image: url(grav.png); +} + +.phpdebugbar a.phpdebugbar-restore-btn { + width: 13px; +} + +.phpdebugbar a.phpdebugbar-tab.phpdebugbar-active { + background: #3DB9EC; + color: #fff; + margin-top: -1px; + padding-top: 6px; +} + +.phpdebugbar .phpdebugbar-widgets-toolbar { + padding-left: 5px; +} + +.phpdebugbar input[type=text] { + padding: 0; + display: inline; +} + +.phpdebugbar dl.phpdebugbar-widgets-varlist, ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label { + font-family: "DejaVu Sans Mono", Menlo, Monaco, Consolas, Courier, monospace; + font-size: 12px; +} + +ul.phpdebugbar-widgets-timeline li span.phpdebugbar-widgets-label { + text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff; + top: 0; +} + +.phpdebugbar pre, .phpdebugbar code { + margin: 0; + font-size: 14px; +} diff --git a/sandbox/grav/system/assets/grav.png b/sandbox/grav/system/assets/grav.png new file mode 100644 index 0000000000..3379934675 Binary files /dev/null and b/sandbox/grav/system/assets/grav.png differ diff --git a/sandbox/grav/system/assets/jquery/jquery-2.1.4.min.js b/sandbox/grav/system/assets/jquery/jquery-2.1.4.min.js new file mode 100644 index 0000000000..49990d6e14 --- /dev/null +++ b/sandbox/grav/system/assets/jquery/jquery-2.1.4.min.js @@ -0,0 +1,4 @@ +/*! jQuery v2.1.4 | (c) 2005, 2015 jQuery Foundation, Inc. | jquery.org/license */ +!function(a,b){"object"==typeof module&&"object"==typeof module.exports?module.exports=a.document?b(a,!0):function(a){if(!a.document)throw new Error("jQuery requires a window with a document");return b(a)}:b(a)}("undefined"!=typeof window?window:this,function(a,b){var c=[],d=c.slice,e=c.concat,f=c.push,g=c.indexOf,h={},i=h.toString,j=h.hasOwnProperty,k={},l=a.document,m="2.1.4",n=function(a,b){return new n.fn.init(a,b)},o=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,p=/^-ms-/,q=/-([\da-z])/gi,r=function(a,b){return b.toUpperCase()};n.fn=n.prototype={jquery:m,constructor:n,selector:"",length:0,toArray:function(){return d.call(this)},get:function(a){return null!=a?0>a?this[a+this.length]:this[a]:d.call(this)},pushStack:function(a){var b=n.merge(this.constructor(),a);return b.prevObject=this,b.context=this.context,b},each:function(a,b){return n.each(this,a,b)},map:function(a){return this.pushStack(n.map(this,function(b,c){return a.call(b,c,b)}))},slice:function(){return this.pushStack(d.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(a){var b=this.length,c=+a+(0>a?b:0);return this.pushStack(c>=0&&b>c?[this[c]]:[])},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:c.sort,splice:c.splice},n.extend=n.fn.extend=function(){var a,b,c,d,e,f,g=arguments[0]||{},h=1,i=arguments.length,j=!1;for("boolean"==typeof g&&(j=g,g=arguments[h]||{},h++),"object"==typeof g||n.isFunction(g)||(g={}),h===i&&(g=this,h--);i>h;h++)if(null!=(a=arguments[h]))for(b in a)c=g[b],d=a[b],g!==d&&(j&&d&&(n.isPlainObject(d)||(e=n.isArray(d)))?(e?(e=!1,f=c&&n.isArray(c)?c:[]):f=c&&n.isPlainObject(c)?c:{},g[b]=n.extend(j,f,d)):void 0!==d&&(g[b]=d));return g},n.extend({expando:"jQuery"+(m+Math.random()).replace(/\D/g,""),isReady:!0,error:function(a){throw new Error(a)},noop:function(){},isFunction:function(a){return"function"===n.type(a)},isArray:Array.isArray,isWindow:function(a){return null!=a&&a===a.window},isNumeric:function(a){return!n.isArray(a)&&a-parseFloat(a)+1>=0},isPlainObject:function(a){return"object"!==n.type(a)||a.nodeType||n.isWindow(a)?!1:a.constructor&&!j.call(a.constructor.prototype,"isPrototypeOf")?!1:!0},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},type:function(a){return null==a?a+"":"object"==typeof a||"function"==typeof a?h[i.call(a)]||"object":typeof a},globalEval:function(a){var b,c=eval;a=n.trim(a),a&&(1===a.indexOf("use strict")?(b=l.createElement("script"),b.text=a,l.head.appendChild(b).parentNode.removeChild(b)):c(a))},camelCase:function(a){return a.replace(p,"ms-").replace(q,r)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,b,c){var d,e=0,f=a.length,g=s(a);if(c){if(g){for(;f>e;e++)if(d=b.apply(a[e],c),d===!1)break}else for(e in a)if(d=b.apply(a[e],c),d===!1)break}else if(g){for(;f>e;e++)if(d=b.call(a[e],e,a[e]),d===!1)break}else for(e in a)if(d=b.call(a[e],e,a[e]),d===!1)break;return a},trim:function(a){return null==a?"":(a+"").replace(o,"")},makeArray:function(a,b){var c=b||[];return null!=a&&(s(Object(a))?n.merge(c,"string"==typeof a?[a]:a):f.call(c,a)),c},inArray:function(a,b,c){return null==b?-1:g.call(b,a,c)},merge:function(a,b){for(var c=+b.length,d=0,e=a.length;c>d;d++)a[e++]=b[d];return a.length=e,a},grep:function(a,b,c){for(var d,e=[],f=0,g=a.length,h=!c;g>f;f++)d=!b(a[f],f),d!==h&&e.push(a[f]);return e},map:function(a,b,c){var d,f=0,g=a.length,h=s(a),i=[];if(h)for(;g>f;f++)d=b(a[f],f,c),null!=d&&i.push(d);else for(f in a)d=b(a[f],f,c),null!=d&&i.push(d);return e.apply([],i)},guid:1,proxy:function(a,b){var c,e,f;return"string"==typeof b&&(c=a[b],b=a,a=c),n.isFunction(a)?(e=d.call(arguments,2),f=function(){return a.apply(b||this,e.concat(d.call(arguments)))},f.guid=a.guid=a.guid||n.guid++,f):void 0},now:Date.now,support:k}),n.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(a,b){h["[object "+b+"]"]=b.toLowerCase()});function s(a){var b="length"in a&&a.length,c=n.type(a);return"function"===c||n.isWindow(a)?!1:1===a.nodeType&&b?!0:"array"===c||0===b||"number"==typeof b&&b>0&&b-1 in a}var t=function(a){var b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u="sizzle"+1*new Date,v=a.document,w=0,x=0,y=ha(),z=ha(),A=ha(),B=function(a,b){return a===b&&(l=!0),0},C=1<<31,D={}.hasOwnProperty,E=[],F=E.pop,G=E.push,H=E.push,I=E.slice,J=function(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return c;return-1},K="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",L="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",N=M.replace("w","w#"),O="\\["+L+"*("+M+")(?:"+L+"*([*^$|!~]?=)"+L+"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|("+N+"))|)"+L+"*\\]",P=":("+M+")(?:\\((('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|((?:\\\\.|[^\\\\()[\\]]|"+O+")*)|.*)\\)|)",Q=new RegExp(L+"+","g"),R=new RegExp("^"+L+"+|((?:^|[^\\\\])(?:\\\\.)*)"+L+"+$","g"),S=new RegExp("^"+L+"*,"+L+"*"),T=new RegExp("^"+L+"*([>+~]|"+L+")"+L+"*"),U=new RegExp("="+L+"*([^\\]'\"]*?)"+L+"*\\]","g"),V=new RegExp(P),W=new RegExp("^"+N+"$"),X={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+O),PSEUDO:new RegExp("^"+P),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+L+"*(even|odd|(([+-]|)(\\d*)n|)"+L+"*(?:([+-]|)"+L+"*(\\d+)|))"+L+"*\\)|)","i"),bool:new RegExp("^(?:"+K+")$","i"),needsContext:new RegExp("^"+L+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+L+"*((?:-\\d)?\\d*)"+L+"*\\)|)(?=[^-]|$)","i")},Y=/^(?:input|select|textarea|button)$/i,Z=/^h\d$/i,$=/^[^{]+\{\s*\[native \w/,_=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,aa=/[+~]/,ba=/'|\\/g,ca=new RegExp("\\\\([\\da-f]{1,6}"+L+"?|("+L+")|.)","ig"),da=function(a,b,c){var d="0x"+b-65536;return d!==d||c?b:0>d?String.fromCharCode(d+65536):String.fromCharCode(d>>10|55296,1023&d|56320)},ea=function(){m()};try{H.apply(E=I.call(v.childNodes),v.childNodes),E[v.childNodes.length].nodeType}catch(fa){H={apply:E.length?function(a,b){G.apply(a,I.call(b))}:function(a,b){var c=a.length,d=0;while(a[c++]=b[d++]);a.length=c-1}}}function ga(a,b,d,e){var f,h,j,k,l,o,r,s,w,x;if((b?b.ownerDocument||b:v)!==n&&m(b),b=b||n,d=d||[],k=b.nodeType,"string"!=typeof a||!a||1!==k&&9!==k&&11!==k)return d;if(!e&&p){if(11!==k&&(f=_.exec(a)))if(j=f[1]){if(9===k){if(h=b.getElementById(j),!h||!h.parentNode)return d;if(h.id===j)return d.push(h),d}else if(b.ownerDocument&&(h=b.ownerDocument.getElementById(j))&&t(b,h)&&h.id===j)return d.push(h),d}else{if(f[2])return H.apply(d,b.getElementsByTagName(a)),d;if((j=f[3])&&c.getElementsByClassName)return H.apply(d,b.getElementsByClassName(j)),d}if(c.qsa&&(!q||!q.test(a))){if(s=r=u,w=b,x=1!==k&&a,1===k&&"object"!==b.nodeName.toLowerCase()){o=g(a),(r=b.getAttribute("id"))?s=r.replace(ba,"\\$&"):b.setAttribute("id",s),s="[id='"+s+"'] ",l=o.length;while(l--)o[l]=s+ra(o[l]);w=aa.test(a)&&pa(b.parentNode)||b,x=o.join(",")}if(x)try{return H.apply(d,w.querySelectorAll(x)),d}catch(y){}finally{r||b.removeAttribute("id")}}}return i(a.replace(R,"$1"),b,d,e)}function ha(){var a=[];function b(c,e){return a.push(c+" ")>d.cacheLength&&delete b[a.shift()],b[c+" "]=e}return b}function ia(a){return a[u]=!0,a}function ja(a){var b=n.createElement("div");try{return!!a(b)}catch(c){return!1}finally{b.parentNode&&b.parentNode.removeChild(b),b=null}}function ka(a,b){var c=a.split("|"),e=a.length;while(e--)d.attrHandle[c[e]]=b}function la(a,b){var c=b&&a,d=c&&1===a.nodeType&&1===b.nodeType&&(~b.sourceIndex||C)-(~a.sourceIndex||C);if(d)return d;if(c)while(c=c.nextSibling)if(c===b)return-1;return a?1:-1}function ma(a){return function(b){var c=b.nodeName.toLowerCase();return"input"===c&&b.type===a}}function na(a){return function(b){var c=b.nodeName.toLowerCase();return("input"===c||"button"===c)&&b.type===a}}function oa(a){return ia(function(b){return b=+b,ia(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function pa(a){return a&&"undefined"!=typeof a.getElementsByTagName&&a}c=ga.support={},f=ga.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?"HTML"!==b.nodeName:!1},m=ga.setDocument=function(a){var b,e,g=a?a.ownerDocument||a:v;return g!==n&&9===g.nodeType&&g.documentElement?(n=g,o=g.documentElement,e=g.defaultView,e&&e!==e.top&&(e.addEventListener?e.addEventListener("unload",ea,!1):e.attachEvent&&e.attachEvent("onunload",ea)),p=!f(g),c.attributes=ja(function(a){return a.className="i",!a.getAttribute("className")}),c.getElementsByTagName=ja(function(a){return a.appendChild(g.createComment("")),!a.getElementsByTagName("*").length}),c.getElementsByClassName=$.test(g.getElementsByClassName),c.getById=ja(function(a){return o.appendChild(a).id=u,!g.getElementsByName||!g.getElementsByName(u).length}),c.getById?(d.find.ID=function(a,b){if("undefined"!=typeof b.getElementById&&p){var c=b.getElementById(a);return c&&c.parentNode?[c]:[]}},d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){return a.getAttribute("id")===b}}):(delete d.find.ID,d.filter.ID=function(a){var b=a.replace(ca,da);return function(a){var c="undefined"!=typeof a.getAttributeNode&&a.getAttributeNode("id");return c&&c.value===b}}),d.find.TAG=c.getElementsByTagName?function(a,b){return"undefined"!=typeof b.getElementsByTagName?b.getElementsByTagName(a):c.qsa?b.querySelectorAll(a):void 0}:function(a,b){var c,d=[],e=0,f=b.getElementsByTagName(a);if("*"===a){while(c=f[e++])1===c.nodeType&&d.push(c);return d}return f},d.find.CLASS=c.getElementsByClassName&&function(a,b){return p?b.getElementsByClassName(a):void 0},r=[],q=[],(c.qsa=$.test(g.querySelectorAll))&&(ja(function(a){o.appendChild(a).innerHTML="",a.querySelectorAll("[msallowcapture^='']").length&&q.push("[*^$]="+L+"*(?:''|\"\")"),a.querySelectorAll("[selected]").length||q.push("\\["+L+"*(?:value|"+K+")"),a.querySelectorAll("[id~="+u+"-]").length||q.push("~="),a.querySelectorAll(":checked").length||q.push(":checked"),a.querySelectorAll("a#"+u+"+*").length||q.push(".#.+[+~]")}),ja(function(a){var b=g.createElement("input");b.setAttribute("type","hidden"),a.appendChild(b).setAttribute("name","D"),a.querySelectorAll("[name=d]").length&&q.push("name"+L+"*[*^$|!~]?="),a.querySelectorAll(":enabled").length||q.push(":enabled",":disabled"),a.querySelectorAll("*,:x"),q.push(",.*:")})),(c.matchesSelector=$.test(s=o.matches||o.webkitMatchesSelector||o.mozMatchesSelector||o.oMatchesSelector||o.msMatchesSelector))&&ja(function(a){c.disconnectedMatch=s.call(a,"div"),s.call(a,"[s!='']:x"),r.push("!=",P)}),q=q.length&&new RegExp(q.join("|")),r=r.length&&new RegExp(r.join("|")),b=$.test(o.compareDocumentPosition),t=b||$.test(o.contains)?function(a,b){var c=9===a.nodeType?a.documentElement:a,d=b&&b.parentNode;return a===d||!(!d||1!==d.nodeType||!(c.contains?c.contains(d):a.compareDocumentPosition&&16&a.compareDocumentPosition(d)))}:function(a,b){if(b)while(b=b.parentNode)if(b===a)return!0;return!1},B=b?function(a,b){if(a===b)return l=!0,0;var d=!a.compareDocumentPosition-!b.compareDocumentPosition;return d?d:(d=(a.ownerDocument||a)===(b.ownerDocument||b)?a.compareDocumentPosition(b):1,1&d||!c.sortDetached&&b.compareDocumentPosition(a)===d?a===g||a.ownerDocument===v&&t(v,a)?-1:b===g||b.ownerDocument===v&&t(v,b)?1:k?J(k,a)-J(k,b):0:4&d?-1:1)}:function(a,b){if(a===b)return l=!0,0;var c,d=0,e=a.parentNode,f=b.parentNode,h=[a],i=[b];if(!e||!f)return a===g?-1:b===g?1:e?-1:f?1:k?J(k,a)-J(k,b):0;if(e===f)return la(a,b);c=a;while(c=c.parentNode)h.unshift(c);c=b;while(c=c.parentNode)i.unshift(c);while(h[d]===i[d])d++;return d?la(h[d],i[d]):h[d]===v?-1:i[d]===v?1:0},g):n},ga.matches=function(a,b){return ga(a,null,null,b)},ga.matchesSelector=function(a,b){if((a.ownerDocument||a)!==n&&m(a),b=b.replace(U,"='$1']"),!(!c.matchesSelector||!p||r&&r.test(b)||q&&q.test(b)))try{var d=s.call(a,b);if(d||c.disconnectedMatch||a.document&&11!==a.document.nodeType)return d}catch(e){}return ga(b,n,null,[a]).length>0},ga.contains=function(a,b){return(a.ownerDocument||a)!==n&&m(a),t(a,b)},ga.attr=function(a,b){(a.ownerDocument||a)!==n&&m(a);var e=d.attrHandle[b.toLowerCase()],f=e&&D.call(d.attrHandle,b.toLowerCase())?e(a,b,!p):void 0;return void 0!==f?f:c.attributes||!p?a.getAttribute(b):(f=a.getAttributeNode(b))&&f.specified?f.value:null},ga.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},ga.uniqueSort=function(a){var b,d=[],e=0,f=0;if(l=!c.detectDuplicates,k=!c.sortStable&&a.slice(0),a.sort(B),l){while(b=a[f++])b===a[f]&&(e=d.push(f));while(e--)a.splice(d[e],1)}return k=null,a},e=ga.getText=function(a){var b,c="",d=0,f=a.nodeType;if(f){if(1===f||9===f||11===f){if("string"==typeof a.textContent)return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=e(a)}else if(3===f||4===f)return a.nodeValue}else while(b=a[d++])c+=e(b);return c},d=ga.selectors={cacheLength:50,createPseudo:ia,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(ca,da),a[3]=(a[3]||a[4]||a[5]||"").replace(ca,da),"~="===a[2]&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),"nth"===a[1].slice(0,3)?(a[3]||ga.error(a[0]),a[4]=+(a[4]?a[5]+(a[6]||1):2*("even"===a[3]||"odd"===a[3])),a[5]=+(a[7]+a[8]||"odd"===a[3])):a[3]&&ga.error(a[0]),a},PSEUDO:function(a){var b,c=!a[6]&&a[2];return X.CHILD.test(a[0])?null:(a[3]?a[2]=a[4]||a[5]||"":c&&V.test(c)&&(b=g(c,!0))&&(b=c.indexOf(")",c.length-b)-c.length)&&(a[0]=a[0].slice(0,b),a[2]=c.slice(0,b)),a.slice(0,3))}},filter:{TAG:function(a){var b=a.replace(ca,da).toLowerCase();return"*"===a?function(){return!0}:function(a){return a.nodeName&&a.nodeName.toLowerCase()===b}},CLASS:function(a){var b=y[a+" "];return b||(b=new RegExp("(^|"+L+")"+a+"("+L+"|$)"))&&y(a,function(a){return b.test("string"==typeof a.className&&a.className||"undefined"!=typeof a.getAttribute&&a.getAttribute("class")||"")})},ATTR:function(a,b,c){return function(d){var e=ga.attr(d,a);return null==e?"!="===b:b?(e+="","="===b?e===c:"!="===b?e!==c:"^="===b?c&&0===e.indexOf(c):"*="===b?c&&e.indexOf(c)>-1:"$="===b?c&&e.slice(-c.length)===c:"~="===b?(" "+e.replace(Q," ")+" ").indexOf(c)>-1:"|="===b?e===c||e.slice(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d,e){var f="nth"!==a.slice(0,3),g="last"!==a.slice(-4),h="of-type"===b;return 1===d&&0===e?function(a){return!!a.parentNode}:function(b,c,i){var j,k,l,m,n,o,p=f!==g?"nextSibling":"previousSibling",q=b.parentNode,r=h&&b.nodeName.toLowerCase(),s=!i&&!h;if(q){if(f){while(p){l=b;while(l=l[p])if(h?l.nodeName.toLowerCase()===r:1===l.nodeType)return!1;o=p="only"===a&&!o&&"nextSibling"}return!0}if(o=[g?q.firstChild:q.lastChild],g&&s){k=q[u]||(q[u]={}),j=k[a]||[],n=j[0]===w&&j[1],m=j[0]===w&&j[2],l=n&&q.childNodes[n];while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if(1===l.nodeType&&++m&&l===b){k[a]=[w,n,m];break}}else if(s&&(j=(b[u]||(b[u]={}))[a])&&j[0]===w)m=j[1];else while(l=++n&&l&&l[p]||(m=n=0)||o.pop())if((h?l.nodeName.toLowerCase()===r:1===l.nodeType)&&++m&&(s&&((l[u]||(l[u]={}))[a]=[w,m]),l===b))break;return m-=e,m===d||m%d===0&&m/d>=0}}},PSEUDO:function(a,b){var c,e=d.pseudos[a]||d.setFilters[a.toLowerCase()]||ga.error("unsupported pseudo: "+a);return e[u]?e(b):e.length>1?(c=[a,a,"",b],d.setFilters.hasOwnProperty(a.toLowerCase())?ia(function(a,c){var d,f=e(a,b),g=f.length;while(g--)d=J(a,f[g]),a[d]=!(c[d]=f[g])}):function(a){return e(a,0,c)}):e}},pseudos:{not:ia(function(a){var b=[],c=[],d=h(a.replace(R,"$1"));return d[u]?ia(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)(f=g[h])&&(a[h]=!(b[h]=f))}):function(a,e,f){return b[0]=a,d(b,null,f,c),b[0]=null,!c.pop()}}),has:ia(function(a){return function(b){return ga(a,b).length>0}}),contains:ia(function(a){return a=a.replace(ca,da),function(b){return(b.textContent||b.innerText||e(b)).indexOf(a)>-1}}),lang:ia(function(a){return W.test(a||"")||ga.error("unsupported lang: "+a),a=a.replace(ca,da).toLowerCase(),function(b){var c;do if(c=p?b.lang:b.getAttribute("xml:lang")||b.getAttribute("lang"))return c=c.toLowerCase(),c===a||0===c.indexOf(a+"-");while((b=b.parentNode)&&1===b.nodeType);return!1}}),target:function(b){var c=a.location&&a.location.hash;return c&&c.slice(1)===b.id},root:function(a){return a===o},focus:function(a){return a===n.activeElement&&(!n.hasFocus||n.hasFocus())&&!!(a.type||a.href||~a.tabIndex)},enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&!!a.checked||"option"===b&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},empty:function(a){for(a=a.firstChild;a;a=a.nextSibling)if(a.nodeType<6)return!1;return!0},parent:function(a){return!d.pseudos.empty(a)},header:function(a){return Z.test(a.nodeName)},input:function(a){return Y.test(a.nodeName)},button:function(a){var b=a.nodeName.toLowerCase();return"input"===b&&"button"===a.type||"button"===b},text:function(a){var b;return"input"===a.nodeName.toLowerCase()&&"text"===a.type&&(null==(b=a.getAttribute("type"))||"text"===b.toLowerCase())},first:oa(function(){return[0]}),last:oa(function(a,b){return[b-1]}),eq:oa(function(a,b,c){return[0>c?c+b:c]}),even:oa(function(a,b){for(var c=0;b>c;c+=2)a.push(c);return a}),odd:oa(function(a,b){for(var c=1;b>c;c+=2)a.push(c);return a}),lt:oa(function(a,b,c){for(var d=0>c?c+b:c;--d>=0;)a.push(d);return a}),gt:oa(function(a,b,c){for(var d=0>c?c+b:c;++db;b++)d+=a[b].value;return d}function sa(a,b,c){var d=b.dir,e=c&&"parentNode"===d,f=x++;return b.first?function(b,c,f){while(b=b[d])if(1===b.nodeType||e)return a(b,c,f)}:function(b,c,g){var h,i,j=[w,f];if(g){while(b=b[d])if((1===b.nodeType||e)&&a(b,c,g))return!0}else while(b=b[d])if(1===b.nodeType||e){if(i=b[u]||(b[u]={}),(h=i[d])&&h[0]===w&&h[1]===f)return j[2]=h[2];if(i[d]=j,j[2]=a(b,c,g))return!0}}}function ta(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function ua(a,b,c){for(var d=0,e=b.length;e>d;d++)ga(a,b[d],c);return c}function va(a,b,c,d,e){for(var f,g=[],h=0,i=a.length,j=null!=b;i>h;h++)(f=a[h])&&(!c||c(f,d,e))&&(g.push(f),j&&b.push(h));return g}function wa(a,b,c,d,e,f){return d&&!d[u]&&(d=wa(d)),e&&!e[u]&&(e=wa(e,f)),ia(function(f,g,h,i){var j,k,l,m=[],n=[],o=g.length,p=f||ua(b||"*",h.nodeType?[h]:h,[]),q=!a||!f&&b?p:va(p,m,a,h,i),r=c?e||(f?a:o||d)?[]:g:q;if(c&&c(q,r,h,i),d){j=va(r,n),d(j,[],h,i),k=j.length;while(k--)(l=j[k])&&(r[n[k]]=!(q[n[k]]=l))}if(f){if(e||a){if(e){j=[],k=r.length;while(k--)(l=r[k])&&j.push(q[k]=l);e(null,r=[],j,i)}k=r.length;while(k--)(l=r[k])&&(j=e?J(f,l):m[k])>-1&&(f[j]=!(g[j]=l))}}else r=va(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):H.apply(g,r)})}function xa(a){for(var b,c,e,f=a.length,g=d.relative[a[0].type],h=g||d.relative[" "],i=g?1:0,k=sa(function(a){return a===b},h,!0),l=sa(function(a){return J(b,a)>-1},h,!0),m=[function(a,c,d){var e=!g&&(d||c!==j)||((b=c).nodeType?k(a,c,d):l(a,c,d));return b=null,e}];f>i;i++)if(c=d.relative[a[i].type])m=[sa(ta(m),c)];else{if(c=d.filter[a[i].type].apply(null,a[i].matches),c[u]){for(e=++i;f>e;e++)if(d.relative[a[e].type])break;return wa(i>1&&ta(m),i>1&&ra(a.slice(0,i-1).concat({value:" "===a[i-2].type?"*":""})).replace(R,"$1"),c,e>i&&xa(a.slice(i,e)),f>e&&xa(a=a.slice(e)),f>e&&ra(a))}m.push(c)}return ta(m)}function ya(a,b){var c=b.length>0,e=a.length>0,f=function(f,g,h,i,k){var l,m,o,p=0,q="0",r=f&&[],s=[],t=j,u=f||e&&d.find.TAG("*",k),v=w+=null==t?1:Math.random()||.1,x=u.length;for(k&&(j=g!==n&&g);q!==x&&null!=(l=u[q]);q++){if(e&&l){m=0;while(o=a[m++])if(o(l,g,h)){i.push(l);break}k&&(w=v)}c&&((l=!o&&l)&&p--,f&&r.push(l))}if(p+=q,c&&q!==p){m=0;while(o=b[m++])o(r,s,g,h);if(f){if(p>0)while(q--)r[q]||s[q]||(s[q]=F.call(i));s=va(s)}H.apply(i,s),k&&!f&&s.length>0&&p+b.length>1&&ga.uniqueSort(i)}return k&&(w=v,j=t),r};return c?ia(f):f}return h=ga.compile=function(a,b){var c,d=[],e=[],f=A[a+" "];if(!f){b||(b=g(a)),c=b.length;while(c--)f=xa(b[c]),f[u]?d.push(f):e.push(f);f=A(a,ya(e,d)),f.selector=a}return f},i=ga.select=function(a,b,e,f){var i,j,k,l,m,n="function"==typeof a&&a,o=!f&&g(a=n.selector||a);if(e=e||[],1===o.length){if(j=o[0]=o[0].slice(0),j.length>2&&"ID"===(k=j[0]).type&&c.getById&&9===b.nodeType&&p&&d.relative[j[1].type]){if(b=(d.find.ID(k.matches[0].replace(ca,da),b)||[])[0],!b)return e;n&&(b=b.parentNode),a=a.slice(j.shift().value.length)}i=X.needsContext.test(a)?0:j.length;while(i--){if(k=j[i],d.relative[l=k.type])break;if((m=d.find[l])&&(f=m(k.matches[0].replace(ca,da),aa.test(j[0].type)&&pa(b.parentNode)||b))){if(j.splice(i,1),a=f.length&&ra(j),!a)return H.apply(e,f),e;break}}}return(n||h(a,o))(f,b,!p,e,aa.test(a)&&pa(b.parentNode)||b),e},c.sortStable=u.split("").sort(B).join("")===u,c.detectDuplicates=!!l,m(),c.sortDetached=ja(function(a){return 1&a.compareDocumentPosition(n.createElement("div"))}),ja(function(a){return a.innerHTML="","#"===a.firstChild.getAttribute("href")})||ka("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ja(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||ka("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ja(function(a){return null==a.getAttribute("disabled")})||ka(K,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),ga}(a);n.find=t,n.expr=t.selectors,n.expr[":"]=n.expr.pseudos,n.unique=t.uniqueSort,n.text=t.getText,n.isXMLDoc=t.isXML,n.contains=t.contains;var u=n.expr.match.needsContext,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^.[^:#\[\.,]*$/;function x(a,b,c){if(n.isFunction(b))return n.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return n.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(w.test(b))return n.filter(b,a,c);b=n.filter(b,a)}return n.grep(a,function(a){return g.call(b,a)>=0!==c})}n.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?n.find.matchesSelector(d,a)?[d]:[]:n.find.matches(a,n.grep(b,function(a){return 1===a.nodeType}))},n.fn.extend({find:function(a){var b,c=this.length,d=[],e=this;if("string"!=typeof a)return this.pushStack(n(a).filter(function(){for(b=0;c>b;b++)if(n.contains(e[b],this))return!0}));for(b=0;c>b;b++)n.find(a,e[b],d);return d=this.pushStack(c>1?n.unique(d):d),d.selector=this.selector?this.selector+" "+a:a,d},filter:function(a){return this.pushStack(x(this,a||[],!1))},not:function(a){return this.pushStack(x(this,a||[],!0))},is:function(a){return!!x(this,"string"==typeof a&&u.test(a)?n(a):a||[],!1).length}});var y,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=n.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a[0]&&">"===a[a.length-1]&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||y).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof n?b[0]:b,n.merge(this,n.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:l,!0)),v.test(c[1])&&n.isPlainObject(b))for(c in b)n.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}return d=l.getElementById(c[2]),d&&d.parentNode&&(this.length=1,this[0]=d),this.context=l,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):n.isFunction(a)?"undefined"!=typeof y.ready?y.ready(a):a(n):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),n.makeArray(a,this))};A.prototype=n.fn,y=n(l);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};n.extend({dir:function(a,b,c){var d=[],e=void 0!==c;while((a=a[b])&&9!==a.nodeType)if(1===a.nodeType){if(e&&n(a).is(c))break;d.push(a)}return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),n.fn.extend({has:function(a){var b=n(a,this),c=b.length;return this.filter(function(){for(var a=0;c>a;a++)if(n.contains(this,b[a]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=u.test(a)||"string"!=typeof a?n(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&n.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?n.unique(f):f)},index:function(a){return a?"string"==typeof a?g.call(n(a),this[0]):g.call(this,a.jquery?a[0]:a):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(n.unique(n.merge(this.get(),n(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){while((a=a[b])&&1!==a.nodeType);return a}n.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return n.dir(a,"parentNode")},parentsUntil:function(a,b,c){return n.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return n.dir(a,"nextSibling")},prevAll:function(a){return n.dir(a,"previousSibling")},nextUntil:function(a,b,c){return n.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return n.dir(a,"previousSibling",c)},siblings:function(a){return n.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return n.sibling(a.firstChild)},contents:function(a){return a.contentDocument||n.merge([],a.childNodes)}},function(a,b){n.fn[a]=function(c,d){var e=n.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=n.filter(d,e)),this.length>1&&(C[a]||n.unique(e),B.test(a)&&e.reverse()),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return n.each(a.match(E)||[],function(a,c){b[c]=!0}),b}n.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):n.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(b=a.memory&&l,c=!0,g=e||0,e=0,f=h.length,d=!0;h&&f>g;g++)if(h[g].apply(l[0],l[1])===!1&&a.stopOnFalse){b=!1;break}d=!1,h&&(i?i.length&&j(i.shift()):b?h=[]:k.disable())},k={add:function(){if(h){var c=h.length;!function g(b){n.each(b,function(b,c){var d=n.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&g(c)})}(arguments),d?f=h.length:b&&(e=c,j(b))}return this},remove:function(){return h&&n.each(arguments,function(a,b){var c;while((c=n.inArray(b,h,c))>-1)h.splice(c,1),d&&(f>=c&&f--,g>=c&&g--)}),this},has:function(a){return a?n.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],f=0,this},disable:function(){return h=i=b=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,b||k.disable(),this},locked:function(){return!i},fireWith:function(a,b){return!h||c&&!i||(b=b||[],b=[a,b.slice?b.slice():b],d?i.push(b):j(b)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!c}};return k},n.extend({Deferred:function(a){var b=[["resolve","done",n.Callbacks("once memory"),"resolved"],["reject","fail",n.Callbacks("once memory"),"rejected"],["notify","progress",n.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return n.Deferred(function(c){n.each(b,function(b,f){var g=n.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&n.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?n.extend(a,d):d}},e={};return d.pipe=d.then,n.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&n.isFunction(a.promise)?e:0,g=1===f?a:n.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&n.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;n.fn.ready=function(a){return n.ready.promise().done(a),this},n.extend({isReady:!1,readyWait:1,holdReady:function(a){a?n.readyWait++:n.ready(!0)},ready:function(a){(a===!0?--n.readyWait:n.isReady)||(n.isReady=!0,a!==!0&&--n.readyWait>0||(H.resolveWith(l,[n]),n.fn.triggerHandler&&(n(l).triggerHandler("ready"),n(l).off("ready"))))}});function I(){l.removeEventListener("DOMContentLoaded",I,!1),a.removeEventListener("load",I,!1),n.ready()}n.ready.promise=function(b){return H||(H=n.Deferred(),"complete"===l.readyState?setTimeout(n.ready):(l.addEventListener("DOMContentLoaded",I,!1),a.addEventListener("load",I,!1))),H.promise(b)},n.ready.promise();var J=n.access=function(a,b,c,d,e,f,g){var h=0,i=a.length,j=null==c;if("object"===n.type(c)){e=!0;for(h in c)n.access(a,b,h,c[h],!0,f,g)}else if(void 0!==d&&(e=!0,n.isFunction(d)||(g=!0),j&&(g?(b.call(a,d),b=null):(j=b,b=function(a,b,c){return j.call(n(a),c)})),b))for(;i>h;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f};n.acceptData=function(a){return 1===a.nodeType||9===a.nodeType||!+a.nodeType};function K(){Object.defineProperty(this.cache={},0,{get:function(){return{}}}),this.expando=n.expando+K.uid++}K.uid=1,K.accepts=n.acceptData,K.prototype={key:function(a){if(!K.accepts(a))return 0;var b={},c=a[this.expando];if(!c){c=K.uid++;try{b[this.expando]={value:c},Object.defineProperties(a,b)}catch(d){b[this.expando]=c,n.extend(a,b)}}return this.cache[c]||(this.cache[c]={}),c},set:function(a,b,c){var d,e=this.key(a),f=this.cache[e];if("string"==typeof b)f[b]=c;else if(n.isEmptyObject(f))n.extend(this.cache[e],b);else for(d in b)f[d]=b[d];return f},get:function(a,b){var c=this.cache[this.key(a)];return void 0===b?c:c[b]},access:function(a,b,c){var d;return void 0===b||b&&"string"==typeof b&&void 0===c?(d=this.get(a,b),void 0!==d?d:this.get(a,n.camelCase(b))):(this.set(a,b,c),void 0!==c?c:b)},remove:function(a,b){var c,d,e,f=this.key(a),g=this.cache[f];if(void 0===b)this.cache[f]={};else{n.isArray(b)?d=b.concat(b.map(n.camelCase)):(e=n.camelCase(b),b in g?d=[b,e]:(d=e,d=d in g?[d]:d.match(E)||[])),c=d.length;while(c--)delete g[d[c]]}},hasData:function(a){return!n.isEmptyObject(this.cache[a[this.expando]]||{})},discard:function(a){a[this.expando]&&delete this.cache[a[this.expando]]}};var L=new K,M=new K,N=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,O=/([A-Z])/g;function P(a,b,c){var d;if(void 0===c&&1===a.nodeType)if(d="data-"+b.replace(O,"-$1").toLowerCase(),c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:N.test(c)?n.parseJSON(c):c}catch(e){}M.set(a,b,c)}else c=void 0;return c}n.extend({hasData:function(a){return M.hasData(a)||L.hasData(a)},data:function(a,b,c){ +return M.access(a,b,c)},removeData:function(a,b){M.remove(a,b)},_data:function(a,b,c){return L.access(a,b,c)},_removeData:function(a,b){L.remove(a,b)}}),n.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=M.get(f),1===f.nodeType&&!L.get(f,"hasDataAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=n.camelCase(d.slice(5)),P(f,d,e[d])));L.set(f,"hasDataAttrs",!0)}return e}return"object"==typeof a?this.each(function(){M.set(this,a)}):J(this,function(b){var c,d=n.camelCase(a);if(f&&void 0===b){if(c=M.get(f,a),void 0!==c)return c;if(c=M.get(f,d),void 0!==c)return c;if(c=P(f,d,void 0),void 0!==c)return c}else this.each(function(){var c=M.get(this,d);M.set(this,d,b),-1!==a.indexOf("-")&&void 0!==c&&M.set(this,a,b)})},null,b,arguments.length>1,null,!0)},removeData:function(a){return this.each(function(){M.remove(this,a)})}}),n.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=L.get(a,b),c&&(!d||n.isArray(c)?d=L.access(a,b,n.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=n.queue(a,b),d=c.length,e=c.shift(),f=n._queueHooks(a,b),g=function(){n.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return L.get(a,c)||L.access(a,c,{empty:n.Callbacks("once memory").add(function(){L.remove(a,[b+"queue",c])})})}}),n.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthx",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue}();var U="undefined";k.focusinBubbles="onfocusin"in a;var V=/^key/,W=/^(?:mouse|pointer|contextmenu)|click/,X=/^(?:focusinfocus|focusoutblur)$/,Y=/^([^.]*)(?:\.(.+)|)$/;function Z(){return!0}function $(){return!1}function _(){try{return l.activeElement}catch(a){}}n.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.get(a);if(r){c.handler&&(f=c,c=f.handler,e=f.selector),c.guid||(c.guid=n.guid++),(i=r.events)||(i=r.events={}),(g=r.handle)||(g=r.handle=function(b){return typeof n!==U&&n.event.triggered!==b.type?n.event.dispatch.apply(a,arguments):void 0}),b=(b||"").match(E)||[""],j=b.length;while(j--)h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o&&(l=n.event.special[o]||{},o=(e?l.delegateType:l.bindType)||o,l=n.event.special[o]||{},k=n.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&n.expr.match.needsContext.test(e),namespace:p.join(".")},f),(m=i[o])||(m=i[o]=[],m.delegateCount=0,l.setup&&l.setup.call(a,d,p,g)!==!1||a.addEventListener&&a.addEventListener(o,g,!1)),l.add&&(l.add.call(a,k),k.handler.guid||(k.handler.guid=c.guid)),e?m.splice(m.delegateCount++,0,k):m.push(k),n.event.global[o]=!0)}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,o,p,q,r=L.hasData(a)&&L.get(a);if(r&&(i=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=Y.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=n.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,m=i[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),g=f=m.length;while(f--)k=m[f],!e&&q!==k.origType||c&&c.guid!==k.guid||h&&!h.test(k.namespace)||d&&d!==k.selector&&("**"!==d||!k.selector)||(m.splice(f,1),k.selector&&m.delegateCount--,l.remove&&l.remove.call(a,k));g&&!m.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||n.removeEvent(a,o,r.handle),delete i[o])}else for(o in i)n.event.remove(a,o+b[j],c,d,!0);n.isEmptyObject(i)&&(delete r.handle,L.remove(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,m,o,p=[d||l],q=j.call(b,"type")?b.type:b,r=j.call(b,"namespace")?b.namespace.split("."):[];if(g=h=d=d||l,3!==d.nodeType&&8!==d.nodeType&&!X.test(q+n.event.triggered)&&(q.indexOf(".")>=0&&(r=q.split("."),q=r.shift(),r.sort()),k=q.indexOf(":")<0&&"on"+q,b=b[n.expando]?b:new n.Event(q,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=r.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+r.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:n.makeArray(c,[b]),o=n.event.special[q]||{},e||!o.trigger||o.trigger.apply(d,c)!==!1)){if(!e&&!o.noBubble&&!n.isWindow(d)){for(i=o.delegateType||q,X.test(i+q)||(g=g.parentNode);g;g=g.parentNode)p.push(g),h=g;h===(d.ownerDocument||l)&&p.push(h.defaultView||h.parentWindow||a)}f=0;while((g=p[f++])&&!b.isPropagationStopped())b.type=f>1?i:o.bindType||q,m=(L.get(g,"events")||{})[b.type]&&L.get(g,"handle"),m&&m.apply(g,c),m=k&&g[k],m&&m.apply&&n.acceptData(g)&&(b.result=m.apply(g,c),b.result===!1&&b.preventDefault());return b.type=q,e||b.isDefaultPrevented()||o._default&&o._default.apply(p.pop(),c)!==!1||!n.acceptData(d)||k&&n.isFunction(d[q])&&!n.isWindow(d)&&(h=d[k],h&&(d[k]=null),n.event.triggered=q,d[q](),n.event.triggered=void 0,h&&(d[k]=h)),b.result}},dispatch:function(a){a=n.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(L.get(this,"events")||{})[a.type]||[],k=n.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=n.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,c=0;while((g=f.handlers[c++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(g.namespace))&&(a.handleObj=g,a.data=g.data,e=((n.event.special[g.origType]||{}).handle||g.handler).apply(f.elem,i),void 0!==e&&(a.result=e)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!==this;i=i.parentNode||this)if(i.disabled!==!0||"click"!==a.type){for(d=[],c=0;h>c;c++)f=b[c],e=f.selector+" ",void 0===d[e]&&(d[e]=f.needsContext?n(e,this).index(i)>=0:n.find(e,this,null,[i]).length),d[e]&&d.push(f);d.length&&g.push({elem:i,handlers:d})}return h]*)\/>/gi,ba=/<([\w:]+)/,ca=/<|&#?\w+;/,da=/<(?:script|style|link)/i,ea=/checked\s*(?:[^=]|=\s*.checked.)/i,fa=/^$|\/(?:java|ecma)script/i,ga=/^true\/(.*)/,ha=/^\s*\s*$/g,ia={option:[1,""],thead:[1,"","
    "],col:[2,"","
    "],tr:[2,"","
    "],td:[3,"","
    "],_default:[0,"",""]};ia.optgroup=ia.option,ia.tbody=ia.tfoot=ia.colgroup=ia.caption=ia.thead,ia.th=ia.td;function ja(a,b){return n.nodeName(a,"table")&&n.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function ka(a){return a.type=(null!==a.getAttribute("type"))+"/"+a.type,a}function la(a){var b=ga.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function ma(a,b){for(var c=0,d=a.length;d>c;c++)L.set(a[c],"globalEval",!b||L.get(b[c],"globalEval"))}function na(a,b){var c,d,e,f,g,h,i,j;if(1===b.nodeType){if(L.hasData(a)&&(f=L.access(a),g=L.set(b,f),j=f.events)){delete g.handle,g.events={};for(e in j)for(c=0,d=j[e].length;d>c;c++)n.event.add(b,e,j[e][c])}M.hasData(a)&&(h=M.access(a),i=n.extend({},h),M.set(b,i))}}function oa(a,b){var c=a.getElementsByTagName?a.getElementsByTagName(b||"*"):a.querySelectorAll?a.querySelectorAll(b||"*"):[];return void 0===b||b&&n.nodeName(a,b)?n.merge([a],c):c}function pa(a,b){var c=b.nodeName.toLowerCase();"input"===c&&T.test(a.type)?b.checked=a.checked:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}n.extend({clone:function(a,b,c){var d,e,f,g,h=a.cloneNode(!0),i=n.contains(a.ownerDocument,a);if(!(k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||n.isXMLDoc(a)))for(g=oa(h),f=oa(a),d=0,e=f.length;e>d;d++)pa(f[d],g[d]);if(b)if(c)for(f=f||oa(a),g=g||oa(h),d=0,e=f.length;e>d;d++)na(f[d],g[d]);else na(a,h);return g=oa(h,"script"),g.length>0&&ma(g,!i&&oa(a,"script")),h},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,k=b.createDocumentFragment(),l=[],m=0,o=a.length;o>m;m++)if(e=a[m],e||0===e)if("object"===n.type(e))n.merge(l,e.nodeType?[e]:e);else if(ca.test(e)){f=f||k.appendChild(b.createElement("div")),g=(ba.exec(e)||["",""])[1].toLowerCase(),h=ia[g]||ia._default,f.innerHTML=h[1]+e.replace(aa,"<$1>")+h[2],j=h[0];while(j--)f=f.lastChild;n.merge(l,f.childNodes),f=k.firstChild,f.textContent=""}else l.push(b.createTextNode(e));k.textContent="",m=0;while(e=l[m++])if((!d||-1===n.inArray(e,d))&&(i=n.contains(e.ownerDocument,e),f=oa(k.appendChild(e),"script"),i&&ma(f),c)){j=0;while(e=f[j++])fa.test(e.type||"")&&c.push(e)}return k},cleanData:function(a){for(var b,c,d,e,f=n.event.special,g=0;void 0!==(c=a[g]);g++){if(n.acceptData(c)&&(e=c[L.expando],e&&(b=L.cache[e]))){if(b.events)for(d in b.events)f[d]?n.event.remove(c,d):n.removeEvent(c,d,b.handle);L.cache[e]&&delete L.cache[e]}delete M.cache[c[M.expando]]}}}),n.fn.extend({text:function(a){return J(this,function(a){return void 0===a?n.text(this):this.empty().each(function(){(1===this.nodeType||11===this.nodeType||9===this.nodeType)&&(this.textContent=a)})},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=ja(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?n.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||n.cleanData(oa(c)),c.parentNode&&(b&&n.contains(c.ownerDocument,c)&&ma(oa(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++)1===a.nodeType&&(n.cleanData(oa(a,!1)),a.textContent="");return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return n.clone(this,a,b)})},html:function(a){return J(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a&&1===b.nodeType)return b.innerHTML;if("string"==typeof a&&!da.test(a)&&!ia[(ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(aa,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(n.cleanData(oa(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,n.cleanData(oa(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,m=this,o=l-1,p=a[0],q=n.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&ea.test(p))return this.each(function(c){var d=m.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(c=n.buildFragment(a,this[0].ownerDocument,!1,this),d=c.firstChild,1===c.childNodes.length&&(c=d),d)){for(f=n.map(oa(c,"script"),ka),g=f.length;l>j;j++)h=c,j!==o&&(h=n.clone(h,!0,!0),g&&n.merge(f,oa(h,"script"))),b.call(this[j],h,j);if(g)for(i=f[f.length-1].ownerDocument,n.map(f,la),j=0;g>j;j++)h=f[j],fa.test(h.type||"")&&!L.access(h,"globalEval")&&n.contains(i,h)&&(h.src?n._evalUrl&&n._evalUrl(h.src):n.globalEval(h.textContent.replace(ha,"")))}return this}}),n.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){n.fn[a]=function(a){for(var c,d=[],e=n(a),g=e.length-1,h=0;g>=h;h++)c=h===g?this:this.clone(!0),n(e[h])[b](c),f.apply(d,c.get());return this.pushStack(d)}});var qa,ra={};function sa(b,c){var d,e=n(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:n.css(e[0],"display");return e.detach(),f}function ta(a){var b=l,c=ra[a];return c||(c=sa(a,b),"none"!==c&&c||(qa=(qa||n("'),l},simpleUpload.dequeueIframe=function(e){e in simpleUpload.iframes&&($("iframe[name=simpleUpload_iframe_"+e+"]").remove(),delete simpleUpload.iframes[e],simpleUpload.iframeCount--)},simpleUpload.convertDataType=function(e,l,n){var t="auto";if("auto"==e){if("string"==typeof l&&""!=l){var a=l.toLowerCase(),o=["json","xml","html","script","text"];for(var r in o)if(o[r]==a){t=a;break}}}else t=e;if("auto"==t)return"undefined"==typeof n?"":"object"==typeof n?n:String(n);if("json"==t){if("undefined"==typeof n||null===n)return null;if("object"==typeof n)return n;if("string"==typeof n)try{return $.parseJSON(n)}catch(i){return!1}return!1}if("xml"==t){if("undefined"==typeof n||null===n)return null;if("string"==typeof n)try{return $.parseXML(n)}catch(i){return!1}return!1}if("script"==t){if("undefined"==typeof n)return"";if("string"==typeof n)try{return $.globalEval(n),n}catch(i){return!1}return!1}return"undefined"==typeof n?"":String(n)},simpleUpload.iframeCallback=function(e){if("object"==typeof e&&null!==e){var l=e.id;if(l in simpleUpload.iframes){var n=simpleUpload.convertDataType(simpleUpload.iframes[l].expect,e.type,e.data);n!==!1?simpleUpload.iframes[l].complete(n):simpleUpload.iframes[l].error("Could not get response from server")}}},simpleUpload.postMessageCallback=function(e){try{var l=e.message?"message":"data",n=e[l];if("string"==typeof n&&""!=n&&(n=$.parseJSON(n),"object"==typeof n&&null!==n&&"string"==typeof n.namespace&&"simpleUpload"==n.namespace)){var t=n.id;if(t in simpleUpload.iframes&&e.origin===simpleUpload.iframes[t].origin){var a=simpleUpload.convertDataType(simpleUpload.iframes[t].expect,n.type,n.data);a!==!1?simpleUpload.iframes[t].complete(a):simpleUpload.iframes[t].error("Could not get response from server")}}}catch(e){}},window.addEventListener?window.addEventListener("message",simpleUpload.postMessageCallback,!1):window.attachEvent("onmessage",simpleUpload.postMessageCallback),function(e){"function"==typeof define&&define.amd?define(["jquery"],e):"object"==typeof exports?module.exports=e(require("jquery")):e(jQuery)}(function(e){e.fn.simpleUpload=function(l,n){return 0==e(this).length&&"object"==typeof n&&null!==n&&"object"==typeof n.files&&null!==n.files?(new simpleUpload(l,null,n),this):this.each(function(){new simpleUpload(l,this,n)})},e.fn.simpleUpload.maxSimultaneousUploads=function(e){return"undefined"==typeof e?simpleUpload.maxUploads:"number"==typeof e&&e>0?(simpleUpload.maxUploads=e,this):void 0}}); \ No newline at end of file diff --git a/sandbox/grav/user/plugins/editable-simplemde/templates/editable-simplemde.html.twig b/sandbox/grav/user/plugins/editable-simplemde/templates/editable-simplemde.html.twig new file mode 100644 index 0000000000..9fe8a812c8 --- /dev/null +++ b/sandbox/grav/user/plugins/editable-simplemde/templates/editable-simplemde.html.twig @@ -0,0 +1,8 @@ +
    + +
     Edit
    + +
    +
    diff --git a/sandbox/grav/user/plugins/email/CHANGELOG.md b/sandbox/grav/user/plugins/email/CHANGELOG.md new file mode 100644 index 0000000000..03723a0e99 --- /dev/null +++ b/sandbox/grav/user/plugins/email/CHANGELOG.md @@ -0,0 +1,146 @@ +# v2.6.1 +## 09/07/2017 + +1. [](#improved) + * Improved the error message when missing `from` in the configuration + * Silently catch malformed email exceptions + +# v2.6.0 +## 05/22/2017 + +1. [](#improved) + * Inherit options from plugin configuration [#39](https://github.com/getgrav/grav-plugin-email/pull/39) +1. [](#bugfix) + * Also process translation on the email subject [https://github.com/getgrav/grav-plugin-comments/issues/38](https://github.com/getgrav/grav-plugin-comments/issues/38) + +# v2.5.3 +## 01/03/2017 + +1. [](#improved) + * Updated to SwiftMailer 5.4.5 [#45](https://github.com/getgrav/grav-plugin-email/issues/45) + +# v2.5.2 +## 12/13/2016 + +1. [](#new) + * RC released as stable + +# v2.5.2-rc.1 +## 11/26/2016 + +1. [](#new) + * Added a new `process_markdown` option for emails in forms +1. [](#improved) + * Improved the `Utils::sendEmail()` method to take the email type as an option + +# v2.5.1 +## 10/19/2016 + +1. [](#improved) + * CLI command will fallback to use the `to` from email plugin config if not provided + * Explicit Composer based class loader to fix issues with class case + +# v2.5.0 +## 09/07/2016 + +1. [](#new) + * Added a new `bin/plugin email test-email` CLI command +1. [](#improved) + * Moved Email `Utils` class from Login to Email plugin + * Provide a sample base `email/base.html.twig` template for emails +1. [](#bugfix) + * Fix handling attachments with the updated file upload field + +# v2.4.3 +## 08/16/2016 + +1. [](#improved) + * Added Russian translation + * Updated Swiftmailer to 5.4.3 [#37](https://github.com/getgrav/grav-plugin-email/issues/37) + +# v2.4.2 +## 08/10/2016 + +1. [](#improved) + * Added Croatian translation + +# v2.4.1 +## 07/14/2016 + +1. [](#improved) + * Allow multiple email recipients (comma separated) [#31](https://github.com/getgrav/grav-plugin-email/issues/31) + * Added Danish and Spanish translations + +# v2.4.0 +## 05/11/2016 + +1. [](#improved) + * Now includes Swiftmailer v5.4.2 which introduces a number of bug fixes and improvements +1. [](#bugfix) + * Correct `starttls` implementation, bundled in TLS + +# v2.3.0 +## 04/20/2016 + +1. [](#improved) + * Added debug option to enable logging on SwiftMailer. + * Updated SwiftMailer from v5.1.0 to v5.4.1. + * Added an option in the Admin settings to enable `starttls` +1. [](#bugfix) + * Correctly name TLS in the Admin settings, the label was `TTS` (but the value was correctly named `tls`) + +# v2.2.0 +## 02/05/2016 + +1. [](#new) + * Allow to send attachments in forms + * Added French translation +1. [](#improved) + * Throw an exception when trying to send emails without a `from` or `to` parameters setup, to intercept less meaningful errors and provide a better description on how to fix the problem + * Changed SMTP password in admin to use a password field instead of plain text + +# v2.1.0 +## 12/18/2015 + +1. [](#new) + * Added missing `content_type` to email.yaml + * Added default values for CC and BCC + 1. [](#improved) + * Improved documentation of new email params in `README.md` + * Moved config setting of `mailer.default` to `mailer.engine` + +# v2.0.0 +## 12/11/2015 + +1. [](#new) + * Added support for from/sender name (Thomas Keitel) + * Added support for message content type (Thomas Keitel) + * Added support for reply addresses (Thomas Keitel) + * Added support for CC/BCC (Thomas Keitel) + * Added support for multiple body parts (Thomas Keitel) +1. [](#bugfix) + * Fix email engine selection (z38) + +# v1.0.0 +## 11/20/2015 + +1. [](#bugfix) + * Fix for issue with no body parameter specified + +# v0.2.1 +## 09/11/2015 + +1. [](#bugfix) + * Fix onFormProcessed event + +# v0.2.0 +## 08/11/2015 + +1. [](#improved) + * Disable `enable` in admin + +# v0.1.0 +## 08/04/2015 + +1. [](#new) + * ChangeLog started... diff --git a/sandbox/grav/user/plugins/email/LICENSE b/sandbox/grav/user/plugins/email/LICENSE new file mode 100644 index 0000000000..0e788c6a28 --- /dev/null +++ b/sandbox/grav/user/plugins/email/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Grav + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sandbox/grav/user/plugins/email/README.md b/sandbox/grav/user/plugins/email/README.md new file mode 100644 index 0000000000..482fabd0cc --- /dev/null +++ b/sandbox/grav/user/plugins/email/README.md @@ -0,0 +1,210 @@ +# Grav Email Plugin + +The **email plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to send email. This is particularly useful for the **admin** and **login** plugins. + +# Installation + +The email plugin is easy to install with GPM. + +``` +$ bin/gpm install email +``` + +# Configuration + +By default, the plugin uses PHP Mail as the mail engine. + +You can configure the Email plugin by using the Admin plugin, navigating to the Plugins list and choosing `Email`. + +That's the easiest route. Or you can also alter the Plugin configuration by copying the `user/plugins/email/email.yaml` file into `user/config/plugins/email.yaml` and make your modifications there. + +The first setting you'd likely change is your `Email from` / `Email to` names and emails. + +Also, you'd likely want to setup a SMTP server instead of using PHP Mail, as the latter is not 100% reliable and you might experience problems with emails. + +# Testing emails + +A good way to test emails is to use a SMTP server service that's built for testing emails, for example [https://mailtrap.io](https://mailtrap.io) + +Setup the Email plugin to use that SMTP server with the fake inbox data. For example enter this configuration in `user/config/plugins/email.yaml` or through the Admin panel: + +``` +mailer: + engine: smtp + smtp: + server: mailtrap.io + port: 2525 + encryption: none + user: YOUR_MAILTRAP_INBOX_USER + password: YOUR_MAILTRAP_INBOX_PASSWORD +``` + +That service will intercept emails and show them on their web-based interface instead of sending them for real. + +You can try and fine tune the emails there while testing. + +## Testing with CLI Command + +You can test your email configuration with the following CLI Command: + +``` +$ bin/plugin email test-email -t steve@apple.com +``` + +You can also pass in a configuration environment: + +``` +$ bin/plugin email test-email -t steve@apple.com -e mysite.com +``` + +This will use the email configuration you have located in `user/mysite.com/config/plugins/email.yaml`. Read the docs to find out more about environment-based configuration: https://learn.getgrav.org/advanced/environment-config + +# Programmatically send emails + +Add this code in your plugins: + +```php + + $to = 'email@test.com'; + $from = 'email@test.com'; + + $subject = 'Test'; + $content = 'Test'; + + $message = $this->grav['Email']->message($subject, $content, 'text/html') + ->setFrom($from) + ->setTo($to); + + $sent = $this->grav['Email']->send($message); +``` + +# Emails sent with Forms + +When executing email actions during form processing, action parameters are inherited from the global configuration but may also be overridden on a per-action basis. + +``` +title: Custom form + +form: + name: custom_form + fields: + + # Any fields you'd like to add to the form: + # Their values may be referenced in email actions via '{{ form.value.FIELDNAME|e }}' + + process: + - email: + subject: "[Custom form] {{ form.value.name|e }}" + body: "{% include 'forms/data.txt.twig' %}" + from: sender@example.com + from_name: 'Custom sender name' + to: recipient@example.com + to_name: 'Custom recipient name' + content_type: 'text/plain' + process_markdown: true +``` + +## Sending Attachments + +You can add file inputs to your form, and send those files via Email. +Just add an `attachments` field and list the file input fields names. You can have multiple file fields, and this will send all the files as attachments. Example: + +``` +form: + name: custom_form + fields: + + - + name: my-file + label: 'Add a file' + type: file + multiple: false + destination: user/data/files + accept: + - application/pdf + - application/x-pdf + - image/png + - text/plain + + process: + - + email: + body: '{% include "forms/data.html.twig" %}' + attachments: + - 'my-file' +``` + +## Additional action parameters + +To have more control over your generated email, you may also use the following additional parameters: + +* `reply_to`: Set one or more addresses that should be used to reply to the message. +* `cc` _(Carbon copy)_: Add one or more addresses to the delivery list. Many email clients will mark email in one's inbox differently depending on whether they are in the `To:` or `Cc:` list. +* `bcc` _(Blind carbon copy)_: Add one or more addresses to the delivery list that should (usually) not be listed in the message data, remaining invisible to other recipients. +* `charset`: Explicitly set a charset for the generated email body (only takes effect if `body` parameter is a string, defaults to `utf-8`) + +### Specifying email addresses + +Email-related parameters (`from`, `to`, `reply_to`, `cc`and `bcc`) allow different notations for single / multiple values: + +#### Single email address string + +``` +to: mail@example.com +``` + +#### Multiple email address strings + +``` +to: + - mail@example.com + - mail+1@example.com + - mail+2@example.com +``` + +#### Single email address with name + +``` +to: + mail: mail@example.com + name: Human-readable name +``` + +#### Multiple email addresses (with and without names) + +``` +to: + - + mail: mail@example.com + name: Human-readable name + - + mail: mail+2@example.com + name: Another human-readable name + - + mail+3@example.com + - + mail+4@example.com +``` + +## Multi-part MIME messages + +Apart from a simple string, an email body may contain different MIME parts (e.g. HTML body with plain text fallback). You may even specify a different charset for each part (default to `utf-8`): + +``` +body: + - + content_type: 'text/html' + body: "{% include 'forms/default/data.html.twig' %}" + - + content_type: 'text/plain' + body: "{% include 'forms/default/data.txt.twig' %}" + charset: 'iso-8859-1' +``` + +# Troubleshooting + +## Emails are not sent + +As explained above in the Configuration section, if you're using the default settings, set the Plugin configuration to use a SMTP server. It can be [Gmail](https://www.digitalocean.com/community/tutorials/how-to-use-google-s-smtp-server) or another SMTP server you have at your disposal. + +This is the first thing to check. The reason is that PHP Mail, the default system used by the Plugin, is not 100% reliable and emails might not arrive. diff --git a/sandbox/grav/user/plugins/email/blueprints.yaml b/sandbox/grav/user/plugins/email/blueprints.yaml new file mode 100644 index 0000000000..1a515caa67 --- /dev/null +++ b/sandbox/grav/user/plugins/email/blueprints.yaml @@ -0,0 +1,182 @@ +name: Email +version: 2.6.1 +description: Enables the emailing system for Grav +icon: envelope +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +keywords: plugin, email, sender +homepage: https://github.com/getgrav/grav-plugin-email +bugs: https://github.com/getgrav/grav-plugin-email/issues +license: MIT + +dependencies: + - { name: grav, version: '>=1.1.9' } + +form: + validation: loose + fields: + enabled: + type: hidden + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + mailer.engine: + type: select + label: Mail Engine + size: medium + options: + none: Disabled + smtp: SMTP + sendmail: Sendmail + mail: PHP Mail + + content_type: + type: select + label: Content type + size: medium + default: 'text/html' + options: + 'text/plain': Plain text + 'text/html': HTML + + charset: + type: text + size: medium + label: Charset + placeholder: "Defaults to UTF-8" + + from: + type: email + size: medium + label: Email from + placeholder: "Default email from address" + validate: + required: true + type: email + + from_name: + type: text + size: medium + label: Email from name + placeholder: "Default email from name" + + to: + type: email + size: medium + label: Email to + placeholder: "Default email to address" + multiple: true + validate: + required: true + type: email + + to_name: + type: text + size: medium + label: Email to name + placeholder: "Default email to name" + + cc: + type: email + size: medium + label: Email CC + placeholder: "Default email CC address" + multiple: true + validate: + type: email + + cc_name: + type: text + size: medium + label: Email CC name + placeholder: "Default email CC name" + + bcc: + type: email + size: medium + label: Email BCC + placeholder: "Default email BCC address" + multiple: true + validate: + type: email + + reply_to: + type: email + size: medium + label: Email reply-to + placeholder: "Default email reply-to address" + multiple: true + validate: + type: email + + reply_to_name: + type: text + size: medium + label: Email reply-to name + placeholder: "Default email reply-to name" + + body: + type: textarea + size: medium + label: Email body + placeholder: "Defaults to a table of all form fields" + + mailer.smtp.server: + type: text + size: medium + label: SMTP server + placeholder: "e.g. smtp.google.com" + + mailer.smtp.port: + type: text + size: small + label: SMTP port + placeholder: "Defaults to 25 (plaintext) / 587 (encrypted)" + validate: + type: number + min: 1 + max: 65535 + + mailer.smtp.encryption: + type: select + size: medium + label: SMTP encryption + options: + none: None + ssl: SSL + tls: TLS + + mailer.smtp.user: + type: text + size: medium + label: SMTP login name + + mailer.smtp.password: + type: password + size: medium + label: SMTP password + + mailer.sendmail.bin: + type: text + size: medium + label: Path to sendmail + placeholder: "/usr/sbin/sendmail" + + debug: + type: toggle + label: Debug + highlight: 1 + default: 0 + options: + true: PLUGIN_ADMIN.ENABLED + false: PLUGIN_ADMIN.DISABLED + validate: + type: bool diff --git a/sandbox/grav/user/plugins/email/classes/Email.php b/sandbox/grav/user/plugins/email/classes/Email.php new file mode 100644 index 0000000000..30e65b2ff4 --- /dev/null +++ b/sandbox/grav/user/plugins/email/classes/Email.php @@ -0,0 +1,189 @@ +get('plugins.email.mailer.engine') != 'none'; + } + + /** + * Returns true if debugging on emails has been enabled. + * + * @return bool + */ + public function debug() + { + return self::getGrav()['config']->get('plugins.email.debug') == 'true'; + } + + /** + * Creates an email message. + * + * @param string $subject + * @param string $body + * @param string $contentType + * @param string $charset + * @return \Swift_Message + */ + public function message($subject = null, $body = null, $contentType = null, $charset = null) + { + return new \Swift_Message($subject, $body, $contentType, $charset); + } + + /** + * Creates an attachment. + * + * @param string $data + * @param string $filename + * @param string $contentType + * @return \Swift_Attachment + */ + public function attachment($data = null, $filename = null, $contentType = null) + { + return new \Swift_Attachment($data, $filename, $contentType); + } + + /** + * Creates an embedded attachment. + * + * @param string $data + * @param string $filename + * @param string $contentType + * @return \Swift_EmbeddedFile + */ + public function embedded($data = null, $filename = null, $contentType = null) + { + return new \Swift_EmbeddedFile($data, $filename, $contentType); + } + + /** + * Creates an image attachment. + * + * @param string $data + * @param string $filename + * @param string $contentType + * @return \Swift_Image + */ + public function image($data = null, $filename = null, $contentType = null) + { + return new \Swift_Image($data, $filename, $contentType); + } + + /** + * Send email. + * + * @param \Swift_Message $message + * @return int + */ + public function send($message) + { + $mailer = $this->getMailer(); + + $result = $mailer ? $mailer->send($message) : 0; + + // Check if emails and debugging are both enabled. + if ($mailer && $this->debug()) { + // Get an instance of the logging service. + $log = self::getGrav()['log']; + // Append the SwiftMailer log to the log. + $log->addDebug("Email Log: " . $this->getLogs()); + } + + return $result; + } + + /** + * Return debugging logs if enabled + * + * @return string + */ + public function getLogs() + { + if ($this->debug()) { + return $this->logger->dump(); + } + return ''; + } + + /** + * @internal + * @return null|\Swift_Mailer + */ + protected function getMailer() + { + if (!$this->enabled()) { + return null; + } + + if (!$this->mailer) { + /** @var Config $config */ + $config = self::getGrav()['config']; + $mailer = $config->get('plugins.email.mailer.engine'); + + // Create the Transport and initialize it. + switch ($mailer) { + case 'smtp': + $transport = \Swift_SmtpTransport::newInstance(); + + $options = $config->get('plugins.email.mailer.smtp'); + if (!empty($options['server'])) { + $transport->setHost($options['server']); + } + if (!empty($options['port'])) { + $transport->setPort($options['port']); + } + if (!empty($options['encryption']) && $options['encryption'] != 'none') { + $transport->setEncryption($options['encryption']); + } + if (!empty($options['user'])) { + $transport->setUsername($options['user']); + } + if (!empty($options['password'])) { + $transport->setPassword($options['password']); + } + break; + case 'sendmail': + $options = $config->get('plugins.email.mailer.sendmail'); + $bin = !empty($options['bin']) ? $options['bin'] : '/usr/sbin/sendmail'; + $transport = \Swift_SendmailTransport::newInstance($bin); + break; + case 'mail': + default: + $transport = \Swift_MailTransport::newInstance(); + } + + // Create the Mailer using your created Transport + $this->mailer = \Swift_Mailer::newInstance($transport); + + // Register the logger if we're debugging. + if ($this->debug()) { + $this->logger = new \Swift_Plugins_Loggers_ArrayLogger(); + $this->mailer->registerPlugin(new \Swift_Plugins_LoggerPlugin($this->logger)); + } + } + + return $this->mailer; + } +} diff --git a/sandbox/grav/user/plugins/email/classes/Utils.php b/sandbox/grav/user/plugins/email/classes/Utils.php new file mode 100644 index 0000000000..690babf6d2 --- /dev/null +++ b/sandbox/grav/user/plugins/email/classes/Utils.php @@ -0,0 +1,57 @@ +get('plugins.email.from'); + } + + if (!isset($grav['Email']) || empty($from)) { + throw new \RuntimeException($grav['language']->translate('PLUGIN_EMAIL.PLEASE_CONFIGURE_A_FROM_ADDRESS')); + } + + if (empty($to) || empty($subject) || empty($content)) { + return false; + } + + //Initialize twig if not yet initialized + $grav['twig']->init(); + + $body = $grav['twig']->processTemplate('email/base.html.twig', ['content' => $content]); + + $message = $grav['Email']->message($subject, $body, $mimetype) + ->setFrom($from) + ->setTo($to); + + $sent = $grav['Email']->send($message); + + if ($sent < 1) { + return false; + } else { + return true; + } + } +} diff --git a/sandbox/grav/user/plugins/email/cli/TestEmailCommand.php b/sandbox/grav/user/plugins/email/cli/TestEmailCommand.php new file mode 100644 index 0000000000..f1047d75ef --- /dev/null +++ b/sandbox/grav/user/plugins/email/cli/TestEmailCommand.php @@ -0,0 +1,98 @@ +setName('test-email') + ->setAliases(['testemail']) + ->addOption( + 'to', + 't', + InputOption::VALUE_REQUIRED, + 'An email address to send the email to' + ) + ->addOption( + 'env', + 'e', + InputOption::VALUE_OPTIONAL, + 'The environment to trigger a specific configuration. For example: localhost, mysite.dev, www.mysite.com' + ) + ->addOption( + 'subject', + 's', + InputOption::VALUE_OPTIONAL, + 'A subject for the email' + ) + ->addOption( + 'body', + 'b', + InputOption::VALUE_OPTIONAL, + 'The body of the email' + ) + ->setDescription('Sends a test email using the plugin\'s configuration') + ->setHelp('The test-email command sends a test email using the plugin\'s configuration'); + } + + /** + * @return int|null|void + */ + protected function serve() + { + $grav = Grav::instance(); + + $this->output->writeln(''); + $this->output->writeln('Current Configuration:'); + $this->output->writeln(''); + + dump($grav['config']->get('plugins.email')); + + $this->output->writeln(''); + + require_once __DIR__ . '/../vendor/autoload.php'; + + $grav['Email'] = new Email(); + + $email_to = $this->input->getOption('to') ?: $grav['config']->get('plugins.email.to'); + $subject = $this->input->getOption('subject'); + $body = $this->input->getOption('body'); + + if (!$subject) { + $subject = 'Testing Grav Email Plugin'; + } + + if (!$body) { + $configuration = print_r($grav['config']->get('plugins.email'), true); + $body = $grav['language']->translate(['PLUGIN_EMAIL.TEST_EMAIL_BODY', $configuration]); + } + + $sent = EmailUtils::sendEmail($subject, $body, $email_to); + + if ($sent) { + $this->output->writeln("Message sent successfully!"); + } else { + $this->output->writeln("Problem sending email..."); + } + + } +} diff --git a/sandbox/grav/user/plugins/email/composer.json b/sandbox/grav/user/plugins/email/composer.json new file mode 100644 index 0000000000..182c714190 --- /dev/null +++ b/sandbox/grav/user/plugins/email/composer.json @@ -0,0 +1,10 @@ +{ + "require": { + "swiftmailer/swiftmailer": "~5.4" + }, + "autoload": { + "psr-4": { + "Grav\\Plugin\\Email\\": "classes/" + } + } +} diff --git a/sandbox/grav/user/plugins/email/composer.lock b/sandbox/grav/user/plugins/email/composer.lock new file mode 100644 index 0000000000..eadf425f85 --- /dev/null +++ b/sandbox/grav/user/plugins/email/composer.lock @@ -0,0 +1,73 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "hash": "6c22283f04560164bb86aa58648babc9", + "content-hash": "2244976b3750b669fab6abb506e9c591", + "packages": [ + { + "name": "swiftmailer/swiftmailer", + "version": "v5.4.5", + "source": { + "type": "git", + "url": "https://github.com/swiftmailer/swiftmailer.git", + "reference": "cd142238a339459b10da3d8234220963f392540c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/cd142238a339459b10da3d8234220963f392540c", + "reference": "cd142238a339459b10da3d8234220963f392540c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "require-dev": { + "mockery/mockery": "~0.9.1", + "symfony/phpunit-bridge": "~3.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.4-dev" + } + }, + "autoload": { + "files": [ + "lib/swift_required.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Chris Corbyn" + }, + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Swiftmailer, free feature-rich PHP mailer", + "homepage": "http://swiftmailer.org", + "keywords": [ + "email", + "mail", + "mailer" + ], + "time": "2016-12-29 10:02:40" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/sandbox/grav/user/plugins/email/email.php b/sandbox/grav/user/plugins/email/email.php new file mode 100644 index 0000000000..ba24447891 --- /dev/null +++ b/sandbox/grav/user/plugins/email/email.php @@ -0,0 +1,319 @@ + ['onPluginsInitialized', 0], + 'onFormProcessed' => ['onFormProcessed', 0], + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0] + ]; + } + + /** + * Initialize emailing. + */ + public function onPluginsInitialized() + { + require_once __DIR__ . '/vendor/autoload.php'; + + $this->email = new Email(); + + if ($this->email->enabled()) { + $this->grav['Email'] = $this->email; + } + } + + /** + * Add twig paths to plugin templates. + */ + public function onTwigTemplatePaths() + { + $twig = $this->grav['twig']; + $twig->twig_paths[] = __DIR__ . '/templates'; + } + + /** + * Send email when processing the form data. + * + * @param Event $event + */ + public function onFormProcessed(Event $event) + { + $form = $event['form']; + $action = $event['action']; + $params = $event['params']; + + if (!$this->email->enabled()) { + return; + } + + switch ($action) { + case 'email': + // Prepare Twig variables + $vars = array( + 'form' => $form + ); + + // Build message + $message = $this->buildMessage($params, $vars); + + if (isset($params['attachments'])) { + $filesToAttach = (array)$params['attachments']; + if ($filesToAttach) foreach ($filesToAttach as $fileToAttach) { + $filesValues = $form->value($fileToAttach); + + if ($filesValues) foreach($filesValues as $fileValues) { + if (isset($fileValues['file'])) { + $filename = $fileValues['file']; + } else { + $filename = ROOT_DIR . $fileValues['path']; + } + + $message->attach(\Swift_Attachment::fromPath($filename)); + } + } + } + + // Send e-mail + $this->email->send($message); + break; + } + } + + /** + * Build e-mail message. + * + * @param array $params + * @param array $vars + * @return \Swift_Message + */ + protected function buildMessage(array $params, array $vars = array()) + { + /** @var Twig $twig */ + $twig = $this->grav['twig']; + + // Extend parameters with defaults. + $params += array( + 'bcc' => $this->config->get('plugins.email.bcc', array()), + 'body' => $this->config->get('plugins.email.body', '{% include "forms/data.html.twig" %}'), + 'cc' => $this->config->get('plugins.email.cc', array()), + 'cc_name' => $this->config->get('plugins.email.cc_name'), + 'charset' => $this->config->get('plugins.email.charset', 'utf-8'), + 'from' => $this->config->get('plugins.email.from'), + 'from_name' => $this->config->get('plugins.email.from_name'), + 'content_type' => $this->config->get('plugins.email.content_type', 'text/html'), + 'reply_to' => $this->config->get('plugins.email.reply_to', array()), + 'reply_to_name' => $this->config->get('plugins.email.reply_to_name'), + 'subject' => !empty($vars['form']) && $vars['form'] instanceof Form ? $vars['form']->page()->title() : null, + 'to' => $this->config->get('plugins.email.to'), + 'to_name' => $this->config->get('plugins.email.to_name'), + 'process_markdown' => false, + ); + + // Create message object. + $message = $this->email->message(); + + if (!$params['to']) { + throw new \RuntimeException($this->grav['language']->translate('PLUGIN_EMAIL.PLEASE_CONFIGURE_A_TO_ADDRESS')); + } + if (!$params['from']) { + throw new \RuntimeException($this->grav['language']->translate('PLUGIN_EMAIL.PLEASE_CONFIGURE_A_FROM_ADDRESS')); + } + + // Process parameters. + foreach ($params as $key => $value) { + switch ($key) { + case 'bcc': + foreach ($this->parseAddressValue($value, $vars) as $address) { + try { + $message->addBcc($address->mail, $address->name); + } catch (Swift_RfcComplianceException $e) { + continue; + } + } + break; + + case 'body': + if (is_string($value)) { + $body = $twig->processString($value, $vars); + + if ($params['process_markdown']) { + $parsedown = new \Parsedown(); + $body = $parsedown->text($body); + } + + $content_type = !empty($params['content_type']) ? $twig->processString($params['content_type'], $vars) : null; + $charset = !empty($params['charset']) ? $twig->processString($params['charset'], $vars) : null; + + $message->setBody($body, $content_type, $charset); + } + elseif (is_array($value)) { + foreach ($value as $body_part) { + $body_part += array( + 'charset' => $params['charset'], + 'content_type' => $params['content_type'], + ); + + $body = !empty($body_part['body']) ? $twig->processString($body_part['body'], $vars) : null; + + if ($params['process_markdown']) { + $parsedown = new \Parsedown(); + $body = $parsedown->text($body); + } + + $content_type = !empty($body_part['content_type']) ? $twig->processString($body_part['content_type'], $vars) : null; + $charset = !empty($body_part['charset']) ? $twig->processString($body_part['charset'], $vars) : null; + + if (!$message->getBody()) { + $message->setBody($body, $content_type, $charset); + } + else { + $message->addPart($body, $content_type, $charset); + } + } + } + break; + + case 'cc': + if (is_string($value) && !empty($params['cc_name'])) { + $value = array( + 'mail' => $twig->processString($value, $vars), + 'name' => $twig->processString($params['cc_name'], $vars), + ); + } + + foreach ($this->parseAddressValue($value, $vars) as $address) { + try { + $message->addCc($address->mail, $address->name); + } catch (Swift_RfcComplianceException $e) { + continue; + } + } + break; + + case 'from': + if (is_string($value) && !empty($params['from_name'])) { + $value = array( + 'mail' => $twig->processString($value, $vars), + 'name' => $twig->processString($params['from_name'], $vars), + ); + } + + foreach ($this->parseAddressValue($value, $vars) as $address) { + try { + $message->addFrom($address->mail, $address->name); + } catch (Swift_RfcComplianceException $e) { + continue; + } + } + break; + + case 'reply_to': + if (is_string($value) && !empty($params['reply_to_name'])) { + $value = array( + 'mail' => $twig->processString($value, $vars), + 'name' => $twig->processString($params['reply_to_name'], $vars), + ); + } + + foreach ($this->parseAddressValue($value, $vars) as $address) { + try { + $message->addReplyTo($address->mail, $address->name); + } catch (Swift_RfcComplianceException $e) { + continue; + } + } + break; + + case 'subject': + $message->setSubject($twig->processString($this->grav['language']->translate($value), $vars)); + break; + + case 'to': + if (is_string($value) && !empty($params['to_name'])) { + $value = array( + 'mail' => $twig->processString($value, $vars), + 'name' => $twig->processString($params['to_name'], $vars), + ); + } + + foreach ($this->parseAddressValue($value, $vars) as $address) { + try { + $message->addTo($address->mail, $address->name); + } catch (Swift_RfcComplianceException $e) { + continue; + } + } + break; + } + } + + return $message; + } + + /** + * Return parsed e-mail address value. + * + * @param $value + * @param array $vars + * @return array + */ + protected function parseAddressValue($value, array $vars = array()) + { + $parsed = array(); + + /** @var Twig $twig */ + $twig = $this->grav['twig']; + + // Single e-mail address string + if (is_string($value)) { + $parsed[] = (object) array( + 'mail' => $twig->processString($value, $vars), + 'name' => null, + ); + } + + else { + // Cast value as array + $value = (array) $value; + + // Single e-mail address array + if (!empty($value['mail'])) { + $parsed[] = (object) array( + 'mail' => $twig->processString($value['mail'], $vars), + 'name' => !empty($value['name']) ? $twig->processString($value['name'], $vars) : NULL, + ); + } + + // Multiple addresses (either as strings or arrays) + elseif (!(empty($value['mail']) && !empty($value['name']))) { + foreach ($value as $y => $itemx) { + $addresses = $this->parseAddressValue($itemx, $vars); + + if (($address = reset($addresses))) { + $parsed[] = $address; + } + } + } + } + + return $parsed; + } +} diff --git a/sandbox/grav/user/plugins/email/email.yaml b/sandbox/grav/user/plugins/email/email.yaml new file mode 100644 index 0000000000..520400db5e --- /dev/null +++ b/sandbox/grav/user/plugins/email/email.yaml @@ -0,0 +1,17 @@ +enabled: true +from: +from_name: +to: +to_name: +mailer: + engine: mail + smtp: + server: localhost + port: 25 + encryption: none + user: '' + password: '' + sendmail: + bin: '/usr/sbin/sendmail' +content_type: text/html +debug: false \ No newline at end of file diff --git a/sandbox/grav/user/plugins/email/hebe.json b/sandbox/grav/user/plugins/email/hebe.json new file mode 100644 index 0000000000..3831af4d94 --- /dev/null +++ b/sandbox/grav/user/plugins/email/hebe.json @@ -0,0 +1,15 @@ +{ + "project":"grav-plugin-email", + "platforms":{ + "grav":{ + "nodes":{ + "plugin":[ + { + "source":"/", + "destination":"/user/plugins/email" + } + ] + } + } + } +} diff --git a/sandbox/grav/user/plugins/email/languages.yaml b/sandbox/grav/user/plugins/email/languages.yaml new file mode 100644 index 0000000000..4067417f08 --- /dev/null +++ b/sandbox/grav/user/plugins/email/languages.yaml @@ -0,0 +1,53 @@ +en: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "Email not configured" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Please configure a 'to' address in the Email Plugin settings, or in the form" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Please configure a 'from' address in the Email Plugin settings, or in the form" + TEST_EMAIL_BODY: "

    Testing Email

    This test email has been sent based on the following configuration:

    %1$s

    " + EMAIL_FOOTER: "GetGrav.org" + +da: + PLUGIN_EMAIL: + PLEASE_CONFIGURE_A_TO_ADDRESS: "Konfigurere venligst en 'til' email adresse i Email Plugin indstillingerne eller her i formularen" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Konfigurere venligst en 'fra' email adresse i Email Plugin indstillingerne eller her i formularen" + +de: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "E-Mail ist nicht konfiguriert" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Bitte konfigurieren sie eine 'An' ('to') Adresse in den Email-Plugin-Einstellungen oder im Formular." + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Bitte konfigurieren sie eine 'Von' ('from') Adresse in den Email-Plugin-Einstellungen oder im Formular." + +es: + PLUGIN_EMAIL: + PLEASE_CONFIGURE_A_TO_ADDRESS: "Por favor configura una dirección de 'remitente' en la configuración del Plugin de Email o en el formulario" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Por favor configura una dirección de 'destinatario' en la configuración del Plugin de Email o en el formulario" + +fr: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "L’e-mail n’est pas configuré" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Veuillez configurer une adresse de 'destinataire' dans les paramètres du Plugin ou dans le formulaire." + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Veuillez configurer une adresse 'd'expéditeur' dans les paramètres du Plugin ou dans le formulaire." + TEST_EMAIL_BODY: "

    E-mail de test

    Cet e-mail de test est basé sur la configuration suivante :

    %1$s

    " + EMAIL_FOOTER: "GetGrav.org" + +hr: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "Email nije konfiguriran" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Konfigurirajte 'za' ('to') adresu u postavkama Email dodatka ili u obrascu" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Konfigurirajte 'od' ('from') adresu u postavkama Email dodatka ili u obrascu" + +it: + PLUGIN_EMAIL: + PLEASE_CONFIGURE_A_TO_ADDRESS: "Per favore, configura l'indirizzo di destinazione ('to') nella configurazione del Plugin Email, oppure direttamente nella form." + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Per favore, configura l'indirizzo di provenienza ('from') nella configurazione del Plugin Email, oppure direttamente nella form" + +ro: + PLUGIN_EMAIL: + EMAIL_NOT_CONFIGURED: "Adresa de email nu este configurată" + PLEASE_CONFIGURE_A_TO_ADDRESS: "Vă rugam setați o adresă 'către' în setările modulului Email sau în formular" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Vă rugam setați o adresă 'de la' în setările modulului Email sau în formular" + +ru: + PLUGIN_EMAIL: + PLEASE_CONFIGURE_A_TO_ADDRESS: "Пожалуйста настройте адрес получателя ('to') в настройках плагина Email Plugin, или на форме" + PLEASE_CONFIGURE_A_FROM_ADDRESS: "Пожалуйста настройте адрес отправителя ('from') в настройках плагина Email Plugin, или на форме" diff --git a/sandbox/grav/user/plugins/email/templates/email/base.html.twig b/sandbox/grav/user/plugins/email/templates/email/base.html.twig new file mode 100644 index 0000000000..de554c26e0 --- /dev/null +++ b/sandbox/grav/user/plugins/email/templates/email/base.html.twig @@ -0,0 +1,210 @@ + + + + + + + + + + + + + + + + + + +
    +
    + + + + +
    + {{ content|raw }} +
    +
    +
    + + + + + + + + + + + + + + diff --git a/sandbox/grav/user/plugins/error/CHANGELOG.md b/sandbox/grav/user/plugins/error/CHANGELOG.md new file mode 100644 index 0000000000..dbb0d78681 --- /dev/null +++ b/sandbox/grav/user/plugins/error/CHANGELOG.md @@ -0,0 +1,54 @@ +# v1.6.0 +## 10/19/2016 + +1. [](#improved) + * Added Croatian translation + * Improved `autoescape: true` support +1. [](#bugfix) + * Fixed issue where template file for `error` page type is only available if page was not found + +# v1.5.1 +## 07/18/2016 + +1. [](#improved) + * Added chinese and german translations +1. [](#bugfix) + * Fixed issue with the Smartypants plugin running before Twig was processed + +# v1.5.0 +## 07/14/2015 + +1. [](#improved) + * Translate some blueprint configuration options + * Allow translating the error message + * Added french, russian, romanian, danish, italian + +# v1.4.1 +## 12/11/2015 + +1. [](#bugfix) + * Fixed CLI command for PHP 5.5 and lower + +# v1.4.0 +## 11/21/2015 + +1. [](#new) + * Implemented CLI commands for the plugin + +# v1.3.0 +## 08/25/2015 + +1. [](#improved) + * Added blueprints for Grav Admin plugin + +# v1.2.2 +## 01/06/2015 + +1. [](#new) + * Added a default `error.json.twig` file + +# v1.2.1 +## 11/30/2014 + +1. [](#new) + * ChangeLog started... diff --git a/sandbox/grav/user/plugins/error/LICENSE b/sandbox/grav/user/plugins/error/LICENSE new file mode 100644 index 0000000000..484793ad19 --- /dev/null +++ b/sandbox/grav/user/plugins/error/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Grav + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sandbox/grav/user/plugins/error/README.md b/sandbox/grav/user/plugins/error/README.md new file mode 100644 index 0000000000..16b2a533ea --- /dev/null +++ b/sandbox/grav/user/plugins/error/README.md @@ -0,0 +1,89 @@ +# Grav Error Plugin + +![GPM Installation](assets/readme_1.png) + +`error` is a [Grav](http://github.com/getgrav/grav) Plugin and allows to redirect errors to nice output pages. + +This plugin is included in any package distributed that contains Grav. If you decide to clone Grav from GitHub you will most likely want to install this. + +# Installation + +Installing the Error plugin can be done in one of two ways. Our GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. + +## GPM Installation (Preferred) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install error + +This will install the Error plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/error`. + +## Manual Installation + +To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `error`. You can find these files either on [GitHub](https://github.com/getgrav/grav-plugin-error) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/error + +>> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav), the [Problems](https://github.com/getgrav/grav-plugin-problems) plugin, and a theme to be installed in order to operate. + +# Usage + +The `error` plugin doesn't require any configuration. The moment you install it, it is ready to use. + +Something you might want to do is to override the look and feel of the error page, and with Grav it is super easy. + +### Template + +Copy the template file [error.html.twig](templates/error.html.twig) into the `templates` folder of your custom theme and that is it. + +``` +/your/site/grav/user/themes/custom-theme/templates/error.html.twig +``` + +You can now edit the override and tweak it however you prefer. + +### Page + +Copy the page file [error.md](pages/error.md) into the `pages` folder of your user directory and that is it. + +``` +/your/site/grav/user/pages/error/error.md +``` + +You can now edit the override and tweak it however you prefer. + +# CLI Usage +The `error` plugin comes with a CLI command that outputs the `grav.log` in a beautified way, with possibility of limiting the amount of errors displayed, as well as include the trace in the output. + +### Commands + +| `bin/plugin error log` | | +|------------------------|-----------------------------------------------------------------| +| [ --limit N \| -l N ] | The amount of errors to display. Default is 5 | +| [ --trace \| -t ] | When used, it will add the backtrace in the output of the error | + + +# Updating + +As development for the Error plugin continues, new versions may become available that add additional features and functionality, improve compatibility with newer Grav releases, and generally provide a better user experience. Updating Error is easy, and can be done through Grav's GPM system, as well as manually. + +## GPM Update (Preferred) + +The simplest way to update this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm). You can do this with this by navigating to the root directory of your Grav install using your system's Terminal (also called command line) and typing the following: + + bin/gpm update error + +This command will check your Grav install to see if your Error plugin is due for an update. If a newer release is found, you will be asked whether or not you wish to update. To continue, type `y` and hit enter. The plugin will automatically update and clear Grav's cache. + +## Manual Update + +Manually updating Error is pretty simple. Here is what you will need to do to get this done: + +* Delete the `your/site/user/plugins/error` directory. +* Download the new version of the Error plugin from either [GitHub](https://github.com/getgrav/grav-plugin-error) or [GetGrav.org](http://getgrav.org/downloads/plugins#extras). +* Unzip the zip file in `your/site/user/plugins` and rename the resulting folder to `error`. +* Clear the Grav cache. The simplest way to do this is by going to the root Grav directory in terminal and typing `bin/grav clear-cache`. + +> Note: Any changes you have made to any of the files listed under this directory will also be removed and replaced by the new set. Any files located elsewhere (for example a YAML settings file placed in `user/config/plugins`) will remain intact. diff --git a/sandbox/grav/user/plugins/error/assets/readme_1.png b/sandbox/grav/user/plugins/error/assets/readme_1.png new file mode 100644 index 0000000000..930b87bb01 Binary files /dev/null and b/sandbox/grav/user/plugins/error/assets/readme_1.png differ diff --git a/sandbox/grav/user/plugins/error/blueprints.yaml b/sandbox/grav/user/plugins/error/blueprints.yaml new file mode 100644 index 0000000000..6ce715f73f --- /dev/null +++ b/sandbox/grav/user/plugins/error/blueprints.yaml @@ -0,0 +1,32 @@ +name: Error +version: 1.6.0 +description: Displays the error page. +icon: warning +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +homepage: https://github.com/getgrav/grav-plugin-error +keywords: error, plugin, required +bugs: https://github.com/getgrav/grav-plugin-error/issues +license: MIT + +form: + validation: strict + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + routes.404: + type: text + size: medium + label: 404 Route + default: '/error' diff --git a/sandbox/grav/user/plugins/error/cli/LogCommand.php b/sandbox/grav/user/plugins/error/cli/LogCommand.php new file mode 100644 index 0000000000..8a6c69a846 --- /dev/null +++ b/sandbox/grav/user/plugins/error/cli/LogCommand.php @@ -0,0 +1,130 @@ + 'green', + 'INFO' => 'cyan', + 'NOTICE' => 'yellow', + 'WARNING' => 'yellow', + 'ERROR' => 'red', + 'CRITICAL' => 'red', + 'ALERT' => 'red', + 'EMERGENCY' => 'magenta' + ]; + + /** + * + */ + protected function configure() + { + $this->logfile = LOG_DIR . 'grav.log'; + $this + ->setName("log") + ->setDescription("Outputs the Error Log") + ->addOption( + 'trace', + 't', + InputOption::VALUE_NONE, + 'Include the errors stack trace in the output' + ) + ->addOption( + 'limit', + 'l', + InputArgument::OPTIONAL, + 'Outputs only the last X amount of errors. Use as --limit 10 / -l 10 [default 5]', + 5 + ) + ->setHelp('The log outputs the Errors Log in Console') + ; + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->options = [ + 'trace' => $this->input->getOption('trace'), + 'limit' => $this->input->getOption('limit') + ]; + + if (!file_exists($this->logfile)) { + $this->output->writeln("\n" . "Log file not found." . "\n"); + exit; + } + + $log = file_get_contents($this->logfile); + $lines = explode("\n", $log); + + if (!is_numeric($this->options['limit'])) { + $this->options['limit'] = 5; + } + + $lines = array_slice($lines, -($this->options['limit'] + 1)); + + foreach ($lines as $line) { + $this->output->writeln($this->parseLine($line)); + } + } + + /** + * @param $line + * + * @return null|string + */ + protected function parseLine($line) + { + $bit = explode(': ', $line); + $line1 = explode('] ', $bit[0]); + + if (!$line1[0]) { + return null; + } + + $line2 = explode(' - ', $bit[1]); + + $date = $line1[0] . ']'; + $type = str_replace('grav.', '', $line1[1]); + $color = $this->colors[$type]; + $error = $line2[0]; + $trace = implode(': ', array_slice($bit, 2)); + + $output = []; + + $output[] = ''; + $output[] = '' . $date . ''; + $output[] = sprintf(' <%s>%s ' . $error . '', $color, $type, $color); + + if ($this->options['trace']) { + $output[] = ' TRACE: '; + $output[] = ' ' . $trace; + } + + $output[] = '' . str_repeat('-', strlen($date)) . ''; + + return implode("\n", $output); + } +} + diff --git a/sandbox/grav/user/plugins/error/error.php b/sandbox/grav/user/plugins/error/error.php new file mode 100644 index 0000000000..71b7d352af --- /dev/null +++ b/sandbox/grav/user/plugins/error/error.php @@ -0,0 +1,65 @@ + ['onPageNotFound', 0], + 'onGetPageTemplates' => ['onGetPageTemplates', 0], + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', -10] + ]; + } + + /** + * Display error page if no page was found for the current route. + * + * @param Event $event + */ + public function onPageNotFound(Event $event) + { + /** @var Pages $pages */ + $pages = $this->grav['pages']; + + // Try to load user error page. + $page = $pages->dispatch($this->config->get('plugins.error.routes.404', '/error'), true); + + if (!$page) { + // If none provided use built in error page. + $page = new Page; + $page->init(new \SplFileInfo(__DIR__ . '/pages/error.md')); + } + + $event->page = $page; + $event->stopPropagation(); + } + + /** + * Add page template types. + */ + public function onGetPageTemplates(Event $event) + { + /** @var Types $types */ + $types = $event->types; + $types->register('error'); + } + + /** + * Add current directory to twig lookup paths. + */ + public function onTwigTemplatePaths() + { + $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; + } +} diff --git a/sandbox/grav/user/plugins/error/error.yaml b/sandbox/grav/user/plugins/error/error.yaml new file mode 100644 index 0000000000..0a51d4c4e4 --- /dev/null +++ b/sandbox/grav/user/plugins/error/error.yaml @@ -0,0 +1,3 @@ +enabled: true +routes: + 404: '/error' diff --git a/sandbox/grav/user/plugins/error/languages.yaml b/sandbox/grav/user/plugins/error/languages.yaml new file mode 100644 index 0000000000..fba0c489f6 --- /dev/null +++ b/sandbox/grav/user/plugins/error/languages.yaml @@ -0,0 +1,37 @@ +en: + PLUGIN_ERROR: + ERROR: "Error" + ERROR_MESSAGE: "Woops. Looks like this page doesn't exist." +de: + PLUGIN_ERROR: + ERROR: "Fehler" + ERROR_MESSAGE: "Uuups. Sieht aus als ob diese Seite nicht existiert." +hr: + PLUGIN_ERROR: + ERROR: "Greška" + ERROR_MESSAGE: "Uups. Izgleda da ova stranica ne postoji." + +ro: + PLUGIN_ERROR: + ERROR: "Eroare" + ERROR_MESSAGE: "Ooops. Se pare că pagina nu există." +fr: + PLUGIN_ERROR: + ERROR: "Erreur" + ERROR_MESSAGE: "Oups. Il semble que cette page n’existe pas." +it: + PLUGIN_ERROR: + ERROR: "Errore" + ERROR_MESSAGE: "Ooops. A quanto pare, questa pagina non esiste." +ru: + PLUGIN_ERROR: + ERROR: "Ошибка" + ERROR_MESSAGE: "Упс. Похоже, этой страницы не существует." +da: + PLUGIN_ERROR: + ERROR: "Fejl" + ERROR_MESSAGE: "Ups. Det ser ud til at siden ikke eksisterer." +zh: + PLUGIN_ERROR: + ERROR: "错误" + ERROR_MESSAGE: "呃,似乎这个页面不存在。" diff --git a/sandbox/grav/user/plugins/error/pages/error.md b/sandbox/grav/user/plugins/error/pages/error.md new file mode 100644 index 0000000000..615b50c370 --- /dev/null +++ b/sandbox/grav/user/plugins/error/pages/error.md @@ -0,0 +1,13 @@ +--- +title: Page not Found +robots: noindex,nofollow +template: error +routable: false +http_response_code: 404 +twig_first: true +process: + twig: true +--- + +{{ 'PLUGIN_ERROR.ERROR_MESSAGE'|t }} + diff --git a/sandbox/grav/user/plugins/error/templates/error.html.twig b/sandbox/grav/user/plugins/error/templates/error.html.twig new file mode 100644 index 0000000000..420702b36e --- /dev/null +++ b/sandbox/grav/user/plugins/error/templates/error.html.twig @@ -0,0 +1,3 @@ +

    {{ 'PLUGIN_ERROR.ERROR'|t }} {{ header.http_response_code }}

    + +

    {{ page.content|raw }}

    diff --git a/sandbox/grav/user/plugins/error/templates/error.json.twig b/sandbox/grav/user/plugins/error/templates/error.json.twig new file mode 100644 index 0000000000..27472f1393 --- /dev/null +++ b/sandbox/grav/user/plugins/error/templates/error.json.twig @@ -0,0 +1 @@ +{{ page.content|json_encode()|raw }} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/.eslintrc b/sandbox/grav/user/plugins/form/.eslintrc new file mode 100644 index 0000000000..1e57de5bdc --- /dev/null +++ b/sandbox/grav/user/plugins/form/.eslintrc @@ -0,0 +1,165 @@ +{ + "env": { + "browser": true, + "node": true + }, + + "parserOptions": { + "ecmaVersion": 6, + "sourceType": "module" + }, + + "rules": { + "accessor-pairs": 2, + "array-bracket-spacing": 0, + "block-scoped-var": 0, + "brace-style": [2, "1tbs", { "allowSingleLine": true }], + "camelcase": 0, + "comma-dangle": [2, "never"], + "comma-spacing": [2, { "before": false, "after": true }], + "comma-style": [2, "last"], + "complexity": 0, + "computed-property-spacing": 0, + "consistent-return": 0, + "consistent-this": 0, + "constructor-super": 2, + "curly": [2, "multi-line"], + "default-case": 0, + "dot-location": [2, "property"], + "dot-notation": 0, + "eol-last": 2, + "eqeqeq": [2, "allow-null"], + "func-names": 0, + "func-style": 0, + "generator-star-spacing": [2, { "before": true, "after": true }], + "guard-for-in": 0, + "handle-callback-err": [2, "^(err|error)$" ], + "indent": [2, 4, { "SwitchCase": 1 }], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "linebreak-style": 0, + "lines-around-comment": 0, + "max-nested-callbacks": 0, + "new-cap": [2, { "newIsCap": true, "capIsNew": false }], + "new-parens": 2, + "newline-after-var": 0, + "no-alert": 0, + "no-array-constructor": 2, + "no-caller": 2, + "no-catch-shadow": 0, + "no-cond-assign": 2, + "no-console": 0, + "no-constant-condition": 0, + "no-continue": 0, + "no-control-regex": 2, + "no-debugger": 2, + "no-delete-var": 2, + "no-div-regex": 0, + "no-dupe-args": 2, + "no-dupe-keys": 2, + "no-duplicate-case": 2, + "no-else-return": 0, + "no-empty": 0, + "no-empty-character-class": 2, + "no-eq-null": 0, + "no-eval": 2, + "no-ex-assign": 2, + "no-extend-native": 2, + "no-extra-bind": 2, + "no-extra-boolean-cast": 2, + "no-extra-parens": 0, + "no-extra-semi": 0, + "no-fallthrough": 2, + "no-floating-decimal": 2, + "no-func-assign": 2, + "no-implied-eval": 2, + "no-inline-comments": 0, + "no-inner-declarations": [2, "functions"], + "no-invalid-regexp": 2, + "no-irregular-whitespace": 2, + "no-iterator": 2, + "no-label-var": 2, + "no-labels": 2, + "no-lone-blocks": 2, + "no-lonely-if": 0, + "no-loop-func": 0, + "no-mixed-requires": 0, + "no-mixed-spaces-and-tabs": 2, + "no-multi-spaces": 2, + "no-multi-str": 2, + "no-multiple-empty-lines": [2, { "max": 1 }], + "no-native-reassign": 2, + "no-negated-in-lhs": 2, + "no-nested-ternary": 0, + "no-new": 2, + "no-new-func": 0, + "no-new-object": 2, + "no-new-require": 2, + "no-new-wrappers": 2, + "no-obj-calls": 2, + "no-octal": 2, + "no-octal-escape": 2, + "no-param-reassign": 0, + "no-path-concat": 0, + "no-process-env": 0, + "no-process-exit": 0, + "no-proto": 0, + "no-redeclare": 2, + "no-regex-spaces": 2, + "no-restricted-modules": 0, + "no-return-assign": 2, + "no-script-url": 0, + "no-self-compare": 2, + "no-sequences": 2, + "no-shadow": 0, + "no-shadow-restricted-names": 2, + "no-spaced-func": 2, + "no-sparse-arrays": 2, + "no-sync": 0, + "no-ternary": 0, + "no-this-before-super": 2, + "no-throw-literal": 2, + "no-trailing-spaces": 2, + "no-undef": 2, + "no-undef-init": 2, + "no-undefined": 0, + "no-underscore-dangle": 0, + "no-unexpected-multiline": 2, + "no-unneeded-ternary": 2, + "no-unreachable": 2, + "no-unused-expressions": 0, + "no-unused-vars": [2, { "vars": "all", "args": "none" }], + "no-use-before-define": 0, + "no-var": 0, + "no-void": 0, + "no-warning-comments": 0, + "no-with": 2, + "object-curly-spacing": 0, + "object-shorthand": 0, + "one-var": [2, { "initialized": "never" }], + "operator-assignment": 0, + "operator-linebreak": [2, "after", { "overrides": { "?": "before", ":": "before" } }], + "padded-blocks": 0, + "prefer-const": 0, + "quote-props": 0, + "quotes": [2, "single", "avoid-escape"], + "radix": 2, + "semi": [2, "always"], + "semi-spacing": 0, + "sort-vars": 0, + "keyword-spacing": [2, {"after": true, "overrides": {"throw": { "after": true}, "return": { "before": true }}}], + "space-before-blocks": [2, "always"], + "space-before-function-paren": [2, "never"], + "space-in-parens": [2, "never"], + "space-infix-ops": 2, + "space-unary-ops": [2, { "words": true, "nonwords": false }], + "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!"] }], + "strict": 0, + "use-isnan": 2, + "valid-jsdoc": 0, + "valid-typeof": 2, + "vars-on-top": 0, + "wrap-iife": [2, "any"], + "wrap-regex": 0, + "yoda": [2, "never"] + } +} diff --git a/sandbox/grav/user/plugins/form/.gitignore b/sandbox/grav/user/plugins/form/.gitignore new file mode 100644 index 0000000000..1df656a26f --- /dev/null +++ b/sandbox/grav/user/plugins/form/.gitignore @@ -0,0 +1,5 @@ +# OS Generated +.DS_Store* +/.idea +node_modules +*.js.map diff --git a/sandbox/grav/user/plugins/form/CHANGELOG.md b/sandbox/grav/user/plugins/form/CHANGELOG.md new file mode 100644 index 0000000000..52cc4e6872 --- /dev/null +++ b/sandbox/grav/user/plugins/form/CHANGELOG.md @@ -0,0 +1,499 @@ +# v2.9.1 +## 09/14/2017 + +1. [](#bugfix) + * Fixed backwards compatibility issue with conditional field [#188](https://github.com/getgrav/grav-plugin-form/pull/188) + +# v2.9.0 +## 09/07/2017 + +1. [](#new) + * Added **Refresh Prevention** capabilities (Not enabled by default) [#184](https://github.com/getgrav/grav-plugin-form/issues/184) + * Added support for field `attributes` [#176](https://github.com/getgrav/grav-plugin-form/pull/176) + * Added global variables for setting form classes + * Added support for new `select_optgroup` form field [#165](https://github.com/getgrav/grav-plugin-form/issues/165) +1. [](#improved) + * Moved messages output into partial to allow style overriding + * Logic cleanup + * Updated Italian and Russian translations +1. [](#bugfix) + * Fixed an issue with conditional field not always displaying properly + * Only add Twig form variable if not already set + * Fixed issue with multiple forms on a page failing on Captcha client-side validation [#182](https://github.com/getgrav/grav-plugin-form/issues/182) + * Fixed issue with Ajax forms return full form HTML on error [#163](https://github.com/getgrav/grav-plugin-form/issues/163) + +# v2.8.2 +## 08/18/2017 + +1. [](#new) + * Added new `columns` and `column` fields for controlled form layout + +# v2.8.1 +## 08/15/2017 + +1. [](#improved) + * Added extra class support to the default field for more flexible styling + +# v2.8.0 +## 07/16/2017 + +1. [](#bugfix) + * Fixed a typo in the spanish translation [#167](https://github.com/getgrav/grav-plugin-form/pull/167) + +# v2.8.0-rc.2 +## 06/22/2017 + +1. [](#improved) + * Add default client-side validation for captcha, with error popup [#139](https://github.com/getgrav/grav-plugin-form/issues/139) + * Added key observe for select + * Added Czech translation +1. [](#bugfix) + * Bug fix for radio type form field [#154](https://github.com/getgrav/grav-plugin-form/pull/154) + * Remove double escaping [#155](https://github.com/getgrav/grav-plugin-form/issues/154) + +# v2.8.0-rc.1 +## 05/22/2017 + +1. [](#new) + * Bundled as RC release for Grav/Admin RC releases + +# v2.7.1 +## 05/22/2017 + +1. [](#improved) + * Force modular sub-pages with forms to set `$never_cache_twig = true` to improve form processing reliability [#153](https://github.com/getgrav/grav-plugin-form/issues/153) + * Use new `Utils::getPagePathFromToken()` method + +# v2.7.0 +## 05/16/2017 + +1. [](#bugfix) + * Fix issue with dynamically added forms (Registration, Profile, Comments, etc) not processed [#149](https://github.com/getgrav/grav-plugin-form/issues/149) + * Fixed issue with nested values not being repopulated on form error [#140](https://github.com/getgrav/grav-plugin-form/issues/140) + +# v2.6.0 +## 05/04/2017 + +1. [](#new) + * Allow form item replacement in redirect location [#144](https://github.com/getgrav/grav-plugin-form/issues/144) +1. [](#bugfix) + * Fix regression with file uploads introduced in 2.5.0 + +# v2.5.0 +## 04/24/2017 + +1. [](#new) + * Support proper form handling with nested fields [#141](https://github.com/getgrav/grav-plugin-form/issues/141) +1. [](#bugfix) + * Added check for valid Grav forms before trying to create a form object + +# v2.4.0 +## 04/19/2017 + +1. [](#new) + * Added the ability for front-end forms to use advanced blueprint features such as `data-*@` and `config-*@` + * Added support for dynamically added pages to process forms properly + * Added a new avatar field for displaying account avatar + * Added method to get all `data` from a form + * Support `task` in button types +1. [](#improved) + * Added `step` to range field [#136](https://github.com/getgrav/grav-plugin-form/issues/136) + * Added a new default ajax handler twig template + * Moved twig events to always process even if forms are not defined + * Some code cleanup + * Handle `null` with session-based form + * Added support for append/prepend to number field +1. [](#bugfix) + * Always process form events as long as a `$_POST` exists [login #101](https://github.com/getgrav/grav-plugin-login/issues/101) + * Various fixes for `file` field + * Allow manually added pages to process forms and upload files + * Fixed issue with nested fileds not showing up in `data.*.twig` templates + +# v2.3.1 +## 03/23/2017 + +1. [](#bugfix) + * Only include `outerclasses` DIV if defined [#135](https://github.com/getgrav/grav-plugin-form/issues/135) + +# v2.3.0 +## 03/17/2017 + +1. [](#new) + * Ability to process any form on any page via `action:`. Super useful if you want to handle form processing on some other non-form page (or Ajax) + * Added the ability for the form to set the `template:` to use to render the form processing response. +1. [](#bugfix) + * Fix `number` field so it works with min value `0` [#130](https://github.com/getgrav/grav-plugin-form/issues/130) + +# v2.2.0 +## 03/13/2017 + +1. [](#new) + * Added new `fieldset` form field [#125](https://github.com/getgrav/grav-plugin-form/issues/125) + * Added new `conditional form field` to show fields only if some `condition` is set +1. [](#improved) + * Added the option to have outer-classes on buttons [#124](https://github.com/getgrav/grav-plugin-form/issues/124) + * Added the option to disable fields label if not defined [#126](https://github.com/getgrav/grav-plugin-form/issues/126) + +# v2.1.1 +## 02/17/2017 + +1. [](#improved) + * Better default output for select, checkbox and checkboxes fields in the form destination page and in the emails sent via form submit [#121](https://github.com/getgrav/grav-plugin-form/issues/121) + + +# v2.1.0 +## 02/10/2017 + +1. [](#improved) + * Reworked logic so form caching is based on `Pages::getPagesCacheId()` + * Added `url` option for button field +1. [](#bugfix) + * Fixed issue with `honeypot` field not throwing exception properly + +# v2.0.10 +## 02/08/2017 + +1. [](#improved) + * Optimistically set 'status' to `success` when requesting a form via Ajax. Form processing listeners should take care of setting status to something else +1. [](#bugfix) + * File uploads are now adding a `__form-file-uploader__` POST field to better allow identifying them with Ajax + * Require jQuery when using the File field, as it's needed by the form.min.js file required in the file upload functionality + +# v2.0.9 +## 01/24/2017 + +1. [](#bugfix) + * Translate the labels in data.html.twig [https://github.com/getgrav/grav-plugin-comments/issues/38](https://github.com/getgrav/grav-plugin-comments/issues/38) + * Fixed file input when `System` > `Twig` > `Autoescape` is set to `Yes` + +# v2.0.8 +## 12/13/2016 + +1. [](#new) + * RC released as stable + * Added a new `honeypot` field for form anti-spam protection + +# v2.0.8-rc.1 +## 11/26/2016 + +1. [](#bugfix) + * Fixed Forms 2.0 changes for registration form [#101](https://github.com/getgrav/grav-plugin-form/issues/101) + * Fixed errant reference to Grav DI container in Form#getPagePathFromToken [#105](https://github.com/getgrav/grav-plugin-form/issues/105) + * Fixed issue with spacer fields being displayed first, not in order [#104](https://github.com/getgrav/grav-plugin-form/issues/104) + +# v2.0.7 +## 11/17/2016 + +1. [](#improved) + * Added method to set all data in a form + * Added params to form action URL + * Added ability to add ids to buttons and to set them disabled +1. [](#bugfix) + * Moved Files Upload GC logic to function in front-end only + +# v2.0.6 +## 10/19/2016 + +1. [](#bugfix) + * Fixed translations for `display` field + * Fixed [#95](https://github.com/getgrav/grav-plugin-form/issues/95) multilanguage forms submission + * Fixed duplicate textarea class tag [#98](https://github.com/getgrav/grav-plugin-form/issues/98) + +# v2.0.5 +## 09/15/2016 + +1. [](#bugfix) + * Fix passing updating the header through event, no need for return value + +# v2.0.4 +## 09/15/2016 + +1. [](#improved) + * Allow filling the page header form dynamically (e.g. use case: Comments plugin) + +# v2.0.3 +## 09/12/2016 + +1. [](#improved) + * Use `Page::slug()` for form name if not set in the form itself (better backwards compatibility) + +# v2.0.2 +## 09/08/2016 + +1. [](#improved) + * Added support for Grav's autoescape twig setting + * Allow to add additional markup fields in form and field twig overrides + * Updated the french language translation + +# v2.0.1 +## 09/07/2016 + +1. [](#bugfix) + * Fixed a backwards compatibility issue with Admin forms + +# v2.0.0 +## 09/07/2016 + +1. [](#new) + * Forms now supports multiple forms per page! + * Access forms from any other page within the current page + * Instantiate forms directly in page content with Twig processing enabled + * New Twig function to get forms data from any other page + * Ability to use Twig in saved filename + * Reworked the `file` field. All files get uploaded via Ajax and are stored upon Submit. Fully backward compatible, `file` field now includes also a `limit` and `filesize` option. The former determines how many files are allowed to be uploaded when in combination with `multiple: true` (default: 10), the latter determines the file size limit (in MB) allowed for each file (default: 5MB) +1. [](#improved) + * Added several missing HTML5 form input field types [#87](https://github.com/getgrav/grav-plugin-form/issues/87) + * Added Support for CSS id in form definition + +# v1.3.2 +## 08/10/2016 + +1. [](#improved) + * Added Romanian translation +1. [](#bugfix) + * Fixed an issue with Recaptcha secret throwing errors [#84](https://github.com/getgrav/grav-plugin-form/pull/84) + +# v1.3.1 +## 07/27/2016 + +1. [](#improved) + * Added support for multiple emails in `email` field (add `multiple: true` to enable) +1. [](#bugfix) + * Fixed backward incompatibility with forms submission and data retrieval [getgrav/grav#933](https://github.com/getgrav/grav/issues/933) + +# v1.3.0 +## 07/14/2016 + +1. [](#improved) + * When uploading a file through a form, if the file is already existing prepend the current day and time to the filename instead of overwriting it. + +# v1.3.0-rc.4 +## 06/21/2016 + +1. [](#bugfix) + * Fixed running on Grav 1.0.x + +# v1.3.0-rc.3 +## 06/17/2016 + +1. [](#new) + * Set hints for checkboxes options and allow field descriptions + +# v1.3.0-rc.2 +## 06/08/2016 + +1. [](#new) + * Allow to process Twig in a hidden field, by setting `evaluate: true` + +# v1.3.0-rc.1 +## 06/01/2016 + +1. [](#improved) + * French updated + +# v1.3.0-beta.6 +## 05/23/2016 + +1. [](#new) + * Added support for advanced blueprint functionality in forms + * Added site-wide form options to set Google Captcha site + secret keys [#34](https://github.com/getgrav/grav-plugin-form/pull/34) + * Session-based 'flash' storage of form for redirects [#48](https://github.com/getgrav/grav-plugin-form/issues/48) + * Added ability to **append** to file if you include a `process: save: body:` template attribute [#65](https://github.com/getgrav/grav-plugin-form/issues/65) +1. [](#improved) + * Support `keyname` form format like admin forms + * Added backwards compatibility for Captcha field + * Added 'markdown-notices' style output for better errors + * Added `Forms::getValue()` method to retrieve values programatically + * Changed `datetime` form field to simply extend `text` until implemented + * Updated french language +1. [](#bugfix) + * Refactored the files upload logic + * Missing Language string + * Fixed errors not getting output + +# v1.3.0-beta.5 +## 05/12/2016 + +1. [](#improved) + * Moved form/field.html.twig file to the default folder, to be more easily extended in themes + +# v1.3.0-beta.4 +## 05/04/2016 + +1. [](#new) + * Added support for `prepend` and `append` field attributes for Text input + +# v1.3.0-beta.3 +## 05/03/2016 + +1. [](#bugfix) + * Fix for select field admin translation + +# v1.3.0-beta.2 +## 04/27/2016 + +1. [](#bugfix) + * Fix for autoescape in spacer and display form fields + * Fix issue with form reset action [#66](https://github.com/getgrav/grav-plugin-form/pull/66) + +# v1.3.0-beta.1 +## 04/20/2016 + +1. [](#new) + * Added the HTML5 `range` input field with `min` and `max` parameters +1. [](#improved) + * Allow to override classes in Form definition for the form element + * Add more blocks in the Form twig template, so classes can be overridden more easily in themes + * Reworked some fields to fit the new Admin + * Use `scope` for form fields to allow fields to be excluded from the data by adding `input@: false` to their definition + * Added german translation + * Allow to add inline Twig to the form message definition +1. [](#bugfix) + * Fixed the form action URL for home page forms + * Fix stopping form events propagation, correctly stop when one event is stopped + * Allow to translate the fields placeholders and the form message + * Fix captcha javascript function ordering. Also, render it in the site active language + * Support attribute `for="id"` on label for checkbox + * Fix select fields with the multiple option enabled + * Fixed select options escaping with autoescape on - [#502](https://github.com/getgrav/grav-plugin-admin/issues/502) + +# v1.2.2 +## 02/11/2016 + +1. [](#bugfix) + * Fixed case issue when including form file. + +# v1.2.1 +## 02/11/2016 + +1. [](#new) + * Allow placeholder for **select** field +1. [](#improved) + * Use common language strings in blueprints + * Use `for` attribute in labels + * Improved `README.md` + * Code lint +1. [](#bugfix) + * Moved `nl2br` to correct place or will break for arrays + +# v1.2.0 +## 01/06/2016 + +1. [](#bugfix) + * Correctly merge the file field configuration + * restore full file information save + +# v1.1.0 +## 12/18/2015 + +1. [](#new) + * Croatian translation + * Added id, style, and disabled options to select fields +1. [](#improved) + * Allow adding form labels and help text as lang strings + * Allow translating field content + * Allow translating button and checkbox labels + * Allow adding classes to the form field container with `field.outerclasses` + * Updated French translation +1. [](#bugfix) + * Fixed error message on file upload + * Fixed overriding defaults for the file type in forms + +# v1.0.3 +## 12/11/2015 + +1. [](#improved) + * Updated languages + * Allow an action to stop processing +1. [](#bugfix) + * Fix captcha validation + * Fix issue where Form was unsetting valid page + +# v1.0.2 +## 12/01/2015 + +1. [](#bugfix) + * Fixed merge of defaults settings + * Support for arrays in `data.txt.twig` + * Fixed blueprint for admin + +# v1.0.1 +## 12/01/2015 + +1. [](#new) + * New **file upload** field + * Added modular form template + * Spanish translation + * Hungarian translation + * Italian translation + +# v1.0.0 +## 11/21/2015 + +1. [](#new) + * Server-side validation of forms #11 + * Added french translation + * Added **nonce** form security +1. [](#improved) + * Show a more meaningful error when the display page is not found + * Added links to learn site for form examples + * Label can be omitted + * Allow user to set the CSS class for buttons +1. [](#bugfix) + * Fixed multi-language forms + * Checkbox is translatable + * Minor fixes + +# v0.6.0 +## 10/21/2015 + +1. [](#bugfix) + * Fixed for missing attributes in textarea field + * Fixed checkbox inputs + +# v0.5.0 +## 10/15/2015 + +1. [](#new) + * New `operation` param to allow different file saving strategies + * Ability to add new file saving strategies + * Now calls a `process()` method during form processing +1. [](#improved) + * Added server-side captcha validation and removed front-end validation + * Allow `filename` instead of `prefix`, `format` + `extension` +1. [](#bugfix) + * Fixed radio inputs + +# v0.4.0 +## 9/16/2015 + +1. [](#new) + * PHP server-side form validation + * Added new Google Catpcha field with front-end validation +1. [](#improved) + * Add defaults for forms, moved from the themes to the Form plugin + * Store multi-line fields with line endings converted to HTML + +# v0.3.0 +## 9/11/2015 + +1. [](#improved) + * Refactored all the forms fields + +# v0.2.1 +## 08/24/2015 + +1. [](#improved) + * Translated tooltips + +# v0.2.0 +## 08/11/2015 + +1. [](#improved) + * Disable `enable` in admin + +# v0.1.0 +## 08/04/2015 + +1. [](#new) + * ChangeLog started... diff --git a/sandbox/grav/user/plugins/form/LICENSE b/sandbox/grav/user/plugins/form/LICENSE new file mode 100644 index 0000000000..484793ad19 --- /dev/null +++ b/sandbox/grav/user/plugins/form/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Grav + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sandbox/grav/user/plugins/form/README.md b/sandbox/grav/user/plugins/form/README.md new file mode 100644 index 0000000000..fb9e682159 --- /dev/null +++ b/sandbox/grav/user/plugins/form/README.md @@ -0,0 +1,29 @@ +# Grav Form Plugin + +The **form plugin** for [Grav](http://github.com/getgrav/grav) adds the ability to create and use forms. This is currently used extensively by the **admin** and **login** plugins. + +# Installation + +The form plugin is easy to install with GPM. + +``` +$ bin/gpm install form +``` + +# Configuration + +Simply copy the `user/plugins/form/form.yaml` into `user/config/plugins/form.yaml` and make your modifications. + +``` +enabled: true +``` + +# How to use the Form Plugin + +The Learn site has two pages describing how to use the Form Plugin: +- [Forms](http://learn.getgrav.org/advanced/forms) +- [Add a contact form](http://learn.getgrav.org/forms/forms/example-form) + +# Using email + +Note: when using email functionality in your forms, make sure you have configured the Email plugin correctly. In particular, make sure you configured the "Email from" and "Email to" email addresses in the Email plugin with your email address diff --git a/sandbox/grav/user/plugins/form/app/main.js b/sandbox/grav/user/plugins/form/app/main.js new file mode 100644 index 0000000000..f0f2a6a6ea --- /dev/null +++ b/sandbox/grav/user/plugins/form/app/main.js @@ -0,0 +1,289 @@ +import $ from 'jquery'; +import Dropzone from 'dropzone'; +import { config, translations } from 'grav-form'; + +let request = {}; + +// translations +const Dictionary = { + dictCancelUpload: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD, + dictCancelUploadConfirmation: translations.PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD_CONFIRMATION, + dictDefaultMessage: translations.PLUGIN_FORM.DROPZONE_DEFAULT_MESSAGE, + dictFallbackMessage: translations.PLUGIN_FORM.DROPZONE_FALLBACK_MESSAGE, + dictFallbackText: translations.PLUGIN_FORM.DROPZONE_FALLBACK_TEXT, + dictFileTooBig: translations.PLUGIN_FORM.DROPZONE_FILE_TOO_BIG, + dictInvalidFileType: translations.PLUGIN_FORM.DROPZONE_INVALID_FILE_TYPE, + dictMaxFilesExceeded: translations.PLUGIN_FORM.DROPZONE_MAX_FILES_EXCEEDED, + dictRemoveFile: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE, + dictRemoveFileConfirmation: translations.PLUGIN_FORM.DROPZONE_REMOVE_FILE_CONFIRMATION, + dictResponseError: translations.PLUGIN_FORM.DROPZONE_RESPONSE_ERROR +}; + +Dropzone.autoDiscover = false; + +const DropzoneMediaConfig = { + createImageThumbnails: { thumbnailWidth: 150 }, + addRemoveLinks: false, + dictDefaultMessage: Dictionary.dictDefaultMessage, + dictRemoveFileConfirmation: Dictionary.dictRemoveFileConfirmation, + previewTemplate: ` +
    +
    + +
    +
    +
    +
    +
    +
    + + + +
    + + + + Check + Created with Sketch. + + + + + + +
    +
    + + + + error + Created with Sketch. + + + + + + + +
    +
    `.trim() +}; + +export default class FilesField { + constructor({ container = '.dropzone.files-upload', options = {} } = {}) { + this.container = $(container); + if (!this.container.length) { return; } + + this.urls = {}; + this.options = Object.assign({}, Dictionary, DropzoneMediaConfig, { + klass: this, + url: this.container.data('file-url-add') || config.current_url, + acceptedFiles: this.container.data('media-types'), + init: this.initDropzone + }, this.container.data('dropzone-options'), options); + + this.dropzone = new Dropzone(container, this.options); + this.dropzone.on('complete', this.onDropzoneComplete.bind(this)); + this.dropzone.on('success', this.onDropzoneSuccess.bind(this)); + this.dropzone.on('removedfile', this.onDropzoneRemovedFile.bind(this)); + this.dropzone.on('sending', this.onDropzoneSending.bind(this)); + this.dropzone.on('error', this.onDropzoneError.bind(this)); + } + + initDropzone() { + let files = this.options.klass.container.find('[data-file]'); + let dropzone = this; + if (!files.length) { return; } + + files.each((index, file) => { + file = $(file); + let data = file.data('file'); + let mock = { + name: data.name, + size: data.size, + type: data.type, + status: Dropzone.ADDED, + accepted: true, + url: this.options.url, + removeUrl: data.remove + }; + + dropzone.files.push(mock); + dropzone.options.addedfile.call(dropzone, mock); + if (mock.type.match(/^image\//)) dropzone.options.thumbnail.call(dropzone, mock, data.path); + + file.remove(); + }); + } + + onDropzoneSending(file, xhr, formData) { + formData.append('__form-name__', this.container.closest('form').find('[name="__form-name__"]').val()); + formData.append('__form-file-uploader__', 1); + formData.append('name', this.options.dotNotation); + formData.append('form-nonce', config.form_nonce); + formData.append('task', 'filesupload'); + } + + onDropzoneSuccess(file, response, xhr) { + if (this.options.reloadPage) { + global.location.reload(); + } + + // store params for removing file from session before it gets saved + if (response.session) { + file.sessionParams = response.session; + file.removeUrl = this.options.url; + + // Touch field value to force a mutation detection + const input = this.container.find('[name][type="hidden"]'); + const value = input.val(); + input.val(value + ' '); + } + + return this.handleError({ + file, + data: response, + mode: 'removeFile', + msg: `

    ${translations.PLUGIN_FORM.FILE_ERROR_UPLOAD} ${file.name}

    +
    ${response.message}
    ` + }); + } + + onDropzoneComplete(file) { + if (!file.accepted && !file.rejected) { + let data = { + status: 'error', + message: `${translations.PLUGIN_FORM.FILE_UNSUPPORTED}: ${file.name.match(/\..+/).join('')}` + }; + + return this.handleError({ + file, + data, + mode: 'removeFile', + msg: `

    ${translations.PLUGIN_FORM.FILE_ERROR_ADD} ${file.name}

    +
    ${data.message}
    ` + }); + } + + if (this.options.reloadPage) { + global.location.reload(); + } + } + + onDropzoneRemovedFile(file, ...extra) { + if (!file.accepted || file.rejected) { return; } + let url = file.removeUrl || this.urls.delete; + let path = (url || '').match(/path:(.*)\//); + let body = { filename: file.name }; + + if (file.sessionParams) { + body.task = 'filessessionremove'; + body.session = file.sessionParams; + } + + request(url, { method: 'post', body }, () => { + if (!path) { return; } + + path = global.atob(path[1]); + let input = this.container.find('[name][type="hidden"]'); + let data = JSON.parse(input.val() || '{}'); + delete data[path]; + input.val(JSON.stringify(data)); + }); + } + + onDropzoneError(file, response, xhr) { + let message = xhr && response.error ? response.error.message : response; + $(file.previewElement).find('[data-dz-errormessage]').html(message); + + return this.handleError({ + file, + data: { status: 'error' }, + msg: `
    ${message}
    ` + }); + } + + handleError(options) { + return true; + /* let { file, data, mode, msg } = options; + if (data.status !== 'error' && data.status !== 'unauthorized') { return; } + + switch (mode) { + case 'addBack': + if (file instanceof File) { + this.dropzone.addFile.call(this.dropzone, file); + } else { + this.dropzone.files.push(file); + this.dropzone.options.addedfile.call(this.dropzone, file); + this.dropzone.options.thumbnail.call(this.dropzone, file, file.extras.url); + } + + break; + case 'removeFile': + default: + if (~this.dropzone.files.indexOf(file)) { + file.rejected = true; + this.dropzone.removeFile.call(this.dropzone, file, { silent: true }); + } + + break; + } + + let modal = $('[data-remodal-id="generic"]'); + modal.find('.error-content').html(msg); + $.remodal.lookup[modal.data('remodal')].open(); */ + } +} + +export function UriToMarkdown(uri) { + uri = uri.replace(/@3x|@2x|@1x/, ''); + uri = uri.replace(/\(/g, '%28'); + uri = uri.replace(/\)/g, '%29'); + + return uri.match(/\.(jpe?g|png|gif|svg)$/i) ? `![](${uri})` : `[${decodeURI(uri)}](${uri})`; +} + +let instances = []; +let cache = $(); +const onAddedNodes = (event, target/* , record, instance */) => { + let files = $(target).find('.dropzone.files-upload'); + if (!files.length) { return; } + + files.each((index, file) => { + file = $(file); + if (!~cache.index(file)) { + addNode(file); + } + }); +}; + +const addNode = (container) => { + container = $(container); + let input = container.find('input[type="file"]'); + let settings = container.data('grav-file-settings') || {}; + + if (settings.accept && ~settings.accept.indexOf('*')) { + settings.accept = ['']; + } + + let options = { + url: container.data('file-url-add') || (container.closest('form').attr('action') || config.current_url) + '.json', + paramName: settings.paramName || 'file', + dotNotation: settings.name || 'file', + acceptedFiles: settings.accept ? settings.accept.join(',') : input.attr('accept') || container.data('media-types'), + maxFilesize: settings.filesize || 256, + maxFiles: settings.limit || null + }; + + cache = cache.add(container); + container = container[0]; + instances.push(new FilesField({ container, options })); +}; + +export let Instances = (() => { + $('.dropzone.files-upload').each((i, container) => addNode(container)); + $('body').on('mutation._grav', onAddedNodes); + + return instances; +})(); + diff --git a/sandbox/grav/user/plugins/form/assets/dropzone.min.css b/sandbox/grav/user/plugins/form/assets/dropzone.min.css new file mode 100644 index 0000000000..d04515e270 --- /dev/null +++ b/sandbox/grav/user/plugins/form/assets/dropzone.min.css @@ -0,0 +1 @@ +@-webkit-keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%, 70%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@-moz-keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%, 70%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@keyframes passing-through{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%, 70%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}100%{opacity:0;-webkit-transform:translateY(-40px);-moz-transform:translateY(-40px);-ms-transform:translateY(-40px);-o-transform:translateY(-40px);transform:translateY(-40px)}}@-webkit-keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}}@-moz-keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}}@keyframes slide-in{0%{opacity:0;-webkit-transform:translateY(40px);-moz-transform:translateY(40px);-ms-transform:translateY(40px);-o-transform:translateY(40px);transform:translateY(40px)}30%{opacity:1;-webkit-transform:translateY(0px);-moz-transform:translateY(0px);-ms-transform:translateY(0px);-o-transform:translateY(0px);transform:translateY(0px)}}@-webkit-keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}@-moz-keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}@keyframes pulse{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}10%{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-ms-transform:scale(1.1);-o-transform:scale(1.1);transform:scale(1.1)}20%{-webkit-transform:scale(1);-moz-transform:scale(1);-ms-transform:scale(1);-o-transform:scale(1);transform:scale(1)}}.dropzone,.dropzone *{box-sizing:border-box}.dropzone{min-height:150px;border:2px solid rgba(0,0,0,0.3);background:white;padding:20px 20px}.dropzone.dz-clickable{cursor:pointer}.dropzone.dz-clickable *{cursor:default}.dropzone.dz-clickable .dz-message,.dropzone.dz-clickable .dz-message *{cursor:pointer}.dropzone.dz-started .dz-message{display:none}.dropzone.dz-drag-hover{border-style:solid}.dropzone.dz-drag-hover .dz-message{opacity:0.5}.dropzone .dz-message{text-align:center;margin:2em 0}.dropzone .dz-preview{position:relative;display:inline-block;vertical-align:top;margin:16px;min-height:100px}.dropzone .dz-preview:hover{z-index:1000}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview.dz-file-preview .dz-image{border-radius:20px;background:#999;background:linear-gradient(to bottom, #eee, #ddd)}.dropzone .dz-preview.dz-file-preview .dz-details{opacity:1}.dropzone .dz-preview.dz-image-preview{background:white}.dropzone .dz-preview.dz-image-preview .dz-details{-webkit-transition:opacity 0.2s linear;-moz-transition:opacity 0.2s linear;-ms-transition:opacity 0.2s linear;-o-transition:opacity 0.2s linear;transition:opacity 0.2s linear}.dropzone .dz-preview .dz-remove{font-size:14px;text-align:center;display:block;cursor:pointer;border:none}.dropzone .dz-preview .dz-remove:hover{text-decoration:underline}.dropzone .dz-preview:hover .dz-details{opacity:1}.dropzone .dz-preview .dz-details{z-index:20;position:absolute;top:0;left:0;opacity:0;font-size:13px;min-width:100%;max-width:100%;padding:2em 1em;text-align:center;color:rgba(0,0,0,0.9);line-height:150%}.dropzone .dz-preview .dz-details .dz-size{margin-bottom:1em;font-size:16px}.dropzone .dz-preview .dz-details .dz-filename{white-space:nowrap}.dropzone .dz-preview .dz-details .dz-filename:hover span{border:1px solid rgba(200,200,200,0.8);background-color:rgba(255,255,255,0.8)}.dropzone .dz-preview .dz-details .dz-filename:not(:hover){overflow:hidden;text-overflow:ellipsis}.dropzone .dz-preview .dz-details .dz-filename:not(:hover) span{border:1px solid transparent}.dropzone .dz-preview .dz-details .dz-filename span,.dropzone .dz-preview .dz-details .dz-size span{background-color:rgba(255,255,255,0.4);padding:0 0.4em;border-radius:3px}.dropzone .dz-preview:hover .dz-image img{-webkit-transform:scale(1.05, 1.05);-moz-transform:scale(1.05, 1.05);-ms-transform:scale(1.05, 1.05);-o-transform:scale(1.05, 1.05);transform:scale(1.05, 1.05);-webkit-filter:blur(8px);filter:blur(8px)}.dropzone .dz-preview .dz-image{border-radius:20px;overflow:hidden;width:120px;height:120px;position:relative;display:block;z-index:10}.dropzone .dz-preview .dz-image img{display:block}.dropzone .dz-preview.dz-success .dz-success-mark{-webkit-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);-moz-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);-ms-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);-o-animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1);animation:passing-through 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview.dz-error .dz-error-mark{opacity:1;-webkit-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);-moz-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);-ms-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);-o-animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1);animation:slide-in 3s cubic-bezier(0.77, 0, 0.175, 1)}.dropzone .dz-preview .dz-success-mark,.dropzone .dz-preview .dz-error-mark{pointer-events:none;opacity:0;z-index:500;position:absolute;display:block;top:50%;left:50%;margin-left:-27px;margin-top:-27px}.dropzone .dz-preview .dz-success-mark svg,.dropzone .dz-preview .dz-error-mark svg{display:block;width:54px;height:54px}.dropzone .dz-preview.dz-processing .dz-progress{opacity:1;-webkit-transition:all 0.2s linear;-moz-transition:all 0.2s linear;-ms-transition:all 0.2s linear;-o-transition:all 0.2s linear;transition:all 0.2s linear}.dropzone .dz-preview.dz-complete .dz-progress{opacity:0;-webkit-transition:opacity 0.4s ease-in;-moz-transition:opacity 0.4s ease-in;-ms-transition:opacity 0.4s ease-in;-o-transition:opacity 0.4s ease-in;transition:opacity 0.4s ease-in}.dropzone .dz-preview:not(.dz-processing) .dz-progress{-webkit-animation:pulse 6s ease infinite;-moz-animation:pulse 6s ease infinite;-ms-animation:pulse 6s ease infinite;-o-animation:pulse 6s ease infinite;animation:pulse 6s ease infinite}.dropzone .dz-preview .dz-progress{opacity:1;z-index:1000;pointer-events:none;position:absolute;height:16px;left:50%;top:50%;margin-top:-8px;width:80px;margin-left:-40px;background:rgba(255,255,255,0.9);-webkit-transform:scale(1);border-radius:8px;overflow:hidden}.dropzone .dz-preview .dz-progress .dz-upload{background:#333;background:linear-gradient(to bottom, #666, #444);position:absolute;top:0;left:0;bottom:0;width:0;-webkit-transition:width 300ms ease-in-out;-moz-transition:width 300ms ease-in-out;-ms-transition:width 300ms ease-in-out;-o-transition:width 300ms ease-in-out;transition:width 300ms ease-in-out}.dropzone .dz-preview.dz-error .dz-error-message{display:block}.dropzone .dz-preview.dz-error:hover .dz-error-message{opacity:1;pointer-events:auto}.dropzone .dz-preview .dz-error-message{pointer-events:none;z-index:1000;position:absolute;display:block;display:none;opacity:0;-webkit-transition:opacity 0.3s ease;-moz-transition:opacity 0.3s ease;-ms-transition:opacity 0.3s ease;-o-transition:opacity 0.3s ease;transition:opacity 0.3s ease;border-radius:8px;font-size:13px;top:130px;left:-10px;width:140px;background:#be2626;background:linear-gradient(to bottom, #be2626, #a92222);padding:0.5em 1.2em;color:white}.dropzone .dz-preview .dz-error-message:after{content:'';position:absolute;top:-6px;left:64px;width:0;height:0;border-left:6px solid transparent;border-right:6px solid transparent;border-bottom:6px solid #be2626} diff --git a/sandbox/grav/user/plugins/form/assets/form-styles.css b/sandbox/grav/user/plugins/form/assets/form-styles.css new file mode 100644 index 0000000000..c5709c7fd5 --- /dev/null +++ b/sandbox/grav/user/plugins/form/assets/form-styles.css @@ -0,0 +1,69 @@ +.form-errors { + background: #fdf7f7; + color: #b52b27; + padding: 0 5px; + border-radius: 3px; + margin-bottom: 10px; +} + +.form-errors p { + margin: 0; + line-height: 2; +} + +.form-field.has-errors .form-errors { + margin-top: -5px; +} + +.form-field.has-errors label { + color: #b52b27; +} + +.form-field.has-errors .form-input-wrapper input, +.form-field.has-errors .form-input-wrapper select, +.form-field.has-errors .form-input-wrapper textarea { + border: 1px solid #d9534f; +} + +.form-input-file.dropzone { + position: relative; + min-height: 70px; + border-radius: 3px; + margin-bottom: .85rem; + border: 2px dashed #ccc; + color: #aaa; + padding: 0.5rem; +} + +.form-input-file input { + display: none; +} + +.form-input-file .dz-default.dz-message { + position: absolute; + text-align: center; + left: 0; + right: 0; + top: 50%; + transform: translateY(-50%); + margin: 0; +} + +.form-input-file.dropzone .dz-preview { + margin: 0.5rem; +} + +.form-input-file.dropzone .dz-preview:hover { + z-index: 2; +} + +.form-input-file.dropzone .dz-preview .dz-error-message { + min-width: 140px; + width: auto; +} + +.form-input-file.dropzone .dz-preview .dz-image, +.form-input-file.dropzone .dz-preview.dz-file-preview .dz-image { + border-radius: 3px; + z-index: 1; +} diff --git a/sandbox/grav/user/plugins/form/assets/form.min.js b/sandbox/grav/user/plugins/form/assets/form.min.js new file mode 100644 index 0000000000..3bcf31e946 --- /dev/null +++ b/sandbox/grav/user/plugins/form/assets/form.min.js @@ -0,0 +1,3 @@ +!function(e){function t(n){if(i[n])return i[n].exports;var o=i[n]={exports:{},id:n,loaded:!1};return e[n].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var i={};return t.m=e,t.c=i,t.p="",t(0)}([function(e,t,i){(function(e){"use strict";function n(e){return e&&e.__esModule?e:{default:e}}function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function r(e){return e=e.replace(/@3x|@2x|@1x/,""),e=e.replace(/\(/g,"%28"),e=e.replace(/\)/g,"%29"),e.match(/\.(jpe?g|png|gif|svg)$/i)?"![]("+e+")":"["+decodeURI(e)+"]("+e+")"}Object.defineProperty(t,"__esModule",{value:!0}),t.Instances=void 0;var s=function(){function e(e,t){for(var i=0;i\n
    \n\n
    \n
    \n
    \n
    \n
    \n
    \n\n\n\n
    \n\n \n \n Check\n Created with Sketch.\n \n \n \n \n \n \n
    \n
    \n\n \n \n error\n Created with Sketch.\n \n \n \n \n \n \n \n
    \n
    '.trim()},f=function(){function t(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},i=e.container,n=void 0===i?".dropzone.files-upload":i,r=e.options,s=void 0===r?{}:r;o(this,t),this.container=(0,a.default)(n),this.container.length&&(this.urls={},this.options=Object.assign({},h,m,{klass:this,url:this.container.data("file-url-add")||d.config.current_url,acceptedFiles:this.container.data("media-types"),init:this.initDropzone},this.container.data("dropzone-options"),s),this.dropzone=new p.default(n,this.options),this.dropzone.on("complete",this.onDropzoneComplete.bind(this)),this.dropzone.on("success",this.onDropzoneSuccess.bind(this)),this.dropzone.on("removedfile",this.onDropzoneRemovedFile.bind(this)),this.dropzone.on("sending",this.onDropzoneSending.bind(this)),this.dropzone.on("error",this.onDropzoneError.bind(this)))}return s(t,[{key:"initDropzone",value:function(){var e=this,t=this.options.klass.container.find("[data-file]"),i=this;t.length&&t.each(function(t,n){n=(0,a.default)(n);var o=n.data("file"),r={name:o.name,size:o.size,type:o.type,status:p.default.ADDED,accepted:!0,url:e.options.url,removeUrl:o.remove};i.files.push(r),i.options.addedfile.call(i,r),r.type.match(/^image\//)&&i.options.thumbnail.call(i,r,o.path),n.remove()})}},{key:"onDropzoneSending",value:function(e,t,i){i.append("__form-name__",this.container.closest("form").find('[name="__form-name__"]').val()),i.append("__form-file-uploader__",1),i.append("name",this.options.dotNotation),i.append("form-nonce",d.config.form_nonce),i.append("task","filesupload")}},{key:"onDropzoneSuccess",value:function(t,i,n){if(this.options.reloadPage&&e.location.reload(),i.session){t.sessionParams=i.session,t.removeUrl=this.options.url;var o=this.container.find('[name][type="hidden"]'),r=o.val();o.val(r+" ")}return this.handleError({file:t,data:i,mode:"removeFile",msg:"

    "+d.translations.PLUGIN_FORM.FILE_ERROR_UPLOAD+" "+t.name+"

    \n
    "+i.message+"
    "})}},{key:"onDropzoneComplete",value:function(t){if(!t.accepted&&!t.rejected){var i={status:"error",message:d.translations.PLUGIN_FORM.FILE_UNSUPPORTED+": "+t.name.match(/\..+/).join("")};return this.handleError({file:t,data:i,mode:"removeFile",msg:"

    "+d.translations.PLUGIN_FORM.FILE_ERROR_ADD+" "+t.name+"

    \n
    "+i.message+"
    "})}this.options.reloadPage&&e.location.reload()}},{key:"onDropzoneRemovedFile",value:function(t){var i=this;if(t.accepted&&!t.rejected){var n=t.removeUrl||this.urls.delete,o=(n||"").match(/path:(.*)\//),r={filename:t.name};t.sessionParams&&(r.task="filessessionremove",r.session=t.sessionParams),c(n,{method:"post",body:r},function(){if(o){o=e.atob(o[1]);var t=i.container.find('[name][type="hidden"]'),n=JSON.parse(t.val()||"{}");delete n[o],t.val(JSON.stringify(n))}})}}},{key:"onDropzoneError",value:function(e,t,i){var n=i&&t.error?t.error.message:t;return(0,a.default)(e.previewElement).find("[data-dz-errormessage]").html(n),this.handleError({file:e,data:{status:"error"},msg:"
    "+n+"
    "})}},{key:"handleError",value:function(e){return!0}}]),t}();t.default=f;var g=[],v=(0,a.default)(),F=function(e,t){var i=(0,a.default)(t).find(".dropzone.files-upload");i.length&&i.each(function(e,t){t=(0,a.default)(t),~v.index(t)||y(t)})},y=function(e){e=(0,a.default)(e);var t=e.find('input[type="file"]'),i=e.data("grav-file-settings")||{};i.accept&&~i.accept.indexOf("*")&&(i.accept=[""]);var n={url:e.data("file-url-add")||(e.closest("form").attr("action")||d.config.current_url)+".json",paramName:i.paramName||"file",dotNotation:i.name||"file",acceptedFiles:i.accept?i.accept.join(","):t.attr("accept")||e.data("media-types"),maxFilesize:i.filesize||256,maxFiles:i.limit||null};v=v.add(e),e=e[0],g.push(new f({container:e,options:n}))};t.Instances=function(){return(0,a.default)(".dropzone.files-upload").each(function(e,t){return y(t)}),(0,a.default)("body").on("mutation._grav",F),g}()}).call(t,function(){return this}())},function(e,t){e.exports=jQuery},function(e,t,i){(function(e){(function(){var t,i,n,o,r,s,l,a,u=[].slice,p={}.hasOwnProperty,d=function(e,t){function i(){this.constructor=e}for(var n in t)p.call(t,n)&&(e[n]=t[n]);return i.prototype=t.prototype,e.prototype=new i,e.__super__=t.prototype,e};l=function(){},i=function(){function e(){}return e.prototype.addEventListener=e.prototype.on,e.prototype.on=function(e,t){return this._callbacks=this._callbacks||{},this._callbacks[e]||(this._callbacks[e]=[]),this._callbacks[e].push(t),this},e.prototype.emit=function(){var e,t,i,n,o,r;if(n=arguments[0],e=2<=arguments.length?u.call(arguments,1):[],this._callbacks=this._callbacks||{},i=this._callbacks[n])for(o=0,r=i.length;o
    '),this.element.appendChild(i)),n=i.getElementsByTagName("span")[0],n&&(null!=n.textContent?n.textContent=this.options.dictFallbackMessage:null!=n.innerText&&(n.innerText=this.options.dictFallbackMessage)),this.element.appendChild(this.getFallbackForm())},resize:function(e){var t,i,n;return t={srcX:0,srcY:0,srcWidth:e.width,srcHeight:e.height},i=e.width/e.height,t.optWidth=this.options.thumbnailWidth,t.optHeight=this.options.thumbnailHeight,null==t.optWidth&&null==t.optHeight?(t.optWidth=t.srcWidth,t.optHeight=t.srcHeight):null==t.optWidth?t.optWidth=i*t.optHeight:null==t.optHeight&&(t.optHeight=1/i*t.optWidth),n=t.optWidth/t.optHeight,e.heightn?(t.srcHeight=e.height,t.srcWidth=t.srcHeight*n):(t.srcWidth=e.width,t.srcHeight=t.srcWidth/n),t.srcX=(e.width-t.srcWidth)/2,t.srcY=(e.height-t.srcHeight)/2,t},drop:function(e){return this.element.classList.remove("dz-drag-hover")},dragstart:l,dragend:function(e){return this.element.classList.remove("dz-drag-hover")},dragenter:function(e){return this.element.classList.add("dz-drag-hover")},dragover:function(e){return this.element.classList.add("dz-drag-hover")},dragleave:function(e){return this.element.classList.remove("dz-drag-hover")},paste:l,reset:function(){return this.element.classList.remove("dz-started")},addedfile:function(e){var i,n,o,r,s,l,a,u,p,d,c,h,m;if(this.element===this.previewsContainer&&this.element.classList.add("dz-started"),this.previewsContainer){for(e.previewElement=t.createElement(this.options.previewTemplate.trim()),e.previewTemplate=e.previewElement,this.previewsContainer.appendChild(e.previewElement),d=e.previewElement.querySelectorAll("[data-dz-name]"),r=0,a=d.length;r'+this.options.dictRemoveFile+""),e.previewElement.appendChild(e._removeLink)),n=function(i){return function(n){return n.preventDefault(),n.stopPropagation(),e.status===t.UPLOADING?t.confirm(i.options.dictCancelUploadConfirmation,function(){return i.removeFile(e)}):i.options.dictRemoveFileConfirmation?t.confirm(i.options.dictRemoveFileConfirmation,function(){return i.removeFile(e)}):i.removeFile(e)}}(this),h=e.previewElement.querySelectorAll("[data-dz-remove]"),m=[],l=0,p=h.length;l\n
    \n
    \n
    \n
    \n
    \n
    \n
    \n
    \n \n Check\n \n \n \n \n \n
    \n
    \n \n Error\n \n \n \n \n \n \n \n
    \n'},n=function(){var e,t,i,n,o,r,s;for(n=arguments[0],i=2<=arguments.length?u.call(arguments,1):[],r=0,s=i.length;r'+this.options.dictDefaultMessage+"")),this.clickableElements.length&&(n=function(e){return function(){return e.hiddenFileInput&&e.hiddenFileInput.parentNode.removeChild(e.hiddenFileInput),e.hiddenFileInput=document.createElement("input"),e.hiddenFileInput.setAttribute("type","file"),(null==e.options.maxFiles||e.options.maxFiles>1)&&e.hiddenFileInput.setAttribute("multiple","multiple"),e.hiddenFileInput.className="dz-hidden-input",null!=e.options.acceptedFiles&&e.hiddenFileInput.setAttribute("accept",e.options.acceptedFiles),null!=e.options.capture&&e.hiddenFileInput.setAttribute("capture",e.options.capture),e.hiddenFileInput.style.visibility="hidden",e.hiddenFileInput.style.position="absolute",e.hiddenFileInput.style.top="0",e.hiddenFileInput.style.left="0",e.hiddenFileInput.style.height="0",e.hiddenFileInput.style.width="0",document.querySelector(e.options.hiddenInputContainer).appendChild(e.hiddenFileInput),e.hiddenFileInput.addEventListener("change",function(){var t,i,o,r;if(i=e.hiddenFileInput.files,i.length)for(o=0,r=i.length;o',this.options.dictFallbackText&&(n+="

    "+this.options.dictFallbackText+"

    "),n+='',i=t.createElement(n),"FORM"!==this.element.tagName?(o=t.createElement('
    '),o.appendChild(i)):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method)),null!=o?o:i)},t.prototype.getExistingFallback=function(){var e,t,i,n,o,r;for(t=function(e){var t,i,n;for(i=0,n=e.length;i0){for(s=["TB","GB","MB","KB","b"],i=l=0,a=s.length;l=t){n=e/Math.pow(this.options.filesizeBase,4-i),o=r;break}n=Math.round(10*n)/10}return""+n+" "+o},t.prototype._updateMaxFilesReachedClass=function(){return null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")},t.prototype.drop=function(e){var t,i;e.dataTransfer&&(this.emit("drop",e),t=e.dataTransfer.files,this.emit("addedfiles",t),t.length&&(i=e.dataTransfer.items,i&&i.length&&null!=i[0].webkitGetAsEntry?this._addFilesFromItems(i):this.handleFiles(t)))},t.prototype.paste=function(e){var t,i;if(null!=(null!=e&&null!=(i=e.clipboardData)?i.items:void 0))return this.emit("paste",e),t=e.clipboardData.items,t.length?this._addFilesFromItems(t):void 0},t.prototype.handleFiles=function(e){var t,i,n,o;for(o=[],i=0,n=e.length;i0){for(r=0,s=i.length;r1024*this.options.maxFilesize*1024?i(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(e.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):t.isValidFile(e,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(i(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",e)):this.options.accept.call(this,e,i):i(this.options.dictInvalidFileType)},t.prototype.addFile=function(e){return e.upload={progress:0,total:e.size,bytesSent:0},this.files.push(e),e.status=t.ADDED,this.emit("addedfile",e),this._enqueueThumbnail(e),this.accept(e,function(t){return function(i){return i?(e.accepted=!1,t._errorProcessing([e],i)):(e.accepted=!0,t.options.autoQueue&&t.enqueueFile(e)),t._updateMaxFilesReachedClass()}}(this))},t.prototype.enqueueFiles=function(e){var t,i,n;for(i=0,n=e.length;i=t)&&(n=this.getQueuedFiles(),n.length>0)){if(this.options.uploadMultiple)return this.processFiles(n.slice(0,t-i));for(;e=T;p=0<=T?++O:--O)r.append(this._getParamName(p),e[p],this._renameFilename(e[p].name));return this.submitRequest(b,r,e)},t.prototype.submitRequest=function(e,t,i){return e.send(t)},t.prototype._finished=function(e,i,n){var o,r,s;for(r=0,s=e.length;rp;)t=o[4*(a-1)+3],0===t?r=a:p=a,a=r+p>>1;return u=a/s,0===u?1:u},s=function(e,t,i,n,o,s,l,a,u,p){var d;return d=r(t),e.drawImage(t,i,n,o,s,l,a,u,p/d)},o=function(e,t){var i,n,o,r,s,l,a,u,p;if(o=!1,p=!0,n=e.document,u=n.documentElement,i=n.addEventListener?"addEventListener":"attachEvent",a=n.addEventListener?"removeEventListener":"detachEvent",l=n.addEventListener?"":"on",r=function(i){if("readystatechange"!==i.type||"complete"===n.readyState)return("load"===i.type?e:n)[a](l+i.type,r,!1),!o&&(o=!0)?t.call(e,i.type||i):void 0},s=function(){var e;try{u.doScroll("left")}catch(t){return e=t,void setTimeout(s,50)}return r("poll")},"complete"!==n.readyState){if(n.createEventObject&&u.doScroll){try{p=!e.frameElement}catch(e){}p&&s()}return n[i](l+"DOMContentLoaded",r,!1),n[i](l+"readystatechange",r,!1),e[i](l+"load",r,!1)}},t._autoDiscoverFunction=function(){if(t.autoDiscover)return t.discover()},o(window,t._autoDiscoverFunction)}).call(this)}).call(t,i(3)(e))},function(e,t){e.exports=function(e){return e.webpackPolyfill||(e.deprecate=function(){},e.paths=[],e.children=[],e.webpackPolyfill=1),e}},function(e,t){e.exports=GravForm}]); +//# sourceMappingURL=form.min.js.map \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/blueprints.yaml b/sandbox/grav/user/plugins/form/blueprints.yaml new file mode 100644 index 0000000000..479d7ae2a7 --- /dev/null +++ b/sandbox/grav/user/plugins/form/blueprints.yaml @@ -0,0 +1,104 @@ +name: Form +version: 2.9.1 +description: Enables the forms handling +icon: check-square +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +keywords: plugin, form +homepage: https://github.com/getgrav/grav-plugin-form +bugs: https://github.com/getgrav/grav-plugin-form/issues +license: MIT + +dependencies: + - { name: grav, version: '>=1.3.0-rc.3' } + +form: + validation: strict + fields: + enabled: + type: hidden + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + general: + type: section + title: PLUGIN_FORM.GENERAL + + fields: + built_in_css: + type: toggle + label: PLUGIN_FORM.USE_BUILT_IN_CSS + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + + refresh_prevention: + type: toggle + label: PLUGIN_FORM.REFRESH_PREVENTION + help: PLUGIN_FORM.REFRESH_PREVENTION_HELP + highlight: 1 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + + files: + type: section + title: PLUGIN_FORM.FILES + + fields: + files.multiple: + type: toggle + label: PLUGIN_FORM.ALLOW_MULTIPLE + help: PLUGIN_FORM.ALLOW_MULTIPLE_HELP + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + files.destination: + type: text + label: PLUGIN_FORM.DESTINATION + help: PLUGIN_FORM.DESTINATION_HELP + default: '@self' + files.accept: + type: selectize + size: large + label: PLUGIN_FORM.ACCEPT + help: PLUGIN_FORM.ACCEPT_HELP + classes: fancy + default: + - image/* + validate: + type: commalist + + recaptcha: + type: section + title: PLUGIN_FORM.RECAPTCHA + + fields: + recaptcha.site_key: + type: text + label: PLUGIN_FORM.RECAPTCHA_SITE_KEY + help: PLUGIN_FORM.RECAPTCHA_SITE_KEY_HELP + default: '' + recaptcha.secret_key: + type: text + label: PLUGIN_FORM.RECAPTCHA_SECRET_KEY + help: PLUGIN_FORM.RECAPTCHA_SECRET_KEY_HELP + default: '' diff --git a/sandbox/grav/user/plugins/form/classes/form.php b/sandbox/grav/user/plugins/form/classes/form.php new file mode 100644 index 0000000000..2947396757 --- /dev/null +++ b/sandbox/grav/user/plugins/form/classes/form.php @@ -0,0 +1,654 @@ +page = $page->route(); + + $header = $page->header(); + $this->rules = isset($header->rules) ? $header->rules : []; + $this->header_data = isset($header->data) ? $header->data : []; + + if ($form) { + $this->items = $form; + } else { + if (isset($header->form)) { + $this->items = $header->form; // for backwards compatibility + } + } + + // Add form specific rules. + if (!empty($this->items['rules']) && is_array($this->items['rules'])) { + $this->rules += $this->items['rules']; + } + + // Set form name if not set. + if ($name && !is_int($name)) { + $this->items['name'] = $name; + } elseif (empty($this->items['name'])) { + $this->items['name'] = $page->slug(); + } + + // Set form id if not set. + if (empty($this->items['id'])) { + $inflector = new Inflector(); + $this->items['id'] = $inflector->hyphenize($this->items['name']); + } + + // Reset and initialize the form + $this->reset(); + } + + /** + * Custom serializer for this complex object + * + * @return string + */ + public function serialize() + { + $data = [ + 'items' => $this->items, + 'message' => $this->message, + 'message_color' => $this->message_color, + 'status' => $this->status, + 'header_data' => $this->header_data, + 'rules' => $this->rules, + 'data' => $this->data->toArray(), + 'values' => $this->values->toArray(), + 'page' => $this->page + ]; + return serialize($data); + } + + /** + * Custom unserializer for this complex object + * + * @param string $data + */ + public function unserialize($data) + { + $data = unserialize($data); + + $this->items = $data['items']; + $this->message = $data['message']; + $this->message_color = $data['message_color']; + $this->status = $data['status']; + $this->header_data = $data['header_data']; + $this->rules = $data['rules']; + + $name = $this->items['name']; + $items = $this->items; + $rules = $this->rules; + + $blueprint = function() use ($name, $items, $rules) { + $blueprint = new Blueprint($name, ['form' => $items, 'rules' => $rules]); + return $blueprint->load()->init(); + }; + + $this->data = new Data($data['data'], $blueprint); + $this->values = new Data($data['values']); + $this->page = $data['page']; + } + + /** + * Allow overriding of fields + * + * @param $fields + */ + public function setFields($fields) + { + $this->fields = $fields; + } + + /** + * Get the name of this form + * + * @return String + */ + public function name() + { + return $this->items['name']; + } + + /** + * Reset data. + */ + public function reset() + { + $name = $this->items['name']; + $grav = Grav::instance(); + + // Fix naming for fields (supports nested fields now!) + if (isset($this->items['fields'])) { + $this->items['fields'] = $this->processFields($this->items['fields']); + } + + $items = $this->items; + $rules = $this->rules; + + $blueprint = function() use ($name, $items, $rules) { + $blueprint = new Blueprint($name, ['form' => $items, 'rules' => $rules]); + return $blueprint->load()->init(); + }; + + $this->data = new Data($this->header_data, $blueprint); + $this->values = new Data(); + $this->fields = null; + $this->fields = $this->fields(); + + // Fire event + $grav->fireEvent('onFormInitialized', new Event(['form' => $this])); + + } + + protected function processFields($fields) + { + $types = Grav::instance()['plugins']->formFieldTypes; + + $return = array(); + foreach ($fields as $key => $value) { + + // default to text if not set + if (!isset($value['type'])) { + $value['type'] = 'text'; + } + + // manually merging the field types + if ($types !== null && key_exists($value['type'], $types)) { + $value += $types[$value['type']]; + } + + // Fix numeric indexes + if (is_numeric($key) && isset($value['name'])) { + $key = $value['name']; + } + if (isset($value['fields']) && is_array($value['fields'])) { + $value['fields'] = $this->processFields($value['fields']); + } + $return[$key] = $value; + } + return $return; + } + + public function fields() { + + if (is_null($this->fields)) { + $blueprint = $this->data->blueprints(); + + if (method_exists($blueprint, 'load')) { + // init the form to process directives + $blueprint->load()->init(); + + // fields set to processed blueprint fields + $this->fields = $blueprint->fields(); + } + } + + return $this->fields; + } + + /** + * Return page object for the form. + * + * @return Page + */ + public function page() + { + return Grav::instance()['pages']->dispatch($this->page); + } + + /** + * Get value of given variable (or all values). + * First look in the $data array, fallback to the $values array + * + * @param string $name + * + * @return mixed + */ + public function value($name = null, $fallback = false) + { + if (!$name) { + return $this->data; + } + + if ($this->data->get($name)) { + return $this->data->get($name); + } + + if ($fallback) { + return $this->values->get($name); + } + + return null; + } + + /** + * Set value of given variable in the values array + * + * @param string $name + * + * @return mixed + */ + public function setValue($name = null, $value = '') + { + if (!$name) { + return; + } + + $this->values->set($name, $value); + } + + /** + * Get a value from the form + * + * @param $name + * @return mixed + */ + public function getValue($name) + { + return $this->values->get($name); + } + + /** + * Get all data + * + * @return Data + */ + public function getData() + { + return $this->data; + } + + /** + * Set value of given variable in the data array + * + * @param string $name + * + * @return mixed + */ + public function setData($name = null, $value = '') + { + if (!$name) { + return false; + } + + $this->data->set($name, $value); + + return true; + } + + public function setAllData($array) + { + $this->data = new Data($array); + } + + /** + * Handles ajax upload for files. + * Stores in a flash object the temporary file and deals with potential file errors. + * + * @return mixed True if the action was performed. + */ + public function uploadFiles() + { + $post = $_POST; + $grav = Grav::instance(); + $uri = $grav['uri']->url; + $config = $grav['config']; + $session = $grav['session']; + + $settings = $this->data->blueprints()->schema()->getProperty($post['name']); + $settings = (object) array_merge( + ['destination' => $config->get('plugins.form.files.destination', 'self@'), + 'avoid_overwriting' => $config->get('plugins.form.files.avoid_overwriting', false), + 'random_name' => $config->get('plugins.form.files.random_name', false), + 'accept' => $config->get('plugins.form.files.accept', ['image/*']), + 'limit' => $config->get('plugins.form.files.limit', 10), + 'filesize' => $config->get('plugins.form.files.filesize', 5242880) // 5MB + ], + (array) $settings, + ['name' => $post['name']] + ); + + $upload = $this->normalizeFiles($_FILES['data'], $settings->name); + + // Handle errors and breaks without proceeding further + if ($upload->file->error != UPLOAD_ERR_OK) { + // json_response + return [ + 'status' => 'error', + 'message' => sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_UPLOAD', null, true), $upload->file->name, $this->upload_errors[$upload->file->error]) + ]; + } else { + // Remove the error object to avoid storing it + unset($upload->file->error); + + // we need to move the file at this stage or else + // it won't be available upon save later on + // since php removes it from the upload location + $tmp_dir = $grav['locator']->findResource('tmp://', true, true); + $tmp_file = $upload->file->tmp_name; + $tmp = $tmp_dir . '/uploaded-files/' . basename($tmp_file); + + Folder::create(dirname($tmp)); + if (!move_uploaded_file($tmp_file, $tmp)) { + // json_response + return [ + 'status' => 'error', + 'message' => sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_MOVE', null, true), '', $tmp) + ]; + } + + $upload->file->tmp_name = $tmp; + } + + // Handle file size limits + $settings->filesize *= 1048576; // 2^20 [MB in Bytes] + if ($settings->filesize > 0 && $upload->file->size > $settings->filesize) { + // json_response + return [ + 'status' => 'error', + 'message' => $grav['language']->translate('PLUGIN_FORM.EXCEEDED_GRAV_FILESIZE_LIMIT') + ]; + } + + + // Handle Accepted file types + // Accept can only be mime types (image/png | image/*) or file extensions (.pdf|.jpg) + $accepted = false; + $errors = []; + foreach ((array) $settings->accept as $type) { + // Force acceptance of any file when star notation + if ($type == '*') { + $accepted = true; + break; + } + + $isMime = strstr($type, '/'); + $find = str_replace('*', '.*', $type); + + $match = preg_match('#'. $find .'$#', $isMime ? $upload->file->type : $upload->file->name); + if (!$match) { + $message = $isMime ? 'The MIME type "' . $upload->file->type . '"' : 'The File Extension'; + $errors[] = $message . ' for the file "' . $upload->file->name . '" is not an accepted.'; + $accepted |= false; + } else { + $accepted |= true; + } + } + + if (!$accepted) { + // json_response + return [ + 'status' => 'error', + 'message' => implode('
    ', $errors) + ]; + } + + // Retrieve the current session of the uploaded files for the field + // and initialize it if it doesn't exist + $sessionField = base64_encode($uri); + $flash = $session->getFlashObject('files-upload'); + if (!$flash) { $flash = []; } + if (!isset($flash[$sessionField])) { $flash[$sessionField] = []; } + if (!isset($flash[$sessionField][$upload->field])) { $flash[$sessionField][$upload->field] = []; } + + // Set destination + $destination = Folder::getRelativePath(rtrim($settings->destination, '/')); + $destination = $this->getPagePathFromToken($destination); + + // Create destination if needed + if (!is_dir($destination)) { + Folder::mkdir($destination); + } + + // Generate random name if required + if ($settings->random_name) { + $extension = pathinfo($upload->file->name)['extension']; + $upload->file->name = Utils::generateRandomString(15) . '.' . $extension; + } + + // Handle conflicting name if needed + if ($settings->avoid_overwriting) { + if (file_exists($destination . '/' . $upload->file->name)) { + $upload->file->name = date('YmdHis') . '-' . $upload->file->name; + } + } + + // Prepare object for later save + $path = $destination . '/' . $upload->file->name; + $upload->file->path = $path; + // $upload->file->route = $page ? $path : null; + + // Prepare data to be saved later + $flash[$sessionField][$upload->field][$path] = (array) $upload->file; + + // Finally store the new uploaded file in the field session + $session->setFlashObject('files-upload', $flash); + + + // json_response + return [ + 'status' => 'success', + 'session' => \json_encode([ + 'sessionField' => base64_encode($uri), + 'path' => $upload->file->path, + 'field' => $settings->name + ]) + ]; + } + + /** + * Handle form processing on POST action. + */ + public function post() + { + $grav = Grav::instance(); + $uri = $grav['uri']->url; + $session = $grav['session']; + + if (isset($_POST)) { + $this->values = new Data(isset($_POST) ? (array)$_POST : []); + $data = $this->values->get('data'); + + // Add post data to form dataset + if (!$data) { + $data = $this->values->toArray(); + } + + if (method_exists('Grav\Common\Utils', 'getNonce')) { + if (!$this->values->get('form-nonce') || !Utils::verifyNonce($this->values->get('form-nonce'), 'form')) { + $event = new Event(['form' => $this, + 'message' => $grav['language']->translate('PLUGIN_FORM.NONCE_NOT_VALIDATED') + ]); + $grav->fireEvent('onFormValidationError', $event); + + return; + } + } + + $i = 0; + foreach ($this->items['fields'] as $key => $field) { + $name = isset($field['name']) ? $field['name'] : $key; + if (!isset($field['name'])) { + if (isset($data[$i])) { //Handle input@ false fields + $data[$name] = $data[$i]; + unset($data[$i]); + } + } + if ($field['type'] == 'checkbox') { + $data[$name] = isset($data[$name]) ? true : false; + } + $i++; + } + + $this->data->merge($data); + } + + // Validate and filter data + try { + $this->data->validate(); + $this->data->filter(); + + $grav->fireEvent('onFormValidationProcessed', new Event(['form' => $this])); + } catch (\RuntimeException $e) { + $event = new Event(['form' => $this, 'message' => $e->getMessage(), 'messages' => $e->getMessages()]); + $grav->fireEvent('onFormValidationError', $event); + if ($event->isPropagationStopped()) { + return; + } + } + + // Process previously uploaded files for the current URI + // and finally store them. Everything else will get discarded + $queue = $session->getFlashObject('files-upload'); + $queue = $queue[base64_encode($uri)]; + if (is_array($queue)) { + foreach ($queue as $key => $files) { + foreach ($files as $destination => $file) { + if (!rename($file['tmp_name'], $destination)) { + throw new \RuntimeException(sprintf($grav['language']->translate('PLUGIN_FORM.FILEUPLOAD_UNABLE_TO_MOVE', null, true), '"' . $file['tmp_name'] . '"', $destination)); + } + + unset($files[$destination]['tmp_name']); + } + + $this->data->merge([$key => $files]); + + } + } + + $process = isset($this->items['process']) ? $this->items['process'] : []; + if (is_array($process)) { + $event = null; + foreach ($process as $action => $data) { + if (is_numeric($action)) { + $action = \key($data); + $data = $data[$action]; + } + + $previousEvent = $event; + $event = new Event(['form' => $this, 'action' => $action, 'params' => $data]); + + if ($previousEvent) { + if (!$previousEvent->isPropagationStopped()) { + $grav->fireEvent('onFormProcessed', $event); + } else { + break; + } + } else { + $grav->fireEvent('onFormProcessed', $event); + } + } + } + } + + public function getPagePathFromToken($path) + { + return Utils::getPagePathFromToken($path, $this->page()); + } + + /** + * Internal method to normalize the $_FILES array + * + * @param array $data $_FILES starting point data + * @param string $key + * @return object a new Object with a normalized list of files + */ + protected function normalizeFiles($data, $key = '') { + $files = new \stdClass(); + $files->field = $key; + $files->file = new \stdClass(); + + foreach($data as $fieldName => $fieldValue) { + // Since Files Upload are always happening via Ajax + // we are not interested in handling `multiple="true"` + // because they are always handled one at a time. + // For this reason we normalize the value to string, + // in case it is arriving as an array. + $value = (array) Utils::getDotNotation($fieldValue, $key); + $files->file->{$fieldName} = array_shift($value); + } + + return $files; + } + + public static function getNonce() + { + $action = 'form-plugin'; + return Utils::getNonce($action); + } +} diff --git a/sandbox/grav/user/plugins/form/form.php b/sandbox/grav/user/plugins/form/form.php new file mode 100644 index 0000000000..9f22b0dfc7 --- /dev/null +++ b/sandbox/grav/user/plugins/form/form.php @@ -0,0 +1,692 @@ + 1000 + ]; + + /** + * @var Form + */ + protected $form; + + protected $forms = []; + + protected $flat_forms = []; + + protected $json_response = []; + + protected $recache_forms = false; + + + /** + * @return array + */ + public static function getSubscribedEvents() + { + return [ + 'onPluginsInitialized' => ['onPluginsInitialized', 0], + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0] + ]; + } + + /** + * Initialize forms from cache if possible + */ + public function onPluginsInitialized() + { + require_once(__DIR__ . '/classes/form.php'); + + if ($this->isAdmin()) { + $this->enable([ + 'onPagesInitialized' => ['onPagesInitialized', 0] + ]); + return; + } + + $this->enable([ + 'onPageProcessed' => ['onPageProcessed', 0], + 'onPagesInitialized' => ['onPagesInitialized', 0], + 'onTwigInitialized' => ['onTwigInitialized', 0], + 'onTwigPageVariables' => ['onTwigVariables', 0], + 'onTwigSiteVariables' => ['onTwigVariables', 0], + 'onFormValidationProcessed' => ['onFormValidationProcessed', 0], + ]); + } + + /** + * Process forms after page header processing, but before caching + * + * @param Event $e + */ + public function onPageProcessed(Event $e) + { + /** @var Page $page */ + $page = $e['page']; + $page_route = $page->route(); + + if ($page->home()) { + $page_route = '/'; + } + + $header = $page->header(); + + //call event to allow filling the page header form dynamically (e.g. use case: Comments plugin) + $this->grav->fireEvent('onFormPageHeaderProcessed', new Event(['header' => $header])); + + if ((isset($header->forms) && is_array($header->forms)) || + (isset($header->form) && is_array($header->form))) { + + $page_forms = []; + + // Force never_cache_twig if modular form + if ($page->modular()) { + $header->never_cache_twig = true; + } + + // Get the forms from the page headers + if (isset($header->forms)) { + $page_forms = $header->forms; + } elseif (isset($header->form)) { + $page_forms[] = $header->form; + } + + // Store the page forms in the forms instance + foreach ($page_forms as $name => $page_form) { + $form = new Form($page, $name, $page_form); + $form_array = [$form['name'] => $form]; + if (array_key_exists($page_route, $this->forms)) { + $this->forms[$page_route] = array_merge($this->forms[$page_route], $form_array); + } else { + $this->forms[$page_route] = $form_array; + } + + } + + $this->recache_forms = true; + } + } + + /** + * Initialize form if the page has one. Also catches form processing if user posts the form. + */ + public function onPagesInitialized() + { + $submitted = false; + $this->json_response = []; + $cache_id = $this->grav['pages']->getPagesCacheId() . '-form-plugin'; + + // Get and set the cache of forms if it exists + list($forms, $flat_forms) = $this->grav['cache']->fetch($cache_id); + + // Only store the forms if they are an array + if (is_array($forms)) { + $this->forms = $forms; + } + + // Only store the flat_forms if they are an array + if (is_array($flat_forms)) { + $this->flat_forms = $flat_forms; + } + + // No forms in pages, try the current one in the page + if (empty($this->forms)) { + + $page = $this->grav['page']; + if (!$page) { + return; + } + + // Create form from page + $header = $page->header(); + if (isset($header->form) && is_array($header->form)) { + $this->form = new Form($page); + } + + } else { + // Regenerate list of flat_forms if not already populated + if (empty($this->flat_forms)) { + $this->flat_forms = Utils::arrayFlatten($this->forms); + } + + // Save the current state of the forms to cache + if ($this->recache_forms) { + $this->grav['cache']->save($cache_id, [$this->forms, $this->flat_forms]); + } + } + + // Enable form events if there's a POST + if ($this->shouldProcessForm()) { + $this->enable([ + 'onFormProcessed' => ['onFormProcessed', 0], + 'onFormValidationError' => ['onFormValidationError', 0], + 'onFormFieldTypes' => ['onFormFieldTypes', 0], + ]); + + // Post the form + if ($this->form()) { + if ($this->grav['uri']->extension() === 'json' && isset($_POST['__form-file-uploader__'])) { + $this->json_response = $this->form->uploadFiles(); + } else { + $this->form->post(); + $submitted = true; + } + } + + // Clear flash objects for previously uploaded files + // whenever the user switches page / reloads + // ignoring any JSON / extension call + if (is_null($this->grav['uri']->extension()) && !$submitted) { + // Discard any previously uploaded files session. + // and if there were any uploaded file, remove them from the filesystem + if ($flash = $this->grav['session']->getFlashObject('files-upload')) { + $flash = new \RecursiveIteratorIterator(new \RecursiveArrayIterator($flash)); + foreach ($flash as $key => $value) { + if ($key !== 'tmp_name') { + continue; + } + @unlink($value); + } + } + } + } + } + + /** + * Add simple `forms()` Twig function + */ + public function onTwigInitialized() + { + $this->grav['twig']->twig()->addFunction( + new \Twig_SimpleFunction('forms', [$this, 'getForm']) + ); + } + + /** + * Add current directory to twig lookup paths. + */ + public function onTwigTemplatePaths() + { + $this->grav['twig']->twig_paths[] = __DIR__ . '/templates'; + } + + /** + * Make form accessible from twig. + * + * @param Event $event + */ + public function onTwigVariables(Event $event = null) + { + if ($event && isset($event['page'])) { + $page = $event['page']; + } else { + $page = $this->grav['page']; + } + + $header = $page->header(); + + // get route to calculated page + $page_route = $page->route(); + // get route to current page + $current_page_route = $this->getCurrentPageRoute(); + $found_forms = []; + + $twig = $this->grav['twig']; + + if (!isset($twig->twig_vars['form'])) { + if (isset($this->form)) { + $twig->twig_vars['form'] = $this->form; + } else { + if (isset($this->forms[$page_route])) { + $found_forms = $this->forms[$page_route]; + } elseif (isset($this->forms[$current_page_route])) { + $found_forms = $this->forms[$current_page_route]; + } elseif (isset($header->form)) { + $found_forms = [new Form($page)]; + } + $twig->twig_vars['form'] = array_shift($found_forms); + } + } + + if ($this->config->get('plugins.form.built_in_css')) { + $this->grav['assets']->addCss('plugin://form/assets/form-styles.css'); + } + + $twig->twig_vars['form_json_response'] = $this->json_response; + } + + /** + * Handle form processing instructions. + * + * @param Event $event + */ + public function onFormProcessed(Event $event) + { + $form = $event['form']; + $action = $event['action']; + $params = $event['params']; + + $this->process($form); + + switch ($action) { + case 'captcha': + if (isset($params['recaptcha_secret'])) { + $recaptchaSecret = $params['recaptcha_secret']; + } else if (isset($params['recatpcha_secret'])) { + // Included for backwards compatibility with typo (issue #51) + $recaptchaSecret = $params['recatpcha_secret']; + } else { + $recaptchaSecret = $this->config->get('plugins.form.recaptcha.secret_key'); + } + + // Validate the captcha + $query = http_build_query([ + 'secret' => $recaptchaSecret, + 'response' => $form->value('g-recaptcha-response', true) + ]); + $url = 'https://www.google.com/recaptcha/api/siteverify?' . $query; + $response = json_decode(file_get_contents($url), true); + + if (!isset($response['success']) || $response['success'] !== true) { + $this->grav->fireEvent('onFormValidationError', new Event([ + 'form' => $form, + 'message' => $this->grav['language']->translate('PLUGIN_FORM.ERROR_VALIDATING_CAPTCHA') + ])); + $event->stopPropagation(); + + return; + } + break; + case 'ip': + $label = isset($params['label']) ? $params['label'] : 'User IP'; + $blueprint = $form->value()->blueprints(); + $blueprint->set('form/fields/ip', ['name'=>'ip', 'label'=> $label]); + $form->setFields($blueprint->fields()); + $form->setData('ip', Uri::ip()); + break; + case 'message': + $translated_string = $this->grav['language']->translate($params); + $vars = array( + 'form' => $form + ); + + /** @var Twig $twig */ + $twig = $this->grav['twig']; + $processed_string = $twig->processString($translated_string, $vars); + + $form->message = $processed_string; + break; + case 'redirect': + $this->grav['session']->setFlashObject('form', $form); + $url = ((string)$params); + $vars = array( + 'form' => $form + ); + /** @var Twig $twig */ + $twig = $this->grav['twig']; + $url = $twig->processString($url, $vars); + $this->grav->redirect($url); + break; + case 'reset': + if (Utils::isPositive($params)) { + $form->reset(); + } + break; + case 'display': + $route = (string)$params; + if (!$route || $route[0] != '/') { + /** @var Uri $uri */ + $uri = $this->grav['uri']; + $route = rtrim($uri->route(), '/'). '/' . ($route ?: ''); + } + + /** @var Twig $twig */ + $twig = $this->grav['twig']; + $twig->twig_vars['form'] = $form; + + /** @var Pages $pages */ + $pages = $this->grav['pages']; + $page = $pages->dispatch($route, true); + + if (!$page) { + throw new \RuntimeException('Display page not found. Please check the page exists.', 400); + } + + unset($this->grav['page']); + $this->grav['page'] = $page; + break; + case 'save': + $prefix = !empty($params['fileprefix']) ? $params['fileprefix'] : ''; + $format = !empty($params['dateformat']) ? $params['dateformat'] : 'Ymd-His-u'; + $ext = !empty($params['extension']) ? '.' . trim($params['extension'], '.') : '.txt'; + $filename = !empty($params['filename']) ? $params['filename'] : ''; + $operation = !empty($params['operation']) ? $params['operation'] : 'create'; + + if (!$filename) { + $filename = $prefix . $this->udate($format) . $ext; + } + + /** @var Twig $twig */ + $twig = $this->grav['twig']; + $vars = [ + 'form' => $form + ]; + + // Process with Twig + $filename = $twig->processString($filename, $vars); + + $locator = $this->grav['locator']; + $path = $locator->findResource('user://data', true); + $dir = $path . DS . $form->name(); + $fullFileName = $dir. DS . $filename; + + $file = File::instance($fullFileName); + + if ($operation == 'create') { + $body = $twig->processString(!empty($params['body']) ? $params['body'] : '{% include "forms/data.txt.twig" %}', + $vars); + $file->save($body); + } elseif ($operation == 'add') { + if (!empty($params['body'])) { + // use body similar to 'create' action and append to file as a log + $body = $twig->processString($params['body'], $vars); + + // create folder if it doesn't exist + if (!file_exists($dir)) { + mkdir($dir); + } + + // append data to existing file + file_put_contents($fullFileName, $body, FILE_APPEND | LOCK_EX); + } else { + // serialize YAML out to file for easier parsing as data sets + $vars = $vars['form']->value()->toArray(); + + foreach ($form->fields as $field) { + if (isset($field['process']) && isset($field['process']['ignore']) && $field['process']['ignore']) { + unset($vars[$field['name']]); + } + } + + if (file_exists($fullFileName)) { + $data = Yaml::parse($file->content()); + if (count($data) > 0) { + array_unshift($data, $vars); + } else { + $data[] = $vars; + } + } else { + $data[] = $vars; + } + + $file->save(Yaml::dump($data)); + } + + } + break; + } + } + + /** + * Custom field logic can go in here + * + * @param Event $event + */ + public function onFormValidationProcessed(Event $event) + { + // special check for honeypot field + if (!empty($event['form']->value('honeypot'))) { + throw new ValidationException('Are you a bot?'); + } + } + + /** + * Handle form validation error + * + * @param Event $event An event object + */ + public function onFormValidationError(Event $event) + { + $form = $event['form']; + if (isset($event['message'])) { + $form->message_color = 'red'; + $form->message = $event['message']; + $form->messages = $event['messages']; + } + + $uri = $this->grav['uri']; + $route = $uri->route(); + + /** @var Twig $twig */ + $twig = $this->grav['twig']; + $twig->twig_vars['form'] = $form; + + /** @var Pages $pages */ + $pages = $this->grav['pages']; + $page = $pages->dispatch($route, true); + + if ($page) { + unset($this->grav['page']); + $this->grav['page'] = $page; + } + + $event->stopPropagation(); + } + + /** + * Get list of form field types specified in this plugin. Only special types needs to be listed. + * + * @return array + */ + public function getFormFieldTypes() + { + return [ + 'display' => [ + 'input@' => false + ], + 'spacer' => [ + 'input@' => false + ], + 'captcha' => [ + 'input@' => false + ] + ]; + } + + /** + * Process a form + * + * Currently available processing tasks: + * + * - fillWithCurrentDateTime + * + * @param Form $form + * + * @return bool + */ + protected function process($form) + { + foreach ($form->fields as $field) { + if (isset($field['process'])) { + if (isset($field['process']['fillWithCurrentDateTime']) && $field['process']['fillWithCurrentDateTime']) { + $form->setData($field['name'], gmdate('D, d M Y H:i:s', time())); + } + } + } + } + + /** + * Create unix timestamp for storing the data into the filesystem. + * + * @param string $format + * @param int $utimestamp + * + * @return string + */ + private function udate($format = 'u', $utimestamp = null) + { + if (is_null($utimestamp)) { + $utimestamp = microtime(true); + } + + $timestamp = floor($utimestamp); + $milliseconds = round(($utimestamp - $timestamp) * 1000000); + + return date(preg_replace('`(?slug(); + } + + return $name; + } + + /** + * function to get a specific form + * + * @param null|array $data optional form `name` + * + * @return null|Form + */ + public function getForm($data = null) + { + $page_route = null; + $form_name = null; + + if (is_array($data)) { + if (isset($data['name'])) { + $form_name = $data['name']; + } + if (isset($data['route'])) { + $page_route = $data['route']; + } + } elseif (is_string($data)) { + $form_name = $data; + } + + // if no form name, use the first form found in the page + if (!$form_name) { + + // If page route not provided, use the current page + if (!$page_route) { + // Get page route + $page_route = $this->grav['page']->route(); + + // fallback using current URI if page not initialized yet + if (!$page_route) { + $page_route = $this->getCurrentPageRoute(); + } + } + + if (isset($this->forms[$page_route])) { + $forms = $this->forms[$page_route]; + $first_form = array_shift($forms); + $form_name = $first_form['name']; + } else { + //No form on this route. Try looking up in the current page first + return new Form($this->grav['page']); + } + } + + // return the form you are looking for if available + $form = $this->getFormByName($form_name); + + return $form; + } + + /** + * Get current page's route + * + * @return mixed + */ + protected function getCurrentPageRoute() + { + $path = $this->grav['uri']->route(); + $path = $path ?: '/'; + return $path; + } + + /** + * Retrieve a form based on the form name + * + * @param $form_name + * @return mixed + */ + protected function getFormByName($form_name) + { + if (array_key_exists($form_name, $this->flat_forms)) { + $form = $this->flat_forms[$form_name]; + return $form; + } + return null; + } + + protected function shouldProcessForm() + { + $status = isset($_POST) && isset($_POST['form-nonce']); + $refresh_prevention = null; + + if ($status && $this->form()) { + + // Set page template if passed by form + if (isset($this->form->template)) { + $this->grav['page']->template($this->form->template); + } + + if (!is_null($this->form->refresh_prevention)) { + $refresh_prevention = (bool) $this->form->refresh_prevention; + } else { + $refresh_prevention = $this->config->get('plugins.form.refresh_prevention', false); + } + + $unique_form_id = filter_input(INPUT_POST, '__unique_form_id__', FILTER_SANITIZE_STRING); + + if ($refresh_prevention && $unique_form_id) { + if(($this->grav['session']->unique_form_id != $unique_form_id)) { + $this->grav['session']->unique_form_id = $unique_form_id; + } else { + $status = false; + $this->form->message = $this->grav['language']->translate('PLUGIN_FORM.FORM_ALREADY_SUBMITTED'); + $this->form->message_color = 'red'; + } + } + } + + return $status; + } + + protected function form() + { + if (!isset($this->form)) { + $current_form_name = $this->getFormName($this->grav['page']); + $this->form = $this->getFormByName($current_form_name); + } + return $this->form; + } +} diff --git a/sandbox/grav/user/plugins/form/form.yaml b/sandbox/grav/user/plugins/form/form.yaml new file mode 100644 index 0000000000..c144e9cf90 --- /dev/null +++ b/sandbox/grav/user/plugins/form/form.yaml @@ -0,0 +1,12 @@ +enabled: true +built_in_css: true +refresh_prevention: false +files: + multiple: false # To allow multiple files, default is single + limit: 10 # Number of allowed files per field (multiple required) + filesize: 5 # Maximum file size allowed (in MB) + destination: 'self@' # Where to upload the files (path and self@, page@, theme@) + avoid_overwriting: false # Prevent files with the same name to be overridden. Date prefix will be added + random_name: false # Generate a random 15 long string name for the uploaded files + accept: # List of mime/types or file extensions allowed (ie, image/*,.zip,.mp4) + - image/* diff --git a/sandbox/grav/user/plugins/form/gulpfile.js b/sandbox/grav/user/plugins/form/gulpfile.js new file mode 100644 index 0000000000..270824c7db --- /dev/null +++ b/sandbox/grav/user/plugins/form/gulpfile.js @@ -0,0 +1,53 @@ +'use strict'; + +var gulp = require('gulp'), + util = require('util'), + path = require('path'), + immutable = require('immutable'), + gulpWebpack = require('gulp-webpack'), + webpack = require('webpack'), + sourcemaps = require('gulp-sourcemaps'), + exec = require('child_process').execSync, + pwd = exec('pwd').toString(); + +var plugins = {}, + base = immutable.fromJS(require('./webpack.conf.js')), + options = { + prod: base.mergeDeep({ + devtool: 'source-map', + plugins: [ + new webpack.DefinePlugin({ + 'process.env': { NODE_ENV: '"production"' } + }), + new webpack.optimize.UglifyJsPlugin({ + sourceMap: true, + compress: { + warnings: false + } + }), + new webpack.ProvidePlugin(plugins) + ], + output: { + filename: 'form.min.js' + } + }) + }; + +var compileJS = function(watch) { + var prodOpts = options.prod.set('watch', watch); + + return gulp.src('app/main.js') + .pipe(gulpWebpack(prodOpts.toJS())) + .pipe(gulp.dest('assets/')); +}; + +gulp.task('js', function() { + compileJS(false); +}); + +gulp.task('watch', function() { + compileJS(true); +}); + +gulp.task('all', ['js']); +gulp.task('default', ['all']); \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/hebe.json b/sandbox/grav/user/plugins/form/hebe.json new file mode 100644 index 0000000000..861d235ba1 --- /dev/null +++ b/sandbox/grav/user/plugins/form/hebe.json @@ -0,0 +1,15 @@ +{ + "project":"grav-plugin-form", + "platforms":{ + "grav":{ + "nodes":{ + "plugin":[ + { + "source":"/", + "destination":"/user/plugins/form" + } + ] + } + } + } +} diff --git a/sandbox/grav/user/plugins/form/languages.yaml b/sandbox/grav/user/plugins/form/languages.yaml new file mode 100644 index 0000000000..eca11bd35b --- /dev/null +++ b/sandbox/grav/user/plugins/form/languages.yaml @@ -0,0 +1,266 @@ +en: + PLUGIN_FORM: + NOT_VALIDATED: "Form not validated. One or more required fields are missing." + NONCE_NOT_VALIDATED: "Oops there was a problem, please check your input and submit the form again." + FILES: "Files Upload" + FORM_ALREADY_SUBMITTED: "This form has already been submitted." + ALLOW_MULTIPLE: "Allow More than one file" + ALLOW_MULTIPLE_HELP: "Allows to select more than one file for upload." + DESTINATION: "Destination" + DESTINATION_HELP: "The location where the files should be uploaded to" + ACCEPT: "Allowed MIME Types" + ACCEPT_HELP: "A list of MIME Types that are allowed for upload" + ERROR_VALIDATING_CAPTCHA: "Error validating the Captcha" + DATA_SUMMARY: "Here is the summary of what you wrote to us:" + NO_FORM_DATA: "No form data available" + RECAPTCHA: "ReCaptcha" + RECAPTCHA_SITE_KEY: "Site key" + RECAPTCHA_SITE_KEY_HELP: "For more info visit https://developers.google.com/recaptcha" + RECAPTCHA_SECRET_KEY: "Secret key" + RECAPTCHA_SECRET_KEY_HELP: "For more info visit https://developers.google.com/recaptcha" + GENERAL: "General" + USE_BUILT_IN_CSS: "Use built-in CSS" + FILEUPLOAD_PREVENT_SELF: 'Cannot use "%s" outside of pages.' + FILEUPLOAD_UNABLE_TO_UPLOAD: 'Unable to upload file %s: %s' + FILEUPLOAD_UNABLE_TO_MOVE: 'Unable to move file %s to "%s"' + DROPZONE_CANCEL_UPLOAD: 'Cancel upload' + DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Are you sure you want to cancel this upload?' + DROPZONE_DEFAULT_MESSAGE: 'Drop your files here or click in this area' + DROPZONE_FALLBACK_MESSAGE: 'Your browser does not support drag and drop file uploads.' + DROPZONE_FALLBACK_TEXT: 'Please use the fallback form below to upload your files like in the olden days.' + DROPZONE_FILE_TOO_BIG: 'File is too big ({{filesize}}MiB). Max filesize: {{maxFilesize}}MiB.' + DROPZONE_INVALID_FILE_TYPE: "You can't upload files of this type." + DROPZONE_MAX_FILES_EXCEEDED: "You can not upload any more files." + DROPZONE_REMOVE_FILE: "Remove file" + DROPZONE_REMOVE_FILE_CONFIRMATION: 'Are you sure you want to delete this file?' + DROPZONE_RESPONSE_ERROR: "Server responded with {{statusCode}} code." + YES: "Yes" + NO: "No" + REFRESH_PREVENTION: "Refresh prevention" + REFRESH_PREVENTION_HELP: "Use the form's unique ID to ensure the same form is not reprocessed when refreshing the browser" + +de: + PLUGIN_FORM: + NOT_VALIDATED: "Formularwerte nicht gültig. Für ein oder mehrere erforderliche Felder fehlen Werte." + NONCE_NOT_VALIDATED: "Ups, es gibt da ein Problem. Eingabewerte bitte noch mal prüfen und das Formular erneut absenden." + FILES: "Dateien hochladen" + ALLOW_MULTIPLE: "Erlaube mehr als eine Datei" + ALLOW_MULTIPLE_HELP: "Erlaubt es, mehr als eine Datei zum Hochladen auszuwählen." + DESTINATION: "Ziel" + DESTINATION_HELP: "Das Ziel, wohin die Dateien hochgeladen werden sollen." + ACCEPT: "Erlaube MIME-Typen" + ACCEPT_HELP: "Eine Liste von MIME-Typen, die hochgeladen werden dürfen." + ERROR_VALIDATING_CAPTCHA: "Die Überprüfung des Captcha ist fehlgeschlagen." + +es: + PLUGIN_FORM: + NOT_VALIDATED: "Falló la validación del formulario. Uno o más campos obligatorios no fueron cubiertos." + NONCE_NOT_VALIDATED: "Oops, hay un problema, por favor revise la información e intente enviar el formulario otra vez." + FILES: "Subida de Ficheros" + ALLOW_MULTIPLE: "Permitir más de un fichero" + ALLOW_MULTIPLE_HELP: "Permitir seleccionar más de un fichero para subir." + DESTINATION: "Destino" + DESTINATION_HELP: "El lugar de destino al que subir los ficheros" + ACCEPT: "MIME Types permitidos" + ACCEPT_HELP: "Una lista de MIME Types que se permiten subir." + ERROR_VALIDATING_CAPTCHA: "Error al comprobar el Captcha" + RECAPTCHA: "ReCaptcha" + RECAPTCHA_SITE_KEY: "Site key" + RECAPTCHA_SITE_KEY_HELP: "Para más información visita https://developers.google.com/recaptcha" + RECAPTCHA_SECRET_KEY: "Secret key" + RECAPTCHA_SECRET_KEY_HELP: "Para más información visita https://developers.google.com/recaptcha" + +fr: + PLUGIN_FORM: + NOT_VALIDATED: "Formulaire non validé. Un ou plusieurs champs obligatoires sont manquants." + NONCE_NOT_VALIDATED: "Oups, un problème est survenu. Veuillez vérifier votre saisie et soumettre à nouveau le formulaire." + FILES: "Fichiers chargés" + ALLOW_MULTIPLE: "Autoriser plus d'un fichier" + ALLOW_MULTIPLE_HELP: "Permet la sélection de plusieurs fichiers pour chargement." + DESTINATION: "Destination" + DESTINATION_HELP: "L'emplacement où les fichiers doivent être chargés." + ACCEPT: "Autoriser les Types MIME" + ACCEPT_HELP: "Liste des Types MIME autorisés au chargement" + ERROR_VALIDATING_CAPTCHA: "Erreur lors de la validation du Captcha" + DATA_SUMMARY: "Voici le résumé de ce que vous nous avez écrit :" + NO_FORM_DATA: "Aucune donnée de formulaire disponible" + RECAPTCHA: "ReCaptcha" + RECAPTCHA_SITE_KEY: "Clé du site" + RECAPTCHA_SITE_KEY_HELP: "Pour plus d'informations veuillez vous rendre sur https://developers.google.com/recaptcha" + RECAPTCHA_SECRET_KEY: "Clé secrète" + RECAPTCHA_SECRET_KEY_HELP: "Pour plus d'informations veuillez vous rendre sur https://developers.google.com/recaptcha" + GENERAL: "Général" + USE_BUILT_IN_CSS: "Utiliser les CSS natifs" + FILEUPLOAD_PREVENT_SELF: "Impossible d'utiliser '%s' en dehors des pages." + FILEUPLOAD_UNABLE_TO_UPLOAD: "Impossible de charger le fichier %s: %s" + FILEUPLOAD_UNABLE_TO_MOVE: 'Impossible de déplacer le fichier %s vers "%s"' + DROPZONE_CANCEL_UPLOAD: "Annuler le chargement" + DROPZONE_CANCEL_UPLOAD_CONFIRMATION: "Êtes-vous certain de vouloir annuler ce téléchargement ?" + DROPZONE_DEFAULT_MESSAGE: "Glissez vos fichiers ici ou cliquez dans cette zone" + DROPZONE_FALLBACK_MESSAGE: "Votre navigateur ne prend pas en charge les téléchargements par glissé-déposé." + DROPZONE_FALLBACK_TEXT: "Veuillez utiliser le formulaire de secours ci-dessous pour transférer vos fichiers." + DROPZONE_FILE_TOO_BIG: "Le fichier est trop volumineux ({{filesize}}MiB). Taille maximale de fichier : {{maxFilesize}}MiB." + DROPZONE_INVALID_FILE_TYPE: "Vous ne pouvez pas charger des fichiers de ce type." + DROPZONE_MAX_FILES_EXCEEDED: "Vous ne pouvez plus télécharger de fichiers." + DROPZONE_REMOVE_FILE: "Supprimer le fichier" + DROPZONE_REMOVE_FILE_CONFIRMATION: "Êtes-vous sûr de vouloir supprimer ce fichier ?" + DROPZONE_RESPONSE_ERROR: "Le serveur a répondu avec le code {{statusCode}}." + +ru: + PLUGIN_FORM: + NOT_VALIDATED: "Форма не подтверждена. Отсутствует одно или несколько обязательных полей." + NONCE_NOT_VALIDATED: "Упс, у вас возникла проблема, проверьте свои данные и отправьте форму еще раз." + FILES: "Загрузка файлов" + ALLOW_MULTIPLE: "Разрешить несколько файлов" + ALLOW_MULTIPLE_HELP: "Позволяет выбрать более одного файла для загрузки." + DESTINATION: "Место назначения" + DESTINATION_HELP: "Место, куда файлы должны быть загружены в" + ACCEPT: "Разрешенные MIME типы" + ACCEPT_HELP: "Список MIME типов, разрешенных для загрузки" + ERROR_VALIDATING_CAPTCHA: "Ошибка проверки Captcha" + DATA_SUMMARY: "Вот краткое изложение того, что вы нам написали:" + NO_FORM_DATA: "Данные формы отсутствуют" + RECAPTCHA: "ReCaptcha" + RECAPTCHA_SITE_KEY: "Site key" + RECAPTCHA_SITE_KEY_HELP: "Для получения дополнительной информации посетите https://developers.google.com/recaptcha" + RECAPTCHA_SECRET_KEY: "Secret key" + RECAPTCHA_SECRET_KEY_HELP: "Для получения дополнительной информации посетите https://developers.google.com/recaptcha" + GENERAL: "Общие" + USE_BUILT_IN_CSS: "Использовать встроенный CSS" + FILEUPLOAD_PREVENT_SELF: 'Нельзя использовать "%s" за пределами страниц.' + FILEUPLOAD_UNABLE_TO_UPLOAD: 'Не удалось загрузить файл %s: %s' + FILEUPLOAD_UNABLE_TO_MOVE: 'Не удалось переместить файл %s в "%s"' + DROPZONE_CANCEL_UPLOAD: 'Отменить загрузку' + DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Вы действительно хотите отменить эту загрузку?' + DROPZONE_DEFAULT_MESSAGE: 'Бросьте свои файлы сюда или щелкните в этой области' + DROPZONE_FALLBACK_MESSAGE: 'Ваш браузер не поддерживает загрузку файлов с перетаскиванием.' + DROPZONE_FALLBACK_TEXT: 'Пожалуйста, используйте приведенную ниже форму для загрузки ваших файлов, как в старые времена.' + DROPZONE_FILE_TOO_BIG: 'Файл слишком большой ({{filesize}}мб). Максимальный размер файла: {{maxFilesize}}мб.' + DROPZONE_INVALID_FILE_TYPE: "Вы не можете загружать файлы этого типа." + DROPZONE_MAX_FILES_EXCEEDED: "Вы не можете загружать больше файлов." + DROPZONE_REMOVE_FILE: "Удалить файл" + DROPZONE_REMOVE_FILE_CONFIRMATION: 'Вы действительно хотите удалить этот файл?' + DROPZONE_RESPONSE_ERROR: "Сервер ответил кодом {{statusCode}}." + YES: "Да" + NO: "Нет" + +hr: + PLUGIN_FORM: + NOT_VALIDATED: "Formular nije validiran. Jedan ili više traženih polja nedostaju." + NONCE_NOT_VALIDATED: "Ups, došlo je do problema, molimo provjerite svoj unos i pokušajte opet." + FILES: "Upload Fajlova" + ALLOW_MULTIPLE: "Dopusti više od jednog fajla" + DESTINATION: "Destinacija" + DESTINATION_HELP: "Lokacija gdje bi fajlovi trebali biti uploadani" + ACCEPT: "Dopušteni MIME Tipovi" + ACCEPT_HELP: "Lista dopuštenih MIME Tipova koji su dozvoljeni za upload" + ERROR_VALIDATING_CAPTCHA: "Greška pri validiranju Captcha" + +hu: + PLUGIN_FORM: + NOT_VALIDATED: "Érvénytelen az űrlap. Egy vagy több kötelező mező nincs kitöltve." + NONCE_NOT_VALIDATED: "Upsz, van egy kis probléma, kérlek nézd át az űrlapot, majd küldd el újra." + FILES: "Fájlok Feltöltése" + ALLOW_MULTIPLE: "Több fájl feltöltése" + ALLOW_MULTIPLE_HELP: "Engedélyezi egyszerre több állomány feltöltését." + DESTINATION: "Feltöltés Helye" + DESTINATION_HELP: "Ide lesznek feltöltve az állományok" + ACCEPT: "Engedélyezett MIME-típusok" + ACCEPT_HELP: "A feltölthető állományok MIME-típusainak listája" + ERROR_VALIDATING_CAPTCHA: "Hiba lépett fel a Captcha validálása során" + +it: + PLUGIN_FORM: + NOT_VALIDATED: "Il Form risulta invalido. Uno o più campi risultano omessi." + NONCE_NOT_VALIDATED: "Oops è stato riscontrato un errore, si prega di ricontrollare i dati inseriti e provare di nuovo." + FILES: "Invio dei Files" + ALLOW_MULTIPLE: "Consenti più di un file" + ALLOW_MULTIPLE_HELP: "Permette la selezione di più di un file per l'upload" + DESTINATION: "Destinazione" + DESTINATION_HELP: "La destinazione dove i files vengono uploadati" + ACCEPT: "Tipi di MIME Concessi" + ACCEPT_HELP: "Una lista di tipi di MIME che sono permessi per l'upload" + ERROR_VALIDATING_CAPTCHA: "Errore durante la validazione del Captcha" + DATA_SUMMARY: "Ecco il riassunto di ciò che ci hai scritto:" + NO_FORM_DATA: "Nessuna informazione disponibile" + RECAPTCHA: "ReCaptcha" + RECAPTCHA_SITE_KEY: "Site key" + RECAPTCHA_SITE_KEY_HELP: "Per maggiori informazioni visita https://developers.google.com/recaptcha" + RECAPTCHA_SECRET_KEY: "Chiave segreta" + RECAPTCHA_SECRET_KEY_HELP: "Per maggiori informazioni visita https://developers.google.com/recaptcha" + GENERAL: "Generale" + USE_BUILT_IN_CSS: "Usa CSS incorporato" + FILEUPLOAD_PREVENT_SELF: 'Non si può usare "%s" fuori dalle pagine.' + FILEUPLOAD_UNABLE_TO_UPLOAD: 'Impossibile caricare il file %s: %s' + FILEUPLOAD_UNABLE_TO_MOVE: 'Impossibile muovere il file %s to "%s"' + DROPZONE_CANCEL_UPLOAD: 'Trasferimento annullato' + DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Sei sicuro di voler cancellare questo trasferimento?' + DROPZONE_DEFAULT_MESSAGE: "Trascina qui i tuoi file o clicca su quest'area" + DROPZONE_FALLBACK_MESSAGE: 'Il tuo browser non supporta il trascinamento dei file per il trasferimento.' + DROPZONE_FALLBACK_TEXT: 'Utilizza il modulo di riserva qui sotto per caricare i tuoi file come ai vecchi tempi.' + DROPZONE_FILE_TOO_BIG: 'Il file è troppo grande ({{filesize}}MiB). Dimensione massima consentita: {{maxFilesize}}MiB.' + DROPZONE_INVALID_FILE_TYPE: "Non puoi caricare questo tipo di file" + DROPZONE_MAX_FILES_EXCEEDED: "Non puoi caricare ulteriori file, hai raggiunto il limite consentito." + DROPZONE_REMOVE_FILE: "Rimuovi il file" + DROPZONE_REMOVE_FILE_CONFIRMATION: 'Sei sicuro di voler eliminare questo file??' + DROPZONE_RESPONSE_ERROR: "Il Server ha risposto con il codice {{statusCode}}." + YES: "Si" + NO: "No" + +ro: + PLUGIN_FORM: + NOT_VALIDATED: "Formularul nu a fost validat. Unul sau mai multe câmpuri sunt goale." + NONCE_NOT_VALIDATED: "Oops a apărut o problemă, vă rugăm verificați datele introduse și trimiteți formularul din nou." + FILES: "Încărcare fișiere" + ALLOW_MULTIPLE: "Permiteți mai multe fișiere" + ALLOW_MULTIPLE_HELP: "Vă permite să selectați mai multe fișiere pentru încărcare." + DESTINATION: "Destinație" + DESTINATION_HELP: "Locația unde vor fi încărcate fișierele." + ACCEPT: "Permite tipuri MIME " + ACCEPT_HELP: "O listă cu tipuri MIME care sunt permise la încărcare." + ERROR_VALIDATING_CAPTCHA: "Eroare la validarea Captcha." + DATA_SUMMARY: "Mai jos aveți un rezumat al mesajului pe care ni l-ați trimis:" + NO_FORM_DATA: "Nu e disponibilă nici o dată pentru formular" + RECAPTCHA: "ReCaptcha" + RECAPTCHA_SITE_KEY: "Cheia pentru Site" + RECAPTCHA_SITE_KEY_HELP: "Pentru mai multe detalii vă rugăm vizitați https://developers.google.com/recaptcha" + RECAPTCHA_SECRET_KEY: "Cheia secretă pentru Site" + RECAPTCHA_SECRET_KEY_HELP: "Pentru mai multe detalii vă rugăm vizitați https://developers.google.com/recaptcha" + +cs: + PLUGIN_FORM: + NOT_VALIDATED: "Formulář nebyl ověřen. Chybí jedno nebo více povinných polí." + NONCE_NOT_VALIDATED: "Jejda, došlo k problému, zkontrolujte vstupní stránku a znovu odešlete formulář." + FILES: "Nahrávání souborů" + ALLOW_MULTIPLE: "Povolit více než jeden soubor" + ALLOW_MULTIPLE_HELP: "Umožňuje vybrat více než jeden soubor pro nahrání." + DESTINATION: "Cílové umístění" + DESTINATION_HELP: "Místo, kam mají být soubory nahrány" + ACCEPT: "Povolené MIME typy" + ACCEPT_HELP: "Seznam MIME typů souborů povolených pro upload" + ERROR_VALIDATING_CAPTCHA: "Nepodařilo se ověřit CAPTCHA (kontrola proti spamu)" + DATA_SUMMARY: "Shrnutí toho, co jste nám napsali:" + NO_FORM_DATA: "Formulář neobsahuje žádná data" + RECAPTCHA: "ReCaptcha" + RECAPTCHA_SITE_KEY: "Site key" + RECAPTCHA_SITE_KEY_HELP: "Více informací https://developers.google.com/recaptcha" + RECAPTCHA_SECRET_KEY: "Secret key" + RECAPTCHA_SECRET_KEY_HELP: "Více informací https://developers.google.com/recaptcha" + GENERAL: "Všeobecné" + USE_BUILT_IN_CSS: "Použít built-in CSS" + FILEUPLOAD_PREVENT_SELF: 'Nelze použít "% s" mimo stránky.' + FILEUPLOAD_UNABLE_TO_UPLOAD: 'Nelze nahrát soubor %s: %s' + FILEUPLOAD_UNABLE_TO_MOVE: 'Nelze přesunout soubor %s do "%s"' + DROPZONE_CANCEL_UPLOAD: 'Zrušit upload' + DROPZONE_CANCEL_UPLOAD_CONFIRMATION: 'Opravdu chcete zrušit nahrávání souboru?' + DROPZONE_DEFAULT_MESSAGE: 'Přetáhněte sem soubory nebo klikněte v tomto prostoru' + DROPZONE_FALLBACK_MESSAGE: 'Váš prohlížeč nepodporuje nahrávání souborů táhni a pusť.' + DROPZONE_FALLBACK_TEXT: 'Použijte níže uvedený formulář pro nahrání souborů, jako v minulých dnech.' + DROPZONE_FILE_TOO_BIG: 'Soubor je příliš velký ({{filesize}}MiB). Max. velikost souboru: {{maxFilesize}}MiB.' + DROPZONE_INVALID_FILE_TYPE: "Nelze nahrát soubory tohoto typu." + DROPZONE_MAX_FILES_EXCEEDED: "Nelze nahrát další soubory." + DROPZONE_REMOVE_FILE: "Odstranit soubor" + DROPZONE_REMOVE_FILE_CONFIRMATION: 'Opravdu chcete tento soubor smazat?' + DROPZONE_RESPONSE_ERROR: "Server vrátil chybový kód: {{statusCode}}." + YES: "Ano" + NO: "Ne" diff --git a/sandbox/grav/user/plugins/form/package.json b/sandbox/grav/user/plugins/form/package.json new file mode 100644 index 0000000000..82b6604ef3 --- /dev/null +++ b/sandbox/grav/user/plugins/form/package.json @@ -0,0 +1,35 @@ +{ + "name": "grav-plugin-form", + "version": "1.0.0", + "description": "Grav Plugin Form", + "repository": "https://github.com/getgrav/grav-plugin-form", + "main": "app/main.js", + "scripts": { + "watch": "webpack --watch --progress --colors --config webpack.conf.js", + "dev": "webpack --progress --colors --config webpack.conf.js", + "prod": "NODE_ENV=production webpack -p --config webpack.conf.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "RocketTheme, LLC", + "license": "MIT", + "dependencies": { + "dropzone": "^4.3.0" + }, + "devDependencies": { + "babel-core": "^6.14.0", + "babel-loader": "^6.2.5", + "babel-polyfill": "^6.13.0", + "babel-preset-es2015": "^6.24.1", + "eslint": "^3.4.0", + "eslint-loader": "^1.5.0", + "exports-loader": "^0.6.3", + "gulp": "^3.9.1", + "gulp-sourcemaps": "^1.6.0", + "gulp-util": "^3.0.8", + "gulp-webpack": "^1.5.0", + "immutable": "^3.8.1", + "imports-loader": "^0.6.5", + "json-loader": "^0.5.4", + "webpack": "^1.13.2" + } +} diff --git a/sandbox/grav/user/plugins/form/templates/form-messages.html.twig b/sandbox/grav/user/plugins/form/templates/form-messages.html.twig new file mode 100644 index 0000000000..cd5e49a3d1 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/form-messages.html.twig @@ -0,0 +1 @@ +{% include 'partials/form-messages.html.twig' %} diff --git a/sandbox/grav/user/plugins/form/templates/form-messages.json.twig b/sandbox/grav/user/plugins/form/templates/form-messages.json.twig new file mode 100644 index 0000000000..71866c7fa9 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/form-messages.json.twig @@ -0,0 +1,2 @@ +{% set message = form.inline_errors and form.messages ? "FORM.VALIDATION_FAIL"|t : form.message %} +{{ {'message':message, 'color':form.message_color}|json_encode|raw }} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/form.html.twig b/sandbox/grav/user/plugins/form/templates/form.html.twig new file mode 100644 index 0000000000..3523d0651f --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/form.html.twig @@ -0,0 +1,8 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} + + {{ content|raw }} + {% include "forms/form.html.twig" %} + +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/form.json.twig b/sandbox/grav/user/plugins/form/templates/form.json.twig new file mode 100644 index 0000000000..f6ba33fc12 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/form.json.twig @@ -0,0 +1 @@ +{% extends 'forms/ajax.json.twig' %} diff --git a/sandbox/grav/user/plugins/form/templates/formdata.html.twig b/sandbox/grav/user/plugins/form/templates/formdata.html.twig new file mode 100644 index 0000000000..16ceb8891c --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/formdata.html.twig @@ -0,0 +1,21 @@ +{% extends 'partials/base.html.twig' %} + +{% if form is null %} + {% set form = grav.session.getFlashObject('form') %} +{% endif %} + +{% block content %} + {{ content|raw }} + + {% if form %} + {% if form.message %} +

    {{ form.message|raw }}

    + {% endif %} +

    {{ 'PLUGIN_FORM.DATA_SUMMARY'|t }}

    + + {% include "forms/data.html.twig" %} + {% else %} +

    {{ 'PLUGIN_FORM.NO_FORM_DATA'|t }}

    + {% endif %} + +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/ajax.json.twig b/sandbox/grav/user/plugins/form/templates/forms/ajax.json.twig new file mode 100644 index 0000000000..ccec039023 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/ajax.json.twig @@ -0,0 +1,5 @@ +{% if form_json_response %} +{{ form_json_response|json_encode|raw }} +{% else %} +{} +{% endif %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/data.html.twig b/sandbox/grav/user/plugins/form/templates/forms/data.html.twig new file mode 100644 index 0000000000..9cb16c23b4 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/data.html.twig @@ -0,0 +1 @@ +{% extends "forms/default/data.html.twig" %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/data.txt.twig b/sandbox/grav/user/plugins/form/templates/forms/data.txt.twig new file mode 100644 index 0000000000..c647832fc1 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/data.txt.twig @@ -0,0 +1 @@ +{% extends "forms/default/data.txt.twig" %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/default/data.html.twig b/sandbox/grav/user/plugins/form/templates/forms/default/data.html.twig new file mode 100644 index 0000000000..d11ef3b1d4 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/default/data.html.twig @@ -0,0 +1,39 @@ +{% macro render_field(form, fields) %} + {% for index, field in fields %} + {% set input = attribute(field, "input@") %} + {% if input is null or input == true %} + {% if form.value(field.name) %} + {% block field %} +
    + {% block field_label %} + {{ field.label|t|e }}: + {% endblock %} + + {% block field_value %} + {% if field.type == 'checkboxes' %} +
      + {% for value in form.value(field.name) %} +
    • {{ field.options[value]|e }}
    • + {% endfor %} +
    + {% elseif field.type == 'checkbox' %} + {{ (form.value(field.name) == 1) ? "PLUGIN_FORM.YES"|t|e : "PLUGIN_FORM.NO"|t|e }} + {% elseif field.type == 'select' %} + {{ field.options[form.value(field.name)]|e }} + {% else %} + {{ string(form.value(field.name))|nl2br }} + {% endif %} + {% endblock %} +
    + {% endblock %} + {% endif %} + {% else %} + {% if field.fields %} + {{ _self.render_field(form, field.fields) }} + {% endif %} + {% endif %} + {% endfor %} +{% endmacro %} + +{{ _self.render_field(form, form.fields) }} + diff --git a/sandbox/grav/user/plugins/form/templates/forms/default/data.txt.twig b/sandbox/grav/user/plugins/form/templates/forms/default/data.txt.twig new file mode 100644 index 0000000000..0b03704daf --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/default/data.txt.twig @@ -0,0 +1,16 @@ +{%- macro render_field(form, fields) %} +{%- for index, field in fields %} + {%- set input = attribute(field, "input@") %} + {%- if input is null or input == true %} + {%- set value = form.value(field.name ?? index) %} + {{- field.name ?? index }}: {{ string((value is iterable ? value|json_encode : value)) ~ "\r\n" }} + {%- else %} + {%- if field.fields %} + {{- _self.render_field(form, field.fields) }} + {%- endif %} + {%- endif %} +{%- endfor %} +{%- endmacro %} +{%- autoescape false %} +{{- _self.render_field(form, form.fields) ~ "\r\n" }} +{%- endautoescape %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/default/field.html.twig b/sandbox/grav/user/plugins/form/templates/forms/default/field.html.twig new file mode 100644 index 0000000000..c708cf8020 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/default/field.html.twig @@ -0,0 +1,81 @@ +{% set originalValue = originalValue is defined ? originalValue : value %} +{% set toggleableChecked = field.toggleable and (originalValue is not null and originalValue is not empty) %} +{% set isDisabledToggleable = field.toggleable and not toggleableChecked %} +{% set value = (is_object(value) or value is null ? field.default : value) %} +{% set errors = attribute(form.messages, field.name) %} + +{% block field %} +
    + {% block contents %} + {% if field.label is not same as(false) %} +
    + +
    + {% endif %} +
    + {% block group %} + {% block input %} +
    + {% block prepend %}{% endblock prepend %} + + {% block append %}{% endblock append %} + {% if form.inline_errors and errors %} +
    + {% for error in errors %} +

    {{ error }}

    + {% endfor %} +
    + {% endif %} +
    + {% endblock %} + {% endblock %} + {% if field.description %} +
    + {{ field.description|raw }} +
    + {% endif %} + +
    + {% endblock %} +
    +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/default/form.html.twig b/sandbox/grav/user/plugins/form/templates/forms/default/form.html.twig new file mode 100644 index 0000000000..e0bf328ce9 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/default/form.html.twig @@ -0,0 +1,82 @@ +{% if form is null %} + {% set form = grav.session.getFlashObject('form') %} +{% endif %} + +{% include 'partials/form-messages.html.twig' %} + +{% set scope = scope ?: 'data.' %} +{% set multipart = '' %} +{% set method = form.method|upper|default('POST') %} + +{% for field in form.fields %} + {% if (method == 'POST' and field.type == 'file') %} + {% set multipart = ' enctype="multipart/form-data"' %} + {% endif %} +{% endfor %} + +{% set action = form.action ? base_url ~ form.action : base_url ~ page.route ~ uri.params %} + +{% if (action == base_url_relative) %} + {% set action = base_url_relative ~ '/' ~ page.slug %} +{% endif %} + +
    + + {% block inner_markup_fields_start %}{% endblock %} + + {% for field in form.fields %} + {% if field.type == 'file' %} + {% do assets.addJs('plugin://form/assets/form.min.js') %} + {% endif %} + {% set value = form.value(field.name) %} + {% include "forms/fields/#{field.type}/#{field.type}.html.twig" ignore missing %} + {% endfor %} + + {% include "forms/fields/formname/formname.html.twig" %} + + {% block inner_markup_fields_end %}{% endblock %} + + {% block inner_markup_buttons_start %} +
    + {% endblock %} + + {% for button in form.buttons %} + {% if button.outerclasses is defined %}
    {% endif %} + {% if button.url %} + + {% endif %} + + {% if button.url %} + + {% endif %} + {% if button.outerclasses is defined %}
    {% endif %} + {% endfor %} + + {% block inner_markup_buttons_end %} +
    + {% endblock %} + + {% include 'forms/fields/uniqueid/uniqueid.html.twig' %} + {{ nonce_field('form', 'form-nonce')|raw }} +
    diff --git a/sandbox/grav/user/plugins/form/templates/forms/field.html.twig b/sandbox/grav/user/plugins/form/templates/forms/field.html.twig new file mode 100644 index 0000000000..447d959bb9 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/field.html.twig @@ -0,0 +1 @@ +{% extends "forms/default/field.html.twig" %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/avatar/avatar.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/avatar/avatar.html.twig new file mode 100644 index 0000000000..33862880cd --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/avatar/avatar.html.twig @@ -0,0 +1,5 @@ +{% if form.data.avatar %} + +{% else %} + +{% endif %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/captcha/captcha.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/captcha/captcha.html.twig new file mode 100644 index 0000000000..5f31820566 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/captcha/captcha.html.twig @@ -0,0 +1,32 @@ +{% extends "forms/field.html.twig" %} + +{% block input %} + + +
    +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/checkbox/checkbox.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/checkbox/checkbox.html.twig new file mode 100644 index 0000000000..f7b53ce5c5 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/checkbox/checkbox.html.twig @@ -0,0 +1,29 @@ +{% extends "forms/field.html.twig" %} + +{% block label %} +{% endblock %} + +{% block input %} +
    + + + +
    +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/checkbox/checkbox.yaml b/sandbox/grav/user/plugins/form/templates/forms/fields/checkbox/checkbox.yaml new file mode 100644 index 0000000000..b407144624 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/checkbox/checkbox.yaml @@ -0,0 +1,11 @@ +field: + checkbox: + node: boolean + key: name + fields: + name: + type: text + validation: + required: true + value: + type: text diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/checkboxes/checkboxes.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/checkboxes/checkboxes.html.twig new file mode 100644 index 0000000000..0c427ae959 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/checkboxes/checkboxes.html.twig @@ -0,0 +1,42 @@ +{% extends "forms/field.html.twig" %} + +{% set originalValue = value %} +{% set value = (value is null ? field.default : value) %} +{% if field.use == 'keys' and field.default %} + {% set value = field.default|merge(value) %} +{% endif %} + +{% block global_attributes %} + {{ parent() }} + data-grav-keys="{{ field.use == 'keys' ? 'true' : 'false' }}" + data-grav-field-name="{{ (scope ~ field.name)|fieldName }}" +{% endblock %} + +{% block input %} + {% for key, text in field.options %} + + {% set id = field.id|default(field.name) ~ '-' ~ key %} + {% set name = field.use == 'keys' ? key : id %} + {% set val = field.use == 'keys' ? '1' : key %} + {% set checked = (field.use == 'keys' ? value[key] : key in value) %} + {% set help = (key in field.help_options|keys ? field.help_options[key] : false) %} + + + + + + {% endfor %} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/color/color.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/color/color.html.twig new file mode 100644 index 0000000000..b0c1f602ab --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/color/color.html.twig @@ -0,0 +1,6 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="color" + {{ parent() }} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/column/column.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/column/column.html.twig new file mode 100644 index 0000000000..970bcd043e --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/column/column.html.twig @@ -0,0 +1,10 @@ +{% if field.fields %} +
    + {% for field in field.fields %} + {% if field.type %} + {% set value = form.value(field.name) %} + {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} + {% endif %} + {% endfor %} +
    +{% endif %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/columns/columns.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/columns/columns.html.twig new file mode 100644 index 0000000000..360fea25ad --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/columns/columns.html.twig @@ -0,0 +1,10 @@ +
    +{% if field.fields %} + {% set cols = field.fields|length %} + {% for field in field.fields %} + {% set value = form.value(field.name) %} + {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/column/column.html.twig'] with {'cols':cols} %} + {% endfor %} +{% endif %} +
    + diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/conditional/conditional.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/conditional/conditional.html.twig new file mode 100644 index 0000000000..bd333d09b4 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/conditional/conditional.html.twig @@ -0,0 +1,22 @@ +{% set value = evaluate(field.condition) %} +{% set value = value == 'true' ? 1 : value %} +{% set value = value == 'false' ? 0 : value %} + +{% if value %} + {% if field.classes %} +
    + {% endif %} + + {% if field.fields %} + {% for field in field.fields %} + {% if field.type %} + {% set value = data.value(field.name) %} + {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} + {% endif %} + {% endfor %} + {% endif %} + + {% if field.classes %} +
    + {% endif %} +{% endif %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/date/date.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/date/date.html.twig new file mode 100644 index 0000000000..4e42985c50 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/date/date.html.twig @@ -0,0 +1,8 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="date" + {% if field.validate.min %}min="{{ field.validate.min }}"{% endif %} + {% if field.validate.max %}max="{{ field.validate.max }}"{% endif %} + {{ parent() }} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/datetime/datetime.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/datetime/datetime.html.twig new file mode 100644 index 0000000000..dd8e4cee93 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/datetime/datetime.html.twig @@ -0,0 +1,2 @@ +{# DEPRECATED. Switched to Text field until implemented properly #} +{% extends "forms/fields/text/text.html.twig" %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/display/display.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/display/display.html.twig new file mode 100644 index 0000000000..bc0eb76ce7 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/display/display.html.twig @@ -0,0 +1,19 @@ +{% extends "forms/field.html.twig" %} + +{% block input %} +
    + {% if field.markdown %} + {% if grav.twig.twig.filters['tu'] is defined %} + {{ field.content|tu|markdown|raw }} + {% else %} + {{ field.content|t|markdown|raw }} + {% endif %} + {% else %} + {% if grav.twig.twig.filters['tu'] is defined %} + {{ field.content|tu|raw }} + {% else %} + {{ field.content|t|raw }} + {% endif %} + {% endif %} +
    +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/email/email.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/email/email.html.twig new file mode 100644 index 0000000000..aa33dcf824 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/email/email.html.twig @@ -0,0 +1,7 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="email" + {% if field.multiple in ['on', 'true', 1] %}multiple="multiple"{% endif %} + {{ parent() }} +{% endblock %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/fieldset/fieldset.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/fieldset/fieldset.html.twig new file mode 100644 index 0000000000..b934e231cf --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/fieldset/fieldset.html.twig @@ -0,0 +1,14 @@ +
    +{% if field.legend %} + {% if grav.twig.twig.filters['tu'] is defined %}{{ field.legend|tu }}{% else %}{{ field.legend|t }}{% endif %} +{% endif %} + +{% if field.fields %} + {% for field in field.fields %} + {% if field.type %} + {% set value = form.value(field.name) %} + {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} + {% endif %} + {% endfor %} +{% endif %} +
    \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/file/file.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/file/file.html.twig new file mode 100644 index 0000000000..d237f72b13 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/file/file.html.twig @@ -0,0 +1,105 @@ +{% extends "forms/field.html.twig" %} +{% set defaults = config.plugins.form %} +{% set files = defaults.files|merge(field|default([])) %} +{% set limit = not field.multiple ? 1 : files.limit %} + +{% macro bytesToSize(bytes) -%} + {% spaceless %} + {% set kilobyte = 1024 %} + {% set megabyte = kilobyte * 1024 %} + {% set gigabyte = megabyte * 1024 %} + {% set terabyte = gigabyte * 1024 %} + + {% if bytes < kilobyte %} + {{ bytes ~ ' B' }} + {% elseif bytes < megabyte %} + {{ (bytes / kilobyte)|number_format(2, '.') ~ ' KB' }} + {% elseif bytes < gigabyte %} + {{ (bytes / megabyte)|number_format(2, '.') ~ ' MB' }} + {% elseif bytes < terabyte %} + {{ (bytes / gigabyte)|number_format(2, '.') ~ ' GB' }} + {% else %} + {{ (bytes / terabyte)|number_format(2, '.') ~ ' TB' }} + {% endif %} + {% endspaceless %} +{%- endmacro %} + +{% macro preview(path, value, global) %} + {% if value %} + {% set uri = global.grav.uri %} + {% set files = global.files %} + {% set config = global.grav.config %} + {% set route = global.context.route() %} + {% set type = global.context.content() is not null ? 'pages' : global.plugin ? 'plugins' : global.theme ? 'themes' : 'config' %} + {% set blueprint_name = global.blueprints.getFilename %} + {% if type == 'pages' %} + {% set blueprint_name = type ~ '/' ~ blueprint_name %} + {% endif %} + {% set blueprint = base64_encode(blueprint_name) %} + {% set real_path = global.form.getPagePathFromToken(path) %} + {% set remove = uri.addNonce(global.base_url_relative ~ + '/media.json' ~ + '/route' ~ config.system.param_sep ~ base64_encode(global.base_path ~ '/' ~ real_path) ~ + '/task' ~ config.system.param_sep ~ 'removeFileFromBlueprint' ~ + '/proute' ~ config.system.param_sep ~ base64_encode(route) ~ + '/blueprint' ~ config.system.param_sep ~ blueprint ~ + '/type' ~ config.system.param_sep ~ type ~ + '/field' ~ config.system.param_sep ~ files.name ~ + '/path' ~ config.system.param_sep ~ base64_encode(value.path), 'admin-form', 'admin-nonce') %} + + {% set file = value|merge({remove: remove, path: (uri.rootUrl == '/' ? '/' : uri.rootUrl ~ '/' ~ real_path) }) %} + + {% endif %} +{% endmacro %} + +{% block input %} + {% set page_can_upload = exists or (type == 'page' and not exists and not (field.destination starts with '@self' or field.destination starts with 'self@')) %} + + {% set settings = {name: field.name, paramName: (scope ~ field.name)|fieldName ~ (files.multiple ? '[]' : ''), limit: limit, filesize: files.filesize, accept: files.accept} %} +
    + + + {% for path, file in value %} + {{ _self.preview(path, file, _context) }} + {% endfor %} + {% include 'forms/fields/hidden/hidden.html.twig' with {field: {name: '_json.' ~ field.name}, value:value|json_encode|e('html_attr')} %} +
    + + {% do assets.addJs('jquery', 101) %} + {% do assets.addJs('plugin://form/assets/form.min.js', { 'group': 'bottom', 'loading': 'defer' }) %} + {% do assets.addCss('plugin://form/assets/dropzone.min.css', { 'group': 'form' }) %} + {{ assets.css('form')|raw }} + {% do assets.addInlineJs(" + window.GravForm = window.GravForm || {}; + window.GravForm.config = { + current_url: '" ~ uri.route(true) ~"', + base_url_relative: '" ~ base_url_relative ~ "', + param_sep: '"~ config.system.param_sep ~ "', + form_nonce: '" ~ form.getNonce ~ "', + }; + window.GravForm.translations = {}; + window.GravForm.translations['PLUGIN_FORM'] = { + 'DROPZONE_CANCEL_UPLOAD': " ~ 'PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD'|t|json_encode ~ ", + 'DROPZONE_CANCEL_UPLOAD_CONFIRMATION': " ~ 'PLUGIN_FORM.DROPZONE_CANCEL_UPLOAD_CONFIRMATION'|t|json_encode ~ ", + 'DROPZONE_DEFAULT_MESSAGE': " ~ 'PLUGIN_FORM.DROPZONE_DEFAULT_MESSAGE'|t|json_encode ~ ", + 'DROPZONE_FALLBACK_MESSAGE': " ~ 'PLUGIN_FORM.DROPZONE_FALLBACK_MESSAGE'|t|json_encode ~ ", + 'DROPZONE_FALLBACK_TEXT': " ~ 'PLUGIN_FORM.DROPZONE_FALLBACK_TEXT'|t|json_encode ~ ", + 'DROPZONE_FILE_TOO_BIG': " ~ 'PLUGIN_FORM.DROPZONE_FILE_TOO_BIG'|t|json_encode ~ ", + 'DROPZONE_INVALID_FILE_TYPE': " ~ 'PLUGIN_FORM.DROPZONE_INVALID_FILE_TYPE'|t|json_encode ~ ", + 'DROPZONE_MAX_FILES_EXCEEDED': " ~ 'PLUGIN_FORM.DROPZONE_MAX_FILES_EXCEEDED'|t|json_encode ~ ", + 'DROPZONE_REMOVE_FILE': " ~ 'PLUGIN_FORM.DROPZONE_REMOVE_FILE'|t|json_encode ~ ", + 'DROPZONE_REMOVE_FILE_CONFIRMATION': " ~ 'PLUGIN_FORM.DROPZONE_REMOVE_FILE_CONFIRMATION'|t|json_encode ~ ", + 'DROPZONE_RESPONSE_ERROR': " ~ 'PLUGIN_FORM.DROPZONE_RESPONSE_ERROR'|t|json_encode ~ " + }; + ", { 'group': 'bottom' }) %} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/formname/formname.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/formname/formname.html.twig new file mode 100644 index 0000000000..70e2bc08b8 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/formname/formname.html.twig @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/hidden/hidden.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/hidden/hidden.html.twig new file mode 100644 index 0000000000..1c0b48188d --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/hidden/hidden.html.twig @@ -0,0 +1,3 @@ +{% set value = (value is null ? (field.evaluate ? evaluate(field.default) : field.default) : value) %} + + diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/honeypot/honeypot.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/honeypot/honeypot.html.twig new file mode 100644 index 0000000000..c1138b16e9 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/honeypot/honeypot.html.twig @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/month/month.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/month/month.html.twig new file mode 100644 index 0000000000..36c0fc23ca --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/month/month.html.twig @@ -0,0 +1,6 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="month" + {{ parent() }} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/number/number.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/number/number.html.twig new file mode 100644 index 0000000000..116c7cabb9 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/number/number.html.twig @@ -0,0 +1,9 @@ +{% extends "forms/fields/text/text.html.twig" %} + +{% block input_attributes %} + type="number" + {% if field.validate.min is defined %}min="{{ field.validate.min }}"{% endif %} + {% if field.validate.max is defined %}max="{{ field.validate.max }}"{% endif %} + {% if field.validate.step is defined %}step="{{ field.validate.step }}"{% endif %} + {{ parent() }} +{% endblock %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/password/password.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/password/password.html.twig new file mode 100644 index 0000000000..58047e8603 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/password/password.html.twig @@ -0,0 +1,7 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="password" + class="input" + {{ parent() }} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/radio/radio.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/radio/radio.html.twig new file mode 100644 index 0000000000..76cc00a072 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/radio/radio.html.twig @@ -0,0 +1,22 @@ +{% extends "forms/field.html.twig" %} + +{% set originalValue = value %} +{% set value = (value is null ? field.default : value) %} + +{% block input %} + {% for key, text in field.options %} + {% set id = field.id|default(field.name) ~ '-' ~ key %} + + + + + + {% endfor %} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/range/range.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/range/range.html.twig new file mode 100644 index 0000000000..3b04284d5d --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/range/range.html.twig @@ -0,0 +1,9 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="range" + {% if field.validate.min %}min="{{ field.validate.min }}"{% endif %} + {% if field.validate.max %}max="{{ field.validate.max }}"{% endif %} + {% if field.validate.step %}step="{{ field.validate.step }}"{% endif %} + {{ parent() }} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/select/select.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/select/select.html.twig new file mode 100644 index 0000000000..c1b4815c46 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/select/select.html.twig @@ -0,0 +1,34 @@ +{% extends "forms/field.html.twig" %} + +{% block global_attributes %} + data-grav-selectize="{{ (field.selectize is defined ? field.selectize : {})|json_encode()|e('html_attr') }}" + {{ parent() }} +{% endblock %} + +{% block input %} +
    + +
    +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/select_optgroup/select_optgroup.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/select_optgroup/select_optgroup.html.twig new file mode 100644 index 0000000000..cf75eab88d --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/select_optgroup/select_optgroup.html.twig @@ -0,0 +1,44 @@ +{% extends "forms/field.html.twig" %} + +{% block global_attributes %} + data-grav-selectize="{{ (field.selectize is defined ? field.selectize : {})|json_encode()|e('html_attr') }}" + {{ parent() }} +{% endblock %} + +{% block input %} +
    + +
    +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/spacer/spacer.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/spacer/spacer.html.twig new file mode 100644 index 0000000000..f0a959391a --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/spacer/spacer.html.twig @@ -0,0 +1,33 @@ +
    + {% if field.title %} +

    + {% if grav.twig.twig.filters['tu'] is defined %} + {{- field.title|tu|raw -}} + {% else %} + {{- field.title|t|raw -}} + {% endif %} +

    + {% endif %} + + {% if field.markdown %} +

    + {% if grav.twig.twig.filters['tu'] is defined %} + {{- field.text|tu|markdown|raw -}} + {% else %} + {{- field.text|t|markdown|raw -}} + {% endif %} +

    + {% else %} +

    + {% if grav.twig.twig.filters['tu'] is defined %} + {{- field.text|tu|raw -}} + {% else %} + {{- field.text|t|raw -}} + {% endif %} +

    + {% endif %} + + {% if field.underline %} +
    + {% endif %} +
    diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/tel/tel.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/tel/tel.html.twig new file mode 100644 index 0000000000..68983131eb --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/tel/tel.html.twig @@ -0,0 +1,6 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="tel" + {{ parent() }} +{% endblock %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/text/text.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/text/text.html.twig new file mode 100644 index 0000000000..d6a9af3279 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/text/text.html.twig @@ -0,0 +1,37 @@ +{% extends "forms/field.html.twig" %} + +{% block prepend %} +{% if field.prepend %} +
    + {% if grav.twig.twig.filters['tu'] is defined %} + {{- field.prepend|tu|raw -}} + {% else %} + {{- field.prepend|t|raw -}} + {% endif %} +
    +{% endif %} +{% endblock %} + +{% block input_attributes %} + type="text" + {{ parent() }} +{% endblock %} + +{% block append %} + {% if field.append %} +
    + {% if grav.twig.twig.filters['tu'] is defined %} + {{- field.append|tu|raw -}} + {% else %} + {{- field.append|t|raw -}} + {% endif %} +
    + {% endif %} +{% endblock %} + +{% block input %} +{% if field.prepend or field.append %} + {% set field = field|merge({'wrapper_classes': 'form-input-addon-wrapper'}) %} +{% endif %} +{{ parent() }} +{% endblock %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/textarea/textarea.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/textarea/textarea.html.twig new file mode 100644 index 0000000000..81ec36cf18 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/textarea/textarea.html.twig @@ -0,0 +1,27 @@ +{% extends "forms/field.html.twig" %} + +{% block input %} +
    + +
    +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/time/time.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/time/time.html.twig new file mode 100644 index 0000000000..b6bb10ecd3 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/time/time.html.twig @@ -0,0 +1,6 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="time" + {{ parent() }} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/uniqueid/uniqueid.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/uniqueid/uniqueid.html.twig new file mode 100644 index 0000000000..417f230a5a --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/uniqueid/uniqueid.html.twig @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/url/url.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/url/url.html.twig new file mode 100644 index 0000000000..1caae0f803 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/url/url.html.twig @@ -0,0 +1,6 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="url" + {{ parent() }} +{% endblock %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/forms/fields/week/week.html.twig b/sandbox/grav/user/plugins/form/templates/forms/fields/week/week.html.twig new file mode 100644 index 0000000000..c56f8711ff --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/fields/week/week.html.twig @@ -0,0 +1,6 @@ +{% extends "forms/field.html.twig" %} + +{% block input_attributes %} + type="week" + {{ parent() }} +{% endblock %} diff --git a/sandbox/grav/user/plugins/form/templates/forms/form.html.twig b/sandbox/grav/user/plugins/form/templates/forms/form.html.twig new file mode 100644 index 0000000000..8f1d4670c2 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/forms/form.html.twig @@ -0,0 +1 @@ +{% extends "forms/default/form.html.twig" %} diff --git a/sandbox/grav/user/plugins/form/templates/modular/form.html.twig b/sandbox/grav/user/plugins/form/templates/modular/form.html.twig new file mode 100644 index 0000000000..3e06da56f2 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/modular/form.html.twig @@ -0,0 +1,4 @@ +
    + {{ content|raw }} + {% include "forms/form.html.twig" %} +
    \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/templates/partials/form-messages.html.twig b/sandbox/grav/user/plugins/form/templates/partials/form-messages.html.twig new file mode 100644 index 0000000000..4fd13bc631 --- /dev/null +++ b/sandbox/grav/user/plugins/form/templates/partials/form-messages.html.twig @@ -0,0 +1,5 @@ +{% if form.message %} + {% set message = form.inline_errors and form.messages ? "FORM.VALIDATION_FAIL"|t : form.message %} + +

    {{ message|raw }}

    +{% endif %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/form/webpack.conf.js b/sandbox/grav/user/plugins/form/webpack.conf.js new file mode 100644 index 0000000000..879f6008e5 --- /dev/null +++ b/sandbox/grav/user/plugins/form/webpack.conf.js @@ -0,0 +1,23 @@ +var webpack = require('webpack'), + path = require('path'), + exec = require('child_process').execSync, + pwd = exec('pwd').toString(); + +module.exports = { + entry: { + app: './app/main.js' + }, + externals: { + jquery: 'jQuery', + 'grav-form': 'GravForm' + }, + module: { + preLoaders: [ + { test: /\.json$/, loader: 'json' }, + { test: /\.js$/, loader: 'eslint', exclude: /node_modules/ } + ], + loaders: [ + { test: /\.js$/, loader: 'babel', exclude: /node_modules/, query: { presets: ['es2015'] } } + ] + } +}; diff --git a/sandbox/grav/user/plugins/login/.gitignore b/sandbox/grav/user/plugins/login/.gitignore new file mode 100644 index 0000000000..090a1f02df --- /dev/null +++ b/sandbox/grav/user/plugins/login/.gitignore @@ -0,0 +1,2 @@ +.idea +.DS_Store diff --git a/sandbox/grav/user/plugins/login/CHANGELOG.md b/sandbox/grav/user/plugins/login/CHANGELOG.md new file mode 100644 index 0000000000..ed6941e471 --- /dev/null +++ b/sandbox/grav/user/plugins/login/CHANGELOG.md @@ -0,0 +1,444 @@ +# v2.4.1 +## 09/12/2017 + +1. [](#bugfix) + * Fixed an issue with 3rd party login plugins [#130](https://github.com/getgrav/grav-plugin-login/issues/130) + +# v2.4.0 +## 09/07/2017 + +1. [](#new) + * Added the ability to have a custom route for login page, but not redirect + * Added a new `unauthorized.md` page that can be customized as needed +1. [](#improved) + * Differentiated between `authenticated` and `authorized` + * Moved rate-limiting logic to the Login class + * Much code cleanup and removing of cruft + * Updated vendor libraries + * Added Russian translation +1. [](#bugfix) + * Fixed login JSON response in case of login failure + * Fixed issue with profile form displaying on login page + * Store referrer page when trying to access Profile page + * Fixed error when logging out with an expired session + +# v2.3.2 +## 06/22/2017 + +1. [](#bugfix) + * Grav plugin cli error on password change [#120](https://github.com/getgrav/grav-plugin-login/issues/120) + +# v2.3.1 +## 05/16/2017 + +1. [](#improved) + * Added routes to the Admin blueprints + +# v2.3.0 +## 04/19/2017 + +1. [](#new) + * Added new built-in profile page support + * Added optional flood protection for password resets and login attempts [#91](https://github.com/getgrav/grav-plugin-login/issues/91) +1. [](#improved) + * Use new system configuration entries for username and password format + * Use initialized form object in Twig templates rather than array from page.header + * Improved alert styling in login templates + * Added `appends` for number field + * Added missing `route` options in admin options (blueprints) +1. [](#bugfix) + * Set cookie path to `/` if `base_url_relative` is empty [#102](https://github.com/getgrav/grav-plugin-login/issues/102) + * Fixed some redirect logic + +# v2.2.1 +## 01/24/2017 + +1. [](#bugfix) + * Fix login form/status templates displaying user as logged in even if he's not authenticated + * Use email validation instead of text validation in the forgot password form [https://github.com/gantry/gantry5/issues/1813](https://github.com/gantry/gantry5/issues/1813) + +# v2.2.0 +## 12/13/2016 + +1. [](#new) + * RC released as stable + +# v2.2.0-rc.5 +## 12/07/2016 + +1. [](#improved) + * Added support for hiding `Remember me` checkbox and and `Forgot` button (for Offline functionality) +1. [](#bugfix) + * Fixed redirect issue in admin plugin + +# v2.2.0-rc.4 +## 12/04/2016 + +1. [](#improved) + * Improved logic for redirect after login to not include login-related pages. + +# v2.2.0-rc.3 +## 11/26/2016 + +1. [](#improved) + * Added some validity checks in the reset password form +1. [](#bugfix) + * Correctly redirect to the last page visited after login, unless `redirect_after_login` is defined + +# v2.2.0-rc.2 +## 11/17/2016 + +1. [](#new) + * Allow to set permissions using nested array syntax [#96](https://github.com/getgrav/grav-plugin-login/issues/96) +1. [](#improved) + * Use the same feedback message when resetting the password if the email exists or not. Remove email in the message as we now recover via email, useless +1. [](#bugfix) + * Fix registration form, fields were not visible [#97](https://github.com/getgrav/grav-plugin-login/issues/97) + * Do not initialize the user session if the user exists but has no `site.login` permission + +# v2.2.0-rc.1 +## 11/09/2016 + +1. [](#new) + * Allow login via `username` or `email` + * Only allow password recovery via `email` address + +# v2.1.2 +## 10/01/2016 + +1. [](#bugfix) + * Fixed an old reference to `LoginUtils` and replaced with new `EmailUtils` + +# v2.1.1 +## 09/08/2016 + +1. [](#improved) + * Use better detection for admin allowing multi-site setup with subfolders + +# v2.1.0 +## 09/07/2016 + +1. [](#improved) + * Added support for Grav's autoescape twig setting + * Dropped unused variable reference + * Moved Email Utils to Email plugin + * Updated vendor libraries + * Allow explicitly showing the login page on pages that are not the Login form template [#11](https://github.com/getgrav/grav-plugin-maintenance/issues/11) + +# v2.0.1 +## 08/10/2016 + +1. [](#improved) + * Added Romanian + +# v2.0.0 +## 07/14/2016 + +1. [](#improved) + * Optimized nonce creation + * Point account path to core's account stream [#85](https://github.com/getgrav/grav-plugin-login/issues/85) + +# v2.0.0-rc.2 +## 06/21/2016 + +1. [](#new) + * Add an option to login protect a login-protected page media accessed through the page route [#45](https://github.com/getgrav/grav-plugin-login/issues/45) +1. [](#improved) + * Fixed some language keys +1. [](#bugfix) + * Correctly show an error message when the reset password form does not provide the correct nonce + +# v2.0.0-rc.1 +## 06/01/2016 + +1. [](#improved) + * French updated +1. [](#bugfix) + * Enable twig processing in a page #75 + * Deny access to registration when user registration is disabled #72 + +# v2.0.0-beta.3 +## 05/23/2016 + +1. [](#improved) + * Added a redirect after activation + * Changed hardcoded redirect routes to config-based +1. [](#bugfix) + * Fix a redirect issue #74 + * Don't error if missing a HTTP_USER_AGENT browser string + +# v2.0.0-beta.2 +## 05/03/2016 + +1. [](#improved) + * Improved the login form page once logged in + * Translate welcome and logout strings +1. [](#bugfix) + * Fixed logging out on the homepage + * Fixed an issue in processing user registration + +# v2.0.0-beta.1 +## 04/20/2016 + +1. [](#new) + * Introduce a more flexible Login plugin architecture, which allows separate authentication plugins to hook into the Login events. Separated OAuth to its own plugin. + * OAuth has been separated to its own plugin, needs to be installed separately and configured. The users account filename format has changed too, to fix an issue that involved people with the same name on a service. + * The `redirect` option has been changed to `redirect_after_login`. Make sure you update your configuration file. +1. [](#improved) + * Add a proper 'Access levels' config section for Login. + * Various underlying improvements + * Updated french, added german +1. [](#bugfix) + * Make username field autofocus + * Add validation to the password reset form + * Fixed an issue that allowed a user logged in, without access to the actual permissions set to view a page, to see its content, and the login form again even if already logged in. + +# v1.3.1 +## 02/05/2016 + +1. [](#new) + * Add translations for Username and Password (placeholders are not translated) +1. [](#improved) + * Improve registration, forgot, reset and login forms accessibility by setting the id attribute + * Improved french translation + * Add the correct message type when raising a form processing error +1. [](#bugfix) + * Show the correct error message when the user is not authorized to view a page + * Fix showing the OAuth links in the login form + +# v1.3.0 +## 01/06/2016 + +1. [](#new) + * Added a new CLI command to change a user's password + * Added a new CLI command to edit the user state +1. [](#improved) + * Improved french translation + +# v1.2.1 +## 12/18/2015 + +1. [](#new) + * Croatian translation +1. [](#improved) + * Use type `email` in registration form + * Drop manual validation in registration + +# v1.2.0 +## 12/11/2015 + +1. [](#new) + * Added account activation email upon registration + * Added forgot password functionality + * Support ACL from parent page + * Allow login immediately after account activation +1. [](#improved) + * Handle admin login page if available + * Example registration form now provided by plugin + * Better error handling of registration + * Tab-based plugin configuration + * Updated translations +1. [](#bugfix) + * Prevent failing when no default values are set + +# v1.1.0 +## 12/01/2015 + +1. [](#new) + * Support new **User Registration** +1. [](#improved) + * Use new security salt for newer and fallback otherwise + * Composer update of libraries + * Check for session existence else throw a runtime error +1. [](#bugfix) + * Fix remember-me functionality + * Check page exists so as not to fail hard + * Fix for static Inflector references #17 + + +# v1.0.1 +## 11/23/2015 + +1. [](#improved) + * Hardening cookies with user-agent and system cache key instead of deprecated system hash + * Set a custom route for login only if it's not an admin path + +# v1.0.0 +## 11/21/2015 + +1. [](#new) + * Added OAuth login support for _Facebook_, _Google_, _GitHub_ and _Twitter_ + * Added **Nonce** form security support + * Added option to "redirect after login" + * Added "remember me" functionality + * Added Hungarian translation +2. [](#improved) + * Added blueprints for Grav Admin plugin (multi-language support!) + +# v0.3.3 +## 09/11/2015 + +1. [](#improved) + * Changed authorise to authorize +1. [](#bugfix) + * Fix denied string + +# v0.3.2 +## 09/01/2015 + +1. [](#improved) + * Broke out login form into its own partial + +# v0.3.1 +## 08/31/2015 + +1. [](#improved) + * Added username field autofocus + +# v0.3.0 +## 08/24/2015 + +1. [](#new) + * Added simple CSS styling + * Added simple login status with logout +1. [](#improved) + * Improved README documentation + * More strings translated + * Updated blueprints + +# v0.2.0 +## 08/11/2015 + +1. [](#improved) + * Disable `enable` in admin + +# v0.1.0 +## 08/04/2015 + +1. [](#new) + * ChangeLog started... + +# v1.3.1 +## 02/05/2016 + +1. [](#new) + * Add translations for Username and Password (placeholders are not translated) +1. [](#improved) + * Improve registration, forgot, reset and login forms accessibility by setting the id attribute + * Improved french translation + * Add the correct message type when raising a form processing error +1. [](#bugfix) + * Show the correct error message when the user is not authorized to view a page + * Fix showing the OAuth links in the login form + +# v1.3.0 +## 01/06/2016 + +1. [](#new) + * Added a new CLI command to change a user's password + * Added a new CLI command to edit the user state +1. [](#improved) + * Improved french translation + +# v1.2.1 +## 12/18/2015 + +1. [](#new) + * Croatian translation +1. [](#improved) + * Use type `email` in registration form + * Drop manual validation in registration + +# v1.2.0 +## 12/11/2015 + +1. [](#new) + * Added account activation email upon registration + * Added forgot password functionality + * Support ACL from parent page + * Allow login immediately after account activation +1. [](#improved) + * Handle admin login page if available + * Example registration form now provided by plugin + * Better error handling of registration + * Tab-based plugin configuration + * Updated translations +1. [](#bugfix) + * Prevent failing when no default values are set + +# v1.1.0 +## 12/01/2015 + +1. [](#new) + * Support new **User Registration** +1. [](#improved) + * Use new security salt for newer and fallback otherwise + * Composer update of libraries + * Check for session existence else throw a runtime error +1. [](#bugfix) + * Fix remember-me functionality + * Check page exists so as not to fail hard + * Fix for static Inflector references #17 + + +# v1.0.1 +## 11/23/2015 + +1. [](#improved) + * Hardening cookies with user-agent and system cache key instead of deprecated system hash + * Set a custom route for login only if it's not an admin path + +# v1.0.0 +## 11/21/2015 + +1. [](#new) + * Added OAuth login support for _Facebook_, _Google_, _GitHub_ and _Twitter_ + * Added **Nonce** form security support + * Added option to "redirect after login" + * Added "remember me" functionality + * Added Hungarian translation +2. [](#improved) + * Added blueprints for Grav Admin plugin (multi-language support!) + +# v0.3.3 +## 09/11/2015 + +1. [](#improved) + * Changed authorise to authorize +1. [](#bugfix) + * Fix denied string + +# v0.3.2 +## 09/01/2015 + +1. [](#improved) + * Broke out login form into its own partial + +# v0.3.1 +## 08/31/2015 + +1. [](#improved) + * Added username field autofocus + +# v0.3.0 +## 08/24/2015 + +1. [](#new) + * Added simple CSS styling + * Added simple login status with logout +1. [](#improved) + * Improved README documentation + * More strings translated + * Updated blueprints + +# v0.2.0 +## 08/11/2015 + +1. [](#improved) + * Disable `enable` in admin + +# v0.1.0 +## 08/04/2015 + +1. [](#new) + * ChangeLog started... diff --git a/sandbox/grav/user/plugins/login/LICENSE b/sandbox/grav/user/plugins/login/LICENSE new file mode 100644 index 0000000000..4bb709282c --- /dev/null +++ b/sandbox/grav/user/plugins/login/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Grav + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sandbox/grav/user/plugins/login/README.md b/sandbox/grav/user/plugins/login/README.md new file mode 100644 index 0000000000..cbaf135b53 --- /dev/null +++ b/sandbox/grav/user/plugins/login/README.md @@ -0,0 +1,366 @@ +# Grav Login Plugin + +The **login plugin** for [Grav](http://github.com/getgrav/grav) adds login, basic ACL, and session wide messages to Grav. It is designed to provide a way to secure front-end and admin content throughout Grav. + +# Installation + +The **login** plugin actually requires the help of the **email** and **form** plugins. The **email** plugin is needed to ensure that you can recover a password via email if required. The **form** plugin is used to generate the forms required. + +These are available via GPM, and because the plugin has dependencies you just need to proceed and install the login plugin, and agree when prompted to install the others: + +``` +$ bin/gpm install login +``` + +# Changes in version 2.0 + +The Login Plugin 2.0 has the following changes compared to 1.0: + +- OAuth has been separated to its own plugin, needs to be installed separately and configured. The users account filename format has changed too, to fix an issue that involved people with the same name on a service. +- The `redirect` option has been changed to `redirect_after_login`. +- The Remember Me session minimum length is now 1 week. +- Removed the option to login from oauth without creating the corresponding user file under `user/accounts/`. + +# Creating Users + +You can either use the built-in CLI capabilities, or you create a user manually by creating a new YAML file in your `user/acounts` folder. + + +# CLI Usage + +The simplest way to create a new user is to simply run the `bin/plugin login newuser` command. This will take you through a few questions to gather information with which to create your user. You can also use inline arguments to avoid the interactive questions. + +### Commands + +| Command | Arguments | Explination | +|---------------|--------------------------------------|----------------------------| +|`newuser`|Aliases: `add-user`, `new-user`|Creates a new user (creates file in `user/accounts/`) +|| [ -u, --user=USER ] | The username. | +|| [ -p, --password=PASSWORD ] | The password. Ensure the password respects Grav's password policy. **Note that this option is not recommended because the password will be visible by users listing the processes.** | +|| [ -e, --email=EMAIL ] | The user email address. | +|| [ -P, --permissions=PERMISSIONS ] | The user permissions. It can be either `a` for Admin access only, `s` for Site access only and `b` for both Admin and Site access. | +|| [ -N, --fullname=FULLNAME ] | The user full name | +|| [ -t, --title=TITLE ] | The title of the user. Usually used as a subtext. Example: Admin, Collaborator, Developer | +|| [ -s, --state=STATE ] | The state of the account. Either `enabled` (default) or `disabled` | +||| +|`changepass`|Aliases: `newpass`, `passwd`|Changes password of the specified user (User file must exist) +|| [ -u, --user=USER ] | The username. | +|| [ -p, --password=PASSWORD ] | The new password. Ensure the password respects Grav's password policy. **Note that this option is not recommended because the password will be visible by users listing the processes.** | + + +### CLI Example +``` +> bin/plugin login newuser -u joeuser -p 8c9sRCeBExAiwk -e joeuser@grav.org -P b -N "Joe User" -t "Site Administrator" +Creating new user + + +Success! User joeuser created. +``` + +### Interactive Example +``` +> bin/plugin login newuser +Creating new user + +Enter a username: joeuser +Enter a password: 8c9sRCeBExAiwk +Enter an email: joeuser@grav.org +Please choose a set of permissions: + [a] admin access + [s] site access + [b] admin and site access + > b +Enter a fullname: Joe User +Enter a title: Site Administrator +Please choose the state for the account: + [enabled ] Enabled + [disabled] Disabled + > enabled + +Success! User joeuser created. +``` + +### Manual User Creation + +Here is example user defined in `user/accounts/admin.yaml`: + +``` +password: password +email: youremail@mail.com +fullname: Johnny Appleseed +title: Site Administrator +access: + admin: + login: true + super: true +``` + +>> Note: the username is based on the name of the YAML file. + +# Usage + +You can add ACL to any page by typing something like below into the page header: + +``` +access: + site.login: true + admin.login: true +``` + +Users who have any of the listed ACL roles enabled will have access to the page. +Others will be forwarded to login screen. + +Because the admin user contains an `admin.login: true` reference he will be able to login to the secured page because that is one of the conditions defined in the page header. You are free to create any specific set of ACL rules you like. Your user account must simply contain those same rules if you wish the user to have access. + +## Create Private Areas + +Enabling the setting "Use parent access rules" (`parent_acl` in login.yaml) allows you to create private areas where you set the access level on the parent page, and all the subpages inherit that requirement. + +# Login Page + +>> Note: the **frontend site** and **admin plugin** use different sessions so you need to explicitly provide a login on the frontend. + +The login plugin can **automatically generate** a login page for you when you try to access a page that your user (or guest account) does not have access to. + +Alternatively, you can also provide a specific login route if you wish to forward users to a specific login page. To do this you need to create a copy of the `login.yaml` from the plugin in your `user/config/plugins` folder and provide a specific route (or just edit the plugin setttings in the admin plugin). + +``` +route: /user-login +``` + +You would then need to provide a suitable login form, probably based on the one that is provided with the plugin. + +## Redirection after Login + +By default Grav will redirect to the prior page visited before entering the login process. Any page is fair game unless you manually set: + +``` +login_redirect_here: false +``` + +In the page's header. If you set this value to `false`, this page will not be a valid redirect page, and the page visited prior to this page will be considered. + +You can override this default behavior by forcing a standard location by specifying an explicit option in your Login configuration YAML: + +``` +redirect_after_login: '/profile' +``` + +This will always take you to the `/profile` route after a successful login. + +# Logout + +The login plugin comes with a simple Twig partial to provide a logout link (`login-status.html.twig`). You will need to include it in your theme however. An example of this can be found in the Antimatter theme's `partials/navigation.html.twig` file: + +``` +{% if config.plugins.login.enabled and grav.user.username %} +
  • {% include 'partials/login-status.html.twig' %}
  • +{% endif %} +``` + +You can also copy this `login-status.html.twig` file into your theme and modify it as you see fit. + +# Allow User Registration + +The Login plugin handles user registration. +To enable the built-in registration form, in the Login Plugin configuration enable user registration and just add a value to the "Registration path" field. + +Then just open your browser on that page, and you'll be presented a registration form. + +## Adding the registration page to the menu + +Here are two ways you can do it, but of course Grav is flexible and you can come up with other ways too. + +The first and easiest way is to add a page with the same slug (route) as the registration form. So for example if in the Login Plugin settings you set /register as the registration form path, then create a `04.register` page (the 04 number is just an example, use your own ordering), with no content. +The Login plugin will "override" that page, serving the registration page form when the user clicks on that menu item. + +A second way is to add a custom menu item that points to the registration page, by editing `site.yaml` with this code, that will append a "Register" menu item: + +``` +menu: + - + url: 'register' + text: Register +``` + +This works in most themes, Antimatter included, but it's not guaranteed to work in all themes, as it's something that must be added to the navigation twig code. + +## Customizing the registration form + +The provided registration form is just a quick way to start using it. You might however need different fields on the registration form, or you want to add more content. Here's how to do it. + +First, create a registration form page. + +Create a folder `04.registration/form.md`. The folder name is just an example. Pick the one that suits you. The important part is the file name: since we're building a form, we need a `form.md` file. + +Also, your theme needs to implement forms. Use Antimatter or another form-compatible theme if yours does not work, then once you're setup with the form you can migrate the forms files and make it work on your theme too. + +Add the following content to your registration form page: + +``` +--- +form: + + fields: + - + name: username + type: text + validate: + required: true + message: PLUGIN_LOGIN.USERNAME_NOT_VALID + config-pattern@: system.username_regex + + - + name: email + type: text + validate: + required: true + message: PLUGIN_LOGIN.EMAIL_VALIDATION_MESSAGE + + - + name: password1 + type: password + label: Enter a password + validate: + required: true + message: PLUGIN_LOGIN.PASSWORD_VALIDATION_MESSAGE + config-pattern@: system.pwd_regex + + - + name: password2 + type: password + label: Repeat the password + validate: + required: true + message: PLUGIN_LOGIN.PASSWORD_VALIDATION_MESSAGE + config-pattern@: system.pwd_regex + + buttons: + - + type: submit + value: Submit + - + type: reset + value: Reset + + process: + register_user: true + display: '/welcome' + message: "Welcome to my site!" +--- + +# Registration +``` + +This is a normal form. The only thing different from a contact form or another form that you might write on your site is the process field `register_user`, which takes care of processing the user registration. + +Once the user is registered, Grav redirects the user to the `display` page with the `message` message. + +The only field strictly required by Grav is `username`. Then the other fields can be added as needed. + +For example in this case we added + +- password1 +- password2 + +to the form. And, in the Login plugin configuration we have by default enable the double password verification with the "Validate double entered password" option. What this does is picking the password1 and password2 fields, validate them, check they are equal and put the content in the `password` field. + +You can avoid having 2 fields for the password, which by the way is a recommended option, and just put a single `password` field. + +Last important thing before the registration is correctly setup: make sure in the Login plugin settings you have the user registration enabled, otherwise the registration will trigger an error, as by default user registration is DISABLED. + +## Sending an activation email + +By default the registration process adds a new user, and sets it as enabled. +Grav allows disabled user accounts, so we can take advantage of this functionality and add a new user, but with a disabled state. Then we can send an email to the user, asking to validate the email address. + +That validation email will contain a link to set the user account to enabled. To do this, just enable "Set the user as disabled" and "Send activation email" in the Login Plugin options. + +## Send a welcome email + +Enable "Send welcome email" in the options. + +The content of the welcome email is defined in the language file, strings `PLUGIN_LOGIN.WELCOME_EMAIL_SUBJECT` and `PLUGIN_LOGIN.WELCOME_EMAIL_BODY`. Customize them as needed in your language file override. + +Note: if the activation email is enabled, the welcome email to be sent upon the account activation action (when the user clicks the link to activate the account) + +## Send a notification email to the site owner + +Enable "Send notification email" in the options. + +The content of the notification email is defined in the language file, strings `PLUGIN_LOGIN.NOTIFICATION_EMAIL_SUBJECT` and `PLUGIN_LOGIN.NOTIFICATION_EMAIL_BODY`. Customize them as needed in your language file override. + +Note: if the activation email is enabled, the notification email to be sent upon the account activation action (when the user clicks the link to activate the account) + +## Adding your own fields + +If you want to add your own custom fields to the registration form, just add fields to the form like you would with any other form. + +Then, to let the Login plugin add those fields to the user yaml file, you also need to add it to the "Registration fields" option in the Login Plugin configuration. + +By default we have + +``` + - 'username' + - 'password' + - 'email' + - 'fullname' + - 'title' + - 'access' + - 'state' +``` + +Add your own as you prefer, to build any custom registration form you can think of. + +## Specifying a default value for a field + +If you want to pre-fill a field, without showing it to the user in the form, you could set it as an hidden field. But the user could see it - and modify it via the browser dev tools. + +To add a field and make sure the user cannot modify it, add it to "Default values" list. + +## Login users directly after the registration + +Just enable "Login the user after registration" + +If the user activation email is enabled, the user will be logged in as soon as the activation link is clicked. + +## Add captcha to the user registration + +Add a captcha like you would with any form: + +Add + +``` + - name: g-recaptcha-response + label: Captcha + type: captcha + recaptcha_site_key: aeio43kdk3idko3k4ikd4 + recaptcha_not_validated: 'Captcha not valid!' + validate: + required: true +``` + +to the form field, and + +``` +process: + - captcha +``` + +to validate it server-side. Put this process action before all the other actions, so it's processed first and the user is not created if the captcha is not valid. + +## Redirect to another page after login + +You can set the "Redirect after registration" option in the Login plugin, or as with any form, use the `process.display` property, and set it to the destination page route: + +``` + process: + - + display: /welcome +``` + + + +# Known issues + +When updating from an older version, pre-october 2015, you might have an error `Class 'Grav\Login\Controller' Not Found`. The problem is during the update, since a file name was changed from lowercase to capitalized. Solution: reinstall the Login plugin, or change the file name `user/plugins/login/classes/controller.php` to `user/plugins/login/classes/Controller.php` (notice the capital `C`). diff --git a/sandbox/grav/user/plugins/login/blueprints.yaml b/sandbox/grav/user/plugins/login/blueprints.yaml new file mode 100644 index 0000000000..2237c5bae9 --- /dev/null +++ b/sandbox/grav/user/plugins/login/blueprints.yaml @@ -0,0 +1,368 @@ +name: Login +version: 2.4.1 +description: Enables user authentication and login screen. +icon: sign-in +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +keywords: admin, plugin, login +homepage: https://github.com/getgrav/grav-plugin-login +keywords: login, authentication, admin, security +bugs: https://github.com/getgrav/grav-plugin-login/issues +license: MIT + +dependencies: + - { name: grav, version: '>=1.3.3' } + - { name: form, version: '>=2.4.0' } + - { name: email, version: '~2.0' } + +form: + validation: loose + fields: + + tabs: + type: tabs + active: 1 + class: subtle + + fields: + login: + type: tab + title: PLUGIN_LOGIN.BTN_LOGIN + + fields: + + enabled: + type: hidden + label: PLUGIN_LOGIN.PLUGIN_STATUS + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + built_in_css: + type: toggle + label: PLUGIN_LOGIN.BUILTIN_CSS + highlight: 1 + default: 1 + help: PLUGIN_LOGIN.BUILTIN_CSS_HELP + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + route: + type: text + size: medium + label: PLUGIN_LOGIN.ROUTE + help: PLUGIN_LOGIN.ROUTE_HELP + placeholder: "/my-custom-login" + + redirect_after_login: + type: text + label: PLUGIN_LOGIN.REDIRECT_AFTER_LOGIN + help: PLUGIN_LOGIN.REDIRECT_AFTER_LOGIN_HELP + placeholder: "/my-page" + + route_forgot: + type: text + size: medium + label: PLUGIN_LOGIN.ROUTE_FORGOT + placeholder: "/forgot_password" + + route_reset: + type: text + size: medium + label: PLUGIN_LOGIN.ROUTE_RESET + placeholder: "/reset_password" + + route_profile: + type: text + size: medium + label: PLUGIN_LOGIN.ROUTE_PROFILE + placeholder: "/user_profile" + + parent_acl: + type: toggle + label: PLUGIN_LOGIN.USE_PARENT_ACL_LABEL + highlight: 1 + default: 0 + help: PLUGIN_LOGIN.USE_PARENT_ACL_HELP + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + protect_protected_page_media: + type: toggle + label: PLUGIN_LOGIN.PROTECT_PROTECTED_PAGE_MEDIA_LABEL + highlight: 1 + default: 0 + help: PLUGIN_LOGIN.PROTECT_PROTECTED_PAGE_MEDIA_HELP + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + routes: + type: section + title: PLUGIN_LOGIN.ROUTES + + fields: + + route_activate: + type: text + size: medium + label: PLUGIN_LOGIN.ROUTE_ACTIVATE + placeholder: '/activate_user' + + route_forgot: + type: text + size: medium + label: PLUGIN_LOGIN.ROUTE_FORGOT + placeholder: '/forgot_password' + + route_reset: + type: text + size: medium + label: PLUGIN_LOGIN.ROUTE_RESET + placeholder: '/reset_password' + + route_profile: + type: text + size: medium + label: PLUGIN_LOGIN.ROUTE_PROFILE + placeholder: '/user_profile' + + route_register: + type: text + size: medium + label: PLUGIN_LOGIN.ROUTE_REGISTER + help: PLUGIN_LOGIN.ROUTE_REGISTER_HELP + placeholder: "/register" + + user_registration.redirect_after_registration: + type: text + label: PLUGIN_LOGIN.REDIRECT_AFTER_REGISTRATION + help: PLUGIN_LOGIN.REDIRECT_AFTER_REGISTRATION_HELP + placeholder: "/page-to-show-after-registration" + + user_registration.redirect_after_activation: + type: text + label: PLUGIN_LOGIN.REDIRECT_AFTER_ACTIVATION + help: PLUGIN_LOGIN.REDIRECT_AFTER_ACTIVATION_HELP + placeholder: "/page-to-show-after-activation" + + rememberme: + type: section + title: PLUGIN_LOGIN.REMEMBER_ME + + fields: + rememberme.enabled: + type: toggle + label: PLUGIN_ADMIN.ENABLED + help: PLUGIN_ADMIN.SESSION_ENABLED_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + rememberme.timeout: + type: text + size: small + default: 604800 + label: PLUGIN_ADMIN.TIMEOUT + help: PLUGIN_LOGIN.TIMEOUT_HELP + validate: + type: number + min: 1 + + rememberme.name: + type: text + size: small + label: PLUGIN_ADMIN.NAME + help: PLUGIN_ADMIN.SESSION_NAME_HELP + + registration: + type: tab + title: PLUGIN_LOGIN.USER_REGISTRATION + + fields: + user_registration.enabled: + type: toggle + label: PLUGIN_ADMIN.ENABLED + help: PLUGIN_LOGIN.USER_REGISTRATION_ENABLED_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + registration_fields: + type: section + title: PLUGIN_LOGIN.REGISTRATION_FIELDS + + fields: + user_registration.fields: + type: array + value_only: true + label: PLUGIN_LOGIN.REGISTRATION_FIELDS + help: PLUGIN_LOGIN.REGISTRATION_FIELDS_HELP + placeholder_key: PLUGIN_LOGIN.REGISTRATION_FIELD_KEY + placeholder_value: PLUGIN_LOGIN.REGISTRATION_FIELD_VALUE + + user_registration.default_values: + type: array + label: PLUGIN_LOGIN.DEFAULT_VALUES + help: PLUGIN_LOGIN.DEFAULT_VALUES_HELP + placeholder_key: PLUGIN_LOGIN.ADDITIONAL_PARAM_KEY + placeholder_value: PLUGIN_LOGIN.ADDITIONAL_PARAM_VALUE + + access_levels: + title: PLUGIN_ADMIN.ACCESS_LEVELS + type: section + security: admin.super + + fields: + user_registration.groups: + type: selectize + size: large + label: PLUGIN_ADMIN.GROUPS + '@data-options': '\Grav\User\Groups::groups' + classes: fancy + help: PLUGIN_LOGIN.GROUPS_HELP + validate: + type: commalist + + user_registration.access.site: + type: array + label: PLUGIN_ADMIN.SITE_ACCESS + help: PLUGIN_LOGIN.SITE_ACCESS_HELP + multiple: false + validate: + type: array + + options: + type: section + title: PLUGIN_LOGIN.OPTIONS + + fields: + + user_registration.options.validate_password1_and_password2: + type: toggle + label: PLUGIN_LOGIN.VALIDATE_PASSWORD1_AND_PASSWORD2 + help: PLUGIN_LOGIN.VALIDATE_PASSWORD1_AND_PASSWORD2_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + user_registration.options.set_user_disabled: + type: toggle + label: PLUGIN_LOGIN.SET_USER_DISABLED + help: PLUGIN_LOGIN.SET_USER_DISABLED_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + user_registration.options.login_after_registration: + type: toggle + label: PLUGIN_LOGIN.LOGIN_AFTER_REGISTRATION + help: PLUGIN_LOGIN.LOGIN_AFTER_REGISTRATION_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + user_registration.options.send_activation_email: + type: toggle + label: PLUGIN_LOGIN.SEND_ACTIVATION_EMAIL + help: PLUGIN_LOGIN.SEND_ACTIVATION_EMAIL_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + user_registration.options.send_notification_email: + type: toggle + label: PLUGIN_LOGIN.SEND_NOTIFICATION_EMAIL + help: PLUGIN_LOGIN.SEND_NOTIFICATION_EMAIL_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + user_registration.options.send_welcome_email: + type: toggle + label: PLUGIN_LOGIN.SEND_WELCOME_EMAIL + help: PLUGIN_LOGIN.SEND_WELCOME_EMAIL_HELP + highlight: 1 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + + Security: + type: tab + title: PLUGIN_LOGIN.SECURITY_TAB + + fields: + max_pw_resets_count: + type: number + size: x-small + label: PLUGIN_LOGIN.MAX_RESETS_COUNT + help: PLUGIN_LOGIN.MAX_RESETS_COUNT_HELP + append: PLUGIN_LOGIN.RESETS + validate: + type: number + min: 0 + + max_pw_resets_interval: + type: number + size: x-small + label: PLUGIN_LOGIN.MAX_RESETS_INTERVAL + help: PLUGIN_LOGIN.MAX_RESETS_INTERVAL_HELP + append: PLUGIN_LOGIN.SECONDS + validate: + type: number + min: 1 + + max_login_count: + type: number + size: x-small + label: PLUGIN_LOGIN.MAX_LOGINS_COUNT + help: PLUGIN_LOGIN.MAX_LOGINS_COUNT_HELP + append: PLUGIN_LOGIN.ATTEMPTS + validate: + type: number + min: 0 + + max_login_interval: + type: number + size: x-small + label: PLUGIN_LOGIN.MAX_LOGINS_INTERVAL + help: PLUGIN_LOGIN.MAX_LOGINS_INTERVAL_HELP + append: PLUGIN_LOGIN.SECONDS + validate: + type: number + min: 1 diff --git a/sandbox/grav/user/plugins/login/classes/Controller.php b/sandbox/grav/user/plugins/login/classes/Controller.php new file mode 100644 index 0000000000..03c52e65d4 --- /dev/null +++ b/sandbox/grav/user/plugins/login/classes/Controller.php @@ -0,0 +1,482 @@ +grav = $grav; + $this->action = $action; + $this->login = isset($this->grav['login']) ? $this->grav['login'] : ''; + $this->post = $post ? $this->getPost($post) : []; + + $this->rememberMe(); + } + + /** + * Performs an action. + */ + public function execute() + { + // Set redirect if available. + if (isset($this->post['_redirect'])) { + $redirect = $this->post['_redirect']; + unset($this->post['_redirect']); + } + + $success = false; + $method = $this->prefix . ucfirst($this->action); + + if (!method_exists($this, $method)) { + throw new \RuntimeException('Page Not Found', 404); + } + + try { + $success = call_user_func([$this, $method]); + } catch (\RuntimeException $e) { + $this->login->setMessage($e->getMessage(), 'error'); + } + + if (!$this->redirect && isset($redirect)) { + $this->setRedirect($redirect); + } + + return $success; + } + + /** + * Handle login. + * + * @return bool True if the action was performed. + */ + public function taskLogin() + { + /** @var Language $t */ + $t = $this->grav['language']; + + /** @var User $user */ + $user = $this->grav['user']; + + $count = $this->grav['config']->get('plugins.login.max_login_count', 5); + $interval =$this->grav['config']->get('plugins.login.max_login_interval', 10); + + if ($this->login->isUserRateLimited($user, 'login_attempts', $count, $interval)) { + $this->login->setMessage($t->translate(['PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $interval]), 'error'); + $this->setRedirect($this->grav['config']->get('plugins.login.route', '/')); + + return true; + } + + + if ($this->authenticate($this->post)) { + $this->login->setMessage($t->translate('PLUGIN_LOGIN.LOGIN_SUCCESSFUL')); + + $this->login->resetRateLimit($user, 'login_attempts'); + + $redirect = $this->grav['config']->get('plugins.login.redirect_after_login'); + if (!$redirect) { + $redirect = $this->grav['session']->redirect_after_login ?: $this->grav['uri']->referrer('/'); + } + $this->setRedirect($redirect); + } else { + if ($user->username) { + $this->login->setMessage($t->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error'); + $this->setRedirect($this->grav['config']->get('plugins.login.route_unauthorized', '/')); + } else { + $this->login->setMessage($t->translate('PLUGIN_LOGIN.LOGIN_FAILED'), 'error'); + } + } + + return true; + } + + /** + * Handle logout. + * + * @return bool True if the action was performed. + */ + public function taskLogout() + { + /** @var User $user */ + $user = $this->grav['user']; + + if (!$this->rememberMe->login()) { + $credentials = $user->get('username'); + $this->rememberMe->getStorage()->cleanAllTriplets($credentials); + } + $this->rememberMe->clearCookie(); + + $this->grav['session']->invalidate()->start(); + $this->setRedirect('/'); + + return true; + } + + /** + * Handle the email password recovery procedure. + * + * @return bool True if the action was performed. + */ + protected function taskForgot() + { + $param_sep = $this->grav['config']->get('system.param_sep', ':'); + $data = $this->post; + + $email = isset($data['email']) ? $data['email'] : ''; + $user = !empty($email) ? User::find($email, ['email']) : null; + + /** @var Language $l */ + $language = $this->grav['language']; + $messages = $this->grav['messages']; + + if (!isset($this->grav['Email'])) { + $messages->add($language->translate('PLUGIN_LOGIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error'); + $this->setRedirect($this->grav['config']->get('plugins.login.route_forgot', '/')); + + return true; + } + + if (!$user || !$user->exists()) { + $messages->add($language->translate('PLUGIN_LOGIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'), 'info'); + $this->setRedirect($this->grav['config']->get('plugins.login.route_forgot', '/')); + + return true; + } + + if (empty($user->email)) { + $messages->add($language->translate(['PLUGIN_LOGIN.FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL', $email]), + 'error'); + $this->setRedirect($this->grav['config']->get('plugins.login.route_forgot', '/')); + + return true; + } + + $from = $this->grav['config']->get('plugins.email.from'); + + if (empty($from)) { + $messages->add($language->translate('PLUGIN_LOGIN.FORGOT_EMAIL_NOT_CONFIGURED'), 'error'); + $this->setRedirect($this->grav['config']->get('plugins.login.route_forgot', '/')); + + return true; + } + + $count = $this->grav['config']->get('plugins.login.max_pw_resets_count', 0); + $interval =$this->grav['config']->get('plugins.login.max_pw_resets_interval', 2); + + if ($this->login->isUserRateLimited($user, 'pw_resets', $count, $interval)) { + $messages->add($language->translate(['PLUGIN_LOGIN.FORGOT_CANNOT_RESET_IT_IS_BLOCKED', $email, $interval]), 'error'); + $this->setRedirect($this->grav['config']->get('plugins.login.route', '/')); + + return true; + } + + $token = md5(uniqid(mt_rand(), true)); + $expire = time() + 604800; // next week + + $user->reset = $token . '::' . $expire; + $user->save(); + + $author = $this->grav['config']->get('site.author.name', ''); + $fullname = $user->fullname ?: $user->username; + + $reset_link = $this->grav['base_url_absolute'] . $this->grav['config']->get('plugins.login.route_reset') . '/task:login.reset/token' . $param_sep . $token . '/user' . $param_sep . $user->username . '/nonce' . $param_sep . Utils::getNonce('reset-form'); + + $sitename = $this->grav['config']->get('site.title', 'Website'); + + $to = $user->email; + + $subject = $language->translate(['PLUGIN_LOGIN.FORGOT_EMAIL_SUBJECT', $sitename]); + $content = $language->translate(['PLUGIN_LOGIN.FORGOT_EMAIL_BODY', $fullname, $reset_link, $author, $sitename]); + + $sent = EmailUtils::sendEmail($subject, $content, $to); + + if ($sent < 1) { + $messages->add($language->translate('PLUGIN_LOGIN.FORGOT_FAILED_TO_EMAIL'), 'error'); + } else { + $messages->add($language->translate('PLUGIN_LOGIN.FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL'), 'info'); + } + + $this->setRedirect($this->grav['config']->get('plugins.login.route', '/')); + + return true; + } + + /** + * Handle the reset password action. + * + * @return bool True if the action was performed. + */ + public function taskReset() + { + $data = $this->post; + $language = $this->grav['language']; + $messages = $this->grav['messages']; + + if (isset($data['password'])) { + $username = isset($data['username']) ? $data['username'] : null; + $user = !empty($username) ? User::find($username) : null; + $password = isset($data['password']) ? $data['password'] : null; + $token = isset($data['token']) ? $data['token'] : null; + + if ($user && !empty($user->reset) && $user->exists()) { + list($good_token, $expire) = explode('::', $user->reset); + + if ($good_token === $token) { + if (time() > $expire) { + $messages->add($language->translate('PLUGIN_LOGIN.RESET_LINK_EXPIRED'), 'error'); + $this->grav->redirect($this->grav['config']->get('plugins.login.route_forgot', '/')); + + return true; + } + + unset($user->hashed_password); + unset($user->reset); + $user->password = $password; + + $user->validate(); + $user->filter(); + $user->save(); + + $messages->add($language->translate('PLUGIN_LOGIN.RESET_PASSWORD_RESET'), 'info'); + $this->setRedirect($this->grav['config']->get('plugins.login.route', '/')); + + return true; + } + } + + $messages->add($language->translate('PLUGIN_LOGIN.RESET_INVALID_LINK'), 'error'); + $this->grav->redirect($this->grav['config']->get('plugins.login.route_forgot')); + + return true; + + } + + $user = $this->grav['uri']->param('user'); + $token = $this->grav['uri']->param('token'); + + if (!$user || !$token) { + $messages->add($language->translate('PLUGIN_LOGIN.RESET_INVALID_LINK'), 'error'); + $this->grav->redirect($this->grav['config']->get('plugins.login.route_forgot')); + + return true; + } + + return true; + } + + /** + * Authenticate user. + * + * @param array $form Form fields. + * + * @return bool + */ + protected function authenticate($form) + { + /** @var User $user */ + $user = $this->grav['user']; + + if (!$user->authenticated) { + $username = isset($form['username']) ? $form['username'] : $this->rememberMe->login(); + + // Normal login process + $user = User::find($username); + if ($user->exists() && !empty($form['username']) && !empty($form['password'])) { + // Authenticate user + $user->authenticated = $user->authenticate($form['password']); + + if ($user->authenticated) { + + // Authorize against user ACL + $user_authorized = $user->authorize('site.login'); + + if ($user_authorized) { + $this->grav['session']->user = $user; + + unset($this->grav['user']); + $this->grav['user'] = $user; + + // If the user wants to be remembered, create Rememberme cookie + if (!empty($form['rememberme'])) { + $this->rememberMe->createCookie($form['username']); + } else { + $this->rememberMe->clearCookie(); + $this->rememberMe->getStorage()->cleanAllTriplets($user->get('username')); + } + } + } + } + } + + // Authorize against user ACL + $user_authorized = $user->authorize('site.login'); + $user->authenticated = ($user->authenticated && $user_authorized); + + return $user->authenticated; + } + + /** + * Redirects an action + */ + public function redirect() + { + if ($this->redirect) { + $this->grav->redirect($this->redirect, $this->redirectCode); + } + } + + /** + * Set redirect. + * + * @param $path + * @param int $code + */ + public function setRedirect($path, $code = 303) + { + $this->redirect = $path; + $this->redirectCode = $code; + } + + /** + * Gets and sets the RememberMe class + * + * @param mixed $var A rememberMe instance to set + * + * @return RememberMe\RememberMe Returns the current rememberMe instance + */ + public function rememberMe($var = null) + { + if ($var !== null) { + $this->rememberMe = $var; + } + + if (!$this->rememberMe) { + /** @var Config $config */ + $config = $this->grav['config']; + + // Setup storage for RememberMe cookies + $storage = new RememberMe\TokenStorage(); + $this->rememberMe = new RememberMe\RememberMe($storage); + $this->rememberMe->setCookieName($config->get('plugins.login.rememberme.name')); + $this->rememberMe->setExpireTime($config->get('plugins.login.rememberme.timeout')); + + // Hardening cookies with user-agent and random salt or + // fallback to use system based cache key + $server_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'unknown'; + $data = $server_agent . $config->get('security.salt', $this->grav['cache']->getKey()); + $this->rememberMe->setSalt(hash('sha512', $data)); + + // Set cookie with correct base path of Grav install + $cookie = new Cookie(); + $cookie->setPath($this->grav['base_url_relative'] ?: '/'); + $this->rememberMe->setCookie($cookie); + } + + return $this->rememberMe; + } + + /** + * Prepare and return POST data. + * + * @param array $post + * + * @return array + */ + protected function &getPost(array $post) + { + unset($post[$this->prefix]); + + // Decode JSON encoded fields and merge them to data. + if (isset($post['_json'])) { + $post = array_merge_recursive($post, $this->jsonDecode($post['_json'])); + unset($post['_json']); + } + + return $post; + } + + /** + * Recursively JSON decode data. + * + * @param array $data + * + * @return array + */ + protected function jsonDecode(array $data) + { + foreach ($data as &$value) { + if (is_array($value)) { + $value = $this->jsonDecode($value); + } else { + $value = json_decode($value, true); + } + } + + return $data; + } + +} diff --git a/sandbox/grav/user/plugins/login/classes/Login.php b/sandbox/grav/user/plugins/login/classes/Login.php new file mode 100644 index 0000000000..ac2cf818a3 --- /dev/null +++ b/sandbox/grav/user/plugins/login/classes/Login.php @@ -0,0 +1,280 @@ +grav = $grav; + $this->config = $this->grav['config']; + $this->language = $this->grav['language']; + $this->session = $this->grav['session']; + $this->user = $this->grav['user']; + $this->uri = $this->grav['uri']; + } + + /** + * Add message into the session queue. + * + * @param string $msg + * @param string $type + */ + public function setMessage($msg, $type = 'info') + { + /** @var Message $messages */ + $messages = $this->grav['messages']; + $messages->add($msg, $type); + } + + /** + * Fetch and delete messages from the session queue. + * + * @param string $type + * + * @return array + */ + public function messages($type = null) + { + /** @var Message $messages */ + $messages = $this->grav['messages']; + + return $messages->fetch($type); + } + + /** + * Create a new user file + * + * @param array $data + * + * @return User + */ + public function register($data) + { + //Add new user ACL settings + $groups = $this->config->get('plugins.login.user_registration.groups', []); + + if (count($groups) > 0) { + $data['groups'] = $groups; + } + + $access = $this->config->get('plugins.login.user_registration.access.site', []); + if (count($access) > 0) { + $data['access']['site'] = $access; + } + + $username = $data['username']; + $file = CompiledYamlFile::instance($this->grav['locator']->findResource('account://' . $username . YAML_EXT, + true, true)); + + // Create user object and save it + $user = new User($data); + $user->file($file); + $user->save(); + + if (isset($data['state']) && $data['state'] === 'enabled' && $this->config->get('plugins.login.user_registration.options.login_after_registration', false)) { + //Login user + $this->session->user = $user; + unset($this->grav['user']); + $this->grav['user'] = $user; + $user->authorized = $user->authorize('site.login'); + } + + return $user; + } + + + /** + * Handle the email to notify the user account creation to the site admin. + * + * @param $user + * + * @return bool True if the action was performed. + */ + public function sendNotificationEmail($user) + { + if (empty($user->email)) { + throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD')); + } + + $site_name = $this->config->get('site.title', 'Website'); + + $subject = $this->language->translate(['PLUGIN_LOGIN.NOTIFICATION_EMAIL_SUBJECT', $site_name]); + $content = $this->language->translate([ + 'PLUGIN_LOGIN.NOTIFICATION_EMAIL_BODY', + $site_name, + $user->username, + $user->email + ]); + $to = $this->config->get('plugins.email.from'); + + if (empty($to)) { + throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_NOT_CONFIGURED')); + } + + $sent = EmailUtils::sendEmail($subject, $content, $to); + + if ($sent < 1) { + throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE')); + } + + return true; + } + + /** + * Handle the email to welcome the new user + * + * @param $user + * + * @return bool True if the action was performed. + */ + public function sendWelcomeEmail($user) + { + if (empty($user->email)) { + throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD')); + } + + $site_name = $this->config->get('site.title', 'Website'); + + $subject = $this->language->translate(['PLUGIN_LOGIN.WELCOME_EMAIL_SUBJECT', $site_name]); + $content = $this->language->translate(['PLUGIN_LOGIN.WELCOME_EMAIL_BODY', $user->username, $site_name]); + $to = $user->email; + + $sent = EmailUtils::sendEmail($subject, $content, $to); + + if ($sent < 1) { + throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE')); + } + + return true; + } + + /** + * Handle the email to activate the user account. + * + * @param User $user + * + * @return bool True if the action was performed. + */ + public function sendActivationEmail($user) + { + if (empty($user->email)) { + throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD')); + } + + $token = md5(uniqid(mt_rand(), true)); + $expire = time() + 604800; // next week + $user->activation_token = $token . '::' . $expire; + $user->save(); + + $param_sep = $this->config->get('system.param_sep', ':'); + $activation_link = $this->grav['base_url_absolute'] . $this->config->get('plugins.login.route_activate') . '/token' . $param_sep . $token . '/username' . $param_sep . $user->username . '/nonce' . $param_sep . Utils::getNonce('user-activation'); + + $site_name = $this->config->get('site.title', 'Website'); + + $subject = $this->language->translate(['PLUGIN_LOGIN.ACTIVATION_EMAIL_SUBJECT', $site_name]); + $content = $this->language->translate([ + 'PLUGIN_LOGIN.ACTIVATION_EMAIL_BODY', + $user->username, + $activation_link, + $site_name + ]); + $to = $user->email; + + $sent = EmailUtils::sendEmail($subject, $content, $to); + + if ($sent < 1) { + throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE')); + } + + return true; + } + + /** + * Check if user may use password reset functionality. + * + * @param User $user + * @param $field + * @param $count + * @param $interval + * @return bool + */ + public function isUserRateLimited(User $user, $field, $count, $interval) + { + if ($count > 0) { + if (!isset($user->{$field})) { + $user->{$field} = array(); + } + //remove older than 1 hour attempts + $actual_resets = array(); + foreach ($user->{$field} as $reset) { + if ($reset > (time() - $interval * 60)) { + $actual_resets[] = $reset; + } + } + + if (count($actual_resets) >= $count) { + return true; + } + $actual_resets[] = time(); // current reset + $user->{$field} = $actual_resets; + + } + return false; + } + + /** + * Reset the rate limit counter + * + * @param User $user + * @param $field + */ + public function resetRateLimit(User $user, $field) + { + $user->{$field} = []; + } + +} diff --git a/sandbox/grav/user/plugins/login/classes/RememberMe/RememberMe.php b/sandbox/grav/user/plugins/login/classes/RememberMe/RememberMe.php new file mode 100644 index 0000000000..4690f55ee1 --- /dev/null +++ b/sandbox/grav/user/plugins/login/classes/RememberMe/RememberMe.php @@ -0,0 +1,41 @@ + + */ +class RememberMe extends Authenticator +{ + /** + * Gets storage interface + * + * @return StorageInterface + */ + public function getStorage() + { + return $this->storage; + } + + /** + * Set storage interface + * + * @param StorageInterface $storage Storage interface + */ + public function setStorage($storage) + { + $this->storage = $storage; + } +} diff --git a/sandbox/grav/user/plugins/login/classes/RememberMe/TokenStorage.php b/sandbox/grav/user/plugins/login/classes/RememberMe/TokenStorage.php new file mode 100644 index 0000000000..4031cd6578 --- /dev/null +++ b/sandbox/grav/user/plugins/login/classes/RememberMe/TokenStorage.php @@ -0,0 +1,197 @@ + + */ +class TokenStorage implements StorageInterface +{ + /** + * @var DoctrineCache + */ + protected $driver; + + /** + * @var string + */ + protected $cache_dir; + + /** + * Constructor + * + * @param string $path Path to storage directory + */ + public function __construct($path = 'cache://rememberme') + { + /** @var Cache $cache */ + $cache = Grav::instance()['cache']; + + $this->cache_dir = Grav::instance()['locator']->findResource($path, true, true); + + // Setup cache + $this->driver = $cache->getCacheDriver(); + if ($this->driver instanceof FilesystemCache) { + $this->driver = new FilesystemCache($this->cache_dir); + } + + // Set the cache namespace to our unique key + $this->driver->setNamespace($cache->getKey()); + } + + /** + * Return Tri-state value constant + * + * @param mixed $credential Unique credential (user id, + * email address, user name) + * @param string $token One-Time Token + * @param string $persistentToken Persistent Token + * + * @return int + */ + public function findTriplet($credential, $token, $persistentToken) + { + + // Hash the tokens, because they can contain a salt and can be + // accessed in the file system + $persistentToken = sha1(trim($persistentToken)); + $token = sha1(trim($token)); + + $id = $this->getId($credential); + if (!$this->driver->contains($id)) { + return self::TRIPLET_NOT_FOUND; + } + + list($expire, $tokens) = $this->driver->fetch($id); + if (isset($tokens[$persistentToken]) && $tokens[$persistentToken] == $token) { + return self::TRIPLET_FOUND; + } + + return self::TRIPLET_INVALID; + } + + /** + * Store the new token for the credential and the persistent token. + * Create a new storage entry, if the combination of credential and + * persistent token does not exist. + * + * @param mixed $credential + * @param string $token + * @param string $persistentToken + * @param int $expire Timestamp when this triplet + * will expire (0 = no expiry) + */ + public function storeTriplet($credential, $token, $persistentToken, $expire = null) + { + // Hash the tokens, because they can contain a salt and can be + // accessed in the file system + $persistentToken = sha1(trim($persistentToken)); + $token = sha1(trim($token)); + + $e = null; + $tokens = []; + $id = $this->getId($credential); + if ($this->driver->contains($id)) { + list($e, $tokens) = $this->driver->fetch($id); + } + + // Get cache lifetime for tokens + if ($expire === null && $e === null) { + /** @var Cache $cache */ + $cache = Grav::instance()['cache']; + $expire = $cache->getLifetime(); + } elseif ($expire === null) { + $expire = $e; + } + + // Update tokens + $tokens[$persistentToken] = $token; + $this->driver->save($id, [$expire, $tokens], $expire); + + return $this; + } + + /** + * Replace current token after successful authentication + * + * @param mixed $credential + * @param string $token + * @param string $persistentToken + * @param int $expire + */ + public function replaceTriplet($credential, $token, $persistentToken, $expire = null) + { + $this->cleanTriplet($credential, $persistentToken); + $this->storeTriplet($credential, $token, $persistentToken, $expire); + } + + /** + * Remove one triplet of the user from the store + * + * @param mixed $credential + * @param string $persistentToken + */ + public function cleanTriplet($credential, $persistentToken) + { + // Hash the tokens, because they can contain a salt and can be + // accessed in the file system + $persistentToken = sha1(trim($persistentToken)); + + // Delete token from storage + $id = $this->getId($credential); + if ($this->driver->contains($id)) { + list($expire, $tokens) = $this->driver->fetch($id); + unset($tokens[$persistentToken]); + $this->driver->save($id, [$expire, $tokens], $expire); + } + } + + /** + * Remove all triplets of a user, effectively logging him out on all + * machines + * + * @param mixed $credential + */ + public function cleanAllTriplets($credential) + { + $id = $this->getId($credential); + $this->driver->delete($id); + } + + /** + * Helper method to clear RememberMe cache + */ + public function clearCache() + { + $this->driver->flushAll(); + } + + /** + * Get the cache id + * + * @param string $key A key to compute the cache id for + * @return string The cache id + */ + protected function getId($key) + { + /** @var Cache $cache */ + $cache = Grav::instance()['cache']; + + return 'login' . md5(trim($key) . $cache->getKey()); + } +} diff --git a/sandbox/grav/user/plugins/login/cli/ChangePasswordCommand.php b/sandbox/grav/user/plugins/login/cli/ChangePasswordCommand.php new file mode 100644 index 0000000000..24be533af4 --- /dev/null +++ b/sandbox/grav/user/plugins/login/cli/ChangePasswordCommand.php @@ -0,0 +1,202 @@ +setName('change-password') + ->setAliases(['edit-password', 'newpass', 'changepass', 'passwd']) + ->addOption( + 'user', + 'u', + InputOption::VALUE_REQUIRED, + 'The username' + ) + ->addOption( + 'password', + 'p', + InputOption::VALUE_REQUIRED, + "The password. Note that this option is not recommended because the password will be visible by users listing the processes. You should also make sure the password respects Grav's password policy." + ) + ->setDescription('Changes a User Password') + ->setHelp('The change-password changes the password of the specified user. (User must exist)') + ; + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->options = [ + 'user' => $this->input->getOption('user'), + 'password1' => $this->input->getOption('password') + ]; + + $this->validateOptions(); + + $helper = $this->getHelper('question'); + $data = []; + + $this->output->writeln('Changing User Password'); + $this->output->writeln(''); + + if (!$this->options['user']) { + // Get username and validate + $question = new Question('Enter a username: '); + $question->setValidator(function ($value) { + return $this->validate('user', $value); + }); + + $username = $helper->ask($this->input, $this->output, $question); + } else { + $username = $this->options['user']; + } + + + if (!$this->options['password1']) { + // Get password and validate + $password = $this->askForPassword($helper, 'Enter a new password: ', function ($password1) use ($helper) { + $this->validate('password1', $password1); + + // Since input is hidden when prompting for passwords, the user is asked to repeat the password + return $this->askForPassword($helper, 'Repeat the password: ', function ($password2) use ($password1) { + return $this->validate('password2', $password2, $password1); + }); + }); + + $data['password'] = $password; + } else { + $data['password'] = $this->options['password1']; + } + + // Lowercase the username for the filename + $username = strtolower($username); + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + // Grab the account file and read in the information before setting the file (prevent setting erase) + $oldUserFile = CompiledYamlFile::instance($locator->findResource('account://' . $username . YAML_EXT, true, true)); + $oldData = (array)$oldUserFile->content(); + + //Set the password feild to new password + $oldData['password'] = $data['password']; + + // Create user object and save it using oldData (with updated password) + $user = new User($oldData); + $file = CompiledYamlFile::instance($locator->findResource('account://' . $username . YAML_EXT, true, true)); + $user->file($file); + $user->save(); + + $this->output->writeln(''); + $this->output->writeln('Success! User ' . $username . '\'s password changed.'); + } + + /** + * + */ + protected function validateOptions() + { + foreach (array_filter($this->options) as $type => $value) { + $this->validate($type, $value); + } + } + + /** + * @param $type + * @param $value + * @param string $extra + * + * @return mixed + */ + protected function validate($type, $value, $extra = '') + { + /** @var Config $config */ + $config = Grav::instance()['config']; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + $username_regex = '/' . $config->get('system.username_regex') . '/'; + $pwd_regex = '/' . $config->get('system.pwd_regex') . '/'; + + switch ($type) { + case 'user': + if (!preg_match($username_regex, $value)) { + throw new \RuntimeException('Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed'); + } + if (!$locator->findResource('account://' . $value . YAML_EXT)) { + throw new \RuntimeException('Username "' . $value . '" does not exist, please pick another username'); + } + + break; + + case 'password1': + if (!preg_match($pwd_regex, $value)) { + throw new \RuntimeException('Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters'); + } + + break; + + case 'password2': + if (strcmp($value, $extra)) { + throw new \RuntimeException('Passwords did not match.'); + } + + break; + } + + return $value; + } + + /** + * Get password and validate. + * + * @param Helper $helper + * @param string $question + * @param callable $validator + * + * @return string + */ + protected function askForPassword(Helper $helper, $question, callable $validator) + { + $question = new Question($question); + $question->setValidator($validator); + $question->setHidden(true); + $question->setHiddenFallback(true); + + return $helper->ask($this->input, $this->output, $question); + } +} diff --git a/sandbox/grav/user/plugins/login/cli/ChangeUserStateCommand.php b/sandbox/grav/user/plugins/login/cli/ChangeUserStateCommand.php new file mode 100644 index 0000000000..ed36b36994 --- /dev/null +++ b/sandbox/grav/user/plugins/login/cli/ChangeUserStateCommand.php @@ -0,0 +1,175 @@ +setName('toggle-user') + ->setAliases(['disableuser', 'enableuser', 'toggleuser', 'change-user-state']) + ->addOption( + 'user', + 'u', + InputOption::VALUE_REQUIRED, + 'The username' + ) + ->addOption( + 'state', + 's', + InputOption::VALUE_REQUIRED, + 'The state of the account. Can be either `enabled` or `disabled`. [default: "enabled"]' + ) + ->setDescription('Changes whether user can login or not') + ->setHelp('The toggle-user sets a user\'s login status to enabled or disabled.') + ; + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->options = [ + 'user' => $this->input->getOption('user'), + 'state' => $this->input->getOption('state') + ]; + + $this->validateOptions(); + + $helper = $this->getHelper('question'); + $data = []; + + $this->output->writeln('Setting User State'); + $this->output->writeln(''); + + if (!$this->options['user']) { + // Get username and validate + $question = new Question('Enter a username: '); + $question->setValidator(function ($value) { + return $this->validate('user', $value); + }); + + $username = $helper->ask($this->input, $this->output, $question); + } else { + $username = $this->options['user']; + } + + + if (!$this->options['state'] && !count(array_filter($this->options))) { + // Choose State + $question = new ChoiceQuestion( + 'Please choose the state for the account:', + array('enabled' => 'Enabled', 'disabled' => 'Disabled'), + 'enabled' + ); + + $question->setErrorMessage('State %s is invalid.'); + $data['state'] = $helper->ask($this->input, $this->output, $question); + } else { + $data['state'] = $this->options['state'] ?: 'enabled'; + } + + // Lowercase the username for the filename + $username = strtolower($username); + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + // Grab the account file and read in the information before setting the file (prevent setting erase) + $oldUserFile = CompiledYamlFile::instance($locator->findResource('account://' . $username . YAML_EXT, true, true)); + $oldData = (array)$oldUserFile->content(); + + //Set the state feild to new state + $oldData['state'] = $data['state']; + + // Create user object and save it using oldData (with updated state) + $user = new User($oldData); + $file = CompiledYamlFile::instance($locator->findResource('account://' . $username . YAML_EXT, true, true)); + $user->file($file); + $user->save(); + + $this->output->writeln(''); + $this->output->writeln('Success! User ' . $username . ' state set to .' . $data['state']); + } + + /** + * + */ + protected function validateOptions() + { + foreach (array_filter($this->options) as $type => $value) { + $this->validate($type, $value); + } + } + + /** + * @param $type + * @param $value + * @param string $extra + * + * @return mixed + */ + protected function validate($type, $value, $extra = '') + { + /** @var Config $config */ + $config = Grav::instance()['config']; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + $username_regex = '/' . $config->get('system.username_regex') . '/'; + + switch ($type) { + case 'user': + if (!preg_match($username_regex, $value)) { + throw new \RuntimeException('Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed'); + } + if (!file_exists($locator->findResource('account://' . $value . YAML_EXT))) { + throw new \RuntimeException('Username "' . $value . '" does not exist, please pick another username'); + } + + break; + + case 'state': + if ($value !== 'enabled' && $value !== 'disabled') { + throw new \RuntimeException('State is not valid'); + } + + break; + } + + return $value; + } +} diff --git a/sandbox/grav/user/plugins/login/cli/NewUserCommand.php b/sandbox/grav/user/plugins/login/cli/NewUserCommand.php new file mode 100644 index 0000000000..51e7495b64 --- /dev/null +++ b/sandbox/grav/user/plugins/login/cli/NewUserCommand.php @@ -0,0 +1,333 @@ +setName('new-user') + ->setAliases(['add-user', 'newuser']) + ->addOption( + 'user', + 'u', + InputOption::VALUE_REQUIRED, + 'The username' + ) + ->addOption( + 'password', + 'p', + InputOption::VALUE_REQUIRED, + "The password. Note that this option is not recommended because the password will be visible by users listing the processes. You should also make sure the password respects Grav's password policy." + ) + ->addOption( + 'email', + 'e', + InputOption::VALUE_REQUIRED, + 'The user email' + ) + ->addOption( + 'permissions', + 'P', + InputOption::VALUE_REQUIRED, + 'The user permissions. It can be either `a` for Admin access only, `s` for Site access only and `b` for both Admin and Site access.' + ) + ->addOption( + 'fullname', + 'N', + InputOption::VALUE_REQUIRED, + 'The user full name.' + ) + ->addOption( + 'title', + 't', + InputOption::VALUE_REQUIRED, + 'The title of the user. Usually used as a subtext. Example: Admin, Collaborator, Developer' + ) + ->addOption( + 'state', + 's', + InputOption::VALUE_REQUIRED, + 'The state of the account. Can be either `enabled` or `disabled`. [default: "enabled"]' + ) + ->setDescription('Creates a new user') + ->setHelp('The new-user creates a new user file in user/accounts/ folder') + ; + } + + /** + * @return int|null|void + */ + protected function serve() + { + $this->options = [ + 'user' => $this->input->getOption('user'), + 'password1' => $this->input->getOption('password'), + 'email' => $this->input->getOption('email'), + 'permissions' => $this->input->getOption('permissions'), + 'fullname' => $this->input->getOption('fullname'), + 'title' => $this->input->getOption('title'), + 'state' => $this->input->getOption('state') + ]; + + $this->validateOptions(); + + $helper = $this->getHelper('question'); + $data = []; + + $this->output->writeln('Creating new user'); + $this->output->writeln(''); + + if (!$this->options['user']) { + // Get username and validate + $question = new Question('Enter a username: ', 'admin'); + $question->setValidator(function ($value) { + return $this->validate('user', $value); + }); + + $username = $helper->ask($this->input, $this->output, $question); + } else { + $username = $this->options['user']; + } + + + if (!$this->options['password1']) { + // Get password and validate + $password = $this->askForPassword($helper, 'Enter a password: ', function ($password1) use ($helper) { + $this->validate('password1', $password1); + + // Since input is hidden when prompting for passwords, the user is asked to repeat the password + return $this->askForPassword($helper, 'Repeat the password: ', function ($password2) use ($password1) { + return $this->validate('password2', $password2, $password1); + }); + }); + + $data['password'] = $password; + } else { + $data['password'] = $this->options['password1']; + } + + if (!$this->options['email']) { + // Get email and validate + $question = new Question('Enter an email: '); + $question->setValidator(function ($value) { + return $this->validate('email', $value); + }); + + $data['email'] = $helper->ask($this->input, $this->output, $question); + } else { + $data['email'] = $this->options['email']; + } + + if (!$this->options['permissions']) { + // Choose permissions + $question = new ChoiceQuestion( + 'Please choose a set of permissions:', + array('a' => 'Admin Access', 's' => 'Site Access', 'b' => 'Admin and Site Access'), + 'a' + ); + + $question->setErrorMessage('Permissions %s is invalid.'); + $permissions_choice = $helper->ask($this->input, $this->output, $question); + } else { + $permissions_choice = $this->options['permissions']; + } + + switch ($permissions_choice) { + case 'a': + $data['access']['admin'] = ['login' => true, 'super' => true]; + break; + case 's': + $data['access']['site'] = ['login' => true]; + break; + case 'b': + $data['access']['admin'] = ['login' => true, 'super' => true]; + $data['access']['site'] = ['login' => true]; + } + + if (!$this->options['fullname']) { + // Get fullname + $question = new Question('Enter a fullname: '); + $question->setValidator(function ($value) { + return $this->validate('fullname', $value); + }); + + $data['fullname'] = $helper->ask($this->input, $this->output, $question); + } else { + $data['fullname'] = $this->options['fullname']; + } + + + if (!$this->options['title'] && !count(array_filter($this->options))) { + // Get title + $question = new Question('Enter a title: '); + $data['title'] = $helper->ask($this->input, $this->output, $question); + } else { + $data['title'] = $this->options['title']; + } + + if (!$this->options['state'] && !count(array_filter($this->options))) { + // Choose State + $question = new ChoiceQuestion( + 'Please choose the state for the account:', + array('enabled' => 'Enabled', 'disabled' => 'Disabled'), + 'enabled' + ); + + $question->setErrorMessage('State %s is invalid.'); + $data['state'] = $helper->ask($this->input, $this->output, $question); + } else { + $data['state'] = $this->options['state'] ?: 'enabled'; + } + + // Lowercase the username for the filename + $username = strtolower($username); + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + // Create user object and save it + $user = new User($data); + $file = CompiledYamlFile::instance($locator->findResource('account://' . $username . YAML_EXT, true, true)); + $user->file($file); + $user->save(); + + $this->output->writeln(''); + $this->output->writeln('Success! User ' . $username . ' created.'); + } + + /** + * + */ + protected function validateOptions() + { + foreach (array_filter($this->options) as $type => $value) { + $this->validate($type, $value); + } + } + + /** + * @param $type + * @param $value + * @param string $extra + * + * @return mixed + */ + protected function validate($type, $value, $extra = '') + { + /** @var Config $config */ + $config = Grav::instance()['config']; + + /** @var UniformResourceLocator $locator */ + $locator = Grav::instance()['locator']; + + + $username_regex = '/' . $config->get('system.username_regex') . '/'; + $pwd_regex = '/' . $config->get('system.pwd_regex') . '/'; + + switch ($type) { + case 'user': + if (!preg_match($username_regex, $value)) { + throw new \RuntimeException('Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed'); + } + if (file_exists($locator->findResource('account://' . $value . YAML_EXT))) { + throw new \RuntimeException('Username "' . $value . '" already exists, please pick another username'); + } + + break; + + case 'password1': + if (!preg_match($pwd_regex, $value)) { + throw new \RuntimeException('Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters'); + } + + break; + + case 'password2': + if (strcmp($value, $extra)) { + throw new \RuntimeException('Passwords did not match.'); + } + + break; + + case 'email': + if (!preg_match('/^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/', $value)) { + throw new \RuntimeException('Not a valid email address'); + } + + break; + + case 'permissions': + if (!in_array($value, ['a', 's', 'b'])) { + throw new \RuntimeException('Permissions ' . $value . ' are invalid.'); + } + + break; + + case 'fullname': + if ($value === null || trim($value) === '') { + throw new \RuntimeException('Fullname cannot be empty'); + } + + break; + + case 'state': + if ($value !== 'enabled' && $value !== 'disabled') { + throw new \RuntimeException('State is not valid'); + } + + break; + } + + return $value; + } + + /** + * Get password and validate. + * + * @param Helper $helper + * @param string $question + * @param callable $validator + * + * @return string + */ + protected function askForPassword(Helper $helper, $question, callable $validator) + { + $question = new Question($question); + $question->setValidator($validator); + $question->setHidden(true); + $question->setHiddenFallback(true); + return $helper->ask($this->input, $this->output, $question); + } +} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/login/composer.json b/sandbox/grav/user/plugins/login/composer.json new file mode 100644 index 0000000000..78751cc190 --- /dev/null +++ b/sandbox/grav/user/plugins/login/composer.json @@ -0,0 +1,37 @@ +{ + "name": "grav-plugin-login", + "description": "Enables user authentication and login screen.", + "keywords": ["login", "authentication", "admin", "security"], + "homepage": "https://github.com/getgrav/grav-plugin-login", + "license": "MIT", + "authors": [ + { + "name": "Team Grav", + "email": "devs@getgrav.org", + "homepage": "http://getgrav.org", + "role": "Developer" + }, + { + "name": "Sommerregen", + "email": "sommerregen@benjamin-regler.de", + "homepage": "http://benjamin-regler.de", + "role": "Developer" + } + ], + "support": { + "email": "support@example.org", + "issues": "https://github.com/getgrav/grav-plugin-login/issues", + "irc": "https://gitter.im/getgrav/grav", + "forum": "http://getgrav.org/forum", + "docs": "https://github.com/getgrav/grav-plugin-login/blob/master/README.md" + }, + "require": { + "php": ">=5.4.0", + "birke/rememberme": "1.*" + }, + "autoload": { + "psr-4": { + "Grav\\Plugin\\Login\\": "classes/" + } + } +} diff --git a/sandbox/grav/user/plugins/login/composer.lock b/sandbox/grav/user/plugins/login/composer.lock new file mode 100644 index 0000000000..8d1fb6d705 --- /dev/null +++ b/sandbox/grav/user/plugins/login/composer.lock @@ -0,0 +1,113 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "540c2e320c9819de1b7c90b59f17263d", + "packages": [ + { + "name": "birke/rememberme", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/gbirke/rememberme.git", + "reference": "810473852eb4823098e47e23376a19b77ba0c165" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/gbirke/rememberme/zipball/810473852eb4823098e47e23376a19b77ba0c165", + "reference": "810473852eb4823098e47e23376a19b77ba0c165", + "shasum": "" + }, + "require": { + "paragonie/random_compat": "^1.1.4" + }, + "require-dev": { + "phpunit/phpunit": "4.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "Birke\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gabriel Birke", + "email": "gb@birke-software.de" + } + ], + "description": "Secure \"Remember Me\" functionality", + "homepage": "https://github.com/gbirke/rememberme", + "keywords": [ + "cookie", + "remember", + "security" + ], + "time": "2017-02-12T12:43:00+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v1.4.2", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "965cdeb01fdcab7653253aa81d40441d261f1e66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/965cdeb01fdcab7653253aa81d40441d261f1e66", + "reference": "965cdeb01fdcab7653253aa81d40441d261f1e66", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "autoload": { + "files": [ + "lib/random.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "pseudorandom", + "random" + ], + "time": "2017-03-13T16:22:52+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.4.0" + }, + "platform-dev": [] +} diff --git a/sandbox/grav/user/plugins/login/css/login.css b/sandbox/grav/user/plugins/login/css/login.css new file mode 100644 index 0000000000..c4abce560b --- /dev/null +++ b/sandbox/grav/user/plugins/login/css/login.css @@ -0,0 +1,81 @@ +#grav-login { + max-width: 30rem; + margin: 5rem auto; + background: #fcfcfc; + border: 4px solid #eee; + border-radius: 4px; + padding: 1rem 3rem 3rem 3rem; + text-align: center; +} + +#grav-login .form-actions { + text-align: right; +} + +#grav-logout { + position: absolute; + bottom: 5px; + right: 5px; +} + +.alert.info { + color: #27ae60; +} + +.alert.error { + color: #e74c3c; +} + +#grav-login p { + font-size: small; + margin: 1rem 0; + padding: 0; + text-align: center; +} +#grav-login .form-actions p { + margin-bottom: 0; +} + +#grav-login .button { + vertical-align: middle; +} + +#grav-login .delimiter { + display: block; + font-size: 1.6rem; + letter-spacing: 1px; + line-height: 1.6rem; + position: relative; + text-transform: uppercase; + margin: 1rem 0; +} + +#grav-login .delimiter:after, +#grav-login .delimiter:before { + background-color: #777777; + content: ""; + height: 1px; + position: absolute; + top: 0.8rem; + width: 40%; +} +#grav-login .delimiter:before { + background-image: -moz-linear-gradient(right center , #777777, #ffffff); + left: 0; +} +#grav-login .delimiter:after { + background-image: -moz-linear-gradient(left center , #777777, #ffffff); + right: 0; +} + +#grav-login .rememberme { + display: inline-block; + float: left; + padding: 7px 0; + vertical-align: middle; +} + +#grav-login .rememberme label { + font-weight: inherit; + display: inline; +} diff --git a/sandbox/grav/user/plugins/login/hebe.json b/sandbox/grav/user/plugins/login/hebe.json new file mode 100644 index 0000000000..d1f65aedf5 --- /dev/null +++ b/sandbox/grav/user/plugins/login/hebe.json @@ -0,0 +1,15 @@ +{ + "project":"grav-plugin-login", + "platforms":{ + "grav":{ + "nodes":{ + "plugin":[ + { + "source":"/", + "destination":"/user/plugins/login" + } + ] + } + } + } +} diff --git a/sandbox/grav/user/plugins/login/languages.yaml b/sandbox/grav/user/plugins/login/languages.yaml new file mode 100644 index 0000000000..773ec5dff5 --- /dev/null +++ b/sandbox/grav/user/plugins/login/languages.yaml @@ -0,0 +1,512 @@ +en: + PLUGIN_LOGIN: + USERNAME: Username + EMAIL: Email + USERNAME_EMAIL: Username/Email + PASSWORD: Password + ACCESS_DENIED: Access denied... + LOGIN_FAILED: Login failed... + LOGIN_SUCCESSFUL: You have been successfully logged in. + BTN_LOGIN: Login + BTN_LOGOUT: Logout + BTN_FORGOT: Forgot + BTN_REGISTER: Register + BTN_RESET: "Reset Password" + BTN_SEND_INSTRUCTIONS: "Send Reset Instructions" + RESET_LINK_EXPIRED: "Reset link has expired, please try again" + RESET_PASSWORD_RESET: "Password has been reset" + RESET_INVALID_LINK: "Invalid reset link used, please try again" + FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instructions to reset your password have been sent via email" + FORGOT_FAILED_TO_EMAIL: "Failed to email instructions, please try again later" + FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Cannot reset password for %s, no email address is set" + FORGOT_USERNAME_DOES_NOT_EXIST: "User with username %s does not exist" + FORGOT_EMAIL_NOT_CONFIGURED: "Cannot reset password. This site is not configured to send emails" + FORGOT_EMAIL_SUBJECT: "%s Password Reset Request" + FORGOT_EMAIL_BODY: "

    Password Reset

    Dear %1$s,

    A request was made on %4$s to reset your password.


    Click this to reset your password

    Alternatively, copy the following URL into your browser's address bar:

    %2$s


    Kind regards,

    %3$s

    " + SESSION: "“Remember Me”-Session" + REMEMBER_ME: Remember Me + REMEMBER_ME_HELP: "Sets a persistent cookie on your browser to allow persistent-login authentication between sessions." + REMEMBER_ME_STOLEN_COOKIE: "Someone else has used your login information to access this page! All sessions were logged out. Please log in with your credentials and check your data." + BUILTIN_CSS: Use built in CSS + BUILTIN_CSS_HELP: Include the CSS provided by the admin plugin + ROUTE: Login path + ROUTE_HELP: Custom route to a custom login page that your theme provides + ROUTE_REGISTER: Registration path + ROUTE_REGISTER_HELP: Route to the registration page. Set this if you want to use the built-in registration page. Leave it empty if you have your own registration form + USERNAME_NOT_VALID: "Username should be between 3 and 16 characters, including lowercase letters, numbers, underscores, and hyphens. Uppercase letters, spaces, and special characters are not allowed" + USERNAME_NOT_AVAILABLE: "Username %s already exists, please pick another username" + PASSWORD_NOT_VALID: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters" + PASSWORDS_DO_NOT_MATCH: "Passwords do not match. Double-check you entered the same password twice" + USER_NEEDS_EMAIL_FIELD: "The user needs an email field" + EMAIL_SENDING_FAILURE: "An error occurred while sending the email" + ACTIVATION_EMAIL_SUBJECT: "Activate your account on %s" + ACTIVATION_EMAIL_BODY: "Hi %s, click here to activate your account on %s" + WELCOME_EMAIL_SUBJECT: "Welcome to %s" + WELCOME_EMAIL_BODY: "Hi %s, welcome to %s!" + NOTIFICATION_EMAIL_SUBJECT: "New user on %s" + NOTIFICATION_EMAIL_BODY: "Hi, a new user registered on %s. Username: %s, email: %s" + EMAIL_FOOTER: "GetGrav.org" + ACTIVATION_LINK_EXPIRED: "Activation link expired" + USER_ACTIVATED_SUCCESSFULLY: "User activated successfully" + INVALID_REQUEST: "Invalid request" + USER_REGISTRATION: "User Registration" + USER_REGISTRATION_ENABLED_HELP: "Enable the user registration" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Validate double entered password" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validate and compare two different fields for the passwords, named `password1` and `password2`. Enable this if you have two password fields in the registration form" + SET_USER_DISABLED: "Set the user as disabled" + SET_USER_DISABLED_HELP: "Best used along with the `Send activation email` email. Adds the user to Grav, but sets it as disabled" + LOGIN_AFTER_REGISTRATION: "Login the user after registration" + LOGIN_AFTER_REGISTRATION_HELP: "Immediately login the user after the registration. If email activation is required, the user will be logged in immediately after activating the account" + SEND_ACTIVATION_EMAIL: "Send activation email" + SEND_ACTIVATION_EMAIL_HELP: "Sends an email to the user to activate his account. Enable the `Set the user as disabled` option when using this feature, so the user will be set as disabled and an email will be sent to activate the account" + SEND_NOTIFICATION_EMAIL: "Send notification email" + SEND_NOTIFICATION_EMAIL_HELP: "Notifies the site admin that a new user has registered. The email will be sent to the `To` field in the Email Plugin configuration" + SEND_WELCOME_EMAIL: "Send welcome email" + SEND_WELCOME_EMAIL_HELP: "Sends an email to the newly registered user" + DEFAULT_VALUES: "Default values" + DEFAULT_VALUES_HELP: "List of field names and values associated, that will be added to the user profile (yaml file) by default, without being configurable by the user. Separate multiple values with a comma, with no spaces between the values" + ADDITIONAL_PARAM_KEY: "Parameter" + ADDITIONAL_PARAM_VALUE: "Value" + REGISTRATION_FIELDS: "Registration fields" + REGISTRATION_FIELDS_HELP: "Add the fields that will be added to the user yaml file. Fields not listed here will not be added even if present in the registration form" + REGISTRATION_FIELD_KEY: "Field name" + REDIRECT_AFTER_LOGIN: "Redirect after login" + REDIRECT_AFTER_LOGIN_HELP: "Custom route to redirect after login" + REDIRECT_AFTER_REGISTRATION: "Redirect after registration" + REDIRECT_AFTER_REGISTRATION_HELP: "Custom route to redirect after the registration" + OPTIONS: Options + EMAIL_VALIDATION_MESSAGE: "Must be a valid email address" + PASSWORD_VALIDATION_MESSAGE: "Password must contain at least one number and one uppercase and lowercase letter, and at least 8 or more characters" + TIMEOUT_HELP: "Sets the session timeout in seconds when Remember Me is enabled and checked by the user. Minimum is 604800 which means 1 week" + GROUPS_HELP: "List of groups the new registered user will be part of, if any" + SITE_ACCESS_HELP: "List of site access levels the new registered user will have. Example: `login` -> `true` " + WELCOME: "Welcome" + REDIRECT_AFTER_ACTIVATION: "Redirect after the user activation" + REDIRECT_AFTER_ACTIVATION_HELP: "Used if the user is required to activate the account via email. Once activated, this route will be shown" + REGISTRATION_DISABLED: "Registration disabled" + USE_PARENT_ACL_LABEL: "Use parent access rules" + USE_PARENT_ACL_HELP: "Check for parent access rules if no rules are defined" + PROTECT_PROTECTED_PAGE_MEDIA_LABEL: "Protect a login-protected page media" + PROTECT_PROTECTED_PAGE_MEDIA_HELP: "If enabled, media of a login protected page is login protected as well and cannot be seen unless logged in" + SECURITY_TAB: "Security" + MAX_RESETS_COUNT: "Max password resets count" + MAX_RESETS_COUNT_HELP: "Password reset flood protection setting (0 - not limited)" + MAX_RESETS_INTERVAL: "Max password resets interval" + MAX_RESETS_INTERVAL_HELP: "The time interval for the max password resets count value" + FORGOT_CANNOT_RESET_IT_IS_BLOCKED: "Cannot reset password for %s, password reset functionality temporarily blocked, please try later (maximum %s minutes)" + MAX_LOGINS_COUNT: "Max logins count" + MAX_LOGINS_COUNT_HELP: "Flood protection setting (0 - not limited)" + MAX_LOGINS_INTERVAL: "Max logins interval" + MAX_LOGINS_INTERVAL_HELP: "The time interval for the login count value" + TOO_MANY_LOGIN_ATTEMPTS: "Too many failed login attempted in the configured time (%s minutes)" + SECONDS: "seconds" + RESETS: "resets" + ATTEMPTS: "attempts" + ROUTES: "Routes" + ROUTE_FORGOT: "Forgot password route" + ROUTE_RESET: "Reset password route" + ROUTE_PROFILE: "User profile route" + ROUTE_ACTIVATE: "User activation route" + +de: + PLUGIN_LOGIN: + USERNAME: Benutzername + EMAIL: Email + USERNAME_EMAIL: Benutzername/Email + PASSWORD: Passwort + ACCESS_DENIED: Zugang verweigert + LOGIN_FAILED: Login fehlgeschlagen... + LOGIN_SUCCESSFUL: Du wurdest erfolgreich eingeloggt. + BTN_LOGIN: Anmelden + BTN_LOGOUT: Abmelden + BTN_FORGOT: Vergessen + BTN_REGISTER: Registrieren + REMEMBER_ME: Angemeldet bleiben + REMEMBER_ME_HELP: "Speichert einen Cookie im Browser, welcher eine fortwährende Anmeldung sicherstellt." + BUILTIN_CSS: "Nutze das integrierte CSS" + BUILTIN_CSS_HELP: "Nutze das CSS, welches vom Admin Plugin bereitgestellt werden" + ROUTE: "Anmeldepfad" + ROUTE_REGISTER: "Registrierungspfad" + USERNAME_NOT_AVAILABLE: "Der Nutzername %s existiert bereits, bitte wähle einen Anderen" + USER_NEEDS_EMAIL_FIELD: "Der Nutzer benötigt ein E-Mail Feld" + EMAIL_SENDING_FAILURE: "Ein Fehler ist beim senden der E-Mail aufgetreten" + ACTIVATION_EMAIL_SUBJECT: "Aktiviere dein Account auf %s" + ACTIVATION_EMAIL_BODY: "Hi %s, click %s to activate your account on %s" + WELCOME_EMAIL_SUBJECT: "Willkommen zu %s" + WELCOME_EMAIL_BODY: "Hi %s, willkommen zu %s!" + NOTIFICATION_EMAIL_SUBJECT: "Neuer Nutzer auf %s" + NOTIFICATION_EMAIL_BODY: "Hi, ein neuer Nutzer hat sich auf %s registriert. Nutzername: %s, E-Mail: %s" + EMAIL_FOOTER: "GetGrav.org" + ACTIVATION_LINK_EXPIRED: "Aktivierungslink ist abgelaufen" + USER_ACTIVATED_SUCCESSFULLY: "Nutzer erfolgreich aktiviert" + INVALID_REQUEST: "Ungültige Anfrage" + USER_REGISTRATION: "Nutzer Registrierung" + USER_REGISTRATION_ENABLED_HELP: "Aktiviere die Nutzer Registrierung" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Überprüfe das doppelt eingegebene Passwort" + SEND_ACTIVATION_EMAIL: "Aktivierungs E-Mail senden" + SEND_NOTIFICATION_EMAIL: "Benachtichtigungs E-Mail senden" + SEND_WELCOME_EMAIL: "Sende eine Willkommens E-Mail" + DEFAULT_VALUES: "Standard Werte" + ADDITIONAL_PARAM_KEY: "Parameter" + ADDITIONAL_PARAM_VALUE: "Wert" + REGISTRATION_FIELDS: "Registrierungsfelder" + REGISTRATION_FIELD_KEY: "Feldname" + REDIRECT_AFTER_LOGIN: "Umleitung nach Login" + REDIRECT_AFTER_REGISTRATION: "Umleitung nach Registrierung" + OPTIONS: Optionen + EMAIL_VALIDATION_MESSAGE: "Muss eine gültige E-Mail Adresse sein" + WELCOME: "Willkommen" + +fr: + PLUGIN_LOGIN: + USERNAME: "Nom d’utilisateur" + EMAIL: "E-mail" + USERNAME_EMAIL: "Nom d’utilisateur/E-mail" + PASSWORD: "Mot de passe" + ACCESS_DENIED: "Accès refusé..." + LOGIN_FAILED: "Échec de la connexion..." + LOGIN_SUCCESSFUL: "Vous vous êtes connecté avec succès." + BTN_LOGIN: "Connexion" + BTN_LOGOUT: "Déconnexion" + BTN_FORGOT: "Mot de passe oublié" + BTN_REGISTER: "S’enregister" + BTN_RESET: "Réinitialiser le mot de passe" + BTN_SEND_INSTRUCTIONS: "Envoyer les instructions de Réinitialisation" + RESET_LINK_EXPIRED: "Le lien de réinitialisation a expiré, veuillez réessayer" + RESET_PASSWORD_RESET: "Le mot de passe a été réinitialisé" + RESET_INVALID_LINK: "Le lien de réinitialisation utilisé n’est pas valide, veuillez réessayer" + FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Les instructions pour la réinitialisation de votre mot de passe ont été envoyées par e-mail" + FORGOT_FAILED_TO_EMAIL: "Impossible d’envoyer les instructions, veuillez réessayer ultérieurement" + FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Impossible de réinitialiser le mot de passe pour %s, aucune adresse e-mail n’a été paramétrée" + FORGOT_USERNAME_DOES_NOT_EXIST: "L’utilisateur avec le nom d’utilisateur %s n’existe pas" + FORGOT_EMAIL_NOT_CONFIGURED: "Impossible de réinitialiser le mot de passe. Ce site n’est pas configuré pour envoyer des e-mails" + FORGOT_EMAIL_SUBJECT: "Demande de réinitialisation de mot de passe %s" + FORGOT_EMAIL_BODY: "

    Réinitialisation de mot de passe

    %1$s,

    Une demande a été faite sur %4$s pour la réinitialisation de votre mot de passe.


    Cliquez ici pour réinitialiser votre mot de passe

    Vous pouvez également copier l’URL suivante dans la barre d’adresse de votre navigateur :

    %2$s


    Cordialement,

    %3$s

    " + SESSION: "Session - “Se souvenir de moi”" + REMEMBER_ME: "Se souvenir de moi" + REMEMBER_ME_HELP: "Définit un cookie persistant sur votre navigateur autorisant l’authentification par connexion persistante entre les sessions." + REMEMBER_ME_STOLEN_COOKIE: "Quelqu’un d’autre a utilisé vos informations de connexion pour accéder à cette page ! Toutes les sessions ont été déconnectées. Veuillez vous connecter avec vos identifiants et vérifier vos données." + BUILTIN_CSS: "Utiliser les CSS intégrés" + BUILTIN_CSS_HELP: "Utiliser les CSS fournis dans le plugin d’administration" + ROUTE: "Chemin de connexion" + ROUTE_HELP: "Chemin personnalisé vers une page de connexion personnalisée proposée par votre thème" + ROUTE_REGISTER: "Chemin vers l’inscription" + ROUTE_REGISTER_HELP: "Chemin vers la page d’inscription. A définir si vous souhaitez utiliser la page d’inscription intégrée. Laisser vide si vous disposez de votre propre formulaire d’inscription." + USERNAME_NOT_VALID: "Le nom d’utilisateur doit comporter entre 3 et 16 caractères et peut être composé de lettres minuscules, de chiffres et de tirets de soulignement (underscores) et des traits d’union. Les lettres majuscules, les espaces et les caractères spéciaux ne sont pas autorisés." + USERNAME_NOT_AVAILABLE: "Le nom d’utilisateur %s existe déjà, veuillez en choisir un autre." + PASSWORD_NOT_VALID: "Le mot de passe doit contenir au moins un chiffre, une majuscule et une minuscule et être composé d’au moins 8 caractères" + PASSWORDS_DO_NOT_MATCH: "Les mots de passe sont différents. Réessayez de saisir le même mot de passe deux fois." + USER_NEEDS_EMAIL_FIELD: "L’utilisateur a besoin d’un champ pour e-mail" + EMAIL_SENDING_FAILURE: "Une erreur est survenue lors de l’envoi de l’e-mail." + ACTIVATION_EMAIL_SUBJECT: "Activer votre compte sur %s" + ACTIVATION_EMAIL_BODY: "Bonjour %s, cliquez pour activer votre compte sur %s" + WELCOME_EMAIL_SUBJECT: "Bienvenue sur %s" + WELCOME_EMAIL_BODY: "Bonjour %s, bienvenue sur %s!" + NOTIFICATION_EMAIL_SUBJECT: "Nouvel utilisateur sur %s" + EMAIL_FOOTER: "GetGrav.org" + NOTIFICATION_EMAIL_BODY: "Bonjour, un nouvel utilisateur s’est inscrit sur %s. Nom d’utilisateur : %s, e-mail : %s" + ACTIVATION_LINK_EXPIRED: "Le lien d’activation a expiré" + USER_ACTIVATED_SUCCESSFULLY: "Utilisateur activé avec succès" + INVALID_REQUEST: "Requête non valide" + USER_REGISTRATION: "Inscription de l’utilisateur" + USER_REGISTRATION_ENABLED_HELP: "Activer l’inscription des utilisateurs" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Valider la double saisie du mot de passe" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Comparer et valider deux champs pour les mots de passe `Mot de passe 1` et `Mot de passe 2`. Activez cette option si vous avez deux champs de mots de passe dans le formulaire d’inscription." + SET_USER_DISABLED: "Définir l’utilisateur comme désactivé" + SET_USER_DISABLED_HELP: "La meilleure pratique si vous utilisez l’option `Envoyer un e-mail d’activation`. Ajoute l’utilisateur à Grav, mais le défini comme étant désactivé." + LOGIN_AFTER_REGISTRATION: "Connecte l’utilisateur après son inscription" + LOGIN_AFTER_REGISTRATION_HELP: "Identifier immédiatement l’utilisateur après l’inscription. Si l’e-mail d’activation est demandé, l’utilisateur sera connecté immédiatement après l’activation du compte." + SEND_ACTIVATION_EMAIL: "Envoyer un e-mail d’activation" + SEND_ACTIVATION_EMAIL_HELP: "Envoyer un e-mail à l’utilisateur pour l’activation son compte. Lorsque vous utilisez cette fonction, activez l’option `Définir l’utilisateur comme désactivé` afin que l’utilisateur soit désactivé et qu’un e-mail lui soit envoyé pour activer son compte." + SEND_NOTIFICATION_EMAIL: "Envoyer un e-mail de notification" + SEND_NOTIFICATION_EMAIL_HELP: "Informe l’administrateur du site qu’un nouvel utilisateur s’est enregistré. L’e-mail sera envoyé à la personne renseignée dans le champ `À` dans la configuration du plugin e-mail." + SEND_WELCOME_EMAIL: "Envoyer un e-mail de bienvenue" + SEND_WELCOME_EMAIL_HELP: "Envoyer un e-mail à un nouvel utilisateur enregistré." + DEFAULT_VALUES: "Valeurs par défaut" + DEFAULT_VALUES_HELP: "Liste des noms et valeurs associés pour les champs. Ils seront ajoutés au profil utilisateur par défaut (fichier yaml), sans pouvoir être configurables par l’utilisateur. Séparez les différentes valeurs par une virgule, sans espaces entre les valeurs." + ADDITIONAL_PARAM_KEY: "Paramètre" + ADDITIONAL_PARAM_VALUE: "Valeur" + REGISTRATION_FIELDS: "Champs d’inscription" + REGISTRATION_FIELDS_HELP: "Ajouter les champs qui seront ajoutés au fichier yaml de l’utilisateur. Les champs non listés ne seront pas ajoutés même s’ils sont présent sur le formulaire d’inscription" + REGISTRATION_FIELD_KEY: "Nom du champ" + REDIRECT_AFTER_LOGIN: "Redirection après connexion" + REDIRECT_AFTER_LOGIN_HELP: "Chemin personnalisé de redirection après la connexion" + REDIRECT_AFTER_REGISTRATION: "Redirection après inscription" + REDIRECT_AFTER_REGISTRATION_HELP: "Chemin personnalisé de redirection après l’inscription" + OPTIONS: "Options" + EMAIL_VALIDATION_MESSAGE: "Doit-être une adresse e-mail valide" + PASSWORD_VALIDATION_MESSAGE: "Le mot de passe doit-être composé d’au moins un chiffre, une majuscule et une minuscule, et au moins 8 caractères" + TIMEOUT_HELP: "Définit le délai d’expiration de la session en secondes lorsque `Se souvenir de moi` est activé et coché par l’utilisateur. Minimum de 604800 ce qui correspond à 1 semaine." + GROUPS_HELP: "Liste des groupes auxquels le nouvel utilisateur enregistré fera partie, le cas échéant." + SITE_ACCESS_HELP: "Liste des niveaux d’accès au site attribués au nouvel utilisateur enregistré. Exemple : `login` -> `true` " + WELCOME: "Bienvenue" + REDIRECT_AFTER_ACTIVATION: "Redirection après l’activation de l’utilisateur" + REDIRECT_AFTER_ACTIVATION_HELP: "Utilisé s’il est nécessaire pour l’utilisateur d’activer le compte par e-mail. Une fois activé, ce chemin s’affichera" + REGISTRATION_DISABLED: "Inscription désactivée" + USE_PARENT_ACL_LABEL: "Appliquer les règles d’accès parentes" + USE_PARENT_ACL_HELP: "Utiliser les règles d’accès parentes si aucune règle n’a été définie" + PROTECT_PROTECTED_PAGE_MEDIA_LABEL: "Protéger le média d'une page par une protection par connexion" + PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Si activé, les médias d'une page protégée par connexion sera également protégé par un système de connexion et ne pourra pas être visible à moins d'être connecté." + +ru: + PLUGIN_LOGIN: + USERNAME: Логин + EMAIL: Email + USERNAME_EMAIL: Логин/Email + PASSWORD: Пароль + ACCESS_DENIED: Доступ закрыт... + LOGIN_FAILED: Ошибка входа... + LOGIN_SUCCESSFUL: Вы успешно вошли в систему. + BTN_LOGIN: Войти + BTN_LOGOUT: Выйти + BTN_FORGOT: Забыл + BTN_REGISTER: Регистрация + BTN_RESET: "Сброс пароля" + BTN_SEND_INSTRUCTIONS: "Отправить инструкции по сбросу" + RESET_LINK_EXPIRED: "Время ссылки для сброса истекло, повторите попытку" + RESET_PASSWORD_RESET: "Пароль был сброшен" + RESET_INVALID_LINK: "Неверная ссылка сброса, повторите попытку" + FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Инструкции по сбросу пароля были отправлены по электронной почте" + FORGOT_FAILED_TO_EMAIL: "Не удалось отправить инструкции по электронной почте, повторите попытку позже" + FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Не удается сбросить пароль для %s, адресс электронной почты не установлен" + FORGOT_USERNAME_DOES_NOT_EXIST: "Пользователь с именем %s не существует" + FORGOT_EMAIL_NOT_CONFIGURED: "Невозможно сбросить пароль. Этот сайт не настроен для отправки писем" + FORGOT_EMAIL_SUBJECT: "%s Запрос на сброс пароля" + FORGOT_EMAIL_BODY: "

    Восстановление пароля

    Уважаемый %1$s,

    Был сделан запрос для сброса пароля от %4$s.


    Нажмите, чтобы сбросить пароль

    Или скопируйте следующий URL-адрес в адресную строку браузера:

    %2$s


    С уважением,

    %3$s

    " + SESSION: "“Запомнить меня”-Сессия" + REMEMBER_ME: Запомнить меня + REMEMBER_ME_HELP: "Устанавливает постоянный файл cookie в вашем браузере, чтобы разрешить постоянную аутентификацию входа между сеансами." + REMEMBER_ME_STOLEN_COOKIE: "Кто-то еще использовал вашу регистрационную информацию для доступа к этой странице! Все сеансы были отключены. Войдите в свою учетную запись и проверьте свои данные." + BUILTIN_CSS: Использовать встроенный CSS + BUILTIN_CSS_HELP: Использовать CSS, предоставленный плагином администратора. + ROUTE: Путь страницы входа + ROUTE_HELP: Путь к пользовательской странице входа, которую предоставляет ваша тема + ROUTE_REGISTER: Путь регистрации + ROUTE_REGISTER_HELP: Путь к пользовательской странице регистрации. Заполните, если вы хотите использовать встроенную страницу регистрации. Оставьте его пустым, если у вас есть собственная регистрационная форма + USERNAME_NOT_VALID: "Имя пользователя должно быть от 3 до 16 символов, включая строчные буквы, цифры, символы подчеркивания и дефисы. Прописные буквы, пробелы и специальные символы не допускаются" + USERNAME_NOT_AVAILABLE: "Имя пользователя %s уже существует, выберите другое имя пользователя" + PASSWORD_NOT_VALID: "Пароль должен содержать как минимум одно число, одну прописную и строчную букву, и быть не менее 8 символов" + PASSWORDS_DO_NOT_MATCH: "Пароли не совпадают. Дважды проверьте, что вы дважды ввели тот же пароль" + USER_NEEDS_EMAIL_FIELD: "Пользователю требуется поле электронной почты" + EMAIL_SENDING_FAILURE: "Произошла ошибка при отправке письма" + ACTIVATION_EMAIL_SUBJECT: "Активируйте свою учетную запись %s" + ACTIVATION_EMAIL_BODY: "Привет %s, перейдите сюда для активации вашей учетной записи %s" + WELCOME_EMAIL_SUBJECT: "Добро пожаловать в %s" + WELCOME_EMAIL_BODY: "Привет %s, добро пожаловать в %s!" + NOTIFICATION_EMAIL_SUBJECT: "Новый пользователь %s" + NOTIFICATION_EMAIL_BODY: "Привет, новый пользователь, зарегистрированный на %s. Имя пользователя: %s, email: %s" + EMAIL_FOOTER: "GetGrav.org" + ACTIVATION_LINK_EXPIRED: "Время ссылки для активации истекло" + USER_ACTIVATED_SUCCESSFULLY: "Пользователь успешно активирован" + INVALID_REQUEST: "Неверный запрос" + USER_REGISTRATION: "Регистрация пользователя" + USER_REGISTRATION_ENABLED_HELP: "Включить регистрацию пользователя" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Двойная проверка введенного пароля" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Подтвердить и сравнить два разных поля для паролей с именами `password1` и` password2`. Включите это, если у вас есть два поля пароля в регистрационной форме" + SET_USER_DISABLED: "Установить пользователя как отключенный" + SET_USER_DISABLED_HELP: "Лучше всего использовать электронную почту «Отправить электронную почту активации». Добавляет пользователя в Grav, но устанавливает его как отключенный" + LOGIN_AFTER_REGISTRATION: "Воход в систему после регистрации" + LOGIN_AFTER_REGISTRATION_HELP: "Автоматический воход в систему после регистрации. Если требуется активация электронной почты, пользователь будет входить в систему сразу после активации учетной записи" + SEND_ACTIVATION_EMAIL: "Отправить письмо активации" + SEND_ACTIVATION_EMAIL_HELP: "Отправляет электронное письмо пользователю для активации своей учетной записи. Включите параметр «Установить пользователя как отключенный» при использовании этой функции, чтобы пользователь был отключен, и для активации учетной записи будет отправлено электронное письмо" + SEND_NOTIFICATION_EMAIL: "Отправить уведомление по электронной почте" + SEND_NOTIFICATION_EMAIL_HELP: "Сообщает администратору сайта о регистрации нового пользователя. Электронная почта будет отправлена в поле «Кому» в конфигурации плагина электронной почты" + SEND_WELCOME_EMAIL: "Отправить приветственное письмо" + SEND_WELCOME_EMAIL_HELP: "Отправляет электронное письмо вновь зарегистрированному пользователю" + DEFAULT_VALUES: "Значения по умолчанию" + DEFAULT_VALUES_HELP: "Список названий полей и связанных значений, которые будут добавлены в профиль пользователя (файл yaml) по умолчанию, без настройки пользователем. Разделите несколько значений запятой, без пробелов между значениями" + ADDITIONAL_PARAM_KEY: "Параметр" + ADDITIONAL_PARAM_VALUE: "Значение" + REGISTRATION_FIELDS: "Регистрационные поля" + REGISTRATION_FIELDS_HELP: "Добавьте поля, которые будут добавлены в файл yaml пользователя. Поля, не перечисленные здесь, не будут добавлены, даже если они присутствуют в регистрационной форме" + REGISTRATION_FIELD_KEY: "Имя поля" + REDIRECT_AFTER_LOGIN: "Перенаправление после входа в систему" + REDIRECT_AFTER_LOGIN_HELP: "Пользовательский маршрут для перенаправления после входа в систему" + REDIRECT_AFTER_REGISTRATION: "Перенаправление после регистрации" + REDIRECT_AFTER_REGISTRATION_HELP: "Пользовательский маршрут для перенаправления после регистрации" + OPTIONS: Опции + EMAIL_VALIDATION_MESSAGE: "Адрес эл. почты должен быть действительным" + PASSWORD_VALIDATION_MESSAGE: "Пароль должен содержать как минимум одно число, одну прописную и строчную букву и быть не менее 8 символов" + TIMEOUT_HELP: "Устанавливает тайм-аут сеанса в секундах, когда функция «Запомнить меня» включена и установлена пользователем. Минимум 604800, что означает 1 неделю" + GROUPS_HELP: "Список групп, в которые войдет новый зарегистрированный пользователь" + SITE_ACCESS_HELP: "Список уровней доступа к сайту, зарегистрированных пользователей. Пример: `login` ->` true`" + WELCOME: "Добро пожаловать" + REDIRECT_AFTER_ACTIVATION: "Перенаправление после активации пользователя" + REDIRECT_AFTER_ACTIVATION_HELP: "Используется, если пользователю требуется активировать учетную запись по электронной почте. После активации этот маршрут будет показан" + REGISTRATION_DISABLED: "Регистрация отключена" + USE_PARENT_ACL_LABEL: "Использовать родительские правила доступа" + USE_PARENT_ACL_HELP: "Проверьте правила доступа к родителям, если правила не определены" + PROTECT_PROTECTED_PAGE_MEDIA_LABEL: "Защита защищенных страниц." + PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Если этот параметр включен, то доступ к защищенной странице для входа в систему также защищен паролем, и его нельзя увидеть, если он не зарегистрирован" + SECURITY_TAB: "Безопасность" + MAX_RESETS_COUNT: "Максимальное количество сброса пароля" + MAX_RESETS_COUNT_HELP: "Настройка защиты пароля от флуда (0 - не ограничено)" + MAX_RESETS_INTERVAL: "Максимальный интервал сброса пароля" + MAX_RESETS_INTERVAL_HELP: "Интервал времени для максимального количества сбросов пароля" + FORGOT_CANNOT_RESET_IT_IS_BLOCKED: "Невозможно сбросить пароль для %s, функция сброса пароля временно отключена, попробуйте позже (максимум %s минут)" + MAX_LOGINS_COUNT: "Максимальное количество входов" + MAX_LOGINS_COUNT_HELP: "Настройка защиты от флуда (0 - не ограничено)" + MAX_LOGINS_INTERVAL: "Максимальный интервал входа" + MAX_LOGINS_INTERVAL_HELP: "Временной интервал для значения счетчика входа" + TOO_MANY_LOGIN_ATTEMPTS: "Слишком много неудачных попыток входа в настроенное время (%s минут)" + SECONDS: "секунд" + RESETS: "сбросов" + ATTEMPTS: "попыток" + ROUTES: "Маршруты" + ROUTE_FORGOT: "Забыли пароль" + ROUTE_RESET: "Сброса пароля" + ROUTE_PROFILE: "Профиля пользователя" + ROUTE_ACTIVATE: "Активации пользователя" + +hr: + PLUGIN_LOGIN: + ACCESS_DENIED: Pristup odbijen... + LOGIN_FAILED: Prijava nije uspjela... + BTN_LOGIN: Prijava + BTN_LOGOUT: Odjava + BTN_FORGOT: Zaboravih + BTN_REGISTER: Registriraj + REMEMBER_ME: Zapamti me + BUILTIN_CSS: Koristi ugrađeni CSS + BUILTIN_CSS_HELP: Uključi CSS koji dolazi sa admin pluginom + ROUTE: Putanja prijave + ROUTE_REGISTER: Putanja registracije + USERNAME_NOT_VALID: "Korisničko ime bi trebalo biti između 3 i 16 znakova, uključujući mala slova, brojeve, _, i crtice. VELIKA SLOVA, razmaci, i posebni znakovi nisu dopušteni" + USERNAME_NOT_AVAILABLE: "Korisničko ime %s već postoji, odaberi neko drugo" + PASSWORD_NOT_VALID: "Lozinka mora sadržavati bar jedan broj i jedno veliko i malo slovo, i bar još 8 ili više znakova" + PASSWORDS_DO_NOT_MATCH: "Lozinke se ne slažu. Poonovo provjeri da li si unio istu lozinku dva puta" + USER_NEEDS_EMAIL_FIELD: "Korisnik treba email polje" + EMAIL_SENDING_FAILURE: "Došlo je do greške pri slanju emaila" + ACTIVATION_LINK_EXPIRED: "Aktivacijski link je istekao" + USER_ACTIVATED_SUCCESSFULLY: "Korisnik je uspješno aktiviran" + INVALID_REQUEST: "Nevaljani zahtjev" + USER_REGISTRATION: "Registracija korisnika" + USER_REGISTRATION_ENABLED_HELP: "Omogući registraciju korisnika" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Validiraj duplo unesenu lozinku" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validiraj i usporedi dva različčita polja za lozinke, imenovana `lozinka1` i `lozinka2`. Omogući ovo ako imaš dva polja za lozinke u formularu registracije" + LOGIN_AFTER_REGISTRATION: "Ulogiraj korisnika nakon reegistracije" + LOGIN_AFTER_REGISTRATION_HELP: "Ulogiraj korisnika odmah nakon registracije. Ako je potrebna email aktivacija, korisnik će biti ulogiran odmah nakon aktiviranja računa" + SEND_ACTIVATION_EMAIL: "Pošalji aktivacijski email" + SEND_ACTIVATION_EMAIL_HELP: "Šalje email korisniku da aktivira svoja račun." + SEND_NOTIFICATION_EMAIL: "Pošalji email obavijest" + SEND_NOTIFICATION_EMAIL_HELP: "Obavještava administratora da se novi korisnik registrirao. Email će biti poslan na `To` polje u Email Plugin konfiguraciji" + SEND_WELCOME_EMAIL: "Pošalji email dobrodošlice" + SEND_WELCOME_EMAIL_HELP: "Šalje email novoregistriranom korisniku" + DEFAULT_VALUES: "Određene vrijednosti" + DEFAULT_VALUES_HELP: "List of field names and values associated, that will be added to the user profile (yaml file) by default, without being configurable by the user. Separate multiple values with a comma, with no spaces between the values" + ADDITIONAL_PARAM_KEY: "Parametar" + ADDITIONAL_PARAM_VALUE: "Vrijednost" + REGISTRATION_FIELDS: "Registracijska polja" + REGISTRATION_FIELDS_HELP: "Add the fields that will be added to the user yaml file. Fields not listed here will not be added even if present in the registration form" + REGISTRATION_FIELD_KEY: "Ime polja" + OPTIONS: Opcije + +hu: + PLUGIN_LOGIN: + ACCESS_DENIED: Hozzáférés megtagadva... + LOGIN_FAILED: Sikertelen belépés... + LOGIN_SUCCESSFUL: Sikeresen beléptél. + BTN_LOGIN: Belépés + BTN_LOGOUT: Kilépés + BTN_FORGOT: Elfelejtettem + BTN_REGISTER: Regisztráció + REMEMBER_ME: Jegyezz Meg + REMEMBER_ME_HELP: "Elhelyezünk egy hosszú lejáratú sütit a böngésződben, hogy belépve maradhass két munkamenet között." + REMEMBER_ME_STOLEN_COOKIE: "Valaki a belépési adataid felhasználásával látogatta meg ezt az oldalt! Minden munkamenetet kiléptettünk. Kérlek, jelentkezz be ismét és ellenőrizd az adataidat." + BUILTIN_CSS: Beépített CSS használata + BUILTIN_CSS_HELP: Az admin plugin által biztosított CSS beillesztése + ROUTE: Belépés útvonala + ROUTE_HELP: Egyedi útvonal egy egyedi belépő oldalhoz, melyet az aktuális téma biztosít + ROUTE_REGISTER: Regisztráció útvonala + ROUTE_REGISTER_HELP: A regisztrációs oldal elérési útja. Akkor állítsd be, ha a beépített regisztrációs oldalt szeretnéd használni + USERNAME_NOT_VALID: "A felhasználónév 3-16 karakter hosszú legyen, tartalmazhat kisbetűket, számokat, aláhúzást és kötőjelet. Nagybetűk, szóköz és speciális karakterek használata nem megengedett" + USERNAME_NOT_AVAILABLE: "%s nevű felhasználó már létezik, kérlek válassz más felhasználónevet" + PASSWORD_NOT_VALID: "A jelszónak tartalmaznia kell legalább egy számot, egy kisbetűt és egy nagybetűt, valamint legalább 8 karakter hosszú kell, hogy legyen" + PASSWORDS_DO_NOT_MATCH: "A két jelszó nem egyezik meg. Győzödj meg róla, hogy azonos legyen a kettő" +ro: + PLUGIN_LOGIN: + USERNAME: "Nume utilizator" + PASSWORD: "Parolă" + ACCESS_DENIED: "Acces refuzat..." + LOGIN_FAILED: "Logare eșuată..." + LOGIN_SUCCESSFUL: "Ați fost logat cu succes." + BTN_LOGIN: "Logare" + BTN_LOGOUT: "Ieșire din cont " + BTN_FORGOT: "Am uitat" + BTN_REGISTER: "Înregistrare" + BTN_RESET: "Resetează parola" + BTN_SEND_INSTRUCTIONS: "Trimite instrucțiuni pentru resetare" + RESET_LINK_EXPIRED: "Link-ul pentru resetarea parolei a expirat, vă rugăm încercați din nou " + RESET_PASSWORD_RESET: "Parola a fost modificată" + RESET_INVALID_LINK: "Link-ul pentru resetare este invalid, Invalid reset link used, vă rugăm încercați din nou " + FORGOT_INSTRUCTIONS_SENT_VIA_EMAIL: "Instrucțiunile pentru resetarea parolei au fst trimise pe email" + FORGOT_FAILED_TO_EMAIL: "Instrucțiunile nu au putut fi trimise pe email, vă rugăm încercați mai târziu " + FORGOT_CANNOT_RESET_EMAIL_NO_EMAIL: "Parola nu poate fi resetată pentru %s, nu este setată nici o adresă de email" + FORGOT_USERNAME_DOES_NOT_EXIST: "Utilizatorul cu numele %s nu există" + FORGOT_EMAIL_NOT_CONFIGURED: "Parola nu poate fi resetată. Acest site nu este configurat pentru a trimite email-uri." + FORGOT_EMAIL_SUBJECT: "%s Cerere de resetare a parolei" + FORGOT_EMAIL_BODY: "

    Resetare parolă

    Dragă %1$s,

    O cerere de resetare a parolei a fost făcută în data de %4$s.


    Apasă aici pentru a reseta parola

    Alternativ, copiază URL de mai jos în bara de adrese a browser-ului favorit:

    %2$s


    Cu respect,

    %3$s

    " + SESSION: "“Ține-mă minte”-Sesiune" + REMEMBER_ME: "Ține-mă minte" + REMEMBER_ME_HELP: "Setează o cookie în browserul Dvs. ce permite menținerea datelor de logare între sesiuni." + REMEMBER_ME_STOLEN_COOKIE: "Altcineva a folosit darele Dvs de logare pentru a accesa această pagină! Toate sesiunile au fost deconectate. Vă rugăm să vă logați cu datele Dvs. și să verificați toate detaliile. " + BUILTIN_CSS: "Folosește CSS-ul din modul" + BUILTIN_CSS_HELP: "Include CSS-ul furnizat de către modulul Admin" + ROUTE: "Calea pentru logare" + ROUTE_HELP: "O rută personalizată către pagina de logare pe care o furnizează tema activă " + ROUTE_REGISTER: "Calea pentru înregistrare " + ROUTE_REGISTER_HELP: "Ruta către pagina de înregistrare. Setați această rută dacă doriți folosirea paginei implicite pentru înregistrare. Lăsați gol dacă folosiți o pagină personalizată pentru înregistrare." + USERNAME_NOT_VALID: "Numele de utilizator trebuie să fie între 3-16 caractere, incluzând litere mici, numere, linii de subliniere și cratime. Literele de tipar, spațiile și caracterele speciale nu sunt permise. " + USERNAME_NOT_AVAILABLE: "Utilizatorul %s există deja, vă rugăm alegeți un alt nume de utilizator " + PASSWORD_NOT_VALID: " Parola trebuie să conțină cel puțin 1 număr și o literă de tipar și o literă mică; și să aibă minim 8 caractere" + PASSWORDS_DO_NOT_MATCH: " Parolele nu sunt identice. Vă rugăm verificați că ași scris aceeiași parolă de două ori." + USER_NEEDS_EMAIL_FIELD: "Utilizatorul trebuie să aibă adresa de email completată" + EMAIL_SENDING_FAILURE: "A apărut o eroare la trimirea email-ului" + ACTIVATION_EMAIL_SUBJECT: "Activați-vă contrul pentru %s" + ACTIVATION_EMAIL_BODY: "Salut %s, apasă aici pentru a-ți activa contul de pe %s" + WELCOME_EMAIL_SUBJECT: "Bine ați venit pe %s" + WELCOME_EMAIL_BODY: "Salut %s, bine ai venit la %s!" + NOTIFICATION_EMAIL_SUBJECT: "Utilizator nou pe %s" + NOTIFICATION_EMAIL_BODY: "Salut, un nou utilizator s-a înregistrat pe %s. Nume de utilizator: %s, adresă de email: %s" + ACTIVATION_LINK_EXPIRED: "Link-ul pentru activare este expirat" + USER_ACTIVATED_SUCCESSFULLY: "Utilizator activat cu succes" + INVALID_REQUEST: "Cerere invalidă " + USER_REGISTRATION: "Înregistrare utilizator " + USER_REGISTRATION_ENABLED_HELP: "Activați înregistrarea utilizatorilor" + VALIDATE_PASSWORD1_AND_PASSWORD2: "Validați parola introdusă de două ori" + VALIDATE_PASSWORD1_AND_PASSWORD2_HELP: "Validați și comparați cele două câmpuri pentru parolă cu numele `password1` și `password2`. Activați această opțiune dacă există două câmpuri pentru parolă în formularul de înregistrare." + SET_USER_DISABLED: "Setați utilizatorul dezactivat" + SET_USER_DISABLED_HELP: "Cel mai bine să fie folosit împreună cu email-ul pentru activare. Adaugă utilizatorul în Grav dar îl setează ca dezactivat" + LOGIN_AFTER_REGISTRATION: "Loghează utilizatorul după înregistrare" + LOGIN_AFTER_REGISTRATION_HELP: "Imediat după înregistrare loghează utilizatorul în cont. Dacă este necesară activarea prin email, utilizatorul va fi logat imediat după activarea contului." + SEND_ACTIVATION_EMAIL: "Trimite email-ul pentru activare" + SEND_ACTIVATION_EMAIL_HELP: "Trimite un email către utilizatorul nou înregistrat pentru activarea contului. Activați opțiunea `Setați utilizatorul dezactivat` când folosiți această opțiune pentru a seta utilizatorul dezactivat și pentru a trimite un email automat pentru activarea contului. " + SEND_NOTIFICATION_EMAIL: "Trimite email cu notificare" + SEND_NOTIFICATION_EMAIL_HELP: "Notifică adminstratorul site-ului când un utilizator nou s-a înregistrat. Email-ul va di trimis către adresa `Către` din configurarea modului de Email" + SEND_WELCOME_EMAIL: "Trimite email de bun venit" + SEND_WELCOME_EMAIL_HELP: "Trimite un email către utilizatorul nou înregistrat." + DEFAULT_VALUES: " Valori implicite" + DEFAULT_VALUES_HELP: "Listă de câmpuri și valorile asociate acestora ce vor fi adăugate profilului utilizatorului (în fișierul yaml) în mod implicit fără a putea fi configurabile de către utilizator. Separați valorile multiple cu virgulă, fără spații între valori." + ADDITIONAL_PARAM_KEY: "Parametru" + ADDITIONAL_PARAM_VALUE: "Valoare" + REGISTRATION_FIELDS: "Câmpuri pentru înregistrare" + REGISTRATION_FIELDS_HELP: "Adaugă câmpurile ce vor fi adăugate fișierului yaml al utilizatorului. Câmpurile care nu sunt listate aici nu vor fi adăugate chiar dacă sunt prezente în formularul de înregistrare." + REGISTRATION_FIELD_KEY: " Numele câmpului" + REDIRECT_AFTER_LOGIN: "Redirecționează după logare" + REDIRECT_AFTER_LOGIN_HELP: "Ruta personalizată pentru redirecționare după logare" + REDIRECT_AFTER_REGISTRATION: "Redirecționează după înregistrare" + REDIRECT_AFTER_REGISTRATION_HELP: "Ruta personalizată pentru redirecționare după înregistrare" + OPTIONS: 'Opțiuni' + EMAIL_VALIDATION_MESSAGE: "Trebuie să fie o adresă de email validă" + PASSWORD_VALIDATION_MESSAGE: "Parola trebuie să conțină cel puțin un număr și o literă de tipar și să aibă cel puțin 8 caractere" + TIMEOUT_HELP: "Setează pauza pentru sesiune când este activat 'Ține-mă minte' de către utilizator. Minimul este de 604800 care înseamnă 1 săptămână." + GROUPS_HELP: "Lista grupurilor din care utilizatorii nou înregistrați vor face parte, dacă este necesar" + SITE_ACCESS_HELP: "Lista cu niveluri de acces la care utilizatorul nou înregistrat are acces. De eg: `login` -> `true` " + WELCOME: "Bine ați venit" + REDIRECT_AFTER_ACTIVATION: "Redirecționează după activarea utilizatorului" + REDIRECT_AFTER_ACTIVATION_HELP: "Folosită dacă utilizatorul trebuie să-și activeze contul prin email. Odată activat contul va fi folosită această rută." + REGISTRATION_DISABLED: " Dezactivează înregistrarea " + USE_PARENT_ACL_LABEL: "Folosește regulile de acces ale părintelui" + USE_PARENT_ACL_HELP: "Verifică regulie de acces ale părintelui dacă nu sunt specificate alte reguli de acces" + PROTECT_PROTECTED_PAGE_MEDIA_LABEL: " Protejează media ce aparține paginii de logare " + PROTECT_PROTECTED_PAGE_MEDIA_HELP: "Dacă este activată, media ce aparține unei pagini de logare este protejată și nu poate fi accesată decât după logare." diff --git a/sandbox/grav/user/plugins/login/login.php b/sandbox/grav/user/plugins/login/login.php new file mode 100644 index 0000000000..febc2f079a --- /dev/null +++ b/sandbox/grav/user/plugins/login/login.php @@ -0,0 +1,774 @@ + [['initializeSession', 10000], ['initializeLogin', 1000]], + 'onTask.login.login' => ['loginController', 0], + 'onTask.login.forgot' => ['loginController', 0], + 'onTask.login.logout' => ['loginController', 0], + 'onTask.login.reset' => ['loginController', 0], + 'onPagesInitialized' => ['storeReferrerPage', 0], + 'onPageInitialized' => ['authorizePage', 0], + 'onPageFallBackUrl' => ['authorizeFallBackUrl', 0], + 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0], + 'onTwigSiteVariables' => ['onTwigSiteVariables', -100000], + 'onFormProcessed' => ['onFormProcessed', 0] + ]; + } + + /** + * Initialize login plugin if path matches. + */ + public function initializeSession() + { + /** @var Config $config */ + $config = $this->grav['config']; + + // Check to ensure sessions are enabled. + if (!$config->get('system.session.enabled')) { + throw new \RuntimeException('The Login plugin requires "system.session" to be enabled'); + } + + // Autoload classes + $autoload = __DIR__ . '/vendor/autoload.php'; + if (!is_file($autoload)) { + throw new \Exception('Login Plugin failed to load. Composer dependencies not met.'); + } + require_once $autoload; + + // Define current user service. + $this->grav['user'] = function ($c) { + /** @var Grav $c */ + + $session = $c['session']; + + if (!isset($session->user)) { + $session->user = new User; + + if ($c['config']->get('plugins.login.rememberme.enabled')) { + $controller = new Controller($c, ''); + $rememberMe = $controller->rememberMe(); + + // If we can present the correct tokens from the cookie, we are logged in + $username = $rememberMe->login(); + if ($username) { + // Normal login process + $user = User::load($username); + if ($user->exists()) { + // There is a chance that an attacker has stolen + // the login token, so we store the fact that + // the user was logged in via RememberMe + // (instead of login form) + $session->remember_me = $rememberMe; + $session->user = $user; + } + } + + // Check if the token was invalid + if ($rememberMe->loginTokenWasInvalid()) { + $controller->setMessage($c['language']->translate('PLUGIN_LOGIN.REMEMBER_ME_STOLEN_COOKIE')); + } + } + } + + return $session->user; + }; + } + + /** + * Initialize login plugin if path matches. + */ + public function initializeLogin() + { + /** @var Uri $uri */ + $uri = $this->grav['uri']; + + //Initialize Login Object + $this->login = new Login($this->grav); + + //Store Login Object in Grav + $this->grav['login'] = $this->login; + + // Admin has its own login; make sure we're not in admin. + if (!isset($this->grav['admin'])) { + $this->route = $this->config->get('plugins.login.route'); + } + + $path = $uri->path(); + $this->redirect_to_login = $this->config->get('plugins.login.redirect_to_login'); + + // Register route to login page if it has been set. + if ($this->route && $this->route === $path) { + $this->enable([ + 'onPagesInitialized' => ['addLoginPage', 0], + ]); + return; + } + + if ($path === $this->config->get('plugins.login.route_forgot')) { + $this->enable([ + 'onPagesInitialized' => ['addForgotPage', 0], + ]); + return; + } + + if ($path === $this->config->get('plugins.login.route_reset')) { + $this->enable([ + 'onPagesInitialized' => ['addResetPage', 0], + ]); + return; + } + + if ($path === $this->config->get('plugins.login.route_register')) { + if ($this->config->get('plugins.login.user_registration.enabled')) { + $this->enable([ + 'onPagesInitialized' => ['addRegisterPage', 0], + ]); + } else { + throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.REGISTRATION_DISABLED'), 404); + } + return; + } + + if ($path === $this->config->get('plugins.login.route_activate')) { + $this->enable([ + 'onPagesInitialized' => ['handleUserActivation', 0], + ]); + return; + } + + if ($path === $this->config->get('plugins.login.route_profile')) { + $this->enable([ + 'onPagesInitialized' => ['addProfilePage', 0], + ]); + return; + } + } + + public function storeReferrerPage() + { + $invalid_redirect_routes = [ + $this->config->get('plugins.login.route') ?: '/login', + $this->config->get('plugins.login.route_register') ?: '/register', + $this->config->get('plugins.login.route_activate') ?: '/activate_user', + $this->config->get('plugins.login.route_forgot') ?: '/forgot_password', + $this->config->get('plugins.login.route_reset') ?: '/reset_password', + ]; + $current_route = $this->grav['uri']->route(); + + + if (!in_array($current_route, $invalid_redirect_routes)) { + + $allowed = true; + + /** @var Page $page */ + $page = $this->grav['pages']->dispatch($current_route); + + if ($page) { + $header = $page->header(); + if (isset($header->login_redirect_here) && $header->login_redirect_here === false) { + $allowed = false; + } + + if ($allowed && $page->routable()) { + $this->grav['session']->redirect_after_login = $page->route() . $this->grav['uri']->params() ?: ''; + } + } + + } + } + + /** + * Add Login page + */ + public function addLoginPage() + { + /** @var Pages $pages */ + $pages = $this->grav['pages']; + $page = $pages->dispatch($this->route); + + if (!$page) { + // Only add login page if it hasn't already been defined. + $page = new Page; + $page->init(new \SplFileInfo(__DIR__ . "/pages/login.md")); + $page->slug(basename($this->route)); + + $pages->addPage($page, $this->route); + } + } + + /** + * Add Login page + */ + public function addForgotPage() + { + $route = $this->config->get('plugins.login.route_forgot'); + /** @var Pages $pages */ + $pages = $this->grav['pages']; + $page = $pages->dispatch($route); + + if (!$page) { + // Only add forgot page if it hasn't already been defined. + $page = new Page; + $page->init(new \SplFileInfo(__DIR__ . "/pages/forgot.md")); + $page->slug(basename($route)); + + $pages->addPage($page, $route); + } + } + + /** + * Add Reset page + */ + public function addResetPage() + { + $route = $this->config->get('plugins.login.route_reset'); + + $uri = $this->grav['uri']; + $token = $uri->param('token'); + $user = $uri->param('user'); + + if (!$user || !$token) { + return; + } + + /** @var Pages $pages */ + $pages = $this->grav['pages']; + $page = $pages->dispatch($route); + + if (!$page) { + // Only add login page if it hasn't already been defined. + $page = new Page; + $page->init(new \SplFileInfo(__DIR__ . "/pages/reset.md")); + $page->slug(basename($route)); + + $pages->addPage($page, $route); + } + } + + /** + * Add Register page + */ + public function addRegisterPage() + { + $route = $this->config->get('plugins.login.route_register'); + + /** @var Pages $pages */ + $pages = $this->grav['pages']; + $page = $pages->dispatch($route); + + if (!$page) { + $page = new Page; + $page->init(new \SplFileInfo(__DIR__ . "/pages/register.md")); + $page->template('form'); + $page->slug(basename($route)); + + $pages->addPage($page, $route); + } + } + + /** + * Handle user activation + */ + public function handleUserActivation() + { + /** @var Uri $uri */ + $uri = $this->grav['uri']; + + /** @var Message $messages */ + $messages = $this->grav['messages']; + + $username = $uri->param('username'); + + $nonce = $uri->param('nonce'); + if ($nonce === null || !Utils::verifyNonce($nonce, 'user-activation')) { + $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST'); + $messages->add($message, 'error'); + $this->grav->redirect('/'); + + return; + } + + $token = $uri->param('token'); + $user = User::load($username); + + if (!$user->activation_token) { + $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST'); + $messages->add($message, 'error'); + } else { + list($good_token, $expire) = explode('::', $user->activation_token); + + if ($good_token === $token) { + if (time() > $expire) { + $message = $this->grav['language']->translate('PLUGIN_LOGIN.ACTIVATION_LINK_EXPIRED'); + $messages->add($message, 'error'); + } else { + $user['state'] = 'enabled'; + $user->save(); + $message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY'); + $messages->add($message, 'info'); + + if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) { + $this->login->sendWelcomeEmail($user); + } + if ($this->config->get('plugins.login.user_registration.options.send_notification_email', false)) { + $this->login->sendNotificationEmail($user); + } + + if ($this->config->get('plugins.login.user_registration.options.login_after_registration', false)) { + //Login user + $this->grav['session']->user = $user; + unset($this->grav['user']); + $this->grav['user'] = $user; + $user->authenticated = true; + $user->authorized = $user->authorize('site.login'); + } + } + } else { + $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST'); + $messages->add($message, 'error'); + } + } + + $redirect_route = $this->config->get('plugins.login.user_registration.redirect_after_activation', '/'); + $this->grav->redirect($redirect_route); + } + + /** + * Add Profile page + */ + public function addProfilePage() + { + $route = $this->config->get('plugins.login.route_profile'); + /** @var Pages $pages */ + $pages = $this->grav['pages']; + $page = $pages->dispatch($route); + + if (!$page) { + // Only add forgot page if it hasn't already been defined. + $page = new Page; + $page->init(new \SplFileInfo(__DIR__ . "/pages/profile.md")); + $page->slug(basename($route)); + + $pages->addPage($page, $route); + } + + $this->storeReferrerPage(); + } + + /** + * Set Unauthorized page + * @throws \Exception + */ + public function setUnauthorizedPage() + { + $route = $this->config->get('plugins.login.route_unauthorized'); + + /** @var Pages $pages */ + $pages = $this->grav['pages']; + $page = $pages->dispatch($route); + + if (!$page) { + $page = new Page; + $page->init(new \SplFileInfo(__DIR__ . '/pages/unauthorized.md')); + $page->template('default'); + $page->slug(basename($route)); + + $pages->addPage($page, $route); + } + + unset($this->grav['page']); + $this->grav['page'] = $page; + } + + /** + * Initialize login controller + */ + public function loginController() + { + /** @var Uri $uri */ + $uri = $this->grav['uri']; + $task = !empty($_POST['task']) ? $_POST['task'] : $uri->param('task'); + $task = substr($task, strlen('login.')); + $post = !empty($_POST) ? $_POST : []; + + switch ($task) { + case 'login': + if (!isset($post['login-form-nonce']) || !Utils::verifyNonce($post['login-form-nonce'], 'login-form')) { + $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.ACCESS_DENIED'), + 'info'); + $this->authorized = false; + $twig = $this->grav['twig']; + $twig->twig_vars['notAuthorized'] = true; + + return; + } + break; + + case 'forgot': + if (!isset($post['forgot-form-nonce']) || !Utils::verifyNonce($post['forgot-form-nonce'], 'forgot-form')) { + $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.ACCESS_DENIED'),'info'); + return; + } + break; + } + + $controller = new Controller($this->grav, $task, $post); + $controller->execute(); + $controller->redirect(); + } + + /** + * Authorize the Page fallback url (page media accessed through the page route) + */ + public function authorizeFallBackUrl() + { + if ($this->config->get('plugins.login.protect_protected_page_media', false)) { + $page_url = dirname($this->grav['uri']->path()); + $page = $this->grav['pages']->find($page_url); + $this->grav['page'] = $page; + $this->authorizePage(); + } + } + + /** + * Authorize Page + */ + public function authorizePage() + { + /** @var User $user */ + $user = $this->grav['user']; + if (!$user->get('access')) { + $user = User::load($user->get('username')); + } + + /** @var Page $page */ + $page = $this->grav['page']; + + if (!$page) { + return; + } + + $header = $page->header(); + $rules = isset($header->access) ? (array)$header->access : []; + + $config = $this->mergeConfig($page); + + if ($config->get('parent_acl')) { + // If page has no ACL rules, use its parent's rules + if (!$rules) { + $parent = $page->parent(); + while (!$rules and $parent) { + $header = $parent->header(); + $rules = isset($header->access) ? (array)$header->access : []; + $parent = $parent->parent(); + } + } + } + + // Continue to the page if it has no ACL rules. + if (!$rules) { + return; + } + + // Continue to the page if user is authorized to access the page. + foreach ($rules as $rule => $value) { + if (is_array($value)) { + foreach ($value as $nested_rule => $nested_value) { + if ($user->authorize($rule . '.' . $nested_rule) == $nested_value) { + return; + } + } + } else { + if ($user->authorize($rule) == $value) { + return; + } + } + } + + // User is not logged in; redirect to login page. + if ($this->redirect_to_login && $this->route && !$user->authenticated) { + $this->grav->redirect($this->route, 302); + } + + /** @var Language $l */ + $l = $this->grav['language']; + + /** @var Twig $twig */ + $twig = $this->grav['twig']; + + // Reset page with login page. + if (!$user->authenticated) { + + if ($this->route) { + $page = $this->grav['pages']->dispatch($this->route); + } else { + $page = new Page; + // $this->grav['session']->redirect_after_login = $this->grav['uri']->path() . ($this->grav['uri']->params() ?: ''); + + // Get the admin Login page is needed, else teh default + if ($this->isAdmin()) { + $login_file = $this->grav['locator']->findResource("plugins://admin/pages/admin/login.md"); + $page->init(new \SplFileInfo($login_file)); + } else { + $page->init(new \SplFileInfo(__DIR__ . "/pages/login.md")); + } + + $page->slug(basename($this->route)); + } + + $this->authenticated = false; + unset($this->grav['page']); + $this->grav['page'] = $page; + + $twig->twig_vars['form'] = new Form($page); + } else { + $this->grav['messages']->add($l->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error'); + $this->authorized = false; + $twig->twig_vars['notAuthorized'] = true; + + $this->setUnauthorizedPage(); + } + } + + + /** + * Add twig paths to plugin templates. + */ + public function onTwigTemplatePaths() + { + $twig = $this->grav['twig']; + $twig->twig_paths[] = __DIR__ . '/templates'; + } + + /** + * Set all twig variables for generating output. + */ + public function onTwigSiteVariables() + { + /** @var Twig $twig */ + $twig = $this->grav['twig']; + + $this->grav->fireEvent('onLoginPage'); + + $extension = $this->grav['uri']->extension(); + $extension = $extension ?: 'html'; + + if (!$this->authenticated) { + $twig->template = "login." . $extension . ".twig"; + } + + // add CSS for frontend if required + if (!$this->isAdmin() && $this->config->get('plugins.login.built_in_css')) { + $this->grav['assets']->add('plugin://login/css/login.css'); + } + + $task = $this->grav['uri']->param('task'); + $task = substr($task, strlen('login.')); + if ($task === 'reset') { + $username = $this->grav['uri']->param('user'); + $token = $this->grav['uri']->param('token'); + + if (!empty($username) && !empty($token)) { + $twig->twig_vars['username'] = $username; + $twig->twig_vars['token'] = $token; + } + + } + } + + /** + * Process the user registration, triggered by a registration form + * + * @param Form $form + */ + private function processUserRegistration($form, Event $event) + { + if (!$this->config->get('plugins.login.enabled')) { + throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED')); + } + + if (!$this->config->get('plugins.login.user_registration.enabled')) { + throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.USER_REGISTRATION_DISABLED')); + } + + $data = []; + $username = $form->value('username'); + $data['username'] = $username; + + if (file_exists($this->grav['locator']->findResource('account://' . $username . YAML_EXT))) { + $this->grav->fireEvent('onFormValidationError', new Event([ + 'form' => $form, + 'message' => $this->grav['language']->translate([ + 'PLUGIN_LOGIN.USERNAME_NOT_AVAILABLE', + $username + ]) + ])); + $event->stopPropagation(); + + return; + } + + if ($this->config->get('plugins.login.user_registration.options.validate_password1_and_password2', + false) + ) { + if ($form->value('password1') !== $form->value('password2')) { + $this->grav->fireEvent('onFormValidationError', new Event([ + 'form' => $form, + 'message' => $this->grav['language']->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH') + ])); + $event->stopPropagation(); + + return; + } + $data['password'] = $form->value('password1'); + } + + $fields = $this->config->get('plugins.login.user_registration.fields', []); + + foreach ($fields as $field) { + // Process value of field if set in the page process.register_user + $default_values = $this->config->get('plugins.login.user_registration.default_values'); + if ($default_values) { + foreach ($default_values as $key => $param) { + $values = explode(',', $param); + + if ($key == $field) { + $data[$field] = $values; + } + } + } + + if (!isset($data[$field]) && $form->value($field)) { + $data[$field] = $form->value($field); + } + } + + if ($this->config->get('plugins.login.user_registration.options.validate_password1_and_password2', + false) + ) { + unset($data['password1']); + unset($data['password2']); + } + + if ($this->config->get('plugins.login.user_registration.options.set_user_disabled', false)) { + $data['state'] = 'disabled'; + } else { + $data['state'] = 'enabled'; + } + + $user = $this->login->register($data); + + if ($this->config->get('plugins.login.user_registration.options.send_activation_email', false)) { + $this->login->sendActivationEmail($user); + } else { + if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) { + $this->login->sendWelcomeEmail($user); + } + if ($this->config->get('plugins.login.user_registration.options.send_notification_email', false)) { + $this->login->sendNotificationEmail($user); + } + } + + $redirect = $this->config->get('plugins.login.user_registration.redirect_after_registration', false); + if ($redirect) { + $this->grav->redirect($redirect); + } + } + + /** + * Save user profile information + * + * @param Form $form + * @param Event $event + * @return bool + */ + private function processUserProfile($form, Event $event) + { + $user = $this->grav['user']; + $user->merge($form->getData()->toArray()); + + try { + $user->save(); + } catch (\Exception $e) { + $form->setMessage($e->getMessage(), 'error'); + return false; + } + + return true; + } + + /** + * Process a registration form. Handles the following actions: + * + * - register_user: registers a user + * + * @param Event $event + */ + public function onFormProcessed(Event $event) + { + $form = $event['form']; + $action = $event['action']; + + switch ($action) { + case 'register_user': + $this->processUserRegistration($form, $event); + break; + case 'update_user': + $this->processUserProfile($form, $event); + break; + } + } +} diff --git a/sandbox/grav/user/plugins/login/login.yaml b/sandbox/grav/user/plugins/login/login.yaml new file mode 100644 index 0000000000..98593d3253 --- /dev/null +++ b/sandbox/grav/user/plugins/login/login.yaml @@ -0,0 +1,45 @@ +enabled: true +built_in_css: true +route: +redirect_to_login: true +redirect_after_login: +route_activate: '/activate_user' +route_forgot: '/forgot_password' +route_reset: '/reset_password' +route_profile: '/user_profile' +route_register: '/user_register' +route_unauthorized: '/user_unauthorized' +parent_acl: false +protect_protected_page_media: false + +user_registration: + enabled: true + + fields: + - 'username' + - 'password' + - 'email' + - 'fullname' + - 'title' + + access: + site: + login: 'true' + + options: + validate_password1_and_password2: true + set_user_disabled: false + login_after_registration: true + send_activation_email: false + send_notification_email: false + send_welcome_email: false + +rememberme: + enabled: true + timeout: 604800 # Timeout in seconds. Defaults to 1 week + name: grav-rememberme # Name prefix of the session cookie + +max_pw_resets_count: 0 +max_pw_resets_interval: 60 +max_login_count: 0 +max_login_interval: 2 \ No newline at end of file diff --git a/sandbox/grav/user/plugins/login/pages/forgot.md b/sandbox/grav/user/plugins/login/pages/forgot.md new file mode 100644 index 0000000000..6f46df640c --- /dev/null +++ b/sandbox/grav/user/plugins/login/pages/forgot.md @@ -0,0 +1,21 @@ +--- +title: Forgot password + +login_redirect_here: false + +form: + + fields: + - name: email + type: email + label: PLUGIN_LOGIN.EMAIL + autofocus: true + validate: + required: true + type: email +--- + + +# Recover your password + +Enter your email to recover your password diff --git a/sandbox/grav/user/plugins/login/pages/login.md b/sandbox/grav/user/plugins/login/pages/login.md new file mode 100644 index 0000000000..648dc0340b --- /dev/null +++ b/sandbox/grav/user/plugins/login/pages/login.md @@ -0,0 +1,28 @@ +--- +title: Login + +login_redirect_here: false + +form: + name: login + action: + method: post + + fields: + - name: username + type: text + id: username + placeholder: Username + label: PLUGIN_LOGIN.USERNAME_EMAIL + autofocus: true + + - name: password + type: password + id: password + placeholder: Password + label: PLUGIN_LOGIN.PASSWORD +--- + +# User Login + +This page is restricted... diff --git a/sandbox/grav/user/plugins/login/pages/profile.md b/sandbox/grav/user/plugins/login/pages/profile.md new file mode 100644 index 0000000000..b3e59baed1 --- /dev/null +++ b/sandbox/grav/user/plugins/login/pages/profile.md @@ -0,0 +1,50 @@ +--- +title: Profile +access: + site.login: true + +form: + fields: + avatar_img: + type: avatar + + username: + type: text + readonly: true + disabled: true + + email: + type: email + placeholder: "Enter your email" + validate: + required: true + message: PLUGIN_LOGIN.EMAIL_VALIDATION_MESSAGE + + fullname: + type: text + + title: + type: text + + password: + type: password + label: Enter new password + validate: + message: PLUGIN_LOGIN.PASSWORD_VALIDATION_MESSAGE + config-pattern@: system.pwd_regex + + + buttons: + - + type: submit + value: Submit + - + type: reset + value: Reset + + process: + update_user: true + message: "Your profile has been updated" +--- + +# Profile \ No newline at end of file diff --git a/sandbox/grav/user/plugins/login/pages/register.md b/sandbox/grav/user/plugins/login/pages/register.md new file mode 100644 index 0000000000..5988d3c069 --- /dev/null +++ b/sandbox/grav/user/plugins/login/pages/register.md @@ -0,0 +1,59 @@ +--- +login_redirect_here: false + +form: + + fields: + - + name: username + type: text + id: username + placeholder: "Choose a username" + validate: + required: true + message: PLUGIN_LOGIN.USERNAME_NOT_VALID + config-pattern@: system.username_regex + + - + name: email + type: email + id: email + placeholder: "Enter your email" + validate: + required: true + message: PLUGIN_LOGIN.EMAIL_VALIDATION_MESSAGE + + - + name: password1 + type: password + id: password1 + label: Enter a password + validate: + required: true + message: PLUGIN_LOGIN.PASSWORD_VALIDATION_MESSAGE + config-pattern@: system.pwd_regex + + - + name: password2 + type: password + id: password2 + label: Enter the password again + validate: + required: true + message: PLUGIN_LOGIN.PASSWORD_VALIDATION_MESSAGE + config-pattern@: system.pwd_regex + + buttons: + - + type: submit + value: Submit + - + type: reset + value: Reset + + process: + register_user: true + message: "You are logged in" +--- + +# Register diff --git a/sandbox/grav/user/plugins/login/pages/reset.md b/sandbox/grav/user/plugins/login/pages/reset.md new file mode 100644 index 0000000000..1c3048a4a1 --- /dev/null +++ b/sandbox/grav/user/plugins/login/pages/reset.md @@ -0,0 +1,35 @@ +--- +title: Reset password + +login_redirect_here: false + +form: + + fields: + - name: username + type: hidden + id: username + placeholder: Username + readonly: true + + - name: password + type: password + id: password + placeholder: Password + autofocus: true + validate: + required: true + message: PLUGIN_LOGIN.PASSWORD_VALIDATION_MESSAGE + config-pattern@: system.pwd_regex + + - name: token + type: hidden + +process: + twig: true +--- + +# Password Reset + +### Username: {{uri.param('user')}} + diff --git a/sandbox/grav/user/plugins/login/pages/unauthorized.md b/sandbox/grav/user/plugins/login/pages/unauthorized.md new file mode 100644 index 0000000000..6b9aa13d95 --- /dev/null +++ b/sandbox/grav/user/plugins/login/pages/unauthorized.md @@ -0,0 +1,5 @@ +--- +title: Unauthorized +--- + +# You don't have access to this page... \ No newline at end of file diff --git a/sandbox/grav/user/plugins/login/templates/forgot.html.twig b/sandbox/grav/user/plugins/login/templates/forgot.html.twig new file mode 100644 index 0000000000..b5b6e19379 --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/forgot.html.twig @@ -0,0 +1,5 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} + {% include 'partials/forgot-form.html.twig' %} +{% endblock %} diff --git a/sandbox/grav/user/plugins/login/templates/login.html.twig b/sandbox/grav/user/plugins/login/templates/login.html.twig new file mode 100644 index 0000000000..9c69bcf934 --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/login.html.twig @@ -0,0 +1,5 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} + {% include 'partials/login-form.html.twig' %} +{% endblock %} diff --git a/sandbox/grav/user/plugins/login/templates/login.json.twig b/sandbox/grav/user/plugins/login/templates/login.json.twig new file mode 100644 index 0000000000..05cb2e45ed --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/login.json.twig @@ -0,0 +1,5 @@ +{%- if not grav.user.authenticated -%} +{"code":401,"status":"unauthenticated","message":"Authentication required","login":{{ include('partials/login-form.html.twig')|trim|json_encode }}} +{%- else -%} +{"code":200,"status":"authenticated","message":"You have been authenticated"} +{%- endif -%} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/login/templates/partials/forgot-form.html.twig b/sandbox/grav/user/plugins/login/templates/partials/forgot-form.html.twig new file mode 100644 index 0000000000..408fb5853e --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/partials/forgot-form.html.twig @@ -0,0 +1,20 @@ +
    + {{ content|raw }} + + {% include 'partials/messages.html.twig' %} + +
    + {% for field in form.fields %} + {% if field.type %} +
    + {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} +
    + {% endif %} + {% endfor %} +
    + +
    + + {{ nonce_field('forgot-form', 'forgot-form-nonce')|raw }} +
    +
    diff --git a/sandbox/grav/user/plugins/login/templates/partials/login-form.html.twig b/sandbox/grav/user/plugins/login/templates/partials/login-form.html.twig new file mode 100644 index 0000000000..a6094d2c56 --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/partials/login-form.html.twig @@ -0,0 +1,53 @@ +
    + {% include 'partials/messages.html.twig' %} + + {% if page.template == 'login' or show_login_form %} + + {% if grav.user.authenticated %} +

    {{ 'PLUGIN_LOGIN.WELCOME'|t }} {{ grav.user.fullname ?: grav.user.username }}

    +
    + {{ 'PLUGIN_LOGIN.BTN_LOGOUT'|t }} + + {% else %} + {{ content|raw }} + +
    + {% if grav.twig.plugins_hooked_loginPage %} + {% for label in grav.twig.plugins_hooked_loginPage %} + {% include label %} + {% endfor %} + {% endif %} + + {% for field in form.fields %} + {% if field.type %} +
    + {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} +
    + {% endif %} + {% endfor %} + +
    + {% if config.plugins.login.rememberme.enabled and page.header.form.login.rememberme ?? true %} +
    +
    + + +
    +
    + {% endif %} + + {% if page.header.form.login.forgot_button ?? true %} + {{ 'PLUGIN_LOGIN.BTN_FORGOT'|t }} + {% endif %} + + +
    + + {{ nonce_field('login-form', 'login-form-nonce')|raw }} +
    + + {% endif %} + + {% endif %} + +
    diff --git a/sandbox/grav/user/plugins/login/templates/partials/login-status.html.twig b/sandbox/grav/user/plugins/login/templates/partials/login-status.html.twig new file mode 100644 index 0000000000..8425439359 --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/partials/login-status.html.twig @@ -0,0 +1,5 @@ + diff --git a/sandbox/grav/user/plugins/login/templates/partials/messages.html.twig b/sandbox/grav/user/plugins/login/templates/partials/messages.html.twig new file mode 100644 index 0000000000..7727bf74a9 --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/partials/messages.html.twig @@ -0,0 +1,3 @@ +{% for message in grav.messages.fetch %} +
    {{ message.message|raw }}
    +{% endfor %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/login/templates/partials/reset-form.html.twig b/sandbox/grav/user/plugins/login/templates/partials/reset-form.html.twig new file mode 100644 index 0000000000..031b21b309 --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/partials/reset-form.html.twig @@ -0,0 +1,26 @@ +{% if uri.param('token') and uri.param('task') %} +
    + {{ content|raw }} + + {% include 'partials/messages.html.twig' %} + +
    + {% for field in form.fields %} + {% set value = attribute(grav.twig.twig_vars, field.name) is defined ? attribute(grav.twig.twig_vars, field.name) : null %} + + {% if field.type %} +
    + {% include ["forms/fields/#{field.type}/#{field.type}.html.twig", 'forms/fields/text/text.html.twig'] %} +
    + {% endif %} + {% endfor %} +
    + +
    + + {{ nonce_field('reset-form', 'reset-form-nonce')|raw }} +
    +
    +{% endif %} + + diff --git a/sandbox/grav/user/plugins/login/templates/profile.html.twig b/sandbox/grav/user/plugins/login/templates/profile.html.twig new file mode 100644 index 0000000000..dbfa868ef5 --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/profile.html.twig @@ -0,0 +1,8 @@ +{% extends 'partials/base.html.twig' %} + +{% do form.setAllData(grav.user.toArray) %} + +{% block content %} + {{ page.content }} + {% include 'forms/form.html.twig' %} +{% endblock %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/login/templates/profile.json.twig b/sandbox/grav/user/plugins/login/templates/profile.json.twig new file mode 100644 index 0000000000..da0d0233b1 --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/profile.json.twig @@ -0,0 +1 @@ +{% extends 'forms/ajax.json.twig' %} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/login/templates/reset.html.twig b/sandbox/grav/user/plugins/login/templates/reset.html.twig new file mode 100644 index 0000000000..e16235656d --- /dev/null +++ b/sandbox/grav/user/plugins/login/templates/reset.html.twig @@ -0,0 +1,7 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} + {% include 'partials/reset-form.html.twig' %} +{% endblock %} + + diff --git a/sandbox/grav/user/plugins/markdown-notices/CHANGELOG.md b/sandbox/grav/user/plugins/markdown-notices/CHANGELOG.md new file mode 100644 index 0000000000..b3b685c0b5 --- /dev/null +++ b/sandbox/grav/user/plugins/markdown-notices/CHANGELOG.md @@ -0,0 +1,5 @@ +# v1.0.0 +## 12/22/2015 + +1. [](#new) + * ChangeLog started... diff --git a/sandbox/grav/user/plugins/markdown-notices/LICENSE b/sandbox/grav/user/plugins/markdown-notices/LICENSE new file mode 100644 index 0000000000..4bb709282c --- /dev/null +++ b/sandbox/grav/user/plugins/markdown-notices/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Grav + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sandbox/grav/user/plugins/markdown-notices/README.md b/sandbox/grav/user/plugins/markdown-notices/README.md new file mode 100644 index 0000000000..397d6db40c --- /dev/null +++ b/sandbox/grav/user/plugins/markdown-notices/README.md @@ -0,0 +1,61 @@ +# Grav Markdown Notices Plugin + +The **markdown-notices plugin** for [Grav](http://github.com/getgrav/grav) allows generation of notice blocks of text via markdown: + +![](assets/screenshot.png) + +# Installation + +This plugin is easy to install with GPM. + +``` +$ bin/gpm install markdown-notices +``` + +# Configuration + +Simply copy the `user/plugins/markdown-notices/markdown-notices.yaml` into `user/config/plugins/markdown-notices.yaml` and make your modifications. + +``` +enabled: true +built_in_css: true +level_classes: [yellow, red, blue, green] +``` + +# Examples + +Using one level of `!` + +``` +! Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris feugiat quam erat, ut iaculis diam posuere nec. +! Vestibulum eu condimentum urna. Vestibulum feugiat odio ut sodales porta. Donec sit amet ante mi. Donec lobortis +! orci dolor. Donec tristique volutpat ultricies. Nullam tempus, enim sit amet fringilla facilisis, ipsum ex +! tincidunt ipsum, vel placerat sem sem vitae risus. Aenean posuere sed purus nec pretium. +``` + +You will output the following HTML + +``` +
    +

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris feugiat quam erat, ut iaculis diam posuere nec. + Vestibulum eu condimentum urna. Vestibulum feugiat odio ut sodales porta. Donec sit amet ante mi. Donec lobortis + orci dolor. Donec tristique volutpat ultricies. Nullam tempus, enim sit amet fringilla facilisis, ipsum ex + tincidunt ipsum, vel placerat sem sem vitae risus. Aenean posuere sed purus nec pretium. +

    +
    +``` + +The `yellow` class is determined by the `level_classes` in the configuration. You can customize this as you need. + +``` +!! Lorem ipsum dolor sit amet, **consectetur adipiscing** elit. Mauris feugiat quam erat, ut iaculis diam posuere nec. +!! +!! * List item a +!! * List item b +!! +!! orci dolor. Donec tristique volutpat ultricies. Nullam tempus, enim sit amet fringilla facilisis, ipsum ex +!! tincidunt ipsum, vel placerat sem sem vitae risus. Aenean posuere sed purus nec pretium. +``` + +Two levels of `!!` will use the second level class etc. You can also use complex markdown inside the notices. diff --git a/sandbox/grav/user/plugins/markdown-notices/assets/notices.css b/sandbox/grav/user/plugins/markdown-notices/assets/notices.css new file mode 100644 index 0000000000..93c3a856dd --- /dev/null +++ b/sandbox/grav/user/plugins/markdown-notices/assets/notices.css @@ -0,0 +1,32 @@ +.notices { + padding: 1px 1px 1px 30px; + margin: 15px 0; +} + +.notices p { + +} + +.notices.yellow { + border-left: 10px solid #f0ad4e; + background: #fcf8f2; + color: #df8a13; +} + +.notices.red { + border-left: 10px solid #d9534f; + background: #fdf7f7; + color: #b52b27; +} + +.notices.blue { + border-left: 10px solid #5bc0de; + background: #f4f8fa; + color: #28a1c5; +} + +.notices.green { + border-left: 10px solid #5cb85c; + background: #f1f9f1; + color: #3d8b3d; +} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/markdown-notices/assets/screenshot.png b/sandbox/grav/user/plugins/markdown-notices/assets/screenshot.png new file mode 100644 index 0000000000..cf27677a97 Binary files /dev/null and b/sandbox/grav/user/plugins/markdown-notices/assets/screenshot.png differ diff --git a/sandbox/grav/user/plugins/markdown-notices/blueprints.yaml b/sandbox/grav/user/plugins/markdown-notices/blueprints.yaml new file mode 100644 index 0000000000..2d3d49bb9c --- /dev/null +++ b/sandbox/grav/user/plugins/markdown-notices/blueprints.yaml @@ -0,0 +1,44 @@ +name: Markdown Notices +version: 1.0.0 +description: "Adds the ability to render notices blocks in Markdown" +icon: asterisk +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +license: MIT + +form: + validation: strict + fields: + enabled: + type: toggle + label: Plugin status + highlight: 1 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + + built_in_css: + type: toggle + label: Use built in CSS + highlight: 1 + default: 1 + options: + 1: Enabled + 0: Disabled + validate: + type: bool + + level_classes: + type: selectize + size: large + placeholder: "e.g. yellow, red, blue, green" + label: Level classes + help: The classes to use for each level of notices depth + classes: fancy + validate: + type: commalist diff --git a/sandbox/grav/user/plugins/markdown-notices/markdown-notices.php b/sandbox/grav/user/plugins/markdown-notices/markdown-notices.php new file mode 100644 index 0000000000..6930481868 --- /dev/null +++ b/sandbox/grav/user/plugins/markdown-notices/markdown-notices.php @@ -0,0 +1,84 @@ + ['onMarkdownInitialized', 0], + 'onTwigSiteVariables' => ['onTwigSiteVariables', 0] + ]; + } + + public function onMarkdownInitialized(Event $event) + { + $markdown = $event['markdown']; + + $markdown->addBlockType('!', 'Notices', true, false); + + $markdown->blockNotices = function($Line) { + + $this->level_classes = $this->config->get('plugins.markdown-notices.level_classes'); + + if (preg_match('/^(!{1,'.count($this->level_classes).'})[ ]+(.*)/', $Line['text'], $matches)) + { + $level = strlen($matches[1]) - 1; + + + + // if we have more levels than we support + if ($level > count($this->level_classes)-1) + { + return; + } + + $text = $matches[2]; + + $Block = array( + 'element' => array( + 'name' => 'div', + 'handler' => 'lines', + 'attributes' => array( + 'class' => 'notices '. $this->level_classes[$level], + ), + 'text' => (array) $text, + ), + ); + + return $Block; + } + }; + + $markdown->blockNoticesContinue = function($Line, array $Block) { + if (isset($Block['interrupted'])) + { + return; + } + + if ($Line['text'][0] === '!' and preg_match('/^(!{1,'.count($this->level_classes).'})(.*)/', $Line['text'], $matches)) + { + $Block['element']['text'] []= ltrim($matches[2]); + + return $Block; + } + }; + } + + public function onTwigSiteVariables() + { + if ($this->config->get('plugins.markdown-notices.built_in_css')) { + $this->grav['assets'] + ->add('plugin://markdown-notices/assets/notices.css'); + } + } + +} \ No newline at end of file diff --git a/sandbox/grav/user/plugins/markdown-notices/markdown-notices.yaml b/sandbox/grav/user/plugins/markdown-notices/markdown-notices.yaml new file mode 100644 index 0000000000..35ce5a6659 --- /dev/null +++ b/sandbox/grav/user/plugins/markdown-notices/markdown-notices.yaml @@ -0,0 +1,3 @@ +enabled: true +built_in_css: true +level_classes: [yellow, red, blue, green] \ No newline at end of file diff --git a/sandbox/grav/user/plugins/patternlab-twig-extensions/CHANGELOG.md b/sandbox/grav/user/plugins/patternlab-twig-extensions/CHANGELOG.md new file mode 100755 index 0000000000..74d77dc062 --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-extensions/CHANGELOG.md @@ -0,0 +1,17 @@ +# v1.0.2 +## 05/14/2017 + +1. [](#new) + * Tweaked `shuffle` to handle associative arrays as well, thanks to @Lamecarlate. + +# v1.0.1 +## 09/30/2016 + +1. [](#new) + * Added demo URL + +# v1.0.0 +## 09/30/2016 + +1. [](#new) + * ChangeLog started... diff --git a/sandbox/grav/user/plugins/patternlab-twig-extensions/LICENSE b/sandbox/grav/user/plugins/patternlab-twig-extensions/LICENSE new file mode 100755 index 0000000000..ff905acd3f --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-extensions/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Aaron Dalton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sandbox/grav/user/plugins/patternlab-twig-extensions/README.md b/sandbox/grav/user/plugins/patternlab-twig-extensions/README.md new file mode 100755 index 0000000000..c8b6836089 --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-extensions/README.md @@ -0,0 +1,67 @@ +# Twig Extensions Plugin + +The **Twig Extensions** plugin is for [Grav CMS](http://github.com/getgrav/grav). It pulls in a subset of the official [Twig Extensions](https://github.com/twigphp/Twig-extensions), v1.4.0. + +For a demo, [visit my blog](https://perlkonig.com/demos/twig-extensions). + +## Installation + +Installing the Twig Extensions plugin can be done in one of two ways. The GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. + +### GPM Installation (Preferred) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install twig-extensions + +This will install the Twig Extensions plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/twig-extensions`. + +### Manual Installation + +To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `twig-extensions`. You can find these files on [GitHub](https://github.com/Perlkonig/grav-plugin-twig-extensions) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/twig-extensions + +> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav) and the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) to operate. + +## Configuration + +Below is the default configuration. An explanation of the various fields follows. To customize, first copy `twig-extensions.yaml` to your `user/config/plugins` folder and edit that copy. + +``` +enabled: true +modules: [array, intl, date] + +``` + +* The `enabled` field turns the plugin off and on. + +* The `modules` array tells the plugin which modules you want imported. This plugin only imports three of the five modules. These are the only valid options. + +## Usage + +Simply enable the plugin to use these Twig filters. There are three modules available: + +* The `Intl` module provides three filters: + * `localizeddate` formats a date based on the locale. + * `localizednumber` formats a number based on the locale. + * `localizedcurrency` formats a number based on a given currency code. + +* The `Array` module provides a single filter: + * `shuffle` randomizes an array. + * **Note:** This code was slightly modified to allow shuffling associative arrays. Simply pass `true` to enable this feature: `{{ myArray | shuffle(true) }}`. + +The `Date` module also only provides a single filter: + * `time_diff` dispays the delta between two dates in a human readable form (e.g., `2 days ago`). + +For more information, [read the official documentation](http://twig.sensiolabs.org/doc/extensions/index.html). + +### Omitted Modules + +* The `Text` module is omitted because Grav already has `truncate` built in, and the `wordwrap` provided here is not very helpful. + +* The `I18n` module is omitted because Grav already has extensive i18n features. + + diff --git a/sandbox/grav/user/plugins/patternlab-twig-extensions/blueprints.yaml b/sandbox/grav/user/plugins/patternlab-twig-extensions/blueprints.yaml new file mode 100755 index 0000000000..750aa65b95 --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-extensions/blueprints.yaml @@ -0,0 +1,27 @@ +name: Pattern Lab Twig Extensions +version: 1.0.0 +description: Incorporates a subset of the official [Twig Extensions](https://github.com/twigphp/Twig-extensions) +icon: filter +author: + name: Salem Ghoweri + email: me@salemghoweri.com +homepage: https://github.com/Perlkonig/grav-plugin-twig-extensions +keywords: grav, plugin, twig, extensions +bugs: https://github.com/Perlkonig/grav-plugin-twig-extensions/issues +docs: https://github.com/Perlkonig/grav-plugin-twig-extensions/blob/master/README.md +demo: https://perlkonig.com/demos/twig-extensions +license: MIT + +form: + validation: strict + fields: + enabled: + type: toggle + label: Plugin status + highlight: 1 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool diff --git a/sandbox/grav/user/plugins/patternlab-twig-extensions/patternlab-twig-extensions.php b/sandbox/grav/user/plugins/patternlab-twig-extensions/patternlab-twig-extensions.php new file mode 100755 index 0000000000..f0ac86751a --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-extensions/patternlab-twig-extensions.php @@ -0,0 +1,156 @@ + ['onPluginsInitialized', 0] + ]; + } + + /** + * Initialize the plugin + */ + public function onPluginsInitialized() + { + // Don't proceed if we are in the admin plugin + if ($this->isAdmin()) { + return; + } + + // Enable the main event we are interested in + $this->enable([ + 'onTwigExtensions' => ['onTwigExtensions', -100], + ]); + } + + /** + * Loads a singleton registry of plugin objects. + */ + public function onTwigExtensions() + { + $modules = $this->grav['config']->get('plugins.patternlab-twig-extensions.extensions'); + + // if (!self::$objects) { + // static::loadAll('filters'); + // static::loadAll('functions'); + // static::loadAll('tags'); + // } + + // if (in_array('intl', $modules)) { + // require_once(__DIR__ . '/vendor/Twig/Intl.php'); + // $this->grav['twig']->twig->addExtension(new \Twig_Extensions_Extension_Intl()); + // } + if (in_array('extension_loader', $modules)) { + require_once(__DIR__ . '/src/TwigExtension/TwigExtensionAdapter.php'); + $this->grav['twig']->twig->addExtension(new \Twig_Extensions_Extension_Adapter()); + } + // if (in_array('date', $modules)) { + // require_once(__DIR__ . '/vendor/Twig/Date.php'); + // $this->grav['twig']->twig->addExtension(new \Twig_Extensions_Extension_Date()); + // } + + + + + } + + + // + // /** + // * Gets all plugin objects of a given type. + // * + // * @param string $type + // * The plugin type to load. + // * + // * @return array + // * An array of loaded objects to be provided by the twig extension for a + // * given type. + // */ + // static public function get($type) { + // return !empty(self::$objects[$type]) ? self::$objects[$type] : []; + // } + // /** + // * Loads all plugins of a given type. + // * + // * This should be called once per $type. + // * + // * @param string $type + // * The type to load all plugins for. + // */ + // static protected function loadAll($type) { + // $themeLocation = $this->grav['locator']->findResources('theme://')[0]; + // $themePath = $themeLocation . '/*/_twig-components/'; + // // $themeLocation = drupal_get_path('theme', $theme); + // + // $extensionPaths = new Finder(); + // $extensionPaths->directories()->depth(0)->in($themePath); + // + // // $themeLocation = $this->grav['locator']->findResources('theme://')[0]; + // // $patternLabTwigExtensions = $theme_dir . '/pattern-lab/source/_twig-components'; + // + // foreach ($extensionPaths as $extensionPath) { + // $fullPath = $extensionPath; + // print $fullPath; + // + // foreach (scandir($fullPath . $type) as $file) { + // $fileInfo = pathinfo($file); + // if ($fileInfo['extension'] === 'php') { + // if ($file[0] != '.' && $file[0] != '_' && substr($file, 0, 3) != 'pl_') { + // static::load($type, $fullPath . $type . '/' . $file); + // } + // } + // } + // } + // } + // /** + // * Loads a specific plugin instance. + // * + // * @param string $type + // * The type of the plugin to be loaded. + // * @param string $file + // * The fully qualified path of the plugin to be loaded. + // */ + // static protected function load($type, $file) { + // include $file; + // switch ($type) { + // case 'filters': + // self::$objects['filters'][] = $filter; + // break; + // case 'functions': + // self::$objects['functions'][] = $function; + // break; + // case 'tags': + // if (preg_match('/^([^\.]+)\.tag\.php$/', basename($file), $matches)) { + // $class = "Project_{$matches[1]}_TokenParser"; + // if (class_exists($class)) { + // self::$objects['parsers'][] = new $class(); + // } + // } + // break; + // } + // } +} diff --git a/sandbox/grav/user/plugins/patternlab-twig-extensions/patternlab-twig-extensions.yaml b/sandbox/grav/user/plugins/patternlab-twig-extensions/patternlab-twig-extensions.yaml new file mode 100755 index 0000000000..e7513a0e24 --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-extensions/patternlab-twig-extensions.yaml @@ -0,0 +1,2 @@ +enabled: true +extensions: [extension_loader] diff --git a/sandbox/grav/user/plugins/patternlab-twig-extensions/src/TwigExtension/TwigExtensionAdapter.php b/sandbox/grav/user/plugins/patternlab-twig-extensions/src/TwigExtension/TwigExtensionAdapter.php new file mode 100755 index 0000000000..606f307c91 --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-extensions/src/TwigExtension/TwigExtensionAdapter.php @@ -0,0 +1,95 @@ +findResources('theme://')[0]; + // $themePath = ROOT_DIR '../' .$theme . '/' . 'pattern-lab/source'; + $themePath = ROOT_DIR . '../' . 'pattern-lab/source'; + // $theme = \Drupal::config('system.theme')->get('default'); + // $themeLocation = + // print_r($themePath); + // $patternLabPatternsDir = $theme_dir . '/pattern-lab/source/_patterns'; + // $patternLabSourceDir = $theme_dir . '/pattern-lab/source'; + + + $extensionPaths = glob($themePath . '*/_twig-components/'); + + // print_r($extensionPaths); + + foreach ($extensionPaths as $extensionPath) { + $fullPath = $extensionPath; + + // print_r($fullPath); + + foreach (scandir($fullPath . $type) as $file) { + $fileInfo = pathinfo($file); + print_r($fileInfo['extension']); + + if ($fileInfo['extension'] === 'php') { + if ($file[0] != '.' && $file[0] != '_' && substr($file, 0, 3) != 'pl_') { + static::load($type, $fullPath . $type . '/' . $file); + } + } + } + } + } + /** + * Loads a specific plugin instance. + * + * @param string $type + * The type of the plugin to be loaded. + * @param string $file + * The fully qualified path of the plugin to be loaded. + */ + static protected function load($type, $file) { + include $file; + + switch ($type) { + case 'filters': + self::$objects['filters'][] = $filter; + break; + case 'functions': + self::$objects['functions'][] = $function; + break; + case 'tags': + if (preg_match('/^([^\.]+)\.tag\.php$/', basename($file), $matches)) { + $class = "Project_{$matches[1]}_TokenParser"; + if (class_exists($class)) { + print("yay!"); + self::$objects['parsers'][] = new $class(); + } + } + break; + } + } +} diff --git a/sandbox/grav/user/plugins/patternlab-twig-namespaces/CHANGELOG.md b/sandbox/grav/user/plugins/patternlab-twig-namespaces/CHANGELOG.md new file mode 100755 index 0000000000..74d77dc062 --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-namespaces/CHANGELOG.md @@ -0,0 +1,17 @@ +# v1.0.2 +## 05/14/2017 + +1. [](#new) + * Tweaked `shuffle` to handle associative arrays as well, thanks to @Lamecarlate. + +# v1.0.1 +## 09/30/2016 + +1. [](#new) + * Added demo URL + +# v1.0.0 +## 09/30/2016 + +1. [](#new) + * ChangeLog started... diff --git a/sandbox/grav/user/plugins/patternlab-twig-namespaces/LICENSE b/sandbox/grav/user/plugins/patternlab-twig-namespaces/LICENSE new file mode 100755 index 0000000000..ff905acd3f --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-namespaces/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Aaron Dalton + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sandbox/grav/user/plugins/patternlab-twig-namespaces/README.md b/sandbox/grav/user/plugins/patternlab-twig-namespaces/README.md new file mode 100755 index 0000000000..c8b6836089 --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-namespaces/README.md @@ -0,0 +1,67 @@ +# Twig Extensions Plugin + +The **Twig Extensions** plugin is for [Grav CMS](http://github.com/getgrav/grav). It pulls in a subset of the official [Twig Extensions](https://github.com/twigphp/Twig-extensions), v1.4.0. + +For a demo, [visit my blog](https://perlkonig.com/demos/twig-extensions). + +## Installation + +Installing the Twig Extensions plugin can be done in one of two ways. The GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. + +### GPM Installation (Preferred) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install twig-extensions + +This will install the Twig Extensions plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/twig-extensions`. + +### Manual Installation + +To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `twig-extensions`. You can find these files on [GitHub](https://github.com/Perlkonig/grav-plugin-twig-extensions) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/twig-extensions + +> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav) and the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) to operate. + +## Configuration + +Below is the default configuration. An explanation of the various fields follows. To customize, first copy `twig-extensions.yaml` to your `user/config/plugins` folder and edit that copy. + +``` +enabled: true +modules: [array, intl, date] + +``` + +* The `enabled` field turns the plugin off and on. + +* The `modules` array tells the plugin which modules you want imported. This plugin only imports three of the five modules. These are the only valid options. + +## Usage + +Simply enable the plugin to use these Twig filters. There are three modules available: + +* The `Intl` module provides three filters: + * `localizeddate` formats a date based on the locale. + * `localizednumber` formats a number based on the locale. + * `localizedcurrency` formats a number based on a given currency code. + +* The `Array` module provides a single filter: + * `shuffle` randomizes an array. + * **Note:** This code was slightly modified to allow shuffling associative arrays. Simply pass `true` to enable this feature: `{{ myArray | shuffle(true) }}`. + +The `Date` module also only provides a single filter: + * `time_diff` dispays the delta between two dates in a human readable form (e.g., `2 days ago`). + +For more information, [read the official documentation](http://twig.sensiolabs.org/doc/extensions/index.html). + +### Omitted Modules + +* The `Text` module is omitted because Grav already has `truncate` built in, and the `wordwrap` provided here is not very helpful. + +* The `I18n` module is omitted because Grav already has extensive i18n features. + + diff --git a/sandbox/grav/user/plugins/patternlab-twig-namespaces/blueprints.yaml b/sandbox/grav/user/plugins/patternlab-twig-namespaces/blueprints.yaml new file mode 100755 index 0000000000..26fc99215e --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-namespaces/blueprints.yaml @@ -0,0 +1,27 @@ +name: Pattern Lab Twig Namespaces +version: 1.0.0 +description: Add namespaces to Grav Twig instance +icon: filter +author: + name: Salem Ghoweri + email: me@salemghoweri.com +homepage: https://github.com/bolt-design-system/grav-plugin-patternlab-twig-namespaces +keywords: grav, plugin, twig, namespaces, pattern lab +bugs: https://github.com/bolt-design-system/grav-plugin-patternlab-twig-namespaces/issues +docs: https://github.com/bolt-design-system/grav-plugin-patternlab-twig-namespaces/blob/master/README.md +#demo: https://perlkonig.com/demos/twig-extensions +license: MIT + +form: + validation: strict + fields: + enabled: + type: toggle + label: Plugin status + highlight: 1 + default: 0 + options: + 1: Enabled + 0: Disabled + validate: + type: bool diff --git a/sandbox/grav/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php b/sandbox/grav/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php new file mode 100755 index 0000000000..c5724811a0 --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.php @@ -0,0 +1,106 @@ + ['onPluginsInitialized', 0] + ]; + } + + /** + * Initialize the plugin + */ + public function onPluginsInitialized() + { + // Don't proceed if we are in the admin plugin + if ($this->isAdmin()) { + return; + } + + // Enable the main event we are interested in + $this->enable([ + 'onTwigLoader' => ['onTwigLoader', -100], + ]); + } + + public function onTwigLoader() + { + // $theme_dir = $this->grav['locator']->findResources('theme://')[0]; + $patternLabPatternsDir = ROOT_DIR . '../' . 'pattern-lab/source'; + // $patternLabPatternsDir = $theme_dir . '/pattern-lab/source/_patterns'; + + // $patternLabSourceDir = $theme_dir . '/pattern-lab/source'; + + $finder = new Finder(); + $finder->directories()->depth(0)->in($patternLabPatternsDir); + foreach ($finder as $file) { + $pattern = $file->getRelativePathName(); + $patternBits = explode("-",$pattern,2); + $patternTypePath = (((int)$patternBits[0] != 0) || ($patternBits[0] == '00')) ? $patternBits[1] : $pattern; + + // echo $file->getPathName(); + + // $filesystemLoader->addPath($file->getPathName(), $patternTypePath); +$this->grav['twig']->addPath($file->getPathName(), $patternTypePath); + // } + } + + + // $pattern = $file->getRelativePathName(); + + $patternFinder = new Finder(); + $patternFinder->files()->name('*.twig')->in($patternLabPatternsDir); + + foreach ($patternFinder as $file) { + $pattern = basename($file->getRealPath()); + $patternBits = explode("-", $pattern, 2); + $patternTypePath = (((int)$patternBits[0] != 0) || ($patternBits[0] == '00')) ? $patternBits[1] : $pattern; + $patternNameBits = explode(".",$patternTypePath); + $path = pathinfo($file->getRealPath()); + $this->grav['twig']->addPath($path['dirname'], 'bolt'); + } + } +} + // $patternName = (((int)$patternBits[0] != 0) || ($patternBits[0] == '00')) ? $patternNameBits[1] : $pattern; + + // print $file->getPathName(); + // print $patternName; + // print_r($patternNameBits[0]); + + + + // print $path['dirname']; + + // echo $file->getPathName(); + + // $filesystemLoader->addPath($file->getPathName(), $patternTypePath); + + // } + + + // return $filesystemLoader; + + + + // $this->grav['twig']->addPath($theme_dir . '/pattern-lab/source/_patterns/00-atoms', 'atoms'); diff --git a/sandbox/grav/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.yaml b/sandbox/grav/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.yaml new file mode 100755 index 0000000000..3e44e698d0 --- /dev/null +++ b/sandbox/grav/user/plugins/patternlab-twig-namespaces/patternlab-twig-namespaces.yaml @@ -0,0 +1,3 @@ +enabled: true +modules: [array, intl, date] + diff --git a/sandbox/grav/user/plugins/problems/CHANGELOG.md b/sandbox/grav/user/plugins/problems/CHANGELOG.md new file mode 100644 index 0000000000..e7ed40f4ed --- /dev/null +++ b/sandbox/grav/user/plugins/problems/CHANGELOG.md @@ -0,0 +1,105 @@ +# v1.4.7 +## 05/16/2017 + +1. [](#improved) + * Added check for Exif module if this feature is enabled + +# v1.4.6 +## 02/17/2017 + +1. [](#improved) + * Return 500 error code if there is a problem instead of 200 [https://github.com/getgrav/grav/issues/1291](https://github.com/getgrav/grav/issues/1291) + +# v1.4.5 +## 09/14/2016 + +1. [](#bugfix) + * Show the correct status for the Zip extension check + +# v1.4.4 +## 09/08/2016 + +1. [](#new) + * Added check for new root folder `tmp` and try to create if missing +1. [](#bugfix) + * Fixed Whoops error if `backup` folder doesn't exist and cannot be created + +# v1.4.3 +## 05/27/2016 + +1. [](#new) + * Reverted compression checks + +# v1.4.2 +## 05/23/2016 + +1. [](#new) + * Check for compression issues + +# v1.4.1 +## 05/03/2016 + +1. [](#new) + * Added a check for XML support in PHP +1. [](#improved) + * Use common language strings in blueprints + +# v1.4.0 +## 01/06/2016 + +1. [](#improved) + * Avoid generating errors on .DS_Store files added to the bin/ folder by OSX + * Removed executable checks for bin/* commands. Going to document instead. + +# v1.3.3 +## 12/09/2015 + +1. [](#new) + * Set minimum PHP requirements to 5.5.9 +1. [](#improved) + * Ensure problems plugin runs before admin + +# v1.3.2 +## 12/09/2015 + +1. [](#improved) + * Skip windows platforms for executable permissions check + * Removed mod_headers from required Apache modules check + +# v1.3.1 +## 12/07/2015 + +1. [](#improved) + * Added executable check on `/bin/` files + +# v1.3.0 +## 12/07/2015 + +1. [](#improved) + * Added check for PHP `OpenSSL`, `Mbstring` and `Curl` are installed + * Added check to ensure `mod_rewrite` and `mod_headers` are installed if running Apache + +# v1.2.0 +## 08/25/2015 + +1. [](#improved) + * Added blueprints for Grav Admin plugin + +# v1.1.6 +## 06/16/2015 + +2. [](#new) + * Try to create missing `backup` folder if it is missing + +# v1.1.5 +## 05/09/2015 + +2. [](#new) + * Added check for `backup` folder for Grav > 0.9.27 + +# v1.1.4 +## 04/26/2015 + +2. [](#new) + * Changelog started + diff --git a/sandbox/grav/user/plugins/problems/LICENSE b/sandbox/grav/user/plugins/problems/LICENSE new file mode 100644 index 0000000000..484793ad19 --- /dev/null +++ b/sandbox/grav/user/plugins/problems/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Grav + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/sandbox/grav/user/plugins/problems/README.md b/sandbox/grav/user/plugins/problems/README.md new file mode 100644 index 0000000000..9280bbd51e --- /dev/null +++ b/sandbox/grav/user/plugins/problems/README.md @@ -0,0 +1,89 @@ +# Grav Problems Plugin + +![Problems](assets/readme_1.png) + +`Problems` is a [Grav](http://github.com/getgrav/grav) Plugin and allows to detect issues. + +This plugin is included in any package distributed that contains Grav. If you decide to clone Grav from GitHub, you will most likely want to install this. + +# Installation + +Installing the Problems plugin can be done in one of two ways. Our GPM (Grav Package Manager) installation method enables you to quickly and easily install the plugin with a simple terminal command, while the manual method enables you to do so via a zip file. + +## GPM Installation (Preferred) + +The simplest way to install this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm) through your system's Terminal (also called the command line). From the root of your Grav install type: + + bin/gpm install problems + +This will install the Problems plugin into your `/user/plugins` directory within Grav. Its files can be found under `/your/site/grav/user/plugins/problems`. + +## Manual Installation + +To install this plugin, just download the zip version of this repository and unzip it under `/your/site/grav/user/plugins`. Then, rename the folder to `problems`. You can find these files either on [GitHub](https://github.com/getgrav/grav-plugin-problems) or via [GetGrav.org](http://getgrav.org/downloads/plugins#extras). + +You should now have all the plugin files under + + /your/site/grav/user/plugins/problems + +>> NOTE: This plugin is a modular component for Grav which requires [Grav](http://github.com/getgrav/grav), the [Error](https://github.com/getgrav/grav-plugin-error) and [Problems](https://github.com/getgrav/grav-plugin-problems) plugins, and a theme to be installed in order to operate. + +# Usage + +`Problems` runs in the background and most of the time you will not know it is there. Although as soon as an issue is caught, the plugin will let you know. + +`Problems` checks for the following common issues: + +| Check | Description | +| :---------------------------------- | :-------------------------------------------------------------------------------------------------------- | +| Apache `mod_rewrite` | Checks to ensure `mod_rewrite` is enabled if you are running an Apache server. | +| PHP Version | Checks to make sure the PHP version being run by the server meets or exceeds Grav's minimum requirements. | +| PHP GD (Image Manipulation Library) | Checks to make sure that PHP GD is installed. | +| PHP Curl (Data Transfer Library) | Checks to make sure that PHP Curl is installed. | +| PHP OpenSSL (Secure Sockets Library) | Checks to make sure that PHP OpenSSL is installed. | +| PHP Mbstring (Multibyte String Library) | Checks to make sure that PHP Mbstring is installed. | +| .htaccess | Checks to make sure that there is an `.htaccess` file in Grav's root directory. | +| `bin/*` executable | Checks that all the files in the `bin/` folder are exectuable. | +| Cache | Checks the `/cache` folder's existence and verifies that it is writeable. | +| Logs | Checks the `/logs` folder's existence and verifies that it is writeable. | +| Images | Checks the `/images` folder's existence and verifies that it is writeable. | +| Assets | Checks the `/assets` folder's existence and verifies that it is writeable. | +| System | Checks the `/system` folder's existence. | +| Data | Checks the `/user/data` folder's existence and verifies that it is writeable. | +| Pages | Checks the `/user/images` folder's existence. | +| Config | Checks the `/user/config` folder's existence. | +| Error | Checks to make sure the **Error** plugin is installed in `/user/plugins/error`. | +| Plugins | Checks the `/user/plugins` folder's existence. | +| Themes | Checks the `/user/themes` folder's existence. | +| Vendor | Checks the `/vendor` folder's existence. | + +If an issue is discovered, you will be greeted with a page that lists these checks and whether or not your install passed or failed them. Green checks mean it passed, and a red x indicates that the there is something amiss with the item. + +Problems uses the cache as refresh indicator. That means that if nothing has changed anywhere, the plugin will just skip its validation tests altogether. + +If a change is caught and the cache is refreshed, the plugin will loop through its validation tests and making sure nothing is out of place. + +`Problems` gets also triggered if any fatal exception is caught. + +# Updating + +As development for the Problems plugin continues, new versions may become available that add additional features and functionality, improve compatibility with newer Grav releases, and generally provide a better user experience. Updating Problems is easy, and can be done through Grav's GPM system, as well as manually. + +## GPM Update (Preferred) + +The simplest way to update this plugin is via the [Grav Package Manager (GPM)](http://learn.getgrav.org/advanced/grav-gpm). You can do this with this by navigating to the root directory of your Grav install using your system's Terminal (also called command line) and typing the following: + + bin/gpm update problems + +This command will check your Grav install to see if your Problems plugin is due for an update. If a newer release is found, you will be asked whether or not you wish to update. To continue, type `y` and hit enter. The plugin will automatically update and clear Grav's cache. + +## Manual Update + +Manually updating Problems is pretty simple. Here is what you will need to do to get this done: + +* Delete the `your/site/user/plugins/problems` directory. +* Download the new version of the Problems plugin from either [GitHub](https://github.com/getgrav/grav-plugin-problems) or [GetGrav.org](http://getgrav.org/downloads/plugins#extras). +* Unzip the zip file in `your/site/user/plugins` and rename the resulting folder to `problems`. +* Clear the Grav cache. The simplest way to do this is by going to the root Grav directory in terminal and typing `bin/grav clear-cache`. + +> Note: Any changes you have made to any of the files listed under this directory will also be removed and replaced by the new set. Any files located elsewhere (for example a YAML settings file placed in `user/config/plugins`) will remain intact. diff --git a/sandbox/grav/user/plugins/problems/assets/readme_1.png b/sandbox/grav/user/plugins/problems/assets/readme_1.png new file mode 100644 index 0000000000..bd6f916051 Binary files /dev/null and b/sandbox/grav/user/plugins/problems/assets/readme_1.png differ diff --git a/sandbox/grav/user/plugins/problems/blueprints.yaml b/sandbox/grav/user/plugins/problems/blueprints.yaml new file mode 100644 index 0000000000..7360395bde --- /dev/null +++ b/sandbox/grav/user/plugins/problems/blueprints.yaml @@ -0,0 +1,37 @@ +name: Problems +version: 1.4.7 +description: Detects and reports problems found in the site. +icon: exclamation-circle +author: + name: Team Grav + email: devs@getgrav.org + url: http://getgrav.org +homepage: https://github.com/getgrav/grav-plugin-problems +keywords: problems, plugin, detector, assistant, required +bugs: https://github.com/getgrav/grav-plugin-problems/issues +license: MIT + +form: + validation: strict + fields: + enabled: + type: toggle + label: PLUGIN_ADMIN.PLUGIN_STATUS + highlight: 1 + default: 0 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool + + built_in_css: + type: toggle + label: Use built in CSS + highlight: 1 + default: 1 + options: + 1: PLUGIN_ADMIN.ENABLED + 0: PLUGIN_ADMIN.DISABLED + validate: + type: bool diff --git a/sandbox/grav/user/plugins/problems/css/problems.css b/sandbox/grav/user/plugins/problems/css/problems.css new file mode 100644 index 0000000000..4917e5489d --- /dev/null +++ b/sandbox/grav/user/plugins/problems/css/problems.css @@ -0,0 +1,71 @@ +section#body { + padding-top: 3rem; +} + +ul.problems { + list-style: none; + padding: 0; + margin-top: 3rem; +} + +ul.problems li { + margin-bottom: 1rem; + padding: 1rem; +} + +ul.problems li.success { + background: #F1F9F1; + border-left: 5px solid #5CB85C; + color: #3d8b3d; +} + +ul.problems li.error { + background: #FDF7F7; + border-left: 5px solid #D9534F; + color: #b52b27; +} + +ul.problems li.info { + background: #F4F8FA; + border-left: 5px solid #5bc0de; + color: #28a1c5; +} + +ul.problems .fa { + font-size: 3rem; + vertical-align: middle; + margin-left: 1rem; + display: block; + float: left; +} + +ul.problems p { + display: block; + margin: 0.5rem 0.5rem 0.5rem 5rem; +} + +.button.big { + font-size: 1.2rem; +} + +.center { + text-align: center; +} + +.underline { + text-decoration: underline; +} + +.clearfix:after { + visibility: hidden; + display: block; + font-size: 0; + content: " "; + clear: both; + height: 0; + } +.clearfix { display: inline-block; } +/* start commented backslash hack \*/ +* html .clearfix { height: 1%; } +.clearfix { display: block; } +/* close commented backslash hack */ diff --git a/sandbox/grav/user/plugins/problems/css/template.css b/sandbox/grav/user/plugins/problems/css/template.css new file mode 100644 index 0000000000..ba59ac88bb --- /dev/null +++ b/sandbox/grav/user/plugins/problems/css/template.css @@ -0,0 +1,762 @@ +@import url(//fonts.googleapis.com/css?family=Montserrat:400|Raleway:300,400,600|Inconsolata); + +#header #logo h3, #header #navbar ul, #header #navbar .panel-activation, #footer p { + position: relative; + top: 50%; + -webkit-transform: translateY(-50%); + -moz-transform: translateY(-50%); + -o-transform: translateY(-50%); + -ms-transform: translateY(-50%); + transform: translateY(-50%); } + +.button, .button-secondary { + display: inline-block; + padding: 7px 20px; } + +html, body { + height: 100%; } + +body { + background: white; + color: #444444; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } + +a { + color: #1bb3e9; } + a:hover { + color: #0e6e90; } + +b, strong, label, th { + font-weight: 600; } + +#container { + min-height: 100%; + position: relative; } + +.fullwidth #body { + padding-left: 0; + padding-right: 0; } + +#body { + padding-top: 8rem; + padding-bottom: 11rem; } + +.default-animation, #body, #header, #header #logo h3, .modular .showcase .button { + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + transition: all 0.5s ease; } + +.padding-horiz, .fullwidth #header, .fullwidth #breadcrumbs, .fullwidth .blog-header, .fullwidth .blog-content-item, .fullwidth .blog-content-list, .fullwidth ul.pagination, .fullwidth #body > .modular-row, #body, #header, #footer { + padding-left: 7rem; + padding-right: 7rem; } + @media only all and (max-width: 59.938rem) { + .padding-horiz, .fullwidth #header, .fullwidth #breadcrumbs, .fullwidth .blog-header, .fullwidth .blog-content-item, .fullwidth .blog-content-list, .fullwidth ul.pagination, .fullwidth #body > .modular-row, #body, #header, #footer { + padding-left: 4rem; + padding-right: 4rem; } } + @media only all and (max-width: 47.938rem) { + .padding-horiz, .fullwidth #header, .fullwidth #breadcrumbs, .fullwidth .blog-header, .fullwidth .blog-content-item, .fullwidth .blog-content-list, .fullwidth ul.pagination, .fullwidth #body > .modular-row, #body, #header, #footer { + padding-left: 1rem; + padding-right: 1rem; } } + +.padding-vert { + padding-top: 3rem; + padding-bottom: 3rem; } + +#header { + position: fixed; + z-index: 10; + width: 100%; + height: 5rem; + background-color: rgba(255, 255, 255, 0.9); + box-shadow: 0 0.05rem 1rem rgba(0, 0, 0, 0.15); } + #header.scrolled { + height: 3rem; + background-color: rgba(255, 255, 255, 0.9) !important; + box-shadow: 0 0.05rem 1rem rgba(0, 0, 0, 0.15) !important; } + #header.scrolled #logo h3 { + color: #444444 !important; + font-size: 1.6rem !important; } + #header.scrolled #logo a { + color: #444444 !important; } + #header.scrolled #navbar a { + color: #1bb3e9 !important; } + #header.scrolled #navbar a:before, #header.scrolled #navbar a:after { + background-color: #1bb3e9 !important; } + #header > .grid, #header #logo, #header #navbar { + height: 100%; } + #header #logo { + float: left; } + #header #logo h3 { + font-size: 2rem; + line-height: 2rem; + margin: 0; + text-transform: uppercase; } + #header #logo h3 a { + color: #444444; } + #header #navbar { + font-size: 0.9rem; } + #header #navbar ul { + display: inline-block; + margin: 0; + list-style: none; + float: right; } + #header #navbar ul li { + float: left; + position: relative; } + #header #navbar ul li a { + font-family: "Montserrat", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + display: inline-block; + padding: 0.1rem 0.8rem; } + #header #navbar ul li a:before, #header #navbar ul li a:after { + content: ""; + position: absolute; + width: 100%; + height: 1px; + bottom: 0; + left: 0; + background-color: #1bb3e9; + visibility: hidden; + -webkit-transform: scaleX(0); + -moz-transform: scaleX(0); + -ms-transform: scaleX(0); + -o-transform: scaleX(0); + transform: scaleX(0); + -webkit-transition: all 0.2s ease; + -moz-transition: all 0.2s ease; + transition: all 0.2s ease; } + #header #navbar ul li a:hover:before { + visibility: visible; + -webkit-transform: scaleX(0.75); + -moz-transform: scaleX(0.75); + -ms-transform: scaleX(0.75); + -o-transform: scaleX(0.75); + transform: scaleX(0.75); } + #header #navbar ul li.active a:after { + top: 0; + visibility: visible; + -webkit-transform: scaleX(0.75); + -moz-transform: scaleX(0.75); + -ms-transform: scaleX(0.75); + -o-transform: scaleX(0.75); + transform: scaleX(0.75); } + @media only all and (max-width: 59.938rem) { + #header #navbar ul { + display: none; } } + #header #navbar .panel-activation { + display: none; + font-size: 2rem; + cursor: pointer; + float: right; } + @media only all and (max-width: 59.938rem) { + #header #navbar .panel-activation { + display: inline-block; } } + +.header-image.fullwidth #body { + padding-left: 0; + padding-right: 0; } + .header-image.fullwidth #body > .listing-row { + padding-left: 7rem; + padding-right: 7rem; } +.header-image .listing-row:last-child { + margin-bottom: 2rem; } +.header-image #body > .blog-header { + margin-top: -9.5rem; + padding-top: 9rem; } +.header-image #breadcrumbs { + margin-top: 1rem; } +.header-image #header { + background-color: rgba(255, 255, 255, 0); + box-shadow: none; } + .header-image #header #logo h3, .header-image #header #logo a { + color: white; } + .header-image #header a, .header-image #header .menu-btn { + color: white; } + .header-image #header a:before, .header-image #header a:after { + background-color: rgba(255, 255, 255, 0.7) !important; } + +#footer { + position: absolute; + background: #333; + height: 6rem; + right: 0; + bottom: 0; + left: 0; + color: #999; + text-align: center; } + #footer a:hover { + color: #fff; } + #footer .totop { + position: absolute; + bottom: 5rem; + text-align: center; + left: 0; + right: 0; } + #footer .totop span { + font-size: 1.7rem; + line-height: 2.5rem; + background: #333; + width: 3rem; + height: 2rem; + border-radius: 3px; + display: inline-block; + text-align: top; } + #footer p { + margin: 0; } + #footer p .fa { + color: #fff; } + +body { + font-family: "Raleway", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + font-weight: 400; } + +h1, h2, h3, h4, h5, h6 { + font-family: "Montserrat", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + font-weight: 400; + text-rendering: optimizeLegibility; + letter-spacing: -0px; } + +h1 { + font-size: 3.2rem; } + @media only all and (max-width: 47.938rem) { + h1 { + font-size: 2.5rem; + line-height: 1.2; + margin-bottom: 2.5rem; } } + +@media only all and (min-width: 48rem) and (max-width: 59.938rem) { + h2 { + font-size: 2.1rem; } } +@media only all and (max-width: 47.938rem) { + h2 { + font-size: 2rem; } } + +@media only all and (min-width: 48rem) and (max-width: 59.938rem) { + h3 { + font-size: 1.7rem; } } +@media only all and (max-width: 47.938rem) { + h3 { + font-size: 1.6rem; } } + +@media only all and (min-width: 48rem) and (max-width: 59.938rem) { + h4 { + font-size: 1.35rem; } } +@media only all and (max-width: 47.938rem) { + h4 { + font-size: 1.25rem; } } + +h1 { + text-align: center; + letter-spacing: -3px; } + +h2 { + letter-spacing: -2px; } + +h3 { + letter-spacing: -1px; } + +h1 + h2 { + margin: -2rem 0 2rem 0; + font-size: 2rem; + line-height: 1; + text-align: center; + font-family: "Raleway", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + font-weight: 300; } + @media only all and (min-width: 48rem) and (max-width: 59.938rem) { + h1 + h2 { + font-size: 1.6rem; } } + @media only all and (max-width: 47.938rem) { + h1 + h2 { + font-size: 1.5rem; } } + +h2 + h3 { + margin: 0.5rem 0 2rem 0; + font-size: 2rem; + line-height: 1; + text-align: center; + font-family: "Raleway", "Helvetica", "Tahoma", "Geneva", "Arial", sans-serif; + font-weight: 300; } + @media only all and (min-width: 48rem) and (max-width: 59.938rem) { + h2 + h3 { + font-size: 1.6rem; } } + @media only all and (max-width: 47.938rem) { + h2 + h3 { + font-size: 1.5rem; } } + +blockquote { + border-left: 10px solid #f0f2f4; } + blockquote p { + font-size: 1.1rem; + color: #999; } + blockquote cite { + display: block; + text-align: right; + color: #666; + font-size: 1.2rem; } + +blockquote > blockquote > blockquote { + margin: 0; } + blockquote > blockquote > blockquote p { + padding: 15px; + display: block; + font-size: 1rem; + margin-top: 0rem; + margin-bottom: 0rem; } + blockquote > blockquote > blockquote > p { + margin-left: -71px; + border-left: 10px solid #F0AD4E; + background: #FCF8F2; + color: #df8a13; } + blockquote > blockquote > blockquote > blockquote > p { + margin-left: -94px; + border-left: 10px solid #D9534F; + background: #FDF7F7; + color: #b52b27; } + blockquote > blockquote > blockquote > blockquote > blockquote > p { + margin-left: -118px; + border-left: 10px solid #5BC0DE; + background: #F4F8FA; + color: #28a1c5; } + blockquote > blockquote > blockquote > blockquote > blockquote > blockquote > p { + margin-left: -142px; + border-left: 10px solid #5CB85C; + background: #F1F9F1; + color: #3d8b3d; } + +code, +kbd, +pre, +samp { + font-family: "Inconsolata", monospace; } + +code { + background: #f9f2f4; + color: #9c1d3d; } + +pre { + padding: 2rem; + background: #f6f6f6; + border: 1px solid #dddddd; + border-radius: 3px; } + pre code { + color: #237794; + background: inherit; } + +hr { + border-bottom: 4px solid #f0f2f4; } + +.page-title { + margin-top: -25px; + padding: 25px; + float: left; + clear: both; + background: #1bb3e9; + color: white; } + +fieldset { + border: 1px solid #dddddd; } + +textarea, input[type="email"], input[type="number"], input[type="password"], input[type="search"], input[type="tel"], input[type="text"], input[type="url"], input[type="color"], input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="month"], input[type="time"], input[type="week"], select[multiple=multiple] { + background-color: white; + border: 1px solid #dddddd; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.06); } + textarea:hover, input[type="email"]:hover, input[type="number"]:hover, input[type="password"]:hover, input[type="search"]:hover, input[type="tel"]:hover, input[type="text"]:hover, input[type="url"]:hover, input[type="color"]:hover, input[type="date"]:hover, input[type="datetime"]:hover, input[type="datetime-local"]:hover, input[type="month"]:hover, input[type="time"]:hover, input[type="week"]:hover, select[multiple=multiple]:hover { + border-color: #c4c4c4; } + textarea:focus, input[type="email"]:focus, input[type="number"]:focus, input[type="password"]:focus, input[type="search"]:focus, input[type="tel"]:focus, input[type="text"]:focus, input[type="url"]:focus, input[type="color"]:focus, input[type="date"]:focus, input[type="datetime"]:focus, input[type="datetime-local"]:focus, input[type="month"]:focus, input[type="time"]:focus, input[type="week"]:focus, select[multiple=multiple]:focus { + border-color: #1bb3e9; + box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.06), 0 0 5px rgba(21, 163, 214, 0.7); } + +.form-field .required { + color: #F3443F; + font-size: 3rem; + line-height: 3rem; + vertical-align: top; + height: 1.5rem; + display: inline-block; } + +form .buttons { + text-align: center; } +form input { + font-weight: 400; } + +table { + border: 1px solid #eaeaea; } + +th { + background: #f7f7f7; + padding: 0.5rem; } + +td { + padding: 0.5rem; + border: 1px solid #eaeaea; } + +.button { + background: white; + color: #1bb3e9; + border: 1px solid #1bb3e9; + border-radius: 3px; } + .button:hover { + background: #1bb3e9; + color: white; } + .button:active { + box-shadow: 0 1px 0 #118ab5; } + +.button-secondary { + background: white; + color: #f6635e; + border: 1px solid #f6635e; + border-radius: 3px; } + .button-secondary:hover { + background: #f6635e; + color: white; } + .button-secondary:active { + box-shadow: 0 1px 0 #f32b24; } + +.bullets { + margin: 1.7rem 0; + margin-left: -0.85rem; + margin-right: -0.85rem; + overflow: auto; } + +.bullet { + float: left; + padding: 0 0.85rem; } + +.two-column-bullet { + width: 50%; } + @media only all and (max-width: 47.938rem) { + .two-column-bullet { + width: 100%; } } + +.three-column-bullet { + width: 33.33333%; } + @media only all and (max-width: 47.938rem) { + .three-column-bullet { + width: 100%; } } + +.four-column-bullet { + width: 25%; } + @media only all and (max-width: 47.938rem) { + .four-column-bullet { + width: 100%; } } + +.bullet-icon { + float: left; + background: #1bb3e9; + padding: 0.875rem; + width: 3.5rem; + height: 3.5rem; + border-radius: 50%; + color: white; + font-size: 1.75rem; + text-align: center; } + +.bullet-icon-1 { + background: #1bb3e9; } + +.bullet-icon-2 { + background: #1be9da; } + +.bullet-icon-3 { + background: #d5e91b; } + +.bullet-content { + margin-left: 4.55rem; } + +#panel { + color: white; } + #panel .navigation { + list-style: none; + padding: 0; } + #panel .navigation li { + padding: 0.5rem 1rem; + border-bottom: 1px solid #404040; + font-weight: 600; } + #panel .navigation li.active { + background: #fff; } + #panel .navigation li.active a { + color: #444444; } + #panel .navigation li.active a:hover { + color: #444444; } + #panel .navigation li:last-child { + border-bottom: 0; } + #panel .navigation li a:hover { + color: white; } + +/* Menu Appearance */ +.pushy { + position: fixed; + width: 250px; + height: 100%; + top: 0; + z-index: 9999; + background: #333333; + overflow: auto; + -webkit-overflow-scrolling: touch; + /* enables momentum scrolling in iOS overflow elements */ } + +/* Menu Movement */ +.pushy-left { + -webkit-transform: translate3d(-250px, 0, 0); + -moz-transform: translate3d(-250px, 0, 0); + -ms-transform: translate3d(-250px, 0, 0); + -o-transform: translate3d(-250px, 0, 0); + transform: translate3d(-250px, 0, 0); } + +.pushy-open { + -webkit-transform: translate3d(0, 0, 0); + -moz-transform: translate3d(0, 0, 0); + -ms-transform: translate3d(0, 0, 0); + -o-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); } + +.container-push, .push-push { + -webkit-transform: translate3d(250px, 0, 0); + -moz-transform: translate3d(250px, 0, 0); + -ms-transform: translate3d(250px, 0, 0); + -o-transform: translate3d(250px, 0, 0); + transform: translate3d(250px, 0, 0); } + +/* Menu Transitions */ +.pushy, #container, .push { + -webkit-transition: -webkit-transform 0.2s cubic-bezier(0.16, 0.68, 0.43, 0.99); + -moz-transition: -moz-transform 0.2s cubic-bezier(0.16, 0.68, 0.43, 0.99); + transition: transform 0.2s cubic-bezier(0.16, 0.68, 0.43, 0.99); + /* improves performance issues on mobile*/ + -webkit-perspective: 1000; } + +/* Site Overlay */ +.site-overlay { + display: none; } + +.pushy-active .site-overlay { + display: block; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 250px; + z-index: 9999; } + +.blog-header { + padding-top: 2rem; + padding-bottom: 2rem; } + .blog-header.blog-header-image { + background-size: cover; + background-position: center; } + .blog-header.blog-header-image h1, .blog-header.blog-header-image h2 { + color: white; } + .blog-header h1 { + font-size: 4rem; + margin-top: 0; } + @media only all and (min-width: 48rem) and (max-width: 59.938rem) { + .blog-header h1 { + font-size: 3rem; } } + @media only all and (max-width: 47.938rem) { + .blog-header h1 { + font-size: 2.5rem; + line-height: 1.2; + margin-bottom: 2.5rem; } } + .blog-header + .blog-content { + padding-top: 3rem; } + +.list-item { + border-bottom: 1px solid #eeeeee; + margin-bottom: 3rem; } + .list-item:last-child { + border-bottom: 0; } + .list-item .list-blog-header { + position: relative; } + .list-item .list-blog-header h4 { + margin-bottom: 0.5rem; } + .list-item .list-blog-header h4 a { + color: #444444; } + .list-item .list-blog-header h4 a:hover { + color: #1bb3e9; } + .list-item .list-blog-header img { + display: block; + margin-top: 1rem; + border-radius: 3px; } + .list-item .list-blog-date { + float: right; + text-align: center; } + .list-item .list-blog-date span { + display: block; + font-size: 1.75rem; + font-weight: 600; + line-height: 110%; } + .list-item .list-blog-date em { + display: block; + border-top: 1px solid #eeeeee; + font-style: normal; + text-transform: uppercase; } + +.blog-content-item .list-blog-padding > p:nth-child(2) { + font-size: 1.2rem; } + +.tags a { + display: inline-block; + font-size: 0.8rem; + border: 1px solid #1bb3e9; + border-radius: 3px; + padding: 0.1rem 0.4rem; + margin-bottom: 0.2rem; + text-transform: uppercase; } + +.archives { + padding: 0; + list-style: none; } + .archives li { + border-bottom: 1px solid #eeeeee; + line-height: 2rem; } + .archives li:last-child { + border-bottom: 0; } + +.syndicate a { + margin-bottom: 1rem; } + +div#breadcrumbs { + padding-left: 0; } + +#sidebar { + padding-left: 3rem; } + @media only all and (max-width: 47.938rem) { + #sidebar { + padding-left: 0; } } + #sidebar .sidebar-content { + margin-bottom: 3rem; } + #sidebar .sidebar-content h4 { + margin-bottom: 1rem; } + #sidebar .sidebar-content p, #sidebar .sidebar-content ul { + margin-top: 1rem; } + +ul.pagination { + margin: 0 0 3rem; + text-align: left; } + +#error { + text-align: center; + display: flex; + align-items: center; + justify-content: center; + height: 100%; + padding-bottom: 6rem; } + #error h1 { + font-size: 5rem; } + #error p { + margin: 1rem 0; } + +.modular.header-image #body > .showcase { + margin-top: -9.5rem; + padding-top: 9rem; } +.modular.header-image #header { + background-color: rgba(255, 255, 255, 0); + box-shadow: none; } + .modular.header-image #header #logo h3 { + color: white; } + .modular.header-image #header #navbar a { + color: white; } +.modular .showcase { + padding-top: 4rem; + padding-bottom: 4rem; + background-color: #666; + background-size: cover; + background-attachment: fixed; + background-position: center; + text-align: center; + color: white; } + .modular .showcase h1 { + font-size: 4rem; + margin-top: 0; } + @media only all and (min-width: 48rem) and (max-width: 59.938rem) { + .modular .showcase h1 { + font-size: 3rem; } } + @media only all and (max-width: 47.938rem) { + .modular .showcase h1 { + font-size: 2.5rem; + line-height: 1.2; + margin-bottom: 2.5rem; } } + .modular .showcase .button { + color: white; + padding: 0.7rem 2rem; + margin-top: 2rem; + background: rgba(255, 255, 255, 0); + border: 1px solid white; + border-radius: 3px; + box-shadow: none; + font-size: 1.3rem; } + .modular .showcase .button:hover { + background: rgba(255, 255, 255, 0.2); } + +.modular .features { + padding: 3rem 0; + text-align: center; } + .modular .features:after { + content: ""; + display: table; + clear: both; } + .modular .features h2 { + margin: 0; + line-height: 100%; } + .modular .features p { + margin: 1rem 0; + font-size: 1.2rem; } + @media only all and (max-width: 47.938rem) { + .modular .features p { + font-size: 1rem; } } + .modular .features .feature-items { + margin-top: 2rem; } + .modular .features .feature { + display: block; + float: left; + width: 25%; + vertical-align: top; + margin-top: 2rem; + margin-bottom: 1rem; } + @media only all and (max-width: 47.938rem) { + .modular .features .feature { + width: 100%; } } + .modular .features .feature i.fa { + font-size: 2rem; + color: #1bb3e9; } + .modular .features .feature h4 { + margin: 0; + font-size: 1.1rem; } + .modular .features .feature p { + display: inline-block; + font-size: 1rem; + margin: 0.2rem 0 1rem; } + .modular .features.big { + text-align: center; } + .modular .features.big .feature { + width: 50%; } + .modular .features.big i.fa { + font-size: 3rem; + float: left; } + .modular .features.big .feature-content { + padding-right: 2rem; } + .modular .features.big .feature-content.push { + margin-left: 5rem; } + .modular .features.big .feature-content h4 { + font-size: 1.3rem; + text-align: left; } + .modular .features.big .feature-content p { + padding: 0; + text-align: left; } + +.callout { + background: #f6f6f6; + padding: 3rem 0.938rem; } + .callout .align-left { + float: left; + margin-right: 2rem; } + .callout .align-right { + float: right; + margin-left: 2rem; } + .callout img { + border-radius: 3px; } + +.modular .modular-row:last-child { + margin-bottom: 2rem; } + +/*# sourceMappingURL=template.css.map */ diff --git a/sandbox/grav/user/plugins/problems/html/problems.html b/sandbox/grav/user/plugins/problems/html/problems.html new file mode 100644 index 0000000000..241d6b778b --- /dev/null +++ b/sandbox/grav/user/plugins/problems/html/problems.html @@ -0,0 +1,28 @@ + + + + + Grav Problems + + + + + + + +
    +
    +

    Issues Found

    +

    Please Review and Resolve before continuing...

    + +

    + Reload Page +

    + +
      + %%PROBLEMS%% +
    +
    +
    + + diff --git a/sandbox/grav/user/plugins/problems/problems.php b/sandbox/grav/user/plugins/problems/problems.php new file mode 100644 index 0000000000..3b21f0e34b --- /dev/null +++ b/sandbox/grav/user/plugins/problems/problems.php @@ -0,0 +1,332 @@ + ['onPluginsInitialized', 100001], + 'onFatalException' => ['onFatalException', 0] + ]; + } + + public function onFatalException() + { + if ($this->isAdmin()) { + $this->active = false; + return; + } + + // Run through potential issues + if ($this->problemChecker()) { + $this->renderProblems(); + } + } + + public function onPluginsInitialized() + { + if ($this->isAdmin()) { + $this->active = false; + return; + } + + /** @var Cache $cache */ + $cache = $this->grav['cache']; + $validated_prefix = 'problem-check-'; + + $this->check = CACHE_DIR . $validated_prefix . $cache->getKey(); + + if (!file_exists($this->check)) { + // If no issues remain, save a state file in the cache + if (!$this->problemChecker()) { + // delete any existing validated files + foreach (new \GlobIterator(CACHE_DIR . $validated_prefix . '*') as $fileInfo) { + @unlink($fileInfo->getPathname()); + } + + // create a file in the cache dir so it only runs on cache changes + touch($this->check); + + } else { + $this->renderProblems(); + } + + } + } + + protected function renderProblems() + { + $theme = 'antimatter'; + + /** @var Uri $uri */ + $uri = $this->grav['uri']; + $baseUrlRelative = $uri->rootUrl(false); + $themeUrl = $baseUrlRelative . '/' . USER_PATH . basename(THEMES_DIR) . '/' . $theme; + $problemsUrl = $baseUrlRelative . '/user/plugins/problems'; + + $html = file_get_contents(__DIR__ . '/html/problems.html'); + + /** + * Process the results, ignore the statuses passed as $ignore_status + * + * @param $results + * @param $ignore_status + */ + $processResults = function ($results, $ignore_status) { + $problems = ''; + + foreach ($results as $key => $result) { + if ($key == 'files' || $key == 'apache' || $key == 'execute') { + foreach ($result as $key_text => $value_text) { + foreach ($value_text as $status => $text) { + if ($status == $ignore_status) continue; + $problems .= $this->getListRow($status, '' . $key_text . ' ' . $text); + } + } + } else { + foreach ($result as $status => $text) { + if ($status == $ignore_status) continue; + $problems .= $this->getListRow($status, $text); + } + } + } + + return $problems; + }; + + // First render the errors + $problems = $processResults($this->results, 'success'); + + // Then render the successful checks + $problems .= $processResults($this->results, 'error'); + + $html = str_replace('%%BASE_URL%%', $baseUrlRelative, $html); + $html = str_replace('%%THEME_URL%%', $themeUrl, $html); + $html = str_replace('%%PROBLEMS_URL%%', $problemsUrl, $html); + $html = str_replace('%%PROBLEMS%%', $problems, $html); + + echo $html; + http_response_code(500); + + exit(); + + + } + + protected function getListRow($status, $text) + { + if ($status == 'error') { + $icon = 'fa-times'; + } elseif ($status == 'info') { + $icon = 'fa-info'; + } else { + $icon = 'fa-check'; + } + $output = "\n"; + $output .= '
  • '. $text . '

  • '; + return $output; + } + + protected function problemChecker() + { + $min_php_version = defined('GRAV_PHP_MIN') ? GRAV_PHP_MIN : '5.4.0'; + $problems_found = false; + + $essential_files = [ + 'cache' => true, + 'logs' => true, + 'images' => true, + 'assets' => true, + 'system' => false, + 'user/data' => true, + 'user/pages' => false, + 'user/config' => false, + 'user/plugins/error' => false, + 'user/plugins' => false, + 'user/themes' => false, + 'vendor' => false + ]; + + if (version_compare(GRAV_VERSION, '0.9.27', ">=")) { + $essential_files['backup'] = true; + $backup_folder = ROOT_DIR . 'backup'; + // try to create backup folder if missing + if (!file_exists($backup_folder)) { + @mkdir($backup_folder, 0770); + } + } + + if (version_compare(GRAV_VERSION, '1.1.4', ">=")) { + $essential_files['tmp'] = true; + $tmp_folder = ROOT_DIR . 'tmp'; + // try to create tmp folder if missing + if (!file_exists($tmp_folder)) { + @mkdir($tmp_folder, 0770); + } + } + + // Perform some Apache checks + if (strpos(php_sapi_name(), 'apache') !== false) { + + $require_apache_modules = ['mod_rewrite']; + $apache_modules = apache_get_modules(); + + $apache_status = []; + + foreach ($require_apache_modules as $module) { + if (in_array($module, $apache_modules)) { + $apache_module_adjective = ' Apache module is enabled'; + $apache_module_status = 'success'; + } else { + $problems_found = true; + $apache_module_adjective = ' Apache module is not installed or enabled'; + $apache_module_status = 'error'; + } + $apache_status[$module] = [$apache_module_status => $apache_module_adjective]; + } + + if (sizeof($apache_status) > 0) { + $this->results['apache'] = $apache_status; + } + } + + // Check PHP version + if (version_compare(phpversion(), $min_php_version, '<')) { + $problems_found = true; + $php_version_adjective = 'lower'; + $php_version_status = 'error'; + + } else { + $php_version_adjective = 'greater'; + $php_version_status = 'success'; + } + $this->results['php'] = [$php_version_status => 'Your PHP version (' . phpversion() . ') is '. $php_version_adjective . ' than the minimum required: ' . $min_php_version . ' - Additional Information']; + + // Check for GD library + if (defined('GD_VERSION') && function_exists('gd_info')) { + $gd_adjective = ''; + $gd_status = 'success'; + } else { + $problems_found = true; + $gd_adjective = 'not '; + $gd_status = 'error'; + } + $this->results['gd'] = [$gd_status => 'PHP GD (Image Manipulation Library) is '. $gd_adjective . 'installed']; + + // Check for PHP CURL library + if (function_exists('curl_version')) { + $curl_adjective = ''; + $curl_status = 'success'; + } else { + $problems_found = true; + $curl_adjective = 'not '; + $curl_status = 'error'; + } + $this->results['curl'] = [$curl_status => 'PHP Curl (Data Transfer Library) is '. $curl_adjective . 'installed']; + + // Check for PHP Open SSL library + if (extension_loaded('openssl') && defined('OPENSSL_VERSION_TEXT')) { + $ssl_adjective = ''; + $ssl_status = 'success'; + } else { + $problems_found = true; + $ssl_adjective = 'not '; + $ssl_status = 'error'; + } + $this->results['ssl'] = [$ssl_status => 'PHP OpenSSL (Secure Sockets Library) is '. $ssl_adjective . 'installed']; + + // Check for PHP XML library + if (extension_loaded('xml')) { + $xml_adjective = ''; + $xml_status = 'success'; + } else { + $problems_found = true; + $xml_adjective = 'not '; + $xml_status = 'error'; + } + $this->results['xml'] = [$xml_status => 'PHP XML Library is '. $xml_adjective . 'installed']; + + // Check for PHP MbString library + if (extension_loaded('mbstring')) { + $mbstring_adjective = ''; + $mbstring_status = 'success'; + } else { + $problems_found = true; + $mbstring_adjective = 'not '; + $mbstring_status = 'error'; + } + $this->results['mbstring'] = [$mbstring_status => 'PHP Mbstring (Multibyte String Library) is '. $mbstring_adjective . 'installed']; + + // Check Exif if enabled + if ($this->grav['config']->get('system.media.auto_metadata_exif')) { + if(extension_loaded('exif')) { + $exif_adjective = ''; + $exif_status = 'success'; + } else { + $problems_found = true; + $exif_adjective = 'not '; + $exif_status = 'error'; + } + $this->results['exif'] = [$exif_status => 'PHP Exif (Exchangeable Image File Format) is '. $exif_adjective . 'installed']; + } + + // Check for PHP Zip library + if (extension_loaded('zip')) { + $zip_adjective = ''; + $zip_status = 'success'; + } else { + $problems_found = true; + $zip_adjective = 'not '; + $zip_status = 'error'; + } + $this->results['zip'] = [$zip_status => 'PHP Zip extension is '. $zip_adjective . 'installed']; + + // Check for essential files & perms + $file_problems = []; + foreach ($essential_files as $file => $check_writable) { + $file_path = ROOT_DIR . $file; + $is_dir = false; + if (!file_exists($file_path)) { + $problems_found = true; + $file_status = 'error'; + $file_adjective = 'does not exist'; + + } else { + $file_status = 'success'; + $file_adjective = 'exists'; + $is_writeable = is_writable($file_path); + $is_dir = is_dir($file_path); + + if ($check_writable) { + if (!$is_writeable) { + $file_status = 'error'; + $problems_found = true; + $file_adjective .= ' but is not writeable'; + } else { + $file_adjective .= ' and is writeable'; + } + } + } + + $file_problems[$file_path] = [$file_status => $file_adjective]; + + } + if (sizeof($file_problems) > 0) { + $this->results['files'] = $file_problems; + } + + return $problems_found; + } +} diff --git a/sandbox/grav/user/plugins/problems/problems.yaml b/sandbox/grav/user/plugins/problems/problems.yaml new file mode 100644 index 0000000000..1ab22e7451 --- /dev/null +++ b/sandbox/grav/user/plugins/problems/problems.yaml @@ -0,0 +1,2 @@ +enabled: true +built_in_css: true diff --git a/sandbox/grav/user/themes/.gitkeep b/sandbox/grav/user/themes/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/sandbox/grav/user/themes/bolt/blueprints.yaml b/sandbox/grav/user/themes/bolt/blueprints.yaml new file mode 100644 index 0000000000..1fb000f949 --- /dev/null +++ b/sandbox/grav/user/themes/bolt/blueprints.yaml @@ -0,0 +1,10 @@ +name: Bolt Pattern Lab +version: 0.0.0 +description: "A starter theme for integration between Grav CMS and Pattern Lab using the Bolt Design System" +icon: empire +author: + name: Salem Ghoweri + url: https://boltdesignsystem.com +homepage: https://github.com/bolt-design-system/bolt +bugs: https://github.com/bolt-design-system/bolt/issues +license: MIT diff --git a/sandbox/grav/user/themes/bolt/bolt.php b/sandbox/grav/user/themes/bolt/bolt.php new file mode 100644 index 0000000000..0d11eb2627 --- /dev/null +++ b/sandbox/grav/user/themes/bolt/bolt.php @@ -0,0 +1,9 @@ + + + + {% block head %} + + {% if header.title %}{{ header.title|e('html') }} | {% endif %}{{ site.title|e('html') }} + {# include '@bolt/metadata.html.twig' #} + + + + + {% block stylesheets %} + {# {% do assets.addCss('theme://css/somefile.css', 102) %} #} + {% endblock %} + {{ assets.css() }} + + {% block javascripts %} + {# {% do assets.addJs('jquery') %} #} + {# {% do assets.addJs('theme://js/somefile.js') %} #} + {% endblock %} + {{ assets.js() }} + {% endblock head %} + + + +
    + + + + + + {% block body %} + {% include "@bolt/button.twig" with { + text: "Example button" + } %} + + {% block content %}{% endblock %} + {% endblock %} + + {% block footer %} +
    +
    + {% endblock %} + + {% block bottom %} + {{ assets.js('bottom') }} + {% endblock %} +
    + + diff --git a/sandbox/grav/user/themes/bolt/templates/default.html.twig b/sandbox/grav/user/themes/bolt/templates/default.html.twig new file mode 100644 index 0000000000..d5353931ca --- /dev/null +++ b/sandbox/grav/user/themes/bolt/templates/default.html.twig @@ -0,0 +1,5 @@ +{% extends '_layouts/base.html.twig' %} + +{% block content %} + {{ page.content }} +{% endblock %} diff --git a/sandbox/grav/user/themes/bolt/templates/error.html.twig b/sandbox/grav/user/themes/bolt/templates/error.html.twig new file mode 100644 index 0000000000..7c433af8b8 --- /dev/null +++ b/sandbox/grav/user/themes/bolt/templates/error.html.twig @@ -0,0 +1,12 @@ +{% extends 'partials/base.html.twig' %} + +{% block content %} +
    +
    +

    {{ 'ERROR'|t }} {{ page.header.http_response_code }}

    +

    + {{ page.content }} +

    +
    +
    +{% endblock %} diff --git a/sandbox/grav/user/themes/bolt/templates/modular.html.twig b/sandbox/grav/user/themes/bolt/templates/modular.html.twig new file mode 100644 index 0000000000..28f7de590d --- /dev/null +++ b/sandbox/grav/user/themes/bolt/templates/modular.html.twig @@ -0,0 +1,56 @@ +{% extends 'partials/base.html.twig' %} + +{% set show_onpage_menu = header.onpage_menu == true or header.onpage_menu is null %} +{% macro pageLinkName(text) %}{{ text|lower|replace({' ':'_'}) }}{% endmacro %} + +{% block javascripts %} + {% if show_onpage_menu %} + {% do assets.add('theme://js/singlePageNav.min.js') %} + {% endif %} + {{ parent() }} +{% endblock %} + + +{% block bottom %} + {{ parent() }} + {% if show_onpage_menu %} + + {% endif %} +{% endblock %} + +{% block header_navigation %} + {% if show_onpage_menu %} + + {% else %} + {{ parent() }} + {% endif %} +{% endblock %} + +{% block content %} + {{ page.content }} + {% for module in page.collection() %} +
    + {{ module.content }} + {% endfor %} +{% endblock %} diff --git a/sandbox/grav/user/themes/bolt/templates/modular/features.html.twig b/sandbox/grav/user/themes/bolt/templates/modular/features.html.twig new file mode 100644 index 0000000000..0f34fc11db --- /dev/null +++ b/sandbox/grav/user/themes/bolt/templates/modular/features.html.twig @@ -0,0 +1,22 @@ +
    + {{ content }} +
    + {% for feature in page.header.features %} +
    + {% if feature.icon %} + +
    + {% else %} +
    + {% endif %} + {% if feature.header %} +

    {{ feature.header }}

    + {% endif %} + {% if feature.text %} +

    {{ feature.text }}

    + {% endif %} +
    +
    + {% endfor %} +
    +
    diff --git a/sandbox/grav/user/themes/bolt/templates/modular/showcase.html.twig b/sandbox/grav/user/themes/bolt/templates/modular/showcase.html.twig new file mode 100644 index 0000000000..cb3aa8e80a --- /dev/null +++ b/sandbox/grav/user/themes/bolt/templates/modular/showcase.html.twig @@ -0,0 +1,13 @@ +{% set showcase_image = page.media.images|first.grayscale().contrast(20).brightness(-125).colorize(-35,81,122) %} +{% if showcase_image %} +
    +{% else %} +
    +{% endif %} + {{ content }} + + {% for button in page.header.buttons %} + {{ button.text }} + {% endfor %} + +
    diff --git a/sandbox/grav/user/themes/bolt/templates/modular/text.html.twig b/sandbox/grav/user/themes/bolt/templates/modular/text.html.twig new file mode 100644 index 0000000000..4b058a5d9d --- /dev/null +++ b/sandbox/grav/user/themes/bolt/templates/modular/text.html.twig @@ -0,0 +1,7 @@ +
    + {% set image = page.media.images|first %} + {% if image %} + {{ image.cropResize(400,400).html('','','align-'~page.header.image_align) }} + {% endif %} +{{ content }} +
    diff --git a/sandbox/grav/webserver-configs/Caddyfile b/sandbox/grav/webserver-configs/Caddyfile new file mode 100644 index 0000000000..a3241327f8 --- /dev/null +++ b/sandbox/grav/webserver-configs/Caddyfile @@ -0,0 +1,33 @@ +:8080 +gzip +fastcgi / 127.0.0.1:9000 php + +# Begin - Security +# deny all direct access for these folders +rewrite { + r /(\.git|cache|bin|logs|backups|tests)/.*$ + to /403 +} +# deny running scripts inside core system folders +rewrite { + r /(system|vendor)/.*\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ + to /403 +} +# deny running scripts inside user folder +rewrite { + r /user/.*\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ + to /403 +} +# deny access to specific files in the root folder +rewrite { + r /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess) + to /403 +} + +status 403 /403 +## End - Security + +# global rewrite should come last. +rewrite { + to {path} {path}/ /index.php?_url={uri}&{query} +} diff --git a/sandbox/grav/webserver-configs/Caddyfile-0.8.x b/sandbox/grav/webserver-configs/Caddyfile-0.8.x new file mode 100644 index 0000000000..aaf92ceda8 --- /dev/null +++ b/sandbox/grav/webserver-configs/Caddyfile-0.8.x @@ -0,0 +1,33 @@ +# Caddyfile for Caddy 0.8.x and below + +:8080 +gzip +fastcgi / 127.0.0.1:9000 php + +# Begin - Security +# deny all direct access for these folders +rewrite { + r /(\.git|cache|bin|logs|backups|tests)/.*$ + status 403 +} +# deny running scripts inside core system folders +rewrite { + r /(system|vendor)/.*\.(txt|xml|md|html|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ + status 403 +} +# deny running scripts inside user folder +rewrite { + r /user/.*\.(txt|md|yaml|yml|php|pl|py|cgi|twig|sh|bat)$ + status 403 +} +# deny access to specific files in the root folder +rewrite { + r /(LICENSE\.txt|composer\.lock|composer\.json|nginx\.conf|web\.config|htaccess\.txt|\.htaccess) + status 403 +} +## End - Security + +# global rewrite should come last. +rewrite { + to {path} {path}/ /index.php?_url={uri}&{query} +} diff --git a/sandbox/grav/webserver-configs/htaccess.txt b/sandbox/grav/webserver-configs/htaccess.txt new file mode 100644 index 0000000000..ef79a4bc26 --- /dev/null +++ b/sandbox/grav/webserver-configs/htaccess.txt @@ -0,0 +1,75 @@ + + +RewriteEngine On + +## Begin RewriteBase +# If you are getting 500 or 404 errors on subpages, you may have to uncomment the RewriteBase entry +# You should change the '/' to your appropriate subfolder. For example if you have +# your Grav install at the root of your site '/' should work, else it might be something +# along the lines of: RewriteBase / +## + +# RewriteBase / + +## End - RewriteBase + +## Begin - X-Forwarded-Proto +# In some hosted or load balanced environments, SSL negotiation happens upstream. +# In order for Grav to recognize the connection as secure, you need to uncomment +# the following lines. +# +# RewriteCond %{HTTP:X-Forwarded-Proto} https +# RewriteRule .* - [E=HTTPS:on] +# +## End - X-Forwarded-Proto + +## Begin - Exploits +# If you experience problems on your site block out the operations listed below +# This attempts to block the most common type of exploit `attempts` to Grav +# +# Block out any script trying to base64_encode data within the URL. +RewriteCond %{QUERY_STRING} base64_encode[^(]*\([^)]*\) [OR] +# Block out any script that includes a