- Issue created by @kulpratap2002
- 🇮🇹Italy apaderno Brescia, 🇮🇹
Thank you for applying!
Please read Review process for security advisory coverage: What to expect → for more details and Security advisory coverage application checklist → to understand what reviewers look for. Tips for ensuring a smooth review → gives some hints for a smoother review.
The important notes are the following.
- If you have not done it yet, enable GitLab CI for the project, and fix what reported from the phpcs job. This help to fix most of what reviewers would report.
- For the time this application is open, only your commits are allowed. No other people, including other maintainers/co-maintainers can make commits.
- The purpose of this application is giving you a new drupal.org role that allows you to opt projects into security advisory coverage, either projects you already created, or projects you will create. The project status won't be changed by this application.
- Nobody else will get the permission to opt projects into security advisory policy. If there are other maintainers/co-maintainers who will to get that permission, they need to apply with a different module.
- We only accept an application per user. If you change your mind about the project to use for this application, or it is necessary to use a different project for the application, please update the issue summary with the link to the correct project and the issue title with the project name and the branch to review.
To the reviewers
Please read How to review security advisory coverage applications → , Application workflow → , What to cover in an application review → , and Tools to use for reviews → .
The important notes are the following.
- It is preferable to wait for a Code Review Administrator before commenting on newly created applications. Code Review Administrators will do some preliminary checks that are necessary before any change on the project files is suggested.
- Reviewers should show the output of a CLI tool → only once per application. The configuration used for these tools needs to be the same configuration used by GitLab CI, stored in the GitLab Templates repository.
- It may be best to have the applicant fix things before further review.
For new reviewers, I would also suggest to first read In which way the issue queue for coverage applications is different from other project queues → .
- 🇮🇹Italy apaderno Brescia, 🇮🇹
Remember to change status, when the project is ready for review, as in this queue Active means Don't review yet the project I am using for this application.
- 🇮🇳India vishal.kadam Mumbai
1. FILE: ai_readme_generator.info.yml
package: Custom
This line is used by custom modules created for specific sites. It is not a package name used for projects hosted on drupal.org.
2. FILE: src/Form/GenerateReadmeForm.php
/** * Constructor. */ public function __construct(
The documentation comment for constructors is not mandatory anymore, If it is given, the description must be “Constructs a new [class name] object”, where [class name] includes the class namespace.
- 🇮🇳India kulpratap2002
@vishal.kadam Thank you for the review.
I've made the suggested changes.
Kindly have a look and let me know if anything else needs to be updated.
Thanks - 🇮🇳India vishal.kadam Mumbai
Rest looks fine to me.
Let’s wait for a Code Review Administrator to take a look and if everything goes fine, you will get the role.
- 🇮🇹Italy apaderno Brescia, 🇮🇹
- The following points are just a start and don't necessarily encompass all of the changes that may be necessary
- A specific point may just be an example and may apply in other places
- A review is about code that does not follow the coding standards, contains possible security issue, or does not correctly use the Drupal API
- The single review points are not ordered, not even by importance
src/Form/GenerateReadmeForm.php
The parent class already defines the
$configFactory
and the$messenger
properties which are initialized using the methods the parent class makes available.if ( (str_starts_with($path, 'modules/custom') || str_starts_with($path, 'modules/contrib')) && substr_count($path, '/') > 2 ) { continue; }
As per coding standards → , the
if()
conditions are written on a single line.Always use curly braces even in situations where they are technically optional. Having them increases readability and decreases the likelihood of logic errors being introduced when new lines are added. The opening curly should be on the same line as the opening statement, preceded by one space. The closing curly should be on a line by itself and indented to the same level as the opening statement.
- 🇮🇳India kulpratap2002
@avpaderno Thank you for the review.
I've made the suggested changes.
Kindly have a look and let me know if anything else needs to be updated.
Thanks. - 🇮🇹Italy bigbabert Milano, Italy
modules/custom and modules/contrib should not be hardcoded since they can be configured in composer
- 🇮🇳India kulpratap2002
@bigbabert Thanks for the suggestion.
But I think it would become too complex if we had to read the main project composer.json file to determine the custom and the contrib folders.
So I updated the
GenerateReadmeForm
andReadmeGeneratorCommands
to use a reliable method for collecting only top-level custom and contrib modules using ExtensionDiscovery.src/Form/GenerateReadmeForm.php
public function getTopLevelCustomAndContribModules(): array { $modules = []; $discovery = new ExtensionDiscovery(DRUPAL_ROOT); $discovery->setProfileDirectories([]); $all_modules = $discovery->scan('module'); foreach ($all_modules as $machine_name => $extension) { $module_path = $extension->getPath(); if (str_starts_with($module_path, 'core/')) { continue; } $path_parts = explode('/', $module_path); $modules_index = array_search('modules', $path_parts); if ($modules_index !== FALSE) { $after_modules = array_slice($path_parts, $modules_index + 1); if (count($after_modules) !== 2) { continue; } } $info = $this->infoParser->parse($extension->getPathname()); $modules[$machine_name] = $info['name'] ?? $machine_name; } asort($modules); return $modules; }
ReadmeGeneratorCommands
public function generate(string $module): void { $discovery = new ExtensionDiscovery(DRUPAL_ROOT); $discovery->setProfileDirectories([]); $all_modules = $discovery->scan('module'); if (!isset($all_modules[$module])) { $this->output()->writeln("Module '$module' not found in the filesystem."); return; } $extension = $all_modules[$module]; $relative_path = $extension->getPath(); if (str_starts_with($relative_path, 'core/')) { $this->output()->writeln("Core module '$module' is not supported."); return; } $module_path = DRUPAL_ROOT . '/' . $relative_path; $scanner = new CodebaseScanner($module_path); $moduleData = $scanner->scan(); $config = $this->configFactory->get('ai_readme_generator.settings')->get(); $apiKey = $config['api_key'] ?? NULL; $chatEndpoint = $config['chat_endpoint'] ?? NULL; $model = $config['model'] ?? NULL; if (empty($apiKey) || empty($chatEndpoint) || empty($model)) { $this->output()->writeln('<error>Please fill the AI configuration form first!</error>'); return; } $ai = new AIResponse($config); $summary = $ai->summarizeArray($moduleData); $readme_path = $module_path . '/README.md'; file_put_contents($readme_path, $summary); $this->output()->writeln("✅ README.md generated at: $readme_path"); }
Please review the above code. If it's correct, I will update it in the module.
- 🇮🇹Italy bigbabert Milano, Italy
Have a look to this API documentation might be helpful is not so complex and you don't have to read from composer installer: https://www.drupal.org/node/2940438 →
- 🇮🇹Italy bigbabert Milano, Italy
I've also tried to install the module with composer in fresh Drupal 11 instance and get following error:
Your requirements could not be resolved to an installable set of packages. Problem 1 - Root composer.json requires drupal/ai_readme_generator ^1.0 -> satisfiable by drupal/ai_readme_generator[1.0.0]. - drupal/ai_readme_generator 1.0.0 requires innoraft/ai-readme-generator ^1.1.0 -> satisfiable by innoraft/ai-readme-generator[v1.1.0]. - innoraft/ai-readme-generator v1.1.0 requires openai-php/client ^0.10.3 -> found openai-php/client[v0.10.3] but the package is fixed to v0.13.0 (lock file version) by a partial update and that version does not match. Make sure you list it as an argument for the update command. Use the option --with-all-dependencies (-W) to allow upgrades, downgrades and removals for packages currently locked to specific versions. Installation failed, reverting ./composer.json and ./composer.lock to their original content.
- 🇮🇳India vishal.kadam Mumbai
This is not an issue created in a project queue.
Comments in this queue are required to review the project files and report what needs to be changed. We do not debug projects.
- 🇮🇹Italy bigbabert Milano, Italy
Hi @vishal.kadam,
i assume that test should cover also that the submitted module is installable and compatible with version declared as supported by the module? is not that part of the review?
eg. Works with Drupal: ^10 || ^11
So if module has composer dependencies this also should be managed per compatibility declaration is that right? Also i can not suggest a solution since the locked dependency declaration is on a packagist module out of drupal.org
Please correct me if i'm wrong.
- 🇮🇳India vishal.kadam Mumbai
The error you shared is related to the overall Composer setup of the project, not an issue with the module itself. The PHP library dependency is correctly defined in the module’s composer.json.
As part of the review, we check that the code follows Drupal standards, uses the Drupal API properly, and is compatible with supported Drupal versions. In this case, everything looks fine from the review side.
- 🇮🇹Italy bigbabert Milano, Italy
i don't understand how you can say that there isn't compatibility issue if the module ask for a dependency older than the current one supported in drupal 11 for openai-php/client and also i don't understand how can be judged best practice scan the file system to get drupal module path instead of use drupal API as per comment #12
- 🇮🇳India kulpratap2002
@bigbabert Thank you for your suggestions,
But the ExtensionDiscovery service scans the entire filesystem and returns all available modules—whether enabled or disabled—including core, contributed, and custom modules. This makes it ideal for use cases requiring a comprehensive list of modules. In contrast, extension.list.module (or ModuleExtensionList) only lists currently installed and enabled modules, better suited for runtime logic such as plugin management or routing requiring active modules.
In my module, users can generate README files even for disabled modules, so we use ExtensionDiscovery to ensure those modules are included in the selection list. Additionally, we exclude modules located under core/modules to avoid cluttering the list with core system modules not relevant for README generation. Using ExtensionDiscovery allows us to provide a more complete and relevant module list for our use case, while extension.list.module would restrict us to only enabled modules and miss disabled but potentially important modules.
Also, the module is installing fine on my Drupal 11 fresh site. I have attached a video for reference.
Thank you.
- 🇮🇹Italy bigbabert Milano, Italy
Hi @kul.pratap, can you check in you composer the minimum stability? have you installed drupal/ai module? I've tried to install on DrupalCMS. but this seems not be a relevant issue for that review.
Are you sure that $this->moduleHandler->moduleExists('block_content') looks only for enabled modules=? i'm quite sure that it return true if the module is in the core/contrib/custom module folder. Then you can get also the path of disabled module. The first method returns the module list service (\Drupal::service('extension.list.module')). The second method the module path for the given module name (\Drupal::service('extension.list.module')->getPath($module_name)).
I add also taht there isn't input sanification here for example:
$config = $this->configFactory->get('ai_readme_generator.settings')->get();
$apiKey = $config['api_key'] ?? NULL;or here:
$module_name = $form_state->getValue('module_name');
This are just examples user inputs in drupal should be always validated to avoid injections:
https://www.drupal.org/docs/administering-a-drupal-site/security-in-drup... →
- 🇮🇹Italy bigbabert Milano, Italy
Just reviewing the code that you pasted above:
$this->output()->writeln("✅ README.md generated at: $readme_path");
The $readme_path variable in PHP in this way will be printed as string with value: $readme_path, not sure if it is expected behavior, i see same issue repeated
Also:
$this->output()->writeln('<error>Please fill the AI configuration form first!</error>');
It is not best practice concatenate html tag with strings.
- 🇮🇹Italy apaderno Brescia, 🇮🇹
The $readme_path variable in PHP in this way will be printed as string with value: $readme_path
That is not true. Supposing that
$readme_path
contains'path'
, the output of"✅ README.md generated at: $readme_path"
would be"✅ README.md generated at: path"
.It is not best practice concatenate html tag with strings.
That is not true.
Such simple strings can contain XML/HMTL markup. If it were a more complex, dynamic, string, it could be replaced by a render array or created using a theme hook.
Even in the case of translatable string, HTML markup is allowed, even if it is suggested to reduce that to the minimum. - 🇮🇹Italy bigbabert Milano, Italy
Oh i was not aware , always used to concatenate with {} variable inside string, good to know.
Stille there are other pending feedback i'll summarize here:
You would use \Drupal::service('extension.list.module')->getList(); that return also disabled modules. Screenshot here, eg. i don't have ai module enabled
I add also that there isn't input sanitization here for example:
$config = $this->configFactory->get('ai_readme_generator.settings')->get();
$apiKey = $config['api_key'] ?? NULL;Above code also don't reflect best practice for drupal you should not use $config as array you can get the config value with $config->get('api_key')
or here:
$module_name = $form_state->getValue('module_name');
composer minumum stability concern for stable module
Thanks
- 🇮🇹Italy apaderno Brescia, 🇮🇹
I add also that there isn't input sanitization here for example:
Sanitization is done when user-entered input is rendered in a page; in other cases, it is not done.
- 🇮🇳India kulpratap2002
@avpaderno If everything looks good now, can we move forward?