{"id":50381402,"url":"https://github.com/parisek/custom-components","last_synced_at":"2026-05-30T12:02:34.611Z","repository":{"id":360057404,"uuid":"1248344575","full_name":"parisek/custom-components","owner":"parisek","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-24T20:30:36.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-24T21:09:50.665Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/parisek.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-24T14:19:24.000Z","updated_at":"2026-05-24T19:26:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/parisek/custom-components","commit_stats":null,"previous_names":["parisek/custom-components"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/parisek/custom-components","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parisek%2Fcustom-components","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parisek%2Fcustom-components/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parisek%2Fcustom-components/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parisek%2Fcustom-components/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/parisek","download_url":"https://codeload.github.com/parisek/custom-components/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/parisek%2Fcustom-components/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33691312,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-30T12:02:33.862Z","updated_at":"2026-05-30T12:02:34.599Z","avatar_url":"https://github.com/parisek.png","language":"PHP","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Custom Components\n\n[![CI](https://github.com/parisek/custom-components/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/parisek/custom-components/actions/workflows/ci.yml)\n[![Drupal](https://img.shields.io/badge/Drupal-10%20%7C%2011-0678BE?logo=drupal\u0026logoColor=white)](https://www.drupal.org)\n[![PHPStan](https://img.shields.io/badge/PHPStan-level%205-2ecc40)](https://phpstan.org/)\n[![Coverage](https://img.shields.io/badge/Coverage-78%25-2ecc40)](.github/workflows/ci.yml)\n[![License: GPL-2.0-or-later](https://img.shields.io/badge/License-GPL--2.0--or--later-blue.svg)](https://spdx.org/licenses/GPL-2.0-or-later.html)\n\nBase library module for [Drupal](https://www.drupal.org) sites built on the **PORTA** component pattern. Provides shared infrastructure (services, base classes, [Twig](https://twig.symfony.com/) extensions, image resizer) reused across projects.\n\nRequires [PHP 8.3+](https://www.php.net/releases/8.3/) and [Drupal](https://www.drupal.org/about/10) 10 or 11.\n\n## What this module provides\n\n**Services**\n\n- `custom_components.entity_helper` — high-level entity loading and rendering helpers consumed by display plugins and Twig templates.\n- `Drupal\\custom_components\\Services\\Resizer` — static utility: image style + focal point + responsive variant generator. Call `Resizer::resizer($images, $variants)` directly.\n- `custom_components.menu_active_trail_resolver` — resolves the active menu trail accounting for entity references and aliases.\n- `custom_components.twig_extension` — registers Twig functions used by component templates.\n- `custom_components.typography_twig_extension` — provides the `|typography` Twig filter; delegates to [`parisek/twig-typography`](https://github.com/parisek/twig-typography) and resolves typography config from `{active_theme}/static/typography.yml`.\n- `custom_components.route_subscriber` — alters routes for entity access edge cases.\n\n**Base classes**\n\n- `Drupal\\custom_components\\ComponentBase` — base for component [block plugins](https://www.drupal.org/docs/drupal-apis/block-api/block-api-overview).\n- `Drupal\\custom_components\\DisplayBase` — base for [`extra_field`](https://www.drupal.org/project/extra_field) display plugins that render components.\n\n**Filters**\n\n`FilterImage`, `FilterLinks`, `FilterTable`, `FilterTypography`, `FilterYoutube` — [text format filters](https://www.drupal.org/docs/drupal-apis/filter-api/overview) that normalize editor output into PORTA's component shape.\n\n## Required modules\n\nPulled automatically by Composer when you install:\n\n- [`drupal/components`](https://www.drupal.org/project/components) — Twig component discovery (`@component/` namespace).\n- [`drupal/config_pages`](https://www.drupal.org/project/config_pages) — single-instance config entities for site-wide content.\n- [`drupal/extra_field`](https://www.drupal.org/project/extra_field) — extra field display plugins on entities.\n- [`drupal/twig_real_content`](https://www.drupal.org/project/twig_real_content) — Twig filter to extract plain text from render arrays.\n- [`drupal/twig_tweak`](https://www.drupal.org/project/twig_tweak) — collection of helpful Twig extensions.\n- [`parisek/twig-typography`](https://github.com/parisek/twig-typography) — upstream typography filter (powers `|typography`).\n\n## Optional integrations\n\nThe following modules are optional. When present, `EntityHelper` automatically exposes additional fields and renderers; when absent, those code paths gracefully no-op.\n\nContrib (install via Composer):\n\n- [`drupal/commerce`](https://www.drupal.org/project/commerce) — `commerce_product` entity support.\n- [`drupal/office_hours`](https://www.drupal.org/project/office_hours) — `office_hours` field rendering.\n\nDrupal core (enable via `drush en …`):\n\n- [`comment`](https://www.drupal.org/docs/8/core/modules/comment) — comment entity support (`comment_body` field on Comment entities).\n\nDrupal core patches (apply via [`cweagans/composer-patches`](https://github.com/cweagans/composer-patches)):\n\n- [drupal.org#2466553](https://www.drupal.org/project/drupal/issues/2466553) — adds `menu.language_tree_manipulator` to Drupal core. When applied, `EntityHelper::getMenu()` filters menu links by the current content language. When absent, the filter step is silently skipped and menu items for all languages appear.\n\n## Local development\n\nLocal environment is [DDEV](https://ddev.com/) — pinned to PHP 8.3 in `.ddev/config.yaml` so it matches the production deploy target and CI. The database container is omitted; kernel tests use sqlite in-memory.\n\n```bash\nddev start\nddev composer install\nddev exec scripts/dev-link-module.sh   # symlink module into web/modules/contrib + bridge web/autoload.php\nddev exec vendor/bin/phpunit\n```\n\n`scripts/dev-link-module.sh` resolves paths relative to where it runs, so it must be invoked inside the container — otherwise the symlinks point to host paths the container can't see.\n\n### Tests\n\nTests are self-contained — Composer scaffolds Drupal via [`installer-paths`](https://github.com/composer/installers); PHPUnit bootstraps from `web/core/tests/bootstrap.php`.\n\n```bash\nddev exec vendor/bin/phpunit --testsuite unit\nddev exec vendor/bin/phpunit --testsuite kernel\n```\n\n### Coverage\n\n`ddev coverage` is a custom command (defined in `.ddev/commands/web/coverage`) that runs PHPUnit with `xdebug.mode=coverage` and emits both clover XML and a textual summary:\n\n```bash\nddev coverage\nddev coverage --filter ResizerTest   # any phpunit args pass through\n```\n\nCI uses the same flags so local + CI numbers stay aligned.\n\n### Step-debugging\n\n```bash\nddev xdebug on    # loads xdebug in debug mode; listen on host port 9003\nddev xdebug off   # debug mode is a heavy perf hit; keep it off by default\n```\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for how to add tests and the unit-vs-kernel decision tree.\n\n### Without DDEV\n\nDDEV is the canonical local environment, but the repo doesn't hard-depend on it — CI runs vanilla `composer install` + `vendor/bin/phpunit` against PHP 8.3 from the [shivammathur/setup-php](https://github.com/shivammathur/setup-php) GitHub Action. If you prefer host-PHP, ensure you're on PHP 8.3 (matching CI / production) to avoid composer.lock drift.\n\n## Related projects\n\nPart of the **PORTA** ecosystem:\n\n- [`parisek/twig-typography`](https://github.com/parisek/twig-typography) — framework-agnostic typography Twig extension that powers our `|typography` filter.\n\n## License\n\n[GPL-2.0-or-later](https://spdx.org/licenses/GPL-2.0-or-later.html). See [`LICENSE`](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparisek%2Fcustom-components","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fparisek%2Fcustom-components","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fparisek%2Fcustom-components/lists"}