├── .github └── CODEOWNERS ├── db-seed └── .gitignore ├── cms ├── web │ ├── cpresources │ │ └── .gitignore │ ├── assets │ │ └── site │ │ │ ├── vanilla-scoop.jpg │ │ │ ├── chocolate-scoop.jpg │ │ │ └── strawberry-scoop.png │ ├── img │ │ └── site │ │ │ ├── login-background-image.png │ │ │ └── nys-logo.svg │ ├── index.php │ ├── .htaccess │ └── web.config ├── config │ ├── project │ │ ├── fieldGroups │ │ │ ├── 1aeaf5aa-fe37-42b5-aef9-f4556b92c43a.yaml │ │ │ ├── 9265d2f0-4fa5-4187-8ee4-b6eb8808e295.yaml │ │ │ ├── 86e92ff9-5119-4b99-b222-f3279dcc7818.yaml │ │ │ └── 92425d88-9e0a-4213-b474-f553f37261c2.yaml │ │ ├── siteGroups │ │ │ └── f89601e9-4ba9-4a48-9e99-350aa9914912.yaml │ │ ├── graphql │ │ │ ├── graphql.yaml │ │ │ └── schemas │ │ │ │ └── 6005c2f9-5d85-4442-b712-22e070096ac8.yaml │ │ ├── users │ │ │ ├── users.yaml │ │ │ └── fieldLayouts │ │ │ │ └── 34d1d67d-a82b-4439-a563-3193f2964a51.yaml │ │ ├── sites │ │ │ └── default--5da841b1-ca0d-46ff-8bb1-04d6c889ac54.yaml │ │ ├── sections │ │ │ ├── pages--7c3fb4f3-2ba5-4834-9056-6712aa5c18f4.yaml │ │ │ ├── orders--1f2ba12a-f04e-47d9-af2b-3e876c5adbce.yaml │ │ │ └── flavors--10571998-751e-449a-98c5-d48adf32034b.yaml │ │ ├── fields │ │ │ ├── flavorCalories--2b2ed478-8e77-4c39-a69a-56ad864557d1.yaml │ │ │ ├── flavorPrice--3d7dba4f-8e70-4965-acec-4e70f830a016.yaml │ │ │ ├── scoops--c6fb33da-e3f0-4f50-a4c5-4cce12415e80.yaml │ │ │ ├── disableMatrixFacades--858b290d-45ae-4da8-8e51-2c03caefeee3.yaml │ │ │ ├── colorsSwatches--ade97714-e4c4-4c73-9322-2c6b398cef04.yaml │ │ │ └── flavorImage--682126f6-e053-4b1d-b707-82879cb43392.yaml │ │ ├── volumes │ │ │ └── site--5c642d7e-b16b-4836-9575-668d75d242e5.yaml │ │ ├── entryTypes │ │ │ ├── default--d2e0ba28-01c7-4060-8087-0c12e978341b.yaml │ │ │ ├── default--46862260-8c13-44f7-9a4e-a3cb6e1a22b3.yaml │ │ │ └── default--88ffed5d-fece-4914-9e1a-a275577840d3.yaml │ │ └── project.yaml │ ├── redactor │ │ ├── Simple.json │ │ └── Default.json │ ├── license.key │ ├── license.key.1 │ ├── htmlpurifier │ │ └── Default.json │ ├── app.console.php │ ├── db.php │ ├── routes.php │ ├── app.web.php │ ├── general.php │ └── app.php ├── templates │ └── index.twig ├── modules │ └── sitemodule │ │ ├── src │ │ ├── fields │ │ │ ├── FacadeInterface.php │ │ │ ├── FacadeTrait.php │ │ │ └── MatrixFacade.php │ │ ├── assetbundles │ │ │ └── sitemodule │ │ │ │ ├── dist │ │ │ │ ├── js │ │ │ │ │ └── SiteModule.js │ │ │ │ ├── css │ │ │ │ │ └── SiteModule.css │ │ │ │ └── img │ │ │ │ │ └── SiteModule-icon.svg │ │ │ │ └── SiteModuleAsset.php │ │ ├── translations │ │ │ └── en │ │ │ │ └── site-module.php │ │ ├── services │ │ │ └── Helper.php │ │ ├── variables │ │ │ └── SiteVariable.php │ │ ├── templates │ │ │ └── _components │ │ │ │ ├── fields │ │ │ │ └── MatrixFacade │ │ │ │ │ └── settings.html │ │ │ │ └── matrixfacades │ │ │ │ ├── scoops │ │ │ │ ├── tablecell.html │ │ │ │ ├── input-variables.html │ │ │ │ └── input.html │ │ │ │ └── colorswatches │ │ │ │ └── input.html │ │ ├── config.php │ │ ├── helpers │ │ │ └── Config.php │ │ ├── behaviors │ │ │ ├── CpVariableBehavior.php │ │ │ └── MatrixCriteriaBehavior.php │ │ └── SiteModule.php │ │ ├── CHANGELOG.md │ │ ├── .craftplugin │ │ ├── .gitignore │ │ ├── LICENSE.md │ │ └── README.md ├── craft ├── craft.bat ├── bootstrap.php ├── example.env └── composer.json ├── docker-config ├── redis │ └── Dockerfile ├── mysql │ └── Dockerfile ├── nginx │ ├── Dockerfile │ └── default.conf ├── php-prod-craft │ ├── composer_install.sh │ ├── run_queue.sh │ └── Dockerfile └── php-dev-craft │ └── Dockerfile ├── docs └── img │ ├── entry-scoops-matrix-facade.png │ ├── entry-scoops-matrix-field.png │ ├── entry-swatches-matrix-facade.png │ ├── entry-swatches-matrix-field.png │ └── user-disable-matrix-facades.png ├── Makefile ├── CHANGELOG.md ├── docker-compose.yml └── README.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @khalwat 2 | -------------------------------------------------------------------------------- /db-seed/.gitignore: -------------------------------------------------------------------------------- 1 | !.gitignore 2 | -------------------------------------------------------------------------------- /cms/web/cpresources/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /docker-config/redis/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM redis:5-alpine 2 | -------------------------------------------------------------------------------- /docker-config/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql/mysql-server:latest 2 | -------------------------------------------------------------------------------- /cms/config/project/fieldGroups/1aeaf5aa-fe37-42b5-aef9-f4556b92c43a.yaml: -------------------------------------------------------------------------------- 1 | name: User 2 | -------------------------------------------------------------------------------- /cms/config/project/fieldGroups/9265d2f0-4fa5-4187-8ee4-b6eb8808e295.yaml: -------------------------------------------------------------------------------- 1 | name: Pages 2 | -------------------------------------------------------------------------------- /cms/config/project/fieldGroups/86e92ff9-5119-4b99-b222-f3279dcc7818.yaml: -------------------------------------------------------------------------------- 1 | name: Flavors 2 | -------------------------------------------------------------------------------- /cms/config/project/fieldGroups/92425d88-9e0a-4213-b474-f553f37261c2.yaml: -------------------------------------------------------------------------------- 1 | name: Orders 2 | -------------------------------------------------------------------------------- /cms/config/project/siteGroups/f89601e9-4ba9-4a48-9e99-350aa9914912.yaml: -------------------------------------------------------------------------------- 1 | name: default 2 | -------------------------------------------------------------------------------- /cms/config/project/graphql/graphql.yaml: -------------------------------------------------------------------------------- 1 | publicToken: 2 | enabled: false 3 | expiryDate: null 4 | -------------------------------------------------------------------------------- /docker-config/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.19-alpine 2 | 3 | COPY ./default.conf /etc/nginx/conf.d/default.conf 4 | -------------------------------------------------------------------------------- /cms/config/project/graphql/schemas/6005c2f9-5d85-4442-b712-22e070096ac8.yaml: -------------------------------------------------------------------------------- 1 | isPublic: true 2 | name: 'Public Schema' 3 | -------------------------------------------------------------------------------- /cms/web/assets/site/vanilla-scoop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/matrixfacades/develop/cms/web/assets/site/vanilla-scoop.jpg -------------------------------------------------------------------------------- /cms/config/redactor/Simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "buttons": ["bold", "italic"], 3 | "toolbarFixed": true, 4 | "plugins": ["richvariables"] 5 | } 6 | -------------------------------------------------------------------------------- /cms/web/assets/site/chocolate-scoop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/matrixfacades/develop/cms/web/assets/site/chocolate-scoop.jpg -------------------------------------------------------------------------------- /docs/img/entry-scoops-matrix-facade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/matrixfacades/develop/docs/img/entry-scoops-matrix-facade.png -------------------------------------------------------------------------------- /docs/img/entry-scoops-matrix-field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/matrixfacades/develop/docs/img/entry-scoops-matrix-field.png -------------------------------------------------------------------------------- /cms/web/assets/site/strawberry-scoop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/matrixfacades/develop/cms/web/assets/site/strawberry-scoop.png -------------------------------------------------------------------------------- /docs/img/entry-swatches-matrix-facade.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/matrixfacades/develop/docs/img/entry-swatches-matrix-facade.png -------------------------------------------------------------------------------- /docs/img/entry-swatches-matrix-field.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/matrixfacades/develop/docs/img/entry-swatches-matrix-field.png -------------------------------------------------------------------------------- /docs/img/user-disable-matrix-facades.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/matrixfacades/develop/docs/img/user-disable-matrix-facades.png -------------------------------------------------------------------------------- /cms/web/img/site/login-background-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nystudio107/matrixfacades/develop/cms/web/img/site/login-background-image.png -------------------------------------------------------------------------------- /cms/config/project/users/users.yaml: -------------------------------------------------------------------------------- 1 | allowPublicRegistration: false 2 | defaultGroup: null 3 | photoSubpath: '' 4 | photoVolumeUid: null 5 | requireEmailVerification: true 6 | -------------------------------------------------------------------------------- /cms/config/project/sites/default--5da841b1-ca0d-46ff-8bb1-04d6c889ac54.yaml: -------------------------------------------------------------------------------- 1 | baseUrl: '@web/' 2 | enabled: true 3 | handle: default 4 | hasUrls: true 5 | language: en-US 6 | name: English 7 | primary: true 8 | siteGroup: f89601e9-4ba9-4a48-9e99-350aa9914912 # default 9 | sortOrder: 1 10 | -------------------------------------------------------------------------------- /cms/config/license.key: -------------------------------------------------------------------------------- 1 | A3S$GQM+SJC1+3APM!TGIA!FK1OW=1%9&YA7SMMX3JDCI52NCR 2 | MD5=Q3/OCM/#KJOX#5!PGI=##CHBR&I!$N$XGS+S=#KEH2EJF# 3 | CORIJI=B$QYX8Q$V*#RKO7TFPR*6+VFBT&Q+4G&MI+I^A4E3V# 4 | XM9L/HZH/VO5LEDBB=XL^$AFBT+B4Q6HRYE0V3==*8NCN/#IMM 5 | YGZ=SD8NJ^9MBVEXP&B=YIRQ5LDPLEA6C2$#^KRUPUHT^4X$J= 6 | -------------------------------------------------------------------------------- /cms/config/license.key.1: -------------------------------------------------------------------------------- 1 | &N=KGVRGUXUB%9W33#F02KG8PS4LOIQCICG+4++8VCQO#D9BHP 2 | PHIDJ2Q$$2$D5A0XNL41I1BZG4ZJ1^%E=BKT0/K03/WC6W#3GV 3 | 2L4SF*P^+JMYJ*E0MA$V09^H^3U!9I#OYRPQNV+3$NKG+M!JGH 4 | D#WL&YXSH49DIJYPB*WQBVT8JN8IH0V6B!R9XB5S26M9%/QIQ* 5 | Y#R*$H3QPH9TN&VFYWH$5T!WNHC=UERI39!ZM^W/4PN8^/BPHC 6 | -------------------------------------------------------------------------------- /cms/templates/index.twig: -------------------------------------------------------------------------------- 1 | {% set orders = craft.entries 2 | .section('orders') 3 | .matrixCriteria('scoops', { 4 | 'type': 'default', 5 | 'nuts': true 6 | }) 7 | .all() %} 8 | 9 | {% for order in orders %} 10 |

Order with nuts: {{ order.title }}

11 | {% endfor %} 12 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/fields/FacadeInterface.php: -------------------------------------------------------------------------------- 1 | run(); 13 | -------------------------------------------------------------------------------- /cms/config/htmlpurifier/Default.json: -------------------------------------------------------------------------------- 1 | { 2 | "Attr.AllowedFrameTargets": [ 3 | "_blank" 4 | ], 5 | "Attr.EnableID": true, 6 | "HTML.AllowedComments": [ 7 | "pagebreak" 8 | ], 9 | "HTML.SafeIframe": true, 10 | "URI.SafeIframeRegexp": "%^(https?:)?//(www.youtube.com/embed/|player.vimeo.com/video/)%" 11 | } 12 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Site Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ## 1.0.0 - 2018-01-19 8 | ### Added 9 | - Initial release 10 | -------------------------------------------------------------------------------- /cms/web/.htaccess: -------------------------------------------------------------------------------- 1 | 2 | RewriteEngine On 3 | 4 | # Send would-be 404 requests to Craft 5 | RewriteCond %{REQUEST_FILENAME} !-f 6 | RewriteCond %{REQUEST_FILENAME} !-d 7 | RewriteCond %{REQUEST_URI} !^/(favicon\.ico|apple-touch-icon.*\.png)$ [NC] 8 | RewriteRule (.+) index.php?p=$1 [QSA,L] 9 | 10 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/.craftplugin: -------------------------------------------------------------------------------- 1 | {"pluginName":"Site","pluginDescription":"An example module for Craft CMS 3 that lets you enhance your websites with a custom site module","pluginVersion":"1.0.0","pluginAuthorName":"nystudio107","pluginVendorName":"nystudio107","pluginAuthorUrl":"https://nystudio107.com/","codeComments":[],"pluginComponents":[],"apiVersion":"module_api_version_3_0"} -------------------------------------------------------------------------------- /cms/craft: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 14 | exit($exitCode); 15 | -------------------------------------------------------------------------------- /cms/config/redactor/Default.json: -------------------------------------------------------------------------------- 1 | { 2 | "buttons": [ 3 | "html", 4 | "formatting", 5 | "bold", 6 | "italic", 7 | "unorderedlist", 8 | "orderedlist", 9 | "link", 10 | "image", 11 | "video" 12 | ], 13 | "plugins": [ 14 | "fullscreen", 15 | "video", 16 | "richvariables" 17 | ], 18 | "linkNewTab": true, 19 | "toolbarFixed": true 20 | } 21 | -------------------------------------------------------------------------------- /cms/config/project/sections/pages--7c3fb4f3-2ba5-4834-9056-6712aa5c18f4.yaml: -------------------------------------------------------------------------------- 1 | defaultPlacement: end 2 | enableVersioning: true 3 | handle: pages 4 | name: Pages 5 | propagationMethod: all 6 | siteSettings: 7 | 5da841b1-ca0d-46ff-8bb1-04d6c889ac54: # English 8 | enabledByDefault: true 9 | hasUrls: true 10 | template: pages/_entry 11 | uriFormat: 'pages/{slug}' 12 | type: channel 13 | -------------------------------------------------------------------------------- /cms/craft.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | rem ------------------------------------------------------------- 4 | rem Craft command line bootstrap script for Windows 5 | rem ------------------------------------------------------------- 6 | 7 | @setlocal 8 | 9 | set CRAFT_PATH=%~dp0 10 | 11 | if "%PHP_COMMAND%" == "" set PHP_COMMAND=php.exe 12 | 13 | "%PHP_COMMAND%" "%CRAFT_PATH%craft" %* 14 | 15 | @endlocal 16 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/translations/en/site-module.php: -------------------------------------------------------------------------------- 1 | 'Site plugin loaded', 18 | ]; 19 | -------------------------------------------------------------------------------- /cms/config/project/users/fieldLayouts/34d1d67d-a82b-4439-a563-3193f2964a51.yaml: -------------------------------------------------------------------------------- 1 | tabs: 2 | - 3 | elements: 4 | - 5 | fieldUid: 858b290d-45ae-4da8-8e51-2c03caefeee3 # Disable Matrix Facades 6 | instructions: null 7 | label: null 8 | required: false 9 | tip: null 10 | type: craft\fieldlayoutelements\CustomField 11 | warning: null 12 | width: 100 13 | name: 'User Settings' 14 | sortOrder: 1 15 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/assetbundles/sitemodule/dist/css/SiteModule.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Site module for Craft CMS 3 | * 4 | * Site CSS 5 | * 6 | * @author nystudio107 7 | * @copyright Copyright (c) 2022 nystudio107 8 | * @link https://nystudio107.com/ 9 | * @package SiteModule 10 | * @since 1.0.0 11 | */ 12 | 13 | body.login { 14 | background-color: #FFF; 15 | background-image: url('/img/site/login-background-image.png'); 16 | } 17 | 18 | body.login label, body.login #forgot-password { 19 | background-color: #FFF; 20 | } 21 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/.gitignore: -------------------------------------------------------------------------------- 1 | # CRAFT ENVIRONMENT 2 | .env.php 3 | .env.sh 4 | .env 5 | 6 | # COMPOSER 7 | /vendor 8 | 9 | # BUILD FILES 10 | /bower_components/* 11 | /node_modules/* 12 | /build/* 13 | /yarn-error.log 14 | 15 | # MISC FILES 16 | .cache 17 | .DS_Store 18 | .idea 19 | .project 20 | .settings 21 | *.esproj 22 | *.sublime-workspace 23 | *.sublime-project 24 | *.tmproj 25 | *.tmproject 26 | .vscode/* 27 | !.vscode/settings.json 28 | !.vscode/tasks.json 29 | !.vscode/launch.json 30 | !.vscode/extensions.json 31 | config.codekit3 32 | prepros-6.config 33 | -------------------------------------------------------------------------------- /cms/config/project/fields/flavorCalories--2b2ed478-8e77-4c39-a69a-56ad864557d1.yaml: -------------------------------------------------------------------------------- 1 | columnSuffix: cihpxaws 2 | contentColumnType: integer(10) 3 | fieldGroup: 86e92ff9-5119-4b99-b222-f3279dcc7818 # Flavors 4 | handle: flavorCalories 5 | instructions: '' 6 | name: 'Flavor Calories' 7 | searchable: false 8 | settings: 9 | decimals: 0 10 | defaultValue: null 11 | max: null 12 | min: 0 13 | prefix: null 14 | previewCurrency: '' 15 | previewFormat: decimal 16 | size: null 17 | suffix: null 18 | translationKeyFormat: null 19 | translationMethod: none 20 | type: craft\fields\Number 21 | -------------------------------------------------------------------------------- /cms/config/project/fields/flavorPrice--3d7dba4f-8e70-4965-acec-4e70f830a016.yaml: -------------------------------------------------------------------------------- 1 | columnSuffix: rsfzwrwz 2 | contentColumnType: 'decimal(12,2)' 3 | fieldGroup: 86e92ff9-5119-4b99-b222-f3279dcc7818 # Flavors 4 | handle: flavorPrice 5 | instructions: '' 6 | name: 'Flavor Price' 7 | searchable: false 8 | settings: 9 | decimals: '2' 10 | defaultValue: null 11 | max: null 12 | min: 0 13 | prefix: null 14 | previewCurrency: '' 15 | previewFormat: decimal 16 | size: null 17 | suffix: null 18 | translationKeyFormat: null 19 | translationMethod: none 20 | type: craft\fields\Number 21 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/services/Helper.php: -------------------------------------------------------------------------------- 1 | safeLoad(); 16 | } 17 | 18 | // Define additional PHP constants 19 | // (see https://craftcms.com/docs/3.x/config/#php-constants) 20 | define('CRAFT_ENVIRONMENT', getenv('ENVIRONMENT') ?: 'production'); 21 | -------------------------------------------------------------------------------- /cms/config/project/sections/orders--1f2ba12a-f04e-47d9-af2b-3e876c5adbce.yaml: -------------------------------------------------------------------------------- 1 | defaultPlacement: end 2 | enableVersioning: true 3 | handle: orders 4 | name: Orders 5 | previewTargets: 6 | - 7 | __assoc__: 8 | - 9 | - label 10 | - 'Primary entry page' 11 | - 12 | - urlFormat 13 | - '{url}' 14 | - 15 | - refresh 16 | - '1' 17 | propagationMethod: all 18 | siteSettings: 19 | 5da841b1-ca0d-46ff-8bb1-04d6c889ac54: # English 20 | enabledByDefault: true 21 | hasUrls: true 22 | template: orders/_entry 23 | uriFormat: 'orders/{slug}' 24 | type: channel 25 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/config.php: -------------------------------------------------------------------------------- 1 | App::env('DB_DSN') ?: null, 15 | 'driver' => 'mysql', 16 | 'server' => 'mysql', 17 | 'port' => 3306, 18 | 'database' => App::env('DB_DATABASE'), 19 | 'user' => App::env('DB_USER'), 20 | 'password' => App::env('DB_PASSWORD'), 21 | 'schema' => App::env('DB_SCHEMA'), 22 | 'tablePrefix' => App::env('DB_TABLE_PREFIX'), 23 | ]; 24 | -------------------------------------------------------------------------------- /cms/config/project/fields/colorsSwatches--ade97714-e4c4-4c73-9322-2c6b398cef04.yaml: -------------------------------------------------------------------------------- 1 | columnSuffix: null 2 | contentColumnType: string 3 | fieldGroup: 9265d2f0-4fa5-4187-8ee4-b6eb8808e295 # Pages 4 | handle: colorsSwatches 5 | instructions: 'Pick the color combination to use for the page theme' 6 | name: 'Colors Swatches' 7 | searchable: false 8 | settings: 9 | contentTable: '{{%matrixcontent_colorsswatches}}' 10 | inputTemplatePath: site-module/_components/matrixfacades/colorswatches/input 11 | maxBlocks: '' 12 | minBlocks: '' 13 | propagationKeyFormat: null 14 | propagationMethod: all 15 | translationKeyFormat: null 16 | translationMethod: site 17 | type: modules\sitemodule\fields\MatrixFacade 18 | -------------------------------------------------------------------------------- /cms/example.env: -------------------------------------------------------------------------------- 1 | # Craft general settings 2 | ALLOW_UPDATES=1 3 | ALLOW_ADMIN_CHANGES=1 4 | BACKUP_ON_UPDATE=0 5 | DEV_MODE=1 6 | ENABLE_TEMPLATE_CACHING=0 7 | ENVIRONMENT=dev 8 | RUN_QUEUE_AUTOMATICALLY=0 9 | SECURITY_KEY=EOdiVBONceb8zFGJP7InMui2pMkvNACz 10 | SITE_NAME=matrixfacades 11 | 12 | # Craft database settings 13 | DB_USER=project 14 | DB_PASSWORD=project 15 | DB_DATABASE=project 16 | DB_SCHEMA=public 17 | DB_TABLE_PREFIX= 18 | 19 | # URL & path settings 20 | SITE_URL=http://localhost:8888 21 | WEB_ROOT_PATH=/var/www/project/cms/web 22 | 23 | # Redis settings 24 | REDIS_HOSTNAME=redis 25 | REDIS_PORT=6379 26 | REDIS_DEFAULT_DB=0 27 | REDIS_CRAFT_DB=1 28 | 29 | # Xdebug Settings 30 | DBGP_IDEKEY=phpstorm 31 | -------------------------------------------------------------------------------- /cms/config/routes.php: -------------------------------------------------------------------------------- 1 | ' => ['template' => 'blog/_archive'], 15 | * 16 | * That example would match URIs such as `/blog/archive/2012`, and pass the 17 | * request along to the `blog/_archive` template, providing it a `year` variable 18 | * set to the value `2012`. 19 | */ 20 | 21 | return [ 22 | 'amp' => ['template' => 'amp-index'], 23 | 'api' => 'graphql/api', 24 | ]; 25 | -------------------------------------------------------------------------------- /cms/config/project/fields/flavorImage--682126f6-e053-4b1d-b707-82879cb43392.yaml: -------------------------------------------------------------------------------- 1 | columnSuffix: null 2 | contentColumnType: string 3 | fieldGroup: 86e92ff9-5119-4b99-b222-f3279dcc7818 # Flavors 4 | handle: flavorImage 5 | instructions: '' 6 | name: 'Flavor Image' 7 | searchable: false 8 | settings: 9 | allowSelfRelations: false 10 | allowUploads: true 11 | allowedKinds: null 12 | defaultUploadLocationSource: 'volume:5c642d7e-b16b-4836-9575-668d75d242e5' # Site 13 | defaultUploadLocationSubpath: '' 14 | limit: '' 15 | localizeRelations: false 16 | previewMode: full 17 | restrictFiles: '' 18 | selectionLabel: '' 19 | showSiteMenu: false 20 | showUnpermittedFiles: false 21 | showUnpermittedVolumes: false 22 | singleUploadLocationSource: 'volume:5c642d7e-b16b-4836-9575-668d75d242e5' # Site 23 | singleUploadLocationSubpath: '' 24 | source: null 25 | sources: '*' 26 | targetSiteId: null 27 | useSingleFolder: false 28 | validateRelatedElements: false 29 | viewMode: list 30 | translationKeyFormat: null 31 | translationMethod: site 32 | type: craft\fields\Assets 33 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 nystudio107 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Determine the docker compose API version to get the separator character 2 | VERSION?=$(shell docker-compose -v) 3 | ifneq (,$(findstring v2.,$(VERSION))) 4 | SEPARATOR:=- 5 | else 6 | SEPARATOR:=_ 7 | endif 8 | CONTAINER?=$(shell basename $(CURDIR))$(SEPARATOR)php$(SEPARATOR)1 9 | 10 | .PHONY: dev clean composer craft nuke ssh up 11 | 12 | dev: up 13 | clean: 14 | rm -f cms/composer.lock 15 | rm -rf cms/vendor/ 16 | composer: up 17 | docker exec -it $(CONTAINER) su-exec www-data composer \ 18 | $(filter-out $@,$(MAKECMDGOALS)) 19 | craft: up 20 | docker exec -it $(CONTAINER) su-exec www-data php craft \ 21 | $(filter-out $@,$(MAKECMDGOALS)) 22 | nuke: 23 | docker-compose down -v 24 | rm -f cms/composer.lock 25 | rm -rf cms/vendor/ 26 | docker-compose up --build --force-recreate 27 | ssh: up 28 | docker exec -it $(CONTAINER) su-exec www-data /bin/sh 29 | up: 30 | if [ ! "$$(docker ps -q -f name=$(CONTAINER))" ]; then \ 31 | cp -n cms/example.env cms/.env; \ 32 | docker-compose up; \ 33 | fi 34 | %: 35 | @: 36 | # ref: https://stackoverflow.com/questions/6273608/how-to-pass-argument-to-makefile-from-command-line 37 | -------------------------------------------------------------------------------- /cms/config/project/volumes/site--5c642d7e-b16b-4836-9575-668d75d242e5.yaml: -------------------------------------------------------------------------------- 1 | fieldLayouts: 2 | e73b4186-2fad-4c0f-8eae-bbb884932c58: 3 | tabs: 4 | - 5 | elements: 6 | - 7 | autocapitalize: true 8 | autocomplete: false 9 | autocorrect: true 10 | class: null 11 | disabled: false 12 | id: null 13 | instructions: null 14 | label: null 15 | max: null 16 | min: null 17 | name: null 18 | orientation: null 19 | placeholder: null 20 | readonly: false 21 | requirable: false 22 | size: null 23 | step: null 24 | tip: null 25 | title: null 26 | type: craft\fieldlayoutelements\AssetTitleField 27 | warning: null 28 | width: 100 29 | name: Content 30 | sortOrder: 1 31 | handle: site 32 | hasUrls: true 33 | name: Site 34 | settings: 35 | path: '@webroot/assets/site' 36 | sortOrder: 0 37 | titleTranslationKeyFormat: null 38 | titleTranslationMethod: site 39 | type: craft\volumes\Local 40 | url: '@web/assets/site' 41 | -------------------------------------------------------------------------------- /docker-config/php-prod-craft/composer_install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Composer Install shell script 4 | # 5 | # This shell script runs `composer install` if either the `composer.lock` file or 6 | # the `vendor/` directory is not present` 7 | # 8 | # @author nystudio107 9 | # @copyright Copyright (c) 2022 nystudio107 10 | # @link https://nystudio107.com/ 11 | # @license MIT 12 | 13 | # Ensure permissions on directories Craft needs to write to 14 | chown -R www-data:www-data /var/www/project/cms/storage 15 | chown -R www-data:www-data /var/www/project/cms/web/cpresources 16 | # Check for `composer.lock` & `vendor/autoload.php` 17 | cd /var/www/project/cms 18 | if [ ! -f "composer.lock" ] || [ ! -f "vendor/autoload.php" ]; then 19 | su-exec www-data composer install --verbose --no-progress --no-scripts --optimize-autoloader --no-interaction 20 | # Wait until the MySQL db container responds 21 | echo "### Waiting for MySQL database" 22 | until eval "mysql -h mysql -u $DB_USER -p$DB_PASSWORD $DB_DATABASE -e 'select 1' > /dev/null 2>&1" 23 | do 24 | sleep 1 25 | done 26 | # Run any pending migrations/project config changes 27 | su-exec www-data composer craft-update 28 | fi 29 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/assetbundles/sitemodule/SiteModuleAsset.php: -------------------------------------------------------------------------------- 1 | sourcePath = "@modules/sitemodule/assetbundles/sitemodule/dist"; 33 | 34 | $this->depends = [ 35 | CpAsset::class, 36 | ]; 37 | 38 | $this->js = [ 39 | 'js/SiteModule.js', 40 | ]; 41 | 42 | $this->css = [ 43 | 'css/SiteModule.css', 44 | ]; 45 | 46 | parent::init(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /cms/web/web.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /cms/config/app.web.php: -------------------------------------------------------------------------------- 1 | [ 22 | 'session' => static function() { 23 | // Get the default component config 24 | $config = App::sessionConfig(); 25 | // Override the class to use Redis' session class and our config settings 26 | $config['class'] = yii\redis\Session::class; 27 | $config['keyPrefix'] = App::env('APP_ID') ?: 'CraftCMS'; 28 | $config['redis'] = [ 29 | 'hostname' => App::env('REDIS_HOSTNAME'), 30 | 'port' => App::env('REDIS_PORT'), 31 | 'database' => App::env('REDIS_DEFAULT_DB'), 32 | ]; 33 | // Instantiate and return it 34 | return Craft::createObject($config); 35 | }, 36 | ], 37 | ]; 38 | -------------------------------------------------------------------------------- /docker-config/php-prod-craft/run_queue.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run Queue shell script 4 | # 5 | # This shell script runs the Craft CMS queue via `php craft queue/listen` 6 | # It waits until the database container responds, then runs any pending 7 | # migrations / project config changes via the `craft-update` Composer script, 8 | # then runs the queue listener that listens for and runs pending queue jobs 9 | # 10 | # @author nystudio107 11 | # @copyright Copyright (c) 2022 nystudio107 12 | # @link https://nystudio107.com/ 13 | # @license MIT 14 | 15 | cd /var/www/project/cms 16 | # Wait until the MySQL db container responds 17 | echo "### Waiting for MySQL database" 18 | until eval "mysql -h mysql -u $DB_USER -p$DB_PASSWORD $DB_DATABASE -e 'select 1' > /dev/null 2>&1" 19 | do 20 | sleep 1 21 | done 22 | # Wait until the `composer install` is done by looking for the `vendor/autoload.php` and `composer.lock` files 23 | echo "### Waiting for vendor/autoload.php" 24 | while [ ! -f "vendor/autoload.php" ] || [ ! -f "composer.lock" ] 25 | do 26 | sleep 1 27 | done 28 | # Ensure permissions on directories Craft needs to write to 29 | chown -R www-data:www-data /var/www/project/cms/storage 30 | chown -R www-data:www-data /var/www/project/cms/web/cpresources 31 | # Run any pending migrations/project config changes 32 | su-exec www-data composer craft-update 33 | # Run a queue listener 34 | su-exec www-data php craft queue/listen 10 35 | -------------------------------------------------------------------------------- /cms/config/project/entryTypes/default--d2e0ba28-01c7-4060-8087-0c12e978341b.yaml: -------------------------------------------------------------------------------- 1 | fieldLayouts: 2 | f9551994-37fd-4761-95f0-9e5083121608: 3 | tabs: 4 | - 5 | elements: 6 | - 7 | autocapitalize: true 8 | autocomplete: false 9 | autocorrect: true 10 | class: null 11 | disabled: false 12 | id: null 13 | instructions: null 14 | label: null 15 | max: null 16 | min: null 17 | name: null 18 | orientation: null 19 | placeholder: null 20 | readonly: false 21 | requirable: false 22 | size: null 23 | step: null 24 | tip: null 25 | title: null 26 | type: craft\fieldlayoutelements\EntryTitleField 27 | warning: null 28 | width: 100 29 | - 30 | fieldUid: c6fb33da-e3f0-4f50-a4c5-4cce12415e80 # Scoops 31 | instructions: null 32 | label: null 33 | required: false 34 | tip: null 35 | type: craft\fieldlayoutelements\CustomField 36 | warning: null 37 | width: 100 38 | name: Content 39 | sortOrder: 1 40 | handle: default 41 | hasTitleField: true 42 | name: Default 43 | section: 1f2ba12a-f04e-47d9-af2b-3e876c5adbce # Orders 44 | sortOrder: 1 45 | titleFormat: null 46 | titleTranslationKeyFormat: null 47 | titleTranslationMethod: site 48 | -------------------------------------------------------------------------------- /cms/config/project/entryTypes/default--46862260-8c13-44f7-9a4e-a3cb6e1a22b3.yaml: -------------------------------------------------------------------------------- 1 | fieldLayouts: 2 | 539f02dd-1bc6-43f1-ad1b-db44ad1bf0d2: 3 | tabs: 4 | - 5 | elements: 6 | - 7 | autocapitalize: true 8 | autocomplete: false 9 | autocorrect: true 10 | class: null 11 | disabled: false 12 | id: null 13 | instructions: null 14 | label: null 15 | max: null 16 | min: null 17 | name: null 18 | orientation: null 19 | placeholder: null 20 | readonly: false 21 | requirable: false 22 | size: null 23 | step: null 24 | tip: null 25 | title: null 26 | type: craft\fieldlayoutelements\EntryTitleField 27 | warning: null 28 | width: 100 29 | - 30 | fieldUid: ade97714-e4c4-4c73-9322-2c6b398cef04 # Colors Swatches 31 | instructions: null 32 | label: null 33 | required: false 34 | tip: null 35 | type: craft\fieldlayoutelements\CustomField 36 | warning: null 37 | width: 100 38 | name: Content 39 | sortOrder: 1 40 | handle: default 41 | hasTitleField: true 42 | name: Default 43 | section: 7c3fb4f3-2ba5-4834-9056-6712aa5c18f4 # Pages 44 | sortOrder: 1 45 | titleFormat: null 46 | titleTranslationKeyFormat: null 47 | titleTranslationMethod: site 48 | -------------------------------------------------------------------------------- /cms/config/general.php: -------------------------------------------------------------------------------- 1 | [ 16 | '@web' => App::env('SITE_URL'), 17 | '@webroot' => App::env('WEB_ROOT_PATH'), 18 | ], 19 | 'allowUpdates' => (bool)App::env('ALLOW_UPDATES'), 20 | 'allowAdminChanges' => (bool)App::env('ALLOW_ADMIN_CHANGES'), 21 | 'backupOnUpdate' => (bool)App::env('BACKUP_ON_UPDATE'), 22 | 'devMode' => (bool)App::env('DEV_MODE'), 23 | 'enableTemplateCaching' => (bool)App::env('ENABLE_TEMPLATE_CACHING'), 24 | 'resourceBasePath' => App::env('WEB_ROOT_PATH') . '/cpresources', 25 | 'runQueueAutomatically' => (bool)App::env('RUN_QUEUE_AUTOMATICALLY'), 26 | 'securityKey' => App::env('SECURITY_KEY'), 27 | // Craft config settings from constants 28 | 'cacheDuration' => false, 29 | 'defaultSearchTermOptions' => [ 30 | 'subLeft' => true, 31 | 'subRight' => true, 32 | ], 33 | 'defaultTokenDuration' => 'P2W', 34 | 'enableCsrfProtection' => true, 35 | 'generateTransformsBeforePageLoad' => true, 36 | 'maxCachedCloudImageSize' => 3000, 37 | 'maxUploadFileSize' => '100M', 38 | 'omitScriptNameInUrls' => true, 39 | 'useEmailAsUsername' => false, 40 | 'usePathInfo' => true, 41 | 'useProjectConfigFile' => true, 42 | ]; 43 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/README.md: -------------------------------------------------------------------------------- 1 | # Site module for Craft CMS 3.x 2 | 3 | An example module for Craft CMS 3 that lets you enhance your websites with a custom site module 4 | 5 | ## Requirements 6 | 7 | This module requires Craft CMS 3.0.0-RC1 or later. 8 | 9 | ## Installation 10 | 11 | To install the module, follow these instructions. 12 | 13 | First, you'll need to add the contents of the `app.php` file to your `config/app.php` (or just copy it there if it does not exist). This ensures that your module will get loaded for each request. The file might look something like this: 14 | ``` 15 | return [ 16 | 'modules' => [ 17 | 'site-module' => [ 18 | 'class' => \modules\sitemodule\SiteModule::class, 19 | ], 20 | ], 21 | 'bootstrap' => ['site-module'], 22 | ]; 23 | ``` 24 | You'll also need to make sure that you add the following to your project's `composer.json` file so that Composer can find your module: 25 | 26 | "autoload": { 27 | "psr-4": { 28 | "modules\\sitemodule\\": "modules/sitemodule/src/" 29 | } 30 | }, 31 | 32 | After you have added this, you will need to do: 33 | 34 | composer dump-autoload 35 | 36 | …from the project’s root directory, to rebuild the Composer autoload map. This will happen automatically any time you do a `composer install` or `composer update` as well. 37 | 38 | ## Site Overview 39 | 40 | -Insert text here- 41 | 42 | ## Using Site 43 | 44 | -Insert text here- 45 | 46 | ## Site Roadmap 47 | 48 | Some things to do, and ideas for potential features: 49 | 50 | * Release it 51 | 52 | Brought to you by [nystudio107](https://nystudio107.com/) 53 | -------------------------------------------------------------------------------- /cms/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nystudio107/matrixfacades", 3 | "description": "This is a project demonstrates how to improve authoring experience Matrix Façades.", 4 | "require": { 5 | "craftcms/cms": "^3.7.0", 6 | "nystudio107/craft-autocomplete": "^1.0.0", 7 | "vlucas/phpdotenv": "^5.4.0", 8 | "yiisoft/yii2-redis": "^2.0.6" 9 | }, 10 | "require-dev": { 11 | "yiisoft/yii2-shell": "^2.0.3" 12 | }, 13 | "autoload": { 14 | "psr-4": { 15 | "modules\\sitemodule\\": "modules/sitemodule/src/" 16 | } 17 | }, 18 | "config": { 19 | "allow-plugins": { 20 | "craftcms/plugin-installer": true, 21 | "yiisoft/yii2-composer": true 22 | }, 23 | "optimize-autoloader": true, 24 | "sort-packages": true, 25 | "platform": { 26 | "php": "7.2.5" 27 | } 28 | }, 29 | "scripts": { 30 | "craft-update": [ 31 | "@pre-craft-update", 32 | "@post-craft-update" 33 | ], 34 | "pre-craft-update": [ 35 | ], 36 | "post-craft-update": [ 37 | "Composer\\Config::disableProcessTimeout", 38 | "@php craft install/check && php craft clear-caches/all --interactive=0 || exit 0", 39 | "@php craft install/check && php craft migrate/all --interactive=0 || exit 0", 40 | "@php craft install/check && php craft project-config/apply --interactive=0 || exit 0" 41 | ], 42 | "post-root-package-install": [ 43 | "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" 44 | ], 45 | "post-create-project-cmd": [ 46 | "@php craft setup/welcome" 47 | ], 48 | "pre-update-cmd": "@pre-craft-update", 49 | "pre-install-cmd": "@pre-craft-update", 50 | "post-update-cmd": "@post-craft-update", 51 | "post-install-cmd": "@post-craft-update" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /docker-config/php-dev-craft/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nystudio107/php-dev-base:7.4-alpine 2 | 3 | # dependencies required for running "phpize" 4 | # these get automatically installed and removed by "docker-php-ext-*" (unless they're already installed) 5 | ENV PHPIZE_DEPS \ 6 | autoconf \ 7 | dpkg-dev \ 8 | dpkg \ 9 | file \ 10 | g++ \ 11 | gcc \ 12 | libc-dev \ 13 | make \ 14 | pkgconf \ 15 | re2c \ 16 | wget 17 | 18 | # Install packages 19 | RUN set -eux; \ 20 | # Packages needed only for build 21 | apk add --no-cache --virtual .build-deps \ 22 | $PHPIZE_DEPS \ 23 | && \ 24 | # Packages to install 25 | apk add --no-cache \ 26 | su-exec \ 27 | gifsicle \ 28 | jpegoptim \ 29 | libwebp-tools \ 30 | nano \ 31 | optipng \ 32 | mysql-client \ 33 | mariadb-connector-c \ 34 | postgresql-client \ 35 | postgresql-dev \ 36 | && \ 37 | # Install PHP extensions 38 | docker-php-ext-install \ 39 | pdo_mysql \ 40 | pdo_pgsql \ 41 | pgsql \ 42 | soap \ 43 | && \ 44 | # Install Composer 45 | curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin/ --filename=composer \ 46 | && \ 47 | # Remove the build deps 48 | apk del .build-deps \ 49 | && \ 50 | # Clean out directories that don't need to be part of the image 51 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 52 | 53 | WORKDIR /var/www/project 54 | 55 | RUN mkdir -p /var/www/project/cms/storage \ 56 | && \ 57 | mkdir -p /var/www/project/cms/web/cpresources \ 58 | && \ 59 | chown -R www-data:www-data /var/www/project 60 | 61 | WORKDIR /var/www/project/cms 62 | 63 | # Start php-fpm 64 | CMD php-fpm 65 | -------------------------------------------------------------------------------- /cms/config/app.php: -------------------------------------------------------------------------------- 1 | App::env('APP_ID') ?: 'CraftCMS', 25 | 'modules' => [ 26 | 'site-module' => [ 27 | 'class' => SiteModule::class, 28 | ], 29 | ], 30 | 'bootstrap' => ['site-module'], 31 | 'components' => [ 32 | 'cache' => [ 33 | 'class' => yii\redis\Cache::class, 34 | 'keyPrefix' => App::env('APP_ID') ?: 'CraftCMS', 35 | 'redis' => [ 36 | 'hostname' => App::env('REDIS_HOSTNAME'), 37 | 'port' => App::env('REDIS_PORT'), 38 | 'database' => App::env('REDIS_CRAFT_DB'), 39 | ], 40 | ], 41 | 'deprecator' => [ 42 | 'throwExceptions' => true, 43 | ], 44 | 'queue' => [ 45 | 'class' => craft\queue\Queue::class, 46 | 'ttr' => 10 * 60, 47 | ], 48 | 'redis' => [ 49 | 'class' => yii\redis\Connection::class, 50 | 'hostname' => App::env('REDIS_HOSTNAME'), 51 | 'port' => App::env('REDIS_PORT'), 52 | 'database' => App::env('REDIS_DEFAULT_DB'), 53 | ], 54 | ], 55 | ]; 56 | -------------------------------------------------------------------------------- /docker-config/nginx/default.conf: -------------------------------------------------------------------------------- 1 | # default Docker DNS server 2 | resolver 127.0.0.11; 3 | 4 | # If a cookie doesn't exist, it evaluates to an empty string, so if neither cookie exists, it'll match : 5 | # (empty string on either side of the :), but if either or both cookies are set, it won't match, and will hit the default rule 6 | map $cookie_XDEBUG_SESSION:$cookie_XDEBUG_PROFILE $my_fastcgi_pass { 7 | default php_xdebug; 8 | ':' php; 9 | } 10 | 11 | server { 12 | listen 80; 13 | listen [::]:80; 14 | 15 | server_name _; 16 | root /var/www/project/cms/web; 17 | index index.html index.htm index.php; 18 | charset utf-8; 19 | 20 | gzip_static on; 21 | 22 | ssi on; 23 | 24 | client_max_body_size 0; 25 | 26 | error_page 404 /index.php?$query_string; 27 | 28 | access_log off; 29 | error_log /dev/stdout info; 30 | 31 | location = /favicon.ico { access_log off; log_not_found off; } 32 | 33 | location / { 34 | try_files $uri/index.html $uri $uri/ /index.php?$query_string; 35 | } 36 | 37 | location ~ [^/]\.php(/|$) { 38 | try_files $uri $uri/ /index.php?$query_string; 39 | fastcgi_split_path_info ^(.+\.php)(/.+)$; 40 | fastcgi_pass $my_fastcgi_pass:9000; 41 | fastcgi_index index.php; 42 | include fastcgi_params; 43 | fastcgi_param PATH_INFO $fastcgi_path_info; 44 | fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; 45 | fastcgi_param DOCUMENT_ROOT $realpath_root; 46 | fastcgi_param HTTP_PROXY ""; 47 | 48 | add_header Last-Modified $date_gmt; 49 | add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0"; 50 | if_modified_since off; 51 | expires off; 52 | etag off; 53 | 54 | fastcgi_intercept_errors off; 55 | fastcgi_buffer_size 16k; 56 | fastcgi_buffers 4 16k; 57 | fastcgi_connect_timeout 300; 58 | fastcgi_send_timeout 300; 59 | fastcgi_read_timeout 300; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/assetbundles/sitemodule/dist/img/SiteModule-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | 14 | 17 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /cms/config/project/entryTypes/default--88ffed5d-fece-4914-9e1a-a275577840d3.yaml: -------------------------------------------------------------------------------- 1 | fieldLayouts: 2 | 0e78f5fb-bf35-4e56-9189-7aa945434937: 3 | tabs: 4 | - 5 | elements: 6 | - 7 | autocapitalize: true 8 | autocomplete: false 9 | autocorrect: true 10 | class: null 11 | disabled: false 12 | id: null 13 | instructions: null 14 | label: null 15 | max: null 16 | min: null 17 | name: null 18 | orientation: null 19 | placeholder: null 20 | readonly: false 21 | requirable: false 22 | size: null 23 | step: null 24 | tip: null 25 | title: null 26 | type: craft\fieldlayoutelements\EntryTitleField 27 | warning: null 28 | width: 100 29 | - 30 | fieldUid: 682126f6-e053-4b1d-b707-82879cb43392 # Flavor Image 31 | instructions: null 32 | label: null 33 | required: false 34 | tip: null 35 | type: craft\fieldlayoutelements\CustomField 36 | warning: null 37 | width: 100 38 | - 39 | fieldUid: 2b2ed478-8e77-4c39-a69a-56ad864557d1 # Flavor Calories 40 | instructions: null 41 | label: null 42 | required: false 43 | tip: null 44 | type: craft\fieldlayoutelements\CustomField 45 | warning: null 46 | width: 100 47 | - 48 | fieldUid: 3d7dba4f-8e70-4965-acec-4e70f830a016 # Flavor Price 49 | instructions: null 50 | label: null 51 | required: false 52 | tip: null 53 | type: craft\fieldlayoutelements\CustomField 54 | warning: null 55 | width: 100 56 | name: Content 57 | sortOrder: 1 58 | handle: default 59 | hasTitleField: true 60 | name: Default 61 | section: 10571998-751e-449a-98c5-d48adf32034b # Flavors 62 | sortOrder: 1 63 | titleFormat: null 64 | titleTranslationKeyFormat: null 65 | titleTranslationMethod: site 66 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # nystudio107/matrixfacades Change Log 2 | 3 | ## 1.0.11 - 2022.05.03 4 | ### Changed 5 | * Use `docker-compose` to test the version, rather than the newer `docker compose`, to be backwards compatible 6 | 7 | ## 1.0.10 - 2022.05.03 8 | ### Changed 9 | * Determine the separator character by checking the Docker Compose API version at runtime 10 | 11 | ## 1.0.9 - 2022.05.02 12 | ### Changed 13 | * Use `su-exec` for the `craft`, `composer`, and `ssh` commands from the Makefile 14 | 15 | ## 1.0.8 - 2022.05.02 16 | ### Fixed 17 | * Clean up initial check for `composer.lock` or `vendor/autoload.php` to ensure the first-time install is done 18 | 19 | ## 1.0.7 - 2022.04.22 20 | ### Fixed 21 | * Ensure that the [mysql/mysql-server](https://hub.docker.com/r/mysql/mysql-server/tags) image can pick from the ARM64 architecture by using `latest` tag 22 | 23 | ## 1.0.6 - 2022.03.26 24 | ### Added 25 | * Added more logging to indicate when a container is waiting for another service to start up, and when seeding a database is complete 26 | * Run `composer craft-update` after a `composer install` is done via `composer_install.sh` 27 | 28 | ### Changed 29 | * Moved permissions setting to Docker image creation 30 | 31 | ## 1.0.5 - 2022.03.26 32 | ### Changed 33 | * Updated to Craft CMS 3.7.37 34 | 35 | ## 1.0.4 - 2022.03.26 36 | ### Added 37 | * Dramatically sped up the startup time for the PHP containers by moving the permissions setting script to run asynchronously in the queue container via the `run_queue.sh` script 38 | 39 | ## 1.0.3 - 2022.03.17 40 | 41 | ### Added 42 | * Significantly increased startup times via a `composer_install.sh` script that only runs `composer install` at container startup time if `composer.lock` or `vendor/` is missing 43 | * Run migrations / project config changes via the `run_queue.sh` script, only after the db container responds 44 | 45 | ## 1.0.2 - 2022.02.11 46 | 47 | ### Changed 48 | 49 | * Minor tweaks to the config so it can run with other local dev environments still running 50 | 51 | ## 1.0.1 - 2022.01.22 52 | 53 | ### Added 54 | 55 | * Added a new "Color Swatches" Matrix Façade and the "Landing Pages" channel to show off how it works 56 | 57 | ## 1.0.0 - 2022.01.19 58 | 59 | ### Added 60 | 61 | * Initial release 62 | 63 | Brought to you by [nystudio107](https://nystudio107.com/) 64 | -------------------------------------------------------------------------------- /cms/config/project/project.yaml: -------------------------------------------------------------------------------- 1 | dateModified: 1648326190 2 | email: 3 | fromEmail: andrew@nystudio107.com 4 | fromName: Craft 5 | transportType: craft\mail\transportadapters\Sendmail 6 | meta: 7 | __names__: 8 | 1aeaf5aa-fe37-42b5-aef9-f4556b92c43a: User # User 9 | 1f2ba12a-f04e-47d9-af2b-3e876c5adbce: Orders # Orders 10 | 1f716763-7f9e-4ffd-9f8c-2c0e7ccf15f7: Selected # Selected 11 | 2b2ed478-8e77-4c39-a69a-56ad864557d1: 'Flavor Calories' # Flavor Calories 12 | 3d7dba4f-8e70-4965-acec-4e70f830a016: 'Flavor Price' # Flavor Price 13 | 5c642d7e-b16b-4836-9575-668d75d242e5: Site # Site 14 | 5da841b1-ca0d-46ff-8bb1-04d6c889ac54: English # English 15 | 7a591e66-18df-490d-92b5-3fd3a3986f09: default # default 16 | 7c3fb4f3-2ba5-4834-9056-6712aa5c18f4: Pages # Pages 17 | 8f804a45-ad1d-4a71-b0b4-6df2040dc205: Nuts # Nuts 18 | 071aeb2b-e8eb-4bcf-825c-dd016745f107: Flavor # Flavor 19 | 86e92ff9-5119-4b99-b222-f3279dcc7818: Flavors # Flavors 20 | 88ffed5d-fece-4914-9e1a-a275577840d3: Default # Default 21 | 858b290d-45ae-4da8-8e51-2c03caefeee3: 'Disable Matrix Facades' # Disable Matrix Facades 22 | 4828c563-32cf-4021-afeb-1d5e38f31daf: 'Accent Color' # Accent Color 23 | 6005c2f9-5d85-4442-b712-22e070096ac8: 'Public Schema' # Public Schema 24 | 6359fe58-db81-4ee6-94eb-e9f4e3c61da6: Notes # Notes 25 | 9265d2f0-4fa5-4187-8ee4-b6eb8808e295: Pages # Pages 26 | 92425d88-9e0a-4213-b474-f553f37261c2: Orders # Orders 27 | 682126f6-e053-4b1d-b707-82879cb43392: 'Flavor Image' # Flavor Image 28 | 10571998-751e-449a-98c5-d48adf32034b: Flavors # Flavors 29 | 46862260-8c13-44f7-9a4e-a3cb6e1a22b3: Default # Default 30 | ade97714-e4c4-4c73-9322-2c6b398cef04: 'Colors Swatches' # Colors Swatches 31 | c2e2b69d-127d-4afe-8032-0209247e70de: default # default 32 | c6fb33da-e3f0-4f50-a4c5-4cce12415e80: Scoops # Scoops 33 | d2e0ba28-01c7-4060-8087-0c12e978341b: Default # Default 34 | dd87a305-fb8e-4bc0-965b-8afadc876934: Syrup # Syrup 35 | e7500fc4-6a5e-4656-8947-27c47c0c9dd8: Whip # Whip 36 | f652db8c-edc2-4a66-8241-2fb0787110ee: 'Primary Color' # Primary Color 37 | f89601e9-4ba9-4a48-9e99-350aa9914912: default # default 38 | system: 39 | edition: solo 40 | live: true 41 | name: matrixfacades 42 | retryDuration: 60 43 | schemaVersion: 3.7.33 44 | timeZone: America/New_York 45 | -------------------------------------------------------------------------------- /docker-config/php-prod-craft/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nystudio107/php-prod-base:7.4-alpine 2 | 3 | # dependencies required for running "phpize" 4 | # these get automatically installed and removed by "docker-php-ext-*" (unless they're already installed) 5 | ENV PHPIZE_DEPS \ 6 | autoconf \ 7 | dpkg-dev \ 8 | dpkg \ 9 | file \ 10 | g++ \ 11 | gcc \ 12 | libc-dev \ 13 | make \ 14 | pkgconf \ 15 | re2c \ 16 | wget 17 | 18 | # Install packages 19 | RUN set -eux; \ 20 | # Packages needed only for build 21 | apk add --no-cache --virtual .build-deps \ 22 | $PHPIZE_DEPS \ 23 | && \ 24 | # Packages to install 25 | apk add --no-cache \ 26 | su-exec \ 27 | gifsicle \ 28 | jpegoptim \ 29 | libwebp-tools \ 30 | nano \ 31 | optipng \ 32 | mysql-client \ 33 | mariadb-connector-c \ 34 | postgresql-client \ 35 | postgresql-dev \ 36 | && \ 37 | # Install PHP extensions 38 | docker-php-ext-install \ 39 | pdo_mysql \ 40 | pdo_pgsql \ 41 | pgsql \ 42 | soap \ 43 | && \ 44 | # Install Composer 45 | curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin/ --filename=composer \ 46 | && \ 47 | # Remove the build deps 48 | apk del .build-deps \ 49 | && \ 50 | # Clean out directories that don't need to be part of the image 51 | rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 52 | 53 | WORKDIR /var/www/project 54 | 55 | COPY ./run_queue.sh . 56 | RUN chmod a+x run_queue.sh \ 57 | && \ 58 | mkdir -p /var/www/project/cms/storage \ 59 | && \ 60 | mkdir -p /var/www/project/cms/web/cpresources \ 61 | && \ 62 | chown -R www-data:www-data /var/www/project 63 | COPY ./composer_install.sh . 64 | RUN chmod a+x composer_install.sh 65 | 66 | WORKDIR /var/www/project/cms 67 | 68 | # Run the composer_install.sh script that will do a `composer install`: 69 | # - If `composer.lock` is missing 70 | # - If `vendor/` is missing 71 | # ...then start up php-fpm. The `run_queue.sh` script in the queue container 72 | # will take care of running any pending migrations and apply any Project Config changes, 73 | # as well as set permissions via an async CLI process 74 | CMD /var/www/project/composer_install.sh \ 75 | && \ 76 | php-fpm 77 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/helpers/Config.php: -------------------------------------------------------------------------------- 1 | getConfig()->env; 44 | 45 | // Try craft/config first 46 | $path = Craft::getAlias('@config/'.$fileName, false); 47 | if ($path === false || !file_exists($path)) { 48 | // Now try our own internal config 49 | $path = Craft::getAlias('@modules/sitemodule/'.$fileName, false); 50 | if ($path === false || !file_exists($path)) { 51 | return []; 52 | } 53 | } 54 | 55 | if (!is_array($config = @include $path)) { 56 | return []; 57 | } 58 | 59 | // If it's not a multi-environment config, return the whole thing 60 | if (!array_key_exists('*', $config)) { 61 | return $config; 62 | } 63 | 64 | // If no environment was specified, just look in the '*' array 65 | if ($currentEnv === null) { 66 | return $config['*']; 67 | } 68 | 69 | $mergedConfig = []; 70 | foreach ($config as $env => $envConfig) { 71 | if ($env === '*' || StringHelper::contains($currentEnv, $env)) { 72 | $mergedConfig = ArrayHelper::merge($mergedConfig, $envConfig); 73 | } 74 | } 75 | 76 | return $mergedConfig; 77 | } 78 | 79 | // Private Methods 80 | // ========================================================================= 81 | } 82 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/templates/_components/matrixfacades/scoops/tablecell.html: -------------------------------------------------------------------------------- 1 | {# Variables passed in: 2 | # 3 | # @var string name the namespaced name of the field being rendered 4 | # @var boolean static whether this is a static or editable table 5 | # @var array col the column that the tablecell is in 6 | # @var mixed value the value of the table cell being rendered 7 | # 8 | # Also any parent variables from the parent editableTable Craft include will be present 9 | #} 10 | {% import '_includes/forms.html' as forms %} 11 | 12 | {%- switch col.type -%} 13 | {%- case 'flavorPicker' -%} 14 | {{ hiddenInput("#{name}[sortOrder][]", value.blockId) }} 15 | {% namespace "#{name}[blocks][#{value.blockId}]" %} 16 | {{ hiddenInput('type', value.blockType) }} 17 | {{ hiddenInput('enabled', 1) }} 18 | {% endnamespace %} 19 | {% set elementSelectConfig = { 20 | id: 'flavor', 21 | name: 'flavor', 22 | fieldClass: 'last', 23 | elements: [], 24 | elementType: value.elementType, 25 | selectionLabel: 'Select Scoop'|t('site-module'), 26 | sources: value.elementSources, 27 | limit: 1, 28 | } %} 29 | {% namespace "#{name}[blocks][#{value.blockId}][fields]" %} 30 | {% if value.flavor %} 31 | {% set elementSelectConfig = elementSelectConfig | merge({ 32 | elements: [craft.entries().id(value.flavor.id).one()], 33 | }) %} 34 | {% set flavorImage = value.flavor.flavorImage.one() ?? null %} 35 | {% if flavorImage %} 36 | 37 | {% endif %} 38 | {% endif %} 39 |
40 | {{ forms.elementSelectfield(elementSelectConfig) }} 41 |
42 | {% endnamespace %} 43 | {%- case 'text' -%} 44 | {{ value }} 45 | {%- case 'lightswitch' -%} 46 | {% namespace "#{name}[blocks][#{value.blockId}][fields]" %} 47 | {{ forms.lightswitch({ 48 | name: value.id, 49 | value: value.id, 50 | on: value.on, 51 | disabled: static, 52 | }) }} 53 | {% endnamespace %} 54 | {%- case 'editabletext' -%} 55 | {% namespace "#{name}[blocks][#{value.blockId}][fields]" %} 56 | {{ forms.text({ 57 | name: value.id, 58 | value: value.text, 59 | disabled: static, 60 | }) }} 61 | {% endnamespace %} 62 | {%- default -%} 63 | {%- endswitch -%} 64 | 65 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | # nginx - web server 5 | nginx: 6 | build: 7 | context: ./docker-config/nginx 8 | dockerfile: ./Dockerfile 9 | env_file: &env 10 | - ./cms/.env 11 | init: true 12 | ports: 13 | - "8888:80" 14 | volumes: 15 | - cpresources:/var/www/project/cms/web/cpresources:delegated 16 | - ./cms/web:/var/www/project/cms/web:cached 17 | # php - run php-fpm 18 | php: 19 | build: &php-build 20 | context: ./docker-config/php-prod-craft 21 | dockerfile: ./Dockerfile 22 | depends_on: 23 | - "mysql" 24 | - "redis" 25 | env_file: 26 | *env 27 | init: true 28 | tty: true 29 | volumes: &php-volumes 30 | - cpresources:/var/www/project/cms/web/cpresources:delegated 31 | - storage:/var/www/project/cms/storage:delegated 32 | - ./cms:/var/www/project/cms:cached 33 | # Specific directories that need to be bind-mounted 34 | - ./cms/storage/logs:/var/www/project/cms/storage/logs:delegated 35 | - ./cms/storage/runtime/compiled_templates:/var/www/project/cms/storage/runtime/compiled_templates:delegated 36 | - ./cms/storage/runtime/compiled_classes:/var/www/project/cms/storage/runtime/compiled_classes:delegated 37 | - ./cms/vendor:/var/www/project/cms/vendor:delegated 38 | - ./cms/storage/rebrand:/var/www/project/cms/storage/rebrand:delegated 39 | # php - run php-fpm with xdebug 40 | php_xdebug: 41 | build: 42 | context: ./docker-config/php-dev-craft 43 | dockerfile: ./Dockerfile 44 | depends_on: 45 | - "php" 46 | env_file: 47 | *env 48 | init: true 49 | tty: true 50 | volumes: 51 | *php-volumes 52 | # queue - runs queue jobs via php craft queue/listen 53 | queue: 54 | build: 55 | *php-build 56 | command: /var/www/project/run_queue.sh 57 | depends_on: 58 | - "php" 59 | env_file: 60 | *env 61 | init: true 62 | tty: true 63 | volumes: 64 | *php-volumes 65 | # mysql - database 66 | mysql: 67 | build: 68 | context: ./docker-config/mysql 69 | dockerfile: ./Dockerfile 70 | cap_add: 71 | - SYS_NICE # CAP_SYS_NICE 72 | env_file: 73 | *env 74 | environment: 75 | MYSQL_ROOT_PASSWORD: secret 76 | MYSQL_DATABASE: project 77 | MYSQL_USER: project 78 | MYSQL_PASSWORD: project 79 | init: true 80 | expose: 81 | - "3306" 82 | volumes: 83 | - db-data:/var/lib/mysql 84 | - ./db-seed:/docker-entrypoint-initdb.d 85 | # redis - key/value database for caching & php sessions 86 | redis: 87 | build: 88 | context: ./docker-config/redis 89 | dockerfile: ./Dockerfile 90 | expose: 91 | - "6379" 92 | init: true 93 | 94 | volumes: 95 | db-data: 96 | cpresources: 97 | storage: 98 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/templates/_components/matrixfacades/colorswatches/input.html: -------------------------------------------------------------------------------- 1 | {# Global variables available in all Twig blocks 2 | # 3 | # @var string id the normalized id of the Matrix field 4 | # @var string name handle of the Matrix field 5 | # @var \craft\elements\MatrixBlock[] blocks the MatrixBlock elements in the Matrix field 6 | # @var bool static whether the field is static or editable 7 | # @var \craft\base\ElementInterface element the parent element that contains the Matrix block 8 | # 9 | # The namespace will be set appropriately for the field 10 | #} 11 | 12 | 32 | 33 | {% set containerId = "#{name}-container" %} 34 |
35 | {% for block in blocks %} 36 | {% set swatchClass = "swatch-background" %} 37 | {% if block.selected %} 38 | {% set swatchClass = swatchClass ~ " swatch-background-selected" %} 39 | {% endif %} 40 | {{ hiddenInput("#{name}[sortOrder][]", block.id) }} 41 | {% namespace "#{name}[blocks][#{block.id}]" %} 42 | {{ hiddenInput('type', block.type) }} 43 | {{ hiddenInput('enabled', 1) }} 44 | {% endnamespace %} 45 |
46 | {% namespace "#{name}[blocks][#{block.id}][fields]" %} 47 | {{ hiddenInput('selected', block.selected) }} 48 | {% endnamespace %} 49 |
50 |
51 |
52 | {% endfor %} 53 |
54 | 55 | 77 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/behaviors/CpVariableBehavior.php: -------------------------------------------------------------------------------- 1 | getView()->getCpTemplateRoots(); 32 | foreach($roots as $handle => $paths) { 33 | foreach ($paths as $path) { 34 | $suggestions = array_merge($suggestions, $this->getTemplateSuggestions($handle, $path)); 35 | } 36 | } 37 | 38 | return [ 39 | [ 40 | 'label' => Craft::t('app', 'Templates'), 41 | 'data' => $suggestions, 42 | ], 43 | ]; 44 | } 45 | 46 | /** 47 | * Returns the available template path suggestions for passed in $root directory. 48 | * 49 | * @return string[] 50 | */ 51 | private function getTemplateSuggestions(string $handle, string $root): array 52 | { 53 | if (!is_dir($root)) { 54 | return []; 55 | } 56 | 57 | $directory = new \RecursiveDirectoryIterator($root); 58 | 59 | $filter = new \RecursiveCallbackFilterIterator($directory, function($current) { 60 | // Skip hidden files and directories, as well as node_modules/ folders 61 | if ($current->getFilename()[0] === '.' || $current->getFilename() === 'node_modules') { 62 | return false; 63 | } 64 | return true; 65 | }); 66 | 67 | $iterator = new \RecursiveIteratorIterator($filter); 68 | /** @var \SplFileInfo[] $files */ 69 | $files = []; 70 | $pathLengths = []; 71 | 72 | foreach ($iterator as $file) { 73 | /** @var \SplFileInfo $file */ 74 | if (!$file->isDir() && $file->getFilename()[0] !== '.') { 75 | $files[] = $file; 76 | $pathLengths[] = strlen($file->getRealPath()); 77 | } 78 | } 79 | 80 | array_multisort($pathLengths, SORT_NUMERIC, $files); 81 | 82 | // Now build the suggestions array 83 | $suggestions = []; 84 | $templates = []; 85 | $config = Craft::$app->getConfig()->getGeneral(); 86 | $rootLength = strlen($root); 87 | 88 | foreach ($files as $file) { 89 | $template = substr($file->getRealPath(), $rootLength + 1); 90 | 91 | // Can we chop off the extension? 92 | $extension = $file->getExtension(); 93 | if (in_array($extension, $config->defaultTemplateExtensions, true)) { 94 | $template = substr($template, 0, strlen($template) - (strlen($extension) + 1)); 95 | } 96 | $template = $handle . '/' . $template; 97 | 98 | $hint = $handle; 99 | 100 | // Avoid listing the same template path twice (considering localized templates) 101 | if (isset($templates[$template])) { 102 | continue; 103 | } 104 | 105 | $templates[$template] = true; 106 | $suggestions[] = [ 107 | 'name' => $template, 108 | 'hint' => $hint, 109 | ]; 110 | } 111 | 112 | ArrayHelper::multisort($suggestions, 'name'); 113 | 114 | return $suggestions; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/templates/_components/matrixfacades/scoops/input-variables.html: -------------------------------------------------------------------------------- 1 | {# Global variables available in all Twig blocks 2 | # 3 | # @var string id the normalized id of the Matrix field 4 | # @var string name handle of the Matrix field 5 | # @var \craft\elements\MatrixBlock[] blocks the MatrixBlock elements in the Matrix field 6 | # @var bool static whether the field is static or editable 7 | # @var \craft\base\ElementInterface element the parent element that contains the Matrix block 8 | # 9 | # The namespace will be set appropriately for the field 10 | #} 11 | 12 | {# Set the columns for our custom table #} 13 | {% set cols = { 14 | flavorPicker: { 15 | type: 'flavorPicker', 16 | heading: 'Scoops'|t('site-module') ~ " (#{ blocks|length })", 17 | class: 'leftalign', 18 | }, 19 | flavorCalories: { 20 | type: 'text', 21 | heading: 'Calories'|t('site-module'), 22 | class: 'leftalign', 23 | }, 24 | flavorPrice: { 25 | type: 'text', 26 | heading: 'Price'|t('site-module'), 27 | class: 'leftalign', 28 | }, 29 | scoopNuts: { 30 | type: 'lightswitch', 31 | heading: 'Nuts'|t('site-module'), 32 | }, 33 | orderNotes: { 34 | type: 'editabletext', 35 | heading: 'Notes'|t('site-module'), 36 | }, 37 | } %} 38 | 39 | {# Set up the elementType and ElementSources #} 40 | {% set elementType = 'craft\\elements\\Entry' %} 41 | {% set elementSources = craft.app.getElementIndexes().getSources(elementType, 'modal') %} 42 | {% set elementSources = elementSources|filter(v => v.criteria.sectionId is defined and v.criteria.sectionId == 7) %} 43 | {% set elementSources = elementSources|map(v => { label: v.label, value: v.key }) %} 44 | {% set newBlock = 'NEWBLOCK' %} 45 | {# Set the rows from the passed in Matric blocks #} 46 | {% set rows = [] %} 47 | {% for block in blocks %} 48 | {% set flavor = block.flavor.one() %} 49 | {% set newBlockName = newBlock ~ loop.index %} 50 | {% set row = { 51 | flavorPicker: { 52 | value: { 53 | flavor: flavor, 54 | blockId: block.id ?? newBlockName, 55 | blockType: block.type, 56 | elementType: elementType, 57 | elementSources: elementSources|first, 58 | }, 59 | hasErrors: block.hasErrors('flavor'), 60 | }, 61 | flavorCalories: { 62 | value: flavor.flavorCalories ?? '-', 63 | }, 64 | flavorPrice: { 65 | value: flavor.flavorPrice ?? '-', 66 | }, 67 | scoopNuts: { 68 | value: { 69 | id: 'nuts', 70 | on: block.nuts, 71 | blockId: block.id ?? newBlockName, 72 | } 73 | }, 74 | orderNotes: { 75 | value: { 76 | id: 'notes', 77 | text: block.notes ?? '', 78 | blockId: block.id ?? newBlockName, 79 | } 80 | }, 81 | } %} 82 | {% set rows = rows|merge([row]) %} 83 | {% endfor %} 84 | 85 | {# Default values that are used for newly added rows #} 86 | {% set defaultValues = { 87 | flavorPicker: { 88 | value: { 89 | flavor: null, 90 | blockId: newBlock, 91 | blockType: 'default', 92 | elementType: elementType, 93 | elementSources: elementSources|first, 94 | }, 95 | }, 96 | flavorCalories: { 97 | value: '-', 98 | }, 99 | flavorPrice: { 100 | value: '-', 101 | }, 102 | scoopNuts: { 103 | value: { 104 | id: 'nuts', 105 | on: false, 106 | blockId: newBlock, 107 | } 108 | }, 109 | orderNotes: { 110 | value: { 111 | id: 'notes', 112 | text: '', 113 | blockId: newBlock, 114 | } 115 | }, 116 | } %} 117 | 118 | {# Render the Matrix Facade's input html #} 119 | {% block input %} 120 | {% endblock input %} 121 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/templates/_components/matrixfacades/scoops/input.html: -------------------------------------------------------------------------------- 1 | {# Global variables available in all Twig blocks 2 | # 3 | # @var string id the normalized id of the Matrix field 4 | # @var string name handle of the Matrix field 5 | # @var \craft\elements\MatrixBlock[] blocks the MatrixBlock elements in the Matrix field 6 | # @var bool static whether the field is static or editable 7 | # @var \craft\base\ElementInterface element the parent element that contains the Matrix block 8 | # 9 | # The namespace will be set appropriately for the field 10 | #} 11 | {% extends 'site-module/_components/matrixfacades/scoops/input-variables' %} 12 | 13 | {# Render the Matrix Facade's input html #} 14 | {% block input %} 15 | 16 | {# Embed the Craft editableTable with our overrides #} 17 | {% embed '_includes/forms/editableTable' with { 18 | id: id, 19 | name: name, 20 | cols: cols, 21 | rows: rows, 22 | defaultValues: defaultValues, 23 | static: static, 24 | initJs: false, 25 | addRowLabel: 'Add a Scoop'|t('site-module'), 26 | } %} 27 | {# Override the tablecell block to handle rendering that ourselves #} 28 | {% block tablecell %} 29 | {% include 'site-module/_components/matrixfacades/scoops/tablecell' with { 30 | name: name, 31 | static: static, 32 | col: col, 33 | value: value, 34 | } %} 35 | {% endblock tablecell %} 36 | {% endembed %} 37 | 38 | {# Template used as a factory for newly added rows #} 39 | {% set templateId = "#{id}-row-template" %} 40 | {% set movable = false %} 41 | 61 | 62 | {# Initialize the JavaScript for the table #} 63 | {% set templateId = templateId|namespaceInputId %} 64 | {% set jsId = id|namespaceInputId|e('js') %} 65 | {% set jsName = name|namespaceInputName|e('js') %} 66 | {% set jsCols = cols|json_encode %} 67 | {% set defaultValues = defaultValues ?? null %} 68 | {% js %} 69 | window.flavorIncrement = 1; 70 | new Craft.EditableTable("{{ jsId }}", "{{ jsName }}", {{ jsCols|raw }}, { 71 | defaultValues: {{ defaultValues ? defaultValues|json_encode|raw : '{}' }}, 72 | onAddRow: function($tr) { 73 | var template = $('#{{ templateId }}').html().trim(); 74 | var id = 'new' + window.flavorIncrement++; 75 | template = template.replaceAll('NEWBLOCK', id); 76 | $tr.html(template); 77 | new Craft.BaseElementSelectInput({ 78 | id: `fields-scoops-blocks-${id}-fields-flavor`, 79 | name: `fields[scoops][blocks][${id}][fields][flavor]`, 80 | elementType: 'craft\\elements\\Entry', 81 | sources: {{ elementSources|first|json_encode|raw }}, 82 | criteria: null, 83 | allowSelfRelations: false, 84 | sourceElementId: null, 85 | disabledElementIds: null, 86 | viewMode: 'list', 87 | single: false, 88 | limit: 1, 89 | showSiteMenu: false, 90 | modalStorageKey: null, 91 | fieldId: `fields-scoops-blocks-${id}-fields-flavor-field`, 92 | sortable: true, 93 | prevalidate: false, 94 | modalSettings: {}, 95 | }); 96 | }, 97 | }); 98 | {% endjs %} 99 | {% endblock input %} 100 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/behaviors/MatrixCriteriaBehavior.php: -------------------------------------------------------------------------------- 1 | function($event) { 51 | $this->applyMatrixCriteriaParams($event); 52 | }, 53 | ]; 54 | } 55 | 56 | /** 57 | * Limit the ElementQuery to elements that match the passed in Matrix criteria 58 | * 59 | * @param string $matrixFieldHandle the handle of the Matrix field to match the criteria in 60 | * @param array $matrixCriteria the criteria for the MatrixBlock query 61 | * @return ElementQueryInterface 62 | */ 63 | public function matrixCriteria(string $matrixFieldHandle, array $matrixCriteria): ElementQueryInterface 64 | { 65 | $this->matrixFieldHandle = $matrixFieldHandle; 66 | $this->matrixCriteria = $matrixCriteria; 67 | /* @var ElementQueryInterface $elementQuery */ 68 | $elementQuery = $this->owner; 69 | 70 | return $elementQuery; 71 | } 72 | 73 | // Private Methods 74 | // ========================================================================= 75 | 76 | /** 77 | * Apply the 'matrixFieldHandle' & 'matrixCriteria' params to select the ids 78 | * of the elements that own matrix blocks that match, and then add them to the 79 | * id parameter of the ElementQuery 80 | * 81 | * @param CancelableEvent $event 82 | */ 83 | private function applyMatrixCriteriaParams(CancelableEvent $event): void 84 | { 85 | if (!$this->matrixFieldHandle || empty($this->matrixCriteria)) { 86 | return; 87 | } 88 | /* @var ElementQueryInterface $elementQuery */ 89 | $elementQuery = $this->owner; 90 | // Get the id of the matrix field from the handle 91 | $matrixField = Craft::$app->getFields()->getFieldByHandle($this->matrixFieldHandle); 92 | if ($matrixField === null) { 93 | return; 94 | } 95 | // Set up the matrix block query 96 | $matrixQuery = MatrixBlock::find(); 97 | // Mix in any criteria for the matrix block query 98 | Craft::configure($matrixQuery, $this->matrixCriteria); 99 | // Get the ids of the elements that contain matrix blocks that match the matrix block query 100 | $ownerIds = $matrixQuery 101 | ->fieldId($matrixField->id) 102 | ->select('matrixblocks.ownerId') 103 | ->orderBy(null) 104 | ->distinct() 105 | ->column(); 106 | // If the original query's `id` is not empty, use the intersection 107 | if (!empty($elementQuery->id)) { 108 | $originalIds = $elementQuery->id; 109 | if (!is_array($originalIds)) { 110 | $originalIds = [(int)$originalIds]; 111 | } 112 | $ownerIds = array_intersect($originalIds, $ownerIds); 113 | } 114 | // Ensure the parent query returns nothing if no ids were found 115 | if (empty($ownerIds)) { 116 | $ownerIds = null; 117 | $elementQuery->uid = self::NO_MATCHING_MATRIX_CRITERIA; 118 | } 119 | // Add them to the original query that was passed in 120 | $elementQuery->id($ownerIds); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/fields/MatrixFacade.php: -------------------------------------------------------------------------------- 1 | disableMatrixFacades()) { 49 | return parent::getStaticHtml($value, $element); 50 | } 51 | 52 | /** @var MatrixBlockQuery $value */ 53 | $value = $value->all(); 54 | 55 | /** @var MatrixBlock[] $value */ 56 | if (empty($value)) { 57 | return '

' . Craft::t('app', 'No blocks.') . '

'; 58 | } 59 | 60 | $id = StringHelper::randomString(); 61 | 62 | return Craft::$app->getView()->renderTemplate( 63 | $this->inputTemplatePath, 64 | [ 65 | 'id' => $id, 66 | 'name' => $id, 67 | 'blocks' => $value, 68 | 'static' => true, 69 | 'element' => $element, 70 | ]); 71 | } 72 | 73 | // Protected Methods 74 | // =================================================================================== 75 | 76 | /** 77 | * Should we disable facades, and display regular Matrix blocks? 78 | * 79 | * @return bool 80 | */ 81 | protected function disableMatrixFacades(): bool 82 | { 83 | // See if facades have been globally disabled 84 | if ($this->disableFacades()) { 85 | return true; 86 | } 87 | 88 | // Ensure there is a value specified in inputTemplatePath 89 | if (empty($this->inputTemplatePath)) { 90 | return true; 91 | } 92 | 93 | // Check permissions 94 | $currentUser = Craft::$app->getUser()->getIdentity(); 95 | if ($currentUser->disableMatrixFacades->contains('disableMatrixFacades')) { 96 | return true; 97 | } 98 | 99 | return false; 100 | } 101 | 102 | /** 103 | * @inheritdoc 104 | */ 105 | public function getSettingsHtml() 106 | { 107 | $matrixSettings = parent::getSettingsHtml(); 108 | 109 | // Render the settings template 110 | return Craft::$app->getView()->renderTemplate( 111 | 'site-module/_components/fields/MatrixFacade/settings', 112 | [ 113 | 'matrixField' => $this, 114 | ] 115 | ) . $matrixSettings; 116 | } 117 | 118 | /** 119 | * @inheritdoc 120 | */ 121 | protected function inputHtml($value, ElementInterface $element = null): string 122 | { 123 | // See if we should render as a Matrix field 124 | if ($this->disableMatrixFacades()) { 125 | return parent::inputHtml($value, $element); 126 | } 127 | 128 | if ($value instanceof MatrixBlockQuery) { 129 | $value = $value->getCachedResult() ?? $value->limit(null)->anyStatus()->all(); 130 | } 131 | 132 | return Craft::$app->getView()->renderTemplate( 133 | $this->inputTemplatePath, 134 | [ 135 | 'id' => Html::id($this->handle), 136 | 'name' => $this->handle, 137 | 'blocks' => $value, 138 | 'static' => false, 139 | 'element' => $element, 140 | ]); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /cms/web/img/site/nys-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | 14 | 20 | 21 | 23 | 25 | 26 | 28 | 32 | 34 | 36 | 37 | 38 | 40 | 46 | 47 | 49 | 50 | 51 | 55 | 57 | 59 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /cms/modules/sitemodule/src/SiteModule.php: -------------------------------------------------------------------------------- 1 | getBasePath()); 63 | $this->controllerNamespace = 'modules\sitemodule\controllers'; 64 | 65 | // Translation category 66 | $i18n = Craft::$app->getI18n(); 67 | /** @noinspection UnSafeIsSetOverArrayInspection */ 68 | if (!isset($i18n->translations[$id]) && !isset($i18n->translations[$id.'*'])) { 69 | $i18n->translations[$id] = [ 70 | 'class' => PhpMessageSource::class, 71 | 'sourceLanguage' => 'en-US', 72 | 'basePath' => '@modules/sitemodule/translations', 73 | 'forceTranslation' => true, 74 | 'allowOverrides' => true, 75 | ]; 76 | } 77 | 78 | // Base template directory 79 | Event::on(View::class, View::EVENT_REGISTER_CP_TEMPLATE_ROOTS, function (RegisterTemplateRootsEvent $e) { 80 | if (is_dir($baseDir = $this->getBasePath().DIRECTORY_SEPARATOR.'templates')) { 81 | $e->roots[$this->id] = $baseDir; 82 | } 83 | }); 84 | 85 | 86 | // Set this as the global instance of this module class 87 | static::setInstance($this); 88 | 89 | parent::__construct($id, $parent, $config); 90 | } 91 | 92 | /** 93 | * @inheritdoc 94 | */ 95 | public function init() 96 | { 97 | parent::init(); 98 | self::$instance = $this; 99 | 100 | // Register our components 101 | $this->setComponents([ 102 | 'helper' => [ 103 | 'class' => Helper::class, 104 | ] 105 | ]); 106 | 107 | // Add the CpVariableBehavior behavior to the Cp variable 108 | Event::on( 109 | CraftVariable::class, 110 | CraftVariable::EVENT_DEFINE_BEHAVIORS, 111 | static function(DefineBehaviorsEvent $event) { 112 | $cpVariable = $event->sender->cp; 113 | $cpVariable->attachBehaviors([ 114 | CpVariableBehavior::class, 115 | ]); 116 | } 117 | ); 118 | // Add the MatrixCriteriaBehavior behavior to the ElementQuery base class 119 | Event::on( 120 | ElementQuery::class, 121 | ElementQuery::EVENT_DEFINE_BEHAVIORS, 122 | static function(DefineBehaviorsEvent $event) { 123 | $event->sender->attachBehaviors([ 124 | MatrixCriteriaBehavior::class, 125 | ]); 126 | } 127 | ); 128 | Event::on( 129 | Fields::class, 130 | Fields::EVENT_REGISTER_FIELD_TYPES, 131 | static function(RegisterComponentTypesEvent $event) { 132 | $event->types[] = MatrixFacadeField::class; 133 | }); 134 | 135 | // Register our variables 136 | Event::on( 137 | CraftVariable::class, 138 | CraftVariable::EVENT_INIT, 139 | function (Event $event) { 140 | /** @var CraftVariable $variable */ 141 | $variable = $event->sender; 142 | $variable->set('site', SiteVariable::class); 143 | } 144 | ); 145 | 146 | // Register our Asset bundle for CP requests 147 | if (Craft::$app->getRequest()->getIsCpRequest()) { 148 | Event::on( 149 | View::class, 150 | View::EVENT_BEFORE_RENDER_TEMPLATE, 151 | function (TemplateEvent $event) { 152 | try { 153 | Craft::$app->getView()->registerAssetBundle(SiteModuleAsset::class); 154 | } catch (InvalidConfigException $e) { 155 | Craft::error( 156 | 'Error registering AssetBundle - '.$e->getMessage(), 157 | __METHOD__ 158 | ); 159 | } 160 | } 161 | ); 162 | } 163 | 164 | Craft::info( 165 | Craft::t( 166 | 'site-module', 167 | '{name} module loaded', 168 | ['name' => 'Site'] 169 | ), 170 | __METHOD__ 171 | ); 172 | } 173 | 174 | // Protected Methods 175 | // ========================================================================= 176 | } 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

nystudio107

2 | 3 | ## About nystudio107/matrixfacades 4 | 5 | This is a project demonstrates how to improve authoring experience Matrix Façades 6 | 7 | ## Using nystudio107/matrixfacades 8 | 9 | ### Initial setup 10 | 11 | You'll need [Docker desktop](https://www.docker.com/products/docker-desktop) for your platform installed to run this project 12 | 13 | To get started: 14 | 15 | 1. Clone the git repo with: 16 | 17 | ``` 18 | git clone https://github.com/nystudio107/matrixfacades.git 19 | ``` 20 | 21 | 2. Go into the project's directory: 22 | 23 | ``` 24 | cd matrixfacades 25 | ``` 26 | 27 | 3. Start up the site by typing this in the project's root directory: 28 | 29 | ``` 30 | make dev 31 | ``` 32 | 33 | If it appears to hang at `Building php_xdebug`, your PhpStorm or other IDE is likely waiting for an Xdebug connection; 34 | quit PhpStorm or stop it from listening for Xdebug during the initial build. 35 | 36 | 4. Once the site is up and running (see below), navigate to: 37 | 38 | ``` 39 | http://localhost:8888 40 | ``` 41 | 42 | The first time you do `make dev` it will be slow, because it has to build all the Docker images. 43 | 44 | Subsequent `make dev` commands will be much faster.. 45 | 46 | Wait until you see the following to indicate that the PHP container is ready: 47 | 48 | ``` 49 | php_1 | Craft is installed. 50 | php_1 | Applying changes from your project config files ... done 51 | php_1 | [01-Dec-2020 18:38:46] NOTICE: fpm is running, pid 22 52 | php_1 | [01-Dec-2020 18:38:46] NOTICE: ready to handle connections 53 | ``` 54 | 55 | ### Login 56 | 57 | The default login is: 58 | 59 | **URL:** `http://localhost:8888/admin` 60 | **User:** `admin` \ 61 | **Password:** `password` 62 | 63 | ### Matrix Façades in Action 64 | 65 | The project comes pre-populated with example content that demonstrates an example Matrix Façades. 66 | 67 | #### Color Swatches Matrix Façade 68 | 69 | Navigate to **Entries → Pages → Landing Page** and you'll see an example entry: 70 | 71 | ![Screenshot](./docs/img/entry-swatches-matrix-facade.png) 72 | 73 | While this looks like some kind of custom UX, it's actually a Matrix Façade field which subclasses Craft Matrix Block 74 | fields, and returns custom HTML for the user input. 75 | 76 | Imagine you have a Page builder where the designers or admins can modify the available color schemes, and this custom UX 77 | is presented to the content authors who can pick from the available choices. 78 | 79 | To see behind the façade, click on the User icon in the upper-right corner of the CP, and click on the **Admin** user. 80 | Then click on **User Settings**: 81 | 82 | ![Screenshot](./docs/img/user-disable-matrix-facades.png) 83 | 84 | Check the **Disable Matrix Façades** checkbox, and click on **Save**. 85 | 86 | Then navigate back to **Entries → Pages → Landing Page** and you'll see the example entry as it really is: 87 | 88 | ![Screenshot](./docs/img/entry-swatches-matrix-field.png) 89 | 90 | ...a series of Matrix blocks 🪄 91 | 92 | You can perform [Matrix Block Queries](https://craftcms.com/docs/3.x/matrix-blocks.html) on the data stored in them just 93 | as normal. 94 | 95 | You can even use the Matrix Criteria Behavior discussed in 96 | the [Searching Craft CMS Matrix Blocks](https://nystudio107.com/blog/searching-craft-cms-matrix-blocks) to find entries 97 | based on data stored in the Matrix Block fields. 98 | 99 | The Matrix Criteria Behavior comes bundled with this project as well, so you can do things like: 100 | 101 | ```twig 102 | {% set orders = craft.entries 103 | .section('pages') 104 | .matrixCriteria('colorsSwatches', { 105 | 'type': 'default', 106 | 'selected': true 107 | }) 108 | .all() 109 | %} 110 | ``` 111 | 112 | #### Scoops Matrix Façade 113 | 114 | Navigate to **Entries → Orders → Some order** and you'll see an example entry: 115 | 116 | ![Screenshot](./docs/img/entry-scoops-matrix-facade.png) 117 | 118 | While this looks like a table, it's actually a Matrix Façade field which subclasses Craft Matrix Block fields, and 119 | returns custom HTML for the user input. 120 | 121 | You can modify the table by adding or removing items, and upon saving the entry, Craft will take care of updating the 122 | Matrix Block data behind the scenes. 123 | 124 | To see behind the façade, click on the User icon in the upper-right corner of the CP, and click on the **Admin** user. 125 | Then click on **User Settings**: 126 | 127 | ![Screenshot](./docs/img/user-disable-matrix-facades.png) 128 | 129 | Check the **Disable Matrix Façades** checkbox, and click on **Save**. 130 | 131 | Then navigate back to **Entries → Orders → Some order** and you'll see the example entry as it really is: 132 | 133 | ![Screenshot](./docs/img/entry-scoops-matrix-field.png) 134 | 135 | ...a series of Matrix blocks 🪄 136 | 137 | You can perform [Matrix Block Queries](https://craftcms.com/docs/3.x/matrix-blocks.html) on the data stored in them just 138 | as normal. 139 | 140 | You can even use the Matrix Criteria Behavior discussed in 141 | the [Searching Craft CMS Matrix Blocks](https://nystudio107.com/blog/searching-craft-cms-matrix-blocks) to find entries 142 | based on data stored in the Matrix Block fields. 143 | 144 | The Matrix Criteria Behavior comes bundled with this project as well, so you can do things like: 145 | 146 | ```twig 147 | {% set orders = craft.entries 148 | .section('orders') 149 | .matrixCriteria('scoops', { 150 | 'type': 'default', 151 | 'nuts': true 152 | }) 153 | .all() 154 | %} 155 | ``` 156 | 157 | ### Makefile Project Commands 158 | 159 | This project uses Docker to shrink-wrap the devops it needs to run around the project. 160 | 161 | To make using it easier, we're using a Makefile and the built-in `make` utility to create local aliases. You can run the 162 | following from terminal in the project directory: 163 | 164 | - `make dev` - starts up the local dev server listening on `http://localhost:8888/` 165 | - `make clean` - removes the `cms/composer.lock` & the entire `cms/vendor/` directory 166 | - `make composer xxx` - runs the `composer` command passed in, e.g. `make composer install` in the php container 167 | - `make craft xxx` - runs the `craft` [console command](https://craftcms.com/docs/3.x/console-commands.html) passed in, 168 | e.g. `make craft project-config/apply` in the php container 169 | - `make nuke` - restarts the project from scratch by running `make clean` (above), then shuts down the Docker containers, removes any mounted volumes (including the database), and then rebuilds the containers from scratch 170 | 171 | **Tip:** If you try a command like `make craft project-config/apply --force` you’ll see an error, because the shell thinks the `--force` flag should be applied to the `make` command. To side-step this, use the `--` (double-dash) to disable further option processing, like this: `make -- craft project-config/apply --force` 172 | 173 | Brought to you by [nystudio107](https://nystudio107.com/) 174 | --------------------------------------------------------------------------------