├── .babelrc ├── .deployignore ├── .dockerignore ├── .editorconfig ├── .env.sample ├── .eslintrc ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── question.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── test.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .modernizrrc.js ├── .nvmrc ├── .prettierignore ├── 404.php ├── CODE_OF_CONDUCT.md ├── Dockerfile ├── LICENSE ├── README.md ├── acf-json └── .gitkeep ├── bin ├── composer ├── db-to-no-upsdock ├── deploy ├── export-db ├── import ├── install ├── logs ├── rename-theme ├── setup-theme ├── start ├── uninstall ├── versionbump └── wp ├── composer.json ├── composer.lock ├── docker-compose.ups-dock.yml ├── docker-compose.yml ├── docker ├── conf │ ├── mysql │ │ └── .gitignore │ └── nginx │ │ └── nginx-site.conf └── scripts │ └── setup.sh ├── documentation ├── images │ ├── .gitignore │ └── paragraph.png ├── index.html ├── scaffold-documentation.md └── style.css ├── front-page.php ├── functions.php ├── gutenberg-acf-backups └── acf-export-2019-06-21.json ├── index.php ├── logs └── .gitignore ├── package-lock.json ├── package.json ├── page.php ├── phpcs.xml.dist ├── phpstan.neon.dist ├── plugins └── .gitkeep ├── postcss.config.js ├── prettier.config.js ├── single.php ├── src ├── Blocks │ ├── Blocks.js │ ├── Blocks.php │ ├── ImageLayout │ │ ├── 2-asymmetrical.png │ │ ├── 2-symmetrical.png │ │ ├── 3-asymmetrical.png │ │ ├── 3-symmetrical.png │ │ ├── ImageLayout.php │ │ ├── editor.scss │ │ └── style.scss │ ├── RelatedArticles │ │ ├── RelatedArticles.php │ │ ├── editor.scss │ │ └── style.scss │ ├── SampleACFBlock │ │ └── ACFBlock.php │ ├── SampleBlock │ │ ├── editor.scss │ │ ├── sample-block.js │ │ └── style.scss │ ├── editor.scss │ └── style.scss ├── Managers │ ├── ACFManager.php │ ├── CustomPostsManager.php │ ├── GutenbergManager.php │ ├── TaxonomiesManager.php │ ├── ThemeManager.php │ └── WordPressManager.php ├── Models │ └── SkelaPost.php ├── Repositories │ ├── PostTypeRepository.php │ └── Repository.php └── Services │ └── WordPressService.php ├── static ├── img │ └── skela.png ├── js │ ├── admin.js │ ├── app.js │ ├── components │ │ └── menu.js │ └── utils │ │ └── index.js └── scss │ ├── admin.scss │ ├── app.scss │ ├── base │ ├── _global.scss │ ├── _reset.scss │ └── _variables.scss │ └── components │ ├── image-layout.scss │ └── related-articles.scss ├── style.css ├── templates ├── components │ ├── acf-block.twig │ ├── footer.twig │ ├── image-layout.twig │ └── related-articles.twig ├── includes │ ├── head.twig │ └── svgs.twig ├── layouts │ └── base.twig └── pages │ ├── 404.twig │ ├── article.twig │ ├── front-page.twig │ └── page.twig ├── tests ├── pa11y.js └── phpstan │ └── bootstrap.php ├── uploads └── .gitkeep └── webpack.config.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [ 4 | [ 5 | "@babel/transform-react-jsx", 6 | { 7 | "pragma": "wp.element.createElement" 8 | } 9 | ] 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.deployignore: -------------------------------------------------------------------------------- 1 | .env 2 | .env.sample 3 | .git 4 | .gitignore 5 | bin 6 | composer.json 7 | composer.lock 8 | docker 9 | docker-compose.yml 10 | docker-compose.ups-dock.yml 11 | Dockerfile 12 | node_modules 13 | wp-content 14 | plugins 15 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /uploads 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | # Global 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | # PHP 14 | # https://github.com/pixelandtonic/CodingStandards 15 | [*.php] 16 | indent_size = 4 17 | indent_style = tab 18 | -------------------------------------------------------------------------------- /.env.sample: -------------------------------------------------------------------------------- 1 | # Directory name for this theme 2 | WORDPRESS_THEME_NAME=skela 3 | 4 | # Runtime environment 5 | WP_ENV=development 6 | 7 | # Compose File 8 | COMPOSE_FILE=docker-compose.yml:docker-compose.ups-dock.yml 9 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": "@upstatement", 4 | "env": { 5 | "browser": true, 6 | "node": true, 7 | "es6": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## How do I... 4 | 5 | - [Use This Guide](#introduction)? 6 | - Ask or Say Something? 7 | - [Request Support](#request-support) 8 | - [Report an Error or Bug](#report-an-error-or-bug) 9 | - [Request a Feature](#request-a-feature) 10 | - Make Something? 11 | - [Project Setup](#project-setup) 12 | - [Contribute Documentation](#contribute-documentation) 13 | - [Contribute Code](#contribute-code) 14 | - Manage Something 15 | - [Provide Support on Issues](#provide-support-on-issues) 16 | - [Label Issues](#label-issues) 17 | - [Clean Up Issues and PRs](#clean-up-issues-and-prs) 18 | - [Review Pull Requests](#review-pull-requests) 19 | - [Merge Pull Requests](#merge-pull-requests) 20 | - [Tag a Release](#tag-a-release) 21 | - Add a Guide Like This One [To My Project](#attribution)? 22 | 23 | ## Introduction 24 | 25 | Thank you so much for your interest in contributing!. All types of contributions are encouraged and valued. See the [table of contents](#toc) for different ways to help and details about how this project handles them! 26 | 27 | Please make sure to read the relevant section before making your contribution! It will make it a lot easier for us maintainers to make the most of it and smooth out the experience for all involved. 28 | 29 | We look forward to your contributions! 30 | 31 | ## Request Support 32 | 33 | If you have a question about this project, how to use it, or just need clarification about something: 34 | 35 | - Open an Issue at https://github.com/Upstatement/skela-wp-theme/issues 36 | - Provide as much context as you can about what you're running into. 37 | - Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. If not, please be ready to provide that information if maintainers ask for it. 38 | 39 | Once it's filed: 40 | 41 | - The project team will [label the issue](#label-issues). 42 | - Someone will try to have a response soon. 43 | - If you or the maintainers don't respond to an issue for 30 days, the [issue will be closed](#clean-up-issues-and-prs). If you want to come back to it, reply (once, please), and we'll reopen the existing issue. Please avoid filing new issues as extensions of one you already made. 44 | 45 | ## Report an Error or Bug 46 | 47 | If you run into an error or bug with the project: 48 | 49 | - Open an Issue at https://github.com/Upstatement/skela-wp-theme/issues 50 | - Include _reproduction steps_ that someone else can follow to recreate the bug or error on their own. 51 | - Provide project and platform versions (nodejs, npm, etc), depending on what seems relevant. If not, please be ready to provide that information if maintainers ask for it. 52 | 53 | Once it's filed: 54 | 55 | - The project team will [label the issue](#label-issues). 56 | - A team member will try to reproduce the issue with your provided steps. If there are no repro steps or no obvious way to reproduce the issue, the team will ask you for those steps and mark the issue as `needs-repro`. Bugs with the `needs-repro` tag will not be addressed until they are reproduced. 57 | - If the team is able to reproduce the issue, it will be marked `needs-fix`, as well as possibly other tags (such as `critical`), and the issue will be left to be [implemented by someone](#contribute-code). 58 | - If you or the maintainers don't respond to an issue for 30 days, the [issue will be closed](#clean-up-issues-and-prs). If you want to come back to it, reply (once, please), and we'll reopen the existing issue. Please avoid filing new issues as extensions of one you already made. 59 | - `critical` issues may be left open, depending on perceived immediacy and severity, even past the 30 day deadline. 60 | 61 | ## Request a Feature 62 | 63 | If the project doesn't do something you need or want it to do: 64 | 65 | - Open an Issue at https://github.com/Upstatement/skela-wp-theme/issues 66 | - Provide as much context as you can about what you're running into. 67 | - Please try and be clear about why existing features and alternatives would not work for you. 68 | 69 | Once it's filed: 70 | 71 | - The project team will [label the issue](#label-issues). 72 | - The project team will evaluate the feature request, possibly asking you more questions to understand its purpose and any relevant requirements. If the issue is closed, the team will convey their reasoning and suggest an alternative path forward. 73 | - If the feature request is accepted, it will be marked for implementation with `feature-accepted`, which can then be done by either by a core team member or by anyone in the community who wants to [contribute code](#contribute-code). 74 | 75 | Note: The team is unlikely to be able to accept every single feature request that is filed. Please understand if they need to say no. 76 | 77 | ## Project Setup 78 | 79 | So you wanna contribute some code! That's great! This project uses GitHub Pull Requests to manage contributions, so [read up on how to fork a GitHub project and file a PR](https://guides.github.com/activities/forking) if you've never done it before. 80 | 81 | If this seems like a lot or you aren't able to do all this setup, you might also be able to [edit the files directly](https://help.github.com/articles/editing-files-in-another-user-s-repository/) without having to do any of this setup. Yes, [even code](#contribute-code). 82 | 83 | If you want to go the usual route and run the project locally, though: 84 | 85 | - [Fork the project](https://guides.github.com/activities/forking/#fork) 86 | 87 | Then in your terminal: 88 | 89 | - `./bin/install.sh` 90 | - `open https://ups.dock` 91 | 92 | And you should be ready to go! 93 | 94 | ## Contribute Documentation 95 | 96 | Documentation is a super important, critical part of this project. Docs are how we keep track of what we're doing, how, and why. It's how we stay on the same page about our policies. And it's how we tell others everything they need in order to be able to use this project -- or contribute to it. So thank you in advance. 97 | 98 | Documentation contributions of any size are welcome! Feel free to file a PR even if you're just rewording a sentence to be more clear, or fixing a spelling mistake! 99 | 100 | To contribute documentation: 101 | 102 | - [Set up the project](#project-setup). 103 | - Edit or add any relevant documentation. 104 | - Make sure your changes are formatted correctly and consistently with the rest of the documentation. 105 | - Re-read what you wrote, and run a spellchecker on it to make sure you didn't miss anything. 106 | - In your commit message(s), begin the first line with `docs:`. For example: `docs: Adding a doc contrib section to CONTRIBUTING.md`. 107 | - Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md). Documentation commits should use `docs(): `. 108 | - Go to https://github.com/Upstatement/skela-wp-theme/pulls and open a new pull request with your changes. 109 | - If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing. 110 | 111 | Once you've filed the PR: 112 | 113 | - One or more maintainers will use GitHub's review feature to review your PR. 114 | - If the maintainer asks for any changes, edit your changes, push, and ask for another review. 115 | - If the maintainer decides to pass on your PR, they will thank you for the contribution and explain why they won't be accepting the changes. That's ok! We still really appreciate you taking the time to do it, and we don't take that lightly. 116 | - If your PR gets accepted, it will be marked as such, and merged into the `master` branch soon after. Your contribution will be distributed to the masses next time the maintainers [tag a release](#tag-a-release) 117 | 118 | ## Contribute Code 119 | 120 | We like code commits a lot! They're super handy, and they keep the project going and doing the work it needs to do to be useful to others. 121 | 122 | Code contributions of just about any size are acceptable! 123 | 124 | The main difference between code contributions and documentation contributions is that contributing code requires inclusion of relevant tests for the code being added or changed. Contributions without accompanying tests will be held off until a test is added, unless the maintainers consider the specific tests to be either impossible, or way too much of a burden for such a contribution. 125 | 126 | To contribute code: 127 | 128 | - [Set up the project](#project-setup). 129 | - Make any necessary changes to the source code. 130 | - Include any [additional documentation](#contribute-documentation) the changes might need. 131 | - Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md). 132 | - Dependency updates, additions, or removals must be in individual commits, and the message must use the format: `(deps): PKG@VERSION`, where `` is any of the usual `conventional-changelog` prefixes, at your discretion. 133 | - Go to https://github.com/Upstatement/skela-wp-theme/pulls and open a new pull request with your changes. 134 | - If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing. 135 | 136 | Once you've filed the PR: 137 | 138 | - One or more maintainers will use GitHub's review feature to review your PR. 139 | - If the maintainer asks for any changes, edit your changes, push, and ask for another review. Additional tags (such as `needs-tests`) will be added depending on the review. 140 | - If the maintainer decides to pass on your PR, they will thank you for the contribution and explain why they won't be accepting the changes. That's ok! We still really appreciate you taking the time to do it, and we don't take that lightly. 141 | - If your PR gets accepted, it will be marked as such, and merged into the `main` branch soon after. Your contribution will be distributed to the masses next time the maintainers [tag a release](#tag-a-release) 142 | 143 | ## Provide Support on Issues 144 | 145 | Helping out other users with their questions is a really awesome way of contributing to any community. It's not uncommon for most of the issues on an open source projects being support-related questions by users trying to understand something they ran into, or find their way around a known bug. 146 | 147 | Sometimes, the `support` label will be added to things that turn out to actually be other things, like bugs or feature requests. In that case, suss out the details with the person who filed the original issue, add a comment explaining what the bug is, and change the label from `support` to `bug` or `feature`. If you can't do this yourself, @mention a maintainer so they can do it. 148 | 149 | In order to help other folks out with their questions: 150 | 151 | - Go to the issue tracker and [filter open issues by the `support` label](https://github.com/Upstatement/skela-wp-theme/issues?q=is%3Aopen+is%3Aissue+label%3Asupport). 152 | - Read through the list until you find something that you're familiar enough with to give an answer to. 153 | - Respond to the issue with whatever details are needed to clarify the question, or get more details about what's going on. 154 | - Once the discussion wraps up and things are clarified, either close the issue, or ask the original issue filer (or a maintainer) to close it for you. 155 | 156 | Some notes on picking up support issues: 157 | 158 | - Avoid responding to issues you don't know you can answer accurately. 159 | - As much as possible, try to refer to past issues with accepted answers. Link to them from your replies with the `#123` format. 160 | - Be kind and patient with users -- often, folks who have run into confusing things might be upset or impatient. This is ok. Try to understand where they're coming from, and if you're too uncomfortable with the tone, feel free to stay away or withdraw from the issue. (note: if the user is outright hostile or is violating the CoC, [refer to the Code of Conduct](CODE_OF_CONDUCT.md) to resolve the conflict). 161 | 162 | ## Label Issues 163 | 164 | One of the most important tasks in handling issues is labeling them usefully and accurately. All other tasks involving issues ultimately rely on the issue being classified in such a way that relevant parties looking to do their own tasks can find them quickly and easily. 165 | 166 | In order to label issues, [open up the list of unlabeled issues](https://github.com/Upstatement/skela-wp-theme/issues?q=is%3Aopen+is%3Aissue+no%3Alabel) and, **from newest to oldest**, read through each one and apply issue labels according to the table below. If you're unsure about what label to apply, skip the issue and try the next one: don't feel obligated to label each and every issue yourself! 167 | 168 | | Label | Apply When | Notes | 169 | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 170 | | `bug` | Cases where the code (or documentation) is behaving in a way it wasn't intended to. | If something is happening that surprises the _user_ but does not go against the way the code is designed, it should use the `enhancement` label. | 171 | | `critical` | Added to `bug` issues if the problem described makes the code completely unusable in a common situation. | 172 | | `documentation` | Added to issues or pull requests that affect any of the documentation for the project. | Can be combined with other labels, such as `bug` or `enhancement`. | 173 | | `duplicate` | Added to issues or PRs that refer to the exact same issue as another one that's been previously labeled. | Duplicate issues should be marked and closed right away, with a message referencing the issue it's a duplicate of (with `#123`) | 174 | | `enhancement` | Added to [feature requests](#request-a-feature), PRs, or documentation issues that are purely additive: the code or docs currently work as expected, but a change is being requested or suggested. | 175 | | `help wanted` | Applied by [Committers](#join-the-project-team) to issues and PRs that they would like to get outside help for. Generally, this means it's lower priority for the maintainer team to itself implement, but that the community is encouraged to pick up if they so desire | Never applied on first-pass labeling. | 176 | | `in-progress` | Applied by [Committers](#join-the-project-team) to PRs that are pending some work before they're ready for review. | The original PR submitter should @mention the team member that applied the label once the PR is complete. | 177 | | `performance` | This issue or PR is directly related to improving performance. | 178 | | `refactor` | Added to issues or PRs that deal with cleaning up or modifying the project for the betterment of it. | 179 | | `starter` | Applied by [Committers](#join-the-project-team) to issues that they consider good introductions to the project for people who have not contributed before. These are not necessarily "easy", but rather focused around how much context is necessary in order to understand what needs to be done for this project in particular. | Existing project members are expected to stay away from these unless they increase in priority. | 180 | | `support` | This issue is either asking a question about how to use the project, clarifying the reason for unexpected behavior, or possibly reporting a `bug` but does not have enough detail yet to determine whether it would count as such. | The label should be switched to `bug` if reliable reproduction steps are provided. Issues primarily with unintended configurations of a user's environment are not considered bugs, even if they cause crashes. | 181 | | `tests` | This issue or PR either requests or adds primarily tests to the project. | If a PR is pending tests, that will be handled through the [PR review process](#review-pull-requests) | 182 | | `wontfix` | Labelers may apply this label to issues that clearly have nothing at all to do with the project or are otherwise entirely outside of its scope/sphere of influence. [Committers](#join-the-project-team) may apply this label and close an issue or PR if they decide to pass on an otherwise relevant issue. | The issue or PR should be closed as soon as the label is applied, and a clear explanation provided of why the label was used. Contributors are free to contest the labeling, but the decision ultimately falls on committers as to whether to accept something or not. | 183 | 184 | ## Clean Up Issues and PRs 185 | 186 | [Needs Collaborator](#join-the-project-team): Issue Tracker 187 | 188 | Issues and PRs can go stale after a while. Maybe they're abandoned. Maybe the team will just plain not have time to address them any time soon. 189 | 190 | In these cases, they should be closed until they're brought up again or the interaction starts over. 191 | 192 | To clean up issues and PRs: 193 | 194 | - Search the issue tracker for issues or PRs, and add the term `updated:<=YYYY-MM-DD`, where the date is 30 days before today. 195 | - Go through each issue _from oldest to newest_, and close them if **all of the following are true**: 196 | - not opened by a maintainer 197 | - not marked as `critical` 198 | - not marked as `starter` or `help wanted` (these might stick around for a while, in general, as they're intended to be available) 199 | - no explicit messages in the comments asking for it to be left open 200 | - does not belong to a milestone 201 | - Leave a message when closing saying "Cleaning up stale issue. Please reopen or ping us if and when you're ready to resume this. See the [contributing guidelines](https://github.com/Upstatement/skela-wp-theme/blob/main/CONTRIBUTING.md#clean-up-issues-and-prs) for more details." 202 | 203 | ## Review Pull Requests 204 | 205 | While anyone can comment on a PR, add feedback, etc, PRs are only _approved_ by team members with Issue Tracker or higher permissions. 206 | 207 | PR reviews use [GitHub's own review feature](https://help.github.com/articles/about-pull-request-reviews/), which manages comments, approval, and review iteration. 208 | 209 | Some notes: 210 | 211 | - You may ask for minor changes ("nitpicks"), but consider whether they are really blockers to merging: try to err on the side of "approve, with comments". 212 | - Please make sure you're familiar with the code or documentation being updated, unless it's a minor change (spellchecking, minor formatting, etc). You may @mention another project member who you think is better suited for the review, but still provide a non-approving review of your own. 213 | - Be extra kind: people who submit code/doc contributions are putting themselves in a pretty vulnerable position, and have put time and care into what they've done (even if that's not obvious to you!) -- always respond with respect, be understanding, but don't feel like you need to sacrifice your standards for their sake, either. Just don't be a jerk about it? 214 | 215 | ## Merge Pull Requests 216 | 217 | TBD - need to hash out a bit more of this process. 218 | 219 | ## Tag A Release 220 | 221 | TBD - need to hash out a bit more of this process. The most important bit here is probably that all tests must pass, and tags must use [semver](https://semver.org). 222 | 223 | ## Attribution 224 | 225 | This guide was generated using the WeAllJS `CONTRIBUTING.md` generator. [Make your own](https://npm.im/weallcontribute)! 226 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 🐛 3 | about: Something isn't working as expected? Here is the right place to report. 4 | --- 5 | 6 | # Description 7 | 8 | Describe the issue that you're seeing. 9 | 10 | ## Steps to reproduce 11 | 12 | Clear steps describing how to reproduce the issue. 13 | 14 | ## Expected result 15 | 16 | What should happen? 17 | 18 | ## Actual result 19 | 20 | What happened. 21 | 22 | ## Environment 23 | 24 | - OS: 25 | - Browser and its version: 26 | - Docker version: 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 💡 3 | about: Suggest a new idea for the project. 4 | --- 5 | 6 | # Summary 7 | 8 | Brief explanation of the feature. 9 | 10 | ## Basic example 11 | 12 | If the proposal involves a new or changed API, include a basic code example. Omit this section if it's not applicable. 13 | 14 | ## Motivation 15 | 16 | Why are we doing this? What use cases does it support? What is the expected outcome? 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 🤔 3 | about: Usage question or discussion about Ups Dock. 4 | --- 5 | 6 | # Summary 7 | 8 | ## Relevant information 9 | 10 | 11 | 12 | ### Environment (if relevant) 13 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | (changes here) 4 | 5 | (image of expected results) 6 | 7 | ## How To Test 8 | 9 | (necessary config changes) 10 | 11 | (how to access the new / changed functionality -- fixtures, URLs) 12 | 13 | ## TODOs: 14 | 15 | - [ ] Updated editor documentation 16 | - [ ] Updated Trello status 17 | 18 | ### Optional: 19 | 20 | - [ ] Run and add accessibility test 21 | - [ ] Add Cypress interaction test 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | lint: 7 | name: Test 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v2 11 | 12 | - name: Read .nvmrc 13 | run: echo ::set-output name=NVMRC::$(cat .nvmrc) 14 | id: node_version 15 | 16 | - name: Setup node 17 | uses: actions/setup-node@v1 18 | with: 19 | node-version: '${{ steps.node_version.outputs.NVMRC }}' 20 | 21 | - name: Load PHP dependencies 22 | uses: php-actions/composer@v1 23 | 24 | - name: Install dependencies 25 | run: npm ci 26 | 27 | - name: Build 28 | run: npm run build 29 | 30 | - name: Check bundlesize 31 | run: npm run test:bundlesize 32 | 33 | - name: Lint scripts 34 | run: npm run lint:scripts 35 | 36 | - name: Lint PHP 37 | run: npm run lint:php 38 | 39 | - name: Run static analysis 40 | run: npm run phpstan 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OS 2 | .DS_Store 3 | Thumbs.db 4 | 5 | # IDE and local environment 6 | /.vscode/ 7 | 8 | # Packages 9 | /vendor/ 10 | /node_modules/ 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Application 16 | .env 17 | min-manifest.json 18 | /uploads/* 19 | !/uploads/.gitkeep 20 | /plugins/* 21 | !/plugins/.gitkeep 22 | 23 | # Tests 24 | .eslintcache 25 | 26 | # Distribution 27 | /dist/ 28 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npm run lint-staged && npm run lint:php 5 | -------------------------------------------------------------------------------- /.modernizrrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: ['setClasses', 'addTest', 'html5printshiv', 'testProp', 'fnBind'], 3 | 'feature-detects': ['emoji', 'test/css/flexbox', 'test/es6/promises'], 4 | }; 5 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.13.1 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.twig 2 | -------------------------------------------------------------------------------- /404.php: -------------------------------------------------------------------------------- 1 | Alex: "Yeah I used X and it was really crazy!" 98 | 99 | > Patt (not a maintainer): "Hey, could you not use that word? What about 'ridiculous' instead?" 100 | 101 | > Alex: "oh sorry, sure." -> edits old comment to say "it was really confusing!" 102 | 103 | #### The Maintainer Case 104 | 105 | Sometimes, though, you need to get maintainers involved. Maintainers will do their best to resolve conflicts, but people who were harmed by something **will take priority**. 106 | 107 | > Patt: "Honestly, sometimes I just really hate using \$library and anyone who uses it probably sucks at their job." 108 | 109 | > Alex: "Whoa there, could you dial it back a bit? There's a CoC thing about attacking folks' tech use like that." 110 | 111 | > Patt: "I'm not attacking anyone, what's your problem?" 112 | 113 | > Alex: "@maintainers hey uh. Can someone look at this issue? Patt is getting a bit aggro. I tried to nudge them about it, but nope." 114 | 115 | > KeeperOfCommitBits: (on issue) "Hey Patt, maintainer here. Could you tone it down? This sort of attack is really not okay in this space." 116 | 117 | > Patt: "Leave me alone I haven't said anything bad wtf is wrong with you." 118 | 119 | > KeeperOfCommitBits: (deletes user's comment), "@patt I mean it. Please refer to the CoC over at (URL to this CoC) if you have questions, but you can consider this an actual warning. I'd appreciate it if you reworded your messages in this thread, since they made folks there uncomfortable. Let's try and be kind, yeah?" 120 | 121 | > Patt: "@keeperofbits Okay sorry. I'm just frustrated and I'm kinda burnt out and I guess I got carried away. I'll DM Alex a note apologizing and edit my messages. Sorry for the trouble." 122 | 123 | > KeeperOfCommitBits: "@patt Thanks for that. I hear you on the stress. Burnout sucks :/. Have a good one!" 124 | 125 | #### The Nope Case 126 | 127 | > PepeTheFrog🐸: "Hi, I am a literal actual nazi and I think white supremacists are quite fashionable." 128 | 129 | > Patt: "NOOOOPE. OH NOPE NOPE." 130 | 131 | > Alex: "JFC NO. NOPE. @keeperofbits NOPE NOPE LOOK HERE" 132 | 133 | > KeeperOfCommitBits: "👀 Nope. NOPE NOPE NOPE. 🔥" 134 | 135 | > PepeTheFrog🐸 has been banned from all organization or user repositories belonging to KeeperOfCommitBits. 136 | 137 | ## Attribution 138 | 139 | This Code of Conduct was generated using [WeAllJS Code of Conduct Generator](https://npm.im/weallbehave), which is based on the [WeAllJS Code of 140 | Conduct](https://wealljs.org/code-of-conduct), which is itself based on 141 | [Contributor Covenant](http://contributor-covenant.org), version 1.4, available 142 | at 143 | [http://contributor-covenant.org/version/1/4](http://contributor-covenant.org/version/1/4), 144 | and the LGBTQ in Technology Slack [Code of 145 | Conduct](http://lgbtq.technology/coc.html). 146 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Note: 1.10.4 fails to initialize 2 | # https://gitlab.com/ric_harvey/nginx-php-fpm/-/issues/307 3 | FROM richarvey/nginx-php-fpm:1.10.3 4 | 5 | RUN apk upgrade && apk update && apk add \ 6 | mysql-client \ 7 | openssl \ 8 | msmtp \ 9 | less \ 10 | tzdata \ 11 | mariadb-connector-c 12 | 13 | # Configure msmtp 14 | RUN { \ 15 | echo "account default"; \ 16 | echo "host mailhog"; \ 17 | echo "port 1025"; \ 18 | echo "auto_from on"; \ 19 | } > /etc/msmtprc 20 | 21 | # Configure PHP to use msmtp for sending mail 22 | RUN { \ 23 | echo 'sendmail_path = "/usr/bin/msmtp -t -i"'; \ 24 | } > /usr/local/etc/php/conf.d/mail.ini 25 | 26 | # Install and configure WP CLI 27 | RUN wget https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar; \ 28 | chmod +x wp-cli.phar; \ 29 | mv wp-cli.phar /usr/local/bin/; \ 30 | # Workaround for root usage scolding. 31 | echo -e "#!/bin/bash\n\n/usr/local/bin/wp-cli.phar \"\$@\" --allow-root\n" > /usr/local/bin/wp; \ 32 | chmod +x /usr/local/bin/wp; \ 33 | # Add bash completons for interactive usage. 34 | wget https://github.com/wp-cli/wp-cli/raw/master/utils/wp-completion.bash; \ 35 | mv wp-completion.bash $HOME; \ 36 | echo -e "source $HOME/wp-completion.bash\n" > $HOME/.bashrc 37 | 38 | # Download WordPress 39 | RUN wp core download 40 | 41 | # Copy custom configuration files into location expected by nginx-php-fpm. 42 | # See https://github.com/richarvey/nginx-php-fpm/blob/master/docs/nginx_configs.md 43 | COPY docker/conf /var/www/html/conf 44 | 45 | # Copy startup scripts into location expected by nginx-php-fpm. 46 | # See https://github.com/richarvey/nginx-php-fpm/blob/master/docs/scripting_templating.md 47 | COPY docker/scripts /var/www/html/scripts 48 | 49 | # Copy the rest of this theme into place 50 | ARG WORDPRESS_THEME_NAME 51 | COPY . /var/www/html/wp-content/themes/${WORDPRESS_THEME_NAME} 52 | COPY plugins /var/www/html/wp-content/plugins 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Upstatement 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 💀 Skela 2 | 3 | > An Upstatement-flavored starter theme for WordPress 4 | 5 | Skela utilizes repositories, managers, services, and models for an [object-oriented approach](#-object-oriented-approach) to organizing your WordPress data. 6 | 7 | Note that this repository is _just_ for your WordPress theme. The WordPress installation should live elsewhere. 8 | 9 | ## Table of Contents 10 | 11 | - [💀 Skela](#-skela) 12 | - [Table of Contents](#table-of-contents) 13 | - [🎁 What's in the Box](#-whats-in-the-box) 14 | - [💻 System Requirements](#-system-requirements) 15 | - [🛠 Installation](#-installation) 16 | - [Option 1: Contributing to Skela](#option-1-contributing-to-skela) 17 | - [Option 2: Using Skela as a template for another project](#option-2-using-skela-as-a-template-for-another-project) 18 | - [Activating ACF \& WP Migrate Plugins (Optional)](#activating-acf--wp-migrate-plugins-optional) 19 | - [🏃‍ Development Workflow](#-development-workflow) 20 | - [Debugging](#debugging) 21 | - [Twig Functions](#twig-functions) 22 | - [Error Logs](#error-logs) 23 | - [Debug Bar \& Timber Debug Bar Plugins](#debug-bar--timber-debug-bar-plugins) 24 | - [Common `wp-cli` commands](#common-wp-cli-commands) 25 | - [� Deployment](#-deployment) 26 | - [🔄 Object-Oriented Approach](#-object-oriented-approach) 27 | - [Managers](#managers) 28 | - [Models](#models) 29 | - [Repositories](#repositories) 30 | - [Services](#services) 31 | - [📰 Gutenberg](#-gutenberg) 32 | - [Creating Custom ACF Blocks](#creating-custom-acf-blocks) 33 | - [Included Custom Blocks](#included-custom-blocks) 34 | - [♿ Accessibility Testing](#-accessibility-testing) 35 | - [Configuring pa11y](#configuring-pa11y) 36 | - [🌐 Configuring Multisite](#-configuring-multisite) 37 | - [For Subdomain](#for-subdomain) 38 | - [Caveats](#caveats) 39 | - [👨‍👩‍👧‍👦 Contributing](#-contributing) 40 | - [📗 Code of Conduct](#-code-of-conduct) 41 | - [About Upstatement](#about-upstatement) 42 | 43 | ## 🎁 What's in the Box 44 | 45 | - Full [Timber](https://www.upstatement.com/timber/) integration 46 | - Built-in support for [Ups Dock](https://github.com/Upstatement/ups-dock), so you can get a full WordPress site up a running with a few commands 47 | - Easy documentation creation with [Flatdoc](http://ricostacruz.com/flatdoc/) 48 | - Code bundling with [Webpack](https://webpack.js.org/), including: 49 | - [BrowserSync](https://www.npmjs.com/package/browser-sync-webpack-plugin) 50 | - [Autoprefixer](https://github.com/postcss/autoprefixer) 51 | - [CSS Extraction](https://www.npmjs.com/package/mini-css-extract-plugin) 52 | - [Environment Variable Injection](https://www.npmjs.com/package/dotenv-webpack) 53 | - Some really great WordPress plugins (and plugin management provided by [Composer](https://getcomposer.org/)) 54 | - [Advanced Custom Fields (ACF)](https://www.advancedcustomfields.com/) 55 | - [WP Migrate DB Pro](https://deliciousbrains.com/wp-migrate-db-pro/) 56 | - [WP Environment Indicator](https://github.com/jon-heller/wp-environment-indicator) 57 | - Some useful PHP libraries 58 | - [phpdotenv](https://github.com/vlucas/phpdotenv) 59 | - [carbon](https://carbon.nesbot.com/) 60 | - Linting and testing 61 | - JS, CSS, and PHP linting thanks to [Prettier](https://github.com/prettier/prettier), [ESLint](https://eslint.org/), and [phpcs](https://github.com/squizlabs/PHP_CodeSniffer) 62 | - Static analysis of PHP code with [PHPStan](https://phpstan.org/) 63 | - Accessibility testing with [pa11y](https://github.com/pa11y/pa11y) 64 | - Bundle size limiting with [bundlesize](https://github.com/siddharthkp/bundlesize) 65 | - [Husky](https://github.com/typicode/husky) to automatically run these lints and tests! 66 | - CI setup with [GitHub Actions](https://help.github.com/en/actions) 67 | 68 | ## 💻 System Requirements 69 | 70 | Before you can start on your theme, you first need a way to run a LAMP/LEMP (Linux, Apache/nginx, MySQL, PHP) stack on your machine. 71 | 72 | We recommend our very own Docker setup called Ups Dock. To install it follow these steps: 73 | 74 | 1. Install [Docker for Mac](https://www.docker.com/docker-mac) 75 | 76 | 2. Install [Ups Dock](https://github.com/upstatement/ups-dock) by following the installation steps in the [README](https://github.com/upstatement/ups-dock#installation) 77 | 78 | ## 🛠 Installation 79 | 80 | 1. Ensure [NVM](https://github.com/creationix/nvm) and [NPM](https://www.npmjs.com/) are installed globally. 81 | 82 | 2. Run `nvm install` to ensure you're using the correct version of Node. 83 | 84 | 3. If you're _not_ using Ups Dock, you can stop here! Otherwise... 85 | 86 | 4. Duplicate the contents of `.env.sample` into a new `.env` file 87 | 88 | If you **do not want to use Ups Dock**, change the `COMPOSE_FILE` line in your `.env` to be: 89 | 90 | ```shell 91 | COMPOSE_FILE=docker-compose.yml 92 | ``` 93 | 94 | **TIP:** To prevent build errors, make sure there are no commented out lines including the `COMPOSE_FILE` variable in your `.env` file. 95 | 96 | ### Option 1: Contributing to Skela 97 | 98 | 1. If you're installing this repository to contribute to Skela, all you need to do next is run the install command 99 | 100 | ```sh 101 | ./bin/install 102 | ``` 103 | 104 | 2. Once the install script succeeds, fire up your site with the start command 105 | 106 | ```sh 107 | ./bin/start 108 | ``` 109 | 110 | Now you should be able to access your WordPress site on [`ups.dock`](http://ups.dock)! 111 | 112 | The default credentials for WP admin are `admin` / `password` (configurable via `docker-compose.yml`) 113 | 114 | ### Option 2: Using Skela as a template for another project 115 | 116 | If you're using Skela as a template for another project, there's a few more steps to go through in order to set up the project to use your desired theme name. 117 | 118 | 1. Run the rename theme command and follow the prompt, which will set up the project with your desired theme name 119 | 120 | ```shell 121 | ./bin/rename-theme 122 | ``` 123 | 124 | 2. In `package.json` and `composer.json`, update repository and author information 125 | 126 | 3. Run the install command 127 | 128 | ```shell 129 | ./bin/install 130 | ``` 131 | 132 | 4. Once the installation script has finished, run the start command 133 | 134 | ```shell 135 | ./bin/start 136 | ``` 137 | 138 | 5. In another terminal tab, run the setup theme command, which will activate your theme and update the seed database 139 | 140 | ```shell 141 | ./bin/setup-theme 142 | ``` 143 | 144 | The site should be up and running with BrowserSync at , which proxies if you're using Ups Dock, or if you're not. 145 | 146 | To access WP admin, visit `/wp-admin`. The default credentials are `admin` / `password` (configurable via `docker-compose.yml`) 147 | 148 | **(Optional)** If you're running an Ups Dock build and you want to re-export the seed database without Ups Dock URLs, run the following command: 149 | 150 | ```shell 151 | ./bin/db-to-no-upsdock 152 | ``` 153 | 154 | ### Activating ACF & WP Migrate Plugins (Optional) 155 | 156 | If you would like to use the [Advanced Custom Fields (ACF)](https://www.advancedcustomfields.com/) and [WP Migrate DB Pro](https://deliciousbrains.com/wp-migrate-db-pro/) plugins, follow these steps: 157 | 158 | 1. Purchase license keys from [ACF](https://www.advancedcustomfields.com/pro/#pricing-table) and [WP Migrate DB Pro](https://deliciousbrains.com/wp-migrate-db-pro/pricing/) 159 | 160 | 2. In `composer.json` add the following to the `"repositories"` array 161 | 162 | ```json 163 | { 164 | "type": "composer", 165 | "url": "https://composer.deliciousbrains.com" 166 | }, 167 | { 168 | "type": "package", 169 | "package": { 170 | "name": "advanced-custom-fields/advanced-custom-fields-pro", 171 | "version": "5.12.3", 172 | "type": "wordpress-plugin", 173 | "dist": { 174 | "type": "zip", 175 | "url": "https://connect.advancedcustomfields.com/index.php?a=download&p=pro&k=ACF_KEY&t=5.12.3" 176 | } 177 | } 178 | }, 179 | ``` 180 | 181 | 3. Replace `ACF_KEY` with your license key 182 | 183 | 4. Create a file called `auth.json` in the root directory and populate it with your API credentials from [your Account settings page](https://deliciousbrains.com/my-account/settings/) 184 | 185 | ```json 186 | { 187 | "http-basic": { 188 | "composer.deliciousbrains.com": { 189 | "username": "COMPOSER_API_KEY_USERNAME", 190 | "password": "COMPOSER_API_KEY_PASSWORD" 191 | } 192 | } 193 | } 194 | ``` 195 | 196 | 5. In `composer.json` add the following to the `"require"` object 197 | 198 | ```json 199 | "advanced-custom-fields/advanced-custom-fields-pro": "5.11.1", 200 | "deliciousbrains-plugin/wp-migrate-db-pro": "^2.2", 201 | "deliciousbrains-plugin/wp-migrate-db-pro-cli": "^1.6", 202 | "deliciousbrains-plugin/wp-migrate-db-pro-media-files": "^2.1", 203 | ``` 204 | 205 | 6. While the container is up, run `./bin/composer update` 206 | 207 | ## 🏃‍ Development Workflow 208 | 209 | 1. Run `nvm use` to ensure you're using the correct version of Node 210 | 211 | 2. Run the start command to start the container and webpack server 212 | 213 | ```shell 214 | ./bin/start 215 | ``` 216 | 217 | 3. Visit the localhost URL in your browser 218 | 219 | By default this is , which proxies your project's Ups Dock URL (i.e. ) 220 | 221 | 4. Access the WP Admin Dashboard at `/wp-admin` (i.e. ) 222 | 223 | To shut down the container and development server, type `Ctrl+C` 224 | 225 | ### Debugging 226 | 227 | 228 | 229 | #### Twig Functions 230 | 231 | In twig files, there are two common function you can use to print variables to the page: `dump()` and `print_r`. 232 | 233 | ```html 234 |
235 |   {{ dump(your_variable) }}
236 | 
237 | 238 | {{ your_variable | print_r }} 239 | ``` 240 | 241 | #### Error Logs 242 | 243 | The gitignored `logs/error.log` file is a good place to look when hitting “critical error” screens during development. You can print to them using the `error_log` function, and can track updates to them in realtime using the following command: 244 | 245 | ```shell 246 | ./bin/logs 247 | ``` 248 | 249 | #### Debug Bar & Timber Debug Bar Plugins 250 | 251 | For more in-depth information like showing query, cache, and other helpful debugging information, this repository includes the [Debug Bar](https://wordpress.org/plugins/debug-bar/) and [Timber Debug Bar](https://wordpress.org/plugins/debug-bar-timber/) plugins. 252 | 253 | ### Common `wp-cli` commands 254 | 255 | If you've installed this theme using Ups Dock, you can use [WP CLI](https://developer.wordpress.org/cli/commands/cli/) with the [`wp` script](/blob/main/bin/wp). 256 | 257 | Start the Docker containers with `./bin/start` and then run any of the following commands in a separate shell: 258 | 259 | ```shell 260 | ./bin/wp [command] 261 | ``` 262 | 263 | To update the local WordPress version: 264 | 265 | ```shell 266 | ./bin/wp core update 267 | ``` 268 | 269 | To export the database: 270 | 271 | ```shell 272 | ./bin/wp db export - > docker/conf/mysql/init-ups-dock.sql 273 | ``` 274 | 275 | To export the database and gzip it: 276 | 277 | ```shell 278 | ./bin/wp db export - | gzip -3 > docker/conf/mysql/init-ups-dock.sql.gz 279 | ``` 280 | 281 | To SSH into the WordPress container: 282 | 283 | ```shell 284 | docker-compose exec wordpress /bin/bash 285 | ``` 286 | 287 | ## 🚀 Deployment 288 | 289 | When creating a deployment, we recommend generating a new release for your project with an appropriate version bump to the theme's version. This will help facilitate cache-busting for static assets, which receive the theme's version as a query string appended to the end of the path. 290 | 291 | You can use the following script to bump the version numbers in this project's `package.json` and the theme's `style.css` (which is where the theme pulls the canonical version from): 292 | 293 | ```sh 294 | ./bin/versionbump [ | major | minor | patch | premajor | preminor | prepatch | prerelease] 295 | ``` 296 | 297 | By default, running the script with no arguments will result in a patch version bump (so, from `1.0.1` to `1.0.2`). The script utilizes [`npm-version`](https://docs.npmjs.com/cli/v7/commands/npm-version) behind the scenes to define the new version number; see [those docs](https://docs.npmjs.com/cli/v7/commands/npm-version) for more information on the available version options. 298 | 299 | ## 🔄 Object-Oriented Approach 300 | 301 | This theme utilizes repositories, managers, services and models for a very object-oriented approach to organizing your WordPress data. 302 | 303 | ### Managers 304 | 305 | Managers do things like: 306 | 307 | - set up your theme (register option pages, hide dashboard widgets, enqueue JS and CSS, etc.) 308 | - create custom post types and taxonomies 309 | - set up basic WordPress defaults 310 | 311 | ### Models 312 | 313 | Models hold and extend your data. 314 | 315 | Have a press release post type that needs a bunch of extra functions? Create a class for them (extending `Timber\Post`) and put your logic there so you can keep your Twig clean! 316 | 317 | ### Repositories 318 | 319 | Repositories are a good place to put query related logic. 320 | 321 | It could be used in situations like the following: 322 | 323 | > Let get me all the posts from September in the hot dog category! 324 | 325 | ### Services 326 | 327 | Services are for more low-lying functions, like routing. 328 | 329 | ## 📰 Gutenberg 330 | 331 | This theme has built-in support for easily creating custom Gutenberg blocks with the help of Advanced Custom Fields. Note that the pro version of ACF is required for this. 332 | 333 | There is an example custom block under `src/Blocks/SampleACFBlock/ACFBlock.php`. This demonstrates creating a block using ACF functions that includes two fields. Those fields are rendered in the file `templates/components/acf-block.twig`. 334 | 335 | Note that in order to get this example to work, you need to create a ACF field group containing two fields, `some_headline` and `some_text`, and then have the field group displayed if the block is equal to ACF block. 336 | 337 | Read more details on [creating Gutenberg blocks using ACF](https://www.advancedcustomfields.com/resources/blocks/) 338 | 339 | ### Creating Custom ACF Blocks 340 | 341 | 1. Create a new ACF block class file in `/src/Blocks`. 342 | 343 | There is an example custom block under `src/Blocks/SampleACFBlock/ACFBlock.php`. This demonstrates creating a block using ACF functions that includes two fields. 344 | 345 | > Note that in order to get this example to work, you need to create an ACF field group containing two fields, `some_headline` and `some_text`, and then have the field group displayed if the block is equal to ACF Block. Be sure to keep your block name all lowercase. ACF drops all uppercase letters and your block might not appear as an option if the names are mismatched. 346 | 347 | 2. Create a new twig file to render the ACF fields. 348 | 349 | The example block fields are rendered in the file `templates/components/acf-block.twig`. 350 | 351 | 3. Invoke the ACF block class in `/src/Blocks/Blocks.php`. 352 | 353 | 4. Add your new Gutenberg block to the array returned in the `allowBlocks` function in `/src/Managers/GutenbergManager.php`. 354 | 355 | ```php 356 | public function allowBlocks($allowed_blocks) 357 | { 358 | return array( 359 | ... 360 | 'acf/acf-block', 361 | ... 362 | ); 363 | } 364 | ``` 365 | 366 | Read more about [creating Gutenberg blocks using ACF](https://www.advancedcustomfields.com/resources/blocks/) 367 | 368 | ### Included Custom Blocks 369 | 370 | Two custom Gutenberg blocks are included: 371 | 372 | - Related Articles 373 | - Image Layout 374 | 375 | These include basic styles so they can work out of the box. They _require_ the Advanced Custom Field plugin to function. If you do not plan to use ACF, you can disable these blocks by removing the applicable lines in the constructor function of `/src/Blocks/Blocks.php` 376 | 377 | These fields are managed using PHP in the file `/src/Managers/ACFManager.php`. You can make updates to the fields here. If you would rather using the ACF to make updates to these fields: 378 | 379 | 1. Under `Advanced Custom Fields -> Tools`, import the JSON file in `/gutenberg-acf-backups/` 380 | 2. Make updates to the fields 381 | 3. Go to `Advanced Custom Fields -> Tools` and generate the PHP code 382 | 4. Update the PHP code in `/src/Managers/ACFManager.php`. Make sure to only update the PHP code for one layout group at a time, as they are separated by function in the manager file. 383 | 384 | ## ♿ Accessibility Testing 385 | 386 | [Pa11y](https://pa11y.org/) is an automated tool that audits our website's pages for accessibility issues according to [WCAG 2.1 AA](https://www.w3.org/TR/WCAG21/) standards. This tool captures machine detectable errors such as missing alt text, wrong heading order, browser errors, etc. For issues such as color contrast, keyboard navigation, or VoiceOver functionality, manual testing is advised. 387 | 388 | To run the tests, run the following command: 389 | 390 | ```sh 391 | npm run test:a11y 392 | ``` 393 | 394 | where `` is a valid URL, or one of `local`, `staging` or `live`. Running the command without specifying the url will default to `local`. 395 | 396 | ### Configuring pa11y 397 | 398 | The `package.json` file has preset configurations for pa11y under `testing.accessibility`. 399 | 400 | - `paths` (array): Paths appended to the specified URL. 401 | - `ignore.codes` (array): WCAG codes to ignore 402 | - `ignore.selectors` (array): CSS selectors to ignore 403 | 404 | ## 🌐 Configuring Multisite 405 | 406 | You can configure your install for multisite by setting the following environment variables in `docker-compose.yml`: 407 | 408 | ```yaml 409 | services: 410 | wordpress: 411 | environment: 412 | ... 413 | 414 | # Configure WordPress for multisite 415 | WORDPRESS_MULTISITE: 1 416 | 417 | # Configure for subdomain routing 418 | # Leave this commented out for subdirectory routing 419 | # WORDPRESS_MULTISITE_SUBDOMAIN_INSTALL: 1 420 | 421 | ... 422 | ``` 423 | 424 | This can be done at anytime - before initial install or to convert an existing single site install to multisite. Note that if you are converting from single site to multisite you will need to restart your server with `./bin/start` in order for the change to take effect. 425 | 426 | ### For Subdomain 427 | 428 | By default, multisite is configured to run in subdirectory mode: 429 | 430 | ```text 431 | skela.ups.dock/site-1 432 | skela.ups.dock/site-2 433 | ``` 434 | 435 | If you are using Ups Dock, you can also configure it to run in subdomain mode with a few extra steps: 436 | 437 | ```text 438 | site-1.skela.ups.dock 439 | site-2.skela.ups.dock 440 | ``` 441 | 442 | 1. Uncomment the following environment variable in `docker-compose.yml` 443 | 444 | ```yaml 445 | WORDPRESS_MULTISITE_SUBDOMAIN_INSTALL: 1 446 | ``` 447 | 448 | 2. Update your `VIRTUAL_HOST` environment variable in `docker-compose.ups-dock.yml` 449 | 450 | For subdomain mode to work, you need to tell Ups Dock to route all subdomains of `skela.ups.dock` to this container: 451 | 452 | ```yaml 453 | VIRTUAL_HOST: skela.ups.dock,*.skela.ups.dock 454 | ``` 455 | 456 | 3. Update the SSL certificates generated by Ups Dock to include your four level subdomains 457 | 458 | a. Navigate to your local copy of [Ups Dock](https://github.com/Upstatement/ups-dock) 459 | 460 | ```shell 461 | cd path/to/ups-dock 462 | ``` 463 | 464 | b. Add your wildcard domain to the `[ alternate_names ]` section in `config/openssl.conf` 465 | 466 | ```text 467 | DNS.1 = ups.dock 468 | DNS.2 = *.ups.dock 469 | DNS.3 = *.skela.ups.dock 470 | ``` 471 | 472 | c. Regenerate certs and restart Ups Dock 473 | 474 | ```shell 475 | ./bin/gen-certs.sh 476 | docker-compose up -d 477 | ``` 478 | 479 | ### Caveats 480 | 481 | - Subdomain installs won't work without Ups Dock (or another tool that allows you to map domains to Docker containers) as it will not work when your base domain is `localhost` 482 | 483 | For more details about everything these config vars do under the surface, consult [MULTISITE.md](multisite.md). 484 | 485 | ## 👨‍👩‍👧‍👦 Contributing 486 | 487 | We welcome all contributions to our projects! Filing bugs, feature requests, code changes, docs changes, or anything else you'd like to contribute are all more than welcome! More information about contributing can be found in the [contributing guidelines](.github/CONTRIBUTING.md). 488 | 489 | ## 📗 Code of Conduct 490 | 491 | Upstatement strives to provide a welcoming, inclusive environment for all users. To hold ourselves accountable to that mission, we have a strictly-enforced [code of conduct](CODE_OF_CONDUCT.md). 492 | 493 | ## About Upstatement 494 | 495 | [Upstatement](https://www.upstatement.com/) is a digital transformation studio headquartered in Boston, MA that imagines and builds exceptional digital experiences. Make sure to check out our [services](https://www.upstatement.com/services/), [work](https://www.upstatement.com/work/), and [open positions](https://www.upstatement.com/jobs/)! 496 | -------------------------------------------------------------------------------- /acf-json/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstatement/skela-wp-theme/1fb41ec3564c3961e2bf126ea87b1e3a5e73413d/acf-json/.gitkeep -------------------------------------------------------------------------------- /bin/composer: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose exec -w /var/www/html/wp-content/themes/skela wordpress composer "$@" 4 | -------------------------------------------------------------------------------- /bin/db-to-no-upsdock: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./bin/wp search-replace skela.ups.dock localhost:8888 4 | ./bin/wp search-replace https://localhost:8888 http://localhost:8888 5 | ./bin/wp db export - | gzip -3 > docker/conf/mysql/init.sql.gz 6 | -------------------------------------------------------------------------------- /bin/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | HOST=HOST_GOES_HERE 6 | USER=USER_GOES_HERE 7 | PORT=PORT_GOES_HERE 8 | BASEDIR=$(dirname "$0") 9 | DEPLOYIGNORE=$BASEDIR/../.deployignore 10 | DESTINATION="~/public/wp-content/themes/YOUR_THEME_GOES_HERE" 11 | PLUGINDESTINATION="~/public/wp-content" 12 | 13 | if [ -z $HOST ] || [ -z $USER ]; then 14 | echo "Please pass the SFTP host, user name, and port for your site." 15 | exit 1 16 | fi 17 | 18 | echo "Creating temporary directory..." 19 | ssh $USER@$HOST -p $PORT -o StrictHostKeyChecking=no "mkdir -p ~/_tmp" 20 | 21 | echo "Deploying WordPress theme..." 22 | rsync \ 23 | -rlvz \ 24 | --exclude-from="$DEPLOYIGNORE" \ 25 | --ipv4 \ 26 | --delete-after \ 27 | -e "ssh -p $PORT -o StrictHostKeyChecking=no" \ 28 | --temp-dir=~/_tmp \ 29 | . \ 30 | $USER@$HOST:$DESTINATION 31 | 32 | echo "Deploying WordPress plugins..." 33 | rsync \ 34 | -rlvz \ 35 | --ipv4 \ 36 | --exclude=".gitignore" \ 37 | --exclude="node_modules/" \ 38 | -e "ssh -p $PORT -o StrictHostKeyChecking=no" \ 39 | --temp-dir=~/_tmp \ 40 | plugins \ 41 | $USER@$HOST:$PLUGINDESTINATION 42 | -------------------------------------------------------------------------------- /bin/export-db: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ./bin/wp db export - | gzip -3 > docker/conf/mysql/init-ups-dock.sql.gz 4 | -------------------------------------------------------------------------------- /bin/import: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "Importing database..." 6 | docker-compose exec wordpress wp db import dbdump.sql 7 | echo "Replacing live URLs with localURLs" 8 | docker-compose exec wordpress wp search-replace www.OLDHOST.com NEWHOST.ups.dock 9 | -------------------------------------------------------------------------------- /bin/install: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | create_containers() { 6 | docker-compose build --no-cache 7 | docker-compose up -d 8 | } 9 | 10 | # echo "Unzipping database seed scripts..." 11 | # echo 12 | # gunzip -kf docker/conf/mysql/*.sql.gz 13 | # echo 14 | 15 | echo "Bringing up project containers..." 16 | echo 17 | create_containers 18 | echo 19 | 20 | echo "Installing NPM packages..." 21 | echo 22 | npm install 23 | echo 24 | 25 | echo "Installing Composer libraries..." 26 | echo 27 | ./bin/composer install 28 | echo 29 | 30 | echo "Activating WordPress plugins..." 31 | echo 32 | ./bin/wp plugin activate --all 33 | echo 34 | 35 | echo "-------------------------------" 36 | echo "Install completed successfully!" 37 | echo "-------------------------------" 38 | echo 39 | -------------------------------------------------------------------------------- /bin/logs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose exec wordpress tail -f logs/error.log 4 | -------------------------------------------------------------------------------- /bin/rename-theme: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | read -p "Please enter your new WordPress theme name (titlecase): " ORIGINAL 6 | 7 | KEBABCASE=`echo "${ORIGINAL}" | tr " " -` 8 | SNAKECASE=`echo "${ORIGINAL}" | tr " " _` 9 | LOWERCASE=`echo "${KEBABCASE}" | tr '[A-Z]' '[a-z]'` 10 | UPPERCASE=`echo "${SNAKECASE}" | tr '[a-z]' '[A-Z]'` 11 | TITLECASE=`echo "${KEBABCASE}" | tr '-' '\n' | awk '{printf "%s%s", toupper(substr($0,0,1)), substr($0,2)}'` 12 | 13 | echo "" 14 | 15 | replace_theme_name() { 16 | eval `sed -i '' "s/Skela/${ORIGINAL}/g" docker-compose.yml` 17 | eval `sed -i '' "s/Skela/${ORIGINAL}/g" MULTISITE.md` 18 | eval `sed -i '' "s/Skela/${ORIGINAL}/g" README.md` 19 | eval `sed -i '' "s/Skela/${ORIGINAL}/g" style.css` 20 | } 21 | 22 | replace_titlecase() { 23 | eval `sed -i '' "s/Skela/${TITLECASE}/g" composer.json` 24 | eval `sed -i '' "s/Skela/${TITLECASE}/g" front-page.php` 25 | eval `sed -i '' "s/Skela/${TITLECASE}/g" functions.php` 26 | eval `sed -i '' "s/Skela/${TITLECASE}/g" index.php` 27 | eval `sed -i '' "s/Skela/${TITLECASE}/g" src/**/*.php` 28 | eval `sed -i '' "s/Skela/${TITLECASE}/g" src/**/**/*.php` 29 | } 30 | 31 | replace_lowercase() { 32 | eval `sed -i '' "s/skela/${LOWERCASE}/g" .env` 33 | eval `sed -i '' "s/skela/${LOWERCASE}/g" .env.sample` 34 | eval `sed -i '' "s/skela.ups.dock/${LOWERCASE}.ups.dock/g" composer.json` 35 | eval `sed -i '' "s/skela.ups.dock/${LOWERCASE}.ups.dock/g" composer.lock` 36 | eval `sed -i '' "s/skela.ups.dock/${LOWERCASE}.ups.dock/g" docker-compose.yml` 37 | eval `sed -i '' "s/skela/${LOWERCASE}/g" MULTISITE.md` 38 | eval `sed -i '' "s/skela/${LOWERCASE}/g" package-lock.json` 39 | eval `sed -i '' "s/skela/${LOWERCASE}/g" package.json` 40 | eval `sed -i '' "s/skela.ups.dock/${LOWERCASE}.ups.dock/g" package.json` 41 | eval `sed -i '' "s/#skela/#${LOWERCASE}/g" README.md` 42 | eval `sed -i '' "s/skela.ups.dock/${LOWERCASE}.ups.dock/g" README.md` 43 | eval `sed -i '' "s/skela/${LOWERCASE}/g" webpack.config.js` 44 | eval `sed -i '' "s/skela/${LOWERCASE}/g" bin/composer` 45 | eval `sed -i '' "s/skela/${LOWERCASE}/g" src/Blocks/SampleBlock/sample-block.js` 46 | eval `sed -i '' "s/skela/${LOWERCASE}/g" src/Managers/ThemeManager.php` 47 | } 48 | 49 | replace_uppercase() { 50 | eval `sed -i '' "s/SKELA/${UPPERCASE}/g" functions.php` 51 | eval `sed -i '' "s/SKELA/${UPPERCASE}/g" src/**/*.php` 52 | } 53 | 54 | 55 | echo "Replacing theme name with '$ORIGINAL'" 56 | replace_theme_name 57 | 58 | echo "Replacing 'Skela' with '$TITLECASE'" 59 | replace_titlecase 60 | 61 | echo "Replacing 'skela' with '$LOWERCASE'" 62 | replace_lowercase 63 | 64 | echo "Replacing 'SKELA' with '$UPPERCASE'" 65 | replace_uppercase 66 | -------------------------------------------------------------------------------- /bin/setup-theme: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Activating your WordPress theme..." 4 | echo 5 | ./bin/wp theme activate "REPLACE_ME_THEMENAME" 6 | echo 7 | 8 | echo "Updating site name..." 9 | echo 10 | ./bin/wp option update blogname "REPLACE_ME_BLOGNAME" 11 | echo 12 | 13 | echo "Updating seed database URLs..." 14 | echo 15 | ./bin/wp search-replace skela.ups.dock REPLACE_ME.ups.dock 16 | echo 17 | 18 | echo "Exporting updated seed database..." 19 | echo 20 | ./bin/export-db 21 | echo 22 | -------------------------------------------------------------------------------- /bin/start: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | read_var() { 6 | if [ -z "$1" ]; then 7 | echo "environment variable name is required" 8 | return 9 | fi 10 | 11 | local VAR=$(grep $1 ".env" | xargs) 12 | IFS="=" read -ra VAR <<< "$VAR" 13 | echo ${VAR[1]} 14 | } 15 | 16 | stop_containers() { 17 | echo "Shutting off containers..." 18 | echo 19 | docker-compose stop 20 | echo 21 | echo "All done!" 22 | } 23 | 24 | trap stop_containers SIGINT 25 | 26 | echo "Starting WordPress..." 27 | echo 28 | docker-compose up -d 29 | echo 30 | 31 | COMPOSE_FILE=$(read_var COMPOSE_FILE) 32 | NO_UPS_DOCK_FILE="docker-compose.yml" 33 | 34 | echo "Running start script..." 35 | echo 36 | if [[ "$COMPOSE_FILE" == "$NO_UPS_DOCK_FILE" ]]; then 37 | npm start 38 | else 39 | npm run start:ups-dock 40 | fi 41 | echo 42 | -------------------------------------------------------------------------------- /bin/uninstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | docker-compose down -v --remove-orphans 6 | 7 | echo "-------------------------------" 8 | echo "Uninstall completed successfully!" 9 | echo "-------------------------------" 10 | echo 11 | -------------------------------------------------------------------------------- /bin/versionbump: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | if [ "$1" = "help" ]; then 6 | echo "Usage: $(basename $0) [ | major | minor | patch | premajor | preminor | prepatch | prerelease]" 7 | exit 8 | fi 9 | 10 | RELEASE=$1 11 | 12 | if [ -z "$1" ]; then 13 | RELEASE="patch" 14 | fi 15 | 16 | CURRENT_VERSION=$(cat package.json | grep version | head -1 | awk -F: '{ print $2 }' | sed 's/[",]//g') 17 | 18 | # Update the npm version 19 | NEW_VERSION=$(npm version ${RELEASE} --no-git-tag-version) 20 | NEW_VERSION=$(echo "${NEW_VERSION}" | sed -e 's/^v//g') 21 | 22 | # Update the css version 23 | sed -i '' -E "s/(Version:[[:space:]]).+/\1${NEW_VERSION}/g" "style.css" 24 | -------------------------------------------------------------------------------- /bin/wp: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker-compose exec wordpress wp "$@" 4 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "upstatement/skela-wp-theme", 3 | "authors": [ 4 | { 5 | "name": "Upstatement", 6 | "email": "tech@upstatement.com" 7 | } 8 | ], 9 | "description": "WordPress theme", 10 | "license": "proprietary", 11 | "repositories": [ 12 | { 13 | "type": "composer", 14 | "url": "https://wpackagist.org" 15 | } 16 | ], 17 | "extra": { 18 | "installer-paths": { 19 | "plugins/{$name}/": [ 20 | "wpackagist-plugin/*", 21 | "type:wordpress-plugin" 22 | ] 23 | } 24 | }, 25 | "config": { 26 | "optimize-autoloader": true, 27 | "platform": { 28 | "php": "7.2" 29 | } 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Skela\\": "src" 34 | } 35 | }, 36 | "require": { 37 | "wpackagist-plugin/wp-environment-indicator": "^1.0", 38 | "wpackagist-plugin/wp-help": "^1.7", 39 | "wpackagist-plugin/debug-bar": "^1.1", 40 | "wpackagist-plugin/debug-bar-timber": "@stable", 41 | "vlucas/phpdotenv": "^5.4", 42 | "timber/timber": "^1.19", 43 | "nesbot/carbon": "^2.54", 44 | "php-ds/php-ds": "^1.2" 45 | }, 46 | "require-dev": { 47 | "squizlabs/php_codesniffer": "^3.6", 48 | "friendsofphp/php-cs-fixer": "^3.3", 49 | "symfony/var-dumper": "^4.4", 50 | "dealerdirect/phpcodesniffer-composer-installer": "^0.7", 51 | "wp-coding-standards/wpcs": "^2.3", 52 | "phpcompatibility/phpcompatibility-wp": "^2.1", 53 | "szepeviktor/phpstan-wordpress": "^1.0", 54 | "php-stubs/acf-pro-stubs": "^5.10" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /docker-compose.ups-dock.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | services: 3 | wordpress: 4 | environment: 5 | # WordPress install configuration 6 | WORDPRESS_URL: https://skela.ups.dock 7 | 8 | # Runtime configuration for upstatement/ups-dock 9 | # See https://github.com/upstatement/ups-dock 10 | UPS_DOCK_NAME: Skela 11 | 12 | # Runtime configuration for jwilder/nginx-proxy 13 | # See https://github.com/jwilder/nginx-proxy 14 | VIRTUAL_HOST: skela.ups.dock 15 | HTTPS_METHOD: noredirect 16 | 17 | networks: 18 | - proxy 19 | - default 20 | db: 21 | volumes: 22 | # Export an initial DB seed using `wp db export` and load it automatically on first install 23 | # See https://hub.docker.com/_/mysql 24 | - ./docker/conf/mysql/init-ups-dock.sql:/docker-entrypoint-initdb.d/init.sql 25 | networks: 26 | proxy: 27 | name: ups-dock 28 | external: true 29 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.6' 2 | services: 3 | wordpress: 4 | build: 5 | context: . 6 | args: 7 | - WORDPRESS_THEME_NAME 8 | volumes: 9 | - ./:/var/www/html/wp-content/themes/${WORDPRESS_THEME_NAME}:cached 10 | - ./plugins:/var/www/html/wp-content/plugins:cached 11 | - ./uploads:/var/www/html/wp-content/uploads:cached 12 | - ./docker/conf:/var/www/html/conf 13 | - ./logs:/var/www/html/logs 14 | - ./docker/scripts:/var/www/html/scripts 15 | depends_on: 16 | db: 17 | condition: service_healthy 18 | ports: 19 | - '8888:80' 20 | environment: 21 | # WordPress install configuration 22 | WORDPRESS_TITLE: 'Skela' 23 | WORDPRESS_URL: http://localhost:8888 24 | WORDPRESS_ADMIN_USER: admin 25 | WORDPRESS_ADMIN_EMAIL: admin@example.com 26 | WORDPRESS_ADMIN_PASSWORD: password 27 | 28 | # WordPress database configuration 29 | WORDPRESS_DB_HOST: db 30 | WORDPRESS_DB_NAME: wordpress 31 | WORDPRESS_DB_USER: wordpress 32 | WORDPRESS_DB_PASSWORD: wordpress 33 | 34 | # Defined in `.env`, which is automatically loaded by `docker-compose` 35 | # See https://docs.docker.com/compose/environment-variables/#the-env-file 36 | WORDPRESS_THEME_NAME: ${WORDPRESS_THEME_NAME} 37 | WP_ENV: ${WP_ENV} 38 | 39 | # Configure WordPress for multisite 40 | # WORDPRESS_MULTISITE: 1 41 | 42 | # Configure for subdomain routing 43 | # Leave this commented out for subdirectory routing 44 | # WORDPRESS_MULTISITE_SUBDOMAIN_INSTALL: 1 45 | 46 | # Runtime configuration for richarvey/nginx-php-fpm 47 | # See https://github.com/richarvey/nginx-php-fpm 48 | RUN_SCRIPTS: 1 49 | 50 | # Comment out this next line to avoid permissions issues when not mounting the 51 | # theme codebase into the container via `volumes` in `docker-composer.yml`. 52 | # We're skipping it by default as it's not strictly required when using `volumes` 53 | # and it adds significantly to boot time with larger codebases. 54 | SKIP_CHOWN: 1 55 | 56 | # Set the time zone 57 | TZ: America/New_York 58 | db: 59 | image: mysql/mysql-server:8.0.27 60 | volumes: 61 | # Export an initial DB seed using `wp db export` and load it automatically on first install 62 | # See https://hub.docker.com/_/mysql 63 | # - ./docker/conf/mysql/init.sql:/docker-entrypoint-initdb.d/init.sql 64 | - db_data:/var/lib/mysql 65 | environment: 66 | # See https://hub.docker.com/_/mysql/ 67 | MYSQL_ROOT_PASSWORD: root 68 | MYSQL_DATABASE: wordpress 69 | MYSQL_USER: wordpress 70 | MYSQL_PASSWORD: wordpress 71 | volumes: 72 | db_data: 73 | -------------------------------------------------------------------------------- /docker/conf/mysql/.gitignore: -------------------------------------------------------------------------------- 1 | *.sql 2 | -------------------------------------------------------------------------------- /docker/conf/nginx/nginx-site.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; ## listen for ipv4; this line is default and implied 3 | listen [::]:80 default ipv6only=on; ## listen for ipv6 4 | 5 | root /var/www/html; 6 | index index.php index.html index.htm; 7 | 8 | # Make site accessible from http://localhost/ 9 | server_name _; 10 | 11 | # Disable sendfile as per https://docs.vagrantup.com/v2/synced-folders/virtualbox.html 12 | sendfile off; 13 | 14 | # Add stdout logging 15 | error_log /dev/stdout info; 16 | access_log /dev/stdout; 17 | 18 | location = /favicon.ico { 19 | log_not_found off; 20 | access_log off; 21 | } 22 | 23 | location = /robots.txt { 24 | allow all; 25 | log_not_found off; 26 | access_log off; 27 | } 28 | 29 | # WordPress Multisite Subdirectory Rules 30 | # https://wordpress.org/support/article/nginx/#wordpress-multisite-subdirectory-rules 31 | if (!-e $request_filename) { 32 | rewrite /wp-admin$ $scheme://$host$request_uri/ permanent; 33 | rewrite ^(/[^/]+)?(/wp-.*) $2 last; 34 | rewrite ^(/[^/]+)?(/.*\.php) $2 last; 35 | } 36 | 37 | # Override base location to work with WordPress pretty permalinks. 38 | # see https://codex.wordpress.org/Nginx#Per_Site_configuration 39 | location / { 40 | try_files $uri $uri/ /index.php?$args; 41 | } 42 | 43 | error_page 404 /404.html; 44 | 45 | location = /404.html { 46 | root /var/www/errors; 47 | internal; 48 | } 49 | 50 | # pass the PHP scripts to FastCGI server listening on socket 51 | # 52 | location ~ \.php$ { 53 | try_files $uri =404; 54 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 55 | fastcgi_pass unix:/var/run/php-fpm.sock; 56 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 57 | fastcgi_param SCRIPT_NAME $fastcgi_script_name; 58 | fastcgi_index index.php; 59 | include fastcgi_params; 60 | } 61 | 62 | # deny access to . files, for security 63 | # 64 | location ~ /\. { 65 | log_not_found off; 66 | deny all; 67 | } 68 | 69 | location ^~ /.well-known { 70 | allow all; 71 | auth_basic off; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /docker/scripts/setup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | WORDPRESS_MULTISITE=${WORDPRESS_MULTISITE:-false} 6 | WORDPRESS_MULTISITE_SUBDOMAIN_INSTALL=${WORDPRESS_MULTISITE_SUBDOMAIN_INSTALL:-false} 7 | 8 | # Ensure MySQL connection is up before proceeding. 9 | until mysql -uwordpress -pwordpress -hdb wordpress; do 10 | >&2 echo "Waiting for MySQL ..." 11 | sleep 1 12 | done 13 | 14 | # Generate wp-config.php 15 | echo "Configuring WordPress..." 16 | wp config create \ 17 | --dbname=$WORDPRESS_DB_NAME \ 18 | --dbuser=$WORDPRESS_DB_USER \ 19 | --dbpass=$WORDPRESS_DB_PASSWORD \ 20 | --dbhost=$WORDPRESS_DB_HOST \ 21 | --extra-php < 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Website Documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 29 | 30 | 31 | 32 |
33 | 34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /documentation/scaffold-documentation.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | This document serves as a reference for administrating the WordPress CMS backend 4 | 5 | ## Example content 6 | 7 | This is powered by [Flatdoc](http://ricostacruz.com/flatdoc/) 8 | 9 | Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Duis condimentum felis non mollis porta. Curabitur sed arcu consectetur, porta felis et, pulvinar ante. Maecenas euismod ex lorem, nec aliquam velit vehicula tempor. 10 | 11 | ![Editing a paragraph in Gutenberg](images/paragraph.png) 12 | -------------------------------------------------------------------------------- /documentation/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Please don't edit this file directly. 4 | Instead, edit the stylus (.styl) files and compile it to CSS on your machine. 5 | 6 | */ 7 | 8 | /* ---------------------------------------------------------------------------- 9 | * Fonts 10 | */ 11 | 12 | @import url('//fonts.googleapis.com/css?family=Montserrat:700|Open+Sans:300'); 13 | /* ---------------------------------------------------------------------------- 14 | * Base 15 | */ 16 | 17 | html, 18 | body, 19 | div, 20 | span, 21 | applet, 22 | object, 23 | iframe, 24 | h1, 25 | h2, 26 | h3, 27 | h4, 28 | h5, 29 | h6, 30 | p, 31 | blockquote, 32 | pre, 33 | a, 34 | abbr, 35 | acronym, 36 | address, 37 | big, 38 | cite, 39 | code, 40 | del, 41 | dfn, 42 | em, 43 | img, 44 | ins, 45 | kbd, 46 | q, 47 | s, 48 | samp, 49 | small, 50 | strike, 51 | strong, 52 | sub, 53 | sup, 54 | tt, 55 | var, 56 | dl, 57 | dt, 58 | dd, 59 | ol, 60 | ul, 61 | li, 62 | fieldset, 63 | form, 64 | label, 65 | legend, 66 | table, 67 | caption, 68 | tbody, 69 | tfoot, 70 | thead, 71 | tr, 72 | th, 73 | td { 74 | margin: 0; 75 | padding: 0; 76 | border: 0; 77 | outline: 0; 78 | font-weight: inherit; 79 | font-style: inherit; 80 | font-family: inherit; 81 | font-size: 100%; 82 | vertical-align: baseline; 83 | } 84 | 85 | body { 86 | line-height: 1; 87 | color: #000; 88 | background: #fff; 89 | } 90 | 91 | ol, 92 | ul { 93 | list-style: none; 94 | } 95 | 96 | table { 97 | border-collapse: separate; 98 | border-spacing: 0; 99 | vertical-align: middle; 100 | } 101 | 102 | caption, 103 | th, 104 | td { 105 | text-align: left; 106 | font-weight: normal; 107 | vertical-align: middle; 108 | } 109 | 110 | a img { 111 | border: none; 112 | } 113 | 114 | html, 115 | body { 116 | height: 100%; 117 | } 118 | 119 | html { 120 | overflow-x: hidden; 121 | } 122 | 123 | body, 124 | td, 125 | textarea, 126 | input { 127 | font-family: Helvetica Neue, Open Sans, sans-serif; 128 | line-height: 1.6; 129 | font-size: 14px; 130 | color: #000; 131 | } 132 | 133 | @media (max-width: 480px) { 134 | body, 135 | td, 136 | textarea, 137 | input { 138 | font-size: 12px; 139 | } 140 | } 141 | 142 | a { 143 | color: #fc4b4e; 144 | text-decoration: none; 145 | } 146 | 147 | a:hover { 148 | color: #000; 149 | } 150 | 151 | /* ---------------------------------------------------------------------------- 152 | * Content styling 153 | */ 154 | 155 | .content p, 156 | .content ul, 157 | .content ol, 158 | .content h1, 159 | .content h2, 160 | .content h3, 161 | .content h4, 162 | .content h5, 163 | .content h6, 164 | .content pre, 165 | .content blockquote { 166 | padding: 10px 0; 167 | -webkit-box-sizing: border-box; 168 | -moz-box-sizing: border-box; 169 | box-sizing: border-box; 170 | } 171 | 172 | .content h1, 173 | .content h2, 174 | .content h3, 175 | .content h4, 176 | .content h5, 177 | .content h6 { 178 | font-weight: bold; 179 | -webkit-font-smoothing: antialiased; 180 | text-rendering: optimizeLegibility; 181 | } 182 | 183 | .content pre { 184 | font-family: Menlo, monospace; 185 | } 186 | 187 | .content ul > li { 188 | list-style-type: disc; 189 | } 190 | 191 | .content ol > li { 192 | list-style-type: decimal; 193 | } 194 | 195 | .content ul, 196 | .content ol { 197 | /* margin-left: 20px; */ 198 | } 199 | 200 | .content ul > li { 201 | list-style-type: none; 202 | position: relative; 203 | } 204 | 205 | .content ul > li:before { 206 | content: ''; 207 | display: block; 208 | position: absolute; 209 | left: -17px; 210 | top: 7px; 211 | width: 5px; 212 | height: 5px; 213 | -webkit-border-radius: 4px; 214 | border-radius: 4px; 215 | -webkit-box-sizing: border-box; 216 | -moz-box-sizing: border-box; 217 | box-sizing: border-box; 218 | background: #fff; 219 | border: solid 1px #fc4b4e; 220 | } 221 | 222 | .content li > :first-child { 223 | padding-top: 0; 224 | } 225 | 226 | .content strong, 227 | .content b { 228 | font-weight: bold; 229 | } 230 | 231 | .content i, 232 | .content em { 233 | font-style: italic; 234 | } 235 | 236 | .content code { 237 | font-family: Menlo, monospace; 238 | background: #f3f6fb; 239 | padding: 1px 3px; 240 | font-size: 0.95em; 241 | } 242 | 243 | .content pre > code { 244 | display: block; 245 | background: transparent; 246 | font-size: 0.85em; 247 | letter-spacing: -1px; 248 | } 249 | 250 | .content blockquote :first-child { 251 | padding-top: 0; 252 | } 253 | 254 | .content blockquote :last-child { 255 | padding-bottom: 0; 256 | } 257 | 258 | .content table { 259 | margin-top: 10px; 260 | margin-bottom: 10px; 261 | padding: 0; 262 | border-collapse: collapse; 263 | clear: both; 264 | } 265 | 266 | .content table tr { 267 | border-top: 1px solid #ccc; 268 | background-color: #fff; 269 | margin: 0; 270 | padding: 0; 271 | } 272 | 273 | .content table tr :nth-child(2n) { 274 | background-color: #f8f8f8; 275 | } 276 | 277 | .content table tr th { 278 | text-align: auto; 279 | font-weight: bold; 280 | border: 1px solid #ccc; 281 | margin: 0; 282 | padding: 6px 13px; 283 | } 284 | 285 | .content table tr td { 286 | text-align: auto; 287 | border: 1px solid #ccc; 288 | margin: 0; 289 | padding: 6px 13px; 290 | } 291 | 292 | .content table tr th :first-child, 293 | .content table tr td :first-child { 294 | margin-top: 0; 295 | } 296 | 297 | .content table tr th :last-child, 298 | .content table tr td :last-child { 299 | margin-bottom: 0; 300 | } 301 | 302 | /* ---------------------------------------------------------------------------- 303 | * Content 304 | */ 305 | 306 | .content-root { 307 | min-height: 90%; 308 | position: relative; 309 | } 310 | 311 | .content { 312 | padding-top: 30px; 313 | padding-bottom: 40px; 314 | padding-left: 40px; 315 | padding-right: 40px; 316 | zoom: 1; 317 | max-width: 700px; 318 | } 319 | 320 | .content:before, 321 | .content:after { 322 | content: ''; 323 | display: table; 324 | } 325 | 326 | .content:after { 327 | clear: both; 328 | } 329 | 330 | .content blockquote { 331 | color: #fc4b4e; 332 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 333 | } 334 | 335 | .content h1, 336 | .content h2, 337 | .content h3 { 338 | -webkit-font-smoothing: antialiased; 339 | text-rendering: optimizeLegibility; 340 | font-family: montserrat; 341 | padding-bottom: 0; 342 | } 343 | 344 | .content h1 + p, 345 | .content h2 + p, 346 | .content h3 + p, 347 | .content h1 ul, 348 | .content h2 ul, 349 | .content h3 ul, 350 | .content h1 ol, 351 | .content h2 ol, 352 | .content h3 ol { 353 | padding-top: 10px; 354 | } 355 | 356 | .content h1 { 357 | font-size: 3em; 358 | letter-spacing: -0.02em; 359 | font-weight: bold; 360 | color: #fc4b4e; 361 | } 362 | 363 | .content h2 { 364 | font-weight: bold; 365 | font-size: 1.8em; 366 | } 367 | 368 | .content h3 { 369 | font-size: 1.2em; 370 | } 371 | 372 | .content h1, 373 | .content .big-heading, 374 | body.big-h3 .content h3 { 375 | padding-top: 80px; 376 | } 377 | 378 | .content h2 { 379 | padding-top: 25px; 380 | } 381 | 382 | .content h1:before, 383 | /* .content h2:before, */ 384 | 385 | .content .big-heading:before, body.big-h3 .content h3:before { 386 | display: block; 387 | content: ''; 388 | background: -webkit-gradient( 389 | linear, 390 | left top, 391 | right top, 392 | color-stop(0.8, #dfe2e7), 393 | color-stop(1, rgba(223, 226, 231, 0)) 394 | ); 395 | background: -webkit-linear-gradient(left, #dfe2e7 80%, rgba(223, 226, 231, 0) 100%); 396 | background: -moz-linear-gradient(left, #dfe2e7 80%, rgba(223, 226, 231, 0) 100%); 397 | background: -o-linear-gradient(left, #dfe2e7 80%, rgba(223, 226, 231, 0) 100%); 398 | background: -ms-linear-gradient(left, #dfe2e7 80%, rgba(223, 226, 231, 0) 100%); 399 | background: linear-gradient(left, #dfe2e7 80%, rgba(223, 226, 231, 0) 100%); 400 | -webkit-box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4); 401 | box-shadow: 0 1px 0 rgba(255, 255, 255, 0.4); 402 | height: 1px; 403 | position: relative; 404 | top: -40px; 405 | left: -40px; 406 | width: 100%; 407 | } 408 | 409 | @media (max-width: 768px) { 410 | .content h1, 411 | .content h2, 412 | .content .big-heading, 413 | body.big-h3 .content h3 { 414 | padding-top: 40px; 415 | } 416 | .content h1:before, 417 | .content h2:before, 418 | .content .big-heading:before, 419 | body.big-h3 .content h3:before { 420 | background: #dfe2e7; 421 | left: -40px; 422 | top: -20px; 423 | width: 120%; 424 | } 425 | } 426 | 427 | .content h4, 428 | .content h5, 429 | .content .small-heading, 430 | body:not(.big-h3) .content h3 { 431 | border-bottom: solid 1px rgba(0, 0, 0, 0.07); 432 | color: #fc4b4e; 433 | padding-top: 30px; 434 | padding-bottom: 10px; 435 | } 436 | 437 | body:not(.big-h3) .content h3 { 438 | font-size: 0.9em; 439 | } 440 | 441 | .content h1:first-child { 442 | padding-top: 0; 443 | } 444 | 445 | .content h1:first-child, 446 | .content h1:first-child a, 447 | .content h1:first-child a:visited { 448 | color: #000; 449 | } 450 | 451 | .content h1:first-child:before { 452 | display: none; 453 | } 454 | 455 | @media (max-width: 768px) { 456 | .content h4, 457 | .content h5, 458 | .content .small-heading, 459 | body:not(.big-h3) .content h3 { 460 | padding-top: 20px; 461 | } 462 | } 463 | 464 | @media (max-width: 480px) { 465 | .content { 466 | padding: 20px; 467 | padding-top: 40px; 468 | } 469 | .content h4, 470 | .content h5, 471 | .content .small-heading, 472 | body:not(.big-h3) .content h3 { 473 | padding-top: 10px; 474 | } 475 | } 476 | 477 | body.no-literate .content pre > code { 478 | background: #f3f6fb; 479 | border: solid 1px #e7eaee; 480 | border-top: solid 1px #dbdde2; 481 | border-left: solid 1px #e2e5e9; 482 | display: block; 483 | padding: 10px; 484 | -webkit-border-radius: 2px; 485 | border-radius: 2px; 486 | overflow: auto; 487 | } 488 | 489 | body.no-literate .content pre > code { 490 | -webkit-overflow-scrolling: touch; 491 | } 492 | 493 | body.no-literate .content pre > code::-webkit-scrollbar { 494 | width: 15px; 495 | height: 15px; 496 | } 497 | 498 | body.no-literate .content pre > code::-webkit-scrollbar-thumb { 499 | background: #ddd; 500 | -webkit-border-radius: 8px; 501 | border-radius: 8px; 502 | border: solid 4px #f3f6fb; 503 | } 504 | 505 | body.no-literate .content pre > code:hover::-webkit-scrollbar-thumb { 506 | background: #999; 507 | -webkit-box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.2); 508 | box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.2); 509 | } 510 | 511 | @media (max-width: 1180px) { 512 | .content pre > code { 513 | background: #f3f6fb; 514 | border: solid 1px #e7eaee; 515 | border-top: solid 1px #dbdde2; 516 | border-left: solid 1px #e2e5e9; 517 | display: block; 518 | padding: 10px; 519 | -webkit-border-radius: 2px; 520 | border-radius: 2px; 521 | overflow: auto; 522 | } 523 | .content pre > code { 524 | -webkit-overflow-scrolling: touch; 525 | } 526 | .content pre > code::-webkit-scrollbar { 527 | width: 15px; 528 | height: 15px; 529 | } 530 | .content pre > code::-webkit-scrollbar-thumb { 531 | background: #ddd; 532 | -webkit-border-radius: 8px; 533 | border-radius: 8px; 534 | border: solid 4px #f3f6fb; 535 | } 536 | .content pre > code:hover::-webkit-scrollbar-thumb { 537 | background: #999; 538 | -webkit-box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.2); 539 | box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.2); 540 | } 541 | } 542 | 543 | .button { 544 | -webkit-font-smoothing: antialiased; 545 | text-rendering: optimizeLegibility; 546 | font-family: montserrat, sans-serif; 547 | letter-spacing: -1px; 548 | font-weight: bold; 549 | display: inline-block; 550 | padding: 3px 25px; 551 | border: solid 2px #fc4b4e; 552 | -webkit-border-radius: 20px; 553 | border-radius: 20px; 554 | margin-right: 15px; 555 | } 556 | 557 | .button, 558 | .button:visited { 559 | background: #fc4b4e; 560 | color: #fff; 561 | text-shadow: none; 562 | } 563 | 564 | .button:hover { 565 | border-color: #111; 566 | background: #111; 567 | color: #fff; 568 | } 569 | 570 | .button.light, 571 | .button.light:visited { 572 | background: transparent; 573 | color: #fc4b4e; 574 | border-color: #fc4b4e; 575 | text-shadow: none; 576 | } 577 | 578 | .button.light:hover { 579 | border-color: #fc4b4e; 580 | background: #fc4b4e; 581 | color: #fff; 582 | } 583 | 584 | .content .button + em { 585 | color: #fc4b4e; 586 | } 587 | 588 | @media (min-width: 1180px) { 589 | body:not(.no-literate) .content-root { 590 | background-color: #f3f6fb; 591 | -webkit-box-shadow: inset 900px 0 #fff, inset 901px 0 #dfe2e7, 592 | inset 910px 0 5px -10px rgba(0, 0, 0, 0.1); 593 | box-shadow: inset 900px 0 #fff, inset 901px 0 #dfe2e7, 594 | inset 910px 0 5px -10px rgba(0, 0, 0, 0.1); 595 | } 596 | } 597 | 598 | @media (min-width: 1180px) { 599 | body:not(.no-literate) .content { 600 | padding-left: 0; 601 | padding-right: 0; 602 | width: 930px; 603 | max-width: none; 604 | } 605 | body:not(.no-literate) .content > p, 606 | body:not(.no-literate) .content > ul, 607 | body:not(.no-literate) .content > ol, 608 | body:not(.no-literate) .content > h1, 609 | body:not(.no-literate) .content > h2, 610 | body:not(.no-literate) .content > h3, 611 | body:not(.no-literate) .content > h4, 612 | body:not(.no-literate) .content > h5, 613 | body:not(.no-literate) .content > h6, 614 | body:not(.no-literate) .content > pre, 615 | body:not(.no-literate) .content > blockquote { 616 | width: 650px; 617 | -webkit-box-sizing: border-box; 618 | -moz-box-sizing: border-box; 619 | box-sizing: border-box; 620 | padding-right: 40px; 621 | padding-left: 40px; 622 | } 623 | body:not(.no-literate) .content > h1, 624 | body:not(.no-literate) .content > h2, 625 | body:not(.no-literate) .content > h3 { 626 | clear: both; 627 | width: 100%; 628 | } 629 | body:not(.no-literate) .content > pre, 630 | body:not(.no-literate) .content > blockquote { 631 | width: 380px; 632 | padding-left: 20px; 633 | padding-right: 20px; 634 | float: right; 635 | clear: right; 636 | } 637 | body:not(.no-literate) .content > pre + p, 638 | body:not(.no-literate) .content > blockquote + p, 639 | body:not(.no-literate) .content > pre + ul, 640 | body:not(.no-literate) .content > blockquote + ul, 641 | body:not(.no-literate) .content > pre + ol, 642 | body:not(.no-literate) .content > blockquote + ol, 643 | body:not(.no-literate) .content > pre + h4, 644 | body:not(.no-literate) .content > blockquote + h4, 645 | body:not(.no-literate) .content > pre + h5, 646 | body:not(.no-literate) .content > blockquote + h5, 647 | body:not(.no-literate) .content > pre + h6, 648 | body:not(.no-literate) .content > blockquote + h6 { 649 | /*clear: both;*/ 650 | } 651 | body:not(.no-literate) .content > p, 652 | body:not(.no-literate) .content > ul, 653 | body:not(.no-literate) .content > ol, 654 | body:not(.no-literate) .content > h4, 655 | body:not(.no-literate) .content > h5, 656 | body:not(.no-literate) .content > h6 { 657 | float: left; 658 | clear: left; 659 | } 660 | body:not(.no-literate) .content > h4, 661 | body:not(.no-literate) .content > h5, 662 | body:not(.no-literate) .content > .small-heading, 663 | body:not(.big-h3) body:not(.no-literate) .content > h3 { 664 | margin-left: 40px; 665 | width: 590px; 666 | margin-bottom: 3px; 667 | padding-left: 0; 668 | padding-right: 0; 669 | } 670 | body:not(.no-literate) .content > table { 671 | margin-left: 40px; 672 | margin-right: 40px; 673 | max-width: 590px; 674 | } 675 | body:not(.no-literate):not(.big-h3) .content > h3 { 676 | margin-left: 40px; 677 | width: 590px; 678 | margin-bottom: 3px; 679 | padding-left: 0; 680 | padding-right: 0; 681 | } 682 | } 683 | 684 | .header { 685 | background: #f3f6fb; 686 | text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5); 687 | border-bottom: solid 1px #dfe2e7; 688 | padding: 15px 15px 15px 30px; 689 | zoom: 1; 690 | line-height: 20px; 691 | position: relative; 692 | } 693 | 694 | .header:before, 695 | .header:after { 696 | content: ''; 697 | display: table; 698 | } 699 | 700 | .header:after { 701 | clear: both; 702 | } 703 | 704 | .header .left { 705 | float: left; 706 | } 707 | 708 | .header .right { 709 | text-align: right; 710 | position: absolute; 711 | right: 15px; 712 | top: 15px; 713 | } 714 | 715 | .header .right iframe { 716 | display: inline-block; 717 | vertical-align: middle; 718 | } 719 | 720 | .header h1 { 721 | -webkit-font-smoothing: antialiased; 722 | text-rendering: optimizeLegibility; 723 | font-weight: bold; 724 | font-family: montserrat, sans-serif; 725 | font-size: 13px; 726 | } 727 | 728 | .header h1, 729 | .header h1 a, 730 | .header h1 a:visited { 731 | color: #fc4b4e; 732 | } 733 | 734 | .header h1 a:hover { 735 | color: #000; 736 | } 737 | 738 | .header li a { 739 | font-size: 0.88em; 740 | color: #fc4b4e; 741 | display: block; 742 | } 743 | 744 | .header li a:hover { 745 | color: #3a3a44; 746 | } 747 | 748 | @media (min-width: 480px) { 749 | .header h1 { 750 | float: left; 751 | } 752 | .header ul, 753 | .header li { 754 | display: block; 755 | float: left; 756 | } 757 | .header ul { 758 | margin-left: -15px; 759 | } 760 | .header h1 + ul { 761 | border-left: solid 1px #dfe2e7; 762 | margin-left: 15px; 763 | } 764 | .header li { 765 | border-left: solid 1px rgba(255, 255, 255, 0.5); 766 | border-right: solid 1px #dfe2e7; 767 | } 768 | .header li:last-child { 769 | border-right: 0; 770 | } 771 | .header li a { 772 | padding: 0 15px; 773 | } 774 | } 775 | 776 | @media (max-width: 480px) { 777 | .right { 778 | display: none; 779 | } 780 | } 781 | 782 | .menubar { 783 | -webkit-font-smoothing: antialiased; 784 | text-rendering: optimizeLegibility; 785 | } 786 | 787 | .menubar .section { 788 | padding: 30px 30px; 789 | -webkit-box-sizing: border-box; 790 | -moz-box-sizing: border-box; 791 | box-sizing: border-box; 792 | } 793 | 794 | .menubar .section + .section { 795 | border-top: solid 1px #dfe2e7; 796 | } 797 | 798 | .menubar .section.no-line { 799 | border-top: 0; 800 | padding-top: 0; 801 | } 802 | 803 | a.big.button { 804 | display: block; 805 | -webkit-box-sizing: border-box; 806 | -moz-box-sizing: border-box; 807 | box-sizing: border-box; 808 | width: 100%; 809 | padding: 10px 20px; 810 | text-align: center; 811 | font-weight: bold; 812 | font-size: 1.1em; 813 | background: transparent; 814 | border: solid 3px #fc4b4e; 815 | -webkit-border-radius: 30px; 816 | border-radius: 30px; 817 | font-family: montserrat, sans-serif; 818 | } 819 | 820 | a.big.button, 821 | a.big.button:visited { 822 | color: #fc4b4e; 823 | text-decoration: none; 824 | } 825 | 826 | a.big.button:hover { 827 | background: #fc4b4e; 828 | } 829 | 830 | a.big.button:hover, 831 | a.big.button:hover:visited { 832 | color: #fff; 833 | } 834 | 835 | @media (max-width: 480px) { 836 | .menubar { 837 | padding: 20px; 838 | border-bottom: solid 1px #dfe2e7; 839 | } 840 | } 841 | 842 | @media (max-width: 768px) { 843 | .menubar { 844 | display: none; 845 | } 846 | } 847 | 848 | @media (min-width: 768px) { 849 | .content-root { 850 | padding-left: 230px; 851 | } 852 | .menubar { 853 | position: absolute; 854 | left: 0; 855 | top: 0; 856 | bottom: 0; 857 | width: 230px; 858 | border-right: solid 1px #dfe2e7; 859 | } 860 | .menubar.fixed { 861 | position: fixed; 862 | overflow-y: auto; 863 | } 864 | .menubar.fixed { 865 | -webkit-overflow-scrolling: touch; 866 | } 867 | .menubar.fixed::-webkit-scrollbar { 868 | width: 15px; 869 | height: 15px; 870 | } 871 | .menubar.fixed::-webkit-scrollbar-thumb { 872 | background: #ddd; 873 | -webkit-border-radius: 8px; 874 | border-radius: 8px; 875 | border: solid 4px #fff; 876 | } 877 | .menubar.fixed:hover::-webkit-scrollbar-thumb { 878 | background: #999; 879 | -webkit-box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.2); 880 | box-shadow: inset 2px 2px 3px rgba(0, 0, 0, 0.2); 881 | } 882 | } 883 | 884 | .menubar { 885 | font-size: 0.9em; 886 | } 887 | 888 | .menu ul.level-1 > li + li { 889 | margin-top: 20px; 890 | } 891 | 892 | .menu a { 893 | -webkit-box-sizing: border-box; 894 | -moz-box-sizing: border-box; 895 | box-sizing: border-box; 896 | position: relative; 897 | display: block; 898 | padding-top: 1px; 899 | padding-bottom: 1px; 900 | margin-right: -30px; 901 | } 902 | 903 | .menu a, 904 | .menu a:visited { 905 | color: #fc4b4e; 906 | } 907 | 908 | .menu a:hover { 909 | color: #000; 910 | } 911 | 912 | .menu a.level-1 { 913 | font-family: montserrat, sans-serif; 914 | text-transform: uppercase; 915 | font-size: 0.9em; 916 | font-weight: bold; 917 | } 918 | 919 | .menu a.level-1, 920 | .menu a.level-1:visited { 921 | color: #fc4b4e; 922 | } 923 | 924 | .menu a.level-1:hover { 925 | color: #000000; 926 | } 927 | 928 | .menu a.level-2 { 929 | font-weight: normal; 930 | color: #000000; 931 | } 932 | 933 | .menu a.level-2:hover { 934 | color: #fc4b4e; 935 | } 936 | 937 | .menu a.level-3 { 938 | font-weight: normal; 939 | font-size: 0.9em; 940 | padding-left: 10px; 941 | } 942 | 943 | .menu a.active { 944 | font-weight: bold !important; 945 | } 946 | 947 | .menu a.active, 948 | .menu a.active:visited, 949 | .menu a.active:hover { 950 | color: #000 !important; 951 | } 952 | 953 | .menu a.active:after { 954 | content: ''; 955 | display: block; 956 | -webkit-box-sizing: border-box; 957 | -moz-box-sizing: border-box; 958 | box-sizing: border-box; 959 | position: absolute; 960 | top: 10px; 961 | right: 30px; 962 | width: 9px; 963 | height: 3px; 964 | -webkit-border-radius: 2px; 965 | border-radius: 2px; 966 | background: #fc4b4e; 967 | } 968 | 969 | code .string, 970 | code .number { 971 | color: #3ac; 972 | } 973 | 974 | code .init { 975 | color: #383; 976 | } 977 | 978 | code .keyword { 979 | font-weight: bold; 980 | } 981 | 982 | code .comment { 983 | color: #adadcc; 984 | } 985 | 986 | .large-brief .content > h1:first-child + p, 987 | .content > p.brief { 988 | font-size: 1.3em; 989 | font-family: Open Sans, sans-serif; 990 | font-weight: 300; 991 | } 992 | 993 | .title-area { 994 | min-height: 100px; 995 | -webkit-box-sizing: border-box; 996 | -moz-box-sizing: border-box; 997 | box-sizing: border-box; 998 | -webkit-font-smoothing: antialiased; 999 | text-rendering: optimizeLegibility; 1000 | text-align: center; 1001 | border-bottom: solid 1px #dfe2e7; 1002 | overflow: hidden; 1003 | } 1004 | 1005 | .title-area > img.bg { 1006 | z-index: 0; 1007 | position: absolute; 1008 | left: -9999px; 1009 | } 1010 | 1011 | .title-area > div { 1012 | position: relative; 1013 | z-index: 1; 1014 | } 1015 | 1016 | .small { 1017 | width: 470px; 1018 | margin-bottom: 3px; 1019 | padding-left: 0; 1020 | padding-right: 0; 1021 | clear: both; 1022 | font-size: 0.85em; 1023 | color: #fc4b4e; 1024 | padding-top: 30px; 1025 | padding-bottom: 10px; 1026 | -webkit-font-smoothing: antialiased; 1027 | text-rendering: optimizeLegibility; 1028 | font-family: montserrat; 1029 | } 1030 | -------------------------------------------------------------------------------- /front-page.php: -------------------------------------------------------------------------------- 1 | latest_posts( 10, null, array(), null )->get(); 14 | $context['posts'] = $latest_posts; 15 | 16 | // Render view. 17 | Timber::render( 'pages/front-page.twig', $context ); 18 | -------------------------------------------------------------------------------- /functions.php: -------------------------------------------------------------------------------- 1 | get( 'Version' ) ); 20 | 21 | /** 22 | * Use Dotenv to set required environment variables and load .env file when present. 23 | */ 24 | Dotenv\Dotenv::create( __DIR__ )->safeLoad(); 25 | 26 | /** 27 | * Set up our global environment constant and load its config first 28 | * Default: production 29 | */ 30 | define( 'WP_ENV', getenv( 'WP_ENV' ) ? getenv( 'WP_ENV' ) : 'production' ); 31 | 32 | $timber = new Timber\Timber(); 33 | Timber::$dirname = array( 'templates' ); 34 | 35 | add_action( 36 | 'after_setup_theme', 37 | function () { 38 | $managers = array( 39 | /* new \Skela\Managers\TaxonomiesManager(), */ 40 | new \Skela\Managers\WordPressManager(), 41 | new \Skela\Managers\GutenbergManager(), 42 | new \Skela\Managers\CustomPostsManager(), 43 | ); 44 | 45 | if ( function_exists( 'acf_add_local_field_group' ) ) { 46 | $managers[] = new \Skela\Managers\ACFManager(); 47 | } 48 | 49 | $theme_manager = new ThemeManager( $managers ); 50 | $theme_manager->run(); 51 | } 52 | ); 53 | -------------------------------------------------------------------------------- /gutenberg-acf-backups/acf-export-2019-06-21.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "key": "group_5cdef47b943c9", 4 | "title": "Image Layout", 5 | "fields": [ 6 | { 7 | "key": "field_5cdef487a8fcc", 8 | "label": "Images", 9 | "name": "imageLayoutImages", 10 | "type": "gallery", 11 | "instructions": "", 12 | "required": 0, 13 | "conditional_logic": 0, 14 | "wrapper": { 15 | "width": "", 16 | "class": "", 17 | "id": "" 18 | }, 19 | "min": "", 20 | "max": "", 21 | "insert": "append", 22 | "library": "all", 23 | "min_width": "", 24 | "min_height": "", 25 | "min_size": "", 26 | "max_width": "", 27 | "max_height": "", 28 | "max_size": "", 29 | "mime_types": "", 30 | "return_format": "array", 31 | "preview_size": "medium" 32 | }, 33 | { 34 | "key": "field_5cdef49aa8fcd", 35 | "label": "Layout", 36 | "name": "imageLayoutLayout", 37 | "type": "radio", 38 | "instructions": "", 39 | "required": 0, 40 | "conditional_logic": 0, 41 | "wrapper": { 42 | "width": "", 43 | "class": "", 44 | "id": "" 45 | }, 46 | "choices": { 47 | "2-symmetrical": "2 Symmetrical", 48 | "2-asymmetrical": "2 Asymmetrical", 49 | "3-symmetrical": "3 Symmetrical", 50 | "3-asymmetrical": "3 Asymmetrical" 51 | }, 52 | "allow_null": 0, 53 | "other_choice": 0, 54 | "default_value": "", 55 | "layout": "horizontal", 56 | "return_format": "value", 57 | "save_other_choice": 0 58 | }, 59 | { 60 | "key": "field_5cdeffb837320", 61 | "label": "Crop image to same aspect ratio?", 62 | "name": "imageLayoutCrop", 63 | "type": "true_false", 64 | "instructions": "", 65 | "required": 0, 66 | "conditional_logic": 0, 67 | "wrapper": { 68 | "width": "", 69 | "class": "", 70 | "id": "" 71 | }, 72 | "message": "", 73 | "default_value": 0, 74 | "ui": 1, 75 | "ui_on_text": "", 76 | "ui_off_text": "" 77 | } 78 | ], 79 | "location": [ 80 | [ 81 | { 82 | "param": "block", 83 | "operator": "==", 84 | "value": "acf/imagelayout" 85 | } 86 | ] 87 | ], 88 | "menu_order": 0, 89 | "position": "normal", 90 | "style": "default", 91 | "label_placement": "top", 92 | "instruction_placement": "label", 93 | "hide_on_screen": "", 94 | "active": true, 95 | "description": "" 96 | }, 97 | { 98 | "key": "group_5cd5a5077e357", 99 | "title": "Related Articles", 100 | "fields": [ 101 | { 102 | "key": "field_5cd5a5401a9eb", 103 | "label": "Header text", 104 | "name": "header_text", 105 | "type": "text", 106 | "instructions": "", 107 | "required": 0, 108 | "conditional_logic": 0, 109 | "wrapper": { 110 | "width": "", 111 | "class": "", 112 | "id": "" 113 | }, 114 | "default_value": "Related Articles", 115 | "placeholder": "", 116 | "prepend": "", 117 | "append": "", 118 | "maxlength": "" 119 | }, 120 | { 121 | "key": "field_5cd5a50d1a9e9", 122 | "label": "Chosen articles", 123 | "name": "chosen_articles", 124 | "type": "repeater", 125 | "instructions": "", 126 | "required": 1, 127 | "conditional_logic": 0, 128 | "wrapper": { 129 | "width": "", 130 | "class": "", 131 | "id": "" 132 | }, 133 | "collapsed": "", 134 | "min": 1, 135 | "max": 4, 136 | "layout": "table", 137 | "button_label": "Add an article", 138 | "sub_fields": [ 139 | { 140 | "key": "field_5cd5c5733d782", 141 | "label": "Related Article", 142 | "name": "related_article", 143 | "type": "post_object", 144 | "instructions": "", 145 | "required": 0, 146 | "conditional_logic": 0, 147 | "wrapper": { 148 | "width": "", 149 | "class": "", 150 | "id": "" 151 | }, 152 | "post_type": ["post"], 153 | "taxonomy": "", 154 | "allow_null": 0, 155 | "multiple": 0, 156 | "return_format": "object", 157 | "ui": 1 158 | } 159 | ] 160 | } 161 | ], 162 | "location": [ 163 | [ 164 | { 165 | "param": "block", 166 | "operator": "==", 167 | "value": "acf/relatedarticles" 168 | } 169 | ] 170 | ], 171 | "menu_order": 0, 172 | "position": "normal", 173 | "style": "default", 174 | "label_placement": "top", 175 | "instruction_placement": "label", 176 | "hide_on_screen": "", 177 | "active": true, 178 | "description": "" 179 | } 180 | ] 181 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | 1%", 30 | "last 2 versions" 31 | ], 32 | "scripts": { 33 | "build": "NODE_ENV=production webpack --progress --hide-modules --mode production", 34 | "start": "NODE_ENV=development webpack --hide-modules --watch --mode development", 35 | "start:ups-dock": "NODE_ENV=development webpack --hide-modules --watch --mode development --env.platform=ups-dock", 36 | "lint:scripts": "eslint static/js/**/*.js --cache", 37 | "lint:php": "./vendor/bin/phpcs --standard=phpcs.xml.dist --error-severity=1 --warning-severity=8 -p", 38 | "fix:php": "./vendor/bin/phpcbf", 39 | "phpstan": "./vendor/bin/phpstan analyze --memory-limit 512M", 40 | "test:bundlesize": "bundlesize", 41 | "test:a11y": "node tests/pa11y.js", 42 | "prepare": "husky install", 43 | "lint-staged": "lint-staged" 44 | }, 45 | "lint-staged": { 46 | "*.{js,css,json,md}": [ 47 | "prettier --write" 48 | ], 49 | "*.js": [ 50 | "eslint --fix" 51 | ] 52 | }, 53 | "dependencies": { 54 | "lazysizes": "^5.2.1" 55 | }, 56 | "devDependencies": { 57 | "@babel/core": "^7.14.2", 58 | "@babel/eslint-parser": "^7.14.2", 59 | "@babel/plugin-transform-react-jsx": "^7.12.11", 60 | "@babel/preset-env": "^7.12.11", 61 | "@upstatement/eslint-config": "^1.0.1", 62 | "@upstatement/prettier-config": "^1.0.0", 63 | "autoprefixer": "^9.8.6", 64 | "babel-loader": "^8.2.2", 65 | "babel-preset-env": "^1.7.0", 66 | "browser-sync": "^2.26.13", 67 | "browser-sync-webpack-plugin": "^2.3.0", 68 | "bundlesize": "^0.17.2", 69 | "css-loader": "^3.6.0", 70 | "dotenv-webpack": "^1.8.0", 71 | "eslint": "^7.26.0", 72 | "eslint-config-prettier": "^8.3.0", 73 | "file-loader": "^5.0.2", 74 | "husky": "^6.0.0", 75 | "lint-staged": "^11.0.0", 76 | "mini-css-extract-plugin": "^0.8.0", 77 | "pa11y": "^5.1.0", 78 | "postcss-loader": "^3.0.0", 79 | "prettier": "^2.3.0", 80 | "sass": "^1.30.0", 81 | "sass-loader": "7.*", 82 | "webpack": "^4.44.2", 83 | "webpack-cli": "^3.3.12", 84 | "webpack-dev-middleware": "^3.7.3", 85 | "webpack-modernizr-loader": "^5.0.0" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /page.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | Baseline coding standards 4 | 5 | 9 | 10 | 11 | 12 | ./src/ 13 | 14 | 15 | ./404.php 16 | ./front-page.php 17 | ./functions.php 18 | ./index.php 19 | ./page.php 20 | ./single.php 21 | 22 | *.js 23 | *.css 24 | 25 | 26 | 27 | error 28 | 29 | 30 | 31 | 32 | 42 | 43 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /phpstan.neon.dist: -------------------------------------------------------------------------------- 1 | includes: 2 | - vendor/szepeviktor/phpstan-wordpress/extension.neon 3 | parameters: 4 | level: max 5 | inferPrivatePropertyTypeFromConstructor: true 6 | # TODO Add types inside arrays 7 | checkMissingIterableValueType: false 8 | bootstrapFiles: 9 | - tests/phpstan/bootstrap.php 10 | scanFiles: 11 | - vendor/php-stubs/acf-pro-stubs/acf-pro-stubs.php 12 | paths: 13 | - src/ 14 | excludePaths: 15 | - */node_modules/* 16 | -------------------------------------------------------------------------------- /plugins/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstatement/skela-wp-theme/1fb41ec3564c3961e2bf126ea87b1e3a5e73413d/plugins/.gitkeep -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [require('autoprefixer')], 3 | }; 4 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@upstatement/prettier-config'); 2 | -------------------------------------------------------------------------------- /single.php: -------------------------------------------------------------------------------- 1 | 'imageLayout', 44 | 'title' => __( 'Image Layout' ), 45 | 'description' => __( 'A custom image block.' ), 46 | 'render_callback' => array( $this, 'render' ), 47 | 'category' => 'formatting', 48 | 'icon' => 'images-alt', 49 | 'keywords' => array( 'image', 'layout' ), 50 | 'mode' => 'edit', 51 | ) 52 | ); 53 | } 54 | } 55 | 56 | /** 57 | * Get info from the related ACF fields and then render corrosponding template. 58 | * 59 | * @param array $block The block settings and attributes. 60 | * @param string $content The block content (empty content). 61 | * @param bool $is_preview True during AJAX preview. 62 | * 63 | * @return void 64 | */ 65 | public function render( $block, $content, $is_preview ) { 66 | $context['layout'] = get_field( 'imageLayoutLayout' ); 67 | $context['cropToSameRatio'] = get_field( 'imageLayoutCrop' ); 68 | 69 | $context['images'] = array_map( 70 | function ( $image ) { 71 | // @phpstan-ignore-next-line 72 | return new TimberImage( $image['id'] ); 73 | }, 74 | // @phpstan-ignore-next-line 75 | get_field( 'imageLayoutImages' ) 76 | ); 77 | 78 | $templates = array( 'templates/components/image-layout.twig' ); 79 | 80 | if ( $is_preview ) { 81 | echo 'Preview mode is not supported for related articles. Please change to Edit mode by clicking the pencil icon in the toolbar above.'; 82 | } else { 83 | Timber::render( $templates, $context ); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Blocks/ImageLayout/editor.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * #.# Editor Styles 3 | * 4 | * CSS for just Backend enqueued after style.scss 5 | * which makes it higher in priority. 6 | */ 7 | 8 | /* ImageBlock editor styles */ 9 | 10 | $image-path: './ImageLayout/'; 11 | 12 | $opacity-active: 1; 13 | $opacity-inactive: 0.3; 14 | 15 | div[data-name='imageLayoutLayout'] { 16 | .acf-radio-list { 17 | padding-top: 70px; 18 | } 19 | label { 20 | opacity: $opacity-inactive; 21 | transition: opacity 0.3s; 22 | width: 140px; 23 | &.selected { 24 | opacity: $opacity-active; 25 | } 26 | } 27 | 28 | input[type='radio'] { 29 | opacity: $opacity-inactive; 30 | position: relative; 31 | 32 | &::after { 33 | background-position: 50% 50%; 34 | background-repeat: no-repeat; 35 | border-radius: 0; 36 | content: ''; 37 | display: block; 38 | height: 60px; 39 | left: 20px; 40 | position: absolute; 41 | top: -80px; 42 | width: 80px; 43 | } 44 | } 45 | 46 | input[value='2-symmetrical']:after { 47 | background-image: url('#{$image-path}2-symmetrical.png'); 48 | } 49 | input[value='2-asymmetrical']:after { 50 | background-image: url('#{$image-path}2-asymmetrical.png'); 51 | } 52 | input[value='3-symmetrical']:after { 53 | background-image: url('#{$image-path}3-symmetrical.png'); 54 | } 55 | input[value='3-asymmetrical']:after { 56 | background-image: url('#{$image-path}3-asymmetrical.png'); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Blocks/ImageLayout/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * #.# Styles 3 | * 4 | * CSS for both Frontend+Backend. 5 | */ 6 | -------------------------------------------------------------------------------- /src/Blocks/RelatedArticles/RelatedArticles.php: -------------------------------------------------------------------------------- 1 | 'relatedArticles', 42 | 'title' => __( 'Related Articles' ), 43 | 'description' => __( 'A custom block for inserting links to other articles.' ), 44 | 'render_callback' => array( $this, 'render' ), 45 | 'category' => 'widgets', 46 | 'icon' => array( 47 | 'background' => '#ecf6f6', 48 | 'src' => 'list-view', 49 | ), 50 | 'keywords' => array( 'related', 'articles' ), 51 | 'mode' => 'edit', 52 | ) 53 | ); 54 | } 55 | } 56 | 57 | /** 58 | * Get the Related Articles text and related articles, 59 | * and then render corrosponding template 60 | * 61 | * @param array $block The block settings and attributes. 62 | * @param string $content The block content (empty content). 63 | * @param bool $is_preview True during AJAX preview. 64 | * 65 | * @return void 66 | */ 67 | public function render( $block, $content, $is_preview ) { 68 | $context = Timber::context(); 69 | 70 | $related_articles = get_field( 'chosen_articles' ); 71 | $related_article_ids = array(); 72 | 73 | $context['relatedArticlesHeader'] = get_field( 'header_text' ); 74 | 75 | if ( ! empty( $related_articles ) && is_array( $related_articles ) ) { 76 | foreach ( $related_articles as $article ) { 77 | if ( ! empty( $article['related_article'] ) ) { 78 | $related_article_ids[] = $article['related_article']->ID; 79 | } 80 | } 81 | // Set query args. 82 | if ( ! empty( $related_article_ids ) ) { 83 | $args = array( 84 | 'post_status' => 'publish', 85 | 'post__in' => $related_article_ids, 86 | 'orderby' => 'post__in', 87 | ); 88 | 89 | $context['relatedArticles'] = new PostQuery( $args ); 90 | } 91 | } 92 | 93 | if ( $is_preview ) { 94 | echo 'Preview mode is not supported for related articles. Please change to Edit mode by clicking the pencil icon in the toolbar above.'; 95 | } else { 96 | Timber::render( 'templates/components/related-articles.twig', $context ); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Blocks/RelatedArticles/editor.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * #.# Editor Styles 3 | * 4 | * CSS for just Backend enqueued after style.scss 5 | * which makes it higher in priority. 6 | */ 7 | 8 | /* RelatedArticles editor styles */ 9 | -------------------------------------------------------------------------------- /src/Blocks/RelatedArticles/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * #.# Styles 3 | * 4 | * CSS for both Frontend+Backend. 5 | */ 6 | -------------------------------------------------------------------------------- /src/Blocks/SampleACFBlock/ACFBlock.php: -------------------------------------------------------------------------------- 1 | 'acf-block', 35 | 'title' => __( 'ACF Block' ), 36 | 'description' => __( 'A custom block that incorporates ACF fields.' ), 37 | 'render_callback' => array( $this, 'render' ), 38 | 'category' => 'widgets', 39 | 'icon' => array( 40 | 'background' => '#ecf6f6', 41 | 'src' => 'email', 42 | ), 43 | 'keywords' => array( 'example', 'acf' ), 44 | 'mode' => 'edit', 45 | ) 46 | ); 47 | } 48 | } 49 | 50 | /** 51 | * Get info from the related ACF fields 52 | * and then render corrosponding template 53 | * 54 | * @param array $block The block settings and attributes. 55 | * @param string $content The block content (empty content). 56 | * @param bool $is_preview True during AJAX preview. 57 | * 58 | * @return void 59 | */ 60 | public function render( $block, $content, $is_preview ) { 61 | // If the block renders info from TimberTheme, TimberSite, etc., then 62 | // uncomment the following. 63 | /** $context = Timber::context(); */ 64 | 65 | $context['some_headline'] = get_field( 'some_headline' ); 66 | $context['some_text'] = get_field( 'some_text' ); 67 | 68 | Timber::render( array( 'templates/components/acf-block.twig' ), $context ); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Blocks/SampleBlock/editor.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * #.# Editor Styles 3 | * 4 | * CSS for just Backend enqueued after style.scss 5 | * which makes it higher in priority. 6 | */ 7 | 8 | .sample-block { 9 | border: 0.2rem solid #000; 10 | margin: 0 auto; 11 | max-width: 740px; 12 | padding: 2rem; 13 | } 14 | -------------------------------------------------------------------------------- /src/Blocks/SampleBlock/sample-block.js: -------------------------------------------------------------------------------- 1 | // Import CSS. 2 | import './style.scss'; 3 | import './editor.scss'; 4 | 5 | class SampleBlock { 6 | constructor(el) { 7 | this.registerMyBlock(); 8 | } 9 | 10 | registerMyBlock() { 11 | const { __ } = wp.i18n; 12 | const { registerBlockType } = wp.blocks; // Import registerBlockType() from wp.blocks 13 | const { RichText } = wp.editor; 14 | 15 | registerBlockType('skela/sample-block', { 16 | title: __('Sample Block'), // Block title. 17 | icon: 'nametag', 18 | category: 'widgets', 19 | keywords: [__('sample block')], 20 | attributes: { 21 | content: { 22 | source: 'html', 23 | selector: 'p', 24 | }, 25 | }, 26 | 27 | edit({ attributes, className, setAttributes }) { 28 | const { content } = attributes; 29 | 30 | function onChangeContent(newContent) { 31 | setAttributes({ content: newContent }); 32 | } 33 | 34 | return ( 35 | 36 | ); 37 | }, 38 | 39 | save({ attributes, className }) { 40 | const { content } = attributes; 41 | 42 | return ; 43 | }, 44 | }); 45 | } 46 | } 47 | 48 | export default SampleBlock; 49 | -------------------------------------------------------------------------------- /src/Blocks/SampleBlock/style.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * #.# Styles 3 | * 4 | * CSS for both Frontend+Backend. 5 | */ 6 | 7 | .sample-block { 8 | border: 0.2rem solid #000; 9 | margin: 0 auto; 10 | max-width: 740px; 11 | padding: 2rem; 12 | } 13 | -------------------------------------------------------------------------------- /src/Blocks/editor.scss: -------------------------------------------------------------------------------- 1 | @import 'ImageLayout/editor'; 2 | @import 'RelatedArticles/editor'; 3 | @import 'SampleBlock/editor'; 4 | -------------------------------------------------------------------------------- /src/Blocks/style.scss: -------------------------------------------------------------------------------- 1 | @import 'ImageLayout/style'; 2 | @import 'RelatedArticles/style'; 3 | @import 'SampleBlock/style'; 4 | -------------------------------------------------------------------------------- /src/Managers/ACFManager.php: -------------------------------------------------------------------------------- 1 | 'group_5cdef47b943c9', 37 | 'title' => 'Image Layout', 38 | 'fields' => array( 39 | array( 40 | 'key' => 'field_5cdef487a8fcc', 41 | 'label' => 'Images', 42 | 'name' => 'imageLayoutImages', 43 | 'type' => 'gallery', 44 | 'instructions' => '', 45 | 'required' => 0, 46 | 'conditional_logic' => 0, 47 | 'wrapper' => array( 48 | 'width' => '', 49 | 'class' => '', 50 | 'id' => '', 51 | ), 52 | 'min' => '2', 53 | 'max' => '3', 54 | 'insert' => 'append', 55 | 'library' => 'all', 56 | 'min_width' => '', 57 | 'min_height' => '', 58 | 'min_size' => '', 59 | 'max_width' => '', 60 | 'max_height' => '', 61 | 'max_size' => '', 62 | 'mime_types' => '', 63 | 'return_format' => 'array', 64 | 'preview_size' => 'medium', 65 | ), 66 | array( 67 | 'key' => 'field_5cdef49aa8fcd', 68 | 'label' => 'Layout', 69 | 'name' => 'imageLayoutLayout', 70 | 'type' => 'radio', 71 | 'instructions' => '', 72 | 'required' => 0, 73 | 'conditional_logic' => 0, 74 | 'wrapper' => array( 75 | 'width' => '', 76 | 'class' => '', 77 | 'id' => '', 78 | ), 79 | 'choices' => array( 80 | '2-symmetrical' => '2 Symmetrical', 81 | '2-asymmetrical' => '2 Asymmetrical', 82 | '3-symmetrical' => '3 Symmetrical', 83 | '3-asymmetrical' => '3 Asymmetrical', 84 | ), 85 | 'allow_null' => 0, 86 | 'other_choice' => 0, 87 | 'default_value' => '', 88 | 'layout' => 'horizontal', 89 | 'return_format' => 'value', 90 | 'save_other_choice' => 0, 91 | ), 92 | array( 93 | 'key' => 'field_5cdeffb837320', 94 | 'label' => 'Crop image to same aspect ratio?', 95 | 'name' => 'imageLayoutCrop', 96 | 'type' => 'true_false', 97 | 'instructions' => '', 98 | 'required' => 0, 99 | 'conditional_logic' => 0, 100 | 'wrapper' => array( 101 | 'width' => '', 102 | 'class' => '', 103 | 'id' => '', 104 | ), 105 | 'message' => '', 106 | 'default_value' => 0, 107 | 'ui' => 1, 108 | 'ui_on_text' => '', 109 | 'ui_off_text' => '', 110 | ), 111 | ), 112 | 'location' => array( 113 | array( 114 | array( 115 | 'param' => 'block', 116 | 'operator' => '==', 117 | 'value' => 'acf/imagelayout', 118 | ), 119 | ), 120 | ), 121 | 'menu_order' => 0, 122 | 'position' => 'normal', 123 | 'style' => 'default', 124 | 'label_placement' => 'top', 125 | 'instruction_placement' => 'label', 126 | 'hide_on_screen' => '', 127 | 'active' => true, 128 | 'description' => '', 129 | ) 130 | ); 131 | } 132 | 133 | /** 134 | * Register related article fields 135 | * 136 | * @return void 137 | */ 138 | public function related_article_fields() { 139 | acf_add_local_field_group( 140 | array( 141 | 'key' => 'group_5cd5a5077e357', 142 | 'title' => 'Related Articles', 143 | 'fields' => array( 144 | array( 145 | 'key' => 'field_5cd5a5401a9eb', 146 | 'label' => 'Header text', 147 | 'name' => 'header_text', 148 | 'type' => 'text', 149 | 'instructions' => '', 150 | 'required' => 0, 151 | 'conditional_logic' => 0, 152 | 'wrapper' => array( 153 | 'width' => '', 154 | 'class' => '', 155 | 'id' => '', 156 | ), 157 | 'default_value' => 'Related Articles', 158 | 'placeholder' => '', 159 | 'prepend' => '', 160 | 'append' => '', 161 | 'maxlength' => '', 162 | ), 163 | array( 164 | 'key' => 'field_5cd5a50d1a9e9', 165 | 'label' => 'Chosen articles', 166 | 'name' => 'chosen_articles', 167 | 'type' => 'repeater', 168 | 'instructions' => '', 169 | 'required' => 1, 170 | 'conditional_logic' => 0, 171 | 'wrapper' => array( 172 | 'width' => '', 173 | 'class' => '', 174 | 'id' => '', 175 | ), 176 | 'collapsed' => '', 177 | 'min' => 1, 178 | 'max' => 4, 179 | 'layout' => 'table', 180 | 'button_label' => 'Add an article', 181 | 'sub_fields' => array( 182 | array( 183 | 'key' => 'field_5cd5c5733d782', 184 | 'label' => 'Related Article', 185 | 'name' => 'related_article', 186 | 'type' => 'post_object', 187 | 'instructions' => '', 188 | 'required' => 0, 189 | 'conditional_logic' => 0, 190 | 'wrapper' => array( 191 | 'width' => '', 192 | 'class' => '', 193 | 'id' => '', 194 | ), 195 | 'post_type' => array( 196 | 0 => 'post', 197 | ), 198 | 'taxonomy' => '', 199 | 'allow_null' => 0, 200 | 'multiple' => 0, 201 | 'return_format' => 'object', 202 | 'ui' => 1, 203 | ), 204 | ), 205 | ), 206 | ), 207 | 'location' => array( 208 | array( 209 | array( 210 | 'param' => 'block', 211 | 'operator' => '==', 212 | 'value' => 'acf/relatedarticles', 213 | ), 214 | ), 215 | ), 216 | 'menu_order' => 0, 217 | 'position' => 'normal', 218 | 'style' => 'default', 219 | 'label_placement' => 'top', 220 | 'instruction_placement' => 'label', 221 | 'hide_on_screen' => '', 222 | 'active' => true, 223 | 'description' => '', 224 | ) 225 | ); 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/Managers/CustomPostsManager.php: -------------------------------------------------------------------------------- 1 | __( 'Authors' ), 36 | 'singular_name' => __( 'Author' ), 37 | 'add_new_item' => __( 'Add New Author' ), 38 | ); 39 | 40 | register_post_type( 41 | 'author', 42 | array( 43 | 'labels' => $author_labels, 44 | 'public' => true, 45 | 'has_archive' => true, 46 | 'menu_position' => 5, 47 | 'rewrite' => array( 'slug' => 'authors' ), 48 | 'supports' => array( 'editor', 'excerpt', 'title', 'thumbnail' ), 49 | 'taxonomies' => array(), 50 | ) 51 | ); 52 | */ 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Managers/GutenbergManager.php: -------------------------------------------------------------------------------- 1 | _x( 'Authors', 'Taxonomy General Name', 'text_domain' ), 31 | 'singular_name' => _x( 'Author', 'Taxonomy Singular Name', 'text_domain' ), 32 | 'menu_name' => __( 'Authors', 'text_domain' ), 33 | 'all_items' => __( 'All Authors', 'text_domain' ), 34 | 'parent_item' => __( 'Parent Author', 'text_domain' ), 35 | 'parent_item_colon' => __( 'Parent Author:', 'text_domain' ), 36 | 'new_item_name' => __( 'New Author Name', 'text_domain' ), 37 | 'add_new_item' => __( 'Add New Author', 'text_domain' ), 38 | 'edit_item' => __( 'Edit Author', 'text_domain' ), 39 | 'update_item' => __( 'Update Author', 'text_domain' ), 40 | 'view_item' => __( 'View Author', 'text_domain' ), 41 | 'separate_items_with_commas' => __( 'Separate authors with commas', 'text_domain' ), 42 | 'add_or_remove_items' => __( 'Add or remove authors', 'text_domain' ), 43 | 'choose_from_most_used' => __( 'Choose from the most used', 'text_domain' ), 44 | 'popular_items' => __( 'Popular Authors', 'text_domain' ), 45 | 'search_items' => __( 'Search Authors', 'text_domain' ), 46 | 'not_found' => __( 'Not Found', 'text_domain' ), 47 | 'no_terms' => __( 'No authors', 'text_domain' ), 48 | 'items_list' => __( 'Authors list', 'text_domain' ), 49 | 'items_list_navigation' => __( 'Authors list navigation', 'text_domain' ), 50 | ); 51 | 52 | $authors_args = array( 53 | 'labels' => $authors_labels, 54 | 'hierarchical' => false, 55 | 'public' => true, 56 | 'show_ui' => true, 57 | 'show_in_quick_edit' => false, 58 | 'meta_box_cb' => false, 59 | 'show_admin_column' => true, 60 | 'show_in_nav_menus' => true, 61 | 'show_in_rest' => true, 62 | 'show_tagcloud' => true, 63 | 'query_var' => 'authors', 64 | ); 65 | 66 | register_taxonomy( 'author', array( 'post' ), $authors_args ); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Managers/ThemeManager.php: -------------------------------------------------------------------------------- 1 | managers = $managers; 29 | 30 | add_filter( 'timber/context', array( $this, 'add_wp_env_to_context' ) ); 31 | add_filter( 'timber/context', array( $this, 'add_theme_version_to_context' ) ); 32 | add_filter( 'timber/context', array( $this, 'add_is_home_to_context' ) ); 33 | add_filter( 'timber/context', array( $this, 'add_menus_to_context' ) ); 34 | add_filter( 'timber/context', array( $this, 'add_acf_options_to_context' ) ); 35 | 36 | add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_admin' ) ); 37 | add_action( 'wp_dashboard_setup', array( $this, 'add_documentation_widget' ) ); 38 | add_action( 'admin_init', array( $this, 'redirect_to_docs' ), 1 ); 39 | add_action( 'admin_menu', array( $this, 'add_documentation_menu_item' ) ); 40 | add_action( 'admin_init', array( $this, 'register_menus' ) ); 41 | 42 | add_action( 'init', array( $this, 'register_options' ) ); 43 | 44 | add_filter( 'acf/fields/relationship/query', array( $this, 'post_relationship_query' ), 10, 3 ); 45 | } 46 | 47 | /** 48 | * Runs initialization tasks. 49 | * 50 | * @return void 51 | */ 52 | public function run() { 53 | if ( count( $this->managers ) > 0 ) { 54 | foreach ( $this->managers as $manager ) { 55 | $manager->run(); 56 | } 57 | } 58 | 59 | add_action( 'wp_enqueue_scripts', array( $this, 'enqueue' ), 999 ); 60 | add_theme_support( 'post-thumbnails' ); 61 | add_theme_support( 'menus' ); 62 | } 63 | 64 | /** 65 | * Enqueue javascript using WordPress 66 | * 67 | * @return void 68 | */ 69 | public function enqueue() { 70 | // Remove default Gutenberg CSS. 71 | wp_deregister_style( 'wp-block-library' ); 72 | 73 | wp_enqueue_script( 'vendor', SKELA_THEME_URL . '/dist/vendor.js', array(), SKELA_THEME_VERSION, true ); 74 | 75 | // Enqueue custom js file, with cache busting. 76 | wp_enqueue_script( 'script.js', SKELA_THEME_URL . '/dist/app.js', array(), SKELA_THEME_VERSION, true ); 77 | } 78 | 79 | /** 80 | * Enqueue JS and CSS for WP admin panel 81 | * 82 | * @return void 83 | */ 84 | public function enqueue_admin() { 85 | wp_enqueue_style( 'admin-styles', SKELA_THEME_URL . '/dist/admin.css', array(), SKELA_THEME_VERSION ); 86 | 87 | wp_enqueue_script( 'vendor', SKELA_THEME_URL . '/dist/vendor.js', array(), SKELA_THEME_VERSION, false ); 88 | wp_enqueue_script( 'admin.js', SKELA_THEME_URL . '/dist/admin.js', array(), SKELA_THEME_VERSION, false ); 89 | } 90 | 91 | /** 92 | * Adds ability to check the environment in a twig file 93 | * 94 | * @param array $context Timber context. 95 | * 96 | * @return array 97 | */ 98 | public function add_wp_env_to_context( $context ) { 99 | $context['wp_env'] = WP_ENV; 100 | return $context; 101 | } 102 | 103 | /** 104 | * Expose current theme version to Timber context 105 | * 106 | * @param array $context Timber context. 107 | * 108 | * @return array 109 | */ 110 | public function add_theme_version_to_context( $context ) { 111 | $context['theme_version'] = SKELA_THEME_VERSION; 112 | 113 | return $context; 114 | } 115 | 116 | /** 117 | * Adds ability to check if we are on the homepage in a twig file 118 | * 119 | * @param array $context Timber context. 120 | * 121 | * @return array 122 | */ 123 | public function add_is_home_to_context( $context ) { 124 | $context['is_home'] = is_home(); 125 | 126 | return $context; 127 | } 128 | 129 | /** 130 | * Register nav menus 131 | * 132 | * @return void 133 | */ 134 | public function register_menus() { 135 | register_nav_menus( 136 | array( 137 | 'nav_topics_menu' => 'Navigation Topics Menu', 138 | 'nav_pages_menu' => 'Navigation Pages Menu', 139 | ) 140 | ); 141 | } 142 | 143 | 144 | /** 145 | * Registers and adds menus to context 146 | * 147 | * @param array $context Timber context. 148 | * 149 | * @return array 150 | */ 151 | public function add_menus_to_context( $context ) { 152 | $context['nav_topics_menu'] = new Menu( 'nav_topics_menu' ); 153 | $context['nav_pages_menu'] = new Menu( 'nav_pages_menu' ); 154 | return $context; 155 | } 156 | 157 | /** 158 | * Adds a widget to the dashboard with a link to editor docs 159 | * 160 | * @return void 161 | */ 162 | public function add_documentation_widget() { 163 | wp_add_dashboard_widget( 164 | 'custom_dashboard_widget', 165 | 'Editor Documentation', 166 | function () { 167 | echo "

View the editor documentation

"; 168 | } 169 | ); 170 | } 171 | 172 | /** 173 | * Adds a menu item to WP admin that links to editor docs 174 | * 175 | * @return void 176 | */ 177 | public function add_documentation_menu_item() { 178 | add_menu_page( 179 | 'Editor Docs', 180 | 'Editor Docs', 181 | 'manage_options', 182 | 'link-to-docs', 183 | array( $this, 'redirect_to_docs' ), 184 | 'dashicons-admin-links', 185 | 100 186 | ); 187 | } 188 | 189 | 190 | /** 191 | * To have an external link to the docs we need this weird function 192 | * 193 | * @return void 194 | */ 195 | public function redirect_to_docs() { 196 | $menu_redirect = isset( $_GET['page'] ) ? $_GET['page'] : false; 197 | if ( 'link-to-docs' === $menu_redirect ) { 198 | header( 'Location: https://' . $_SERVER['HTTP_HOST'] . '/wp-content/themes/skela/documentation' ); 199 | exit(); 200 | } 201 | } 202 | 203 | /** 204 | * Add ACF options page to WordPress 205 | * 206 | * @return void 207 | */ 208 | public function register_options() { 209 | if ( function_exists( 'acf_add_options_page' ) ) { 210 | acf_add_options_page( 211 | array( 212 | 'page_title' => 'Site Settings', 213 | 'menu_title' => 'Site Settings', 214 | 'menu_slug' => 'site-settings', 215 | ) 216 | ); 217 | } 218 | } 219 | 220 | /** 221 | * Adds ability to access array of ACF options fields in a twig field 222 | * 223 | * @param array $context Timber context. 224 | * 225 | * @return array 226 | */ 227 | public function add_acf_options_to_context( $context ) { 228 | if ( class_exists( 'acf' ) ) { 229 | $context['options'] = get_fields( 'option' ); 230 | } 231 | return $context; 232 | } 233 | 234 | /** 235 | * Modify ACF relationship field to show most recent posts instead of alpha 236 | * 237 | * @param array $args Args. 238 | * @param string $field Field. 239 | * @param int $post_id Post ID. 240 | * 241 | * @return array 242 | */ 243 | public function post_relationship_query( $args, $field, $post_id ) { 244 | // Order returned query collection by date, starting with most recent. 245 | $args['order'] = 'DESC'; 246 | $args['orderby'] = 'post_date'; 247 | 248 | return $args; 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/Managers/WordPressManager.php: -------------------------------------------------------------------------------- 1 | 401 ) 81 | ); 82 | } 83 | 84 | return $result; 85 | } 86 | ); 87 | } 88 | 89 | /** 90 | * Hide extranneous dashboard widgets 91 | * 92 | * @return void 93 | */ 94 | public function remove_dashboard_widgets() { 95 | global $wp_meta_boxes; 96 | 97 | unset( $wp_meta_boxes['dashboard']['side']['core']['dashboard_quick_press'] ); 98 | unset( $wp_meta_boxes['dashboard']['normal']['core']['dashboard_incoming_links'] ); 99 | unset( $wp_meta_boxes['dashboard']['normal']['core']['dashboard_right_now'] ); 100 | unset( $wp_meta_boxes['dashboard']['normal']['core']['dashboard_plugins'] ); 101 | unset( $wp_meta_boxes['dashboard']['normal']['core']['dashboard_recent_drafts'] ); 102 | unset( $wp_meta_boxes['dashboard']['normal']['core']['dashboard_recent_comments'] ); 103 | unset( $wp_meta_boxes['dashboard']['side']['core']['dashboard_primary'] ); 104 | unset( $wp_meta_boxes['dashboard']['side']['core']['dashboard_secondary'] ); 105 | unset( $wp_meta_boxes['dashboard']['normal']['core']['yoast_db_widget'] ); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Models/SkelaPost.php: -------------------------------------------------------------------------------- 1 | 1000 ) { 37 | $limit = 1000; 38 | } 39 | 40 | // Note the + symbol. See https://codex.wordpress.org/Class_Reference/WP_Query#Category_Parameters. 41 | if ( is_array( $slug ) ) { 42 | $slug = implode( '+', $slug ); 43 | } 44 | 45 | $params = array( 46 | 'posts_per_page' => (int) $limit, 47 | 'category_name' => $slug, 48 | 'post_type' => 'post', 49 | 'post_status' => 'publish', 50 | 'orderby' => 'date', 51 | 'order' => 'DESC', 52 | ); 53 | 54 | if ( is_array( $exclude ) && count( $exclude ) > 0 ) { 55 | $params['post__not_in'] = $exclude; 56 | } 57 | 58 | if ( (int) $paged > 0 ) { 59 | $params['paged'] = $paged; 60 | } 61 | 62 | return $this->query( $params ); 63 | } 64 | 65 | /** 66 | * Returns list of "Posts" between the specified date ranges. $end_datae defaults 67 | * to now if null. 68 | * 69 | * @param Carbon $start_date Start date. 70 | * @param Carbon $end_datae End date. 71 | * @param integer $paged Enable pagination. 72 | * 73 | * @return Repository 74 | */ 75 | public function articles_by_date_range( Carbon $start_date, Carbon $end_datae = null, $paged = 0 ) { 76 | if ( null === $end_datae ) { 77 | $end_datae = Carbon::now(); 78 | } 79 | 80 | $date_query = array( 81 | 'after' => array( 82 | 'year' => $start_date->year, 83 | 'month' => $start_date->month, 84 | 'day' => $start_date->day, 85 | ), 86 | 'before' => array( 87 | 'year' => $end_datae->year, 88 | 'month' => $end_datae->month, 89 | 'day' => $end_datae->day, 90 | ), 91 | 'inclusive' => true, 92 | ); 93 | 94 | $params = array( 95 | 'date_query' => $date_query, 96 | 'posts_per_page' => -1, 97 | 'post_type' => 'post', 98 | 'post_status' => 'publish', 99 | 'orderby' => 'date', 100 | 'order' => 'DESC', 101 | ); 102 | 103 | if ( (int) $paged > 0 ) { 104 | $params['paged'] = $paged; 105 | } 106 | 107 | return $this->query( $params ); 108 | } 109 | 110 | /** 111 | * Returns a chronological list of latest posts across all *public* post types. 112 | * This acts as a "firehose" of new content so to speak. 113 | * 114 | * @param integer $limit Number of posts to return. 115 | * @param array $post_types WordPress post types. 116 | * @param array $exclude IDs of posts to exclude. 117 | * @param integer $paged Enable pagination. 118 | * 119 | * @return Repository 120 | */ 121 | public function latest_posts( $limit = 10, $post_types = self::POST_TYPES, array $exclude = array(), $paged = 0 ) { 122 | 123 | // Set sane defaults so we don't do full table scans. 124 | if ( $limit <= 0 || $limit > 1000 ) { 125 | $limit = 1000; 126 | } 127 | 128 | $params = array( 129 | 'posts_per_page' => (int) $limit, 130 | 'post_type' => $post_types, 131 | 'post_status' => 'publish', 132 | 'orderby' => 'date', 133 | 'order' => 'DESC', 134 | ); 135 | 136 | if ( count( $exclude ) > 0 ) { 137 | $params['post__not_in'] = $exclude; 138 | } 139 | 140 | if ( (int) $paged > 0 ) { 141 | $params['paged'] = $paged; 142 | } 143 | 144 | return $this->query( $params ); 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/Repositories/Repository.php: -------------------------------------------------------------------------------- 1 | result_set; 30 | } 31 | 32 | /** 33 | * Returns the first item in a collection. 34 | * Returns null if there are 0 items in the collection. 35 | * 36 | * @return mixed 37 | */ 38 | public function first() { 39 | $local_array = $this->get(); 40 | return isset( $local_array[0] ) ? $local_array[0] : null; 41 | } 42 | 43 | /** 44 | * Returns a slice of the collection starting at the given index. 45 | * Similar to Laravel's slice(). 46 | * 47 | * @param int $start Start index. 48 | * 49 | * @return array 50 | */ 51 | public function slice( $start ) { 52 | $local_array = $this->get(); 53 | 54 | if ( count( $local_array ) < 1 ) { 55 | return array(); 56 | } 57 | 58 | if ( is_object( $local_array ) && $local_array instanceof PostCollection ) { 59 | $local_array = $local_array->getArrayCopy(); 60 | } 61 | 62 | return array_slice( $local_array, $start ); 63 | } 64 | 65 | /** 66 | * Shuffles (and slices) the result set. 67 | * 68 | * @param integer $and_slice Index to slice the array at (optional). 69 | * 70 | * @return array 71 | */ 72 | public function shuffle( $and_slice = 0 ) { 73 | $local_array = $this->get(); 74 | 75 | if ( count( $local_array ) < 1 ) { 76 | return array(); 77 | } 78 | 79 | if ( is_object( $local_array ) && $local_array instanceof PostCollection ) { 80 | $local_array = $local_array->getArrayCopy(); 81 | } 82 | 83 | shuffle( $local_array ); 84 | 85 | if ( $and_slice < 1 ) { 86 | return $local_array; 87 | } 88 | 89 | return array_slice( $local_array, 0, $and_slice ); 90 | } 91 | 92 | /** 93 | * Runs a query. 94 | * 95 | * @param array $params WP Query params. 96 | * @param string $post_class Post class to return. 97 | * 98 | * @return Repository 99 | */ 100 | protected function query( array $params, $post_class = '\Timber\Post' ) { 101 | // Clear old result sets. 102 | $this->reset(); 103 | 104 | $cache_key = __FUNCTION__ . md5( http_build_query( $params ) ); 105 | $cached_posts = wp_cache_get( $cache_key, __CLASS__, false, $found_in_cache ); 106 | 107 | if ( $found_in_cache && is_array( $cached_posts ) && count( $cached_posts ) > 0 ) { 108 | // Use cached results. 109 | return $this->result_set( $cached_posts ); 110 | } 111 | 112 | $posts = new PostQuery( $params, $post_class ); 113 | 114 | // Cache our results. 115 | if ( count( $posts ) > 0 ) { 116 | wp_cache_set( $cache_key, $posts, __CLASS__ ); 117 | } 118 | 119 | return $this->result_set( $posts ); 120 | } 121 | 122 | /** 123 | * Clears the current result set. 124 | * 125 | * @return Repository 126 | */ 127 | protected function reset() { 128 | $this->result_set = array(); 129 | return $this; 130 | } 131 | 132 | /** 133 | * Returns current result set 134 | * 135 | * @param array|PostCollection $result_set Result set. 136 | * 137 | * @return Repository 138 | */ 139 | protected function result_set( $result_set = array() ) { 140 | $this->result_set = $result_set; 141 | return $this; 142 | } 143 | 144 | } 145 | -------------------------------------------------------------------------------- /src/Services/WordPressService.php: -------------------------------------------------------------------------------- 1 | 403 ) 50 | ); 51 | // no break. 52 | case 404: 53 | global $wp_query; 54 | $wp_query->set_404(); 55 | status_header( 404 ); 56 | nocache_headers(); 57 | include get_query_template( '404' ); 58 | exit; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /static/img/skela.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstatement/skela-wp-theme/1fb41ec3564c3961e2bf126ea87b1e3a5e73413d/static/img/skela.png -------------------------------------------------------------------------------- /static/js/admin.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstatement/skela-wp-theme/1fb41ec3564c3961e2bf126ea87b1e3a5e73413d/static/js/admin.js -------------------------------------------------------------------------------- /static/js/app.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import LazySizes from 'lazysizes'; 3 | import Unveilhooks from 'lazysizes/plugins/unveilhooks/ls.unveilhooks'; 4 | /* eslint-enable */ 5 | 6 | import { onDocumentReady } from './utils'; 7 | 8 | // Components 9 | import Menu from './components/menu'; 10 | 11 | onDocumentReady(() => { 12 | new Menu(); 13 | 14 | // This is an example of how to do code splitting. The JS in this 15 | // referenced file will only be loaded on that page. Good for 16 | // when you have a large amount of JS only needed in one place 17 | // 18 | // if (document.querySelector('#js-process')) { 19 | // import(/* webpackChunkName: "process" */ './pages/process') 20 | // .then(module => { 21 | // const Process = module.default; 22 | // this.process = new Process(); 23 | // }); 24 | // } 25 | }); 26 | -------------------------------------------------------------------------------- /static/js/components/menu.js: -------------------------------------------------------------------------------- 1 | class Menu { 2 | constructor() { 3 | this.initialize(); 4 | } 5 | 6 | initialize() { 7 | // eslint-disable-next-line 8 | console.log('💀'); 9 | } 10 | } 11 | 12 | export default Menu; 13 | -------------------------------------------------------------------------------- /static/js/utils/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Event listener for when the document is ready. This serves as replacement for 3 | * JQuery's `$(document).ready()` function. 4 | * 5 | * @see http://youmightnotneedjquery.com/#ready 6 | * 7 | * @param {Function} callback the function to call when the DOM is ready 8 | */ 9 | export const onDocumentReady = callback => { 10 | if (document.readyState !== 'loading') { 11 | callback(); 12 | } else { 13 | document.addEventListener('DOMContentLoaded', callback, { once: true }); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /static/scss/admin.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Upstatement/skela-wp-theme/1fb41ec3564c3961e2bf126ea87b1e3a5e73413d/static/scss/admin.scss -------------------------------------------------------------------------------- /static/scss/app.scss: -------------------------------------------------------------------------------- 1 | @charset 'UTF-8'; 2 | 3 | // --------------------------------------------------------------------------- 4 | // BASE STYLES 5 | // --------------------------------------------------------------------------- 6 | 7 | @import 'base/variables'; 8 | @import 'base/reset'; 9 | @import 'base/global'; 10 | 11 | // --------------------------------------------------------------------------- 12 | // COMPONENTS 13 | // --------------------------------------------------------------------------- 14 | 15 | @import 'components/related-articles'; 16 | @import 'components/image-layout'; 17 | -------------------------------------------------------------------------------- /static/scss/base/_global.scss: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // HTML & Body 3 | // --------------------------------------------------------------------------- 4 | 5 | html { 6 | color: $c-black; 7 | font-size: $fs-base; 8 | line-height: 1em; 9 | font-family: $ff-body; 10 | -webkit-font-smoothing: antialiased; 11 | -moz-osx-font-smoothing: grayscale; 12 | text-size-adjust: 100%; 13 | height: 100%; 14 | box-sizing: border-box; 15 | } 16 | 17 | body { 18 | height: 100%; 19 | overflow-y: auto; 20 | overflow-x: hidden; 21 | box-sizing: border-box; 22 | position: relative; 23 | } 24 | 25 | // --------------------------------------------------------------------------- 26 | // Defaults 27 | // --------------------------------------------------------------------------- 28 | 29 | *, 30 | *:before, 31 | *:after { 32 | box-sizing: inherit; 33 | } 34 | 35 | img { 36 | width: 100%; 37 | height: auto; 38 | display: block; 39 | } 40 | 41 | a { 42 | text-decoration: none; 43 | } 44 | 45 | // --------------------------------------------------------------------------- 46 | // Text highlight 47 | // --------------------------------------------------------------------------- 48 | 49 | ::selection { 50 | background: $c-yellow; 51 | text-shadow: none; 52 | } 53 | 54 | // --------------------------------------------------------------------------- 55 | // Skip to content link 56 | // --------------------------------------------------------------------------- 57 | 58 | .skip-to-content { 59 | position: absolute; 60 | left: -999px; 61 | width: 1px; 62 | height: 1px; 63 | overflow: hidden; 64 | z-index: -99; 65 | text-decoration: none; 66 | color: $c-white; 67 | 68 | &:focus, 69 | &:active { 70 | background-color: $c-black; 71 | top: 0; 72 | left: 0; 73 | width: auto; 74 | height: auto; 75 | overflow: auto; 76 | padding: 16px 20px; 77 | z-index: 99; 78 | } 79 | } 80 | 81 | // --------------------------------------------------------------------------- 82 | // Lazysizes: https://github.com/aFarkas/lazysizes 83 | // --------------------------------------------------------------------------- 84 | 85 | .lazyload, 86 | .lazyloading { 87 | opacity: 0; 88 | } 89 | 90 | .lazyloaded { 91 | transition: opacity 0.3s; 92 | opacity: 1; 93 | } 94 | 95 | // For twig template debugging 96 | pre { 97 | font-family: monospace; 98 | } 99 | 100 | #content { 101 | padding: 50px; 102 | min-height: 100vh; 103 | 104 | &:focus { 105 | outline: 0; 106 | } 107 | } 108 | 109 | // --------------------------------------------------------------------------- 110 | // Front page demo styles 111 | // --------------------------------------------------------------------------- 112 | .page { 113 | max-width: 1000px; 114 | margin: 0 auto; 115 | 116 | h1 { 117 | font-size: 3em; 118 | text-align: center; 119 | } 120 | h2 { 121 | font-size: 2em; 122 | } 123 | 124 | img { 125 | max-width: 500px; 126 | margin: 0 auto; 127 | } 128 | 129 | ul { 130 | list-style: initial; 131 | padding-left: 30px; 132 | } 133 | } 134 | 135 | .footer { 136 | display: flex; 137 | justify-content: center; 138 | align-items: center; 139 | padding: 20px; 140 | 141 | .logo { 142 | color: $c-gray; 143 | width: 120px; 144 | height: auto; 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /static/scss/base/_reset.scss: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // Reset 3 | // --------------------------------------------------------------------------- 4 | 5 | html, 6 | body, 7 | div, 8 | span, 9 | applet, 10 | object, 11 | iframe, 12 | h1, 13 | h2, 14 | h3, 15 | h4, 16 | h5, 17 | h6, 18 | p, 19 | blockquote, 20 | pre, 21 | a, 22 | abbr, 23 | acronym, 24 | address, 25 | big, 26 | cite, 27 | code, 28 | del, 29 | dfn, 30 | em, 31 | img, 32 | ins, 33 | kbd, 34 | q, 35 | s, 36 | samp, 37 | small, 38 | strike, 39 | strong, 40 | tt, 41 | var, 42 | b, 43 | u, 44 | i, 45 | center, 46 | dl, 47 | dt, 48 | dd, 49 | ol, 50 | ul, 51 | li, 52 | fieldset, 53 | form, 54 | label, 55 | legend, 56 | table, 57 | caption, 58 | tbody, 59 | tfoot, 60 | thead, 61 | tr, 62 | th, 63 | td, 64 | article, 65 | aside, 66 | canvas, 67 | details, 68 | figcaption, 69 | figure, 70 | footer, 71 | header, 72 | hgroup, 73 | menu, 74 | nav, 75 | section, 76 | summary, 77 | time, 78 | mark, 79 | audio, 80 | video { 81 | margin: 0; 82 | padding: 0; 83 | border: 0; 84 | font-size: 100%; 85 | font: inherit; 86 | vertical-align: baseline; 87 | } 88 | 89 | article, 90 | aside, 91 | details, 92 | figcaption, 93 | figure, 94 | footer, 95 | header, 96 | hgroup, 97 | menu, 98 | nav, 99 | section, 100 | img { 101 | display: block; 102 | } 103 | 104 | body { 105 | line-height: 1; 106 | } 107 | 108 | strong, 109 | b { 110 | font-weight: bold; 111 | } 112 | 113 | em, 114 | i { 115 | font-style: italic; 116 | } 117 | 118 | ol, 119 | ul { 120 | list-style: none; 121 | } 122 | 123 | blockquote, 124 | q { 125 | quotes: none; 126 | } 127 | 128 | blockquote:before, 129 | blockquote:after, 130 | q:before, 131 | q:after { 132 | content: ''; 133 | content: none; 134 | } 135 | 136 | ins { 137 | text-decoration: none; 138 | } 139 | 140 | del { 141 | text-decoration: line-through; 142 | } 143 | 144 | table { 145 | border-collapse: collapse; 146 | border-spacing: 0; 147 | } 148 | -------------------------------------------------------------------------------- /static/scss/base/_variables.scss: -------------------------------------------------------------------------------- 1 | // --------------------------------------------------------------------------- 2 | // COLORS 3 | // --------------------------------------------------------------------------- 4 | 5 | $c-black: #000000; 6 | $c-white: #ffffff; 7 | $c-gray: #777777; 8 | $c-yellow: #ffff00; 9 | 10 | // --------------------------------------------------------------------------- 11 | // Colors to toggle for accessibility 12 | // --------------------------------------------------------------------------- 13 | 14 | :root { 15 | --primary: $c-black; 16 | } 17 | 18 | // --------------------------------------------------------------------------- 19 | // FONTS 20 | // --------------------------------------------------------------------------- 21 | 22 | $fs-base: 16px; 23 | 24 | $ff-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 25 | 'Noto Sans', sans-serif; 26 | 27 | // --------------------------------------------------------------------------- 28 | // BREAKPOINTS 29 | // --------------------------------------------------------------------------- 30 | 31 | $xxs: 380px; 32 | $xs: 450px; 33 | $sm: 600px; 34 | $md: 768px; 35 | $lg: 960px; 36 | $xl: 1200px; 37 | $xxl: 1400px; 38 | $xxxl: 1800px; 39 | 40 | // --------------------------------------------------------------------------- 41 | // Easing 42 | // --------------------------------------------------------------------------- 43 | 44 | $easeInSine: cubic-bezier(0.47, 0, 0.745, 0.715); 45 | $easeOutSine: cubic-bezier(0.39, 0.575, 0.565, 1); 46 | $easeInOutSine: cubic-bezier(0.39, 0.575, 0.565, 1); 47 | 48 | $easeInQuad: cubic-bezier(0.55, 0.085, 0.68, 0.53); 49 | $easeOutQuad: cubic-bezier(0.25, 0.46, 0.45, 0.94); 50 | $easeInOutQuad: cubic-bezier(0.25, 0.46, 0.45, 0.94); 51 | 52 | $easeInCubic: cubic-bezier(0.55, 0.055, 0.675, 0.19); 53 | $easeOutCubic: cubic-bezier(0.215, 0.61, 0.355, 1); 54 | $easeInOutCubic: cubic-bezier(0.215, 0.61, 0.355, 1); 55 | 56 | $easeInQuart: cubic-bezier(0.895, 0.03, 0.685, 0.22); 57 | $easeOutQuart: cubic-bezier(0.165, 0.84, 0.44, 1); 58 | $easeInOutQuart: cubic-bezier(0.165, 0.84, 0.44, 1); 59 | 60 | $easeInQuint: cubic-bezier(0.755, 0.05, 0.855, 0.06); 61 | $easeOutQuint: cubic-bezier(0.23, 1, 0.32, 1); 62 | $easeInOutQuint: cubic-bezier(0.23, 1, 0.32, 1); 63 | 64 | $easeInExpo: cubic-bezier(0.95, 0.05, 0.795, 0.035); 65 | $easeOutExpo: cubic-bezier(0.19, 1, 0.22, 1); 66 | $easeInOutExpo: cubic-bezier(0.19, 1, 0.22, 1); 67 | 68 | $easeInCirc: cubic-bezier(0.6, 0.04, 0.98, 0.335); 69 | $easeOutCirc: cubic-bezier(0.075, 0.82, 0.165, 1); 70 | $easeInOutCirc: cubic-bezier(0.075, 0.82, 0.165, 1); 71 | 72 | $easeInBack: cubic-bezier(0.6, -0.28, 0.735, 0.045); 73 | $easeOutBack: cubic-bezier(0.175, 0.885, 0.32, 1.275); 74 | $easeInOutBack: cubic-bezier(0.68, -0.55, 0.265, 1.55); 75 | 76 | $easeInOutFast: cubic-bezier(1, 0, 0, 1); 77 | -------------------------------------------------------------------------------- /static/scss/components/image-layout.scss: -------------------------------------------------------------------------------- 1 | .image-layout { 2 | display: grid; 3 | grid-column-gap: 10px; 4 | grid-row-gap: 10px; 5 | padding: 50px 0; 6 | max-width: 100%; 7 | 8 | &--2-symmetrical { 9 | grid-template-rows: 1fr; 10 | grid-template-columns: 1fr 1fr; 11 | } 12 | 13 | &--2-asymmetrical { 14 | grid-template-rows: 1fr; 15 | grid-template-columns: 1.5fr 1fr; 16 | } 17 | 18 | &--3-symmetrical { 19 | grid-template-rows: 1fr; 20 | grid-template-columns: 1fr 1fr 1fr; 21 | } 22 | 23 | &--3-asymmetrical { 24 | grid-template-rows: 1fr 1fr; 25 | grid-template-columns: 2.263fr 1fr; 26 | 27 | > :nth-child(1) { 28 | grid-row: span 2 / auto; 29 | } 30 | } 31 | } 32 | .image-layout__figure { 33 | position: relative; 34 | z-index: 1; 35 | } 36 | 37 | .image-layout__img { 38 | display: block; 39 | object-fit: cover; 40 | height: 100%; 41 | width: 100%; 42 | } 43 | 44 | .image-layout__caption { 45 | z-index: 1; 46 | position: absolute; 47 | bottom: 0; 48 | left: 0; 49 | background-color: black; 50 | color: white; 51 | padding: 8px; 52 | font: bold 11px / 1.375 arial; 53 | } 54 | -------------------------------------------------------------------------------- /static/scss/components/related-articles.scss: -------------------------------------------------------------------------------- 1 | .related-articles { 2 | margin: 2em 0; 3 | } 4 | 5 | .related-articles__head { 6 | font-size: 150%; 7 | margin-bottom: 1em; 8 | } 9 | 10 | .related-articles__list { 11 | list-style: circle; 12 | margin-left: 1em; 13 | } 14 | -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Theme Name: Skela 3 | * Description: Skela WordPress Theme 4 | * Author: Upstatement 5 | * Version: 0.0.0 6 | */ 7 | -------------------------------------------------------------------------------- /templates/components/acf-block.twig: -------------------------------------------------------------------------------- 1 |
2 |

{{ some_headline }}

3 |

{{ some_text }}

4 |
5 | -------------------------------------------------------------------------------- /templates/components/footer.twig: -------------------------------------------------------------------------------- 1 |
2 | 5 |
6 | -------------------------------------------------------------------------------- /templates/components/image-layout.twig: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | {% set imageCountToUse = '2' in layout ? 2 : 3 %} 5 | {% set imagesToUse = images[0 : imageCountToUse] %} 6 | {% set resizeWidth = 600 %} 7 | {% set resizeHeight = cropToSameRatio ? 400 : null %} 8 | 9 | {% for image in imagesToUse %} 10 |
11 | {{ image.alt }} 12 | {% if image.caption | length %} 13 |
14 | {{ image.caption }} 15 |
16 | {% endif %} 17 |
18 | {% endfor %} 19 |
20 | 21 | -------------------------------------------------------------------------------- /templates/components/related-articles.twig: -------------------------------------------------------------------------------- 1 | {% if relatedArticles is not empty %} 2 | 18 | {% endif %} 19 | -------------------------------------------------------------------------------- /templates/includes/head.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% if wp_title %}{{ wp_title }} | {{ site.name }}{% else %}{{ site.name }}{% endif %} 5 | 6 | 7 | {{ function('wp_head') }} 8 | 9 | -------------------------------------------------------------------------------- /templates/includes/svgs.twig: -------------------------------------------------------------------------------- 1 | {# 2 | ------------------------------------------------------------ 3 | All SVGs 4 | ------------------------------------------------------------ 5 | #} 6 | 7 | 9 | 10 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /templates/layouts/base.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {% include "includes/head.twig" %} 5 | 6 | 7 | 8 | Skip to content 9 | 10 | {% include "includes/svgs.twig" %} 11 | 12 |
13 | {% block pageBody %} 14 | {% endblock %} 15 |
16 | 17 | {% block footer %} 18 | {% include "components/footer.twig" %} 19 | {% endblock %} 20 | 21 | {{ function('wp_footer') }} 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /templates/pages/404.twig: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.twig" %} 2 | 3 | 4 | {% block pageBody %} 5 | Custom 404 Not Found 6 | {% endblock %} 7 | -------------------------------------------------------------------------------- /templates/pages/article.twig: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.twig" %} 2 | 3 | {% block pageBody %} 4 | 5 |

{{ article.title }}

6 | 7 | {{ article.content }} 8 | 9 | {% endblock %} 10 | 11 | -------------------------------------------------------------------------------- /templates/pages/front-page.twig: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.twig" %} 2 | 3 | {% block pageBody %} 4 |
5 | 6 | {# This is how you add a local image #} 7 | Skela banner 8 | 9 |


10 | 11 |

{{ site.name }}

12 | 13 |

14 | 15 |

Posts

16 | 17 |
18 | 19 |
    20 | {# Posts come from front-page.php #} 21 | {% for post in posts %} 22 |
  • {{ post.title }}
  • 23 | {% endfor %} 24 |
25 |
26 | 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /templates/pages/page.twig: -------------------------------------------------------------------------------- 1 | {% extends "layouts/base.twig" %} 2 | 3 | {% block pageBody %} 4 | 5 |

{{ page.title }}

6 | 7 | {{ page.content }} 8 | 9 | {% endblock %} 10 | 11 | -------------------------------------------------------------------------------- /tests/pa11y.js: -------------------------------------------------------------------------------- 1 | /* global require, process */ 2 | 3 | 'use strict'; 4 | 5 | const pa11y = require('pa11y'); 6 | const chalk = require('chalk'); 7 | const packageJson = require('../package.json'); 8 | const testingUrls = packageJson.testing.urls; 9 | 10 | // Initialize variables 11 | let url; 12 | let key; 13 | 14 | // Loop through all the URLs and set the test destination 15 | if (process.argv[2]) { 16 | for (key in testingUrls) { 17 | if (key === process.argv[2]) { 18 | // Set the testing URL 19 | if (packageJson.testing.urls[key] !== '') { 20 | url = packageJson.testing.urls[key]; 21 | } else { 22 | // If the URL object exists, but is empty 23 | console.log(chalk.red.bold(`✘ Error: Please add a URL for ${key}`)); 24 | console.log(''); 25 | process.exit(1); 26 | } 27 | } 28 | } // for() 29 | } else { 30 | url = packageJson.testing.urls.local; 31 | } 32 | 33 | // Set up the pa11y config options 34 | const config = { 35 | standard: packageJson.testing.accessibility.compliance, 36 | hideElements: '#wpadminbar', 37 | includeWarnings: true, 38 | rootElement: 'body', 39 | threshold: 2, 40 | timeout: 20000, 41 | userAgent: 'pa11y', 42 | width: 1280, 43 | ignore: ['notice'], 44 | log: { 45 | debug: console.log.bind(console), 46 | error: console.error.bind(console), 47 | info: console.log.bind(console), 48 | }, 49 | chromeLaunchConfig: { 50 | ignoreHTTPSErrors: true, 51 | }, 52 | }; 53 | 54 | /** 55 | * Run Accessibility Test 56 | * @param {string} url test URL 57 | * @param {object} config test configuration option 58 | * @param {Function} [cb] Callback 59 | * @returns {object} test results 60 | */ 61 | pa11y(url, config, (error, results) => { 62 | if (error) { 63 | return console.error(error); 64 | } else if (results.issues.length) { 65 | console.log(results); 66 | } else { 67 | console.log(chalk.green.bold('✔ All accessibility tests have passed.')); 68 | } 69 | }); 70 | -------------------------------------------------------------------------------- /tests/phpstan/bootstrap.php: -------------------------------------------------------------------------------- 1 | ({ 9 | mode: process.env.NODE_ENV === 'production' ? 'production' : 'development', 10 | 11 | entry: { 12 | app: ['./static/js/app.js', './static/scss/app.scss'], 13 | admin: ['./static/js/admin.js', './static/scss/admin.scss'], 14 | blocks: ['./src/Blocks/Blocks.js', './src/Blocks/editor.scss'], 15 | }, 16 | 17 | output: { 18 | filename: '[name].js', 19 | path: path.resolve(__dirname, 'dist'), 20 | publicPath: '/wp-content/themes/skela/dist/', 21 | }, 22 | 23 | module: { 24 | rules: [ 25 | { 26 | test: /\.js$/, 27 | exclude: /node_modules/, 28 | loader: 'babel-loader', 29 | }, 30 | { 31 | test: /\.scss$/, 32 | use: [ 33 | MiniCssExtractPlugin.loader, 34 | 'css-loader', 35 | 'postcss-loader', 36 | { 37 | loader: 'sass-loader', 38 | options: { 39 | implementation: require('sass'), 40 | }, 41 | }, 42 | ], 43 | }, 44 | { 45 | test: /\.modernizrrc\.js$/, 46 | loader: 'webpack-modernizr-loader', 47 | }, 48 | { 49 | test: /\.(png|svg|jpg|gif)$/, 50 | loader: 'file-loader', 51 | options: { 52 | name: '[name].[ext]', 53 | outputPath: 'img', 54 | }, 55 | }, 56 | { 57 | test: /\.(woff|woff2|eot|ttf|otf)$/, 58 | loader: 'file-loader', 59 | options: { 60 | name: '[name].[ext]', 61 | outputPath: 'fonts', 62 | }, 63 | }, 64 | ], 65 | }, 66 | 67 | optimization: { 68 | splitChunks: { 69 | cacheGroups: { 70 | commons: { 71 | test: /[\\/]node_modules[\\/]/, 72 | name: 'vendor', 73 | chunks: 'all', 74 | enforce: true, 75 | }, 76 | }, 77 | }, 78 | }, 79 | 80 | resolve: { 81 | alias: { 82 | modernizr$: path.resolve(__dirname, '.modernizrrc.js'), 83 | }, 84 | }, 85 | 86 | plugins: [ 87 | // https://www.npmjs.com/package/dotenv-webpack 88 | new Dotenv(), 89 | // https://www.npmjs.com/package/browser-sync-webpack-plugin 90 | new BrowserSyncPlugin({ 91 | host: 'localhost', 92 | port: 3000, 93 | proxy: env 94 | ? env.platform === 'ups-dock' 95 | ? 'http://skela.ups.dock' 96 | : 'http://localhost:8888/' 97 | : 'http://localhost:8888/', 98 | files: ['dist/**/*.+(css|js)', '*.php', 'templates/**/*.twig'], 99 | open: false, 100 | }), 101 | // https://www.npmjs.com/package/mini-css-extract-plugin 102 | new MiniCssExtractPlugin({ 103 | filename: '[name].css', 104 | chunkFilename: '[id].css', 105 | }), 106 | ], 107 | 108 | devtool: process.env.NODE_ENV === 'production' ? 'source-map' : 'inline-source-map', 109 | }); 110 | --------------------------------------------------------------------------------