SearchUnify Drupal Connector integrates the SearchUnify cognitive enterprise search platform with Drupal-powered websites using a configurable Drupal module. It supports global account configuration via UID, Provision Key, Endpoint URL, and CDN settings, secure token handling through the Key module, AI-powered NLP-based search, intelligent autocomplete, account-scoped search configuration, content indexing across disparate silos, user behavior insights, and responsive front-end search result display.
This module is different from existing Drupal search modules since it allows for global SearchUnify account configuration with the option to deliver personalized, contextual search results powered by AI and machine learning. This allows enterprises to unify content from multiple sources into a single intelligent search experience. The module also provides the option to filter and rank results based on NLP query context rather than just keywords.
Comments
Comment #2
aman.kumar2Comment #3
vishal.kadamComment #4
santerref commentedManual review of the 3.0.x branch, commit 5a03a9b.
The issue summary lists three claims that I could not confirm in the code. Since they are the security-relevant part of the summary, they should be corrected or implemented before this goes further.
drupal/keyentry in composer.json, nokeyentry in sudc.info.yml dependencies, and no use ofkey.repositoryin src/. The provision key and the access token are stored as plain strings in thesudc.configsconfig object, which means they are also written to any config export.SuResultController::getsearchjwt()puts the SearchUnify access token into the JWT payload, asaccess_tokeninbuildJwtPayload(). A JWT payload is base64url, not encrypted. The routesudc.getsearchjwtonly requires_user_is_logged_in, so any authenticated user, including a self-registered one, can call /search-unify/v1/search_jwt and base64-decode the site's SearchUnify access token out of the response. The token does not need to be in the payload for the signature to work, since it is already used as the HMAC key.SuConfigForm::buildForm()the Provision Key element is apasswordelement, but it also sets an#attributesarray whosevaluekey holds the stored provision key. That renders the secret in cleartext in thevalueattribute of the HTML source of /admin/config/sudc. The usual pattern is a password field left empty with a "leave blank to keep the current value" description, and a submit handler that keeps the stored value when the field is empty.config/schema/directory.config/install/sudc.configs.ymldefines uid, epoint, cdn, token_expiry, provision_key and access_token, andrepeater_dataandnum_fieldsare written at runtime, but none of them have a schema. Config schema is required.version: '3.0.1'andproject: 'sudc'. Both keys are added by the drupal.org packaging script and must not be in the repository."name": "grazitti/drupal-searchunify-connector". For a project hosted on drupal.org this has to bedrupal/sudc..gitlab-ci.ymlin the branch, so CI is not running and PHP_CodeSniffer results are not visible.css/all.min.css, which does not exist in the repository (css/only contains style.css), and it usesversion: VERSION, which is reserved for Drupal core modules.js/index.jsexists but is not attached by any library, so it is currently dead code..DS_Storeis committed twice, at the repository root and insrc/.SuResultController::getsearchjwt()line 257 uses\Drupal::service('private_key').CommonCalls::grazittiHeader()line 66 uses\Drupal::service('extension.list.module')in a class that already receives its dependencies through the container. AndSuHelpController::create()andSuResultController::create()both inject the result ofgetCurrentRequest()on the request stack. The Request object should not be stored on the controller: type-hint it on the page callback and Drupal passes it in, or injectrequest_stackitself.SuConfigForm::__construct()does not callparent::__construct(), so ConfigFormBase does not receive its ConfigFactoryInterface and, on Drupal 10.2 and later, its TypedConfigManagerInterface.buildForm()reads adfquery parameter, and?df=1renders the access token in a form field. If this is debugging code it should be removed.true,falseandnull, snake_case class properties such as$provision_keyand$token_expiry, and PEAR-style docblock tags (@category,@package,@author,@link,@since). Drupal uses 2 spaces, the brace on the same line, uppercase TRUE, FALSE and NULL, lowerCamelCase properties, and none of those tags. Note also that SuHelpController.php and SuConfigForm.php still carry the template placeholder author name and example email address, and that several docblocks link tohttp://graztti.com, which is both a typo and plain http.SuResultController::content()builds a render array fromsudc.configsbut attaches no cacheability metadata. It needs at least theconfig:sudc.configscache tag, otherwise the rendered search page will not be invalidated when the configuration changes.I did not test the module against a live SearchUnify instance, so the first three points are based on reading the code only. Happy to re-check once the branch is updated.
Comment #5
aman.kumar2Hi @santerref
Thank you for the detailed review. I have reviewed all the points you raised, including the security, configuration, dependency injection, coding standards, CI, library, and cacheability concerns.
I will address the required updates and share a revised status with you soon. I will also arrange a follow-up review and live SearchUnify testing once the branch has been updated.
Thanks.
Comment #6
avpadernoComment #7
aman.kumar2Hi @avpaderno
Thank you for the detailed review. I’ve fixed all the issues you mentioned and updated the code on the same **3.0.x** branch.
All security, dependency injection, cacheability, configuration, hook system, packaging, assets, navigation, tooling, and coding standard changes have been implemented as suggested.
Could you please check the updated code and share your feedback? If anything else needs adjustment, I’ll fix it right away.
Project Link:
https://www.drupal.org/project/sudc/
Project git link
https://git.drupalcode.org/project/sudc
Thanks
Comment #8
avpadernoComment #9
avpadernosrc/Form/SuConfigForm.php
That code cannot be used in Drupal 9.x and in Drupal 11.x because the constructor for
\Drupal\Core\Form\ConfigFormBaseaccepts two parameters only on Drupal 10.2.x and forward. (The relative change record is New parameter added to \Drupal\Core\Form\ConfigFormBase::__construct.)While there are other possible ways to rewrite that code, dropping the support for no longer supported Drupal releases, in the reviewed branch, is probably the quicker way to fix the code. If there is really the need to also support Drupal releases no longer supported by Drupal core maintainers, it is possible to have a branch for Drupal 9 and a branch for currently supported Drupal releases.
With Drupal 10 and Drupal 11, there is no longer need to use
#default_valuefor each form element, when the parent class isConfigFormBase: It is sufficient to use#config_target, as in the following code.Using that code, it is no longer needed to save the configuration values in the form submission handler: The parent class will take care of that.
For this change, it is necessary to require at least Drupal 10.3, but that is not an issue, considering which Drupal releases are currently supported by Drupal core maintainers.
That said,
ConfigBaseFormis usually the base class used for simple configuration forms, not for complex forms like this one. For these cases, a different base class is probably preferable..An hidden form element does not use attributes like autocomplete because it is not an input form element.
Furthermore, that element is not used by the form validation handler nor the for submission handler; it can be removed.
As per Drupal coding standards, before and after operators like
=>there needs to be a single space. Array values are not vertically aligned, simply to avoid changing the code when a longer key is added to the form.The configuration editable object is returned by
$this->config(), when the parent class isConfigFormBase. Clearly, when the parent class is different, that method could be not available.buildForm()does not call the method defined by the parent class.The object returned by
$this->config()can be used to set values for the configuration objects whose names are returned bygetEditableConfigNames(). (That method name should give an hint about what it is supposed to return.)I understand that a password form element is used to avoid the provision key is visible to somebody who gained access to an account with the permission to access the form, but that is bad UX for people who legitimately need to change that value. As it is, they can only rely on an error message which tells the provision key is wrong, but they need to re-enter the full provision key, instead of correcting the entered value.
To protect against those cases, there are better solutions, including using the Key module, or doing like Drupal core does with public files folder, for example.
That code is not necessary: It is sufficient to set
#requiredtoTRUEfor all the required values. Drupal core will not validate the form when values for required form elements are not entered.src/Controller/SuHelpController.php
Since that class does not use methods from the parent class, or it uses a single method from the parent class, it does not need to use
ControllerBaseas parent class.Controllers do not need to have a parent class; as long as they implement
\Drupal\Core\DependencyInjection\ContainerInjectionInterface, they are fine.There is no need to use a
\Symfony\Component\HttpFoundation\RequestStackor a\Symfony\Component\HttpFoundation\Requestas class properties, since the\Symfony\Component\HttpFoundation\Requestobject is automatically passed to the page controller, if one of its parameters is correctly type-hinted. See Typehinted parameters.The documentation comment for class constructors is no longer mandatory; if it is provided, it need to give the same information given in the following example code, formatted in the same way. I would rather avoid adding documentation comments for class constructors, though.
To show a help page, Drupal core provides at least two ways: The easier is implementing
hook_help(); the more complex is using help topics. Both are documented in Help and documentation.In both the cases, a contributed project should not need a specific route/controller just to show its own help.
src/Controller/SuResultController.php
Drupal coding standards say that control structures should be formatted like the following examples.
Comment #10
avpadernoComment #11
avpadernoI did not check everything santerref reported, but it seems that not all his points have been taken in consideration.
Comment #12
aman.kumar2Hi @avpaderno
Thank you for your continued review. All points raised in comments #9 and #10 have been addressed in the `3.0.x` branch, including the Drupal version support and code-formatting updates.
The requested changes to `SuConfigForm`, `SuHelpController`, and `SuResultController` have also been completed. Please review the latest changes and confirm whether all the reported issues have been resolved. We would appreciate your feedback.
Comment #13
avpadernoRemember to change status, when files have been changed basing on the last review done.
Comment #14
vishal.kadamFILE: src/Controller/SuHelpController.php
FILE: src/Controller/SuResultController.php
FILE: src/Form/SuConfigForm.php
FILE: src/Services/CommonCalls.php
Modules which are compatible with Drupal 10 and higher versions are expected to use constructor property promotion.
Comment #15
aman.kumar2Hi @vishal.kadam
Thank you for the review.
I have applied constructor property promotion across all applicable classes as required for Drupal 10+ compatibility. The following files have been updated:
src/Controller/SuHelpController.php
src/Controller/SuResultController.php
src/Form/SuConfigForm.php
src/Services/CommonCalls.php
src/Services/RestCalls.php
Properties that are assigned directly are now promoted in the constructor. For cases where the injected type differs from the stored type (e.g., ConfigFactoryInterface resolved to ImmutableConfig via ->get()), constructor property promotion is not applicable, so those remain as explicit assignments in the constructor body.
Please review and let me know if any further changes are needed.
Thanks
Comment #16
vishal.kadamRemember to change status, when the project is ready to be reviewed. In this queue, projects are only reviewed when the status is Needs review.
Comment #17
aman.kumar2Hi,
I forgot to update the status this time. It won’t happen again. I’ll make sure to update it properly next time.
Thanks.
Comment #18
avpaderno@aman.kumar2 When the status is Needs work like now, no reviewer will check the project. For the few reviewers who already commented, the status is still Needs work because you are not yet done with changes.
As a side note, to change status you need to click on the Save button, or changes will be lost.
Comment #19
avpadernoAs a further note, reviewers report a change only once, but you need to check whether the change applies to other files too. If, for example, it has been reported that before and after operators that accept two arguments there must be a single space, not multiple spaces, you need to verify that no other file contains lines like
$this->rendertemp = $rendertemp;.Comment #20
aman.kumar2Comment #21
avpadernosrc/Controller/SuHelpController.php
@filedocumentation tags are not used for .php files.To show a help page, Drupal core provides at least two ways: The easier is implementing
hook_help(); the more complex is using help topics. Both are documented in Help and documentation.In both the cases, a contributed project should not need a specific route/controller just to show its own help.
src/Controller/SuResultController.php
That is a
Drupal\Core\Config\ConfigFactoryInterfaceproperty.With Drupal 10.2.x and higher versions, a controller class does not need to implement
create(); usingAutowireTrait, it just needs to implement the class constructor.Drupal coding standards say that control structures should be formatted like the following examples.
src/Form/SuConfigForm.php
With Drupal 10 and Drupal 11, there is no longer need to use
#default_valuefor each form element, when the parent class isConfigFormBase: It is sufficient to use#config_target, as in the following code.Using that code, it is no longer needed to save the configuration values in the form submission handler: The parent class will take care of that.
For this change, it is necessary to require at least Drupal 10.3, but that is not an issue, considering which Drupal releases are currently supported by Drupal core maintainers.
I understand that a password form element is used to avoid the provision key is visible to somebody who gained access to an account with the permission to access the form, but that is bad UX for people who legitimately need to change that value. As it is, they can only rely on an error message which tells the provision key is wrong, but they need to re-enter the full provision key, instead of correcting the entered value.
To protect against those cases, there are better solutions, including using the Key module, or doing like Drupal core does with public files folder, for example.
src/Services/RestCalls.php
Every class property needs a documentation comment.
@paramlines are not correctly formatted.As per Drupal coding standards, operators that require two arguments need to have a single space before and after them. In
=>, there are seven spaces before the operator.DRUPAL_COMMUNITY_CHECKLIST.md
That file is not necessary for the module to work. It is rather a checklist for this application, which is no longer relevant when this application will be done.
composer.json
"drupal/core": "^10.2 || ^11 || ^12",The Drupal.org Composer Façade automatically adds the core requirements for the project basing on what entered in the .info.yml file. As noted in Add a composer.json file:
This point is not suggesting a change is necessary; its purpose is to make sure you know what the Drupal.org Composer façade does, and that, when adding core requirements also in the composer.json file, it is necessary to double check they are compatible with the core requirements in the .info.yml file.
I cannot think of any workflow where Composer is not used to gather the projects necessary for a site, and their dependencies, considering that Composer is required by Drupal core to gather its dependencies, but that does not mean that having the core requirements also in the composer.json file is not necessary in specific cases.
"php": "^8.1",Drupal core 10.2.x already requires PHP 8.1. That line is not necessary.
sudc.services.yml
Services can be autowired starting with Drupal 9.3.x. This means that it is no longer necessary to give a list of service arguments in the .services.yml file; they will be retrieved from the constructor definition.
Comment #22
aman.kumar2Hi,
Thank you for the detailed review. We have addressed all the reported issues. Here is a summary of the changes made:
Removed the custom help controller and route; the module now uses hook_help() to display help content via the standard Drupal help system.
Applied AutowireTrait to SuResultController and removed the create() method.
Replaced arguments: with autowire: true in sudc.services.yml.
Used #config_target in SuConfigForm for cdn, epoint, and token_expiry fields, removing the need to manually save those values in the submit handler.
Updated core_version_requirement to ^10.3 || ^11 || ^12 in both sudc.info.yml and composer.json.
Removed the redundant php version requirement from composer.json.
Added @var docblocks to all properties in RestCalls.php and fixed @param formatting.
Removed all => alignment from arrays across all files.
Removed @file documentation tags from all class files.
Removed DRUPAL_COMMUNITY_CHECKLIST.md as it is not part of the module.
Please review and share your feedback. Let us know if any further changes are needed.
Comment #23
aman.kumar2Comment #24
aman.kumar2Hi,
just following up on the request for the SearchUnify Drupal Connector project on Drupal.org. Please let me know if you need any additional details from my side.
Comment #25
santerref commentedRe-checked
3.0.xat6b8b268against my review in #4. The JWT payload, the signing key, the Referer based UID and the route permissions are all fixed. Four things left, plus a naming suggestion.Why does the
ClientExceptionhandler read the whole response body?In
RestCalls::exeHttpClient()andexeHttpClientSuGpt()it fills abodykey that neitherresultsByPost()norgetAPIParams()reads on that path, since both only returnstatusandmessagewhen the call fails.The uid is not validated against
repeater_dataresultsByPost()forwards a client-supplied uid to SearchUnify with the site'saccess_tokenwithout validating it against the configuredrepeater_data. Other paths (content(),getsearchjwt()) read the UID from config; this one trusts the request. A caller can target an unconfigured search instance, and a missing uid triggers an undefined-array-key notice with a NULL UID sent upstream.num_fieldsandrepeater_datacan disagreeIn
SuResultController::content()the loop runs on thenum_fieldsconfig value but reads$repeater[$i], whileSuConfigForm::addMore()savesnum_fieldson the Add click, before the form is submitted. Click Add, leave the page without saving, and/searchunify/{dynamic_path}emits "Undefined array key" on PHP 8. Looping onrepeater_datacovers both cases and makesnum_fieldsunnecessary.getAPIParams()throws a TypeErrorproperty_exists()gets the return ofjson_decode($requestData). An empty body,null, an array or a number gives NULL, array or int, whichproperty_exists()rejects on PHP 8. That is a 500 for any logged in user.SudcHooksandSudcHooks1say nothing about what they holdYou have to open both to find out that one implements
hook_theme()and the otherhook_help(), and the1suffix gives no clue at all. Naming them after what they implement would help, and since a single class can carry several#[Hook]attributes, merging them is also an option.The summary still mentions the Key module
It says "Key module integration for secure API token storage". There is no
drupal/keyorkey.repositoryanywhere in the branch, andprovision_keyandaccess_tokenare plaintype: stringin the schema. Implement it or fix the summary.Your pipeline never runs
GitLab answers "Unable to run pipeline. Project
project/gitlab_templatesfile.gitlab-ci/drupal_project.ymldoes not exist". Worth fixing, so you get that feedback before publishing a release.Coding standards
Still off, for example: the
@varblocks added in #21 have no short description, the concatenation inSEARCH_RESULT_API_PATHis pointless, andgetAPIParams()is not lowerCamel. Alsosudc.links.task.ymlstill declaressudc.help, whose route was removed in the last commit.Comment #26
aman.kumar2Hi,
Thank you for sharing your feedback. I have addressed the following issues:
- Removed the unused `body` key from both error handlers in `RestCalls`.
- Added UID validation in `resultsByPost()`. Missing or unknown UIDs now return a 400 response.
- Updated `content()` to iterate directly over `repeater_data`, avoiding PHP 8 undefined-key notices.
- Added an `is_object()` check in `getApiParams()` and renamed the method using lower camel case.
- Fixed the `addMore()` race condition by storing the temporary row count in form state instead of configuration.
- Merged `SudcHooks1` into `SudcHooks` and removed the outdated service entry.
- Removed the invalid `sudc.help` entry from `sudc.links.task.yml`.
- Resolved all PHPCS line-length issues and added the required documentation.
- Updated the GitLab CI/CD variables, and the pipeline is now running successfully.
Please review the changes and let me know if anything is still pending from my side.
Thanks.
Comment #27
aman.kumar2Comment #28
santerref commentedRe-checked
3.0.xat33bd2482. Five of the seven points in #25 are fixed, and fixed well. Good job. Two small things remain, nothing that blocks the security review itself, but they matter.The pipeline still does not run
Pipeline #920046, created on
33bd2482shortly before #26, failed with 0 jobs, like every pipeline on this project. So "the pipeline is now running successfully" does not match what the pipelines page shows. The error message on that pipeline tells you where to look, I will let you dig into it.The summary still mentions the Key module
Same as #4 and #25: "Key module integration for secure API token storage", with no
drupal/keyorkey.repositoryanywhere in the branch. Implement it or fix the summary.Two leftovers from the merge:
SudcHooks1.phpis now a file with no class in it and can simply be deleted, and thenum_fieldskey in the config schema andconfig/installis dead now that the form tracks the counter in form state, no code reads or writes that config key anymore.One friendly tip, since you are clearly moving fast: take a moment to test and double check each change before replying to the ticket. This process evaluates the code, but also how rigorously you work as a future maintainer.
Comment #29
aman.kumar2Hi team,
Please address the following cleanup items:
- Delete `SudcHooks1.php`, as it is an empty file left over from a previous merge and does not contain a class.
- Remove `num_fields` from the configuration schema and from `config/install/sudc.configs.yml`. The counter is now tracked in form state, and this configuration key is no longer used.
- Remove the “Key module integration” statement from the module description and summary. The `drupal/key` module is not a dependency, and `key.repository` is not used.
- Delete the current `.gitlab-ci.yml` file so that the appropriate pipeline jobs can be picked up. Pipeline `#920046` currently contains zero jobs.
Thank you for sharing this feedback. If you encounter any issues after these changes, please let us know.
Comment #30
aman.kumar2Comment #31
aman.kumar2Hi team,
Just following up on the request for the SearchUnify Drupal Connector project on Drupal.org. Please let me know if you need any additional details or actions from my side to move this forward.
Thanks!
Comment #32
avpadernoI am going to ask some questions about the used code. No change is expected in project files; only answers are expected by this review.
Do you see any difference between using a password form element for an account password and using it for a provision key?
Since
$form_state->getUserInput()returns an array reference, is calling$form_state->setUserInput($userInput)necessary?Why does the code call
cleanValues()?In a controller, is using a property for
RequestStacknecessary?$jwtSecretKey = hash_hmac('sha256', $this->privateKey->get(), $accessToken);In that code, is
$jwtSecretKeyset to a secret key?Does Drupal 10.3, the minimum Drupal version required by this project, support OOP hooks?
Why does the project use a service for rendering a footer which is rendered by a controller?
Following Drupal coding standards, what is the correct way to format code for control structures?
Why does the project use a template file for what returned by
hook_help()?Comment #33
aman.kumar2Hi Team,
As requested, I have added answers to each question. Please review them and share your feedback.
1. Difference between a password field for an account password vs. a provision key
Both use #type => 'password', which is appropriate in both cases because it masks the input visually. The meaningful differences lie in browser autofill behavior and the requirement logic.
For an account password, the browser is expected to offer to save and autofill the credential, so autocomplete='current-password' is the correct attribute value.
For a provision key (an API credential), browser-side storage is undesirable, so autocomplete='off' is set. However, autocomplete='off' is widely ignored by modern browsers — browsers deliberately override it to respect the user's own password-saving preferences. A more reliable signal for an API token is autocomplete='one-time-code', which most browsers treat as a non-saveable field.
The #required => empty($suConfigs->get('provision_key')) and the "Leave blank to keep the current value" description are appropriate for an API credential that is stored server-side and should not be echoed back to the browser. For a user account password, by contrast, the field would typically always be required and would not pre-exist in configuration.
2. Is calling $form_state->setUserInput($userInput) necessary?
Yes, it is necessary.
FormState::getUserInput() returns the internal $input array by value, not by reference. In PHP, arrays are value types: assigning the return value of getUserInput() to $userInput produces an independent copy. The subsequent calls to array_splice() and array_values() modify only that local copy. Without calling $form_state->setUserInput($userInput) to write it back, the $form_state object's internal input would remain unchanged, and the removed item would reappear on the next AJAX rebuild.
3. Why does the code call cleanValues()?
FormState::cleanValues() removes Drupal's internal form-tracking fields from the values array before getValues() is called. These internal fields include form_build_id, form_token, form_id, submit button labels (op), and values from structural elements such as #type => 'actions'. Without this call, those keys would be present in $values alongside the actual field data and could be accidentally stored to configuration. Calling cleanValues() at the start of submitForm() is standard Drupal form practice.
4. Is storing RequestStack as a constructor property necessary in a controller?
Not strictly necessary. In Drupal (built on Symfony), a controller action method can declare a Request parameter directly in its signature, and Symfony's argument resolver injects the current request automatically — no constructor injection or property is needed. The two methods that use it (resultsByPost() and getApiParams()) could each simply declare Request $request as a method parameter.
That said, the current approach is not wrong. When a service needs the current request it should inject RequestStack rather than Request directly, because Request is request-scoped and can change across sub-requests. Since the class already uses AutowireTrait, constructor injection of RequestStack is idiomatic and follows Drupal's service container conventions. The choice is a matter of style — controller-action parameter injection is slightly more explicit; constructor injection is consistent with how the rest of the class is wired.
5. Is $jwtSecretKey set to a correct secret key?
No — the arguments to hash_hmac() are inverted.
PHP's function signature is:
hash_hmac(string $algo, string $data, string $key): string
The call on line 199 is:
$jwtSecretKey = hash_hmac('sha256', $this->privateKey->get(), $accessToken);
Here, $this->privateKey->get() (the stable, server-side Drupal private key) is passed as $data, and $accessToken (the per-session SearchUnify token) is passed as $key — the HMAC secret. The roles are reversed. The correct call should be:
$jwtSecretKey = hash_hmac('sha256', $accessToken, $this->privateKey->get());
where the private key is the HMAC secret ($key) that authenticates the access token ($data).
As written, the JWT is signed using the access token as the secret, which defeats the purpose of the private key. The access token is transmitted to third-party systems and is relatively more exposed; the private key is a stable server-side secret that should never leave the server.
6. Does Drupal 10.3 support OOP hooks?
Partially — as an experimental feature only.
The #[Hook] attribute-based OOP hook system was introduced in Drupal 10.2 as experimental. It did not become a stable, supported API until Drupal 11.0. This module declares core_version_requirement: ^10.3 || ^11 || ^12.
For Drupal 10.3, attribute-based hooks are available and functional, but they carry experimental status, meaning the API could change without a standard deprecation cycle. Drupal.org's contributed module policy generally discourages reliance on experimental APIs for released modules. The .module file comment ("All hooks are implemented via attribute-based hooks") presents this as a settled approach, which is accurate for Drupal 11+ but is technically on experimental ground for the 10.3 minimum.
7. Why does the project use a service to render a footer that is output by a form?
Looking at the code, CommonCalls::grazittiHeader() is called in exactly one place: SuConfigForm::buildForm() (line 160). The method builds a render array using '#theme' => 'sufooter' and renders it immediately. To do this, the CommonCalls service injects three dependencies: RendererInterface, RequestStack, and ModuleExtensionList.
This is an over-engineered abstraction for a task performed in a single location. The same result could be achieved with a few lines inside the form class itself, which already has access to the service container. Additionally:
The service name CommonCalls implies broad, shared utility, but the class has only one method.
The docblock says "common utility calls such as rendering the module footer" — the hedging ("such as") implies intended future growth that never materialized.
RequestStack is used only to construct the module's absolute URL for the logo path, which could instead be resolved using a relative path directly in the Twig template, removing the need for runtime request inspection entirely.
The service should either be eliminated by inlining its logic into the form, or the footer should be extracted to a proper Twig block managed by the theme layer.
8. Correct Drupal coding standard formatting for multi-line control structures
Drupal coding standards require that when a control structure condition spans multiple lines, each condition is indented by two spaces, and the closing parenthesis and opening brace appear together on their own line.
The current code (lines 162–164):
if (is_object($payloadData) &&
property_exists($payloadData, 'streaming') &&
$payloadData->streaming) {
The closing ) is attached to the last condition, which is non-compliant. The correct format is:
if (
is_object($payloadData) &&
property_exists($payloadData, 'streaming') &&
$payloadData->streaming
) {
This places each condition on its own line, all at the same indent level, with ) { on a dedicated closing line — matching the Drupal coding standards specification for complex control structures.
9. Why does the project use a template file for the output of hook_help()?
There is no compelling reason to do so, and it introduces several problems.
hook_help() conventionally returns a simple render array — typically ['#markup' => $this->t('...')] or a structured array of translated strings. Using a Twig template for this requires a hook_theme() registration, an extra template file, and the construction of an absolute module path at runtime just to pass it to the template.
Beyond the unnecessary complexity, the template itself contains several defects:
Stale content: It lists Client Id, Client Secret, Username, and Password as fields to fill in (line 12–19). None of these fields exist in the current configuration form, which uses CDN, Provision Key, Endpoint, and Token Expiry. The help page is therefore actively misleading to users.
Broken HTML: Line 11 contains a stray closing tag with no corresponding opening tag.
Invalid CSS: Line 30 has background-color:E0E0D8 — the # prefix for the hex color value is missing, making the style ineffective.
Not translatable: The template text is hardcoded in English with no Twig trans tags, making it impossible to translate through Drupal's string translation system.
A simple render array inside hook_help() would be easier to maintain, translatable, and would have prevented the help content from drifting out of sync with the actual form.
Thanks.
Comment #34
avpadernoThe differences with an account password are:
So, what is using a password form element trying to achieve, in this case? Was the user experience considered?
Is that what
FormState::getUserInput()code says?Most importantly, what does the following note in that documentation page tell us? (Emphasis is mine.)
The question was In that code, is
$jwtSecretKeyset to a secret key? It was not asking whether$jwtSecretKeyis set to a correct secret key. Furthermore, the answer to my question does not depend on the arguments passed tohash_hmac().Rephrasing the question, in the following code is
$jwtSecretKey, a variable whose name makes think the variable stores a JWT secret key, set to a secret key?I could ask the same question for the following code, which is the code the module is currently using.
The project code expressly accesses specific values from
$values, and those values are not the onesFormState::cleanValues()would remove. So, what is the purpose of callingFormState::cleanValues()which removes values the code is already ignoring?That is correct. So, why is the project using a service which is used only by the form class, and which is adding more code than necessary?
Can you point out which part of the Drupal coding standards say that?
About that code, I said:
The answer to that implied that the code was changed to fix what I pointed out, but the code was never changed. Your last comment says there is no compelling reason for not changing it.
No,
#[Hook]is an attribute that can only be used with Drupal 11.1.x. Drupal 10.3.x will ignore it. In fact, the documentation page forDrupal\Core\Hook\Attribute\Hooksays:Given that, how can the project work in Drupal 10.3.x and any Drupal version before Drupal 11.1.x?
The most important questions: Did you use AI to answer my questions? Are you using AI to write code for this project?