├── translations ├── .gitignore ├── validators+intl-icu.zh-hans.yaml ├── validators+intl-icu.ja.yaml ├── validators+intl-icu.en.yaml ├── messages+intl-icu.zh-hans.yaml ├── messages+intl-icu.ja.yaml └── messages+intl-icu.en.yaml ├── src ├── Controller │ ├── .gitignore │ ├── StaffController.php │ └── FragmentsController.php ├── Query │ ├── CraftCMS │ │ ├── graphql │ │ │ ├── feeds │ │ │ │ ├── comments.graphql │ │ │ │ ├── events.graphql │ │ │ │ ├── news.graphql │ │ │ │ ├── press-releases.graphql │ │ │ │ └── blog.graphql │ │ │ ├── fragments │ │ │ │ ├── contentImage.graphql │ │ │ │ ├── thumbnailImage.graphql │ │ │ │ ├── breadcrumbs.graphql │ │ │ │ ├── listingEvent.graphql │ │ │ │ ├── listingExternalEvent.graphql │ │ │ │ ├── listingPageEvent.graphql │ │ │ │ ├── defaultFlexibleComponents.graphql │ │ │ │ ├── ecosystemsBottomFlexibleComponents.graphql │ │ │ │ ├── ecosystemsFlexibleComponents.graphql │ │ │ │ └── landingFlexibleComponents.graphql │ │ │ ├── blog │ │ │ │ ├── comment.graphql │ │ │ │ ├── create-comment.graphql │ │ │ │ ├── comments.graphql │ │ │ │ ├── filters.graphql │ │ │ │ ├── listing.graphql │ │ │ │ ├── collection.graphql │ │ │ │ └── entry.graphql │ │ │ ├── taxonomies │ │ │ │ ├── tags.graphql │ │ │ │ ├── categories.graphql │ │ │ │ ├── category-info.graphql │ │ │ │ └── group-info.graphql │ │ │ ├── newsletters │ │ │ │ ├── entry.graphql │ │ │ │ ├── filters.graphql │ │ │ │ ├── collection.graphql │ │ │ │ └── listing.graphql │ │ │ ├── news │ │ │ │ ├── filters.graphql │ │ │ │ ├── listing.graphql │ │ │ │ ├── collection.graphql │ │ │ │ └── entry.graphql │ │ │ ├── press-releases │ │ │ │ ├── filters.graphql │ │ │ │ ├── listing.graphql │ │ │ │ ├── collection.graphql │ │ │ │ └── entry.graphql │ │ │ ├── events │ │ │ │ ├── page.graphql │ │ │ │ ├── listing.graphql │ │ │ │ ├── filters.graphql │ │ │ │ └── entry.graphql │ │ │ ├── ecosystems │ │ │ │ ├── testimonials.graphql │ │ │ │ ├── ecosystem.graphql │ │ │ │ └── recent-activities.graphql │ │ │ ├── singles-breadcrumbs.graphql │ │ │ ├── staff │ │ │ │ └── alumni.graphql │ │ │ ├── home │ │ │ │ ├── recent-activities.graphql │ │ │ │ └── page.graphql │ │ │ ├── global-navigation.graphql │ │ │ └── page.graphql │ │ ├── Blog │ │ │ ├── Comment.php │ │ │ ├── Comments.php │ │ │ ├── CreateComment.php │ │ │ └── Listing.php │ │ ├── Feeds │ │ │ ├── Comments.php │ │ │ ├── News.php │ │ │ ├── PressReleases.php │ │ │ ├── Blog.php │ │ │ ├── Events.php │ │ │ └── Taxonomy.php │ │ ├── Taxonomies │ │ │ ├── Tags.php │ │ │ ├── Categories.php │ │ │ ├── GroupInfo.php │ │ │ └── CategoryInfo.php │ │ ├── Newsletters │ │ │ ├── Entry.php │ │ │ ├── Listing.php │ │ │ ├── Filters.php │ │ │ └── Collection.php │ │ ├── News │ │ │ ├── Listing.php │ │ │ └── Filters.php │ │ ├── Events │ │ │ └── Page.php │ │ ├── PressReleases │ │ │ ├── Listing.php │ │ │ └── Filters.php │ │ ├── Staff │ │ │ └── AlumniListing.php │ │ ├── Ecosystems │ │ │ ├── Testimonials.php │ │ │ └── Ecosystem.php │ │ └── Home │ │ │ └── RecentActivities.php │ └── W3C │ │ ├── Ecosystem │ │ ├── Bizdev.php │ │ ├── BizdevLead.php │ │ ├── Members.php │ │ ├── Groups.php │ │ └── Evangelists.php │ │ ├── Group.php │ │ ├── Healthcheck.php │ │ └── Home │ │ └── Members.php ├── Service │ ├── W3C.php │ ├── CraftCMS.php │ └── FeedHelper.php ├── Kernel.php └── Form │ └── CommentType.php ├── docs ├── front-end-integration │ ├── testing.md │ ├── README.md │ ├── matrix-flexible-components.md │ ├── routing.md │ └── templating.md ├── images │ ├── matrix-graphql.png │ ├── matrix-craft-cms.png │ ├── section-craft-cms.png │ ├── content-field-craft-cms.png │ ├── graphiql_execute_button.png │ └── matrix-graphql-explorer.png ├── README.md ├── routing.md ├── git_workflow.md ├── continuous_integration.md ├── testing.md ├── configuration.md ├── internationalization.md └── adding-languages.md ├── .env.dev ├── config ├── packages │ ├── lock.yaml │ ├── test │ │ ├── twig.yaml │ │ ├── validator.yaml │ │ ├── framework.yaml │ │ ├── web_profiler.yaml │ │ └── monolog.yaml │ ├── cache.yaml │ ├── dev │ │ ├── routing.yaml │ │ ├── web_profiler.yaml │ │ ├── debug.yaml │ │ ├── framework.yaml │ │ ├── assets.yaml │ │ └── monolog.yaml │ ├── mailer.yaml │ ├── strata.yaml │ ├── twig.yaml │ ├── assets.yaml │ ├── translation.yaml │ ├── exercise_html_purifier.yaml │ ├── validator.yaml │ ├── prod │ │ ├── deprecations.yaml │ │ └── monolog.yaml │ ├── routing.yaml │ ├── nelmio_cors.yaml │ ├── notifier.yaml │ ├── fos_http_cache.yaml │ ├── framework.yaml │ ├── security.yaml │ ├── nyholm_psr7.yaml │ └── chrisguitarguy_request_id.yaml ├── routes │ ├── dev │ │ ├── framework.yaml │ │ └── web_profiler.yaml │ └── annotations.yaml ├── preload.php ├── routes.yaml ├── bundles.php └── services.yaml ├── public ├── favicon.ico └── index.php ├── .phplint.yml ├── w3c.json ├── .ddev ├── ddev-docker-compose-communicate.yaml └── docker-compose.mounts.yaml ├── .env.prod ├── tests ├── symfony-container.php ├── bootstrap.php ├── urls │ └── test-urls.php └── Controller │ └── DefaultControllerTest.php ├── .env.test ├── templates ├── staff │ └── alumni.html.twig ├── news │ ├── show.html.twig │ └── index.html.twig ├── pages │ ├── landing.html.twig │ ├── default.html.twig │ └── home.html.twig ├── events │ ├── show.html.twig │ └── index.html.twig ├── bundles │ └── W3CWebsiteTemplatesBundle │ │ ├── base.html.twig │ │ └── components │ │ └── styles │ │ └── global_nav.html.twig ├── press-releases │ ├── show.html.twig │ └── index.html.twig ├── newsletters │ └── index.html.twig ├── ecosystems │ └── show.html.twig ├── blog │ ├── index.html.twig │ └── show.html.twig ├── rss_entry.html.twig ├── debug │ └── test.html.twig ├── partials │ ├── defaultFlexibleComponents.html.twig │ ├── landingFlexibleComponents.html.twig │ └── ecosystemsFlexibleComponents.html.twig └── strata-logo.svg ├── bin ├── phpunit └── console ├── phpcs.xml.dist ├── .env.local.dist ├── .gitignore ├── phpunit.xml.dist ├── rector.php ├── .github └── workflows │ └── php.yml └── .env /translations/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/Controller/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/front-end-integration/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | TODO 4 | -------------------------------------------------------------------------------- /.env.dev: -------------------------------------------------------------------------------- 1 | ASSETS_WEBSITE_2021=https://www.w3.org/assets/website-2021/ 2 | -------------------------------------------------------------------------------- /config/packages/lock.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | lock: '%env(LOCK_DSN)%' 3 | -------------------------------------------------------------------------------- /config/packages/test/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | strict_variables: true 3 | -------------------------------------------------------------------------------- /config/packages/cache.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | cache: 3 | app: cache.adapter.filesystem 4 | -------------------------------------------------------------------------------- /config/packages/dev/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | strict_requirements: null -------------------------------------------------------------------------------- /config/packages/mailer.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | mailer: 3 | dsn: '%env(MAILER_DSN)%' 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3c/w3c-website-frontend/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.phplint.yml: -------------------------------------------------------------------------------- 1 | path: ./ 2 | jobs: 10 3 | extensions: 4 | - php 5 | exclude: 6 | - vendor 7 | - var 8 | -------------------------------------------------------------------------------- /config/packages/test/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | not_compromised_password: false 4 | -------------------------------------------------------------------------------- /docs/images/matrix-graphql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3c/w3c-website-frontend/HEAD/docs/images/matrix-graphql.png -------------------------------------------------------------------------------- /w3c.json: -------------------------------------------------------------------------------- 1 | { 2 | "group": [122803] 3 | , "contacts": ["vivienlacourba"] 4 | , "repo-type": "tool" 5 | } 6 | -------------------------------------------------------------------------------- /docs/images/matrix-craft-cms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3c/w3c-website-frontend/HEAD/docs/images/matrix-craft-cms.png -------------------------------------------------------------------------------- /docs/images/section-craft-cms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3c/w3c-website-frontend/HEAD/docs/images/section-craft-cms.png -------------------------------------------------------------------------------- /config/packages/strata.yaml: -------------------------------------------------------------------------------- 1 | strata: 2 | tags: 3 | enabled: true 4 | preview_mode: 5 | data_provider: 'craft' -------------------------------------------------------------------------------- /docs/images/content-field-craft-cms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3c/w3c-website-frontend/HEAD/docs/images/content-field-craft-cms.png -------------------------------------------------------------------------------- /docs/images/graphiql_execute_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3c/w3c-website-frontend/HEAD/docs/images/graphiql_execute_button.png -------------------------------------------------------------------------------- /docs/images/matrix-graphql-explorer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/w3c/w3c-website-frontend/HEAD/docs/images/matrix-graphql-explorer.png -------------------------------------------------------------------------------- /config/routes/dev/framework.yaml: -------------------------------------------------------------------------------- 1 | _errors: 2 | resource: '@FrameworkBundle/Resources/config/routing/errors.xml' 3 | prefix: /_error 4 | -------------------------------------------------------------------------------- /.ddev/ddev-docker-compose-communicate.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | external_links: 4 | - "ddev-router:w3c-website-craft.ddev.site/api" -------------------------------------------------------------------------------- /config/packages/test/framework.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | test: true 3 | session: 4 | storage_factory_id: session.storage.factory.mock_file 5 | -------------------------------------------------------------------------------- /config/packages/twig.yaml: -------------------------------------------------------------------------------- 1 | twig: 2 | default_path: '%kernel.project_dir%/templates' 3 | globals: 4 | default_locale: '%env(APP_DEFAULT_LOCALE)%' 5 | -------------------------------------------------------------------------------- /.ddev/docker-compose.mounts.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | web: 3 | volumes: 4 | - "~/Sites/w3c/w3c-website-templates-bundle:/home/w3c-website-templates-bundle" 5 | -------------------------------------------------------------------------------- /config/packages/test/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: false 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: { collect: false } 7 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/feeds/comments.graphql: -------------------------------------------------------------------------------- 1 | query comments($owners: [QueryArgument]) { 2 | comments(ownerId: $owners) { 3 | ownerId 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/Service/W3C.php: -------------------------------------------------------------------------------- 1 | boot(); 9 | 10 | return $appKernel->getContainer(); 11 | -------------------------------------------------------------------------------- /config/packages/assets.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | assets: 3 | packages: 4 | website-2021: 5 | base_path: '%env(ASSETS_WEBSITE_2021)%' 6 | logos: 7 | base_path: '/assets/logos/' 8 | -------------------------------------------------------------------------------- /config/packages/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler: 2 | toolbar: true 3 | intercept_redirects: false 4 | 5 | framework: 6 | profiler: 7 | only_exceptions: false 8 | collect: '%env(bool:ENABLE_DEV_PROFILER)%' 9 | -------------------------------------------------------------------------------- /.env.test: -------------------------------------------------------------------------------- 1 | # define your env variables for the test env here 2 | KERNEL_CLASS='App\Kernel' 3 | APP_SECRET='$ecretf0rt3st' 4 | SYMFONY_DEPRECATIONS_HELPER=999999 5 | PANTHER_APP_ENV=panther 6 | PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots 7 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/taxonomies/categories.graphql: -------------------------------------------------------------------------------- 1 | query Categories($site: [String], $handle: [String]) { 2 | categories(site: $site, group: $handle) { 3 | id 4 | title 5 | uri 6 | slug 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /config/packages/translation.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | default_locale: '%env(APP_DEFAULT_LOCALE)%' 3 | translator: 4 | default_path: '%kernel.project_dir%/translations' 5 | fallbacks: 6 | - '%env(APP_DEFAULT_LOCALE)%' 7 | -------------------------------------------------------------------------------- /config/routes.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: 3 | path: ../src/Controller/ 4 | namespace: App\Controller 5 | type: attribute 6 | 7 | ekreative_health_check: 8 | resource: '@EkreativeHealthCheckBundle/Resources/config/routes.xml' 9 | -------------------------------------------------------------------------------- /config/packages/dev/debug.yaml: -------------------------------------------------------------------------------- 1 | debug: 2 | # Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser. 3 | # See the "server:dump" command to start a new server. 4 | dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%" 5 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/taxonomies/category-info.graphql: -------------------------------------------------------------------------------- 1 | query CategoryInfo($site: [String], $handle: [String], $slug: [String]) { 2 | category(site: $site, group: $handle, slug: $slug) { 3 | id 4 | title 5 | uri 6 | slug 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /config/routes/dev/web_profiler.yaml: -------------------------------------------------------------------------------- 1 | web_profiler_wdt: 2 | resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml' 3 | prefix: /_wdt 4 | 5 | web_profiler_profiler: 6 | resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml' 7 | prefix: /_profiler 8 | -------------------------------------------------------------------------------- /templates/staff/alumni.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/listing.html.twig' %} 2 | 3 | {% block list %} 4 | {% include '@W3CWebsiteTemplates/components/styles/alumni.html.twig' with { 'alumni': alumni } %} 5 | {% endblock %} 6 | 7 | {% block pagination %}{% endblock %} 8 | -------------------------------------------------------------------------------- /config/packages/dev/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | http_client: 4 | default_options: 5 | # Disable SSL verification on local dev only 6 | verify_host: false 7 | verify_peer: false -------------------------------------------------------------------------------- /templates/news/show.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/post.html.twig' %} 2 | 3 | {% set body_classes = 'post' %} 4 | 5 | {% block feed %} 6 | {% include '@W3CWebsiteTemplates/components/styles/feed.html.twig' with { 'feed_url': '/news/feed', 'feed_type': 'news' } %} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /templates/pages/landing.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/landing.html.twig' %} 2 | 3 | {% block components %} 4 | {% for component in page.landingFlexibleComponents %} 5 | {{ include('partials/landingFlexibleComponents.html.twig') }} 6 | {% endfor %} 7 | {% endblock %} 8 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/newsletters/entry.graphql: -------------------------------------------------------------------------------- 1 | query NewsletterEntry($date: [String], $site: [String]) { 2 | entry(postDate: $date, site: $site, section: "newsletter") { 3 | title 4 | ... on newsletter_default_Entry { 5 | fullDocumentContent 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /config/packages/exercise_html_purifier.yaml: -------------------------------------------------------------------------------- 1 | exercise_html_purifier: 2 | # full configuration reference: http://htmlpurifier.org/live/configdoc/plain.html 3 | html_profiles: 4 | custom: 5 | config: 6 | Core.Encoding: 'UTF-8' 7 | AutoFormat.AutoParagraph: true 8 | -------------------------------------------------------------------------------- /config/packages/validator.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | validation: 3 | email_validation_mode: html5 4 | 5 | # Enables validator auto-mapping support. 6 | # For instance, basic validation constraints will be inferred from Doctrine's metadata. 7 | #auto_mapping: 8 | # App\Entity\: [] 9 | -------------------------------------------------------------------------------- /config/packages/dev/assets.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | assets: 3 | packages: 4 | website-2021: 5 | base_urls: '%env(ASSETS_WEBSITE_2021)%' 6 | base_path: ~ 7 | logos: 8 | base_urls: 'https://www.w3.org/assets/logos/' 9 | base_path: ~ 10 | -------------------------------------------------------------------------------- /config/packages/prod/deprecations.yaml: -------------------------------------------------------------------------------- 1 | # As of Symfony 5.1, deprecations are logged in the dedicated "deprecation" channel when it exists 2 | #monolog: 3 | # channels: [deprecation] 4 | # handlers: 5 | # deprecation: 6 | # type: stream 7 | # channels: [deprecation] 8 | # path: php://stderr 9 | -------------------------------------------------------------------------------- /templates/events/show.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/event.html.twig' %} 2 | 3 | {% set body_classes = 'event' %} 4 | 5 | {% block components %} 6 | {% for component in page.defaultFlexibleComponents %} 7 | {{ include('partials/defaultFlexibleComponents.html.twig') }} 8 | {% endfor %} 9 | {% endblock %} -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/taxonomies/group-info.graphql: -------------------------------------------------------------------------------- 1 | query GroupInfo($site: [String], $slug: [String]) { 2 | category(site: $site, group: "groups", slug: $slug) { 3 | id 4 | title 5 | uri 6 | slug 7 | ... on groups_Category { 8 | type: groupType 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /translations/validators+intl-icu.zh-hans.yaml: -------------------------------------------------------------------------------- 1 | blog: 2 | comments: 3 | form: 4 | name: 5 | blank: 请输入姓名 6 | email: 7 | blank: 请输入电子邮箱地址 8 | format: 请输入有效的电子邮箱地址 9 | comment: 10 | blank: 请输入评论 11 | post: 12 | blank: 评论必须提及某一博客链接 13 | -------------------------------------------------------------------------------- /config/packages/routing.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | router: 3 | # Configure how to generate URLs in non-HTTP contexts, such as CLI commands. 4 | # See https://symfony.com/doc/current/routing.html#generating-urls-in-commands 5 | default_uri: '%env(APP_URL)%' 6 | 7 | when@prod: 8 | framework: 9 | router: 10 | strict_requirements: null 11 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/news/filters.graphql: -------------------------------------------------------------------------------- 1 | query NewsFilters($site: [String]) { 2 | first: entry(section: "newsArticles", site: $site, orderBy: "postDate asc") { 3 | year: postDate@formatDateTime(format: "Y") 4 | } 5 | last: entry(section: "newsArticles", site: $site, orderBy: "postDate desc") { 6 | year: postDate@formatDateTime(format: "Y") 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__) . '/.env'); 11 | } 12 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/newsletters/filters.graphql: -------------------------------------------------------------------------------- 1 | query NewsletterFilters($site: [String]) { 2 | first: entry(section: "newsletter", site: $site, orderBy: "postDate asc") { 3 | year: postDate@formatDateTime(format: "Y") 4 | } 5 | last: entry(section: "newsletter", site: $site, orderBy: "postDate desc") { 6 | year: postDate@formatDateTime(format: "Y") 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/press-releases/filters.graphql: -------------------------------------------------------------------------------- 1 | query PressReleasesFilters($site: [String]) { 2 | first: entry(section: "pressReleases", site: $site, orderBy: "postDate asc") { 3 | year: postDate@formatDateTime(format: "Y") 4 | } 5 | last: entry(section: "pressReleases", site: $site, orderBy: "postDate desc") { 6 | year: postDate@formatDateTime(format: "Y") 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /translations/validators+intl-icu.ja.yaml: -------------------------------------------------------------------------------- 1 | blog: 2 | comments: 3 | form: 4 | name: 5 | blank: 名前を入力してください 6 | email: 7 | blank: メールアドレスを入力してください 8 | format: 有効なメールアドレスを入力してください 9 | comment: 10 | blank: コメントを入力してください 11 | post: 12 | blank: コメントはブログ投稿に関連付ける必要があります 13 | -------------------------------------------------------------------------------- /config/packages/test/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [404, 405] 8 | channels: ["!event"] 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Project documentation 2 | 3 | Table of contents: 4 | 5 | * [Configuration](configuration.md) - Where to store application configuration and environment variables 6 | * [Routing](routing.md) 7 | * [Internationalization](internationalization.md) 8 | * [Git workflow](git_workflow.md) - How to name and use git branches 9 | * [Continuous integration](continuous_integration.md) 10 | * [Testing](testing.md) 11 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/blog/create-comment.graphql: -------------------------------------------------------------------------------- 1 | mutation NewComment($parentId: ID, $postId: ID, $siteId: ID, $name: String, $email: String, $comment: String) { 2 | saveComment(newParentId: $parentId, ownerId: $postId, siteId: $siteId, name: $name, email: $email, comment: $comment) { 3 | id 4 | ownerId 5 | name 6 | email 7 | comment 8 | commentDate 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /translations/validators+intl-icu.en.yaml: -------------------------------------------------------------------------------- 1 | blog: 2 | comments: 3 | form: 4 | name: 5 | blank: Enter a name 6 | email: 7 | blank: Enter an email address 8 | format: Enter a valid email address 9 | comment: 10 | blank: Enter a comment 11 | post: 12 | blank: Comment must refer to a blog post 13 | -------------------------------------------------------------------------------- /config/packages/nelmio_cors.yaml: -------------------------------------------------------------------------------- 1 | nelmio_cors: 2 | defaults: 3 | origin_regex: true 4 | allow_origin: ['%env(CORS_ALLOW_ORIGIN)%'] 5 | allow_methods: ['HEAD', 'GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE'] 6 | allow_headers: ['Content-Type', 'Authorization'] 7 | expose_headers: ['Link'] 8 | max_age: 3600 9 | allow_credentials: true 10 | paths: 11 | '^/': null 12 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/blog/comments.graphql: -------------------------------------------------------------------------------- 1 | query Comments($postId: [QueryArgument], $siteId: [QueryArgument]) { 2 | comments (ownerId: $postId, siteId: $siteId, orderBy: "commentDate ASC") { 3 | ...comment 4 | } 5 | } 6 | 7 | fragment comment on CommentInterface { 8 | id 9 | date: commentDate 10 | name 11 | email 12 | comment 13 | level 14 | parent { 15 | id 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /docs/routing.md: -------------------------------------------------------------------------------- 1 | # Routing 2 | 3 | ## Locale 4 | 5 | Symfony is setup to set the locale based on URL paths. E.g. `/ja` for Japanese. If no URL path is setup 6 | then English is set as the default locale. 7 | 8 | Setup in [annotations.yaml](../config/routes/annotations.yaml) 9 | 10 | ## Routing 11 | 12 | [Annotations](https://symfony.com/doc/current/routing.html#creating-routes-as-attributes-or-annotations) are used in controllers to define routes. 13 | 14 | -------------------------------------------------------------------------------- /templates/bundles/W3CWebsiteTemplatesBundle/base.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@!W3CWebsiteTemplates/base.html.twig' %} 2 | 3 | {% block lang_nav %}{{ lang_nav(site.locale|default(default_locale))|raw }}{% endblock %} 4 | 5 | {% block global_nav %}{{ global_nav(site.locale|default(default_locale))|raw }}{% endblock %} 6 | 7 | {% block closing_body_scripts %} 8 | 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /src/Query/W3C/Ecosystem/Bizdev.php: -------------------------------------------------------------------------------- 1 | setUri('/functions/bizdev'); 15 | } 16 | 17 | public function getRequiredDataProviderClass(): string 18 | { 19 | return W3C::class; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /templates/press-releases/show.html.twig: -------------------------------------------------------------------------------- 1 | {% extends site.localeData('siteLink') ? '@W3CWebsiteTemplates/pages/post.html.twig' : '@W3CWebsiteTemplates/pages/basic.html.twig' %} 2 | 3 | {% set body_classes = 'post' %} 4 | 5 | {% block feed %} 6 | {% if site.localeData('siteLink') %} 7 | {% include '@W3CWebsiteTemplates/components/styles/feed.html.twig' with { 'feed_url': '/press-releases/feed', 'feed_type': 'press release' } %} 8 | {% endif %} 9 | {% endblock %} 10 | -------------------------------------------------------------------------------- /bin/phpunit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | setUri('/groups/' . $type . '/' . $shortname) 15 | ->addParam('embed', true) 16 | ; 17 | } 18 | 19 | public function getRequiredDataProviderClass(): string 20 | { 21 | return W3C::class; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /phpcs.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | config/ 14 | public/ 15 | src/ 16 | tests/ 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/blog/listing.graphql: -------------------------------------------------------------------------------- 1 | query BlogListing($site: [String]) { 2 | entry(section: "blogListing", site: $site) { 3 | id 4 | slug 5 | title 6 | uri 7 | expiryDate 8 | breadcrumbs: parent { 9 | ...breadcrumbs 10 | } 11 | ... on blogListing_Entry { 12 | excerpt 13 | thumbnailImage { 14 | ...thumbnailImage 15 | } 16 | thumbnailAltText 17 | lead: pageLead 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/events/page.graphql: -------------------------------------------------------------------------------- 1 | query EventsPage($site: [String]) { 2 | entry(section: "eventsListing", site: $site) { 3 | id 4 | slug 5 | title 6 | uri 7 | expiryDate 8 | breadcrumbs: parent { 9 | ...breadcrumbs 10 | } 11 | ... on eventsListing_Entry { 12 | excerpt 13 | thumbnailImage { 14 | ...thumbnailImage 15 | } 16 | thumbnailAltText 17 | lead: pageLead 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/news/listing.graphql: -------------------------------------------------------------------------------- 1 | query NewsListing($site: [String]) { 2 | entry(section: "newsListing", site: $site) { 3 | id 4 | slug 5 | title 6 | uri 7 | expiryDate 8 | breadcrumbs: parent { 9 | ...breadcrumbs 10 | } 11 | ... on newsListing_Entry { 12 | excerpt 13 | thumbnailImage { 14 | ...thumbnailImage 15 | } 16 | thumbnailAltText 17 | lead: pageLead 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /templates/pages/default.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/default.html.twig' %} 2 | 3 | {% set components %} 4 | {% if page.defaultFlexibleComponents is defined and page.defaultFlexibleComponents %} 5 | {% for component in page.defaultFlexibleComponents %} 6 | {{ include('partials/defaultFlexibleComponents.html.twig') }} 7 | {% endfor %} 8 | {% endif %} 9 | {% endset %} 10 | {% set toc = table_of_contents(components, ['h2']) %} 11 | 12 | {% block components %} 13 | {{ toc.html|raw }} 14 | {% endblock %} 15 | -------------------------------------------------------------------------------- /config/packages/prod/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: fingers_crossed 5 | action_level: error 6 | handler: nested 7 | excluded_http_codes: [ 404, 405 ] 8 | channels: [ "!php" ] 9 | nested: 10 | type: stream 11 | path: "%kernel.logs_dir%/%kernel.environment%.log" 12 | level: debug 13 | console: 14 | type: console 15 | process_psr_3_messages: false 16 | channels: [ "!event", "!doctrine" ] 17 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/newsletters/collection.graphql: -------------------------------------------------------------------------------- 1 | query NewsletterCollection($site: [String], $year: [String], $limit: Int, $offset: Int) { 2 | total: entryCount(section: "newsletter", site: $site, postDate: $year) 3 | entries(section: "newsletter", site: $site, postDate: $year, limit: $limit, offset: $offset, orderBy: "postDate desc") { 4 | title 5 | date: postDate 6 | year: postDate@formatDateTime(format: "Y") 7 | month: postDate@formatDateTime(format: "m") 8 | day: postDate@formatDateTime(format: "d") 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/newsletters/listing.graphql: -------------------------------------------------------------------------------- 1 | query NewslettersListing($site: [String]) { 2 | entry(section: "newsletterArchive", site: $site) { 3 | id 4 | slug 5 | title 6 | uri 7 | expiryDate 8 | breadcrumbs: parent { 9 | ...breadcrumbs 10 | } 11 | ... on newsletterArchive_Entry { 12 | excerpt 13 | thumbnailImage { 14 | ...thumbnailImage 15 | } 16 | thumbnailAltText 17 | lead: pageLead 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/feeds/events.graphql: -------------------------------------------------------------------------------- 1 | query Events($site: [String], $limit: Int) { 2 | entries(section: "events", site: $site, limit: $limit, orderBy: "postDate desc") { 3 | ... on events_default_Entry { 4 | ...listingEvent 5 | defaultFlexibleComponents { 6 | ...defaultFlexibleComponents 7 | } 8 | } 9 | ... on external_Entry { 10 | ...listingExternalEvent 11 | } 12 | ... on entryContentIsACraftPage_Entry { 13 | ...listingPageEvent 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/press-releases/listing.graphql: -------------------------------------------------------------------------------- 1 | query PressReleasesListing($site: [String]) { 2 | entry(section: "pressReleasesListing", site: $site) { 3 | id 4 | slug 5 | title 6 | uri 7 | expiryDate 8 | breadcrumbs: parent { 9 | ...breadcrumbs 10 | } 11 | ... on pressReleasesListing_Entry { 12 | excerpt 13 | thumbnailImage { 14 | ...thumbnailImage 15 | } 16 | thumbnailAltText 17 | lead: pageLead 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/fragments/breadcrumbs.graphql: -------------------------------------------------------------------------------- 1 | fragment breadcrumbs on EntryInterface { 2 | title 3 | uri 4 | parent { 5 | title 6 | uri 7 | parent { 8 | title 9 | uri 10 | parent { 11 | title 12 | uri 13 | parent { 14 | title 15 | uri 16 | parent { 17 | title 18 | uri 19 | } 20 | } 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /config/packages/notifier.yaml: -------------------------------------------------------------------------------- 1 | framework: 2 | notifier: 3 | #chatter_transports: 4 | # slack: '%env(SLACK_DSN)%' 5 | # telegram: '%env(TELEGRAM_DSN)%' 6 | #texter_transports: 7 | # twilio: '%env(TWILIO_DSN)%' 8 | # nexmo: '%env(NEXMO_DSN)%' 9 | channel_policy: 10 | # use chat/slack, chat/telegram, sms/twilio or sms/nexmo 11 | urgent: ['email'] 12 | high: ['email'] 13 | medium: ['email'] 14 | low: ['email'] 15 | admin_recipients: 16 | - { email: admin@example.com } 17 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/ecosystems/testimonials.graphql: -------------------------------------------------------------------------------- 1 | query testimonials($ecosystemId: [QueryArgument]) { 2 | entries(section: "testimonial", ecosystems: $ecosystemId, site: "*") { 3 | id 4 | language 5 | ... on testimonial_default_Entry { 6 | quote: quoteForCarousel 7 | author: authorName 8 | authorJobTitle 9 | organization: authorOrganization 10 | logo: organizationLogo { 11 | ... on publicS3Bucket_Asset { 12 | url 13 | } 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /public/index.php: -------------------------------------------------------------------------------- 1 | bootEnv(dirname(__DIR__) . '/.env'); 11 | 12 | if ($_SERVER['APP_DEBUG']) { 13 | umask(0000); 14 | 15 | Debug::enable(); 16 | } 17 | 18 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 19 | $request = Request::createFromGlobals(); 20 | $response = $kernel->handle($request); 21 | $response->send(); 22 | $kernel->terminate($request, $response); 23 | -------------------------------------------------------------------------------- /config/packages/fos_http_cache.yaml: -------------------------------------------------------------------------------- 1 | fos_http_cache: 2 | flash_message: 3 | enabled: true 4 | tags: 5 | enabled: true 6 | response_header: Cache-Tag 7 | proxy_client: 8 | default: noop 9 | noop: ~ 10 | cache_control: 11 | defaults: 12 | overwrite: false 13 | rules: 14 | # match everything to set defaults 15 | - 16 | match: 17 | path: ^/ 18 | headers: 19 | cache_control: 20 | public: true 21 | s_maxage: 86400 22 | etag: true 23 | -------------------------------------------------------------------------------- /docs/front-end-integration/README.md: -------------------------------------------------------------------------------- 1 | # Front-end integration 2 | 3 | The following is an overview on how to integrate HTML/CSS templates with content (from a CMS or other data 4 | sources) in Symfony to create finished web pages with real content. We are using the 5 | [Strata](https://github.com/strata/frontend) library to help integrate with headless CMS and other data 6 | sources. CraftCMS uses [GraphQL](https://graphql.org/learn/) for all API queries. 7 | 8 | * [Routing](routing.md) 9 | * [Retrieving data](retrieving-data.md) 10 | * [GraphQL queries for CraftCMS](craftcms-graphql.md) 11 | * [Templating](templating.md) (todo) 12 | * [Testing](testing.md) (todo) 13 | -------------------------------------------------------------------------------- /config/packages/dev/monolog.yaml: -------------------------------------------------------------------------------- 1 | monolog: 2 | handlers: 3 | main: 4 | type: stream 5 | path: "%kernel.logs_dir%/%kernel.environment%.log" 6 | level: debug 7 | channels: ["!event"] 8 | # uncomment to get logging in your browser 9 | # you may have to allow bigger header sizes in your Web server configuration 10 | #firephp: 11 | # type: firephp 12 | # level: info 13 | #chromephp: 14 | # type: chromephp 15 | # level: info 16 | console: 17 | type: console 18 | process_psr_3_messages: false 19 | channels: ["!event", "!doctrine", "!console"] 20 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/fragments/listingEvent.graphql: -------------------------------------------------------------------------------- 1 | fragment listingEvent on events_default_Entry { 2 | id 3 | typeHandle 4 | slug 5 | uri 6 | title 7 | date: postDate 8 | start: startDatetime 9 | end: endDatetime 10 | tz: startDatetime@formatDateTime(format: "e") 11 | year: startDatetime @formatDateTime(format: "Y") 12 | category: blogCategories { 13 | id 14 | slug 15 | uri 16 | title 17 | } 18 | type: eventType { 19 | id 20 | slug 21 | title 22 | } 23 | excerpt: eventExcerpt 24 | thumbnailImage { 25 | ...thumbnailImage 26 | } 27 | thumbnailAltText 28 | location 29 | host 30 | } 31 | -------------------------------------------------------------------------------- /config/packages/framework.yaml: -------------------------------------------------------------------------------- 1 | # see https://symfony.com/doc/current/reference/configuration/framework.html 2 | framework: 3 | secret: '%env(APP_SECRET)%' 4 | trusted_proxies: '%env(TRUSTED_PROXIES)%' 5 | #csrf_protection: true 6 | #http_method_override: true 7 | 8 | # Enables session support. Note that the session will ONLY be started if you read or write from it. 9 | # Remove or comment this section to explicitly disable session support. 10 | session: 11 | handler_id: null 12 | cookie_secure: auto 13 | cookie_samesite: lax 14 | storage_factory_id: session.storage.factory.native 15 | 16 | #esi: true 17 | #fragments: true 18 | php_errors: 19 | log: true 20 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/fragments/listingExternalEvent.graphql: -------------------------------------------------------------------------------- 1 | fragment listingExternalEvent on external_Entry { 2 | id 3 | typeHandle 4 | slug 5 | uri 6 | title 7 | date: postDate 8 | start: startDatetime 9 | end: endDatetime 10 | tz: startDatetime@formatDateTime(format: "e") 11 | year: startDatetime @formatDateTime(format: "Y") 12 | category: blogCategories { 13 | id 14 | slug 15 | uri 16 | title 17 | } 18 | type: eventType { 19 | id 20 | slug 21 | title 22 | } 23 | excerpt: eventExcerpt 24 | thumbnailImage { 25 | ...thumbnailImage 26 | } 27 | thumbnailAltText 28 | location 29 | host 30 | urlLink 31 | } 32 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Blog/Comment.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/blog/comment.graphql') 22 | ->setRootPropertyPath('[comment]') 23 | ->addVariable('id', '' . $id) 24 | // ->enableCache($cacheLifetime) 25 | ; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /templates/newsletters/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/listing.html.twig' %} 2 | 3 | {% set extra_main_attributes = 'data-anchor="no"' %} 4 | 5 | {% block filters %} 6 | {{ include('@W3CWebsiteTemplates/components/listings/newsletters/filters.html.twig') }} 7 | {% endblock %} 8 | 9 | {% block list %} 10 |
11 | {{ 'pagination.summary'|trans({ 12 | 'page': entries.pagination.page, 13 | 'from': entries.pagination.from, 14 | 'to': entries.pagination.to 15 | }, 'w3c_website_templates_bundle') }} 16 |
17 | 18 | {# use same template as news #} 19 | {{ include('@W3CWebsiteTemplates/components/listings/news/list.html.twig') }} 20 | {% endblock %} 21 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/fragments/listingPageEvent.graphql: -------------------------------------------------------------------------------- 1 | fragment listingPageEvent on entryContentIsACraftPage_Entry { 2 | id 3 | typeHandle 4 | slug 5 | uri 6 | title 7 | date: postDate 8 | start: startDatetime 9 | end: endDatetime 10 | tz: startDatetime@formatDateTime(format: "e") 11 | year: startDatetime @formatDateTime(format: "Y") 12 | category: blogCategories { 13 | id 14 | slug 15 | uri 16 | title 17 | } 18 | type: eventType { 19 | id 20 | slug 21 | title 22 | } 23 | location 24 | host 25 | page: eventPage { 26 | uri 27 | } 28 | excerpt: eventExcerpt 29 | thumbnailImage { 30 | ...thumbnailImage 31 | } 32 | thumbnailAltText 33 | } 34 | -------------------------------------------------------------------------------- /templates/ecosystems/show.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/ecosystem.html.twig' %} 2 | 3 | {% block components %} 4 | {% for component in page.ecosystemsFlexibleComponents %} 5 | {{ include('partials/ecosystemsFlexibleComponents.html.twig') }} 6 | {% endfor %} 7 | {% endblock %} 8 | {% block bottom_components %} 9 | {% for component in page.ecosystemsBottomFlexibleComponents %} 10 | {{ include('partials/ecosystemsFlexibleComponents.html.twig') }} 11 | {% endfor %} 12 | {% endblock %} 13 | {% block pre_footer %} 14 | {{ include('@W3CWebsiteTemplates/components/styles/feed.html.twig', { 15 | 'feed_url': path('app_feed_ecosystem', {'slug': page.slug}), 16 | 'feed_type': page.title 17 | }) }} 18 | {% endblock %} 19 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/feeds/news.graphql: -------------------------------------------------------------------------------- 1 | query news($site: [String], $limit: Int) { 2 | entries(section: "newsArticles", site: $site, limit: $limit, orderBy: "postDate desc") { 3 | id 4 | slug 5 | uri 6 | title 7 | year: postDate@formatDateTime(format: "Y") 8 | date: postDate 9 | dateUpdated 10 | ... on newsArticles_default_Entry { 11 | excerpt 12 | defaultFlexibleComponents(orderBy: "sortOrder") { 13 | ...defaultFlexibleComponents 14 | } 15 | } 16 | ... on newsArticles_importedEntries_Entry { 17 | excerpt 18 | pageContent 19 | } 20 | } 21 | } 22 | 23 | fragment contentImage on AssetInterface { 24 | src: url(width: 580) 25 | extension 26 | } 27 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Blog/Comments.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/blog/comments.graphql') 22 | ->setRootPropertyPath('[comments]') 23 | ->addVariable('postId', $id) 24 | ->addVariable('siteId', $siteId) 25 | ->cache($cacheLifetime) 26 | ; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Query/W3C/Ecosystem/BizdevLead.php: -------------------------------------------------------------------------------- 1 | setUri('/users/' . $hash); 16 | } 17 | 18 | public function getRequiredDataProviderClass(): string 19 | { 20 | return W3C::class; 21 | } 22 | 23 | public function getMapping(): MappingStrategyInterface|array 24 | { 25 | return [ 26 | '[name]' => '[name]', 27 | '[work_title]' => '[work-title]', 28 | '[phone]' => '[phone]', 29 | '[email]' => '[email]', 30 | ]; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/singles-breadcrumbs.graphql: -------------------------------------------------------------------------------- 1 | query SinglesBreadcrumbs($site: [String]) { 2 | homepage: entry(section: "homepage", site: $site) { 3 | slug 4 | title 5 | uri 6 | } 7 | blog: entry(section: "blogListing", site: $site) { 8 | slug 9 | title 10 | uri 11 | } 12 | pressReleases: entry(section: "pressReleasesListing", site: $site) { 13 | slug 14 | title 15 | uri 16 | } 17 | events: entry(section: "eventsListing", site: $site) { 18 | slug 19 | title 20 | uri 21 | } 22 | news: entry(section: "newsListing", site: $site) { 23 | slug 24 | title 25 | uri 26 | } 27 | ecosystems: entry(section: "ecosystemsLandingPage", site: $site) { 28 | slug 29 | title 30 | uri 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/feeds/press-releases.graphql: -------------------------------------------------------------------------------- 1 | query pressReleases($site: [String], $limit: Int) { 2 | entries(section: "pressReleases", site: $site, limit: $limit, orderBy: "postDate desc") { 3 | id 4 | slug 5 | uri 6 | title 7 | year: postDate@formatDateTime(format: "Y") 8 | date: postDate 9 | dateUpdated 10 | ... on pressReleases_default_Entry { 11 | excerpt 12 | defaultFlexibleComponents(orderBy: "sortOrder") { 13 | ...defaultFlexibleComponents 14 | } 15 | } 16 | ... on importedEntries_Entry { 17 | excerpt 18 | pageContent 19 | } 20 | } 21 | comments(site: $site) { 22 | ownerId 23 | } 24 | } 25 | 26 | fragment contentImage on AssetInterface { 27 | src: url(width: 580) 28 | extension 29 | } 30 | -------------------------------------------------------------------------------- /.env.local.dist: -------------------------------------------------------------------------------- 1 | # Application environment (dev, staging, prod) 2 | APP_ENV=dev 3 | APP_URL=https://w3c-website-frontend.ddev.site 4 | 5 | # W3C API API key 6 | # see https://w3c.github.io/w3c-api/ 7 | W3C_API_KEY="op://Clients/W3C API URLs/W3C_API_TOKEN" 8 | 9 | # Craft CMS API tokens 10 | CRAFTCMS_API_URL="https://cms-dev.w3.org/api" 11 | CRAFTCMS_API_READ_TOKEN="op://Clients/W3C API URLs/CRAFTCMS_API_TOKEN_DEV" 12 | CRAFTCMS_API_PUBLISH_TOKEN="" 13 | 14 | # Point assets to W3C CDN for local dev 15 | ASSETS_WEBSITE_2021=https://www.w3.org/assets/website-2021/ 16 | 17 | # Other Symfony configuration 18 | MAILER_DRIVER="smtp" 19 | MAILER_HOST="127.0.0.1" 20 | MAILER_PORT="1025" 21 | MAILER_URL="smtp://127.0.0.1:1025" 22 | MAILER_WEB_URL="https://w3c-website-frontend.ddev.site:8026" 23 | MAILER_AUTH_MODE="" 24 | MAILER_PASSWORD="" 25 | MAILER_USERNAME="" 26 | MAILER_CATCHER="1" 27 | MAILER_DSN="smtp://127.0.0.1:1025" 28 | -------------------------------------------------------------------------------- /config/packages/security.yaml: -------------------------------------------------------------------------------- 1 | security: 2 | # https://symfony.com/doc/current/security.html#where-do-users-come-from-user-providers 3 | providers: 4 | users_in_memory: { memory: null } 5 | firewalls: 6 | dev: 7 | pattern: ^/(_(profiler|wdt)|css|images|js)/ 8 | security: false 9 | main: 10 | lazy: true 11 | provider: users_in_memory 12 | 13 | # activate different ways to authenticate 14 | # https://symfony.com/doc/current/security.html#firewalls-authentication 15 | 16 | # https://symfony.com/doc/current/security/impersonating_user.html 17 | # switch_user: true 18 | # Easy way to control access for large sections of your site 19 | # Note: Only the *first* access control that matches will be used 20 | access_control: 21 | # - { path: ^/admin, roles: ROLE_ADMIN } 22 | # - { path: ^/profile, roles: ROLE_USER } 23 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/staff/alumni.graphql: -------------------------------------------------------------------------------- 1 | query AlumniListing($site: [String]) { 2 | entry(section: "alumniListing", site: $site) { 3 | id 4 | slug 5 | title 6 | uri 7 | expiryDate 8 | breadcrumbs: parent { 9 | ...breadcrumbs 10 | } 11 | ... on alumniListing_Entry { 12 | excerpt 13 | thumbnailImage { 14 | ...thumbnailImage 15 | } 16 | thumbnailAltText 17 | lead: pageLead 18 | } 19 | } 20 | total: entryCount(section: "alumni", site: $site) 21 | entries(section: "alumni", site: $site, orderBy: "familyName asc, title asc") { 22 | ... on alumni_default_Entry { 23 | slug 24 | given: givenName 25 | last: familyName 26 | start: startDate 27 | end: endDate 28 | biography: shortBiography 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Service/CraftCMS.php: -------------------------------------------------------------------------------- 1 | setAuthorization($apiKey); 26 | } 27 | 28 | /** 29 | * Set API authorization token to use with all requests 30 | * 31 | * @param string $token 32 | */ 33 | public function setAuthorization(string $token) 34 | { 35 | $this->setDefaultOptions([ 36 | 'auth_bearer' => $token 37 | ]); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Query/W3C/Healthcheck.php: -------------------------------------------------------------------------------- 1 | setUri('healthcheck') 20 | // ->disableCache() 21 | ; 22 | } 23 | 24 | public function getRequiredDataProviderClass(): string 25 | { 26 | return W3C::class; 27 | } 28 | 29 | /** 30 | * Check all services are online 31 | * 32 | * @return bool 33 | * @throws \Strata\Data\Exception\MapperException 34 | */ 35 | public function isHealthy(): bool 36 | { 37 | $data = $this->get(); 38 | if ($data['app'] === true && $data['database'] === true) { 39 | return true; 40 | } 41 | 42 | return false; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/fragments/defaultFlexibleComponents.graphql: -------------------------------------------------------------------------------- 1 | fragment defaultFlexibleComponents on defaultFlexibleComponents_MatrixField { 2 | ... on textComponent_Entry { 3 | typeHandle 4 | contentField 5 | } 6 | ... on blockquoteComponent_Entry { 7 | typeHandle 8 | quoteText 9 | citation 10 | } 11 | ...on imageMediaComponent_Entry { 12 | typeHandle 13 | imageMedia { 14 | ...contentImage 15 | } 16 | altText 17 | figureCaption 18 | informativeOrDecorative 19 | } 20 | ...on videoMediaComponent_Entry { 21 | typeHandle 22 | videoTitle 23 | videoUrl { 24 | url 25 | image 26 | imageWidth 27 | imageHeight 28 | code 29 | width 30 | height 31 | aspectRatio 32 | providerName 33 | } 34 | videoCaption 35 | linkToVideoTranscript 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /config/routes/annotations.yaml: -------------------------------------------------------------------------------- 1 | controllers: 2 | resource: ../../src/Controller/ 3 | type: attribute 4 | prefix: 5 | af: '/af' 6 | ar: '/ar' 7 | bg: '/bg' 8 | cs: '/cs' 9 | da: '/da' 10 | de: '/de' 11 | el: '/el' 12 | es: '/es' 13 | et: '/et' 14 | fi: '/fi' 15 | fr: '/fr' 16 | ga: '/ga' 17 | hi: '/hi' 18 | hu: '/hu' 19 | it: '/it' 20 | ja: '/ja' 21 | ko: '/ko' 22 | lt: '/lt' 23 | lv: '/lv' 24 | mt: '/mt' 25 | nl: '/nl' 26 | pt-br: '/pt-br' 27 | pt: '/pt' 28 | ro: '/ro' 29 | ru: '/ru' 30 | sk: '/sk' 31 | sl: '/sl' 32 | sv: '/sv' 33 | zh-hans: '/zh-hans' 34 | zu: '/zu' 35 | en: '' # safety net: this is last to ensure a URL containing a locale is not considered as a parameter for an English route 36 | 37 | kernel: 38 | resource: ../../src/Kernel.php 39 | type: attribute 40 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/home/recent-activities.graphql: -------------------------------------------------------------------------------- 1 | query RecentActivities($site: [String]) { 2 | recentEntries: entries(site: $site, section: ["blogPosts", "newsArticles", "pressReleases"], limit: 4, orderBy: "postDate DESC") { 3 | id 4 | slug 5 | sectionHandle 6 | typeHandle 7 | title 8 | uri 9 | url 10 | year: postDate @formatDateTime(format: "Y") 11 | ... on blogPosts_default_Entry { 12 | excerpt 13 | thumbnailImage { 14 | ...thumbnailImage 15 | } 16 | thumbnailAltText 17 | } 18 | ... on newsArticles_default_Entry { 19 | excerpt 20 | thumbnailImage { 21 | ...thumbnailImage 22 | } 23 | thumbnailAltText 24 | } 25 | ... on pressReleases_default_Entry { 26 | excerpt 27 | thumbnailImage { 28 | ...thumbnailImage 29 | } 30 | thumbnailAltText 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Blog/CreateComment.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/blog/create-comment.graphql') 27 | ->setRootPropertyPath('[saveComment]') 28 | ->addVariable('postId', $id) 29 | ->addVariable('siteId', $siteId) 30 | ->addVariable('name', $name) 31 | ->addVariable('email', $email) 32 | ->addVariable('comment', $comment) 33 | ->addVariable('parentId', $parentId) 34 | ; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Feeds/Comments.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/feeds/comments.graphql') 32 | ->setRootPropertyPath('[comments]') 33 | ->addVariable('ownerId', $postIds) 34 | ; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /templates/blog/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/listing.html.twig' %} 2 | 3 | {% set extra_main_attributes = 'data-anchor="no"' %} 4 | 5 | {% block filters %} 6 | {{ include('@W3CWebsiteTemplates/components/listings/blog/filters.html.twig') }} 7 | {% endblock %} 8 | 9 | {% block list %} 10 |
11 | {{ 'pagination.summary'|trans({ 12 | 'page': entries.pagination.page, 13 | 'from': entries.pagination.from, 14 | 'to': entries.pagination.to 15 | }, 'w3c_website_templates_bundle') }} 16 |
17 | 18 | {{ include('@W3CWebsiteTemplates/components/listings/blog/list.html.twig') }} 19 | {% endblock %} 20 | 21 | {% block pre_footer %} 22 | {% embed '@W3CWebsiteTemplates/components/pre-footer.html.twig' %} 23 | {% block content %} 24 | {% include '@W3CWebsiteTemplates/components/styles/feed.html.twig' with { 'feed_url': path('app_feed_blog'), 'feed_type': 'blog' } %} 25 | {% endblock %} 26 | {% endembed %} 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /templates/news/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/listing.html.twig' %} 2 | 3 | {% set extra_main_attributes = 'data-anchor="no"' %} 4 | 5 | {% block filters %} 6 | {{ include('@W3CWebsiteTemplates/components/listings/news/filters.html.twig') }} 7 | {% endblock %} 8 | 9 | {% block list %} 10 |
11 | {{ 'pagination.summary'|trans({ 12 | 'page': entries.pagination.page, 13 | 'from': entries.pagination.from, 14 | 'to': entries.pagination.to 15 | }, 'w3c_website_templates_bundle') }} 16 |
17 | 18 | {{ include('@W3CWebsiteTemplates/components/listings/news/list.html.twig') }} 19 | {% endblock %} 20 | 21 | {% block pre_footer %} 22 | {% embed '@W3CWebsiteTemplates/components/pre-footer.html.twig' %} 23 | {% block content %} 24 | {% include '@W3CWebsiteTemplates/components/styles/feed.html.twig' with { 'feed_url': path('app_feed_news'), 'feed_type': 'news' } %} 25 | {% endblock %} 26 | {% endembed %} 27 | {% endblock %} 28 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Feeds/News.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/feeds/news.graphql') 29 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/defaultFlexibleComponents.graphql') 30 | ->setRootPropertyPath('[entries]') 31 | ->addVariable('site', $siteHandle) 32 | ->addVariable('limit', $limit) 33 | ->cacheTags(['newsArticles']) 34 | ; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # NPM 2 | node_modules 3 | npm-debug.log 4 | 5 | # SASS 6 | .sass_cache 7 | 8 | # Caches 9 | .phplint-cache 10 | 11 | #PHPStorm 12 | .idea 13 | 14 | ###> symfony/framework-bundle ### 15 | /.env.local 16 | /.env.local.php 17 | /.env.*.local 18 | /config/secrets/prod/prod.decrypt.private.php 19 | /public/bundles/ 20 | /var/ 21 | /vendor/ 22 | ###< symfony/framework-bundle ### 23 | 24 | ###> symfony/phpunit-bridge ### 25 | .phpunit 26 | .phpunit.result.cache 27 | /phpunit.xml 28 | ###< symfony/phpunit-bridge ### 29 | 30 | ###> squizlabs/php_codesniffer ### 31 | /.phpcs-cache 32 | /phpcs.xml 33 | ###< squizlabs/php_codesniffer ### 34 | 35 | ###> phpunit/phpunit ### 36 | /phpunit.xml 37 | .phpunit.result.cache 38 | ###< phpunit/phpunit ### 39 | 40 | ###> PHPStorm graphql ### 41 | /.graphqlconfig 42 | /schema.graphql 43 | ###< PHPStorm graphql ### 44 | 45 | test-cache-clear.php 46 | 47 | # YAML file that ignores peer and host 48 | # Gets ignored so it doesn't go on the dev server on deployment 49 | config/packages/dev/http_client.yaml 50 | 51 | # Ignore design system assets symlink 52 | public/assets -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | tests 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | src 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/press-releases/collection.graphql: -------------------------------------------------------------------------------- 1 | query PressReleasesCollection($site: [String], $before: String, $after: String, $limit: Int, $offset: Int) { 2 | total: entryCount(section: "pressReleases", site: $site, before: $before, after: $after) 3 | entries(section: "pressReleases", site: $site, before: $before, after: $after, limit: $limit, offset: $offset, orderBy: "postDate desc") { 4 | id 5 | slug 6 | uri 7 | title 8 | date: postDate 9 | year: postDate@formatDateTime(format: "Y") 10 | ... on pressReleases_default_Entry { 11 | excerpt 12 | thumbnailImage { 13 | ...thumbnailImage 14 | } 15 | thumbnailAltText 16 | } 17 | ... on importedEntries_Entry { 18 | excerpt 19 | thumbnailImage { 20 | ...thumbnailImage 21 | } 22 | thumbnailAltText 23 | } 24 | } 25 | } 26 | 27 | fragment thumbnailImage on AssetInterface { 28 | url(transform: "size360x270") 29 | srcset(sizes: ["580w"]) 30 | } 31 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Feeds/PressReleases.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/feeds/press-releases.graphql') 29 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/defaultFlexibleComponents.graphql') 30 | ->setRootPropertyPath('[entries]') 31 | ->addVariable('site', $siteHandle) 32 | ->addVariable('limit', $limit) 33 | ->cacheTags(['pressReleases']) 34 | ; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/news/collection.graphql: -------------------------------------------------------------------------------- 1 | query NewsCollection($site: [String], $before: String, $after: String, $search: String, $limit: Int, $offset: Int) { 2 | total: entryCount(section: "newsArticles", site: $site, before: $before, after: $after, search: $search) 3 | entries(section: "newsArticles", site: $site, before: $before, after: $after, search: $search, limit: $limit, offset: $offset, orderBy: "postDate desc") { 4 | id 5 | slug 6 | uri 7 | title 8 | date: postDate 9 | year: postDate@formatDateTime(format: "Y") 10 | ... on newsArticles_default_Entry { 11 | excerpt 12 | thumbnailImage { 13 | ...thumbnailImage 14 | } 15 | thumbnailAltText 16 | } 17 | ... on newsArticles_importedEntries_Entry { 18 | excerpt 19 | thumbnailImage { 20 | ...thumbnailImage 21 | } 22 | thumbnailAltText 23 | } 24 | } 25 | } 26 | 27 | fragment thumbnailImage on AssetInterface { 28 | url(transform: "size360x270") 29 | srcset(sizes: ["580w"]) 30 | } 31 | -------------------------------------------------------------------------------- /templates/press-releases/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/listing.html.twig' %} 2 | 3 | {% set extra_main_attributes = 'data-anchor="no"' %} 4 | 5 | {% block filters %} 6 | {{ include('@W3CWebsiteTemplates/components/listings/press-releases/filters.html.twig') }} 7 | {% endblock %} 8 | 9 | {% block list %} 10 |
11 | {{ 'pagination.summary'|trans({ 12 | 'page': entries.pagination.page, 13 | 'from': entries.pagination.from, 14 | 'to': entries.pagination.to 15 | }, 'w3c_website_templates_bundle') }} 16 |
17 | 18 | {{ include('@W3CWebsiteTemplates/components/listings/press-releases/list.html.twig') }} 19 | {% endblock %} 20 | 21 | {% block pre_footer %} 22 | {% embed '@W3CWebsiteTemplates/components/pre-footer.html.twig' %} 23 | {% block content %} 24 | {% include '@W3CWebsiteTemplates/components/styles/feed.html.twig' with { 'feed_url': path('app_feed_pressreleases'), 'feed_type': 'press releases' } %} 25 | {{ parent() }} 26 | {% endblock %} 27 | {% endembed %} 28 | {% endblock %} 29 | -------------------------------------------------------------------------------- /config/packages/nyholm_psr7.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | # Register nyholm/psr7 services for autowiring with PSR-17 (HTTP factories) 3 | Psr\Http\Message\RequestFactoryInterface: '@nyholm.psr7.psr17_factory' 4 | Psr\Http\Message\ResponseFactoryInterface: '@nyholm.psr7.psr17_factory' 5 | Psr\Http\Message\ServerRequestFactoryInterface: '@nyholm.psr7.psr17_factory' 6 | Psr\Http\Message\StreamFactoryInterface: '@nyholm.psr7.psr17_factory' 7 | Psr\Http\Message\UploadedFileFactoryInterface: '@nyholm.psr7.psr17_factory' 8 | Psr\Http\Message\UriFactoryInterface: '@nyholm.psr7.psr17_factory' 9 | 10 | # Register nyholm/psr7 services for autowiring with HTTPlug factories 11 | Http\Message\MessageFactory: '@nyholm.psr7.httplug_factory' 12 | Http\Message\RequestFactory: '@nyholm.psr7.httplug_factory' 13 | Http\Message\ResponseFactory: '@nyholm.psr7.httplug_factory' 14 | Http\Message\StreamFactory: '@nyholm.psr7.httplug_factory' 15 | Http\Message\UriFactory: '@nyholm.psr7.httplug_factory' 16 | 17 | nyholm.psr7.psr17_factory: 18 | class: Nyholm\Psr7\Factory\Psr17Factory 19 | 20 | nyholm.psr7.httplug_factory: 21 | class: Nyholm\Psr7\Factory\HttplugFactory 22 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Taxonomies/Tags.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/taxonomies/tags.graphql') 29 | ->addVariable('site', $siteHandle) 30 | ->addVariable('handle', $handle) 31 | ->setRootPropertyPath('[tags]') 32 | ->cache($cacheLifetime) 33 | ; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Feeds/Blog.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/feeds/blog.graphql') 29 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/defaultFlexibleComponents.graphql') 30 | ->setRootPropertyPath('[entries]') 31 | ->addVariable('site', $siteHandle) 32 | ->addVariable('limit', $limit) 33 | ->addVariable('category', $category) 34 | ->addVariable('tag', $tag) 35 | ->cacheTags(['blogPosts']) 36 | ; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Taxonomies/Categories.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/taxonomies/categories.graphql') 29 | ->addVariable('site', $siteHandle) 30 | ->addVariable('handle', $handle) 31 | ->setRootPropertyPath('[categories]') 32 | ->cache($cacheLifetime) 33 | ; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /rector.php: -------------------------------------------------------------------------------- 1 | withPaths([ 12 | __DIR__ . '/config', 13 | __DIR__ . '/public', 14 | __DIR__ . '/src', 15 | __DIR__ . '/tests', 16 | __DIR__ . '/templates', 17 | ]) 18 | // uncomment to reach your current PHP version 19 | // ->withPhpSets() 20 | ->withTypeCoverageLevel(0) 21 | ->withSymfonyContainerXml(__DIR__ . '/var/cache/dev/App_KernelDevDebugContainer.xml') 22 | ->withSets( 23 | [ 24 | SymfonySetList::SYMFONY_71, 25 | SymfonySetList::SYMFONY_CODE_QUALITY, 26 | SymfonySetList::SYMFONY_CONSTRUCTOR_INJECTION, 27 | SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES, 28 | SymfonySetList::CONFIGS, 29 | ] 30 | ) 31 | ->withSymfonyContainerPhp(__DIR__ . '/tests/symfony-container.php') 32 | ->registerService(SymfonyRoutesProvider::class, SymfonyRoutesProviderInterface::class) 33 | ; 34 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Taxonomies/GroupInfo.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/taxonomies/group-info.graphql') 32 | ->addVariable('site', $siteHandle) 33 | ->addVariable('slug', $slug) 34 | ->setRootPropertyPath('[category]') 35 | ->cache($cacheLifetime); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /config/bundles.php: -------------------------------------------------------------------------------- 1 | ['all' => true], 5 | Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true], 6 | Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true], 7 | Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true], 8 | Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true], 9 | Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true], 10 | Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true], 11 | Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true], 12 | W3C\WebsiteTemplatesBundle\W3CWebsiteTemplatesBundle::class => ['all' => true], 13 | FOS\HttpCacheBundle\FOSHttpCacheBundle::class => ['all' => true], 14 | Strata\SymfonyBundle\StrataBundle::class => ['all' => true], 15 | Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true], 16 | Exercise\HTMLPurifierBundle\ExerciseHTMLPurifierBundle::class => ['all' => true], 17 | Chrisguitarguy\RequestId\ChrisguitarguyRequestIdBundle::class => ['all' => true], 18 | Ekreative\HealthCheckBundle\EkreativeHealthCheckBundle::class => ['all' => true], 19 | ]; 20 | -------------------------------------------------------------------------------- /src/Query/W3C/Ecosystem/Members.php: -------------------------------------------------------------------------------- 1 | setUri('/ecosystems/' . $slug . '/member-organizations') 17 | ->addParam('items', $perPage) 18 | ->addParam('embed', true) 19 | ->addParam('page', $page) 20 | ->setRootPropertyPath('[_embedded][affiliations]') 21 | ->setCurrentPage('[page]') 22 | ->setTotalResults('[total]') 23 | ->setResultsPerPage('[limit]') 24 | // ->disableCache() 25 | ; 26 | } 27 | 28 | public function getRequiredDataProviderClass(): string 29 | { 30 | return W3C::class; 31 | } 32 | 33 | public function getMapping(): MappingStrategyInterface|array 34 | { 35 | return [ 36 | '[name]' => '[name]', 37 | '[logo]' => '[_links][logo][href]' 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/front-end-integration/matrix-flexible-components.md: -------------------------------------------------------------------------------- 1 | # Matrix: flexible components 2 | 3 | _Information from CraftCMS Support:_ 4 | 5 | If you’re looking to get content from a Rich Text field within a Matrix block, you'll need the entry type, Matrix field 6 | handle, Matrix block type, and Matrix block field handle for the query like this: 7 | 8 | ```graphql 9 | query { 10 | entries(section: "blog") { 11 | ... on blog_blog_Entry { 12 | postBlocks { 13 | ... on postBlocks_text_BlockType { 14 | body 15 | } 16 | } 17 | } 18 | } 19 | } 20 | ``` 21 | 22 | This is for a blog section entry with a postBlocks Matrix field with a body Rich Text field that’s in a text block type. 23 | 24 | Each of these bits and pieces should be discoverable via autocomplete or the Explorer pane in Craft’s GraphiQL interface. 25 | 26 | ![Example of autocompleting GraphQL via the GraphQLi explorer](../images/matrix-graphql.png) 27 | 28 | ![Example of autocompleting field names GraphQL via the GraphQLi explorer](../images/matrix-graphql-explorer.png) 29 | 30 | This is what that Matrix field’s configuration looks like in the Craft control panel. 31 | 32 | ![Example of matrix field configuration in Craft CMS](../images/matrix-craft-cms.png) -------------------------------------------------------------------------------- /src/Query/CraftCMS/Newsletters/Entry.php: -------------------------------------------------------------------------------- 1 | add(new DateInterval('P1D')); 33 | 34 | $this->setGraphQLFromFile(__DIR__ . '/../graphql/newsletters/entry.graphql') 35 | ->setRootPropertyPath('[entry]') 36 | ->addVariable('site', $siteHandle) 37 | ->addVariable('date', ['and', '>=' . $date->format('Y-m-d'), '<' . $nextDay->format('Y-m-d')]) 38 | ->cacheTags(['newsletter']) 39 | ; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Query/W3C/Ecosystem/Groups.php: -------------------------------------------------------------------------------- 1 | setUri('/ecosystems/' . $slug . '/groups') 16 | ->addParam('items', $perPage) 17 | ->addParam('embed', true) 18 | ->addParam('page', $page) 19 | ->setRootPropertyPath('[_embedded][groups]') 20 | ->setCurrentPage('[page]') 21 | ->setTotalResults('[total]') 22 | ->setResultsPerPage('[limit]'); 23 | // ->disableCache(); 24 | } 25 | 26 | public function getRequiredDataProviderClass(): string 27 | { 28 | return W3C::class; 29 | } 30 | 31 | public function getMapping(): MappingStrategyInterface|array 32 | { 33 | return [ 34 | '[name]' => '[name]', 35 | '[type]' => '[type]', 36 | '[description]' => '[description]', 37 | '[url]' => '[_links][homepage][href]', 38 | ]; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Query/W3C/Home/Members.php: -------------------------------------------------------------------------------- 1 | setUri('/affiliations') 17 | ->addParam('is-member', true) 18 | ->addParam('with-logo', true) 19 | ->addParam('items', $perPage) 20 | ->addParam('embed', true) 21 | ->addParam('page', $page) 22 | ->setRootPropertyPath('[_embedded][affiliations]') 23 | ->setCurrentPage('[page]') 24 | ->setTotalResults('[total]') 25 | ->setResultsPerPage('[limit]') 26 | ->cacheTag('members') 27 | ; 28 | } 29 | 30 | public function getRequiredDataProviderClass(): string 31 | { 32 | return W3C::class; 33 | } 34 | 35 | public function getMapping(): MappingStrategyInterface|array 36 | { 37 | return [ 38 | '[name]' => '[name]', 39 | '[logo]' => '[_links][logo][href]' 40 | ]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /config/packages/chrisguitarguy_request_id.yaml: -------------------------------------------------------------------------------- 1 | chrisguitarguy_request_id: 2 | # The header which the bundle inspects for the incoming request ID 3 | # if this is not set an ID will be generated and set at this header 4 | request_header: X-Request-Id 5 | 6 | # Whether or not to trust the incoming request header. This is turned 7 | # on by default. If true a value in the `Request-Id` header in the request 8 | # will be used as the request ID for the rest of the request. If false 9 | # those values are ignored. 10 | trust_request_header: true 11 | 12 | # The header which the bundle will set the request ID to on 13 | # the response 14 | response_header: X-Request-Id 15 | 16 | # The service key of an object that implements 17 | # Chrisguitarguy\RequestId\RequestIdStorage 18 | # optional, defaults to `SimpleIdStorage` 19 | storage_service: ~ 20 | 21 | # The service key of an object that implements 22 | # Chrisguitarguy\RequestId\RequestIdGenerator 23 | # optional, defaults to a UUID v4 based generator 24 | generator_service: ~ 25 | 26 | # Whether or not to add the monolog process (see below), defaults to true 27 | enable_monolog: true 28 | 29 | # Whether or not to add the twig extension (see below), defaults to true 30 | enable_twig: true 31 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/events/listing.graphql: -------------------------------------------------------------------------------- 1 | query EventsListing( 2 | $site: [String], 3 | $eventType: [QueryArgument], 4 | $category: [QueryArgument], 5 | $tag: [QueryArgument], 6 | $start: [QueryArgument], 7 | $end: [QueryArgument], 8 | $limit: Int, 9 | $offset: Int) { 10 | total: entryCount( 11 | section: "events", 12 | site: $site, 13 | eventType: $eventType, 14 | blogCategories: $category, 15 | blogTags: $tag, 16 | startDatetime: $start, 17 | endDatetime: $end) 18 | entries( 19 | section: "events", 20 | site: $site, 21 | eventType: $eventType, 22 | blogCategories: $category, 23 | blogTags: $tag, 24 | startDatetime: $start, 25 | endDatetime: $end, 26 | limit: $limit, 27 | offset: $offset, 28 | orderBy: "startDatetime asc") { 29 | ... on events_default_Entry { 30 | ...listingEvent 31 | } 32 | ... on external_Entry { 33 | ...listingExternalEvent 34 | } 35 | ... on entryContentIsACraftPage_Entry { 36 | ...listingPageEvent 37 | } 38 | } 39 | } 40 | 41 | fragment thumbnailImage on AssetInterface { 42 | url(transform: "size360x270") 43 | srcset(sizes: ["580w"]) 44 | } 45 | 46 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/events/filters.graphql: -------------------------------------------------------------------------------- 1 | query EventFilters($site: [String]) { 2 | categories(site: $site, group: "blogCategories", orderBy: "title asc") { 3 | title 4 | slug 5 | } 6 | types: categories(site: $site, group: "eventType", orderBy: "title asc") { 7 | id 8 | title 9 | slug 10 | ... on eventType_Category { 11 | pageLead 12 | } 13 | } 14 | first: entry(section: "events", site: $site, orderBy: "startDatetime asc") { 15 | ... on events_default_Entry { 16 | year: startDatetime@formatDateTime(format: "Y") 17 | } 18 | ... on external_Entry { 19 | year: startDatetime@formatDateTime(format: "Y") 20 | } 21 | ... on entryContentIsACraftPage_Entry { 22 | year: startDatetime@formatDateTime(format: "Y") 23 | } 24 | } 25 | last: entry(section: "events", site: $site, orderBy: "startDatetime desc") { 26 | ... on events_default_Entry { 27 | year: startDatetime@formatDateTime(format: "Y") 28 | } 29 | ... on external_Entry { 30 | year: startDatetime@formatDateTime(format: "Y") 31 | } 32 | ... on entryContentIsACraftPage_Entry { 33 | year: startDatetime@formatDateTime(format: "Y") 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/News/Listing.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/news/listing.graphql') 32 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/breadcrumbs.graphql') 33 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 34 | ->setRootPropertyPath('[entry]') 35 | ->addVariable('site', $siteHandle) 36 | ->cache($cacheLifetime) 37 | ->cacheTags(['newsArticles']) 38 | ; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Taxonomies/CategoryInfo.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/taxonomies/category-info.graphql') 34 | ->addVariable('site', $siteHandle) 35 | ->addVariable('handle', $handle) 36 | ->addVariable('slug', $slug) 37 | ->setRootPropertyPath('[category]') 38 | ->cache($cacheLifetime) 39 | ; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Events/Page.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/events/page.graphql') 32 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/breadcrumbs.graphql') 33 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 34 | ->setRootPropertyPath('[entry]') 35 | 36 | ->addVariable('site', $siteHandle) 37 | ->cache($cacheLifetime) 38 | ->cacheTags(['events']) 39 | ; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /translations/messages+intl-icu.zh-hans.yaml: -------------------------------------------------------------------------------- 1 | api_available: 2 | w3c: >- 3 | W3C API 可用性: {available, select, 4 | 1 {可用} 5 | other {不可用} 6 | } 7 | craft: >- 8 | Craft CMS API 可用性: {available, select, 9 | 1 {可用} 10 | other {不可用} 11 | } 12 | specifications_found: >- 13 | 在 W3C API ({cache, select, 14 | 1 {缓存} 15 | other {实时请求} 16 | }中{total, plural, 17 | =0 {没有找到规范} 18 | one {找到 # 条规范} 19 | other {找到 # 条规范} 20 | }) 21 | blog: 22 | comments: 23 | form: 24 | comment: 25 | label: 评论 26 | help: 您的邮箱地址将不会被公开。 27 | name: 28 | label: 姓名 29 | email: 30 | label: 邮箱地址 31 | submit: 发布评论 32 | cancel: 取消 33 | success: 您的评论已成功保存,将在审核之后进入他人可见状态。返回评论区 34 | components: 35 | groups_list: 36 | title: 活跃组 37 | events: 38 | archive: 39 | title: W3C archived events 40 | browse: Browse archives 41 | label: Event archive 42 | error404: 43 | title: 页面丢失 44 | message:

很抱歉,您试图访问的页面并不存在。您可以参考下面的步骤来寻找您访问的页面:

45 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/PressReleases/Listing.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/press-releases/listing.graphql') 31 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/breadcrumbs.graphql') 32 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 33 | ->setRootPropertyPath('[entry]') 34 | ->addVariable('site', $siteHandle) 35 | ->cache($cacheLifetime) 36 | ->cacheTags(['pressReleases']) 37 | ; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Query/W3C/Ecosystem/Evangelists.php: -------------------------------------------------------------------------------- 1 | setUri('/ecosystems/' . $slug . '/evangelists') 18 | ->addParam('items', $perPage) 19 | ->addParam('embed', true) 20 | ->addParam('page', $page) 21 | ->setRootPropertyPath('[_embedded][evangelists]') 22 | ->setCurrentPage('[page]') 23 | ->setTotalResults('[total]') 24 | ->setResultsPerPage('[limit]'); 25 | // ->disableCache(); 26 | ; 27 | } 28 | 29 | public function getRequiredDataProviderClass(): string 30 | { 31 | return W3C::class; 32 | } 33 | 34 | public function getMapping(): MappingStrategyInterface|array 35 | { 36 | return [ 37 | '[name]' => '[name]', 38 | '[work_title]' => '[work-title]', 39 | '[phone]' => '[phone]', 40 | '[email]' => '[email]', 41 | ]; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/ecosystems/ecosystem.graphql: -------------------------------------------------------------------------------- 1 | query Ecosystem($slug: [String], $site: [String]) { 2 | entry(slug: $slug, section: "ecosystems", site: $site) { 3 | id 4 | status 5 | slug 6 | uri 7 | language 8 | title 9 | localized { 10 | title 11 | language_code: language 12 | slug 13 | } 14 | postDate 15 | dateUpdated 16 | expiryDate 17 | breadcrumbs: parent { 18 | ...breadcrumbs 19 | } 20 | ... on default_Entry { 21 | ecosystem { 22 | slug 23 | id 24 | } 25 | pageLead 26 | heroIllustration { 27 | url 28 | mimeType 29 | height 30 | width 31 | size 32 | } 33 | excerpt 34 | thumbnailImage { 35 | ...thumbnailImage 36 | } 37 | thumbnailAltText 38 | ecosystemsFlexibleComponents { 39 | ...ecosystemsFlexibleComponents 40 | } 41 | ecosystemsBottomFlexibleComponents { 42 | ...ecosystemsBottomFlexibleComponents 43 | } 44 | recentActivitiesComponentTitle 45 | recentActivitiesComponentIntroduction 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/Query/CraftCMS/Blog/Listing.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/blog/listing.graphql') 33 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/breadcrumbs.graphql') 34 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 35 | ->setRootPropertyPath('[entry]') 36 | ->addVariable('site', $siteHandle) 37 | ->cache($cacheLifetime) 38 | ->cacheTags(['blogPosts']) 39 | ; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Staff/AlumniListing.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/staff/alumni.graphql') 33 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/breadcrumbs.graphql') 34 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 35 | ->setRootPropertyPath('[entries]') 36 | ->setTotalResults('[total]') 37 | 38 | ->addVariable('site', $siteHandle) 39 | ->cache($cacheLifetime) 40 | ->cacheTags(['alumniListing']) 41 | ; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Newsletters/Listing.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/newsletters/listing.graphql') 33 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/breadcrumbs.graphql') 34 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 35 | ->setRootPropertyPath('[entry]') 36 | ->addVariable('site', $siteHandle) 37 | ->cache($cacheLifetime) 38 | ->cacheTags(['newsletter']) 39 | ; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getParameterOption(['--env', '-e'], null, true)) { 24 | putenv('APP_ENV='.$_SERVER['APP_ENV'] = $_ENV['APP_ENV'] = $env); 25 | } 26 | 27 | if ($input->hasParameterOption('--no-debug', true)) { 28 | putenv('APP_DEBUG='.$_SERVER['APP_DEBUG'] = $_ENV['APP_DEBUG'] = '0'); 29 | } 30 | 31 | (new Dotenv())->bootEnv(dirname(__DIR__).'/.env'); 32 | 33 | if ($_SERVER['APP_DEBUG']) { 34 | umask(0000); 35 | 36 | if (class_exists(Debug::class)) { 37 | Debug::enable(); 38 | } 39 | } 40 | 41 | $kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']); 42 | $application = new Application($kernel); 43 | $application->run($input); 44 | -------------------------------------------------------------------------------- /translations/messages+intl-icu.ja.yaml: -------------------------------------------------------------------------------- 1 | api_available: 2 | w3c: >- 3 | 利用可能なW3C API: {available, select, 4 | 1 {はい} 5 | other {いいえ} 6 | } 7 | craft: >- 8 | 利用可能なCraft CMS API: {available, select, 9 | 1 {はい} 10 | other {いいえ} 11 | } 12 | specifications_found: >- 13 | W3C API({cache, select, 14 | 1 {キャッシュ} 15 | other {ライブリクエスト} 16 | } 内で {total, plural, 17 | =0 {仕様は見つかりませんでした} 18 | one {仕様 # 件が見つかりました} 19 | other {仕様 # 件が見つかりました} 20 | }) 21 | blog: 22 | comments: 23 | form: 24 | comment: 25 | label: コメント 26 | help: あなたのメールアドレスは公開されません。 27 | name: 28 | label: 名前 29 | email: 30 | label: メールアドレス 31 | submit: コメントを投稿 32 | cancel: キャンセル 33 | success: あなたのコメントは正常に保存され、表示される前にモデレーションされます。 コメントに戻る 34 | components: 35 | groups_list: 36 | title: アクティブなグループ 37 | events: 38 | archive: 39 | title: W3C アーカイブ済みイベント 40 | browse: アーカイブを閲覧 41 | label: イベントアーカイブ 42 | error404: 43 | title: ページが見つかりません 44 | message:

申し訳ありませんが、アクセスしようとしていたページは存在しません。以下は、お探しのページを見つけるために試すことができるいくつかの手順です:

45 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/global-navigation.graphql: -------------------------------------------------------------------------------- 1 | query GlobalNavigation ($site: [String], $limit: Int) { 2 | total: entryCount(section: "mainNavigation", type: "topLevelItem", site: $site, limit: $limit) 3 | entries(section: "mainNavigation", type: "topLevelItem", site: $site, limit: $limit) { 4 | title 5 | ... on topLevelItem_Entry { 6 | isTitleLinkInternal: mainNavigationInternalOrExternalLink 7 | titleInternalLink: internalLink { 8 | sectionHandle 9 | uri 10 | slug 11 | year: postDate@formatDateTime(format: "Y") 12 | } 13 | titleExternalLink: urlLink 14 | introText: mainNavigationDropdownIntroduction 15 | introLinks: mainNavigationIntroductionLinks { 16 | url: link 17 | title: titleLabel 18 | } 19 | children { 20 | title 21 | ... on externalLink_Entry { 22 | url: urlLink 23 | startNewColumn 24 | } 25 | ... on internalLink_Entry { 26 | startNewColumn 27 | internalLink { 28 | sectionHandle 29 | uri 30 | slug 31 | year: postDate@formatDateTime(format: "Y") 32 | } 33 | } 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /docs/git_workflow.md: -------------------------------------------------------------------------------- 1 | # Git workflow 2 | 3 | The branch called `main` is the production branch, i.e. it always corresponds to the live website CMS. 4 | 5 | You should always perform development work in another branch and merge your working branch into `main` when ready. To do so, 6 | you will need to issue a pull request. The pull request will require approval by another developer and passing [continuous integration](continuous_integration.md) tests before merging. 7 | 8 | ## Naming branches 9 | 10 | Here are our recommendations for naming branches **in the context of this project**. 11 | 12 | If you are working on a new feature, please name your branch `feature/`, e.g. `feature/super-custom-field-module` 13 | 14 | If several developers are working on various features are the same time, and their work is interdependent, it is worth creating a `develop` branch from which to work. You should work directly in develop 15 | or merge your working branches in `develop` frequently. 16 | 17 | If several developers are working on various features at the same time, and in theory their work should be independent but tested and released at the same time, it is worth creating a 'release' 18 | branch in which to merge all the working branches for testing and release. Please name release branches `release/`, e.g. `release/january-website-upgrade`. 19 | 20 | If you are working on an urgent fix to the production site, please create a 'hotfix' branch from 'main', named `hotfix/`, e.g. `hotfix/component-x-failure`. 21 | -------------------------------------------------------------------------------- /.github/workflows/php.yml: -------------------------------------------------------------------------------- 1 | name: PHP tests 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | strategy: 13 | matrix: 14 | php-versions: ['8.2'] 15 | 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v2 21 | 22 | # https://github.com/marketplace/actions/setup-php-action 23 | - name: Setup PHP 24 | uses: shivammathur/setup-php@v2 25 | with: 26 | php-version: ${{ matrix.php-versions }} 27 | extensions: mbstring, intl 28 | ini-values: post_max_size=256M, max_execution_time=180 29 | tools: composer 30 | 31 | - uses: actions/checkout@v2 32 | 33 | - name: Check PHP version 34 | run: php -v 35 | 36 | - name: Install dependencies 37 | run: composer install --prefer-dist 38 | 39 | - name: Code lint PHP files 40 | run: ./vendor/bin/phplint 41 | 42 | - name: Coding standards 43 | run: ./vendor/bin/phpcs 44 | 45 | # - name: PHPUnit 46 | # run: ./vendor/phpunit/phpunit/phpunit 47 | # env: 48 | # CRAFTCMS_API_READ_TOKEN: ${{ secrets.CRAFTCMS_API_READ_TOKEN }} 49 | # CRAFTCMS_API_PUBLISH_TOKEN: ${{ secrets.CRAFTCMS_API_PUBLISH_TOKEN }} 50 | # CACHE_ENABLE: 0 51 | 52 | - name: Redirects 53 | run: php ./tests/urls/test-urls.php 54 | 55 | - name: Symfony lint service container 56 | run: php ./bin/console lint:container 57 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Feeds/Events.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/feeds/events.graphql') 29 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/listingEvent.graphql') 30 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/listingPageEvent.graphql') 31 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/listingExternalEvent.graphql') 32 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/defaultFlexibleComponents.graphql') 33 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 34 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/contentImage.graphql') 35 | ->setRootPropertyPath('[entries]') 36 | ->addVariable('site', $siteHandle) 37 | ->addVariable('limit', $limit) 38 | ->cacheTags(['events']) 39 | ; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/feeds/blog.graphql: -------------------------------------------------------------------------------- 1 | query blog($site: [String], $category: [QueryArgument], $tag: [QueryArgument], $limit: Int) { 2 | entries(section: "blogPosts", site: $site, blogCategories: $category, blogTags: $tag, limit: $limit, orderBy: "postDate desc") { 3 | typeHandle 4 | id 5 | slug 6 | uri 7 | title 8 | year: postDate@formatDateTime(format: "Y") 9 | date: postDate 10 | dateUpdated 11 | ... on blogPosts_default_Entry { 12 | authors: blogAuthors { 13 | ... on author_Entry { 14 | name: authorName 15 | email: authorEmailAddress 16 | } 17 | } 18 | excerpt 19 | defaultFlexibleComponents(orderBy: "sortOrder") { 20 | ...defaultFlexibleComponents 21 | } 22 | categories: blogCategories { 23 | slug 24 | title 25 | } 26 | } 27 | ... on blogPosts_importedEntries_Entry { 28 | authors: blogAuthors { 29 | ... on author_Entry { 30 | name: authorName 31 | email: authorEmailAddress 32 | } 33 | } 34 | excerpt 35 | pageContent 36 | categories: blogCategories { 37 | slug 38 | title 39 | } 40 | } 41 | } 42 | } 43 | 44 | fragment contentImage on AssetInterface { 45 | src: url(width: 580) 46 | extension 47 | } 48 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/ecosystems/recent-activities.graphql: -------------------------------------------------------------------------------- 1 | query RecentActivities($site: [String], $ecosystemId: [QueryArgument], $endDatetime: [QueryArgument]) { 2 | recentEntries: entries(site: $site, section: ["blogPosts", "newsArticles", "pressReleases"], ecosystems: $ecosystemId, limit: 4, orderBy: "postDate DESC") { 3 | id 4 | slug 5 | sectionHandle 6 | typeHandle 7 | title 8 | uri 9 | url 10 | year: postDate @formatDateTime(format: "Y") 11 | ... on blogPosts_default_Entry { 12 | excerpt 13 | thumbnailImage { 14 | ...thumbnailImage 15 | } 16 | thumbnailAltText 17 | } 18 | ... on newsArticles_default_Entry { 19 | excerpt 20 | thumbnailImage { 21 | ...thumbnailImage 22 | } 23 | thumbnailAltText 24 | } 25 | ... on pressReleases_default_Entry { 26 | excerpt 27 | thumbnailImage { 28 | ...thumbnailImage 29 | } 30 | thumbnailAltText 31 | } 32 | } 33 | recentEvents: entries(section: "events", ecosystems: $ecosystemId, endDatetime: $endDatetime, limit: 4, orderBy: "endDatetime ASC") { 34 | ... on events_default_Entry { 35 | ...listingEvent 36 | } 37 | ... on external_Entry { 38 | ...listingExternalEvent 39 | } 40 | ... on entryContentIsACraftPage_Entry { 41 | ...listingPageEvent 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Kernel.php: -------------------------------------------------------------------------------- 1 | import('../config/{packages}/*.yaml'); 17 | $container->import('../config/{packages}/' . $this->environment . '/*.yaml'); 18 | 19 | if (is_file(\dirname(__DIR__) . '/config/services.yaml')) { 20 | $container->import('../config/services.yaml'); 21 | $container->import('../config/{services}_' . $this->environment . '.yaml'); 22 | } elseif (is_file($path = \dirname(__DIR__) . '/config/services.php')) { 23 | (require $path)($container->withPath($path), $this); 24 | } 25 | } 26 | 27 | protected function configureRoutes(RoutingConfigurator $routes): void 28 | { 29 | $routes->import('../config/{routes}/' . $this->environment . '/*.yaml'); 30 | $routes->import('../config/{routes}/*.yaml'); 31 | 32 | if (is_file(\dirname(__DIR__) . '/config/routes.yaml')) { 33 | $routes->import('../config/routes.yaml'); 34 | } elseif (is_file($path = \dirname(__DIR__) . '/config/routes.php')) { 35 | (require $path)($routes->withPath($path), $this); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /tests/urls/test-urls.php: -------------------------------------------------------------------------------- 1 | 0, 32 | ]); 33 | $responses = []; 34 | $found = []; 35 | 36 | echo sprintf("Testing %s URLs to see whether they are already setup as 301 redirects\n", count($urls)); 37 | 38 | foreach ($urls as $url) { 39 | $responses[] = $client->request('GET', $url); 40 | } 41 | 42 | /** @var ResponseInterface $response */ 43 | foreach ($responses as $response) { 44 | echo $response->getInfo('url') . ' ' . $response->getStatusCode() . PHP_EOL; 45 | 46 | if ($response->getStatusCode() === $statusCode) { 47 | $found[] = $response->getInfo('url'); 48 | } 49 | } 50 | 51 | echo PHP_EOL; 52 | echo sprintf("Found %s URLs that match %s:\n%s", count($found), $statusCode, implode(PHP_EOL, $found)); 53 | -------------------------------------------------------------------------------- /docs/continuous_integration.md: -------------------------------------------------------------------------------- 1 | # Continuous integration 2 | 3 | We use [GitHub actions](https://docs.github.com/en/actions) to run automated actions on any merge into the default branch (main). 4 | 5 | When your work includes writing some PHP, make sure to run these tests manually; at least before merging you working branch into 'main'. 6 | 7 | ## PHP 8 | Config: .github/workflows/php.yml 9 | 10 | Tests the project across different versions of PHP. The workflow runs: 11 | 12 | * [PHP linting](https://github.com/studio24/project-base-template/blob/main/docs/continuous-integration.md#php-linting) - is the code syntax valid? 13 | * [Code formatting](https://github.com/studio24/project-base-template/blob/main/docs/continuous-integration.md#code-formatting) - does the PHP code meet our coding standards? 14 | 15 | ### PHPUnit 16 | 17 | You can run PHPUnit by using the command: `./bin/phpunit tests/` 18 | 19 | ### PHP linting 20 | PHPLint tests PHP files for syntax errors. 21 | 22 | Config: .phplint.yml 23 | 24 | We exclude the vendor folder and test all other PHP files in the project. 25 | 26 | You can run Phplint manually by using the command `vendor/bin/phplint` 27 | 28 | ### Code formatting 29 | PHP_CodeSniffer (PHPCS) tests PHP files to ensure they meet coding standards. 30 | 31 | Config: .phpcs.xml.dist 32 | 33 | Coding standard enforced: 34 | 35 | * PSR-12 36 | 37 | You can run PHPCS manually by using the command `vendor/bin/phpcs` 38 | 39 | ### Fixing code issues automatically 40 | Use PHP Code Beautifier and Fixer (phpcbf) to automatically fix code issues. 41 | 42 | Run it manually by using the command `vendor/bin/phpcbf` -------------------------------------------------------------------------------- /templates/rss_entry.html.twig: -------------------------------------------------------------------------------- 1 | {% for component in components %} 2 | {% if component.typeHandle == 'textComponent' %} 3 | {% embed '@W3CWebsiteTemplates/components/text.html.twig' %} 4 | {% block text %} 5 | {{ component.contentField|raw }} 6 | {% endblock %} 7 | {% endembed %} 8 | {% elseif component.typeHandle == 'blockquoteComponent' %} 9 | {% embed '@W3CWebsiteTemplates/components/quote.html.twig' with {'author': component.citation} %} 10 | {% block text %} 11 | {{ component.quoteText }} 12 | {% endblock %} 13 | {% endembed %} 14 | {% elseif component.typeHandle == 'imageMediaComponent' %} 15 | {% embed '@W3CWebsiteTemplates/components/image.html.twig' with { 16 | 'attr': component.imageMedia[0], 17 | 'alt': component.altText, 18 | 'caption': component.figureCaption is defined ? component.figureCaption : null, 19 | 'informative_or_decorative': component.informativeOrDecorative 20 | } %} 21 | {% endembed %} 22 | {% elseif component.typeHandle == 'videoMediaComponent' %} 23 | {% embed '@W3CWebsiteTemplates/components/video.html.twig' with { 24 | 'iframe': component.videoUrl.code, 25 | 'title': component.videoTitle, 26 | 'caption': component.videoCaption is defined ? component.videoCaption : null, 27 | 'transcript': component.linkToVideoTranscript is defined ? component.linkToVideoTranscript : null 28 | } %} 29 | {% endembed %} 30 | {% endif %} 31 | {% endfor %} 32 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/fragments/ecosystemsBottomFlexibleComponents.graphql: -------------------------------------------------------------------------------- 1 | fragment ecosystemsBottomFlexibleComponents on ecosystemsBottomFlexibleComponents_MatrixField { 2 | ... on textComponent_Entry { 3 | typeHandle 4 | contentField 5 | } 6 | ... on blockquoteComponent_Entry { 7 | typeHandle 8 | quoteText 9 | citation 10 | } 11 | ... on fiftyFiftyComponent_Entry { 12 | typeHandle 13 | showImageBeforeText 14 | image { 15 | ...contentImage 16 | } 17 | informativeOrDecorative 18 | altText 19 | titleField 20 | mainContent 21 | ctaUrl 22 | ctaCopy 23 | secondaryLinkUrl 24 | } 25 | ... on imageMediaComponent_Entry { 26 | typeHandle 27 | imageMedia { 28 | ...contentImage 29 | } 30 | altText 31 | figureCaption 32 | informativeOrDecorative 33 | } 34 | ... on videoMediaComponent_Entry { 35 | typeHandle 36 | videoTitle 37 | videoUrl { 38 | url 39 | image 40 | imageWidth 41 | imageHeight 42 | code 43 | width 44 | height 45 | aspectRatio 46 | providerName 47 | } 48 | videoCaption 49 | linkToVideoTranscript 50 | } 51 | ... on groupsListingComponent_Entry { 52 | typeHandle 53 | introduction 54 | } 55 | ... on featuredEvangelistsComponent_Entry { 56 | typeHandle 57 | title: evangelistsTitle 58 | introduction 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/fragments/ecosystemsFlexibleComponents.graphql: -------------------------------------------------------------------------------- 1 | fragment ecosystemsFlexibleComponents on ecosystemsFlexibleComponents_MatrixField { 2 | ... on textComponent_Entry { 3 | typeHandle 4 | contentField 5 | } 6 | ... on blockquoteComponent_Entry { 7 | typeHandle 8 | quoteText 9 | citation 10 | } 11 | ... on fiftyFiftyComponent_Entry { 12 | typeHandle 13 | showImageBeforeText 14 | image { 15 | ...contentImage 16 | } 17 | informativeOrDecorative 18 | altText 19 | titleField 20 | mainContent 21 | ctaUrl 22 | ctaCopy 23 | secondaryLinkUrl 24 | secondaryLinkCopy 25 | } 26 | ... on imageMediaComponent_Entry { 27 | typeHandle 28 | imageMedia { 29 | ...contentImage 30 | } 31 | altText 32 | figureCaption 33 | informativeOrDecorative 34 | } 35 | ... on videoMediaComponent_Entry { 36 | typeHandle 37 | videoTitle 38 | videoUrl { 39 | url 40 | image 41 | imageWidth 42 | imageHeight 43 | code 44 | width 45 | height 46 | aspectRatio 47 | providerName 48 | } 49 | videoCaption 50 | linkToVideoTranscript 51 | } 52 | ... on groupsListingComponent_Entry { 53 | typeHandle 54 | introduction 55 | } 56 | ... on featuredEvangelistsComponent_Entry { 57 | typeHandle 58 | title: evangelistsTitle 59 | introduction 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /templates/debug/test.html.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | W3C test page 7 | 8 | {% block stylesheets %}{% endblock %} 9 | {% block javascripts %}{% endblock %} 10 | 11 | 12 | 13 |

W3C test page

14 | 15 |

Example page with default page content outputted to a basic HTML template. Once this works, integrate me with the real templates!

16 | 17 |

Global navigation

18 | 19 |

Is navigation cached? {% if navigation_cached %}Yes{% else %}No{% endif %}

20 | 21 |
    22 | {% for item in navigation %} 23 |
      24 |

      {{ item.title }}

      25 |

      26 | {{ item.introText }} 27 | {% if item.introLinks %} 28 |
      29 | {% for link in item.introLinks %} 30 | {{ link.title }} 31 | {% endfor %} 32 | {% endif %} 33 |

      34 | {% if item.children %} 35 |
        36 | {% for child in item.children %} 37 |
      • {{ child.title }}
      • 38 | {% endfor %} 39 |
      40 | {% endif %} 41 |
    42 | {% endfor %} 43 |
44 | 45 |

Page: {{ page.title }}

46 | 47 |

Is page cached? {% if page_cached %}Yes{% else %}No{% endif %}

48 | 49 |

W3C API available? {% if w3c_available %}Yes{% else %}No{% endif %}

50 | 51 | 52 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Feeds/Taxonomy.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/feeds/taxonomy.graphql') 29 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/defaultFlexibleComponents.graphql') 30 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 31 | ->setRootPropertyPath('[entries]') 32 | ->addVariable('site', $siteHandle) 33 | ->addVariable('limit', $limit) 34 | ->addVariable('category', $category) 35 | ->addVariable('ecosystem', $ecosystem) 36 | ->addVariable('group', $group) 37 | ->cacheTags(['blogPosts', 'events', 'newsArticles', 'pressReleases']) 38 | ; 39 | } 40 | 41 | public function getMapping(): MappingStrategyInterface|array 42 | { 43 | $mapping = new WildcardMappingStrategy(); 44 | $mapping->addMapping('page', ['[page]' => '[page][0]']); 45 | 46 | return $mapping; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /templates/partials/defaultFlexibleComponents.html.twig: -------------------------------------------------------------------------------- 1 | {% if component.typeHandle == 'textComponent' %} 2 | {% embed '@W3CWebsiteTemplates/components/text.html.twig' %} 3 | {% block text %} 4 | {{ component.contentField|raw }} 5 | {% endblock %} 6 | {% endembed %} 7 | {% elseif component.typeHandle == 'blockquoteComponent' %} 8 | {% embed '@W3CWebsiteTemplates/components/quote.html.twig' with {'author': component.citation} %} 9 | {% block text %} 10 | {{ component.quoteText }} 11 | {% endblock %} 12 | {% endembed %} 13 | {% elseif component.typeHandle == 'imageMediaComponent' %} 14 | {% embed '@W3CWebsiteTemplates/components/image.html.twig' with { 15 | 'attr': component.imageMedia[0], 16 | 'alt': component.altText, 17 | 'caption': component.figureCaption is defined ? component.figureCaption : null, 18 | 'informative_or_decorative': component.informativeOrDecorative 19 | } %} 20 | {% endembed %} 21 | {% elseif component.typeHandle == 'videoMediaComponent' %} 22 | {% if component.videoUrl.code is defined and component.videoUrl.code %} 23 | {% set params = {'iframe': component.videoUrl.code} %} 24 | {% else %} 25 | {% set params = {'url': component.videoUrl.url} %} 26 | {% endif %} 27 | {% set params = params | merge({ 28 | 'title': component.videoTitle, 29 | 'caption': component.videoCaption is defined ? component.videoCaption : null, 30 | 'transcript': component.linkToVideoTranscript is defined ? component.linkToVideoTranscript : null 31 | }) %} 32 | {% embed '@W3CWebsiteTemplates/components/video.html.twig' with params %} 33 | {% endembed %} 34 | {% endif %} 35 | -------------------------------------------------------------------------------- /src/Service/FeedHelper.php: -------------------------------------------------------------------------------- 1 | router = $router; 14 | } 15 | 16 | public function buildTaxonomyFeeds(array $page): array 17 | { 18 | $feeds = []; 19 | // only one category max, could be an empty array if no category is set 20 | if (array_key_exists('category', $page) && count($page['category']) > 0) { 21 | $feeds[] = [ 22 | 'title' => 'W3C - ' . $page['category']['title'], 23 | 'href' => $this->router->generate('app_feed_category', ['slug' => $page['category']['slug']]) 24 | ]; 25 | } 26 | 27 | if (array_key_exists('ecosystems', $page)) { 28 | foreach ($page['ecosystems'] as $ecosystem) { 29 | $feeds[] = [ 30 | 'title' => 'W3C - ' . $ecosystem['title'] . ' Ecosystem', 31 | 'href' => $this->router->generate('app_feed_ecosystem', ['slug' => $ecosystem['slug']]) 32 | ]; 33 | } 34 | } 35 | 36 | if (array_key_exists('groups', $page)) { 37 | foreach ($page['groups'] as $group) { 38 | [$type, $shortname] = explode('-', $group['slug'], 2); 39 | $feeds[] = [ 40 | 'title' => 'W3C - ' . $group['title'], 41 | 'href' => $this->router->generate('app_feed_group', ['type' => $type, 'shortname' => $shortname]) 42 | ]; 43 | } 44 | } 45 | 46 | return $feeds; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /translations/messages+intl-icu.en.yaml: -------------------------------------------------------------------------------- 1 | api_available: 2 | w3c: >- 3 | W3C API available: {available, select, 4 | 1 {yes} 5 | other {no} 6 | } 7 | craft: >- 8 | Craft CMS API available: {available, select, 9 | 1 {yes} 10 | other {no} 11 | } 12 | specifications_found: >- 13 | Found {total, plural, 14 | =0 {no specifications} 15 | one {# specification} 16 | other {# specifications} 17 | } in W3C API ({cache, select, 18 | 1 {cache hit} 19 | other {live request} 20 | }) 21 | blog: 22 | comments: 23 | form: 24 | comment: 25 | label: Comment 26 | help: Your email address will not be published. 27 | name: 28 | label: Name 29 | email: 30 | label: Email address 31 | submit: Post comment 32 | cancel: Cancel 33 | success: Your comment has successfully been saved and will be moderated before becoming visible. Back to comments 34 | components: 35 | groups_list: 36 | title: Active groups 37 | events: 38 | archive: 39 | title: W3C archived events 40 | browse: Browse archives 41 | label: Event archive 42 | error404: 43 | title: Page not found 44 | message:

Sorry, the page you were trying to reach does not exist. Here are some steps you may try to find the page you were looking for:

  • If you typed the URL by hand then please make sure that it is exactly as it should be.
  • If you are looking for information on a particular subject, please start on the W3C home page.
  • Try searching the site using the search icon at the top of this page.
45 | -------------------------------------------------------------------------------- /docs/front-end-integration/routing.md: -------------------------------------------------------------------------------- 1 | # Routing 2 | 3 | A [proposed sitemap](https://docs.google.com/spreadsheets/d/1a9pm5HWzcidtLPCeFRz4F0Ir4TT3oOK54FlEEd3IXUE/edit#gid=315005175) exists in Google Sheets which has been used by W3C and Studio 24 to help plan the web pages for the new w3.org website. 4 | 5 | ## Symfony 6 | 7 | Routes are setup in Symfony using [annotations](https://symfony.com/doc/current/routing.html#creating-routes-as-attributes-or-annotations) 8 | in controllers (stored in `src/Controller`). 9 | 10 | ## Localisation 11 | 12 | Locale prefixes are setup in the [annotation config](config/routes/annotations.yaml) file. The following locales are setup: 13 | * /fr - French 14 | * /ja - Japanese 15 | * /zh-hans - Simplified Chinese 16 | * default - American English 17 | 18 | All non-locale page URLs below can be served in a locale by prefixing with the locale URL prefix, e.g. `/ja/news`. 19 | 20 | ## Root URLs served by Symfony application 21 | 22 | Given a wide range of pages are managed at w3.org not all URLs are directed to the Symfony application. Therefore it's 23 | important to be aware there are a limited number of root URLs served by the Symfony application. These are: 24 | 25 | * /about 26 | * /ecosystems 27 | * /agreements 28 | * /blog 29 | * /careers 30 | * /contact 31 | * /copyright 32 | * /donate 33 | * /evangelists 34 | * /events 35 | * /feeds 36 | * /fr 37 | * /get-involved 38 | * /help 39 | * /in-the-media 40 | * /ja 41 | * /liaisons 42 | * /membership 43 | * /news 44 | * /newsletter 45 | * /policies 46 | * /press-releases 47 | * /resources 48 | * /sponsor 49 | * /staff 50 | * /standards 51 | * /zh-hans 52 | 53 | _Future improvement: look at extracting this URL list into a config file/plugin in CraftCMS so we can highlight to users when they attempt to 54 | add a root URL that is not served by the Symfony application._ -------------------------------------------------------------------------------- /src/Query/CraftCMS/Ecosystems/Testimonials.php: -------------------------------------------------------------------------------- 1 | setGraphQLFromFile(__DIR__ . '/../graphql/ecosystems/testimonials.graphql') 31 | ->setRootPropertyPath('[entries]') 32 | ->addVariable('ecosystemId', $ecosystemId) 33 | ->cache($cacheLifetime) 34 | ->cacheTags(['testimonial']) 35 | ; 36 | 37 | $this->site = $site; 38 | } 39 | 40 | public function getMapping(): MappingStrategyInterface|array 41 | { 42 | 43 | return [ 44 | '[quote]' => '[quote]', 45 | '[author]' => '[author]', 46 | '[authorJobTitle]' => '[authorJobTitle]', 47 | '[organization]' => '[organization]', 48 | '[logo]' => '[logo][0][url]', 49 | '[language]' => '[language]', 50 | '[text_direction]' => new CallableData([ $this, 'getLanguageDirection' ], '[language]'), 51 | ]; 52 | } 53 | 54 | public function getLanguageDirection(string $language_code) 55 | { 56 | return $this->site->getTextDirection($language_code); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/events/entry.graphql: -------------------------------------------------------------------------------- 1 | query Event($type: [QueryArgument], $start: [QueryArgument], $slug: [String], $site: [String]) { 2 | entry(eventType: $type, startDatetime: $start, slug: $slug, type: "events_default", site: $site, section: "events") { 3 | id 4 | uid 5 | status 6 | uri 7 | title 8 | slug 9 | language 10 | localized { 11 | title 12 | language_code: language 13 | slug 14 | } 15 | postDate 16 | dateUpdated 17 | expiryDate 18 | ... on events_default_Entry { 19 | excerpt: eventExcerpt 20 | thumbnailImage { 21 | ...thumbnailImage 22 | } 23 | thumbnailAltText 24 | start: startDatetime 25 | end: endDatetime 26 | tz: startDatetime@formatDateTime(format: "e") 27 | year: startDatetime@formatDateTime(format: "Y") 28 | speakers: speakersName 29 | location 30 | host 31 | website: relatedWebsite { 32 | name: websiteName 33 | url 34 | } 35 | type: eventType { 36 | id 37 | slug 38 | title 39 | } 40 | categories: blogCategories { 41 | title 42 | slug 43 | } 44 | tags: blogTags { 45 | title 46 | slug 47 | } 48 | groups: workingGroups { 49 | title 50 | slug 51 | } 52 | ecosystems { 53 | title 54 | slug 55 | } 56 | defaultFlexibleComponents(orderBy: "sortOrder") { 57 | ...defaultFlexibleComponents 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # In all environments, the following files are loaded if they exist, 2 | # the latter taking precedence over the former: 3 | # 4 | # * .env contains default values for the environment variables needed by the app 5 | # * .env.local uncommitted file with local overrides 6 | # * .env.$APP_ENV committed environment-specific defaults 7 | # * .env.$APP_ENV.local uncommitted environment-specific overrides 8 | # 9 | # Real environment variables win over .env files. 10 | # 11 | # DO NOT DEFINE PRODUCTION SECRETS IN THIS FILE NOR IN ANY OTHER COMMITTED FILES. 12 | # 13 | # Run "composer dump-env prod" to compile .env files for production use (requires symfony/flex >=1.2). 14 | # https://symfony.com/doc/current/best_practices.html#use-environment-variables-for-infrastructure-configuration 15 | 16 | ###> symfony/framework-bundle ### 17 | APP_SECRET=c0b4ccaa8aabfec0811dd9de599a375a 18 | TRUSTED_PROXIES=127.0.0.1 19 | ###< symfony/framework-bundle ### 20 | 21 | ###> symfony/mailer ### 22 | # MAILER_DSN=smtp://localhost 23 | ###< symfony/mailer ### 24 | ###> symfony/lock ### 25 | # Choose one of the stores below 26 | # postgresql+advisory://db_user:db_password@localhost/db_name 27 | LOCK_DSN=semaphore 28 | ###< symfony/lock ### 29 | 30 | ###> nelmio/cors-bundle ### 31 | CORS_ALLOW_ORIGIN='*' 32 | ###< nelmio/cors-bundle ### 33 | 34 | # Dev settings are detailed below, please override in .env.$APP_ENV or .env.local (for secrets) 35 | # Please set APP_ENV application environment in your .env.local file 36 | 37 | APP_DEFAULT_LOCALE=en 38 | APP_URL=https://www-dev.w3.org 39 | 40 | # Craft CMS API URL 41 | CRAFTCMS_API_URL=https://cms-dev.w3.org/api 42 | 43 | # W3C API URL 44 | W3C_API_URL=https://api.w3.org/ 45 | 46 | # Craft CMS API tokens 47 | CRAFTCMS_API_READ_TOKEN= 48 | CRAFTCMS_API_PUBLISH_TOKEN= 49 | 50 | CACHE_ENABLE=0 # don't cache Strata results 51 | 52 | ENABLE_DEV_PROFILER=true 53 | 54 | ASSETS_WEBSITE_2021=/assets/website-2021/ 55 | -------------------------------------------------------------------------------- /templates/events/index.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/listing.html.twig' %} 2 | 3 | {% set extra_main_attributes = 'data-anchor="no"' %} 4 | 5 | {% block filters %} 6 | {{ include('@W3CWebsiteTemplates/components/listings/events/filters.html.twig') }} 7 | {% endblock %} 8 | 9 | {% block list %} 10 |
11 | {{ 'pagination.summary'|trans({ 12 | 'page': entries.pagination.page, 13 | 'from': entries.pagination.from, 14 | 'to': entries.pagination.to 15 | }, 'w3c_website_templates_bundle') }} 16 |
17 | 18 | {{ include('@W3CWebsiteTemplates/components/listings/events/list.html.twig') }} 19 | {% endblock %} 20 | 21 | {% block pre_footer %} 22 | {% embed '@W3CWebsiteTemplates/components/pre-footer.html.twig' %} 23 | {% block content %} 24 | {% include '@W3CWebsiteTemplates/components/styles/feed.html.twig' with { 'feed_url': path('app_feed_events'), 'feed_type': 'events' } %} 25 | 26 | {% if archives is defined and archives|length > 0 %} 27 | 40 | {% endif %} 41 | {% endblock %} 42 | {% endembed %} 43 | {% endblock %} 44 | -------------------------------------------------------------------------------- /src/Controller/StaffController.php: -------------------------------------------------------------------------------- 1 | add('alumni-listing', new AlumniListing($site->siteHandle)); 34 | 35 | $collection = $manager->getCollection('alumni-listing'); 36 | $page = $manager->get('alumni-listing', '[entry]'); 37 | 38 | if ($this->getParameter('kernel.environment') == 'dev') { 39 | dump($page); 40 | dump($collection); 41 | } 42 | 43 | $singlesBreadcrumbs = $manager->get('singles-breadcrumbs'); 44 | $page['breadcrumbs'] = [ 45 | 'title' => $page['title'], 46 | 'url' => $this->generateUrl('app_staff_alumni'), 47 | 'parent' => [ 48 | 'title' => 'Staff', 49 | 'url' => '/staff/', 50 | 'parent' => $singlesBreadcrumbs['homepage'] 51 | ] 52 | ]; 53 | 54 | return $this->render('staff/alumni.html.twig', [ 55 | 'site' => $site, 56 | 'page' => $page, 57 | 'alumni' => $collection, 58 | ]); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /docs/testing.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | This application relies on PHPUnit along with Panther for application tests. 4 | 5 | ## Panther 6 | 7 | [Panther](https://github.com/symfony/panther) is a library to scrape websites and to run end-to-end tests using real 8 | browsers (and so executes Javascript and CSS). 9 | It is based on the same API as [Symfony's WebTestCase](https://symfony.com/doc/current/testing.html#application-tests) 10 | and as such is fully compatible with it, with some additional functions described in its documentation. 11 | 12 | Using it requires [installing either chromedriver or geckodriver](https://github.com/symfony/panther#installing-chromedriver-and-geckodriver) 13 | on the testing platform. For this we don't use the Composer alternative as it tends to have issues with the versions it 14 | installs and recommend using the OS' package manager. 15 | 16 | Basic usage requires for the test class to extend `PantherTestCase` and create a Panther client. 17 | 18 | For more flexibility we can use the `PANTHER_BROWSER` environment variable to choose which driver to use (`chrome` or 19 | `firefox`). This variable is not a part of Panther itself but allows testing the application against different browsers 20 | on-the-fly. 21 | 22 | Setting the `PANTHER_ERROR_SCREENSHOT_DIR` environment variable allows Panther to take screenshots when a test fails. 23 | 24 | Setting the `PANTHER_NO_HEADLESS` environment variable will display the browser window, which can be useful to debug. 25 | 26 | Here is some sample code of a test class using Panther: 27 | ```php 28 | use Symfony\Component\Panther\PantherTestCase; 29 | 30 | class DefaultControllerTest extends PantherTestCase 31 | { 32 | protected $client; 33 | 34 | public function setUp(): void 35 | { 36 | $browser = array_key_exists('PANTHER_BROWSER', $_SERVER) ? $_SERVER['PANTHER_BROWSER'] : self::CHROME; 37 | $this->client = static::createPantherClient(['browser' => $browser]); 38 | } 39 | 40 | public function testIndex(): void 41 | { 42 | $crawler = $this->client->request('GET', '/'); 43 | $this->assertSelectorExists('body'); 44 | } 45 | } 46 | ``` 47 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/page.graphql: -------------------------------------------------------------------------------- 1 | query Page($uri: [String], $site: [String]) { 2 | entry(uri: $uri, site: $site) { 3 | id 4 | typeHandle 5 | status 6 | uri 7 | title 8 | language 9 | localized { 10 | title 11 | language_code: language 12 | uri 13 | } 14 | postDate 15 | dateUpdated 16 | expiryDate 17 | breadcrumbs: parent { 18 | ...breadcrumbs 19 | } 20 | ... on pages_default_Entry { 21 | displayOnThisPageNavigation 22 | siblingNavigation: parent { 23 | siblings: children { 24 | title 25 | uri 26 | } 27 | } 28 | defaultFlexibleComponents(orderBy: "sortOrder") { 29 | ...defaultFlexibleComponents 30 | } 31 | excerpt 32 | thumbnailImage { 33 | ...thumbnailImage 34 | } 35 | thumbnailAltText 36 | } 37 | ... on landingPage_Entry { 38 | pageLead 39 | heroIllustration { 40 | url 41 | mimeType 42 | height 43 | width 44 | size 45 | } 46 | landingFlexibleComponents(orderBy: "sortOrder") { 47 | ...landingFlexibleComponents 48 | } 49 | excerpt 50 | thumbnailImage { 51 | ...thumbnailImage 52 | } 53 | thumbnailAltText 54 | } 55 | ... on ecosystemsLandingPage_Entry { 56 | pageLead 57 | heroIllustration { 58 | url 59 | mimeType 60 | height 61 | width 62 | size 63 | } 64 | landingFlexibleComponents(orderBy: "sortOrder") { 65 | ...landingFlexibleComponents 66 | } 67 | excerpt 68 | thumbnailImage { 69 | ...thumbnailImage 70 | } 71 | thumbnailAltText 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Form/CommentType.php: -------------------------------------------------------------------------------- 1 | add('comment', TextareaType::class, [ 21 | 'label' => 'blog.comments.form.comment.label', 22 | 'help' => 'blog.comments.form.comment.help', 23 | 'constraints' => [new NotBlank(['message' => 'blog.comments.form.comment.blank'])], 24 | ]) 25 | ->add('name', TextType::class, [ 26 | 'label' => 'blog.comments.form.name.label', 27 | 'constraints' => [new NotBlank(['message' => 'blog.comments.form.name.blank'])], 28 | ]) 29 | ->add('email', EmailType::class, [ 30 | 'label' => 'blog.comments.form.email.label', 31 | 'required' => false, 32 | 'constraints' => [ 33 | new Email(['message' => 'blog.comments.form.email.format']) 34 | ], 35 | ]) 36 | ->add('post', HiddenType::class, [ 37 | 'constraints' => [new NotBlank(['message' => 'blog.comments.form.post.blank'])] 38 | ]) 39 | ->add('parent', HiddenType::class, ['required' => false]); 40 | } 41 | 42 | public function getBlockPrefix(): string 43 | { 44 | return ''; 45 | } 46 | 47 | public function configureOptions(OptionsResolver $resolver): void 48 | { 49 | $resolver->setDefaults([ 50 | 'csrf_protection' => false 51 | ]); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/blog/collection.graphql: -------------------------------------------------------------------------------- 1 | query BlogCollection($site: [String], $category: [QueryArgument], $tag: [QueryArgument], $before: String, $after: String, $search: String, $limit: Int, $offset: Int) { 2 | total: entryCount(section: "blogPosts", site: $site, blogCategories: $category, blogTags: $tag, before: $before, after: $after, search: $search) 3 | entries(section: "blogPosts", site: $site, blogCategories: $category, blogTags: $tag, before: $before, after: $after, search: $search, limit: $limit, offset: $offset, orderBy: "postDate desc") { 4 | id 5 | slug 6 | uri 7 | title 8 | ... on blogPosts_default_Entry { 9 | authors: blogAuthors { 10 | ... on author_Entry { 11 | name: authorName 12 | email: authorEmailAddress 13 | } 14 | } 15 | categories: blogCategories { 16 | slug 17 | title 18 | } 19 | tags: blogTags { 20 | title 21 | slug 22 | } 23 | date: postDate 24 | year: postDate@formatDateTime(format: "Y") 25 | excerpt 26 | thumbnailImage { 27 | ...thumbnailImage 28 | } 29 | thumbnailAltText 30 | } 31 | ... on blogPosts_importedEntries_Entry { 32 | authors: blogAuthors { 33 | ... on author_Entry { 34 | name: authorName 35 | email: authorEmailAddress 36 | } 37 | } 38 | categories: blogCategories { 39 | slug 40 | title 41 | } 42 | tags: blogTags { 43 | title 44 | slug 45 | } 46 | date: postDate 47 | year: postDate@formatDateTime(format: "Y") 48 | excerpt 49 | thumbnailImage { 50 | ...thumbnailImage 51 | } 52 | thumbnailAltText 53 | } 54 | } 55 | } 56 | 57 | fragment thumbnailImage on AssetInterface { 58 | url(transform: "size360x270") 59 | srcset(sizes: ["580w"]) 60 | } 61 | 62 | -------------------------------------------------------------------------------- /tests/Controller/DefaultControllerTest.php: -------------------------------------------------------------------------------- 1 | client = static::createPantherClient(['browser' => $browser]); 16 | } 17 | 18 | #[DataProvider('provider')] 19 | public function testIndex(string $lang, string $title, string $langPrefix, string $path): void 20 | { 21 | $this->client->request('GET', $langPrefix . $path); 22 | 23 | $this->assertSelectorAttributeContains('html', 'lang', $lang); 24 | $this->assertSelectorTextSame('h1', $title); 25 | } 26 | 27 | public static function provider() 28 | { 29 | return [ 30 | ['lang' => 'en', 'title' => 'Making the Web work', 'langPrefix' => '', 'path' => '/'], 31 | ['lang' => 'en', 'title' => 'Business Ecosystems', 'langPrefix' => '', 'path' => '/ecosystems/'], 32 | ['lang' => 'en', 'title' => 'Blog', 'langPrefix' => '', 'path' => '/blog/'], 33 | //['lang' => 'ja', 'title' => 'W3C Home', 'langPrefix' => '/ja', 'path' => '/'], 34 | //['lang' => 'ja', 'title' => '日本語で Landing Page', 'langPrefix' => '/ja', 'path' => '/landing-page/'], 35 | ['lang' => 'ja', 'title' => 'Blog listing', 'langPrefix' => '/ja', 'path' => '/blog/'] 36 | ]; 37 | } 38 | 39 | // See https://github.com/symfony/symfony/issues/53812 40 | protected function restoreExceptionHandler(): void 41 | { 42 | while (true) { 43 | $previousHandler = set_exception_handler(static fn() => null); 44 | 45 | restore_exception_handler(); 46 | 47 | if ($previousHandler === null) { 48 | break; 49 | } 50 | 51 | restore_exception_handler(); 52 | } 53 | } 54 | 55 | protected function tearDown(): void 56 | { 57 | parent::tearDown(); 58 | 59 | $this->restoreExceptionHandler(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/news/entry.graphql: -------------------------------------------------------------------------------- 1 | query NewsArticle($year: [String], $slug: [String], $site: [String]) { 2 | entry(postDate: $year, slug: $slug, site: $site, section: "newsArticles") { 3 | id 4 | status 5 | uri 6 | title 7 | slug 8 | language 9 | localized { 10 | title 11 | language_code: language 12 | slug 13 | year: postDate@formatDateTime(format: "Y") 14 | } 15 | postDate 16 | dateUpdated 17 | expiryDate 18 | ... on newsArticles_default_Entry { 19 | excerpt 20 | thumbnailImage { 21 | ...thumbnailImage 22 | } 23 | thumbnailAltText 24 | displayYouMayAlsoLikeListing 25 | groups: workingGroups { 26 | title 27 | slug 28 | ...on groups_Category { 29 | url: urlLink 30 | } 31 | } 32 | specifications { 33 | title 34 | slug 35 | ...on specifications_Category { 36 | url: urlLink 37 | } 38 | } 39 | ecosystems { 40 | title 41 | slug 42 | } 43 | defaultFlexibleComponents(orderBy: "sortOrder") { 44 | ...defaultFlexibleComponents 45 | } 46 | notes: postPageNotes 47 | } 48 | ... on newsArticles_importedEntries_Entry { 49 | excerpt 50 | thumbnailImage { 51 | ...thumbnailImage 52 | } 53 | thumbnailAltText 54 | groups: workingGroups { 55 | title 56 | slug 57 | ...on groups_Category { 58 | url: urlLink 59 | } 60 | } 61 | specifications { 62 | title 63 | slug 64 | ...on specifications_Category { 65 | url: urlLink 66 | } 67 | } 68 | ecosystems { 69 | title 70 | slug 71 | } 72 | pageContent 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/press-releases/entry.graphql: -------------------------------------------------------------------------------- 1 | query PressRelease($year: [String], $slug: [String], $site: [String]) { 2 | entry(postDate: $year, slug: $slug, site: $site, section: "pressReleases") { 3 | id 4 | status 5 | uri 6 | title 7 | slug 8 | language 9 | localized { 10 | title 11 | language_code: language 12 | slug 13 | year: postDate@formatDateTime(format: "Y") 14 | } 15 | postDate 16 | dateUpdated 17 | expiryDate 18 | ... on pressReleases_default_Entry { 19 | excerpt 20 | thumbnailImage { 21 | ...thumbnailImage 22 | } 23 | thumbnailAltText 24 | displayYouMayAlsoLikeListing 25 | groups: workingGroups { 26 | title 27 | slug 28 | ...on groups_Category { 29 | url: urlLink 30 | } 31 | } 32 | specifications { 33 | title 34 | slug 35 | ...on specifications_Category { 36 | url: urlLink 37 | } 38 | } 39 | ecosystems { 40 | title 41 | slug 42 | } 43 | defaultFlexibleComponents(orderBy: "sortOrder") { 44 | ...defaultFlexibleComponents 45 | } 46 | notes: postPageNotes 47 | } 48 | ... on importedEntries_Entry { 49 | excerpt 50 | thumbnailImage { 51 | ...thumbnailImage 52 | } 53 | thumbnailAltText 54 | groups: workingGroups { 55 | title 56 | slug 57 | ...on groups_Category { 58 | url: urlLink 59 | } 60 | } 61 | specifications { 62 | title 63 | slug 64 | ...on specifications_Category { 65 | url: urlLink 66 | } 67 | } 68 | ecosystems { 69 | title 70 | slug 71 | } 72 | pageContent 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /templates/blog/show.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/post.html.twig' %} 2 | {%- import "@W3CWebsiteTemplates/components/styles/notes.html.twig" as note_macros -%} 3 | 4 | {% set body_classes = 'post' %} 5 | {% if comments_open %} 6 | {% form_theme comment_form '@W3CWebsiteTemplates/form/theme.html.twig' %} 7 | {% set rendered_form %} 8 | {{ form_start(comment_form, {attr: {id: 'comment-form', name: 'comment-form', novalidate: true}}) }} 9 | {{ form_row(comment_form.comment) }} 10 |
11 |
12 |
13 | {{ form_row(comment_form.name) }} 14 |
15 |
16 | {{ form_row(comment_form.email) }} 17 |
18 |
19 |
20 |
21 | 24 | 25 | {{ 'blog.comments.form.cancel'|trans }} 26 | 27 |
28 | {{ form_rest(comment_form) }} 29 | {{ form_end(comment_form) }} 30 | {% endset %} 31 | {% endif %} 32 | 33 | {% block feed %} 34 | {% include '@W3CWebsiteTemplates/components/styles/feed.html.twig' with { 'feed_url': '/blog/feed', 'feed_type': 'blog' } %} 35 | {% endblock %} 36 | 37 | {% block comments %} 38 | {{ parent() }} 39 | {% embed '@W3CWebsiteTemplates/components/styles/comments.html.twig' with { 40 | 'post': page, 41 | 'comments': comments, 42 | 'comments_open': comments_open, 43 | 'rendered_form': rendered_form is defined ? rendered_form : null, 44 | 'reply_to': reply_to 45 | } %} 46 | {% endembed %} 47 | {% endblock %} 48 | 49 | {% block closing_body_scripts %} 50 | 55 | {% endblock %} 56 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Newsletters/Filters.php: -------------------------------------------------------------------------------- 1 | router = $router; 41 | $this->translator = $translator; 42 | $this->setGraphQLFromFile(__DIR__ . '/../graphql/newsletters/filters.graphql') 43 | ->addVariable('site', $siteHandle) 44 | ->cache($cacheLifetime) 45 | ; 46 | } 47 | 48 | public function getMapping(): MappingStrategyInterface|array 49 | { 50 | return [ 51 | '[archives]' => new CallableData([$this, 'transformArchives'], '[first][year]', '[last][year]') 52 | ]; 53 | } 54 | 55 | public function transformArchives(?string $first = null, ?string $last = null): array 56 | { 57 | if (!$first) { 58 | return []; 59 | } 60 | 61 | $archives = []; 62 | for ($year = $last; $year >= $first; $year--) { 63 | $archives[] = [ 64 | 'title' => $year, 65 | 'url' => $this->router->generate('app_newsletter_archive', ['year' => $year]) 66 | ]; 67 | } 68 | 69 | return $archives; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /docs/configuration.md: -------------------------------------------------------------------------------- 1 | # Configuration 2 | 3 | It's important to bear in mind this repository is public. Any sensitive variable needs to be excluded from code commits. 4 | 5 | ## Application parameters 6 | 7 | Store any application parameters (e.g. default number of posts per page) under the `parameters` key in the 8 | `config/services.yaml` file. It's best practise to prefix parameters with `app.` to avoid any clashes. 9 | 10 | E.g. 11 | 12 | ```yaml 13 | parameters: 14 | app.posts_per_page = 20 15 | ``` 16 | 17 | These can be accessed in controllers via: 18 | 19 | ``` 20 | $postsPerPage = $this->getParameter('app.posts_per_page'); 21 | ``` 22 | 23 | Also see [configuration parameters](https://symfony.com/doc/current/configuration.html#configuration-parameters). 24 | 25 | ## Environment variables 26 | 27 | The following application environments are supported: 28 | * `dev` (development - used to test functionality) 29 | * `staging` (staging - used for user acceptance testing) 30 | * `prod` (production) 31 | * `test` (used for unit testing) 32 | 33 | Store any environment variables that define infrastructure configuration (e.g. database DSN) to `.env` files. Symfony 34 | has a structured approach to loading env files, which is summarised below. 35 | 36 | Also see [configuring .env files](https://symfony.com/doc/current/configuration.html#config-dot-env). 37 | 38 | ### .env config files safe to commit to git 39 | 40 | * `.env` - environment variables for the application, shared across environments (or defaults to local development sedttings) 41 | * `.env.dev` - environment variables for staging environment, do not include sensitive data 42 | * `.env.staging` - environment variables for staging environment, do not include sensitive data 43 | * `.env.prod` - environment variables for production environment, do not include sensitive data 44 | * `.env.test` - environment variables for unit testing, do not include sensitive data 45 | * `.env.local.dist` - template for required local environment variables (not used by Symfony, do not contain sensitive data) 46 | 47 | ### .env config files which are local only, do not commit to git 48 | 49 | * `.env.local` - local environment settings for the current environment (e.g. database DSN) 50 | 51 | Please note any real environment variables override variables created with `.env` files. 52 | 53 | ### Accessing env variables 54 | 55 | -------------------------------------------------------------------------------- /src/Controller/FragmentsController.php: -------------------------------------------------------------------------------- 1 | add( 35 | 'navigation', 36 | new GlobalNavigation($router, $urlHelper, $site->siteHandle) 37 | ); 38 | $navigation = $manager->getCollection('navigation'); 39 | 40 | return $this->render( 41 | '@W3CWebsiteTemplates/components/styles/global_nav.html.twig', 42 | ['navigation' => $navigation] 43 | ); 44 | } 45 | 46 | #[Cache(expires: 'tomorrow', public: true)] 47 | #[Route(path: '/lang-nav/')] 48 | public function langNav(Site $site): Response 49 | { 50 | return $this->render( 51 | '@W3CWebsiteTemplates/components/styles/lang_nav.html.twig', 52 | ['site' => $site] 53 | ); 54 | } 55 | 56 | #[Cache(expires: 'tomorrow', public: true)] 57 | #[Route(path: '/footer/')] 58 | public function footer(): Response 59 | { 60 | return $this->render('@W3CWebsiteTemplates/components/styles/footer.html.twig'); 61 | } 62 | 63 | #[Cache(expires: 'tomorrow', public: true)] 64 | #[Route(path: '/common-head/')] 65 | public function commonHead(): Response 66 | { 67 | return $this->render('@W3CWebsiteTemplates/_common-head.html.twig'); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/fragments/landingFlexibleComponents.graphql: -------------------------------------------------------------------------------- 1 | fragment landingFlexibleComponents on landingFlexibleComponents_MatrixField { 2 | ... on textComponent_Entry { 3 | typeHandle 4 | contentField 5 | } 6 | ... on blockquoteComponent_Entry { 7 | typeHandle 8 | citation 9 | quoteText 10 | } 11 | ... on fiftyFiftyComponent_Entry { 12 | typeHandle 13 | showImageBeforeText 14 | image { 15 | ...contentImage 16 | } 17 | informativeOrDecorative 18 | altText 19 | titleField 20 | mainContent 21 | ctaUrl 22 | ctaCopy 23 | secondaryLinkUrl 24 | secondaryLinkCopy 25 | } 26 | ... on imagesAndDescriptionsComponent_Entry { 27 | typeHandle 28 | heading 29 | description 30 | cards { 31 | ... on cardsBlock_Entry { 32 | image { 33 | ...thumbnailImage 34 | } 35 | altText 36 | heading 37 | description 38 | linkDestination 39 | linkCopy 40 | } 41 | } 42 | } 43 | ... on iconsAndDescriptionsComponent_Entry { 44 | typeHandle 45 | heading 46 | description 47 | iconCards { 48 | ... on iconCardsBlock_Entry { 49 | icon { 50 | ... on icon_Icon { 51 | inline 52 | } 53 | } 54 | heading 55 | description 56 | linkDestination 57 | linkCopy 58 | } 59 | } 60 | } 61 | ... on imageMediaComponent_Entry { 62 | typeHandle 63 | imageMedia { 64 | ...contentImage 65 | } 66 | altText 67 | figureCaption 68 | informativeOrDecorative 69 | } 70 | ... on videoMediaComponent_Entry { 71 | typeHandle 72 | videoTitle 73 | videoUrl { 74 | url 75 | image 76 | imageWidth 77 | imageHeight 78 | code 79 | width 80 | height 81 | aspectRatio 82 | providerName 83 | } 84 | videoCaption 85 | linkToVideoTranscript 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /templates/strata-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | strata-mark only 4 | 5 | 15 | 16 | -------------------------------------------------------------------------------- /config/services.yaml: -------------------------------------------------------------------------------- 1 | # This file is the entry point to configure your own services. 2 | # Files in the packages/ subdirectory configure your dependencies. 3 | 4 | # Put parameters here that don't need to change on each machine where the app is deployed 5 | # https://symfony.com/doc/current/best_practices/configuration.html#application-related-configuration 6 | parameters: 7 | app.app_url: '%env(resolve:APP_URL)%' 8 | app.craftcms_api_publish_token: '%env(resolve:CRAFTCMS_API_PUBLISH_TOKEN)%' 9 | app.cacheEnable: '%env(default:app.cacheDefault:CACHE_ENABLE)%' 10 | app.cacheDefault: true 11 | 12 | services: 13 | # default configuration for services in *this* file 14 | _defaults: 15 | autowire: true # Automatically injects dependencies in your services. 16 | autoconfigure: true # Automatically registers your services as commands, event subscribers, etc. 17 | 18 | # makes classes in src/ available to be used as services 19 | # this creates a service per class whose id is the fully-qualified class name 20 | App\: 21 | resource: '../src/' 22 | exclude: 23 | - '../src/DependencyInjection/' 24 | - '../src/Entity/' 25 | - '../src/Kernel.php' 26 | - '../src/Tests/' 27 | - '../src/Service/StrataDataCollector.php' 28 | 29 | # controllers are imported separately to make sure services can be injected 30 | # as action arguments even if you don't extend any base controller class 31 | App\Controller\: 32 | resource: '../src/Controller/' 33 | tags: ['controller.service_arguments'] 34 | 35 | # add more service definitions when explicit configuration is needed 36 | # please note that last definitions always *replace* previous ones 37 | 38 | # W3C API connection 39 | App\Service\W3C: 40 | arguments: 41 | $baseUri: '%env(resolve:W3C_API_URL)%' 42 | 43 | # Craft CMS API connection 44 | App\Service\CraftCMS: 45 | arguments: 46 | $apiUrl: '%env(resolve:CRAFTCMS_API_URL)%' 47 | $apiKey: '%env(resolve:CRAFTCMS_API_READ_TOKEN)%' 48 | 49 | # Query manager to retrieve data from APIs 50 | strata.query_manager: 51 | class: Strata\Data\Query\QueryManager 52 | configurator: [ '@App\Service\QueryManagerConfigurator', 'configure' ] 53 | 54 | # Site object 55 | strata.site: 56 | class: Strata\Frontend\Site 57 | configurator: [ '@App\Service\SiteConfigurator', 'configure' ] 58 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/News/Filters.php: -------------------------------------------------------------------------------- 1 | router = $router; 41 | $this->translator = $translator; 42 | $this->setGraphQLFromFile(__DIR__ . '/../graphql/news/filters.graphql') 43 | ->addVariable('site', $siteHandle) 44 | ->cache($cacheLifetime) 45 | ; 46 | } 47 | 48 | public function getMapping(): MappingStrategyInterface|array 49 | { 50 | return [ 51 | '[archives]' => new CallableData([$this, 'transformArchives'], '[first][year]', '[last][year]') 52 | ]; 53 | } 54 | 55 | public function transformArchives(?string $first = null, ?string $last = null): array 56 | { 57 | if (!$first) { 58 | return []; 59 | } 60 | 61 | $archives = [ 62 | [ 63 | 'title' => $this->translator->trans('listing.news.filters.all', [], 'w3c_website_templates_bundle'), 64 | 'url' => $this->router->generate('app_news_index') 65 | ] 66 | ]; 67 | for ($year = $last; $year >= $first; $year--) { 68 | $archives[] = [ 69 | 'title' => $year, 70 | 'url' => $this->router->generate('app_news_archive', ['year' => $year]) 71 | ]; 72 | } 73 | 74 | return $archives; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /templates/pages/home.html.twig: -------------------------------------------------------------------------------- 1 | {% extends '@W3CWebsiteTemplates/pages/home.html.twig' %} 2 | 3 | {% block javascripts %} 4 | {{ parent() }} 5 | {% if members is not empty %} 6 | 9 | {% endif %} 10 | {% endblock %} 11 | 12 | {% block components %} 13 | {% if members%} 14 | {{ include('@W3CWebsiteTemplates/components/members--home.html.twig', { 15 | 'title': page.workingWithIndustryTitle, 16 | 'introduction': page.workingWithIndustryIntroduction, 17 | 'members': members, 18 | 'cta': page.workingWithIndustryCallToActionButton 19 | }) 20 | }} 21 | {% endif %} 22 | {% if page.homeFiftyFiftyComponents %} 23 | {% for fiftyFifty in page.homeFiftyFiftyComponents %} 24 | {{ include('@W3CWebsiteTemplates/components/fifty_fifty.html.twig', { 25 | 'order': fiftyFifty.showImageBeforeText, 26 | 'title': fiftyFifty.titleField, 27 | 'content': fiftyFifty.mainContent, 28 | 'link_main_url': fiftyFifty.ctaUrl is defined ? fiftyFifty.ctaUrl : null, 29 | 'link_main_text': fiftyFifty.ctaCopy is defined ? fiftyFifty.ctaCopy : null, 30 | 'link_secondary_url': fiftyFifty.secondaryLinkUrl is defined ? fiftyFifty.secondaryLinkUrl : null, 31 | 'link_secondary_text': fiftyFifty.secondaryLinkCopy is defined ? fiftyFifty.secondaryLinkCopy : null, 32 | 'image': fiftyFifty.image[0], 33 | 'alt': fiftyFifty.altText, 34 | 'informative_or_decorative': fiftyFifty.informativeOrDecorative }) 35 | }} 36 | {% endfor %} 37 | {% endif %} 38 | {% endblock %} 39 | 40 | {% block crosslinks %} 41 | {% if page.latestNewsFeaturedArticles %} 42 | {{ include('@W3CWebsiteTemplates/components/styles/crosslinks--home.html.twig', { 43 | 'crosslinks': { 44 | 'title': page.latestNewsTitle, 45 | 'text': page.latestNewsIntroduction, 46 | 'links': page.latestNewsFeaturedArticles 47 | } 48 | }) }} 49 | {% endif %} 50 | {% endblock %} 51 | 52 | {% block closing_body_scripts %} 53 | {% if members is not empty %} 54 | 59 | {% endif %} 60 | {% endblock %} 61 | -------------------------------------------------------------------------------- /docs/internationalization.md: -------------------------------------------------------------------------------- 1 | # Internationalization 2 | 3 | ## Setting up languages 4 | 5 | See [adding languages](adding-languages.md). 6 | 7 | ## Routing 8 | 9 | See [routing locales](routing.md#locale). 10 | 11 | ## Messages 12 | 13 | The application uses [Symfony Translation](https://symfony.com/doc/current/translation.html) to manage translated messages 14 | on the page (e.g. microcopy such as 'Displaying 100 results'). 15 | 16 | Translations are stored in `translations/` and use the [ICU message format](https://symfony.com/doc/current/translation/message_format.html). 17 | 18 | It's important for messages to have clear and understandable keywords that represent what the message is for. We can 19 | also use nested groups to help indicate where the content is used. 20 | 21 | E.g. 22 | 23 | ``` 24 | footer.copyright_message: Copyright © 2020 W3C ® 25 | ``` 26 | 27 | See https://symfony.com/doc/current/translation.html#using-real-or-keyword-messages 28 | 29 | ### Outputting messages in Twig 30 | 31 | Output message via the [trans](https://symfony.com/doc/current/reference/twig_reference.html#trans) filter: 32 | 33 | ```html 34 | { 'title'|trans }} 35 | ``` 36 | 37 | You can pass arguments to the translation via: 38 | 39 | ```html 40 |

{{ 'api_available.w3c'|trans({'available': w3c_available}) }}

41 | ``` 42 | 43 | Read more on [selecting different messages based on a condition](https://symfony.com/doc/current/translation/message_format.html#selecting-different-messages-based-on-a-condition). 44 | 45 | ## Messages from w3c/website-templates-bundle 46 | 47 | [w3c/website-templates-bundle](https://github.com/w3c/website-templates-bundle) also defines and uses some translatable 48 | strings for messages that are common to all templates. They are defined in a different translation domain named 49 | `w3c_website_templates_bundle` as recommended 50 | in [Symfony best practices](https://symfony.com/doc/current/bundles/best_practices.html#translation-files). 51 | Those strings are defined in the [bundle's translations directory](https://github.com/w3c/w3c-website-templates-bundle/tree/main/translations). 52 | 53 | To use those strings you need to pass the domain as a parameter of the trans filter or tag: 54 | ```html 55 | {{ 'w3c.description'|trans([], 'w3c_website_templates_bundle') }} 56 | ``` 57 | 58 | ### Outputting messages in JavaScript 59 | 60 | There is a need to display localized messages on the front-end via JavaScript. The translation of these messages is managed 61 | in the template bundle. Please refer to [the documentation on internationalization of the W3C template bundle](https://github.com/w3c/w3c-website-templates-bundle/blob/main/docs/internationalization/README.md). 62 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/PressReleases/Filters.php: -------------------------------------------------------------------------------- 1 | router = $router; 41 | $this->translator = $translator; 42 | $this->setGraphQLFromFile(__DIR__ . '/../graphql/press-releases/filters.graphql') 43 | ->addVariable('site', $siteHandle) 44 | ->cache($cacheLifetime) 45 | ; 46 | } 47 | 48 | public function getMapping(): MappingStrategyInterface|array 49 | { 50 | return [ 51 | '[archives]' => new CallableData([$this, 'transformArchives'], '[first][year]', '[last][year]') 52 | ]; 53 | } 54 | 55 | public function transformArchives(?string $first = null, ?string $last = null): array 56 | { 57 | if (!$first) { 58 | return []; 59 | } 60 | 61 | $archives = [ 62 | [ 63 | 'title' => $this->translator->trans( 64 | 'listing.press_releases.filters.all', 65 | [], 66 | 'w3c_website_templates_bundle' 67 | ), 68 | 'url' => $this->router->generate('app_pressreleases_index') 69 | ] 70 | ]; 71 | for ($year = $last; $year >= $first; $year--) { 72 | $archives[] = [ 73 | 'title' => $year, 74 | 'url' => $this->router->generate('app_pressreleases_archive', ['year' => $year]) 75 | ]; 76 | } 77 | 78 | return $archives; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Newsletters/Collection.php: -------------------------------------------------------------------------------- 1 | router = $router; 47 | 48 | $this->setGraphQLFromFile(__DIR__ . '/../graphql/newsletters/collection.graphql') 49 | ->setRootPropertyPath('[entries]') 50 | ->setTotalResults('[total]') 51 | ->setResultsPerPage($limit) 52 | ->setCurrentPage($page) 53 | ->cacheTags(['newsletter']) 54 | ->addVariable('site', $siteHandle) 55 | ->addVariable('limit', $limit) 56 | ->addVariable('offset', ($page - 1) * $limit) 57 | ; 58 | 59 | if ($year) { 60 | $this->addVariable('year', ['and', '>=' . $year, '<' . ($year+1)]); 61 | } 62 | } 63 | 64 | public function getMapping(): MappingStrategyInterface|array 65 | { 66 | return [ 67 | '[title]' => '[title]', 68 | '[url]' => new CallableData([$this, 'transformUrl'], '[year]', '[month]', '[day]'), 69 | '[date]' => new DateTimeValue('[date]'), 70 | '[year]' => new IntegerValue('[year]'), 71 | '[month]' => new IntegerValue('[month]'), 72 | '[day]' => new IntegerValue('[day]') 73 | ]; 74 | } 75 | 76 | public function transformUrl(string $year, string $month, string $day): string 77 | { 78 | return $this->router->generate('app_newsletter_show', ['year' => $year, 'month' => $month, 'day' => $day]); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /docs/adding-languages.md: -------------------------------------------------------------------------------- 1 | # Adding languages 2 | 3 | We aim to use standard locale strings, based on the [ISO 639-1 alpha-2 language list](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) 4 | and any required script and region strings. Since locales are used in URLs, we use `-` instead of `_` and lower-case for clean URLs. 5 | 6 | For example French is `fr`, Portuguese - Brazil is `pt-br` 7 | 8 | Below we'll use the character `{lc}` to refer to the locale code. 9 | 10 | ## Craft CMS 11 | 12 | You need to setup a site in Craft with the correct locale language. Make a note of the site ID since we need to set 13 | this in the frontend app. 14 | 15 | Please refer to the [instructions for adding a new language in Craft in the W3C website Craft repo](https://github.com/w3c/w3c-website-craft/blob/main/docs/new_language.md) 16 | 17 | ## Routing 18 | 19 | Add the language URL prefix to [config/routes/annotations.yml](../config/routes/annotations.yaml) in 20 | `controllers.prefix` in the format: 21 | 22 | ```yaml 23 | {lc}: '/{lc}' 24 | ``` 25 | 26 | E.g. for French: 27 | 28 | ```yaml 29 | fr: '/fr' 30 | ``` 31 | 32 | See [Internationalized routing](https://symfony.com/blog/new-in-symfony-4-1-internationalized-routing). 33 | 34 | ## Hosting platform routing 35 | 36 | Please note only fixed prefixes are routed from w3.org to the Symfony app. To ensure the new language route points to 37 | the Symfony frontend web app ... 38 | 39 | TODO - W3C 40 | 41 | ## Frontend site setup 42 | 43 | Edit the `src/Service/SiteConfigurator.php` file and add the locale along with the CraftCMS site handle. This helps auto-set the 44 | correct site handle when retrieving content. It also helps set whether content is left-to-right (default) or right-to-left. 45 | 46 | In the format: 47 | 48 | ```php 49 | $site->addLocale('{lc}', ['siteHandle' => {handle}]); 50 | ``` 51 | 52 | For example, to add German: 53 | 54 | ```php 55 | $site->addLocale('de', ['siteHandle' => 'german']); 56 | ``` 57 | 58 | You can use `$site->addRtfLocale()` to add a right-to-left locale. For example, to add Arabic: 59 | 60 | ```php 61 | $site->addRtfLocale('ar', ['siteHandle' => 'arabic']); 62 | ``` 63 | 64 | ## Translation messages file 65 | 66 | Add a file named `translations/messages+intl-icu.{lc}.yaml`, copy and paste the contents of the English (`en`) file as the starting point. 67 | 68 | Do the same in the [template bundle codebase](https://github.com/w3c/w3c-website-templates-bundle). In the translations folder, add a file named 69 | `w3c_website_templates_bundle+intl-icu.{lc}.yaml`. Copy and past the contents of the English yaml file and replace the entry values with translations in the 70 | target language. 71 | 72 | Please note if a messages file does not exist the application defaults to English. 73 | 74 | See [translation messages](internationalization.md#messages). 75 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/home/page.graphql: -------------------------------------------------------------------------------- 1 | query Home($site: [String]) { 2 | entry(slug: "homepage", site: $site) { 3 | title 4 | postDate 5 | dateUpdated 6 | expiryDate 7 | ... on homepage_Entry { 8 | heroIllustration { 9 | url 10 | mimeType 11 | height 12 | width 13 | size 14 | } 15 | heroVideo { 16 | url 17 | } 18 | pageLead 19 | heroCopy 20 | heroCallToActionButton { 21 | buttonText 22 | url 23 | } 24 | workingWithIndustryTitle 25 | workingWithIndustryIntroduction 26 | workingWithIndustryCallToActionButton { 27 | buttonText 28 | url 29 | } 30 | homeFiftyFiftyComponents { 31 | ...on fiftyFiftyComponent_Entry { 32 | showImageBeforeText 33 | image { 34 | ...contentImage 35 | } 36 | informativeOrDecorative 37 | altText 38 | titleField 39 | mainContent 40 | ctaUrl 41 | ctaCopy 42 | } 43 | } 44 | latestNewsTitle 45 | latestNewsIntroduction 46 | latestNewsFeaturedArticles { 47 | sectionHandle 48 | slug 49 | title 50 | year: postDate@formatDateTime(format: "Y") 51 | ...on blogPosts_default_Entry { 52 | excerpt 53 | thumbnailImage { 54 | ...thumbnailImage 55 | } 56 | thumbnailAltText 57 | } 58 | ...on blogPosts_importedEntries_Entry { 59 | excerpt 60 | thumbnailImage { 61 | ...thumbnailImage 62 | } 63 | thumbnailAltText 64 | } 65 | ...on newsArticles_default_Entry { 66 | excerpt 67 | thumbnailImage { 68 | ...thumbnailImage 69 | } 70 | thumbnailAltText 71 | } 72 | ...on pressReleases_default_Entry { 73 | excerpt 74 | thumbnailImage { 75 | ...thumbnailImage 76 | } 77 | thumbnailAltText 78 | } 79 | } 80 | excerpt 81 | thumbnailImage { 82 | ...thumbnailImage 83 | } 84 | thumbnailAltText 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Home/RecentActivities.php: -------------------------------------------------------------------------------- 1 | router = $router; 37 | $this->setGraphQLFromFile(__DIR__ . '/../graphql/home/recent-activities.graphql') 38 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 39 | ->setRootPropertyPath('[recentEntries]') 40 | ->addVariable('site', $siteHandle) 41 | ->cache($cacheLifetime) 42 | ->cacheTags(['blogPosts', 'newsArticles', 'pressReleases']) 43 | ; 44 | } 45 | 46 | public function getMapping(): MappingStrategyInterface|array 47 | { 48 | return [ 49 | '[category]' => '[sectionHandle]', 50 | '[title]' => '[title]', 51 | '[text]' => '[excerpt]', 52 | '[img]' => '[thumbnailImage][0]', 53 | '[thumbnailAltText]' => '[thumbnailAltText]', 54 | '[url]' => new CallableData( 55 | [$this, 'transformEntryUri'], 56 | '[sectionHandle]', 57 | '[slug]', 58 | '[year]' 59 | ) 60 | ]; 61 | } 62 | 63 | public function transformEntryUri(string $sectionHandle, string $slug, string $year): ?string 64 | { 65 | switch ($sectionHandle) { 66 | case 'blogPosts': 67 | $route = 'app_blog_show'; 68 | break; 69 | case 'pressReleases': 70 | $route = 'app_pressreleases_show'; 71 | break; 72 | case 'newsArticles': 73 | $route = 'app_news_show'; 74 | break; 75 | default: 76 | return null; 77 | } 78 | 79 | return $this->router->generate($route, ['slug' => $slug, 'year' => $year]); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /docs/front-end-integration/templating.md: -------------------------------------------------------------------------------- 1 | # Twig templates 2 | 3 | ## Template bundle 4 | 5 | We include the [W3C Symfony template bundle](https://github.com/w3c/w3c-website-templates-bundle) in this project. The 6 | aim is to store all global page and component templates in the Symfony bundle so they can be re-used on different 7 | projects. 8 | 9 | On installation, front-end assets (CSS, JavaScript, fonts, etc.) will be copied into the public directory of the 10 | application. You will find them in the folder `public/bundles/w3cwebsitetemplates/dist/assets` 11 | 12 | _TODO: How are front-end asset files updated on composer update?_ 13 | 14 | The Symfony [Asset](https://symfony.com/doc/current/components/asset.html) component is setup to load assets from the 15 | `public/bundles/w3cwebsitetemplates/dist/assets` folder. This is setup in `config/packages/assets.yaml` 16 | 17 | ## Loading templates 18 | 19 | Symfony templates are stored in `templates/` 20 | 21 | To use templates from the bundle prefix with the handle `@W3CWebsiteTemplates`, for example: 22 | 23 | ```twig 24 | {% extends '@W3CWebsiteTemplates/base.html.twig' %} 25 | ``` 26 | 27 | ## Generating URLs 28 | 29 | See https://symfony.com/doc/current/routing.html#generating-urls 30 | 31 | TODO 32 | 33 | ## Working on the frontend and templates bundle at the same time 34 | 35 | If you need to make modifications to the templates bundle, it can be cumbersome to push changes and update dependencies 36 | every time you make a change. 37 | 38 | A simple workaround is to create a symlink to the bundle in the frontend's vendor directory. 39 | To do so, run the following command (replace `${BASE}` with the path to your local checkout of the templates bundle): 40 | ```shell 41 | composer install # download dependencies 42 | rm -rf vendor/w3c/website-templates-bundle # delete the template bundle's folder 43 | ln -s ${BASE}/w3c-website-templates-bundle vendor/w3c/website-templates-bundle # create the symlink to the bundle 44 | ``` 45 | 46 | For example if your template bundle is stored in `~/Sites/w3c/w3c-website-templates-bundle` the symlink command is: 47 | 48 | ```shell 49 | ln -s ~/Sites/w3c/w3c-website-templates-bundle vendor/w3c/website-templates-bundle 50 | ``` 51 | 52 | That will replace the folder `vendor/w3c/website-templates-bundle` with a symlink to your development version of the 53 | bundle. 54 | 55 | You can now make changes to the templates bundle and test them directly in the frontend. 56 | 57 | Then, when you finally push your changes to the templates bundle, you'll need to synchronize `composer.lock` with the 58 | new version for `composer install` to download the new version. To do so, run the following command (replace `${BASE}` with the path to your local checkout of the 59 | templates bundle): 60 | ```shell 61 | rm -rf vendor/w3c/website-templates-bundle # delete the symlink 62 | composer update # update dependencies 63 | rm -rf vendor/w3c/website-templates-bundle # delete the folder 64 | ln -s ${BASE}/w3c-website-templates-bundle vendor/w3c/website-templates-bundle # re-create the symlink to the bundle 65 | ``` 66 | These commands can be chained using `&&`. 67 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/graphql/blog/entry.graphql: -------------------------------------------------------------------------------- 1 | query BlogPost($year: [String], $slug: [String], $site: [String]) { 2 | entry(postDate: $year, slug: $slug, site: $site, section: "blogPosts") { 3 | id 4 | typeHandle 5 | siteId 6 | status 7 | uri 8 | title 9 | slug 10 | language 11 | localized { 12 | title 13 | language_code: language 14 | slug 15 | year: postDate@formatDateTime(format: "Y") 16 | } 17 | postDate 18 | dateUpdated 19 | expiryDate 20 | ... on blogPosts_default_Entry { 21 | excerpt 22 | thumbnailImage { 23 | ...thumbnailImage 24 | } 25 | thumbnailAltText 26 | displayYouMayAlsoLikeListing 27 | authors: blogAuthors { 28 | ... on author_Entry { 29 | name: authorName 30 | email: authorEmailAddress 31 | } 32 | } 33 | category: blogCategories { 34 | title 35 | slug 36 | } 37 | tags: blogTags { 38 | title 39 | slug 40 | } 41 | groups: workingGroups { 42 | title 43 | slug 44 | ...on groups_Category { 45 | url: urlLink 46 | } 47 | } 48 | specifications { 49 | title 50 | slug 51 | ...on specifications_Category { 52 | url: urlLink 53 | } 54 | } 55 | ecosystems { 56 | title 57 | slug 58 | } 59 | defaultFlexibleComponents(orderBy: "sortOrder") { 60 | ...defaultFlexibleComponents 61 | } 62 | notes: postPageNotes 63 | } 64 | ... on blogPosts_importedEntries_Entry { 65 | excerpt 66 | thumbnailImage { 67 | ...thumbnailImage 68 | } 69 | thumbnailAltText 70 | authors: blogAuthors { 71 | ... on author_Entry { 72 | name: authorName 73 | email: authorEmailAddress 74 | } 75 | } 76 | category: blogCategories { 77 | title 78 | slug 79 | } 80 | tags: blogTags { 81 | title 82 | slug 83 | } 84 | groups: workingGroups { 85 | title 86 | slug 87 | ...on groups_Category { 88 | url: urlLink 89 | } 90 | } 91 | specifications { 92 | title 93 | slug 94 | } 95 | ecosystems { 96 | title 97 | slug 98 | } 99 | pageContent 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Query/CraftCMS/Ecosystems/Ecosystem.php: -------------------------------------------------------------------------------- 1 | router = $router; 46 | $this->setGraphQLFromFile(__DIR__ . '/../graphql/ecosystems/ecosystem.graphql') 47 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/ecosystemsFlexibleComponents.graphql') 48 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/ecosystemsBottomFlexibleComponents.graphql') 49 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/contentImage.graphql') 50 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/breadcrumbs.graphql') 51 | ->addFragmentFromFile(__DIR__ . '/../graphql/fragments/thumbnailImage.graphql') 52 | ->setRootPropertyPath('[entry]') 53 | ->addVariable('slug', $slug) 54 | ->addVariable('site', $siteHandle) 55 | ->cache($cacheLifetime) 56 | ->cacheTags(['ecosystems']) 57 | ; 58 | } 59 | 60 | public function getMapping(): MappingStrategyInterface|array 61 | { 62 | $mapping = new WildcardMappingStrategy(); 63 | $mapping->addMapping('heroIllustration', ['[heroIllustration]' => '[heroIllustration][0]']); 64 | $mapping->addMapping('ecosystem', ['[taxonomy-slug]' => '[ecosystem][0][slug]']); 65 | $mapping->addMapping('ecosystem', ['[taxonomy-id]' => '[ecosystem][0][id]']); 66 | $mapping->addMapping('localized', [ 67 | '[localized]' => new MapArray('[localized]', [ 68 | '[title]' => '[title]', 69 | '[language_code]' => '[language_code]', 70 | '[url]' => new CallableData([$this, 'transformLocalizedUrl'], '[language_code]', '[slug]') 71 | ]) 72 | ]); 73 | 74 | return $mapping; 75 | } 76 | 77 | public function transformLocalizedUrl(string $lang, string $slug) 78 | { 79 | return $this->router->generate('app_ecosystem_show', ['slug' => $slug, '_locale' => strtolower($lang)]); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /templates/partials/landingFlexibleComponents.html.twig: -------------------------------------------------------------------------------- 1 | {% if component.typeHandle == 'textComponent' %} 2 | {% embed '@W3CWebsiteTemplates/components/text.html.twig' %} 3 | {% block text %} 4 | {{ component.contentField|raw }} 5 | {% endblock %} 6 | {% endembed %} 7 | {% elseif component.typeHandle == 'blockquoteComponent' %} 8 | {% embed '@W3CWebsiteTemplates/components/quote.html.twig' with {'author': component.citation} %} 9 | {% block text %} 10 | {{ component.quoteText }} 11 | {% endblock %} 12 | {% endembed %} 13 | {% elseif component.typeHandle == 'imageMediaComponent' %} 14 | {% embed '@W3CWebsiteTemplates/components/image.html.twig' with { 15 | 'attr': component.imageMedia[0], 16 | 'alt': component.altText, 17 | 'caption': component.figureCaption is defined ? component.figureCaption : null, 18 | 'informative_or_decorative': component.informativeOrDecorative 19 | } %} 20 | {% endembed %} 21 | {% elseif component.typeHandle == 'videoMediaComponent' %} 22 | {% if component.videoUrl.code is defined and component.videoUrl.code %} 23 | {% set params = {'iframe': component.videoUrl.code} %} 24 | {% else %} 25 | {% set params = {'url': component.videoUrl.url} %} 26 | {% endif %} 27 | {% set params = params | merge({ 28 | 'title': component.videoTitle, 29 | 'caption': component.videoCaption is defined ? component.videoCaption : null, 30 | 'transcript': component.linkToVideoTranscript is defined ? component.linkToVideoTranscript : null 31 | }) %} 32 | {% embed '@W3CWebsiteTemplates/components/video.html.twig' with params %} 33 | {% endembed %} 34 | {% elseif component.typeHandle == 'fiftyFiftyComponent' %} 35 | {% embed '@W3CWebsiteTemplates/components/fifty_fifty.html.twig' with { 36 | 'order': component.showImageBeforeText, 37 | 'title': component.titleField, 38 | 'content': component.mainContent, 39 | 'link_main_url': component.ctaUrl is defined ? component.ctaUrl : null, 40 | 'link_main_text': component.ctaCopy is defined ? component.ctaCopy : null, 41 | 'link_secondary_url': component.secondaryLinkUrl is defined ? component.secondaryLinkUrl : null, 42 | 'link_secondary_text': component.secondaryLinkCopy is defined ? component.secondaryLinkCopy : null, 43 | 'image': component.image[0], 44 | 'alt': component.altText, 45 | 'informative_or_decorative': component.informativeOrDecorative 46 | } %} 47 | {% endembed %} 48 | {% elseif component.typeHandle == 'iconsAndDescriptionsComponent' %} 49 | {% embed '@W3CWebsiteTemplates/components/columns_with_icons.html.twig' with { 50 | 'title': component.heading is defined ? component.heading : null, 51 | 'summary': component.description is defined ? component.description : null, 52 | 'cards': component.iconCards 53 | } %} 54 | {% endembed %} 55 | {% elseif component.typeHandle == 'imagesAndDescriptionsComponent' %} 56 | {% embed '@W3CWebsiteTemplates/components/columns_with_images.html.twig' with { 57 | 'title': component.heading is defined ? component.heading : null, 58 | 'summary': component.description is defined ? component.description : null, 59 | 'cards': component.cards 60 | } %} 61 | {% endembed %} 62 | {% endif %} 63 | -------------------------------------------------------------------------------- /templates/partials/ecosystemsFlexibleComponents.html.twig: -------------------------------------------------------------------------------- 1 | {% if component.typeHandle == 'textComponent' %} 2 | {% embed '@W3CWebsiteTemplates/components/text.html.twig' %} 3 | {% block text %} 4 | {{ component.contentField|raw }} 5 | {% endblock %} 6 | {% endembed %} 7 | {% elseif component.typeHandle == 'blockquoteComponent' %} 8 | {% embed '@W3CWebsiteTemplates/components/quote.html.twig' with {'author': component.citation} %} 9 | {% block text %} 10 | {{ component.quoteText }} 11 | {% endblock %} 12 | {% endembed %} 13 | {% elseif component.typeHandle == 'imageMediaComponent' %} 14 | {% embed '@W3CWebsiteTemplates/components/image.html.twig' with { 15 | 'attr': component.imageMedia[0], 16 | 'alt': component.altText, 17 | 'caption': component.figureCaption is defined ? component.figureCaption : null, 18 | 'informative_or_decorative': component.informativeOrDecorative 19 | } %} 20 | {% endembed %} 21 | {% elseif component.typeHandle == 'videoMediaComponent' %} 22 | {% if component.videoUrl.code is defined and component.videoUrl.code %} 23 | {% set params = {'iframe': component.videoUrl.code} %} 24 | {% else %} 25 | {% set params = {'url': component.videoUrl.url} %} 26 | {% endif %} 27 | {% set params = params | merge({ 28 | 'title': component.videoTitle, 29 | 'caption': component.videoCaption is defined ? component.videoCaption : null, 30 | 'transcript': component.linkToVideoTranscript is defined ? component.linkToVideoTranscript : null 31 | }) %} 32 | {% embed '@W3CWebsiteTemplates/components/video.html.twig' with params %} 33 | {% endembed %} 34 | {% elseif component.typeHandle == 'fiftyFiftyComponent' %} 35 | {% embed '@W3CWebsiteTemplates/components/fifty_fifty.html.twig' with { 36 | 'order': component.showImageBeforeText, 37 | 'title': component.titleField, 38 | 'content': component.mainContent, 39 | 'link_main_url': component.ctaUrl is defined ? component.ctaUrl : null, 40 | 'link_main_text': component.ctaCopy is defined ? component.ctaCopy : null, 41 | 'link_secondary_url': component.secondaryLinkUrl is defined ? component.secondaryLinkUrl : null, 42 | 'link_secondary_text': component.secondaryLinkCopy is defined ? component.secondaryLinkCopy : null, 43 | 'image': component.image[0], 44 | 'alt': component.altText, 45 | 'informative_or_decorative': component.informativeOrDecorative 46 | } %} 47 | {% endembed %} 48 | {% elseif component.typeHandle == 'groupsListingComponent' %} 49 | {% embed '@W3CWebsiteTemplates/components/groups_list.html.twig' with { 50 | 'list_title': 'components.groups_list.title'|trans({}), 51 | 'list_introduction': component.introduction is defined ? component.introduction : null, 52 | 'groups': page.groups is defined ? page.groups : [] 53 | } %} 54 | {% endembed %} 55 | {% elseif component.typeHandle == 'featuredEvangelistsComponent' %} 56 | {% embed '@W3CWebsiteTemplates/components/evangelists.html.twig' with { 57 | 'title': component.title, 58 | 'introduction': component.introduction is defined ? component.introduction : null, 59 | 'evangelists': page.evangelists is defined ? page.evangelists : [], 60 | 'ecosystem_name': page.title 61 | } %} 62 | {% endembed %} 63 | {% endif %} 64 | --------------------------------------------------------------------------------