This commit is contained in:
Nejc Kovač 2022-08-12 16:10:12 +02:00
commit 9fb81db4e0
28144 changed files with 3258517 additions and 105 deletions

2
.gitignore vendored
View File

@ -50,3 +50,5 @@ docker
/composer.lock /composer.lock
/.favorites.json /.favorites.json
main/survey/skins/1045_test.css main/survey/skins/1045_test.css
/d

View File

@ -2,6 +2,9 @@ RewriteEngine On
################ FRONTEND DRUPAL konfiguracija ################ ################ FRONTEND DRUPAL konfiguracija ################
## Drupal API call
#RewriteRule ^api/drupal/(.*) frontend/drupal.php?action=$1
## root domain ## root domain
# RewriteCond %{REQUEST_URI} ^(/)?$ # RewriteCond %{REQUEST_URI} ^(/)?$
# RewriteRule ^(.*)$ /frontend/drupal/$1 [L] # RewriteRule ^(.*)$ /frontend/drupal/$1 [L]
@ -45,6 +48,7 @@ RewriteRule ^koda/(.*) main/survey/sa_koda.php?%{QUERY_STRING}
RewriteRule ^a/(.*) main/survey/index.php?anketa=$1&%{QUERY_STRING} RewriteRule ^a/(.*) main/survey/index.php?anketa=$1&%{QUERY_STRING}
RewriteRule ^podatki/(.*?[^/])/(.*[^/])? admin/survey/public.php?anketa=$1&urlhash=$2&%{QUERY_STRING} RewriteRule ^podatki/(.*?[^/])/(.*[^/])? admin/survey/public.php?anketa=$1&urlhash=$2&%{QUERY_STRING}
RewriteRule ^admin/survey/minify/([a-z]=.*) admin/survey/minify/index.php?$1 [L,NE] RewriteRule ^admin/survey/minify/([a-z]=.*) admin/survey/minify/index.php?$1 [L,NE]
RewriteRule ^api/drupal/(.*) frontend/drupal.php?action=?$1
## Google OAuth2 prijava ## Google OAuth2 prijava
RewriteRule ^google-prijava\b(.*) frontend/api/google-oauth2.php$1 RewriteRule ^google-prijava\b(.*) frontend/api/google-oauth2.php$1

View File

@ -17,8 +17,12 @@ class ApiLogin
var $pass; var $pass;
var $method;
var $prijava = ''; var $prijava = '';
var $data;
var $EncPass; var $EncPass;
var $page_urls = []; // Url-ji za podstrani - to se bo verjetno nastavljalo v settings.php var $page_urls = []; // Url-ji za podstrani - to se bo verjetno nastavljalo v settings.php
@ -284,14 +288,13 @@ class ApiLogin
// Preveri ce je user ze logiran v 1ko in nastavi globalne spremenljivke in cookie (kopirano iz function.php) // Preveri ce je user ze logiran v 1ko in nastavi globalne spremenljivke in cookie (kopirano iz function.php)
public function executeAction($params, $data) public function executeAction($params, $data, $method)
{ {
global $site_url; global $site_url;
global $global_user_id; global $global_user_id;
global $lang; global $lang;
global $cookie_domain; global $cookie_domain;
// Nastavimo prejete podatke // Nastavimo prejete podatke
if (isset($data['ime'])) { if (isset($data['ime'])) {
$this->ime = $data['ime']; $this->ime = $data['ime'];
@ -305,6 +308,12 @@ class ApiLogin
if (isset($data['pass'])) { if (isset($data['pass'])) {
$this->pass = $data['pass']; $this->pass = $data['pass'];
} }
if (isset($data['method'])){
$this->method = $data['method'];
}
if (isset($data)){
$this->data = $data;
}
if (!isset($params['action'])) { if (!isset($params['action'])) {
$response = 'Napaka! Manjkajo parametri!'; $response = 'Napaka! Manjkajo parametri!';
@ -416,6 +425,9 @@ class ApiLogin
global $originating_domain; global $originating_domain;
global $keep_domain; global $keep_domain;
$piskotek = [];
$error = [];
// Ce imamo vklopljeno blokiranje dostopa do admina glede na ip // Ce imamo vklopljeno blokiranje dostopa do admina glede na ip
$admin_allow_only_ip = AppSettings::getInstance()->getSetting('app_limits-admin_allow_only_ip'); $admin_allow_only_ip = AppSettings::getInstance()->getSetting('app_limits-admin_allow_only_ip');
@ -425,8 +437,16 @@ class ApiLogin
// Preverimo ip - ce se ne ujema ne pustimo logina // Preverimo ip - ce se ne ujema ne pustimo logina
if(!in_array($ip, $admin_allow_only_ip)){ if(!in_array($ip, $admin_allow_only_ip)){
header('location: '.$this->page_urls['page_login'.$this->prijava]); if($this->method == 'AJAX'){
die();
return $this->ajaxResponse('error', 'Napaka pri prijavi.');
}else {
header('location: '.$this->page_urls['page_login'.$this->prijava]);
die();
}
} }
} }
@ -446,6 +466,7 @@ class ApiLogin
$LifeTime = $LifeTime; $LifeTime = $LifeTime;
} }
// Preverimo ce obstaja uporabnik s tem emailom // Preverimo ce obstaja uporabnik s tem emailom
$user_id = User::findByEmail($this->email); $user_id = User::findByEmail($this->email);
if (!empty($user_id)) { if (!empty($user_id)) {
@ -454,8 +475,17 @@ class ApiLogin
// BAN // BAN
if ($r['status'] == 0) { if ($r['status'] == 0) {
header('Location: '.$this->page_urls['page_user_ban'.$this->prijava].'&error=user_ban&email='.$this->email);
die(); if($this->method == 'AJAX'){
return $this->ajaxResponse('error', $lang['cms_error_user_ban']);
}else {
header('Location: '.$this->page_urls['page_user_ban'.$this->prijava].'&error=user_ban&email='.$this->email);
die();
}
} }
$user_lang = 1; $user_lang = 1;
@ -463,6 +493,7 @@ class ApiLogin
$user_lang = 2; $user_lang = 2;
} }
// Preverimo ce je password ok // Preverimo ce je password ok
if (base64_encode((hash('SHA256', $this->pass.$pass_salt))) == $r['pass'] || $this->EncPass == $r['pass']) { if (base64_encode((hash('SHA256', $this->pass.$pass_salt))) == $r['pass'] || $this->EncPass == $r['pass']) {
@ -481,41 +512,131 @@ class ApiLogin
// Ustvarimo login cookie // Ustvarimo login cookie
setcookie("uid", base64_encode($r['email']), time() + $LifeTime, '/', $cookie_domain); if($this->method == 'AJAX') {
$piskotek['uid'] = [
'ime' => 'uid',
'vrednost' => base64_encode($r['email']),
'opcije' => [
'expires' => time() + $LifeTime,
'path' => '/',
'domain' => $cookie_domain,
//'secure' => true
]
];
} else {
setcookie("uid", base64_encode($r['email']), time() + $LifeTime, '/', $cookie_domain);
}
//Preverimo če gre za Google 2FA //Preverimo če gre za Google 2FA
$user_2fa_enabled = User::option($r['id'], 'google-2fa-validation'); $user_2fa_enabled = User::option($r['id'], 'google-2fa-validation');
if(!empty($user_2fa_enabled) && $user_2fa_enabled != 'NOT'){ if(!empty($user_2fa_enabled) && $user_2fa_enabled != 'NOT'){
setcookie("g2fa", base64_encode($user_2fa_enabled), time() + $LifeTime, '/', $cookie_domain); setcookie("g2fa", base64_encode($user_2fa_enabled), time() + $LifeTime, '/', $cookie_domain);
header('location: '.$this->page_urls['page_login_2fa']);
die(); if($this->method == 'AJAX'){
//TODO: preveri kako je s piškoti
return $this->ajaxResponse('error', $lang['cms_error_user_ban']);
} else{
header('location: '.$this->page_urls['page_login_2fa']);
die();
}
} }
// Ustvarimo piškotek še z imenom in geslom // Ustvarimo piškotek še z imenom in geslom
setcookie("unam", base64_encode($r['name'].' '.$r['surname']),time() + $LifeTime, '/', $cookie_domain);
setcookie("secret", $r['pass'], time() + $LifeTime, '/', $cookie_domain); if($this->method == 'AJAX'){
$piskotek['unam'] = [
'ime' => 'unam',
'vrednost' => base64_encode($r['name'].' '.$r['surname']),
'opcije' => [
'expires' => time() + $LifeTime,
'path' => '/',
'domain' => $cookie_domain,
//'secure' => true
]
];
$piskotek['secret'] = [
'ime' => 'secret',
'vrednost' => $r['pass'],
'opcije' => [
'expires' => time() + $LifeTime,
'path' => '/',
'domain' => $cookie_domain,
//'secure' => true
]
];
} else {
setcookie("unam", base64_encode($r['name'].' '.$r['surname']),time() + $LifeTime, '/', $cookie_domain);
setcookie("secret", $r['pass'], time() + $LifeTime, '/', $cookie_domain);
}
if ($r['status'] == "2" || $r['status'] == "6") { if ($r['status'] == "2" || $r['status'] == "6") {
setcookie("P", time(), time() + $LifeTime, '/', $cookie_domain);
header('location: '.$this->page_urls['page_login'.$this->prijava].'&email='.$this->email.'&error=password'); if($this->method == 'AJAX'){
die();
$piskotek['P'] = [
'ime' => 'P',
'vrednost' => time(),
'opcije' => [
'expires' => time() + $LifeTime,
'path' => '/',
'domain' => $cookie_domain,
'secure' => true
]
];
$error['password'] = $lang['cms_error_password'];
return $this->ajaxResponse('error', [
'piskotek' => $this->cookieEncode($piskotek),
'error' => $error
]);
}else{
setcookie("P", time(), time() + $LifeTime, '/', $cookie_domain);
header('location: '.$this->page_urls['page_login'.$this->prijava].'&email='.$this->email.'&error=password');
die();
}
} }
} }
else { else {
// Password prompt // Password prompt
header('location: '.$this->page_urls['page_login'.$this->prijava].'&email='.$this->email.'&error=password'); if($this->method == 'AJAX'){
die();
return $this->ajaxResponse('error', $lang['cms_error_password']);
}else{
header('location: '.$this->page_urls['page_login'.$this->prijava].'&email='.$this->email.'&error=password');
die();
}
} }
} }
else { else {
// Preverimo, če je sploh vpisal email // Preverimo, če je sploh vpisal email
if (validEmail($this->email)) { if (validEmail($this->email)) {
// Emaila ni v bazi if($this->method == 'AJAX'){
header('location: '.$this->page_urls['page_login_noEmail'.$this->prijava].'&email='.$this->email);
return $this->ajaxResponse('error', $lang['cms_error_missing_email']);
}else{
// Emaila ni v bazi
header('location: '.$this->page_urls['page_login_noEmail'.$this->prijava].'&email='.$this->email);
}
} else { } else {
// Ni vpisana prava oblika maila if($this->method == 'AJAX'){
header('location: '.$this->page_urls['page_login_noEmail'.$this->prijava].'&email='.$this->email);
return $this->ajaxResponse('error', $lang['cms_error_email']);
}else{
// Ni vpisana prava oblika maila
header('location: '.$this->page_urls['page_login_noEmail'.$this->prijava].'&email='.$this->email);
}
} }
die(); die();
} }
@ -530,8 +651,17 @@ class ApiLogin
die(); die();
} }
// Vse je ok - prijavljenega preusmerimo na moje ankete // Vse je ok - prijavljenega preusmerimo na moje ankete
if($this->method == 'AJAX'){
return $this->ajaxResponse('success', [
'url' => $site_url.'admin/survey/index.php?lang='.$user_lang,
'piskotek' => $this->cookieEncode($piskotek)
]);
}
header('location: '.$site_url.'admin/survey/index.php?lang='.$user_lang); header('location: '.$site_url.'admin/survey/index.php?lang='.$user_lang);
die(); die();
} }
@ -889,7 +1019,7 @@ class ApiLogin
$mails = explode(";", $data[0]); $mails = explode(";", $data[0]);
sort($mails); sort($mails);
$mail = $mails[0]; $mail = $mails[0];
// Pridobimo aai (shibboleth) "uuid" // Pridobimo aai (shibboleth) "uuid"
$aai_id = $data[1]; $aai_id = $data[1];
@ -925,7 +1055,7 @@ class ApiLogin
else { else {
// potegni geslo in mu daj kuki // potegni geslo in mu daj kuki
$result = sisplet_query("SELECT pass, email FROM users WHERE id='".$user_id_1ka."'"); $result = sisplet_query("SELECT pass, email FROM users WHERE id='".$user_id_1ka."'");
$r = mysqli_fetch_row($result); $r = mysqli_fetch_row($result);
$pass = $r[0]; $pass = $r[0];
@ -1031,27 +1161,35 @@ class ApiLogin
{ {
$error = []; $error = [];
$email = (isset($_POST['email'])) ? $_POST['email'] : ''; $email = (isset($this->data['email'])) ? $this->data['email'] : '';
$ime = (isset($_POST['ime'])) ? $_POST['ime'] : ''; $ime = (isset($this->data['ime'])) ? $this->data['ime'] : '';
$geslo = (isset($_POST['geslo'])) ? $_POST['geslo'] : ''; $geslo = (isset($this->data['geslo'])) ? $this->data['geslo'] : '';
$geslo2 = (isset($_POST['geslo2'])) ? $_POST['geslo2'] : ''; $geslo2 = (isset($this->data['geslo2'])) ? $this->data['geslo2'] : '';
$agree = (isset($_POST['agree'])) ? $_POST['agree'] : '0'; $agree = (isset($this->data['agree'])) ? $this->data['agree'] : '0';
$gdprAgree = (isset($_POST['gdpr-agree'])) ? $_POST['gdpr-agree'] : '0'; $gdprAgree = (isset($this->data['gdpr-agree'])) ? $this->data['gdpr-agree'] : '0';
$ajaxKlic = (isset($_POST['ajax'])) ? $_POST['ajax'] : '0'; // Če izvajamo registracjo preko drupala, ker se pošlje post request preko ajaxa $ajaxKlic = (isset($this->data['ajax'])) ? $this->data['ajax'] : '0'; // Če izvajamo registracjo preko drupala, ker se pošlje post request preko ajaxa
$varnostno_polje = (isset($_POST['varnostno-polje'])) ? $_POST['varnostno-polje'] : false; $varnostno_polje = (isset($this->data['varnostno-polje'])) ? $this->data['varnostno-polje'] : false;
if (!empty($varnostno_polje)) { if (!empty($varnostno_polje)) {
header('Location: '.$this->page_urls['page_robot_redirect']);
die(); if($this->method == 'AJAX'){
return $this->ajaxResponse('error', [
'url' => $this->page_urls['page_robot_redirect'],
'message' => 'Robot'
]);
} else {
header('Location: ' . $this->page_urls['page_robot_redirect']);
die();
}
} }
// Preverimo ReCaptcha // Preverimo ReCaptcha
if (AppSettings::getInstance()->getSetting('google-secret_captcha') !== false) { if (AppSettings::getInstance()->getSetting('google-secret_captcha') !== false) {
$recaptchaResponse = $_POST['g-recaptcha-response']; $recaptchaResponse = $this->data['g-recaptcha-response'];
$requestReCaptcha = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=".AppSettings::getInstance()->getSetting('google-secret_captcha')."&response=".$recaptchaResponse); $requestReCaptcha = file_get_contents('https://www.google.com/recaptcha/api/siteverify?secret=' . $secret_captcha . '&response=' . $recaptchaResponse);
if (!strstr($requestReCaptcha, "true")) { if (!strstr($requestReCaptcha, 'true')) {
$error['invalid_recaptcha'] = '1'; $error['invalid_recaptcha'] = '1';
} }
} }
@ -1137,11 +1275,8 @@ class ApiLogin
} // Vse je ok - preusmerimo na potrditveno stran } // Vse je ok - preusmerimo na potrditveno stran
else { else {
if($ajaxKlic){ if($ajaxKlic || $this->method == 'AJAX'){
echo json_encode([ return $this->ajaxResponse('success');
'success' => '1'
]);
die();
} }
// Hidden form, ki ga z js potem postamo naprej (da prenesemo vnesene podatke na naslednjo stran) // Hidden form, ki ga z js potem postamo naprej (da prenesemo vnesene podatke na naslednjo stran)
@ -1169,15 +1304,15 @@ class ApiLogin
// Nastavimo jezik // Nastavimo jezik
$language = 1; $language = 1;
if(isset($_POST['language'])){ if(isset($this->data['language'])){
$language = $_POST['language']; $language = $this->data['language'];
} }
elseif(isset($_POST['jezik'])){ elseif(isset($this->data['jezik'])){
$language = ($_POST['jezik'] == 'en' ? 2 : 1); $language = ($this->data['jezik'] == 'en' ? 2 : 1);
} }
if(is_numeric($language)){ if(is_numeric($language)){
include_once('../../lang/'.$language.'.php'); include('../../lang/'.$language.'.php');
} }
$napaka = []; $napaka = [];
@ -1261,25 +1396,25 @@ class ApiLogin
global $lang; global $lang;
$email = (isset($_POST['email']) ? $_POST['email'] : ''); $email = (isset($this->data['email']) ? $this->data['email'] : '');
$ime = (isset($_POST['ime']) ? $_POST['ime'] : ''); $ime = (isset($this->data['ime']) ? $this->data['ime'] : '');
//$geslo = (isset($_POST['geslo']) ? base64_decode($_POST['geslo']) : ''); //$geslo = (isset($this->data['geslo']) ? base64_decode($this->data['geslo']) : '');
$geslo = (isset($_POST['geslo']) ? $_POST['geslo'] : ''); $geslo = (isset($this->data['geslo']) ? $this->data['geslo'] : '');
$gdprAgree = (isset($_POST['gdpr-agree']) ? $_POST['gdpr-agree'] : 0); $gdprAgree = (isset($this->data['gdpr-agree']) ? $this->data['gdpr-agree'] : 0);
$ajax = (isset($_POST['ajax']) ? $_POST['ajax'] : 0); // če je Drupal ajax request $ajax = (isset($this->data['ajax']) ? $this->data['ajax'] : 0); // če je Drupal ajax request
// Nastavimo jezik // Nastavimo jezik
$language = 1; $language = 1;
if(isset($_POST['language'])){ if(isset($this->data['language'])){
$language = $_POST['language']; $language = $this->data['language'];
} }
elseif(isset($_POST['jezik'])){ elseif(isset($this->data['jezik'])){
$language = ($_POST['jezik'] == 'en' ? 2 : 1); $language = ($this->data['jezik'] == 'en' ? 2 : 1);
} }
if(is_numeric($language)){ if(is_numeric($language)){
include_once('../../lang/'.$language.'.php'); include('../../lang/'.$language.'.php');
} }
@ -1333,7 +1468,7 @@ class ApiLogin
$Content .= $lang['confirm_user_mail_ignore']; $Content .= $lang['confirm_user_mail_ignore'];
// Ce gre slucajno za virutalko // Ce gre slucajno za virutalko
$Subject = (isVirtual()) ? $lang['confirm_user_mail_subject_virtual'] : $lang['confirm_user_mail_subject']; $Subject = (isVirtual()) ? $lang['confirm_user_mail_subject_virtual'] : $lang['confirm_user_mail_subject'];
// Ce mora admin potrditi dobi email admin in ne uporabnik! // Ce mora admin potrditi dobi email admin in ne uporabnik!
if(AppSettings::getInstance()->getSetting('confirm_registration') === true){ if(AppSettings::getInstance()->getSetting('confirm_registration') === true){
@ -1369,7 +1504,8 @@ class ApiLogin
// Za testiranje brez posiljanja maila // Za testiranje brez posiljanja maila
if(isDebug()) { if(isDebug()) {
echo $ZaMail; return $this->ajaxResponse('success',['mail' => $ZaMail, 'code' => $code]);
echo $ZaMail;
die(); die();
} }
@ -1402,11 +1538,8 @@ class ApiLogin
} }
if($ajax){ if($ajax || $this->method == 'AJAX'){
echo json_encode([ return $this->ajaxResponse('success');
'success' => 1
]);
die();
} }
@ -1632,7 +1765,7 @@ class ApiLogin
$ByeEmailSubject = 'Uspešna odjava'; $ByeEmailSubject = 'Uspešna odjava';
$result = sisplet_query("SELECT name FROM users WHERE email='$email'"); $result = sisplet_query("SELECT name FROM users WHERE email='$email'");
list ($ime) = mysqli_fetch_row($result); [$ime] = mysqli_fetch_row($result);
$PageName = AppSettings::getInstance()->getSetting('app_settings-app_name'); $PageName = AppSettings::getInstance()->getSetting('app_settings-app_name');
@ -1709,7 +1842,7 @@ class ApiLogin
global $site_domain; global $site_domain;
global $cookie_domain; global $cookie_domain;
if (isset ($_GET['email']) || isset ($_POST['email'])) { if (isset ($_GET['email']) || isset ($_POST['email']) || isset($this->email)) {
if (isset ($_GET['email'])) { if (isset ($_GET['email'])) {
$email = strtolower($_GET['email']); $email = strtolower($_GET['email']);
@ -1717,15 +1850,18 @@ class ApiLogin
if (isset ($_POST['email'])) { if (isset ($_POST['email'])) {
$email = strtolower($_POST['email']); $email = strtolower($_POST['email']);
} }
if (isset ($this->email)) {
$email = strtolower($this->email);
}
$email = CleanXSS($email); $email = CleanXSS($email);
// Ali gre za ajax klic // Ali gre za ajax klic
$ajaxKlic = false; $ajaxKlic = false;
if(!empty($_POST['ajax'])){ if(!empty($_POST['ajax']) || $this->method == 'AJAX'){
$ajaxKlic = true; $ajaxKlic = true;
if($_POST['lang'] == 'en' || $_POST['jezik'] == 'en'){ if($this->data['lang'] == 'en' || $this->data['jezik'] == 'en'){
include('../../lang/2.php'); include('../../lang/2.php');
} }
else { else {
@ -1739,24 +1875,22 @@ class ApiLogin
// Ce emaila ni v bazi // Ce emaila ni v bazi
$user_id_1ka = User::findByEmail($email); $user_id_1ka = User::findByEmail($email);
if (empty($user_id_1ka)) { if (empty($user_id_1ka)) {
if($ajaxKlic){ if($ajaxKlic || $this->method == 'AJAX'){
echo json_encode([ return $this->ajaxResponse('error', ['text' => $lang['cms_error_no_email']]);
'type' => 'error',
'text' => $lang['cms_error_no_email']
]);
}else { }else {
header('location: '.$this->page_urls['page_login_noEmail'.$this->prijava].'&email='.$email); header('location: '.$this->page_urls['page_login_noEmail'.$this->prijava].'&email='.$email);
} }
die(); die();
} else { } else {
$result = sisplet_query("SELECT name, pass, surname FROM users WHERE id='".$user_id_1ka."'"); $result = sisplet_query("SELECT name, pass, surname FROM users WHERE id='".$user_id_1ka."'");
list ($ime, $geslo, $priimek) = mysqli_fetch_row($result); [$ime, $geslo, $priimek] = mysqli_fetch_row($result);
} }
// Novo geslo sestavis iz dveh nakljucnih besed + stevilke // Novo geslo sestavis iz dveh nakljucnih besed + stevilke
include_once($site_path.'lang/words_'.$lang['language_short'].'.php'); include($site_path.'lang/words_'.$lang['language_short'].'.php');
$geslo = strtolower($words[rand(0, 999)].rand(0, 9).$words[rand(0, 999)]); $geslo = strtolower($words[rand(0, 999)].rand(0, 9).$words[rand(0, 999)]);
@ -1827,11 +1961,8 @@ class ApiLogin
} }
} }
if($ajaxKlic){ if($ajaxKlic || $this->method == 'AJAX'){
echo json_encode([ return $this->ajaxResponse('success', ['text' => $lang['lp_sent']]);
'type' => 'success',
'text' => $lang['lp_sent'].'.'
]);
}else { }else {
// Preusmerimo na stran potrditve // Preusmerimo na stran potrditve
header('location: '.$this->page_urls['page_reset_password'].'&email='.$email); header('location: '.$this->page_urls['page_reset_password'].'&email='.$email);
@ -1906,4 +2037,22 @@ class ApiLogin
header('location: '.$this->page_urls['page_reset_password_activate'].'&code='.$code.'&error=1'); header('location: '.$this->page_urls['page_reset_password_activate'].'&code='.$code.'&error=1');
} }
} }
private function ajaxResponse($type, $data = [])
{
echo json_encode([
'type' => $type,
'data' => $data
]);
die();
}
/*
* Kodiramo piškotek, ki ga posredujemo preko cUrl-ja
*/
private function cookieEncode(array $piskotek)
{
return base64_encode(serialize($piskotek));
}
} }

View File

@ -22,8 +22,11 @@ class ApiLoginController{
// Preberemo poslane podatke // Preberemo poslane podatke
//$this->processCall(); $this->processCall();
$this->processCallForm();
if(empty($this->data)) {
$this->processCallForm();
}
/*echo 'Params:'; /*echo 'Params:';
@ -31,11 +34,10 @@ class ApiLoginController{
echo '<br>Data:'; echo '<br>Data:';
var_dump($this->data); var_dump($this->data);
echo 'Metoda: '.$this->method;*/ echo 'Metoda: '.$this->method;*/
// Izvedemo akcijo // Izvedemo akcijo
$login = new ApiLogin(); $login = new ApiLogin();
$login->executeAction($this->params, $this->data); $login->executeAction($this->params, $this->data, $this->method);
} }
@ -51,6 +53,7 @@ class ApiLoginController{
// Preberemo podatke iz post-a // Preberemo podatke iz post-a
$this->data = json_decode(file_get_contents('php://input'), true); $this->data = json_decode(file_get_contents('php://input'), true);
} }
// Preberemo poslane podatke (ce posiljamo direktno iz forme) // Preberemo poslane podatke (ce posiljamo direktno iz forme)

116
frontend/drupal.php Normal file
View File

@ -0,0 +1,116 @@
<?php
//ini_set('display_errors', 1);
//ini_set('display_startup_errors', 1);
//error_reporting(E_ALL);
// V koliko ni POST request
if(empty($_SERVER['HTTP_X_REQUESTED_WITH']) || strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) != 'xmlhttprequest')
{
die();
}
require($_SERVER['DOCUMENT_ROOT'] . '/settings.php');
$api_url = $site_url . 'frontend/api/api.php';
$parts = explode("/", $_SERVER['REQUEST_URI']);
$action = end($parts);
// V kolikor nimamo get parametra pri naši poizvedbi
if(empty($_GET) && empty($action)) {
echo json_encode([
'type' => 'error',
'message' => 'Prišlo je do napake.'
]);
die();
}
/* $sporocilo = [
'type' => 'success',
'text' => 'Prijava'
];
echo json_encode($sporocilo);
die();
*/
// GET params
$params = 'action='. $action; // Funkcija, ki jo želimo izvesti
$post_data = $_POST;
if(!empty($post_data) && sizeof($post_data) > 0){
$request_method = 'POST';
$raw_post_data = http_build_query($post_data);
$post_data['method'] = "AJAX";
$raw_post_data .= '&method=AJAX';
} else {
$request_method = 'GET';
$raw_post_data = '';
}
// Pripravimo stvari za izdelavo tokena
$request_url = $api_url.'?'.$params;
$data = $request_method . $request_url .$raw_post_data;
// Nastavimo identifier in key userja
$private_key = 'NLFYb67/[pUE%W-s';
// Izracunamo hash (token)
$token = hash_hmac('sha256', $data, $private_key);
// Pripravimo klic dodamo parametra »identifikator« in »token«
$ch = curl_init($request_url.'&token='.$token);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $request_method);
if($request_method == 'POST') {
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($post_data)); // JSON string za POST
curl_setopt($ch, CURLOPT_POST, TRUE);
}
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_HEADER ,1);
//curl_setopt($ch, CURLOPT_FOLLOWLOCATION ,1);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
//curl_setopt($ch, CURLINFO_HEADER_OUT, true); //Vrne header, kar ni ok, ker drual direktno sprejmo echo
// Izvedemo klic
$result = curl_exec($ch);
curl_close($ch);
$result = json_decode($result);
if(!empty($result->data->piskotek))
{
$piskotki = piskotekDecode($result->data->piskotek);
foreach($piskotki as $piskotek){
setcookie($piskotek['ime'], $piskotek['vrednost'], $piskotek['opcije']);
}
unset($result->data->piskotek);
unset($piskotki);
}
//setcookie('testni_1ka', 'testni-vnos');
echo json_encode($result);
die();
function piskotekDecode($piskotek)
{
return unserialize(base64_decode($piskotek));
}

View File

@ -2,6 +2,8 @@ $oranzna: #FFA608;
$modra: #1E88E5; $modra: #1E88E5;
$siva: #D1D1D1; $siva: #D1D1D1;
$siva-3: #d3d3d3;
$siva-9: #999;
$siva-burger: #979797; $siva-burger: #979797;
$arnes: #E35205; $arnes: #E35205;

17
frontend/drupal9/.editorconfig Executable file
View File

@ -0,0 +1,17 @@
# Drupal editor configuration normalization
# @see http://editorconfig.org/
# This is the top-most .editorconfig file; do not search in parent directories.
root = true
# All files.
[*]
end_of_line = LF
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[composer.{json,lock}]
indent_size = 4

61
frontend/drupal9/.gitattributes vendored Executable file
View File

@ -0,0 +1,61 @@
# Drupal git normalization
# @see https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html
# @see https://www.drupal.org/node/1542048
# Normally these settings would be done with macro attributes for improved
# readability and easier maintenance. However macros can only be defined at the
# repository root directory. Drupal avoids making any assumptions about where it
# is installed.
# Define text file attributes.
# - Treat them as text.
# - Ensure no CRLF line-endings, neither on checkout nor on checkin.
# - Detect whitespace errors.
# - Exposed by default in `git diff --color` on the CLI.
# - Validate with `git diff --check`.
# - Deny applying with `git apply --whitespace=error-all`.
# - Fix automatically with `git apply --whitespace=fix`.
*.config text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.css text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.dist text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.engine text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php
*.html text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=html
*.inc text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php
*.install text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php
*.js text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.json text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.lock text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.map text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.md text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.module text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php
*.php text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php
*.po text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.profile text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php
*.script text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.sh text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php
*.sql text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.svg text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.theme text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2 diff=php
*.twig text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.txt text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.xml text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
*.yml text eol=lf whitespace=blank-at-eol,-blank-at-eof,-space-before-tab,tab-in-indent,tabwidth=2
# Define binary file attributes.
# - Do not treat them as text.
# - Include binary diff in patches instead of "binary files differ."
*.eot -text diff
*.exe -text diff
*.gif -text diff
*.gz -text diff
*.ico -text diff
*.jpeg -text diff
*.jpg -text diff
*.otf -text diff
*.phar -text diff
*.png -text diff
*.svgz -text diff
*.ttf -text diff
*.woff -text diff
*.woff2 -text diff

4
frontend/drupal9/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
web/sites/settings.local.php
web/sites/default/settings.local.php
web/sites/default/files
web/sites/default/settings.php

Binary file not shown.

144
frontend/drupal9/composer.json Executable file
View File

@ -0,0 +1,144 @@
{
"name": "drupal/recommended-project",
"description": "Project template for Drupal 9 projects with a relocated document root",
"type": "project",
"license": "GPL-2.0-or-later",
"homepage": "https://www.drupal.org/project/drupal",
"support": {
"docs": "https://www.drupal.org/docs/user_guide/en/index.html",
"chat": "https://www.drupal.org/node/314178"
},
"repositories": [
{
"type": "composer",
"url": "https://packages.drupal.org/8"
}
],
"require": {
"composer/installers": "^1.9",
"drupal/advagg": "^4.1",
"drupal/back_to_top": "^1.1",
"drupal/block_class": "^1.3",
"drupal/content_access": "^1.0-alpha3",
"drupal/core-composer-scaffold": "^9.1",
"drupal/core-project-message": "^9.1",
"drupal/core-recommended": "^9.1",
"drupal/ctools": "^3.7",
"drupal/ds": "^3.12",
"drupal/entity": "^1.2",
"drupal/entity_clone": "^1.0@beta",
"drupal/eu_cookie_compliance": "^1.14",
"drupal/facets": "^2.0",
"drupal/facets_block": "^1.4",
"drupal/google_analytics": "^3.1",
"drupal/google_cse": "^3.4",
"drupal/google_tag": "^1.4",
"drupal/imce": "^2.4",
"drupal/insert_view": "^2.0",
"drupal/languageicons": "^1.0-beta3",
"drupal/lazy": "^3.10",
"drupal/libraries": "^3.0-beta1",
"drupal/menu_block": "^1.7",
"drupal/menu_link_clone": "^3.2",
"drupal/menu_position": "^1.0-beta1",
"drupal/menu_trail_by_path": "^1.3",
"drupal/metatag": "^1.16",
"drupal/pathauto": "^1.8",
"drupal/publication_date": "^2.0-beta2",
"drupal/quicktabs": "^3.0-alpha4",
"drupal/redirect": "^1.6",
"drupal/rename_admin_paths": "^2.0",
"drupal/responsive_menu": "^4.4",
"drupal/route_specific_breadcrumb": "^2.0",
"drupal/scheduler": "^1.3",
"drupal/search_api": "^1.19",
"drupal/search_api_db": "^1.19",
"drupal/search_api_page": "^1.0-beta3",
"drupal/search_api_stats": "^1.0-alpha2",
"drupal/superfish": "^1.4",
"drupal/taxonomy_menu": "^3.x-dev",
"drupal/token": "^1.9",
"drupal/tvi": "^1.0-rc4",
"drupal/vardumper": "^1.6",
"drupal/video_embed_field": "^2.4",
"drupal/views_slideshow": "^4.8",
"drupal/xmlsitemap": "^1.0"
},
"conflict": {
"drupal/drupal": "*"
},
"minimum-stability": "stable",
"prefer-stable": true,
"config": {
"sort-packages": true,
"allow-plugins": {
"composer/installers": true,
"drupal/core-composer-scaffold": true,
"drupal/core-project-message": true
}
},
"extra": {
"drupal-scaffold": {
"locations": {
"web-root": "web/"
},
"file-mapping": {
"[web-root]/sites/development.services.yml": false
}
},
"installer-paths": {
"web/core": [
"type:drupal-core"
],
"web/libraries/{$name}": [
"type:drupal-library"
],
"web/modules/contrib/{$name}": [
"type:drupal-module"
],
"web/profiles/contrib/{$name}": [
"type:drupal-profile"
],
"web/themes/contrib/{$name}": [
"type:drupal-theme"
],
"drush/Commands/contrib/{$name}": [
"type:drupal-drush"
],
"web/modules/custom/{$name}": [
"type:drupal-custom-module"
],
"web/profiles/custom/{$name}": [
"type:drupal-custom-profile"
],
"web/themes/custom/{$name}": [
"type:drupal-custom-theme"
]
},
"drupal-core-project-message": {
"include-keys": [
"homepage",
"support"
],
"post-create-project-cmd-message": [
"<bg=blue;fg=white> </>",
"<bg=blue;fg=white> Congratulations, youve installed the Drupal codebase </>",
"<bg=blue;fg=white> from the drupal/recommended-project template! </>",
"<bg=blue;fg=white> </>",
"",
"<bg=yellow;fg=black>Next steps</>:",
" * Install the site: https://www.drupal.org/docs/8/install",
" * Read the user guide: https://www.drupal.org/docs/user_guide/en/index.html",
" * Get support: https://www.drupal.org/support",
" * Get involved with the Drupal community:",
" https://www.drupal.org/getting-involved",
" * Remove the plugin that prints this message:",
" composer remove drupal/core-project-message"
]
}
},
"require-dev": {
"drupal/twig_vardumper": "^3.0",
"drupal/devel": "^4.1"
}
}

8643
frontend/drupal9/composer.lock generated Executable file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
## MYSQL pobrišemo drupal cache v bazi
TRUNCATE TABLE cache;
TRUNCATE TABLE cache_block;
TRUNCATE TABLE cache_bootstrap;
TRUNCATE TABLE cache_field;
TRUNCATE TABLE cache_filter;
TRUNCATE TABLE cache_form;
TRUNCATE TABLE cache_image;
TRUNCATE TABLE cache_menu;
TRUNCATE TABLE cache_page;
TRUNCATE TABLE cache_path;
TRUNCATE TABLE cache_token;
TRUNCATE TABLE cache_update;
# Cache disabled
TRUNCATE TABLE cache_bootstrap;
TRUNCATE TABLE cache_bootstrap;

View File

@ -0,0 +1,19 @@
Copyright (c) 2013-2017 Alexander <iam.asm89@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,83 @@
# Stack/Cors
Library and middleware enabling cross-origin resource sharing for your
http-{foundation,kernel} using application. It attempts to implement the
[W3C Recommendation] for cross-origin resource sharing.
[W3C Recommendation]: http://www.w3.org/TR/cors/
Master [![Build Status](https://secure.travis-ci.org/asm89/stack-cors.png?branch=master)](http://travis-ci.org/asm89/stack-cors)
## Installation
Require `asm89/stack-cors` using composer.
## Usage
This package can be used as a library or as [stack middleware].
[stack middleware]: http://stackphp.com/
### Options
| Option | Description | Default value |
|------------------------|------------------------------------------------------------|---------------|
| allowedMethods | Matches the request method. | `array()` |
| allowedOrigins | Matches the request origin. | `array()` |
| allowedOriginsPatterns | Matches the request origin with `preg_match`. | `array()` |
| allowedHeaders | Sets the Access-Control-Allow-Headers response header. | `array()` |
| exposedHeaders | Sets the Access-Control-Expose-Headers response header. | `false` |
| maxAge | Sets the Access-Control-Max-Age response header. | `false` |
| supportsCredentials | Sets the Access-Control-Allow-Credentials header. | `false` |
The _allowedMethods_ and _allowedHeaders_ options are case-insensitive.
You don't need to provide both _allowedOrigins_ and _allowedOriginsPatterns_. If one of the strings passed matches, it is considered a valid origin.
If `array('*')` is provided to _allowedMethods_, _allowedOrigins_ or _allowedHeaders_ all methods / origins / headers are allowed.
### Example: using the library
```php
<?php
use Asm89\Stack\CorsService;
$cors = new CorsService(array(
'allowedHeaders' => array('x-allowed-header', 'x-other-allowed-header'),
'allowedMethods' => array('DELETE', 'GET', 'POST', 'PUT'),
'allowedOrigins' => array('localhost'),
'allowedOriginsPatterns' => array('/localhost:\d/'),
'exposedHeaders' => false,
'maxAge' => false,
'supportsCredentials' => false,
));
$cors->addActualRequestHeaders(Response $response, $origin);
$cors->handlePreflightRequest(Request $request);
$cors->isActualRequestAllowed(Request $request);
$cors->isCorsRequest(Request $request);
$cors->isPreflightRequest(Request $request);
```
## Example: using the stack middleware
```php
<?php
use Asm89\Stack\Cors;
$app = new Cors($app, array(
// you can use array('*') to allow any headers
'allowedHeaders' => array('x-allowed-header', 'x-other-allowed-header'),
// you can use array('*') to allow any methods
'allowedMethods' => array('DELETE', 'GET', 'POST', 'PUT'),
// you can use array('*') to allow requests from any origin
'allowedOrigins' => array('localhost'),
// you can enter regexes that are matched to the origin request header
'allowedOriginsPatterns' => array('/localhost:\d/'),
'exposedHeaders' => false,
'maxAge' => false,
'supportsCredentials' => false,
));
```

View File

@ -0,0 +1,43 @@
{
"name": "asm89/stack-cors",
"description": "Cross-origin resource sharing library and stack middleware",
"keywords": ["stack", "cors"],
"homepage": "https://github.com/asm89/stack-cors",
"type": "library",
"license": "MIT",
"authors": [
{
"name": "Alexander",
"email": "iam.asm89@gmail.com"
}
],
"require": {
"php": ">=5.5.9",
"symfony/http-foundation": "~2.7|~3.0|~4.0|~5.0",
"symfony/http-kernel": "~2.7|~3.0|~4.0|~5.0"
},
"require-dev": {
"phpunit/phpunit": "^5.0 || ^4.8.10",
"squizlabs/php_codesniffer": "^2.3"
},
"autoload": {
"psr-4": {
"Asm89\\Stack\\": "src/Asm89/Stack/"
}
},
"autoload-dev": {
"psr-4": {
"Asm89\\Stack\\": "test/Asm89/Stack/"
}
},
"scripts": {
"test": "phpunit",
"check-style": "phpcs -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src",
"fix-style": "phpcbf -p --standard=PSR2 --runtime-set ignore_errors_on_exit 1 --runtime-set ignore_warnings_on_exit 1 src"
},
"extra": {
"branch-alias": {
"dev-master": "1.2-dev"
}
}
}

View File

@ -0,0 +1,64 @@
<?php
/*
* This file is part of asm89/stack-cors.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Stack;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class Cors implements HttpKernelInterface
{
/**
* @var \Symfony\Component\HttpKernel\HttpKernelInterface
*/
private $app;
/**
* @var \Asm89\Stack\CorsService
*/
private $cors;
private $defaultOptions = array(
'allowedHeaders' => array(),
'allowedMethods' => array(),
'allowedOrigins' => array(),
'allowedOriginsPatterns' => array(),
'exposedHeaders' => false,
'maxAge' => false,
'supportsCredentials' => false,
);
public function __construct(HttpKernelInterface $app, array $options = array())
{
$this->app = $app;
$this->cors = new CorsService(array_merge($this->defaultOptions, $options));
}
public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true)
{
if (!$this->cors->isCorsRequest($request)) {
return $this->app->handle($request, $type, $catch);
}
if ($this->cors->isPreflightRequest($request)) {
return $this->cors->handlePreflightRequest($request);
}
if (!$this->cors->isActualRequestAllowed($request)) {
return new Response('Not allowed.', 403);
}
$response = $this->app->handle($request, $type, $catch);
return $this->cors->addActualRequestHeaders($response, $request);
}
}

View File

@ -0,0 +1,205 @@
<?php
/*
* This file is part of asm89/stack-cors.
*
* (c) Alexander <iam.asm89@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Asm89\Stack;
use Symfony\Component\HttpKernel\HttpKernelInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class CorsService
{
private $options;
public function __construct(array $options = array())
{
$this->options = $this->normalizeOptions($options);
}
private function normalizeOptions(array $options = array())
{
$options += array(
'allowedOrigins' => array(),
'allowedOriginsPatterns' => array(),
'supportsCredentials' => false,
'allowedHeaders' => array(),
'exposedHeaders' => array(),
'allowedMethods' => array(),
'maxAge' => 0,
);
// normalize array('*') to true
if (in_array('*', $options['allowedOrigins'])) {
$options['allowedOrigins'] = true;
}
if (in_array('*', $options['allowedHeaders'])) {
$options['allowedHeaders'] = true;
} else {
$options['allowedHeaders'] = array_map('strtolower', $options['allowedHeaders']);
}
if (in_array('*', $options['allowedMethods'])) {
$options['allowedMethods'] = true;
} else {
$options['allowedMethods'] = array_map('strtoupper', $options['allowedMethods']);
}
return $options;
}
public function isActualRequestAllowed(Request $request)
{
return $this->checkOrigin($request);
}
public function isCorsRequest(Request $request)
{
return $request->headers->has('Origin') && !$this->isSameHost($request);
}
public function isPreflightRequest(Request $request)
{
return $this->isCorsRequest($request)
&& $request->getMethod() === 'OPTIONS'
&& $request->headers->has('Access-Control-Request-Method');
}
public function addActualRequestHeaders(Response $response, Request $request)
{
if (!$this->checkOrigin($request)) {
return $response;
}
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
if (!$response->headers->has('Vary')) {
$response->headers->set('Vary', 'Origin');
} else {
$response->headers->set('Vary', $response->headers->get('Vary') . ', Origin');
}
if ($this->options['supportsCredentials']) {
$response->headers->set('Access-Control-Allow-Credentials', 'true');
}
if ($this->options['exposedHeaders']) {
$response->headers->set('Access-Control-Expose-Headers', implode(', ', $this->options['exposedHeaders']));
}
return $response;
}
public function handlePreflightRequest(Request $request)
{
if (true !== $check = $this->checkPreflightRequestConditions($request)) {
return $check;
}
return $this->buildPreflightCheckResponse($request);
}
private function buildPreflightCheckResponse(Request $request)
{
$response = new Response();
if ($this->options['supportsCredentials']) {
$response->headers->set('Access-Control-Allow-Credentials', 'true');
}
$response->headers->set('Access-Control-Allow-Origin', $request->headers->get('Origin'));
if ($this->options['maxAge']) {
$response->headers->set('Access-Control-Max-Age', $this->options['maxAge']);
}
$allowMethods = $this->options['allowedMethods'] === true
? strtoupper($request->headers->get('Access-Control-Request-Method'))
: implode(', ', $this->options['allowedMethods']);
$response->headers->set('Access-Control-Allow-Methods', $allowMethods);
$allowHeaders = $this->options['allowedHeaders'] === true
? strtoupper($request->headers->get('Access-Control-Request-Headers'))
: implode(', ', $this->options['allowedHeaders']);
$response->headers->set('Access-Control-Allow-Headers', $allowHeaders);
$response->setStatusCode(204);
return $response;
}
private function checkPreflightRequestConditions(Request $request)
{
if (!$this->checkOrigin($request)) {
return $this->createBadRequestResponse(403, 'Origin not allowed');
}
if (!$this->checkMethod($request)) {
return $this->createBadRequestResponse(405, 'Method not allowed');
}
$requestHeaders = array();
// if allowedHeaders has been set to true ('*' allow all flag) just skip this check
if ($this->options['allowedHeaders'] !== true && $request->headers->has('Access-Control-Request-Headers')) {
$headers = strtolower($request->headers->get('Access-Control-Request-Headers'));
$requestHeaders = array_filter(explode(',', $headers));
foreach ($requestHeaders as $header) {
if (!in_array(trim($header), $this->options['allowedHeaders'])) {
return $this->createBadRequestResponse(403, 'Header not allowed');
}
}
}
return true;
}
private function createBadRequestResponse($code, $reason = '')
{
return new Response($reason, $code);
}
private function isSameHost(Request $request)
{
return $request->headers->get('Origin') === $request->getSchemeAndHttpHost();
}
private function checkOrigin(Request $request)
{
if ($this->options['allowedOrigins'] === true) {
// allow all '*' flag
return true;
}
$origin = $request->headers->get('Origin');
if (in_array($origin, $this->options['allowedOrigins'])) {
return true;
}
foreach ($this->options['allowedOriginsPatterns'] as $pattern) {
if (preg_match($pattern, $origin)) {
return true;
}
}
return false;
}
private function checkMethod(Request $request)
{
if ($this->options['allowedMethods'] === true) {
// allow all '*' flag
return true;
}
$requestMethod = strtoupper($request->headers->get('Access-Control-Request-Method'));
return in_array($requestMethod, $this->options['allowedMethods']);
}
}

7
frontend/drupal9/vendor/autoload.php vendored Executable file
View File

@ -0,0 +1,7 @@
<?php
// autoload.php @generated by Composer
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInitd428c8960f3d72900807e718545d9f8d::getLoader();

117
frontend/drupal9/vendor/bin/dcg vendored Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../chi-teck/drupal-code-generator/bin/dcg)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/chi-teck/drupal-code-generator/bin/dcg');
exit(0);
}
}
include __DIR__ . '/..'.'/chi-teck/drupal-code-generator/bin/dcg';

117
frontend/drupal9/vendor/bin/drush vendored Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../drush/drush/drush)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/drush/drush/drush');
exit(0);
}
}
include __DIR__ . '/..'.'/drush/drush/drush';

117
frontend/drupal9/vendor/bin/php-parse vendored Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../nikic/php-parser/bin/php-parse)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse');
exit(0);
}
}
include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse';

117
frontend/drupal9/vendor/bin/psysh vendored Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../psy/psysh/bin/psysh)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/psy/psysh/bin/psysh');
exit(0);
}
}
include __DIR__ . '/..'.'/psy/psysh/bin/psysh';

117
frontend/drupal9/vendor/bin/release vendored Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../consolidation/self-update/scripts/release)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/consolidation/self-update/scripts/release');
exit(0);
}
}
include __DIR__ . '/..'.'/consolidation/self-update/scripts/release';

117
frontend/drupal9/vendor/bin/robo vendored Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../consolidation/robo/robo)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/consolidation/robo/robo');
exit(0);
}
}
include __DIR__ . '/..'.'/consolidation/robo/robo';

117
frontend/drupal9/vendor/bin/security-checker vendored Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../enlightn/security-checker/security-checker)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/enlightn/security-checker/security-checker');
exit(0);
}
}
include __DIR__ . '/..'.'/enlightn/security-checker/security-checker';

117
frontend/drupal9/vendor/bin/var-dump-server vendored Executable file
View File

@ -0,0 +1,117 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../symfony/var-dumper/Resources/bin/var-dump-server)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper')) {
include("phpvfscomposer://" . __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server');
exit(0);
}
}
include __DIR__ . '/..'.'/symfony/var-dumper/Resources/bin/var-dump-server';

View File

@ -0,0 +1,8 @@
version: 2
updates:
- package-ecosystem: composer
directory: "/"
schedule:
interval: daily
time: "22:00"
open-pull-requests-limit: 10

View File

@ -0,0 +1,46 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
jobs:
tests :
runs-on: ubuntu-latest
strategy:
matrix:
php:
- "7.4"
- "8.0"
- "8.1"
dependency-mode:
- prefer-stable
- prefer-lowest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install PHP with extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
tools: composer:v2
- name: Install dependencies
run: composer install --ansi
- name: Update dependencies
run: composer update --${{ matrix.dependency-mode }} --ansi
- name: Run Twig linter
run: ./vendor/bin/twigcs ./templates
- name: Run code sniffer
run: vendor/bin/phpcs -p --colors
- name: Run tests
run: ./vendor/bin/phpunit --colors=always

View File

@ -0,0 +1,339 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Lesser General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.

View File

@ -0,0 +1,33 @@
# Drupal Code Generator
[![Tests](https://github.com/Chi-teck/drupal-code-generator/workflows/Tests/badge.svg)](https://github.com/Chi-teck/drupal-code-generator/actions?query=workflow%3ATests)
[![Total Downloads](https://poser.pugx.org/chi-teck/drupal-code-generator/downloads)](//packagist.org/packages/chi-teck/drupal-code-generator)
[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%207.4-8892BF.svg?style=flat)](https://php.net/)
A command line code generator for Drupal.
## Installation
```
composer require chi-teck/drupal-code-generator --dev
```
## Usage
```shell
# Display navigation.
./vendor/bin/dcg
# Call generator directly.
./vendor/bin/dcg plugin:field:widget
# Generate code non-interactively.
/vendor/bin/dcg config-form -a Example -a example -a SettingsForm -a No
```
## Compatibility
DCG|PHP|Symfony|Twig|Drupal|Drush
:-:|:-:|:-:|:-:|:-:|:-:
1|7.1+|3, 4|1, 2|7, 8|9, 10
2|7.4+|4, 5|2, 3|7, 9|11+
## License
GNU General Public License, version 2 or later.

View File

@ -0,0 +1,29 @@
#!/usr/bin/env php
<?php
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\BootstrapHandler;
use DrupalCodeGenerator\ClassResolver\SimpleClassResolver;
use DrupalCodeGenerator\Command\GenerateCompletion;
use DrupalCodeGenerator\Command\Navigation;
use DrupalCodeGenerator\GeneratorFactory;
use Psr\Log\NullLogger;
// The autoloader may have a different location if DCG is installed as a local
// Composer package.
$class_loader = file_exists(__DIR__ . '/../vendor/autoload.php')
? require __DIR__ . '/../vendor/autoload.php'
: require __DIR__ . '/../../../autoload.php';
$bootstrap_handler = new BootstrapHandler($class_loader);
$container = $bootstrap_handler->bootstrap();
$generator_factory = new GeneratorFactory(new SimpleClassResolver(), new NullLogger());
$generators = $generator_factory->getGenerators([Application::ROOT . '/src/Command'], Application::GENERATOR_NAMESPACE);
$application = Application::create($container);
$application->addCommands($generators);
$application->add(new GenerateCompletion());
$application->add(new Navigation());
$application->setDefaultCommand('navigation');
$application->run();

View File

@ -0,0 +1,52 @@
{
"name": "chi-teck/drupal-code-generator",
"description": "Drupal code generator",
"license": "GPL-2.0-or-later",
"bin": [
"bin/dcg"
],
"autoload": {
"psr-4": {
"DrupalCodeGenerator\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"DrupalCodeGenerator\\Tests\\": "tests/dcg"
}
},
"config": {
"sort-packages": true,
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": false
}
},
"extra": {
"branch-alias": {
"dev-master": "2.x-dev"
}
},
"require": {
"php": ">=7.4",
"ext-json": "*",
"psr/log": "^1.1 || ^2.0 || ^3.0",
"symfony/console": "^4.4.15 || ^5.1 || ^6.0",
"symfony/filesystem": "^4.4 || ^5.1 || ^6",
"symfony/polyfill-php80": "^1.23",
"symfony/string": "^5.1 || ^6",
"twig/twig": "^2.14.11 || ^3.1"
},
"require-dev": {
"chi-teck/drupal-coder-extension": "^1.2",
"drupal/coder": "^8.3.14",
"friendsoftwig/twigcs": "dev-master",
"phpspec/prophecy-phpunit": "^2.0",
"phpunit/phpunit": "^9.4",
"squizlabs/php_codesniffer": "^3.5",
"symfony/var-dumper": "^5.2 || ^6.0",
"symfony/yaml": "^5.2 || ^6.0"
},
"conflict": {
"squizlabs/php_codesniffer": "<3.6"
}
}

View File

@ -0,0 +1,443 @@
_dcg()
{
local cur script coms opts com
COMPREPLY=()
_get_comp_words_by_ref -n : cur words
# for an alias, get the real script behind it
if [[ $(type -t ${words[0]}) == "alias" ]]; then
script=$(alias ${words[0]} | sed -E "s/alias ${words[0]}='(.*)'/\1/")
else
script=${words[0]}
fi
# lookup for command
for word in ${words[@]:1}; do
if [[ $word != -* ]]; then
com=$word
break
fi
done
# completing for an option
if [[ ${cur} == --* ]] ; then
opts="--help --quiet --verbose --version --ansi --no-ansi --no-interaction"
case "$com" in
composer)
opts="${opts} --directory --answer --dry-run"
;;
configuration-entity)
opts="${opts} --directory --answer --dry-run"
;;
content-entity)
opts="${opts} --directory --answer --dry-run"
;;
controller)
opts="${opts} --directory --answer --dry-run"
;;
field)
opts="${opts} --directory --answer --dry-run"
;;
generate-completion)
opts="${opts} --shell"
;;
help)
opts="${opts} --format --raw"
;;
hook)
opts="${opts} --directory --answer --dry-run"
;;
install-file)
opts="${opts} --directory --answer --dry-run"
;;
javascript)
opts="${opts} --directory --answer --dry-run"
;;
layout)
opts="${opts} --directory --answer --dry-run"
;;
list)
opts="${opts} --raw --format"
;;
module)
opts="${opts} --directory --answer --dry-run"
;;
module-file)
opts="${opts} --directory --answer --dry-run"
;;
navigation)
opts="${opts} --directory"
;;
plugin-manager)
opts="${opts} --directory --answer --dry-run"
;;
render-element)
opts="${opts} --directory --answer --dry-run"
;;
service-provider)
opts="${opts} --directory --answer --dry-run"
;;
template)
opts="${opts} --directory --answer --dry-run"
;;
theme)
opts="${opts} --directory --answer --dry-run"
;;
theme-file)
opts="${opts} --directory --answer --dry-run"
;;
theme-settings)
opts="${opts} --directory --answer --dry-run"
;;
console:dcg-command)
opts="${opts} --directory --answer --dry-run"
;;
console:drupal-console-command)
opts="${opts} --directory --answer --dry-run"
;;
console:drush-command)
opts="${opts} --directory --answer --dry-run"
;;
console:symfony-command)
opts="${opts} --directory --answer --dry-run"
;;
form:config)
opts="${opts} --directory --answer --dry-run"
;;
form:confirm)
opts="${opts} --directory --answer --dry-run"
;;
form:simple)
opts="${opts} --directory --answer --dry-run"
;;
misc:apache-virtual-host)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:ctools-plugin:access)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:ctools-plugin:content-type)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:ctools-plugin:relationship)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:hook)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:install-file)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:javascript)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:module)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:module-file)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:module-info)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:settings.php)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:template.php)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:test)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:theme)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:theme-info)
opts="${opts} --directory --answer --dry-run"
;;
misc:d7:views-plugin:argument-default)
opts="${opts} --directory --answer --dry-run"
;;
misc:html-page)
opts="${opts} --directory --answer --dry-run"
;;
misc:nginx-virtual-host)
opts="${opts} --directory --answer --dry-run"
;;
misc:project)
opts="${opts} --directory --answer --dry-run"
;;
plugin:action)
opts="${opts} --directory --answer --dry-run"
;;
plugin:block)
opts="${opts} --directory --answer --dry-run"
;;
plugin:ckeditor)
opts="${opts} --directory --answer --dry-run"
;;
plugin:condition)
opts="${opts} --directory --answer --dry-run"
;;
plugin:constraint)
opts="${opts} --directory --answer --dry-run"
;;
plugin:entity-reference-selection)
opts="${opts} --directory --answer --dry-run"
;;
plugin:field:formatter)
opts="${opts} --directory --answer --dry-run"
;;
plugin:field:type)
opts="${opts} --directory --answer --dry-run"
;;
plugin:field:widget)
opts="${opts} --directory --answer --dry-run"
;;
plugin:filter)
opts="${opts} --directory --answer --dry-run"
;;
plugin:menu-link)
opts="${opts} --directory --answer --dry-run"
;;
plugin:migrate:destination)
opts="${opts} --directory --answer --dry-run"
;;
plugin:migrate:process)
opts="${opts} --directory --answer --dry-run"
;;
plugin:migrate:source)
opts="${opts} --directory --answer --dry-run"
;;
plugin:queue-worker)
opts="${opts} --directory --answer --dry-run"
;;
plugin:rest-resource)
opts="${opts} --directory --answer --dry-run"
;;
plugin:views:argument-default)
opts="${opts} --directory --answer --dry-run"
;;
plugin:views:field)
opts="${opts} --directory --answer --dry-run"
;;
plugin:views:style)
opts="${opts} --directory --answer --dry-run"
;;
service:access-checker)
opts="${opts} --directory --answer --dry-run"
;;
service:breadcrumb-builder)
opts="${opts} --directory --answer --dry-run"
;;
service:cache-context)
opts="${opts} --directory --answer --dry-run"
;;
service:custom)
opts="${opts} --directory --answer --dry-run"
;;
service:event-subscriber)
opts="${opts} --directory --answer --dry-run"
;;
service:logger)
opts="${opts} --directory --answer --dry-run"
;;
service:middleware)
opts="${opts} --directory --answer --dry-run"
;;
service:param-converter)
opts="${opts} --directory --answer --dry-run"
;;
service:path-processor)
opts="${opts} --directory --answer --dry-run"
;;
service:request-policy)
opts="${opts} --directory --answer --dry-run"
;;
service:response-policy)
opts="${opts} --directory --answer --dry-run"
;;
service:route-subscriber)
opts="${opts} --directory --answer --dry-run"
;;
service:theme-negotiator)
opts="${opts} --directory --answer --dry-run"
;;
service:twig-extension)
opts="${opts} --directory --answer --dry-run"
;;
service:uninstall-validator)
opts="${opts} --directory --answer --dry-run"
;;
test:browser)
opts="${opts} --directory --answer --dry-run"
;;
test:kernel)
opts="${opts} --directory --answer --dry-run"
;;
test:nightwatch)
opts="${opts} --directory --answer --dry-run"
;;
test:unit)
opts="${opts} --directory --answer --dry-run"
;;
test:webdriver)
opts="${opts} --directory --answer --dry-run"
;;
yml:breakpoints)
opts="${opts} --directory --answer --dry-run"
;;
yml:links:action)
opts="${opts} --directory --answer --dry-run"
;;
yml:links:contextual)
opts="${opts} --directory --answer --dry-run"
;;
yml:links:menu)
opts="${opts} --directory --answer --dry-run"
;;
yml:links:task)
opts="${opts} --directory --answer --dry-run"
;;
yml:module-info)
opts="${opts} --directory --answer --dry-run"
;;
yml:module-libraries)
opts="${opts} --directory --answer --dry-run"
;;
yml:permissions)
opts="${opts} --directory --answer --dry-run"
;;
yml:routing)
opts="${opts} --directory --answer --dry-run"
;;
yml:services)
opts="${opts} --directory --answer --dry-run"
;;
yml:theme-info)
opts="${opts} --directory --answer --dry-run"
;;
yml:theme-libraries)
opts="${opts} --directory --answer --dry-run"
;;
esac
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
__ltrim_colon_completions "$cur"
return 0;
fi
# completing for a command
if [[ $cur == $com ]]; then
coms="composer configuration-entity content-entity controller field generate-completion help hook install-file javascript layout list module module-file navigation plugin-manager render-element service-provider template theme theme-file theme-settings console:dcg-command console:drupal-console-command console:drush-command console:symfony-command form:config form:confirm form:simple misc:apache-virtual-host misc:d7:ctools-plugin:access misc:d7:ctools-plugin:content-type misc:d7:ctools-plugin:relationship misc:d7:hook misc:d7:install-file misc:d7:javascript misc:d7:module misc:d7:module-file misc:d7:module-info misc:d7:settings.php misc:d7:template.php misc:d7:test misc:d7:theme misc:d7:theme-info misc:d7:views-plugin:argument-default misc:html-page misc:nginx-virtual-host misc:project plugin:action plugin:block plugin:ckeditor plugin:condition plugin:constraint plugin:entity-reference-selection plugin:field:formatter plugin:field:type plugin:field:widget plugin:filter plugin:menu-link plugin:migrate:destination plugin:migrate:process plugin:migrate:source plugin:queue-worker plugin:rest-resource plugin:views:argument-default plugin:views:field plugin:views:style service:access-checker service:breadcrumb-builder service:cache-context service:custom service:event-subscriber service:logger service:middleware service:param-converter service:path-processor service:request-policy service:response-policy service:route-subscriber service:theme-negotiator service:twig-extension service:uninstall-validator test:browser test:kernel test:nightwatch test:unit test:webdriver yml:breakpoints yml:links:action yml:links:contextual yml:links:menu yml:links:task yml:module-info yml:module-libraries yml:permissions yml:routing yml:services yml:theme-info yml:theme-libraries"
COMPREPLY=($(compgen -W "${coms}" -- ${cur}))
__ltrim_colon_completions "$cur"
return 0
fi
}
complete -o default -F _dcg dcg

View File

@ -0,0 +1,609 @@
function __fish_dcg_no_subcommand
for i in (commandline -opc)
if contains -- $i composer configuration-entity content-entity controller field generate-completion help hook install-file javascript layout list module module-file navigation plugin-manager render-element service-provider template theme theme-file theme-settings console:dcg-command console:drupal-console-command console:drush-command console:symfony-command form:config form:confirm form:simple misc:apache-virtual-host misc:d7:ctools-plugin:access misc:d7:ctools-plugin:content-type misc:d7:ctools-plugin:relationship misc:d7:hook misc:d7:install-file misc:d7:javascript misc:d7:module misc:d7:module-file misc:d7:module-info misc:d7:settings.php misc:d7:template.php misc:d7:test misc:d7:theme misc:d7:theme-info misc:d7:views-plugin:argument-default misc:html-page misc:nginx-virtual-host misc:project plugin:action plugin:block plugin:ckeditor plugin:condition plugin:constraint plugin:entity-reference-selection plugin:field:formatter plugin:field:type plugin:field:widget plugin:filter plugin:menu-link plugin:migrate:destination plugin:migrate:process plugin:migrate:source plugin:queue-worker plugin:rest-resource plugin:views:argument-default plugin:views:field plugin:views:style service:access-checker service:breadcrumb-builder service:cache-context service:custom service:event-subscriber service:logger service:middleware service:param-converter service:path-processor service:request-policy service:response-policy service:route-subscriber service:theme-negotiator service:twig-extension service:uninstall-validator test:browser test:kernel test:nightwatch test:unit test:webdriver yml:breakpoints yml:links:action yml:links:contextual yml:links:menu yml:links:task yml:module-info yml:module-libraries yml:permissions yml:routing yml:services yml:theme-info yml:theme-libraries
return 1
end
end
return 0
end
# global options
complete -c dcg -n '__fish_dcg_no_subcommand' -l help -d 'Display this help message'
complete -c dcg -n '__fish_dcg_no_subcommand' -l quiet -d 'Do not output any message'
complete -c dcg -n '__fish_dcg_no_subcommand' -l verbose -d 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'
complete -c dcg -n '__fish_dcg_no_subcommand' -l version -d 'Display this application version'
complete -c dcg -n '__fish_dcg_no_subcommand' -l ansi -d 'Force ANSI output'
complete -c dcg -n '__fish_dcg_no_subcommand' -l no-ansi -d 'Disable ANSI output'
complete -c dcg -n '__fish_dcg_no_subcommand' -l no-interaction -d 'Do not ask any interactive question'
# commands
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a composer -d 'Generates a composer.json file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a configuration-entity -d 'Generates configuration entity module'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a content-entity -d 'Generates content entity module'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a controller -d 'Generates a controller'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a field -d 'Generates a field'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a generate-completion -d 'Generates shell completion'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a help -d 'Displays help for a command'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a hook -d 'Generates a hook'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a install-file -d 'Generates an install file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a javascript -d 'Generates Drupal JavaScript file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a layout -d 'Generates a layout'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a list -d 'Lists commands'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a module -d 'Generates Drupal module'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a module-file -d 'Generates a module file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a navigation -d 'Command line code generator'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin-manager -d 'Generates plugin manager'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a render-element -d 'Generates Drupal render element'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service-provider -d 'Generates a service provider'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a template -d 'Generates a template'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a theme -d 'Generates Drupal theme'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a theme-file -d 'Generates a theme file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a theme-settings -d 'Generates Drupal theme-settings.php file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a console:dcg-command -d 'Generates DCG command'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a console:drupal-console-command -d 'Generates Drupal Console command'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a console:drush-command -d 'Generates Drush command'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a console:symfony-command -d 'Generates Symfony console command'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a form:config -d 'Generates a configuration form'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a form:confirm -d 'Generates a confirmation form'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a form:simple -d 'Generates simple form'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:apache-virtual-host -d 'Generates an Apache site configuration file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:ctools-plugin:access -d 'Generates CTools access plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:ctools-plugin:content-type -d 'Generates CTools content type plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:ctools-plugin:relationship -d 'Generates CTools relationship plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:hook -d 'Generates a hook'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:install-file -d 'Generates Drupal 7 install file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:javascript -d 'Generates Drupal 7 JavaScript file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:module -d 'Generates Drupal 7 module'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:module-file -d 'Generates Drupal 7 module file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:module-info -d 'Generates Drupal 7 info file for a module'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:settings.php -d 'Generates Drupal 7 settings.php file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:template.php -d 'Generates Drupal 7 template.php file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:test -d 'Generates Drupal 7 test case'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:theme -d 'Generates Drupal 7 theme'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:theme-info -d 'Generates info file for a Drupal 7 theme'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:d7:views-plugin:argument-default -d 'Generates Drupal 7 argument default views plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:html-page -d 'Generates a simple html page'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:nginx-virtual-host -d 'Generates an Nginx site configuration file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a misc:project -d 'Generates a composer project'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:action -d 'Generates action plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:block -d 'Generates block plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:ckeditor -d 'Generates CKEditor plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:condition -d 'Generates condition plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:constraint -d 'Generates constraint plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:entity-reference-selection -d 'Generates entity reference selection plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:field:formatter -d 'Generates field formatter plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:field:type -d 'Generates field type plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:field:widget -d 'Generates field widget plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:filter -d 'Generates filter plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:menu-link -d 'Generates menu-link plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:migrate:destination -d 'Generates migrate destination plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:migrate:process -d 'Generates migrate process plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:migrate:source -d 'Generates migrate source plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:queue-worker -d 'Generates queue worker plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:rest-resource -d 'Generates rest resource plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:views:argument-default -d 'Generates views default argument plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:views:field -d 'Generates views field plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a plugin:views:style -d 'Generates views style plugin'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:access-checker -d 'Generates an access checker service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:breadcrumb-builder -d 'Generates a breadcrumb builder service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:cache-context -d 'Generates a cache context service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:custom -d 'Generates a custom Drupal service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:event-subscriber -d 'Generates an event subscriber'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:logger -d 'Generates a logger service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:middleware -d 'Generates a middleware'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:param-converter -d 'Generates a param converter service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:path-processor -d 'Generates a path processor service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:request-policy -d 'Generates a request policy service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:response-policy -d 'Generates a response policy service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:route-subscriber -d 'Generates a route subscriber'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:theme-negotiator -d 'Generates a theme negotiator'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:twig-extension -d 'Generates Twig extension service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a service:uninstall-validator -d 'Generates a uninstall validator service'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a test:browser -d 'Generates a browser based test'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a test:kernel -d 'Generates a kernel based test'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a test:nightwatch -d 'Generates a nightwatch test'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a test:unit -d 'Generates a unit test'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a test:webdriver -d 'Generates a test that supports JavaScript'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:breakpoints -d 'Generates a breakpoints yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:links:action -d 'Generates a links.action yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:links:contextual -d 'Generates links.contextual yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:links:menu -d 'Generates a links.menu yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:links:task -d 'Generates a links.task yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:module-info -d 'Generates a module info yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:module-libraries -d 'Generates module libraries yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:permissions -d 'Generates a permissions yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:routing -d 'Generates a routing yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:services -d 'Generates a services yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:theme-info -d 'Generates a theme info yml file'
complete -c dcg -f -n '__fish_dcg_no_subcommand' -a yml:theme-libraries -d 'Generates theme libraries yml file'
# command options
# composer
complete -c dcg -A -n '__fish_seen_subcommand_from composer' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from composer' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from composer' -l dry-run -d 'Output the generated code but not save it to file system'
# configuration-entity
complete -c dcg -A -n '__fish_seen_subcommand_from configuration-entity' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from configuration-entity' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from configuration-entity' -l dry-run -d 'Output the generated code but not save it to file system'
# content-entity
complete -c dcg -A -n '__fish_seen_subcommand_from content-entity' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from content-entity' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from content-entity' -l dry-run -d 'Output the generated code but not save it to file system'
# controller
complete -c dcg -A -n '__fish_seen_subcommand_from controller' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from controller' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from controller' -l dry-run -d 'Output the generated code but not save it to file system'
# field
complete -c dcg -A -n '__fish_seen_subcommand_from field' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from field' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from field' -l dry-run -d 'Output the generated code but not save it to file system'
# generate-completion
complete -c dcg -A -n '__fish_seen_subcommand_from generate-completion' -l shell -d 'Shell type'
# help
complete -c dcg -A -n '__fish_seen_subcommand_from help' -l format -d 'The output format (txt, xml, json, or md)'
complete -c dcg -A -n '__fish_seen_subcommand_from help' -l raw -d 'To output raw command help'
# hook
complete -c dcg -A -n '__fish_seen_subcommand_from hook' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from hook' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from hook' -l dry-run -d 'Output the generated code but not save it to file system'
# install-file
complete -c dcg -A -n '__fish_seen_subcommand_from install-file' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from install-file' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from install-file' -l dry-run -d 'Output the generated code but not save it to file system'
# javascript
complete -c dcg -A -n '__fish_seen_subcommand_from javascript' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from javascript' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from javascript' -l dry-run -d 'Output the generated code but not save it to file system'
# layout
complete -c dcg -A -n '__fish_seen_subcommand_from layout' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from layout' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from layout' -l dry-run -d 'Output the generated code but not save it to file system'
# list
complete -c dcg -A -n '__fish_seen_subcommand_from list' -l raw -d 'To output raw command list'
complete -c dcg -A -n '__fish_seen_subcommand_from list' -l format -d 'The output format (txt, xml, json, or md)'
# module
complete -c dcg -A -n '__fish_seen_subcommand_from module' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from module' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from module' -l dry-run -d 'Output the generated code but not save it to file system'
# module-file
complete -c dcg -A -n '__fish_seen_subcommand_from module-file' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from module-file' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from module-file' -l dry-run -d 'Output the generated code but not save it to file system'
# navigation
complete -c dcg -A -n '__fish_seen_subcommand_from navigation' -l directory -d 'Working directory'
# plugin-manager
complete -c dcg -A -n '__fish_seen_subcommand_from plugin-manager' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin-manager' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin-manager' -l dry-run -d 'Output the generated code but not save it to file system'
# render-element
complete -c dcg -A -n '__fish_seen_subcommand_from render-element' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from render-element' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from render-element' -l dry-run -d 'Output the generated code but not save it to file system'
# service-provider
complete -c dcg -A -n '__fish_seen_subcommand_from service-provider' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service-provider' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service-provider' -l dry-run -d 'Output the generated code but not save it to file system'
# template
complete -c dcg -A -n '__fish_seen_subcommand_from template' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from template' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from template' -l dry-run -d 'Output the generated code but not save it to file system'
# theme
complete -c dcg -A -n '__fish_seen_subcommand_from theme' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from theme' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from theme' -l dry-run -d 'Output the generated code but not save it to file system'
# theme-file
complete -c dcg -A -n '__fish_seen_subcommand_from theme-file' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from theme-file' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from theme-file' -l dry-run -d 'Output the generated code but not save it to file system'
# theme-settings
complete -c dcg -A -n '__fish_seen_subcommand_from theme-settings' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from theme-settings' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from theme-settings' -l dry-run -d 'Output the generated code but not save it to file system'
# console:dcg-command
complete -c dcg -A -n '__fish_seen_subcommand_from console:dcg-command' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from console:dcg-command' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from console:dcg-command' -l dry-run -d 'Output the generated code but not save it to file system'
# console:drupal-console-command
complete -c dcg -A -n '__fish_seen_subcommand_from console:drupal-console-command' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from console:drupal-console-command' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from console:drupal-console-command' -l dry-run -d 'Output the generated code but not save it to file system'
# console:drush-command
complete -c dcg -A -n '__fish_seen_subcommand_from console:drush-command' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from console:drush-command' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from console:drush-command' -l dry-run -d 'Output the generated code but not save it to file system'
# console:symfony-command
complete -c dcg -A -n '__fish_seen_subcommand_from console:symfony-command' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from console:symfony-command' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from console:symfony-command' -l dry-run -d 'Output the generated code but not save it to file system'
# form:config
complete -c dcg -A -n '__fish_seen_subcommand_from form:config' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from form:config' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from form:config' -l dry-run -d 'Output the generated code but not save it to file system'
# form:confirm
complete -c dcg -A -n '__fish_seen_subcommand_from form:confirm' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from form:confirm' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from form:confirm' -l dry-run -d 'Output the generated code but not save it to file system'
# form:simple
complete -c dcg -A -n '__fish_seen_subcommand_from form:simple' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from form:simple' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from form:simple' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:apache-virtual-host
complete -c dcg -A -n '__fish_seen_subcommand_from misc:apache-virtual-host' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:apache-virtual-host' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:apache-virtual-host' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:ctools-plugin:access
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:ctools-plugin:access' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:ctools-plugin:access' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:ctools-plugin:access' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:ctools-plugin:content-type
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:ctools-plugin:content-type' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:ctools-plugin:content-type' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:ctools-plugin:content-type' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:ctools-plugin:relationship
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:ctools-plugin:relationship' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:ctools-plugin:relationship' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:ctools-plugin:relationship' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:hook
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:hook' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:hook' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:hook' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:install-file
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:install-file' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:install-file' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:install-file' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:javascript
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:javascript' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:javascript' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:javascript' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:module
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:module' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:module' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:module' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:module-file
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:module-file' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:module-file' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:module-file' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:module-info
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:module-info' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:module-info' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:module-info' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:settings.php
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:settings.php' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:settings.php' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:settings.php' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:template.php
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:template.php' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:template.php' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:template.php' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:test
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:test' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:test' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:test' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:theme
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:theme' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:theme' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:theme' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:theme-info
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:theme-info' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:theme-info' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:theme-info' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:d7:views-plugin:argument-default
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:views-plugin:argument-default' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:views-plugin:argument-default' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:d7:views-plugin:argument-default' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:html-page
complete -c dcg -A -n '__fish_seen_subcommand_from misc:html-page' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:html-page' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:html-page' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:nginx-virtual-host
complete -c dcg -A -n '__fish_seen_subcommand_from misc:nginx-virtual-host' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:nginx-virtual-host' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:nginx-virtual-host' -l dry-run -d 'Output the generated code but not save it to file system'
# misc:project
complete -c dcg -A -n '__fish_seen_subcommand_from misc:project' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:project' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from misc:project' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:action
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:action' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:action' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:action' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:block
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:block' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:block' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:block' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:ckeditor
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:ckeditor' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:ckeditor' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:ckeditor' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:condition
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:condition' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:condition' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:condition' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:constraint
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:constraint' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:constraint' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:constraint' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:entity-reference-selection
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:entity-reference-selection' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:entity-reference-selection' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:entity-reference-selection' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:field:formatter
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:field:formatter' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:field:formatter' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:field:formatter' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:field:type
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:field:type' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:field:type' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:field:type' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:field:widget
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:field:widget' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:field:widget' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:field:widget' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:filter
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:filter' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:filter' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:filter' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:menu-link
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:menu-link' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:menu-link' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:menu-link' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:migrate:destination
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:migrate:destination' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:migrate:destination' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:migrate:destination' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:migrate:process
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:migrate:process' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:migrate:process' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:migrate:process' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:migrate:source
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:migrate:source' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:migrate:source' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:migrate:source' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:queue-worker
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:queue-worker' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:queue-worker' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:queue-worker' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:rest-resource
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:rest-resource' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:rest-resource' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:rest-resource' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:views:argument-default
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:views:argument-default' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:views:argument-default' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:views:argument-default' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:views:field
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:views:field' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:views:field' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:views:field' -l dry-run -d 'Output the generated code but not save it to file system'
# plugin:views:style
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:views:style' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:views:style' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from plugin:views:style' -l dry-run -d 'Output the generated code but not save it to file system'
# service:access-checker
complete -c dcg -A -n '__fish_seen_subcommand_from service:access-checker' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:access-checker' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:access-checker' -l dry-run -d 'Output the generated code but not save it to file system'
# service:breadcrumb-builder
complete -c dcg -A -n '__fish_seen_subcommand_from service:breadcrumb-builder' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:breadcrumb-builder' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:breadcrumb-builder' -l dry-run -d 'Output the generated code but not save it to file system'
# service:cache-context
complete -c dcg -A -n '__fish_seen_subcommand_from service:cache-context' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:cache-context' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:cache-context' -l dry-run -d 'Output the generated code but not save it to file system'
# service:custom
complete -c dcg -A -n '__fish_seen_subcommand_from service:custom' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:custom' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:custom' -l dry-run -d 'Output the generated code but not save it to file system'
# service:event-subscriber
complete -c dcg -A -n '__fish_seen_subcommand_from service:event-subscriber' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:event-subscriber' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:event-subscriber' -l dry-run -d 'Output the generated code but not save it to file system'
# service:logger
complete -c dcg -A -n '__fish_seen_subcommand_from service:logger' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:logger' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:logger' -l dry-run -d 'Output the generated code but not save it to file system'
# service:middleware
complete -c dcg -A -n '__fish_seen_subcommand_from service:middleware' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:middleware' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:middleware' -l dry-run -d 'Output the generated code but not save it to file system'
# service:param-converter
complete -c dcg -A -n '__fish_seen_subcommand_from service:param-converter' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:param-converter' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:param-converter' -l dry-run -d 'Output the generated code but not save it to file system'
# service:path-processor
complete -c dcg -A -n '__fish_seen_subcommand_from service:path-processor' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:path-processor' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:path-processor' -l dry-run -d 'Output the generated code but not save it to file system'
# service:request-policy
complete -c dcg -A -n '__fish_seen_subcommand_from service:request-policy' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:request-policy' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:request-policy' -l dry-run -d 'Output the generated code but not save it to file system'
# service:response-policy
complete -c dcg -A -n '__fish_seen_subcommand_from service:response-policy' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:response-policy' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:response-policy' -l dry-run -d 'Output the generated code but not save it to file system'
# service:route-subscriber
complete -c dcg -A -n '__fish_seen_subcommand_from service:route-subscriber' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:route-subscriber' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:route-subscriber' -l dry-run -d 'Output the generated code but not save it to file system'
# service:theme-negotiator
complete -c dcg -A -n '__fish_seen_subcommand_from service:theme-negotiator' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:theme-negotiator' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:theme-negotiator' -l dry-run -d 'Output the generated code but not save it to file system'
# service:twig-extension
complete -c dcg -A -n '__fish_seen_subcommand_from service:twig-extension' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:twig-extension' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:twig-extension' -l dry-run -d 'Output the generated code but not save it to file system'
# service:uninstall-validator
complete -c dcg -A -n '__fish_seen_subcommand_from service:uninstall-validator' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from service:uninstall-validator' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from service:uninstall-validator' -l dry-run -d 'Output the generated code but not save it to file system'
# test:browser
complete -c dcg -A -n '__fish_seen_subcommand_from test:browser' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from test:browser' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from test:browser' -l dry-run -d 'Output the generated code but not save it to file system'
# test:kernel
complete -c dcg -A -n '__fish_seen_subcommand_from test:kernel' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from test:kernel' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from test:kernel' -l dry-run -d 'Output the generated code but not save it to file system'
# test:nightwatch
complete -c dcg -A -n '__fish_seen_subcommand_from test:nightwatch' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from test:nightwatch' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from test:nightwatch' -l dry-run -d 'Output the generated code but not save it to file system'
# test:unit
complete -c dcg -A -n '__fish_seen_subcommand_from test:unit' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from test:unit' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from test:unit' -l dry-run -d 'Output the generated code but not save it to file system'
# test:webdriver
complete -c dcg -A -n '__fish_seen_subcommand_from test:webdriver' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from test:webdriver' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from test:webdriver' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:breakpoints
complete -c dcg -A -n '__fish_seen_subcommand_from yml:breakpoints' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:breakpoints' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:breakpoints' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:links:action
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:action' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:action' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:action' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:links:contextual
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:contextual' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:contextual' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:contextual' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:links:menu
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:menu' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:menu' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:menu' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:links:task
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:task' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:task' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:links:task' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:module-info
complete -c dcg -A -n '__fish_seen_subcommand_from yml:module-info' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:module-info' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:module-info' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:module-libraries
complete -c dcg -A -n '__fish_seen_subcommand_from yml:module-libraries' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:module-libraries' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:module-libraries' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:permissions
complete -c dcg -A -n '__fish_seen_subcommand_from yml:permissions' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:permissions' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:permissions' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:routing
complete -c dcg -A -n '__fish_seen_subcommand_from yml:routing' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:routing' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:routing' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:services
complete -c dcg -A -n '__fish_seen_subcommand_from yml:services' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:services' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:services' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:theme-info
complete -c dcg -A -n '__fish_seen_subcommand_from yml:theme-info' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:theme-info' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:theme-info' -l dry-run -d 'Output the generated code but not save it to file system'
# yml:theme-libraries
complete -c dcg -A -n '__fish_seen_subcommand_from yml:theme-libraries' -l directory -d 'Working directory'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:theme-libraries' -l answer -d 'Answer to generator question'
complete -c dcg -A -n '__fish_seen_subcommand_from yml:theme-libraries' -l dry-run -d 'Output the generated code but not save it to file system'

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,89 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator;
use DrupalCodeGenerator\Helper\DrupalContext;
use DrupalCodeGenerator\Helper\Dumper;
use DrupalCodeGenerator\Helper\QuestionHelper;
use DrupalCodeGenerator\Helper\Renderer;
use DrupalCodeGenerator\Helper\ResultPrinter;
use DrupalCodeGenerator\Twig\TwigEnvironment;
use Symfony\Component\Console\Application as BaseApplication;
use Symfony\Component\Console\Helper\HelperSet;
use Symfony\Component\Console\Input\InputDefinition;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\Filesystem\Filesystem;
use Twig\Loader\FilesystemLoader;
/**
* DCG console application.
*/
class Application extends BaseApplication {
/**
* Path to DCG root directory.
*/
public const ROOT = __DIR__ . '/..';
/**
* DCG version.
*/
public const VERSION = '2.4.0-dev';
/**
* DCG API version.
*/
public const API = 2;
/**
* Path to templates directory.
*/
public const TEMPLATE_PATH = Application::ROOT . '/templates';
/**
* Namespace of core DCG generators.
*/
public const GENERATOR_NAMESPACE = '\DrupalCodeGenerator\Command';
/**
* Creates the application.
*/
public static function create(?ContainerInterface $container = NULL): Application {
$application = new static('Drupal Code Generator', self::VERSION);
$helper_set = new HelperSet([
new QuestionHelper(),
new Dumper(new Filesystem()),
new Renderer(new TwigEnvironment(new FilesystemLoader([Application::TEMPLATE_PATH]))),
new ResultPrinter(),
]);
if ($container) {
$helper_set->set(new DrupalContext($container));
}
$application->setHelperSet($helper_set);
return $application;
}
/**
* {@inheritdoc}
*/
public function getDefaultInputDefinition(): InputDefinition {
$definition = parent::getDefaultInputDefinition();
$options = $definition->getOptions();
// As most generators are interactive these options make no sense.
unset($options['no-interaction'], $options['quiet']);
$definition->setOptions($options);
$definition->addOption(new InputOption('working-dir', 'd', InputOption::VALUE_OPTIONAL, 'Working directory'));
$definition->addOption(new InputOption('answer', 'a', InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Answer to generator question'));
$definition->addOption(new InputOption('dry-run', NULL, InputOption::VALUE_NONE, 'Output the generated code but not save it to file system'));
$definition->addOption(new InputOption('full-path', NULL, InputOption::VALUE_NONE, 'Print full path to generated assets'));
$definition->addOption(new InputOption('destination', NULL, InputOption::VALUE_OPTIONAL, 'Path to a base directory for file writing'));
return $definition;
}
}

View File

@ -0,0 +1,90 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Asset;
use DrupalCodeGenerator\Utils;
/**
* Base class for assets.
*/
abstract class Asset {
/**
* Asset path.
*/
protected string $path;
/**
* Asset mode.
*/
protected int $mode;
/**
* Template variables.
*
* @var array
*/
protected array $vars = [];
/**
* Asset constructor.
*/
public function __construct(string $path) {
$this->path = $path;
}
/**
* Getter for the asset path.
*/
public function getPath(): string {
return $this->replaceTokens($this->path);
}
/**
* Getter for the asset mode.
*/
public function getMode(): int {
return $this->mode;
}
/**
* Getter for the asset vars.
*/
public function getVars(): array {
return $this->vars;
}
/**
* Setter for asset mode.
*/
public function mode(int $mode): Asset {
if ($mode < 0000 || $mode > 0777) {
throw new \InvalidArgumentException("Incorrect mode value $mode.");
}
$this->mode = $mode;
return $this;
}
/**
* Setter for the asset vars.
*/
public function vars(array $vars): self {
$this->vars = $vars;
return $this;
}
/**
* Implements the magic __toString() method.
*/
public function __toString(): string {
return $this->getPath();
}
/**
* Replaces all tokens in a given string with appropriate values.
*/
protected function replaceTokens(string $text): ?string {
return Utils::replaceTokens($text, $this->vars);
}
}

View File

@ -0,0 +1,132 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Asset;
/**
* Asset collection.
*/
final class AssetCollection implements \ArrayAccess, \IteratorAggregate, \Countable {
/**
* Assets.
*
* @var \DrupalCodeGenerator\Asset\Asset[]
*/
private array $assets;
/**
* AssetCollection constructor.
*
* @param \DrupalCodeGenerator\Asset\Asset[] $assets
* Assets.
*/
public function __construct(array $assets = []) {
$this->assets = $assets;
}
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function offsetSet($key, $value) {
if ($key === NULL) {
$this->assets[] = $value;
}
else {
$this->assets[$key] = $value;
}
}
/**
* {@inheritdoc}
*/
#[\ReturnTypeWillChange]
public function offsetGet($key) {
if (isset($this->assets[$key])) {
return $this->assets[$key];
}
}
/**
* {@inheritdoc}
*/
public function offsetUnset($key): void {
unset($this->assets[$key]);
}
/**
* {@inheritdoc}
*/
public function offsetExists($key): bool {
return isset($this->assets[$key]);
}
/**
* {@inheritdoc}
*/
public function getIterator(): \ArrayIterator {
return new \ArrayIterator($this->assets);
}
/**
* {@inheritdoc}
*/
public function count(): int {
return \count($this->assets);
}
/**
* Returns a collection of directory assets.
*/
public function getDirectories(): self {
$assets = \array_filter(
$this->assets,
static fn ($asset): bool => $asset instanceof Directory,
);
return new self($assets);
}
/**
* Returns a collection of file assets.
*/
public function getFiles(): self {
$assets = \array_filter(
$this->assets,
static fn ($asset): bool => $asset instanceof File,
);
return new self($assets);
}
/**
* Returns a collection of symlink assets.
*/
public function getSymlinks(): self {
$assets = \array_filter(
$this->assets,
static fn ($asset): bool => $asset instanceof Symlink,
);
return new self($assets);
}
/**
* Returns a collection of sorted assets.
*/
public function getSorted(): self {
$sorter = static function (Asset $a, Asset $b): int {
$name_a = (string) $a;
$name_b = (string) $b;
// Top level assets should go first.
$result = \strcasecmp(\dirname($name_a), \dirname($name_b));
if ($result === 0) {
$result = \strcasecmp($name_a, $name_b);
}
return $result;
};
$assets = $this->assets;
\usort($assets, $sorter);
return new self($assets);
}
}

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Asset;
/**
* Simple data structure to represent a directory being created.
*/
final class Directory extends Asset {
/**
* {@inheritdoc}
*/
public function __construct(string $path) {
parent::__construct($path);
$this->mode(0755);
}
}

View File

@ -0,0 +1,217 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Asset;
/**
* Simple data structure to represent a file being generated.
*/
final class File extends Asset {
public const ACTION_REPLACE = 0x01;
public const ACTION_PREPEND = 0x02;
public const ACTION_APPEND = 0x03;
public const ACTION_SKIP = 0x04;
/**
* Asset content.
*/
private ?string $content = NULL;
/**
* Twig template to render header.
*/
private ?string $headerTemplate = NULL;
/**
* Twig template to render main content.
*/
private ?string $template = NULL;
/**
* The template string to render.
*/
private ?string $inlineTemplate = NULL;
/**
* Action.
*
* An action to take if specified file already exists.
*/
private int $action = self::ACTION_REPLACE;
/**
* Header size.
*/
private int $headerSize = 0;
/**
* Content resolver.
*
* @var callable|null
*/
private $resolver = NULL;
/**
* {@inheritdoc}
*/
public function __construct(string $path) {
parent::__construct($path);
$this->mode(0644);
}
/**
* Returns the asset content.
*/
public function getContent(): ?string {
return $this->content;
}
/**
* Returns the header template.
*/
public function getHeaderTemplate(): ?string {
return $this->headerTemplate ? $this->replaceTokens($this->headerTemplate) : $this->headerTemplate;
}
/**
* Returns the asset template.
*/
public function getTemplate(): ?string {
return $this->template ? $this->replaceTokens($this->template) : $this->template;
}
/**
* Returns the asset inline template.
*/
public function getInlineTemplate(): ?string {
return $this->inlineTemplate;
}
/**
* Returns the asset action.
*/
public function getAction(): int {
return $this->action;
}
/**
* Returns the asset header size (number of lines).
*/
public function getHeaderSize(): int {
return $this->headerSize;
}
/**
* Returns the asset resolver.
*/
public function getResolver(): ?callable {
return $this->resolver;
}
/**
* Sets the asset content.
*/
public function content(?string $content): self {
$this->content = $content;
return $this;
}
/**
* Sets the asset header template.
*/
public function headerTemplate(?string $header_template): self {
$this->headerTemplate = self::addTwigFileExtension($header_template);
return $this;
}
/**
* Returns the asset template.
*/
public function template(?string $template): self {
if ($template !== NULL) {
$this->template = self::addTwigFileExtension($template);
}
return $this;
}
/**
* Returns the asset inline template.
*/
public function inlineTemplate(?string $inline_template): self {
$this->inlineTemplate = $inline_template;
return $this;
}
/**
* Sets the "replace" action.
*/
public function replaceIfExists(): self {
$this->action = self::ACTION_REPLACE;
return $this;
}
/**
* Sets the "prepend" action.
*/
public function prependIfExists(): self {
$this->action = self::ACTION_PREPEND;
return $this;
}
/**
* Sets the "append" action.
*/
public function appendIfExists(): self {
$this->action = self::ACTION_APPEND;
return $this;
}
/**
* Sets the "skip" action.
*/
public function skipIfExists(): self {
$this->action = self::ACTION_SKIP;
return $this;
}
/**
* Set the asset header size.
*/
public function headerSize(int $header_size): self {
if ($header_size <= 0) {
throw new \InvalidArgumentException('Header size must be greater than or equal to 0.');
}
$this->headerSize = $header_size;
return $this;
}
/**
* Setter for asset resolver.
*
* @param callable|null $resolver
* A callable responsible for resolving content.
* @code
* $resolver = static function (?string $existing_content, ?string $generated_content): ?string {
* if ($existing_content !== NULL) {
* return $generated_content . "\n" . $existing_content;
* }
* return $generated_content;
* }
* @endcode
*/
public function resolver(?callable $resolver): self {
$this->resolver = $resolver;
return $this;
}
/**
* Adds the Twig file extension if needed.
*/
private static function addTwigFileExtension(string $template): string {
if ($template && \pathinfo($template, \PATHINFO_EXTENSION) != 'twig') {
$template .= '.twig';
}
return $template;
}
}

View File

@ -0,0 +1,70 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Asset;
/**
* Simple data structure to represent a symlink being generated.
*/
final class Symlink extends Asset {
public const ACTION_REPLACE = 0x01;
public const ACTION_SKIP = 0x04;
/**
* Action.
*
* An action to take if specified symlink already exists.
*/
private int $action = self::ACTION_REPLACE;
/**
* Symlink target.
*/
private string $target;
/**
* {@inheritdoc}
*/
public function __construct(string $path, string $target) {
parent::__construct($path);
$this->target = $target;
$this->mode(0644);
}
/**
* Getter for symlink target.
*
* @return string
* Asset action.
*/
public function getTarget(): string {
return $this->replaceTokens($this->target);
}
/**
* Getter for asset action.
*
* @return string|callable
* Asset action.
*/
public function getAction() {
return $this->action;
}
/**
* Sets the "replace" action.
*/
public function replaceIfExists(): self {
$this->action = self::ACTION_REPLACE;
return $this;
}
/**
* Sets the "skip" action.
*/
public function skipIfExists(): self {
$this->action = self::ACTION_SKIP;
return $this;
}
}

View File

@ -0,0 +1,49 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator;
use Composer\Autoload\ClassLoader;
use Drupal\Core\DrupalKernel;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a handler to bootstrap Drupal.
*/
class BootstrapHandler {
/**
* The class loader.
*/
private ClassLoader $classLoader;
/**
* Construct a BootstrapHandler object.
*/
public function __construct(ClassLoader $class_loader) {
$this->classLoader = $class_loader;
}
/**
* Bootstraps Drupal.
*
* @return \Symfony\Component\DependencyInjection\ContainerInterface|null
* Current service container or null if bootstrap failed.
*/
public function bootstrap(): ?ContainerInterface {
if (!\defined('Drupal::VERSION') || \version_compare(\Drupal::VERSION, '9.0.0', '<')) {
return NULL;
}
try {
$request = Request::createFromGlobals();
$kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod');
$kernel->boot();
$kernel->preHandle($request);
return $kernel->getContainer();
}
catch (\Exception $exception) {
return NULL;
}
}
}

View File

@ -0,0 +1,24 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\ClassResolver;
/**
* An interface to get an instance of a class with dependency injection.
*/
interface ClassResolverInterface {
/**
* Returns a class instance with a given class name.
*
* @param string $class
* A class name.
*
* @return object
* The instance of the class.
*
* @throws \InvalidArgumentException
* If the class does not exist.
*/
public function getInstance(string $class): object;
}

View File

@ -0,0 +1,20 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\ClassResolver;
/**
* Defines a class resolver without dependency injection.
*/
final class SimpleClassResolver implements ClassResolverInterface {
/**
* {@inheritdoc}
*/
public function getInstance(string $class): object {
if (!\class_exists($class)) {
throw new \InvalidArgumentException(\sprintf('Class "%s" does not exist.', $class));
}
return new $class();
}
}

View File

@ -0,0 +1,62 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
/**
* Implements composer command.
*/
final class Composer extends DrupalGenerator {
protected string $name = 'composer';
protected string $description = 'Generates a composer.json file';
protected string $alias = 'composer.json';
protected string $label = 'composer.json';
protected string $templatePath = Application::TEMPLATE_PATH . '/composer';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
// @see https://getcomposer.org/doc/04-schema.md#name
$validator = static function (string $input): string {
if (!\preg_match('#^[a-z0-9]([_.-]?[a-z0-9]+)*/[a-z0-9](([_.]?|-{0,2})[a-z0-9]+)*$#', $input)) {
throw new \UnexpectedValueException('The package name sdsdf is invalid, it should be lowercase and have a vendor name, a forward slash, and a package name.');
}
return $input;
};
$vars['project_name'] = $this->ask('Project name', 'drupal/example', $validator);
[, $vars['machine_name']] = \explode('/', $vars['project_name']);
$vars['description'] = $this->ask('Description');
$type_choices = [
'drupal-module',
'drupal-custom-module',
'drupal-theme',
'drupal-custom-theme',
'drupal-library',
'drupal-profile',
'drupal-custom-profile',
'drupal-drush',
];
$vars['type'] = $this->choice('Project type', \array_combine($type_choices, $type_choices));
$custom_types = [
'drupal-custom-module',
'drupal-custom-theme',
'drupal-custom-profile',
];
if (\in_array($vars['type'], $custom_types)) {
$vars['drupal_org'] = FALSE;
}
else {
// If project type is custom, there is no reason to ask this.
$vars['drupal_org'] = $this->confirm('Is this project hosted on drupal.org?', FALSE);
}
$this->addFile('composer.json', 'composer');
}
}

View File

@ -0,0 +1,55 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Console;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\Generator;
use DrupalCodeGenerator\Utils;
/**
* Implements console:dcg-command command.
*
* @todo Test this manually.
*/
final class DcgCommand extends Generator {
protected string $name = 'console:dcg-command';
protected string $description = 'Generates DCG command';
protected string $alias = 'dcg-command';
protected string $label = 'DCG command';
protected string $templatePath = Application::TEMPLATE_PATH . '/console/dcg-command';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$command_name_validator = static fn (?string $value): ?string
=> self::validate($value, '^[a-z][a-z0-9-_:]*[a-z0-9]$', 'The value is not correct command name.');
$vars['command_name'] = $this->ask('Command name', 'custom:example', $command_name_validator);
$vars['description'] = $this->ask('Command description');
$sub_names = \explode(':', $vars['command_name']);
$short_name = \array_pop($sub_names);
$alias_validator = static fn (?string $value): ?string
=> self::validate($value, '^[a-z0-9][a-z0-9_]+$', 'The value is not correct alias name.');
$vars['alias'] = $this->ask('Command alias', $short_name, $alias_validator);
$vars['class'] = Utils::camelize($short_name);
$vars['namespace'] = 'DrupalCodeGenerator';
$vars['template_name'] = $short_name;
$vars['path'] = '';
$file_path = '';
if ($sub_names) {
$vars['namespace'] .= '\\' . \implode('\\', $sub_names);
$file_path = \implode(\DIRECTORY_SEPARATOR, $sub_names);
$vars['path'] = '/' . $file_path;
}
$this->addFile($file_path . '/{class}.php', 'command');
$this->addFile($file_path . '/{template_name}.twig', 'template');
}
}

View File

@ -0,0 +1,37 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Console;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements console:drupal-console-command command.
*/
final class DrupalConsoleCommand extends ModuleGenerator {
protected string $name = 'console:drupal-console-command';
protected string $description = 'Generates Drupal Console command';
protected string $alias = 'drupal-console-command';
protected string $templatePath = Application::TEMPLATE_PATH . '/console/drupal-console-command';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['command_name'] = $this->ask('Command name', '{machine_name}:example');
$vars['description'] = $this->ask('Command description', 'Command description.');
$vars['drupal_aware'] = $this->confirm('Make the command aware of the Drupal site installation?');
$vars['service_short_name'] = \str_replace(':', '_', $vars['command_name']);
$vars['service_name'] = '{machine_name}.{service_short_name}';
$vars['class'] = '{service_short_name|camelize}Command';
$vars['base_class'] = $vars['drupal_aware'] ? 'ContainerAwareCommand' : 'Command';
$this->addFile('src/Command/{class}.php', 'command');
$this->addServicesFile('console.services.yml')->template('services');
}
}

View File

@ -0,0 +1,46 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Console;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\Generator;
/**
* Implements console:drush-command command.
*/
final class DrushCommand extends Generator {
protected string $name = 'console:drush-command';
protected string $description = 'Generates Drush command';
protected string $alias = 'drush-command';
protected string $templatePath = Application::TEMPLATE_PATH . '/console/drush-command';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$vars['command_name'] = $this->ask('Command name', '');
$vars['alias'] = $this->ask('Command alias', \substr($vars['command_name'], 0, 3));
$vars['description'] = $this->ask('Command description', 'Command description.');
$vars['argument'] = $this->ask('Argument name', 'foo');
$vars['option'] = $this->ask('Option name', 'bar');
$directoryBaseName = \basename($this->directory);
// The suggestion depends on whether the command global or local.
$prefix = $directoryBaseName == 'drush' || $directoryBaseName == '.drush' ?
$vars['command_name'] : $directoryBaseName;
$default_command_file = \str_replace('-', '_', $prefix) . '.drush.inc';
$vars['command_file'] = $this->ask('Command file', $default_command_file);
[$vars['command_file_prefix']] = \explode('.drush.inc', $vars['command_file']);
// Command callback name pattern gets shorter if command file name matches
// command name.
$vars['command_callback_suffix'] = $vars['command_file_prefix'] == \str_replace('-', '_', $vars['command_name'])
? $vars['command_file_prefix']
: $vars['command_file_prefix'] . '_' . $vars['command_name'];
$this->addFile('{command_file}', 'command');
}
}

View File

@ -0,0 +1,43 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Console;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
use DrupalCodeGenerator\Utils;
/**
* Implements console:symfony-command command.
*/
final class SymfonyCommand extends ModuleGenerator {
protected string $name = 'console:symfony-command';
protected string $description = 'Generates Symfony console command';
protected string $alias = 'symfony-command';
protected string $templatePath = Application::TEMPLATE_PATH . '/console/symfony-command';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$command_name_validator = static fn (?string $value): ?string => self::validate($value, '^[a-z][a-z0-9-_:]*[a-z0-9]$', 'The value is not correct command name.');
$vars['command']['name'] = $this->ask('Command name', '{machine_name}:example', $command_name_validator);
$vars['command']['description'] = $this->ask('Command description');
$sub_names = \explode(':', $vars['command']['name']);
$short_name = \array_pop($sub_names);
$alias_validator = static fn (?string $value): ?string => self::validate($value, '^[a-z0-9][a-z0-9_]+$', 'The value is not correct alias name.');
$vars['command']['alias'] = $this->ask('Command alias', $short_name, $alias_validator);
$vars['class'] = $this->ask('Class', Utils::camelize($short_name) . 'Command');
if ($this->confirm('Would you like to run the command with Drush')) {
$this->addServicesFile('drush.services.yml')->template('services');
}
$this->addFile('src/Command/{class}.php', 'command');
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
/**
* Implements controller command.
*/
final class Controller extends ModuleGenerator {
protected string $name = 'controller';
protected string $description = 'Generates a controller';
protected string $templatePath = Application::TEMPLATE_PATH . '/controller';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['class'] = $this->ask('Class', '{machine_name|camelize}Controller');
$this->collectServices($vars, FALSE);
if ($this->confirm('Would you like to create a route for this controller?')) {
$vars['route_name'] = $this->ask('Route name', '{machine_name}.example');
$vars['route_path'] = $this->ask('Route path', '/{machine_name|u2h}/example');
$vars['route_title'] = $this->ask('Route title', 'Example');
$vars['route_permission'] = $this->ask('Route permission', 'access content');
$this->addFile('{machine_name}.routing.yml', 'route')->appendIfExists();
}
$this->addFile('src/Controller/{class}.php', 'controller');
}
}

View File

@ -0,0 +1,153 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Helper\DrupalContext;
use DrupalCodeGenerator\Utils;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\Question;
/**
* Base class for Drupal generators.
*/
abstract class DrupalGenerator extends Generator {
public const EXTENSION_TYPE_MODULE = 0x01;
public const EXTENSION_TYPE_THEME = 0x02;
public const EXTENSION_TYPE_PROFILE = 0x03;
/**
* Name question.
*/
protected ?string $nameQuestion = 'Extension name';
/**
* Machine name question.
*/
protected ?string $machineNameQuestion = 'Extension machine name';
/**
* Indicates that generated code is a new Drupal extension.
*
* Most of Drupal generators fall into two categories:
* - new extensions (i.e. module, profile, theme)
* - components for existing extensions (i.e. controller, form, service, etc)
* This flag makes the distinction. It is used for setting autocompleter
* values for the extension name and defining correct destination for
* generated code.
*/
protected bool $isNewExtension = FALSE;
/**
* Extension type (module, theme, profile).
*/
protected ?int $extensionType = NULL;
/**
* Drupal context.
*
* This helper is set if Drupal is fully bootstrapped.
*/
protected ?DrupalContext $drupalContext = NULL;
/**
* {@inheritdoc}
*/
protected function initialize(InputInterface $input, OutputInterface $output): void {
parent::initialize($input, $output);
if ($this->getHelperSet()->has('drupal_context')) {
$this->drupalContext = $this->getHelper('drupal_context');
}
// Set working directory to extension root.
if (!$this->isNewExtension) {
$this->directory = Utils::getExtensionRoot($this->directory) ?: $this->directory;
}
}
/**
* Collects default variables.
*/
protected function collectDefault(array &$vars): void {
// If Drupal context is available it is quite possible that we can provide
// the extension name without interacting with a user.
if (!$this->isNewExtension && $this->drupalContext) {
$vars['machine_name'] = $this->askMachineName($vars);
$vars['name'] = $this->getExtensionList()[$vars['machine_name']]
?? Utils::machine2human($vars['machine_name']);
}
else {
if ($this->nameQuestion) {
$vars['name'] = $this->askName();
}
if ($this->machineNameQuestion) {
$vars['machine_name'] = $this->askMachineName($vars);
}
}
}
/**
* Asks name question.
*/
protected function askName(): string {
$root_directory = \basename(Utils::getExtensionRoot($this->directory) ?: $this->directory);
$default_value = Utils::machine2human($root_directory, TRUE);
$name_question = new Question($this->nameQuestion, $default_value);
$name_question->setValidator([static::class, 'validateRequired']);
if (!$this->isNewExtension && $extensions = $this->getExtensionList()) {
$name_question->setAutocompleterValues($extensions);
}
return $this->io->askQuestion($name_question);
}
/**
* Asks machine name question.
*/
protected function askMachineName(array $vars): string {
$default_value = Utils::human2machine($vars['name'] ?? \basename($this->directory));
$machine_name_question = new Question($this->machineNameQuestion, $default_value);
$machine_name_question->setValidator([static::class, 'validateRequiredMachineName']);
if (!$this->isNewExtension && $extensions = $this->getExtensionList()) {
$machine_name_question->setAutocompleterValues(\array_keys($extensions));
}
return $this->io->askQuestion($machine_name_question);
}
/**
* Returns extension list.
*
* @return \Drupal\Core\Extension\Extension[]
* An associative array whose keys are the machine names of the extensions
* and whose values are extension names.
*/
protected function getExtensionList(): array {
if ($this->drupalContext === NULL) {
return [];
}
switch ($this->extensionType) {
case DrupalGenerator::EXTENSION_TYPE_MODULE:
return $this->drupalContext->getModules();
case DrupalGenerator::EXTENSION_TYPE_THEME:
return $this->drupalContext->getThemes();
}
}
/**
* {@inheritdoc}
*/
protected function getDestination(array $vars): ?string {
if ($this->drupalContext) {
if ($this->extensionType === DrupalGenerator::EXTENSION_TYPE_MODULE) {
$destination = $this->drupalContext->getModuleDestination($this->isNewExtension, $vars['machine_name'] ?? NULL);
}
elseif ($this->extensionType === DrupalGenerator::EXTENSION_TYPE_THEME) {
$destination = $this->drupalContext->getThemeDestination($this->isNewExtension, $vars['machine_name'] ?? NULL);
}
}
return $destination ?? parent::getDestination($vars);
}
}

View File

@ -0,0 +1,54 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Entity;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements configuration-entity command.
*/
final class ConfigurationEntity extends ModuleGenerator {
protected string $name = 'entity:configuration';
protected string $description = 'Generates configuration entity';
protected string $alias = 'config-entity';
protected string $templatePath = Application::TEMPLATE_PATH . '/configuration-entity';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['entity_type_label'] = $this->ask('Entity type label', '{name}');
$vars['entity_type_id'] = $this->ask('Entity type ID', '{entity_type_label|h2m}');
$vars['class_prefix'] = '{entity_type_id|camelize}';
$this->addFile('src/{class_prefix}ListBuilder.php', 'src/ExampleListBuilder.php');
$this->addFile('src/Form/{class_prefix}Form.php', 'src/Form/ExampleForm.php');
$this->addFile('src/{class_prefix}Interface.php', 'src/ExampleInterface.php');
$this->addFile('src/Entity/{class_prefix}.php', 'src/Entity/Example.php');
$this->addFile('{machine_name}.routing.yml', 'model.routing.yml')
->appendIfExists();
$this->addFile('{machine_name}.links.action.yml', 'model.links.action.yml')
->appendIfExists();
$this->addFile('{machine_name}.links.menu.yml', 'model.links.menu.yml')
->appendIfExists();
$this->addFile('{machine_name}.permissions.yml', 'model.permissions.yml')
->appendIfExists();
$this->addFile('config/schema/{machine_name}.schema.yml', 'config/schema/model.schema.yml')
->appendIfExists();
// Add 'configure' link to the info file if it exists.
$update_info = static function (?string $existing_content) use ($vars): ?string {
if ($existing_content && !\preg_match('/^configure: /m', $existing_content)) {
return "{$existing_content}configure: entity.{$vars['entity_type_id']}.collection\n";
}
return NULL;
};
$this->addFile('{machine_name}.info.yml')
->resolver($update_info);
}
}

View File

@ -0,0 +1,115 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Entity;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements content-entity command.
*/
final class ContentEntity extends ModuleGenerator {
protected string $name = 'entity:content';
protected string $description = 'Generates content entity';
protected string $alias = 'content-entity';
protected string $templatePath = Application::TEMPLATE_PATH . '/content-entity';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['entity_type_label'] = $this->ask('Entity type label', '{name}');
$vars['entity_type_id'] = $this->ask('Entity type ID', '{entity_type_label|h2m}');
$vars['entity_base_path'] = $this->ask('Entity base path', '/{entity_type_id|u2h}');
$vars['fieldable'] = $this->confirm('Make the entity type fieldable?');
$vars['revisionable'] = $this->confirm('Make the entity type revisionable?', FALSE);
$vars['translatable'] = $this->confirm('Make the entity type translatable?', FALSE);
$vars['bundle'] = $this->confirm('The entity type has bundle?', FALSE);
$vars['canonical'] = $this->confirm('Create canonical page?');
$vars['template'] = $vars['canonical'] ? $this->confirm('Create entity template?') : FALSE;
$vars['access_controller'] = $this->confirm('Create CRUD permissions?', FALSE);
$vars['label_base_field'] = $this->confirm('Add "label" base field?');
$vars['status_base_field'] = $this->confirm('Add "status" base field?');
$vars['created_base_field'] = $this->confirm('Add "created" base field?');
$vars['changed_base_field'] = $this->confirm('Add "changed" base field?');
$vars['author_base_field'] = $this->confirm('Add "author" base field?');
$vars['description_base_field'] = $this->confirm('Add "description" base field?');
$vars['has_base_fields'] = $vars['label_base_field'] || $vars['status_base_field'] ||
$vars['created_base_field'] || $vars['changed_base_field'] ||
$vars['author_base_field'] || $vars['description_base_field'];
$vars['rest_configuration'] = $this->confirm('Create REST configuration for the entity?', FALSE);
if ($vars['entity_base_path'][0] != '/') {
$vars['entity_base_path'] = '/' . $vars['entity_base_path'];
}
if (($vars['fieldable_no_bundle'] = $vars['fieldable'] && !$vars['bundle'])) {
$vars['configure'] = 'entity.{entity_type_id}.settings';
}
elseif ($vars['bundle']) {
$vars['configure'] = 'entity.{entity_type_id}_type.collection';
}
$vars['class_prefix'] = '{entity_type_id|camelize}';
$vars['template_name'] = '{entity_type_id|u2h}.html.twig';
// Contextual links need title suffix to be added to entity template.
if ($vars['template']) {
$this->addFile('{machine_name}.links.contextual.yml', 'model.links.contextual.yml')
->appendIfExists();
}
$this->addFile('{machine_name}.links.action.yml', 'model.links.action.yml')
->appendIfExists();
$this->addFile('{machine_name}.links.menu.yml', 'model.links.menu.yml')
->appendIfExists();
$this->addFile('{machine_name}.links.task.yml', 'model.links.task.yml')
->appendIfExists();
$this->addFile('{machine_name}.permissions.yml', 'model.permissions.yml')
->appendIfExists();
$this->addFile('src/Entity/{class_prefix}.php', 'src/Entity/Example.php');
$this->addFile('src/{class_prefix}Interface.php', 'src/ExampleInterface.php');
if (!$vars['canonical']) {
$this->addFile('src/Routing/{class_prefix}HtmlRouteProvider.php', 'src/Routing/ExampleHtmlRouteProvider.php');
}
$this->addFile('src/{class_prefix}ListBuilder.php', 'src/ExampleListBuilder.php');
$this->addFile('src/Form/{class_prefix}Form.php', 'src/Form/ExampleForm.php');
if ($vars['fieldable_no_bundle']) {
$this->addFile('{machine_name}.routing.yml', 'model.routing.yml')
->appendIfExists();
$this->addFile('src/Form/{class_prefix}SettingsForm.php', 'src/Form/ExampleSettingsForm.php');
}
if ($vars['template']) {
$this->addFile('templates/{entity_type_id|u2h}.html.twig', 'templates/model-example.html.twig');
$this->addFile('{machine_name}.module', 'model.module')
->appendIfExists()
->headerSize(7);
}
if ($vars['access_controller']) {
$this->addFile('src/{class_prefix}AccessControlHandler.php', 'src/ExampleAccessControlHandler.php');
}
if ($vars['rest_configuration']) {
$this->addFile('config/optional/rest.resource.entity.{entity_type_id}.yml', 'config/optional/rest.resource.entity.example.yml');
}
if ($vars['bundle']) {
$this->addFile('config/schema/{machine_name}.entity_type.schema.yml', 'config/schema/model.entity_type.schema.yml')
->appendIfExists();
$this->addFile('src/{class_prefix}TypeListBuilder.php', 'src/ExampleTypeListBuilder.php');
$this->addFile('src/Entity/{class_prefix}Type.php', 'src/Entity/ExampleType.php');
$this->addFile('src/Form/{class_prefix}TypeForm.php', 'src/Form/ExampleTypeForm.php');
}
}
}

View File

@ -0,0 +1,114 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Entity;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Implements entity-bundle-class command.
*
* @todo Create a test.
*/
final class EntityBundleClass extends ModuleGenerator {
/**
* {@inheritdoc}
*/
protected string $name = 'entity:bundle-class';
protected string $description = 'Generate a bundle class for a content entity.';
protected string $templatePath = Application::TEMPLATE_PATH . '/entity-bundle-class';
protected string $alias = 'bundle-class';
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
// @todo Figure out how to hide generators that cannot run without Drupal context.
if (!$this->drupalContext) {
$this->io->getErrorStyle()->error('This command requires a fully bootstrapped Drupal instance.');
return 1;
}
return parent::execute($input, $output);
}
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['namespace'] = 'Drupal\\' . $vars['machine_name'] . '\Entity\Bundle';
/** @var \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager */
$entity_type_manager = $this->drupalContext->getContainer()->get('entity_type.manager');
$definitions = \array_filter(
$entity_type_manager->getDefinitions(),
static fn (EntityTypeInterface $definition): bool => $definition->getGroup() === 'content',
);
$entity_type_choices = \array_map(
static fn (ContentEntityTypeInterface $definition): string => (string) $definition->get('label'),
$definitions,
);
$vars['entity_type_id'] = $this->choice('Entity type', $entity_type_choices);
// @todo Should this use 'original_class' instead?
$vars['entity_class_fqn'] = $definitions[$vars['entity_type_id']]->get('class');
$vars['entity_class'] = \array_slice(\explode('\\', $vars['entity_class_fqn']), -1)[0];
/** @var \Drupal\Core\Entity\EntityTypeBundleInfoInterface $bundle_info */
$bundle_info = $this->drupalContext->getContainer()->get('entity_type.bundle.info');
$bundles = \array_map(
static fn (array $bundle): string => (string) $bundle['label'],
$bundle_info->getBundleInfo($vars['entity_type_id']),
);
// Skip the question when only 1 bundle exists.
if (\count($bundles) === 1) {
$vars['bundle_ids'] = \array_keys($bundles);
}
else {
// Prepend an 'All' choice for user's convenience.
$bundle_choices = ['' => 'All'] + $bundles;
$vars['bundle_ids'] = $this->choice('Bundles, comma separated', $bundle_choices, NULL, TRUE);
if (\in_array('', $vars['bundle_ids'])) {
if (\count($vars['bundle_ids']) >= 2) {
throw new \UnexpectedValueException("'All' may not be combined with other choices.");
}
// Replace 'all' with all bundle IDs.
$vars['bundle_ids'] = \array_keys($bundles);
}
}
$vars['classes'] = [];
$vars['classes_fqn'] = [];
foreach ($vars['bundle_ids'] as $bundle_id) {
$vars['bundle_id'] = $bundle_id;
$vars['class'] = $this->ask(
\sprintf('Class for %s bundle', $bundles[$bundle_id]),
'{bundle_id|camelize}Bundle',
);
$vars['class_fqn'] = '\\' . $vars['namespace'] . '\\' . $vars['class'];
$this->addFile('src/Entity/Bundle/{class}.php', 'bundle-class')->vars($vars);
// Track all bundle classes to generate hook_entity_bundle_info_alter().
$vars['classes'][$bundle_id] = $vars['class'];
$vars['classes_fqn'][$bundle_id] = $vars['class_fqn'];
}
if ($this->confirm('Use a base class?', FALSE)) {
$vars['base_class'] = $this->ask('Base class', '{entity_type_id|camelize}Bundle');
$this->addFile('src/Entity/Bundle/{base_class}.php', 'bundle-base-class');
}
// @todo Handle duplicated hooks.
$this->addFile('{machine_name}.module', 'module.twig')
->appendIfExists()
->headerSize(7);
}
}

View File

@ -0,0 +1,263 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Utils;
/**
* Implements field command.
*/
final class Field extends ModuleGenerator {
protected string $name = 'field';
protected string $description = 'Generates a field';
protected string $templatePath = Application::TEMPLATE_PATH . '/field';
/**
* Field sub-types.
*
* @var array
*/
private array $subTypes = [
'boolean' => [
'label' => 'Boolean',
'list' => FALSE,
'random' => FALSE,
'inline' => FALSE,
'link' => FALSE,
'data_type' => 'boolean',
],
'string' => [
'label' => 'Text',
'list' => TRUE,
'random' => TRUE,
'inline' => TRUE,
'link' => FALSE,
'data_type' => 'string',
],
'text' => [
'label' => 'Text (long)',
'list' => FALSE,
'random' => TRUE,
'inline' => FALSE,
'link' => FALSE,
'data_type' => 'string',
],
'integer' => [
'label' => 'Integer',
'list' => TRUE,
'random' => FALSE,
'inline' => TRUE,
'link' => FALSE,
'data_type' => 'integer',
],
'float' => [
'label' => 'Float',
'list' => TRUE,
'random' => FALSE,
'inline' => TRUE,
'link' => FALSE,
'data_type' => 'float',
],
'numeric' => [
'label' => 'Numeric',
'list' => TRUE,
'random' => FALSE,
'inline' => TRUE,
'link' => FALSE,
'data_type' => 'float',
],
'email' => [
'label' => 'Email',
'list' => TRUE,
'random' => TRUE,
'inline' => TRUE,
'link' => TRUE,
'data_type' => 'email',
],
'telephone' => [
'label' => 'Telephone',
'list' => TRUE,
'random' => FALSE,
'inline' => TRUE,
'link' => TRUE,
'data_type' => 'string',
],
'uri' => [
'label' => 'Url',
'list' => TRUE,
'random' => TRUE,
'inline' => TRUE,
'link' => TRUE,
'data_type' => 'uri',
],
'datetime' => [
'label' => 'Date',
'list' => TRUE,
'random' => FALSE,
'inline' => FALSE,
'link' => FALSE,
'data_type' => 'datetime_iso8601',
],
];
/**
* Date types.
*
* @var array
*/
protected array $dateTypes = [
'date' => 'Date only',
'datetime' => 'Date and time',
];
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['field_label'] = $this->ask('Field label', 'Example', '::validateRequired');
$vars['field_id'] = $this->ask('Field ID', '{machine_name}_{field_label|h2m}', '::validateRequiredMachineName');
$subfield_count_validator = static function ($value) {
if (!\is_numeric($value) || \intval($value) != $value || $value <= 0) {
throw new \UnexpectedValueException('The value should be greater than zero.');
}
return $value;
};
$vars['subfield_count'] = $this->ask('How many sub-fields would you like to create?', '3', $subfield_count_validator);
$type_choice_keys = \array_keys($this->subTypes);
$type_choice_labels = \array_column($this->subTypes, 'label');
$type_choices = \array_combine($type_choice_keys, $type_choice_labels);
// Indicates that at least one of sub-fields needs Random component.
$vars['random'] = FALSE;
// Indicates that all sub-fields can be rendered inline.
$vars['inline'] = TRUE;
// Indicates that at least one of sub-fields has limited allowed values.
$vars['list'] = FALSE;
// Indicates that at least one of sub-fields is required.
$vars['required'] = FALSE;
// Indicates that at least one of sub-fields is of email type.
$vars['email'] = FALSE;
// Indicates that at least one of sub-fields can be rendered as a link.
$vars['link'] = FALSE;
// Indicates that at least one of sub-fields is of datetime type.
$vars['datetime'] = FALSE;
$vars['type_class'] = '{field_label|camelize}Item';
$vars['widget_class'] = '{field_label|camelize}Widget';
$vars['formatter_class'] = '{field_label|camelize}DefaultFormatter';
for ($i = 1; $i <= $vars['subfield_count']; $i++) {
$this->io->writeln(\sprintf('<fg=green>%s</>', \str_repeat('', 50)));
$subfield = new \stdClass();
$subfield->name = $this->ask("Label for sub-field #$i", "Value $i");
$subfield->machineName = $this->ask("Machine name for sub-field #$i", Utils::human2machine($subfield->name));
$type = $this->choice("Type of sub-field #$i", $type_choices, 'Text');
if ($type == 'datetime') {
$subfield->dateType = $this->choice("Date type for sub-field #$i", $this->dateTypes, 'Date only');
}
$definition = $this->subTypes[$type];
if ($definition['list']) {
$subfield->list = $this->confirm("Limit allowed values for sub-field #$i?", FALSE);
}
$subfield->required = $this->confirm("Make sub-field #$i required?", FALSE);
// Build sub-field vars.
$vars['subfields'][$i] = [
'name' => $subfield->name,
'machine_name' => $subfield->machineName,
'type' => $type,
'data_type' => $definition['data_type'],
'list' => !empty($subfield->list),
'allowed_values_method' => 'allowed' . Utils::camelize($subfield->name, TRUE) . 'Values',
'required' => $subfield->required,
'link' => $definition['link'],
];
if (isset($subfield->dateType)) {
$vars['subfields'][$i]['date_type'] = $subfield->dateType;
// Back to date type ID.
$vars['subfields'][$i]['date_storage_format'] = $subfield->dateType == 'date' ? 'Y-m-d' : 'Y-m-d\TH:i:s';
}
if ($definition['random']) {
$vars['random'] = TRUE;
}
if (!$definition['inline']) {
$vars['inline'] = FALSE;
}
if ($vars['subfields'][$i]['list']) {
$vars['list'] = TRUE;
}
if ($vars['subfields'][$i]['required']) {
$vars['required'] = TRUE;
}
if ($type == 'email') {
$vars['email'] = TRUE;
}
if ($definition['link']) {
$vars['link'] = TRUE;
}
if ($type == 'datetime') {
$vars['datetime'] = TRUE;
}
}
$this->io->writeln(\sprintf('<fg=green>%s</>', \str_repeat('', 50)));
$vars['storage_settings'] = $this->confirm('Would you like to create field storage settings form?', FALSE);
$vars['instance_settings'] = $this->confirm('Would you like to create field instance settings form?', FALSE);
$vars['widget_settings'] = $this->confirm('Would you like to create field widget settings form?', FALSE);
$vars['formatter_settings'] = $this->confirm('Would you like to create field formatter settings form?', FALSE);
$vars['table_formatter'] = $this->confirm('Would you like to create table formatter?', FALSE);
$vars['key_value_formatter'] = $this->confirm('Would you like to create key-value formatter?', FALSE);
$this->addFile('src/Plugin/Field/FieldType/{type_class}.php', 'type');
$this->addFile('src/Plugin/Field/FieldWidget/{widget_class}.php', 'widget');
$this->addFile('src/Plugin/Field/FieldFormatter/{formatter_class}.php', 'default-formatter');
$this->addSchemaFile()->template('schema');
$this->addFile('{machine_name}.libraries.yml', 'libraries')
->appendIfExists();
$this->addFile('css/{field_id|u2h}-widget.css', 'widget-css');
if ($vars['table_formatter']) {
$vars['table_formatter_class'] = '{field_label|camelize}TableFormatter';
$this->addFile('src/Plugin/Field/FieldFormatter/{table_formatter_class}.php', '/table-formatter');
}
if ($vars['key_value_formatter']) {
$vars['key_value_formatter_class'] = '{field_label|camelize}KeyValueFormatter';
$this->addFile('src/Plugin/Field/FieldFormatter/{key_value_formatter_class}.php', 'key-value-formatter');
}
}
}

View File

@ -0,0 +1,47 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Form;
use DrupalCodeGenerator\Application;
/**
* Implements form:config command.
*/
final class Config extends FormGenerator {
protected string $name = 'form:config';
protected string $description = 'Generates a configuration form';
protected string $alias = 'config-form';
protected string $templatePath = Application::TEMPLATE_PATH . '/form/config';
protected ?string $defaultPathPrefix = '/admin/config/system';
protected string $defaultPermission = 'administer site configuration';
protected string $defaultClass = 'SettingsForm';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->generateRoute($vars);
if ($vars['route']) {
if ($vars['link'] = $this->confirm('Would you like to create a menu link for this route?')) {
$vars['link_title'] = $this->ask('Link title', $vars['route_title']);
$vars['link_description'] = $this->ask('Link description');
// Try to guess parent menu item using route path.
if (\preg_match('#^/admin/config/([^/]+)/[^/]+$#', $vars['route_path'], $matches)) {
$vars['link_parent'] = $this->ask('Parent menu item', 'system.admin_config_' . $matches[1]);
}
$this->addFile('{machine_name}.links.menu.yml')
->template('links.menu')
->appendIfExists();
}
}
$this->addFile('src/Form/{class}.php', 'form');
$this->addSchemaFile()->template('schema');
}
}

View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Form;
use DrupalCodeGenerator\Application;
/**
* Implements form:confirm command.
*/
final class Confirm extends FormGenerator {
protected string $name = 'form:confirm';
protected string $description = 'Generates a confirmation form';
protected string $alias = 'confirm-form';
protected string $templatePath = Application::TEMPLATE_PATH . '/form/confirm';
protected string $defaultPermission = 'administer site configuration';
protected string $defaultClass = 'ExampleConfirmForm';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->generateRoute($vars);
$this->addFile('src/Form/{class}.php', 'form');
}
}

View File

@ -0,0 +1,57 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Form;
use DrupalCodeGenerator\Command\ModuleGenerator;
use DrupalCodeGenerator\Utils;
/**
* Base class for form generators.
*/
abstract class FormGenerator extends ModuleGenerator {
/**
* Default form class.
*/
protected string $defaultClass;
/**
* Default path prefix.
*/
protected ?string $defaultPathPrefix = NULL;
/**
* Default permission.
*/
protected string $defaultPermission;
/**
* {@inheritdoc}
*/
protected function collectDefault(array &$vars): void {
parent::collectDefault($vars);
$vars['class'] = $this->ask('Class', $this->defaultClass);
$vars['raw_form_id'] = \preg_replace('/_form/', '', Utils::camel2machine($vars['class']));
$vars['form_id'] = '{machine_name}_{raw_form_id}';
}
/**
* Interacts with the user and builds route variables.
*/
protected function generateRoute(array &$vars): void {
$vars['route'] = $this->confirm('Would you like to create a route for this form?');
if ($vars['route']) {
$this->defaultPathPrefix = $this->defaultPathPrefix ?: '/' . $vars['machine_name'];
$default_route_path = \str_replace('_', '-', $this->defaultPathPrefix . '/' . $vars['raw_form_id']);
$vars['route_name'] = $this->ask('Route name', '{machine_name}.' . $vars['raw_form_id']);
$vars['route_path'] = $this->ask('Route path', $default_route_path);
$vars['route_title'] = $this->ask('Route title', '{raw_form_id|m2h}');
$vars['route_permission'] = $this->ask('Route permission', $this->defaultPermission);
$this->addFile('{machine_name}.routing.yml')
->template('form/routing')
->appendIfExists();
}
}
}

View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Form;
use DrupalCodeGenerator\Application;
/**
* Implements form:simple command.
*/
final class Simple extends FormGenerator {
protected string $name = 'form:simple';
protected string $description = 'Generates simple form';
protected string $alias = 'form';
protected string $templatePath = Application::TEMPLATE_PATH . '/form/simple';
protected string $defaultPermission = 'access content';
protected string $defaultClass = 'ExampleForm';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->generateRoute($vars);
$this->addFile('src/Form/{class}.php', 'form');
}
}

View File

@ -0,0 +1,43 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Implements generate-completion command.
*/
class GenerateCompletion extends Command {
/**
* {@inheritdoc}
*/
protected function configure() {
$this
->setName('generate-completion')
->setDescription('Generates shell completion')
->addUsage('--shell=bash >> ~/.bash_completion')
->addOption('shell', NULL, InputOption::VALUE_OPTIONAL, 'Shell type', 'bash');
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output) {
$shell = $input->getOption('shell');
if (!\in_array($shell, ['bash', 'fish', 'zsh'])) {
$message = \sprintf('<error>%s shell is not supported.</error>', \strip_tags($shell));
/** @var \Symfony\Component\Console\Output\ConsoleOutput $output */
$output->getErrorOutput()->writeLn($message);
return 1;
}
$content = \file_get_contents(Application::ROOT . "/resources/$shell-completion");
$output->writeln($content, OutputInterface::OUTPUT_RAW);
return 0;
}
}

View File

@ -0,0 +1,373 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Asset\AssetCollection;
use DrupalCodeGenerator\Asset\Directory;
use DrupalCodeGenerator\Asset\File;
use DrupalCodeGenerator\Asset\Symlink;
use DrupalCodeGenerator\Exception\ExceptionInterface;
use DrupalCodeGenerator\Helper\DumperOptions;
use DrupalCodeGenerator\IOAwareInterface;
use DrupalCodeGenerator\IOAwareTrait;
use DrupalCodeGenerator\Logger\ConsoleLogger;
use DrupalCodeGenerator\Style\GeneratorStyle;
use DrupalCodeGenerator\Utils;
use DrupalCodeGenerator\ValidatorTrait;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\Helper;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
/**
* Base class for code generators.
*/
abstract class Generator extends Command implements GeneratorInterface, IOAwareInterface, LoggerAwareInterface {
use IOAwareTrait;
use LoggerAwareTrait;
use ValidatorTrait;
/**
* The API version.
*/
protected static int $api;
/**
* The command name.
*/
protected string $name;
/**
* The command description.
*/
protected string $description;
/**
* The command alias.
*/
protected string $alias = '';
/**
* A human-readable name of the generator.
*/
protected string $label = '';
/**
* A path where templates are stored.
*/
protected string $templatePath = '';
/**
* The working directory.
*
* This is used to supply generators with some context. For instance, the
* directory name can be used to set default extension name.
*/
protected string $directory;
/**
* Assets to create.
*/
protected AssetCollection $assets;
/**
* Twig template variables.
*
* @var array
*/
private array $vars;
/**
* {@inheritdoc}
*/
protected function configure(): void {
$this
->setName($this->name)
->setDescription($this->description)
->setAliases($this->alias ? [$this->alias] : []);
}
/**
* {@inheritdoc}
*/
protected function initialize(InputInterface $input, OutputInterface $output): void {
$this->assets = new AssetCollection();
$helper_set = $this->getHelperSet();
/** @var \DrupalCodeGenerator\Helper\QuestionHelper $question_helper */
$logger = new ConsoleLogger($output);
$question_helper = $helper_set->get('question');
$io = new GeneratorStyle($input, $output, $question_helper);
$items = \iterator_to_array($this->getHelperSet());
$items[] = $this;
foreach ($items as $item) {
if ($item instanceof IOAwareInterface) {
$item->io($io);
}
if ($item instanceof LoggerAwareInterface) {
$item->setLogger($logger);
}
}
if ($this->templatePath) {
$this->getHelper('renderer')->prependPath($this->templatePath);
}
$this->directory = $input->getOption('working-dir') ?: \getcwd();
$this->logger->debug('Working directory: {directory}', ['directory' => $this->directory]);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
$this->logger->debug('Command: {command}', ['command' => static::class]);
try {
$this->printHeader();
$vars = $this->getDefaultVars();
// Use class property to make vars available for IO helpers.
$this->vars = &$vars;
$this->generate($vars);
$vars = self::processVars($vars);
$collected_vars = \preg_replace('/^Array/', '', \print_r($vars, TRUE));
$this->logger->debug('Collected variables: {vars}', ['vars' => $collected_vars]);
$this->processAssets($vars);
$this->render();
// Destination passed through command line option takes precedence over
// destination defined in a generator.
$destination = $input->getOption('destination') ?: $this->getDestination($vars);
$this->logger->debug('Destination directory: {directory}', ['directory' => $destination]);
$full_path = $input->getOption('full-path');
$dry_run = $input->getOption('dry-run');
$dumped_assets = $this->dump($destination, $dry_run, $full_path);
$this->printSummary($dumped_assets, $full_path ? $destination . '/' : '');
}
catch (ExceptionInterface $exception) {
$this->io()->getErrorStyle()->error($exception->getMessage());
return 1;
}
$this->logger->debug('Memory usage: {memory}', ['memory' => Helper::formatMemory(\memory_get_peak_usage())]);
return 0;
}
/**
* Generates assets.
*/
abstract protected function generate(array &$vars): void;
/**
* Render assets.
*/
protected function render(): void {
$renderer = $this->getHelper('renderer');
foreach ($this->assets->getFiles() as $asset) {
// Supply the asset with all collected variables if it has no local ones.
if (!$asset->getVars()) {
$asset->vars($this->vars);
}
$renderer->renderAsset($asset);
}
}
/**
* Dumps assets.
*/
protected function dump(string $destination, bool $dry_run, bool $full_path): AssetCollection {
$options = new DumperOptions(NULL, $dry_run, $full_path);
return $this->getHelper('dumper')->dump($this->assets, $destination, $options);
}
/**
* Prints header.
*/
protected function printHeader(): void {
$this->io->title(\sprintf('Welcome to %s generator!', $this->getAliases()[0] ?? $this->getName()));
}
/**
* Prints summary.
*/
protected function printSummary(AssetCollection $dumped_assets, string $base_path): void {
$this->getHelper('result_printer')->printResult($dumped_assets, $base_path);
}
/**
* {@inheritdoc}
*/
public function getLabel(): string {
return $this->label;
}
/**
* Asks a question.
*
* @param string $question
* A question to ask.
* @param string|null $default
* The default answer to return if the user enters nothing.
* @param mixed $validator
* A validator for the question.
*
* @return mixed
* The user answer
*/
protected function ask(string $question, ?string $default = NULL, $validator = NULL) {
$question = Utils::stripSlashes(Utils::replaceTokens($question, $this->vars));
if ($default) {
$default = Utils::stripSlashes(Utils::replaceTokens($default, $this->vars));
}
// Allow the validators to be referenced in a short form like
// '::validateMachineName'.
if (\is_string($validator) && \str_starts_with($validator, '::')) {
$validator = [static::class, \substr($validator, 2)];
}
return $this->io->ask($question, $default, $validator);
}
/**
* Asks for confirmation.
*/
protected function confirm(string $question, bool $default = TRUE): bool {
$question = Utils::stripSlashes(Utils::replaceTokens($question, $this->vars));
return (bool) $this->io->confirm($question, $default);
}
/**
* Asks a choice question.
*
* @param string $question
* A question to ask.
* @param array $choices
* The list of available choices.
* @param string|null $default
* The default answer to return if the user enters nothing.
* @param bool $multiselect
* Indicates that multiple choices can be answered.
*
* @return mixed
* The user answer
*/
protected function choice(string $question, array $choices, ?string $default = NULL, bool $multiselect = FALSE) {
$question = Utils::stripSlashes(Utils::replaceTokens($question, $this->vars));
// The choices can be an associative array.
$choice_labels = \array_values($choices);
// Start choices list form '1'.
\array_unshift($choice_labels, NULL);
unset($choice_labels[0]);
$question = new ChoiceQuestion($question, $choice_labels, $default);
$question->setMultiselect($multiselect);
// Do not use IO choice here as it prints choice key as default value.
// @see \Symfony\Component\Console\Style\SymfonyStyle::choice().
$answer = $this->io->askQuestion($question);
// @todo Create a test for this.
$get_key = static fn (string $answer): string => \array_search($answer, $choices);
return \is_array($answer) ? \array_map($get_key, $answer) : $get_key($answer);
}
/**
* Creates a directory asset.
*
* @param string $path
* (Optional) Directory path.
*
* @return \DrupalCodeGenerator\Asset\Directory
* The directory asset.
*/
protected function addDirectory(string $path): Directory {
return $this->assets[] = new Directory($path);
}
/**
* Creates a file asset.
*
* @param string $path
* (Optional) File path.
* @param string|null $template
* (Optional) Template.
*
* @return \DrupalCodeGenerator\Asset\File
* The file asset.
*/
protected function addFile(string $path, ?string $template = NULL): File {
$asset = new File($path);
$asset->template($template);
return $this->assets[] = $asset;
}
/**
* Creates a symlink asset.
*
* @param string $path
* Symlink path.
* @param string $target
* Symlink target.
*
* @return \DrupalCodeGenerator\Asset\File
* The file asset.
*/
protected function addSymlink(string $path, string $target): Symlink {
$asset = new Symlink($path, $target);
return $this->assets[] = $asset;
}
/**
* Processes collected variables.
*/
private static function processVars(array $vars): array {
$process_vars = static function (&$var, string $key, array $vars): void {
if (\is_string($var)) {
$var = Utils::stripSlashes(Utils::replaceTokens($var, $vars));
}
};
\array_walk_recursive($vars, $process_vars, $vars);
return $vars;
}
/**
* Processes collected assets.
*/
private function processAssets(array $vars): void {
foreach ($this->assets as $asset) {
// Local asset variables take precedence over global ones.
$asset->vars(\array_merge($vars, $asset->getVars()));
}
}
/**
* Returns destination for generated files.
*/
protected function getDestination(array $vars): ?string {
return $this->directory;
}
/**
* Returns default template variables.
*/
protected function getDefaultVars(): array {
return [];
}
}

View File

@ -0,0 +1,15 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
/**
* Defines generator interface.
*/
interface GeneratorInterface {
/**
* Returns the human-readable command label.
*/
public function getLabel(): string;
}

View File

@ -0,0 +1,150 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
use Symfony\Component\Console\Question\Question;
/**
* Implements hook command.
*/
final class Hook extends ModuleGenerator {
protected string $name = 'hook';
protected string $description = 'Generates a hook';
protected string $templatePath = Application::TEMPLATE_PATH;
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$hook_question = new Question('Hook name');
$supported_hooks = $this->getSupportedHooks();
$hook_validator = static function ($value) use ($supported_hooks) {
if (!\in_array($value, $supported_hooks)) {
throw new \UnexpectedValueException('The value is not correct hook name.');
}
return $value;
};
$hook_question->setValidator($hook_validator);
$hook_question->setAutocompleterValues($supported_hooks);
$vars['hook_name'] = $this->io->askQuestion($hook_question);
$vars['file_type'] = self::getFileType($vars['hook_name']);
$file = $this->addFile('{machine_name}.{file_type}')
->headerTemplate('_lib/file-docs/{file_type}')
->appendIfExists()
->headerSize(7);
/** @var \DrupalCodeGenerator\Helper\DrupalContext $drupal_context */
if ($this->drupalContext) {
$hook_template = $this->drupalContext->getHooks()[$vars['hook_name']];
$file->inlineTemplate($hook_template);
}
else {
$file->template('hook/{hook_name}');
}
}
/**
* Returns list of supported hooks.
*/
private function getSupportedHooks(): array {
$hook_names = [];
if ($this->drupalContext) {
$hook_names = \array_keys($this->drupalContext->getHooks());
}
// When Drupal context is not provided build list of supported hooks from
// hook template names.
else {
$iterator = new \DirectoryIterator($this->templatePath . '/hook');
foreach ($iterator as $file_info) {
if (!$file_info->isDot()) {
$hook_names[] = $file_info->getBasename('.twig');
}
}
}
return $hook_names;
}
/**
* Returns file type of the hook.
*/
private static function getFileType(string $hook_name): string {
// Drupal hooks that are not situated in MODULE_NAME.module file.
$special_hooks = [
'install' => [
'install',
'uninstall',
'schema',
'requirements',
'update_N',
'update_last_removed',
],
// See views_hook_info().
'views.inc' => [
'views_data',
'views_data_alter',
'views_analyze',
'views_invalidate_cache',
'field_views_data',
'field_views_data_alter',
// See \Drupal\views\views::$plugins.
'views_plugins_access_alter',
'views_plugins_area_alter',
'views_plugins_argument_alter',
'views_plugins_argument_default_alter',
'views_plugins_argument_validator_alter',
'views_plugins_cache_alter',
'views_plugins_display_extender_alter',
'views_plugins_display_alter',
'views_plugins_exposed_form_alter',
'views_plugins_field_alter',
'views_plugins_filter_alter',
'views_plugins_join_alter',
'views_plugins_pager_alter',
'views_plugins_query_alter',
'views_plugins_relationship_alter',
'views_plugins_row_alter',
'views_plugins_sort_alter',
'views_plugins_style_alter',
'views_plugins_wizard_alter',
],
'views_execution.inc' => [
'views_query_substitutions',
'views_form_substitutions',
'views_pre_view',
'views_pre_build',
'views_post_build',
'views_pre_execute',
'views_post_execute',
'views_pre_render',
'views_post_render',
'views_query_alter',
],
// See system_hook_info().
'tokens.inc' => [
'token_info',
'token_info_alter',
'tokens',
'tokens_alter',
],
'post_update.php' => [
'post_update_N',
],
];
foreach ($special_hooks as $group => $hooks) {
if (\in_array($hook_name, $hooks)) {
return $group;
}
}
return 'module';
}
}

View File

@ -0,0 +1,24 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
/**
* Implements install-file command.
*/
final class InstallFile extends ModuleGenerator {
protected string $name = 'install-file';
protected string $description = 'Generates an install file';
protected string $templatePath = Application::TEMPLATE_PATH . '/install-file';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->addFile('{machine_name}.install', 'install');
}
}

View File

@ -0,0 +1,33 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Utils;
/**
* Implements javascript command.
*/
final class JavaScript extends ModuleGenerator {
protected string $name = 'javascript';
protected string $description = 'Generates Drupal JavaScript file';
protected string $templatePath = Application::TEMPLATE_PATH . '/javascript';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['file_name_full'] = $this->ask('File name', '{machine_name|u2h}.js');
$vars['file_name'] = \pathinfo($vars['file_name_full'], \PATHINFO_FILENAME);
$vars['behavior'] = Utils::camelize($vars['machine_name'], FALSE) . Utils::camelize($vars['file_name']);
if ($this->confirm('Would you like to create a library for this file?')) {
$vars['library'] = $this->ask('Library name', '{file_name|h2u}');
$this->addFile('{machine_name}.libraries.yml', 'libraries')
->appendIfExists();
}
$this->addFile('js/{file_name_full}', 'javascript');
}
}

View File

@ -0,0 +1,51 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
/**
* Implements layout command.
*/
final class Layout extends ModuleGenerator {
protected string $name = 'layout';
protected string $description = 'Generates a layout';
protected ?string $nameQuestion = NULL;
protected string $templatePath = Application::TEMPLATE_PATH . '/layout';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['layout_name'] = $this->ask('Layout name', 'Example');
$vars['layout_machine_name'] = $this->ask('Layout machine name', '{layout_name|h2m}');
$vars['category'] = $this->ask('Category', 'My layouts');
$vars['js'] = $this->confirm('Would you like to create JavaScript file for this layout?', FALSE);
$vars['css'] = $this->confirm('Would you like to create CSS file for this layout?', FALSE);
$this->addFile('{machine_name}.layouts.yml', 'layouts')
->appendIfExists();
if ($vars['js'] || $vars['css']) {
$this->addFile('{machine_name}.libraries.yml', 'libraries')
->appendIfExists();
}
$vars['layout_asset_name'] = '{layout_machine_name|u2h}';
$this->addFile('layouts/{layout_machine_name}/{layout_asset_name}.html.twig', 'template');
if ($vars['js']) {
$this->addFile('layouts/{layout_machine_name}/{layout_asset_name}.js', 'javascript');
}
if ($vars['css']) {
$this->addFile('layouts/{layout_machine_name}/{layout_asset_name}.css', 'styles');
}
}
}

View File

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\Generator;
/**
* Implements misc:apache-virtual-host command.
*/
final class ApacheVirtualHost extends Generator {
protected string $name = 'misc:apache-virtual-host';
protected string $description = 'Generates an Apache site configuration file';
protected string $alias = 'apache-virtual-host';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/apache-virtual-host';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$validator = static function (?string $value): string {
$value = self::validateRequired($value);
if (!\filter_var($value, \FILTER_VALIDATE_DOMAIN, \FILTER_FLAG_HOSTNAME)) {
throw new \UnexpectedValueException('The value is not correct domain name.');
}
return $value;
};
$vars['hostname'] = $this->ask('Host name', 'example.local', $validator);
$vars['docroot'] = $this->ask('Document root', '/var/www/{hostname}/public');
$this->addFile('{hostname}.conf', 'host');
$this->addFile('{hostname}-ssl.conf', 'host-ssl');
}
}

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7\CToolsPlugin;
use DrupalCodeGenerator\Application;
/**
* Implements misc:d7:ctools-plugin:access command.
*/
final class Access extends BasePlugin {
protected string $name = 'misc:d7:ctools-plugin:access';
protected string $description = 'Generates CTools access plugin';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/ctools-plugin/access';
protected string $template = 'access';
protected string $subDirectory = 'plugins/access';
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7\CToolsPlugin;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Base class for misc:d7:ctools-plugin commands.
*/
abstract class BasePlugin extends ModuleGenerator {
protected string $template;
protected string $subDirectory;
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['plugin_name'] = $this->ask('Plugin name', 'Example', '::validateRequired');
$vars['plugin_machine_name'] = $this->ask('Plugin machine name', '{plugin_name|h2m}', '::validateRequiredMachineName');
$vars['description'] = $this->ask('Plugin description', 'Plugin description.');
$vars['category'] = $this->ask('Category', 'Custom');
$contexts = ['-', 'Node', 'User', 'Term'];
$vars['context'] = $this->io->choice('Required context', $contexts);
$this->addFile($this->subDirectory . '/{plugin_machine_name}.inc')
->template($this->template);
}
}

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7\CToolsPlugin;
use DrupalCodeGenerator\Application;
/**
* Implements misc:d7:ctools-plugin:content-type command.
*/
final class ContentType extends BasePlugin {
protected string $name = 'misc:d7:ctools-plugin:content-type';
protected string $description = 'Generates CTools content type plugin';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/ctools-plugin/content-type';
protected string $template = 'content-type';
protected string $subDirectory = 'plugins/content_types';
}

View File

@ -0,0 +1,18 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7\CToolsPlugin;
use DrupalCodeGenerator\Application;
/**
* Implements misc:d7:ctools-plugin:relationship command.
*/
final class Relationship extends BasePlugin {
protected string $name = 'misc:d7:ctools-plugin:relationship';
protected string $description = 'Generates CTools relationship plugin';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/ctools-plugin/relationship';
protected string $template = 'relationship';
protected string $subDirectory = 'plugins/relationships';
}

View File

@ -0,0 +1,83 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
use Symfony\Component\Console\Question\Question;
/**
* Implements misc:d7:hook command.
*/
final class Hook extends ModuleGenerator {
protected string $name = 'misc:d7:hook';
protected string $description = 'Generates a hook';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/hook';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$question = new Question('Hook name');
$question->setValidator(function (?string $value): ?string {
if (!\in_array($value, $this->getSupportedHooks())) {
throw new \UnexpectedValueException('The value is not correct hook name.');
}
return $value;
});
$question->setAutocompleterValues($this->getSupportedHooks());
$vars['hook_name'] = $this->io->askQuestion($question);
// Most Drupal hooks are situated in a module file but some are not.
$special_hooks = [
'install' => [
'install',
'uninstall',
'enable',
'disable',
'schema',
'schema_alter',
'field_schema',
'requirements',
'update_N',
'update_last_removed',
],
// See system_hook_info().
'tokens.inc' => [
'token_info',
'token_info_alter',
'tokens',
'tokens_alter',
],
];
$file_type = 'module';
foreach ($special_hooks as $group => $hooks) {
if (\in_array($vars['hook_name'], $hooks)) {
$file_type = $group;
break;
}
}
$this->addFile("{machine_name}.$file_type")
->headerTemplate("misc/d7/_lib/file-docs/$file_type")
->template('{hook_name}')
->appendIfExists()
->headerSize(7);
}
/**
* Gets list of supported hooks.
*
* @return array
* List of supported hooks.
*/
protected function getSupportedHooks(): array {
return \array_map(static fn (string $file): string => \pathinfo($file, \PATHINFO_FILENAME), \array_diff(\scandir(Application::TEMPLATE_PATH . '/misc/d7/hook'), ['.', '..']));
}
}

View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements misc:d7:install-file command.
*/
final class InstallFile extends ModuleGenerator {
protected string $name = 'misc:d7:install-file';
protected string $description = 'Generates Drupal 7 install file';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/install-file';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->addFile('{machine_name}.install', 'install');
}
}

View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements misc:d7:javascript command.
*/
final class JavaScript extends ModuleGenerator {
protected string $name = 'misc:d7:javascript';
protected string $description = 'Generates Drupal 7 JavaScript file';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/javascript';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->addFile('{machine_name|u2h}.js', 'javascript');
}
}

View File

@ -0,0 +1,34 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements misc:d7:module command.
*/
final class Module extends ModuleGenerator {
protected string $name = 'misc:d7:module';
protected string $description = 'Generates Drupal 7 module';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['description'] = $this->ask('Module description', 'Module description.');
$vars['package'] = $this->ask('Package', 'Custom');
$this->addFile('{machine_name}/{machine_name}.info', 'module-info/module-info');
$this->addFile('{machine_name}/{machine_name}.module', 'module-file/module');
$this->addFile('{machine_name}/{machine_name}.install', 'install-file/install');
$this->addFile('{machine_name}/{machine_name}.admin.inc', 'admin.inc');
$this->addFile('{machine_name}/{machine_name}.pages.inc', 'pages.inc');
$this->addFile('{machine_name}/{machine_name|u2h}.js', 'javascript/javascript');
}
}

View File

@ -0,0 +1,25 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements misc:d7:module-file command.
*/
final class ModuleFile extends ModuleGenerator {
protected string $name = 'misc:d7:module-file';
protected string $description = 'Generates Drupal 7 module file';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/module-file';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->addFile('{machine_name}.module', 'module');
}
}

View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements misc:d7:module-info command.
*/
final class ModuleInfo extends ModuleGenerator {
protected string $name = 'misc:d7:module-info';
protected string $description = 'Generates Drupal 7 info file for a module';
protected string $label = 'Info (module)';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/module-info';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['description'] = $this->ask('Module description', 'Module description.');
$vars['package'] = $this->ask('Package', 'Custom');
$this->addFile('{machine_name}.info', 'module-info');
}
}

View File

@ -0,0 +1,36 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\Generator;
use Symfony\Component\Console\Question\Question;
/**
* Implements misc:d7:settings.php command.
*/
final class Settings extends Generator {
protected string $name = 'misc:d7:settings.php';
protected string $description = 'Generates Drupal 7 settings.php file';
protected string $label = 'settings.php';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/settings.php';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$db_driver_question = new Question('Database driver', 'mysql');
$db_driver_question->setAutocompleterValues(['mysql', 'pgsql', 'sqlite']);
$vars['db_driver'] = $this->io->askQuestion($db_driver_question);
$vars['db_name'] = $this->ask('Database name', 'drupal');
$vars['db_user'] = $this->ask('Database user', 'root');
$vars['db_password'] = $this->ask('Database password', '123');
// @see: drupal_get_hash_salt()
$vars['hash_salt'] = \hash('sha256', \serialize($vars));
$this->addFile('settings.php', 'settings');
}
}

View File

@ -0,0 +1,27 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ThemeGenerator;
/**
* Implements misc:d7:template.php command.
*/
final class TemplatePhp extends ThemeGenerator {
protected string $name = 'misc:d7:template.php';
protected string $description = 'Generates Drupal 7 template.php file';
protected string $alias = 'template.php';
protected string $label = 'template.php';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/template.php';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->addFile('template.php', 'template.php');
}
}

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements misc:d7:test command.
*/
final class Test extends ModuleGenerator {
protected string $name = 'misc:d7:test';
protected string $description = 'Generates Drupal 7 test case';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/test';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['class'] = $this->ask('Class', '{machine_name|camelize}TestCase');
$this->addFile('{machine_name}.test', 'test');
}
}

View File

@ -0,0 +1,35 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ThemeGenerator;
/**
* Implements misc:d7:theme command.
*/
final class Theme extends ThemeGenerator {
protected string $name = 'misc:d7:theme';
protected string $description = 'Generates Drupal 7 theme';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['description'] = $this->ask('Theme description', 'A simple Drupal 7 theme.');
$vars['base_theme'] = $this->ask('Base theme');
$vars['asset_name'] = '{machine_name|u2h}';
$this->addFile('{machine_name}/{machine_name}.info', 'theme-info/theme-info');
$this->addFile('{machine_name}/template.php', 'template.php/template.php');
$this->addFile('{machine_name}/js/{asset_name}.js', 'javascript/javascript');
$this->addFile('{machine_name}/css/{asset_name}.css', 'theme-css');
$this->addDirectory('{machine_name}/templates');
$this->addDirectory('{machine_name}/images');
}
}

View File

@ -0,0 +1,28 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ThemeGenerator;
/**
* Implements misc:d7:theme-info command.
*/
final class ThemeInfo extends ThemeGenerator {
protected string $name = 'misc:d7:theme-info';
protected string $description = 'Generates info file for a Drupal 7 theme';
protected string $label = 'Info (theme)';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/theme-info';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['description'] = $this->ask('Theme description', 'A simple Drupal 7 theme.');
$vars['base_theme'] = $this->ask('Base theme');
$this->addFile('{machine_name}.info', 'theme-info');
}
}

View File

@ -0,0 +1,39 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc\Drupal_7\ViewsPlugin;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\ModuleGenerator;
/**
* Implements misc:d7:views-plugin:argument-default command.
*/
final class ArgumentDefault extends ModuleGenerator {
protected string $name = 'misc:d7:views-plugin:argument-default';
protected string $description = 'Generates Drupal 7 argument default views plugin';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/d7/views-plugin/argument-default';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['plugin_name'] = $this->ask('Plugin name', 'Example');
$vars['plugin_machine_name'] = $this->ask('Plugin machine name', '{plugin_name|h2m}');
$this->addFile('{machine_name}.module')
->template('module')
->appendIfExists()
->headerSize(7);
$this->addFile('views/{machine_name}.views.inc')
->template('views.inc')
->appendIfExists()
->headerSize(7);
$this->addFile('views/views_plugin_argument_{plugin_machine_name}.inc')
->template('argument-default');
}
}

View File

@ -0,0 +1,31 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\Generator;
/**
* Implements misc:html-page command.
*/
final class HtmlPage extends Generator {
protected string $name = 'misc:html-page';
protected string $description = 'Generates a simple html page';
protected string $alias = 'html-page';
protected string $label = 'HTML page';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/html-page';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$vars['file_name'] = $this->ask('File name', 'index.html');
$this->addFile('{file_name}', 'index');
$this->addFile('css/main.css')
->content("body{\n background-color: #EEE;\n}\n");
$this->addFile('js/main.js')
->content("console.log('It works!');\n");
}
}

View File

@ -0,0 +1,38 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\Generator;
/**
* Implements misc:nginx-virtual-host command.
*/
final class NginxVirtualHost extends Generator {
protected string $name = 'misc:nginx-virtual-host';
protected string $description = 'Generates an Nginx site configuration file';
protected string $alias = 'nginx-virtual-host';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/nginx-virtual-host';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$socket = \PHP_MAJOR_VERSION == 5
? '/run/php5-fpm.sock'
: \sprintf('/run/php/php%s.%s-fpm.sock', \PHP_MAJOR_VERSION, \PHP_MINOR_VERSION);
$vars['server_name'] = $this->ask('Server name', 'example.com');
$vars['docroot'] = $this->ask('Document root', '/var/www/{server_name}/docroot');
$vars['file_public_path'] = $this->ask('Public file system path', 'sites/default/files');
$vars['file_private_path'] = $this->ask('Private file system path');
$vars['fastcgi_pass'] = $this->ask('Address of a FastCGI server', 'unix:' . $socket);
$vars['file_public_path'] = \trim($vars['file_public_path'], '/');
$vars['file_private_path'] = \trim($vars['file_private_path'], '/');
$this->addFile('{server_name}', 'host.twig');
}
}

View File

@ -0,0 +1,298 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Misc;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Asset\AssetCollection;
use DrupalCodeGenerator\Command\Generator;
use DrupalCodeGenerator\Utils;
use Symfony\Component\Console\Question\Question;
/**
* Implements misc:project command.
*
* Inspired by drupal-composer/drupal-project.
*/
final class Project extends Generator {
protected string $name = 'misc:project';
protected string $description = 'Generates a composer project';
protected string $alias = 'project';
protected string $templatePath = Application::TEMPLATE_PATH . '/misc/project';
/**
* Array of packages to check versions for.
*
* The key is package name and the value is allowable major version.
*/
private const PACKAGES = [
'composer/installers' => '^1.8',
'cweagans/composer-patches' => '^1.6',
'drupal/core' => '^8.8',
'drupal/core-composer-scaffold' => '^8.8',
'drush/drush' => '^10.2',
'oomphinc/composer-installers-extender' => '^1.1',
'symfony/dotenv' => '^4.4',
'drupal/core-recommended' => '^8.8',
'drupal/core-dev' => '^8.8',
'zaporylie/composer-drupal-optimizations' => '^1.1',
'weitzman/drupal-test-traits' => '^1.3',
];
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$name_validator = static function (?string $value): ?string {
$value = self::validateRequired($value);
if (!\preg_match('#[^/]+/[^/]+$#i', $value)) {
throw new \UnexpectedValueException('The value is not correct project name.');
}
return $value;
};
$vars['name'] = $this->ask('Project name (vendor/name)', NULL, $name_validator);
$vars['description'] = $this->ask('Description');
$license_question = new Question('License', 'GPL-2.0-or-later');
// @see https://getcomposer.org/doc/04-schema.md#license
$licenses = [
'Apache-2.0',
'BSD-2-Clause',
'BSD-3-Clause',
'BSD-4-Clause',
'GPL-2.0-only',
'GPL-2.0-or-later',
'GPL-3.0-only',
'GPL-3.0-or-later',
'LGPL-2.1-onl',
'LGPL-2.1-or-later',
'LGPL-3.0-only',
'LGPL-3.0-or-later',
'MIT',
'proprietary',
];
$license_question->setAutocompleterValues($licenses);
$vars['license'] = $this->io->askQuestion($license_question);
// Suggest most typical document roots.
$document_roots = [
'docroot',
'web',
'www',
'public_html',
'public',
'htdocs',
'httpdocs',
'html',
];
$document_root_question = new Question('Document root directory', 'docroot');
$document_root_question->setAutocompleterValues($document_roots);
$vars['document_root'] = $this->io->askQuestion($document_root_question);
$vars['document_root_path'] = $vars['document_root'] . '/';
$vars['php'] = $this->ask('PHP version', '>=' . \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION);
$vars['drupal_core_recommended'] = $this->confirm('Would you like to install recommended Drupal core dependencies?', FALSE);
$vars['drupal_core_dev'] = $this->confirm('Would you like to install Drupal core development dependencies?', FALSE);
$vars['drush'] = $this->confirm('Would you like to install Drush?');
$vars['composer_patches'] = $this->confirm('Would you like to install Composer patches plugin?');
$vars['env'] = $this->confirm('Would you like to load environment variables from .env files?', FALSE);
$vars['asset_packagist'] = $this->confirm('Would you like to add asset-packagist repository?', FALSE);
$vars['tests'] = $this->confirm('Would you like to create tests?', FALSE);
if ($vars['tests']) {
// @codingStandardsIgnoreStart
[$vendor, $short_name] = explode('/', $vars['name']);
$vars['namespace'] = Utils::camelize($vendor == $short_name ? $vendor : $vars['name']);
// @codingStandardsIgnoreEnd
}
$this->addFile('composer.json')->content($this->buildComposerJson($vars));
$this->addFile('.gitignore', 'gitignore');
$this->addFile('phpcs.xml', 'phpcs.xml');
if ($vars['env']) {
$this->addFile('.env.example', 'env.example');
$this->addFile('load.environment.php', 'load.environment.php');
}
if ($vars['document_root']) {
$this->addDirectory('config/sync');
}
if ($vars['drush']) {
$this->addFile('drush/Commands/PolicyCommands.php', 'drush/Commands/PolicyCommands.php');
$this->addFile('drush/sites/self.site.yml', 'drush/sites/self.site.yml');
$this->addFile('scripts/sync-site.sh', 'scripts/sync-site.sh')->mode(0544);
}
if ($vars['tests']) {
$this->addFile('phpunit.xml', 'phpunit.xml');
$this->addFile('tests/src/HomePageTest.php', 'tests/src/HomePageTest.php');
}
$this->addFile('patches/.keep')->content('');
$this->addDirectory('{document_root}/modules/contrib');
$this->addDirectory('{document_root}/modules/custom');
$this->addDirectory('{document_root}/themes/custom');
$this->addDirectory('{document_root}/libraries');
}
/**
* {@inheritdoc}
*/
protected function printSummary(AssetCollection $dumped_assets, string $base_path): void {
parent::printSummary($dumped_assets, $base_path);
$message = [
'Next steps:',
'',
'1. Review generated files',
'2. Run <comment>composer install</comment> command',
'3. Install Drupal',
];
$this->io->text($message);
$this->io->newLine();
}
/**
* Builds composer.json file.
*
* @param array $vars
* Collected variables.
*
* @return string
* Encoded JSON content.
*/
private function buildComposerJson(array $vars): string {
$document_root_path = $vars['document_root_path'];
$composer_json = [];
$composer_json['name'] = $vars['name'];
$composer_json['description'] = (string) $vars['description'];
$composer_json['type'] = 'project';
$composer_json['license'] = $vars['license'];
$composer_json['repositories'][] = [
'type' => 'composer',
'url' => 'https://packages.drupal.org/8',
];
if ($vars['asset_packagist']) {
$composer_json['repositories'][] = [
'type' => 'composer',
'url' => 'https://asset-packagist.org',
];
}
$require = [];
$require_dev = [];
$this->addPackage($require, 'drupal/core-composer-scaffold');
$this->addPackage($require, 'zaporylie/composer-drupal-optimizations');
if ($vars['asset_packagist']) {
$this->addPackage($require, 'oomphinc/composer-installers-extender');
}
if ($vars['drupal_core_recommended']) {
$this->addPackage($require, 'drupal/core-recommended');
}
else {
$this->addPackage($require, 'drupal/core');
$this->addPackage($require, 'composer/installers');
}
if ($vars['drupal_core_dev']) {
$this->addPackage($require_dev, 'drupal/core-dev');
}
if ($vars['drush']) {
$this->addPackage($require, 'drush/drush');
}
if ($vars['composer_patches']) {
$this->addPackage($require, 'cweagans/composer-patches');
}
if ($vars['env']) {
$this->addPackage($require, 'symfony/dotenv');
$composer_json['autoload']['files'][] = 'load.environment.php';
}
if ($vars['tests']) {
$this->addPackage($require_dev, 'weitzman/drupal-test-traits');
$composer_json['autoload-dev']['psr-4'][$vars['namespace'] . '\\Tests\\'] = 'tests/src';
}
$composer_json['require'] = [
'php' => $vars['php'],
'ext-curl' => '*',
'ext-gd' => '*',
'ext-json' => '*',
];
\ksort($require);
$composer_json['require'] += $require;
\ksort($require_dev);
$composer_json['require-dev'] = (object) $require_dev;
$composer_json['scripts']['phpcs'] = 'phpcs --standard=phpcs.xml';
$composer_json['minimum-stability'] = 'dev';
$composer_json['prefer-stable'] = TRUE;
$composer_json['config'] = [
'sort-packages' => TRUE,
'bin-dir' => 'bin',
];
if ($vars['composer_patches']) {
$composer_json['extra']['composer-exit-on-patch-failure'] = TRUE;
}
$composer_json['extra']['drupal-scaffold']['locations']['web-root'] = $vars['document_root_path'];
if ($vars['asset_packagist']) {
$composer_json['extra']['installer-types'] = [
'bower-asset',
'npm-asset',
];
}
$composer_json['extra']['installer-paths'] = [
$document_root_path . 'core' => ['type:drupal-core'],
$document_root_path . 'libraries/{$name}' => ['type:drupal-library'],
$document_root_path . 'modules/contrib/{$name}' => ['type:drupal-module'],
$document_root_path . 'themes/{$name}' => ['type:drupal-theme'],
'drush/{$name}' => ['type:drupal-drush'],
];
if ($vars['asset_packagist']) {
$composer_json['extra']['installer-paths'][$document_root_path . 'libraries/{$name}'][] = 'type:bower-asset';
$composer_json['extra']['installer-paths'][$document_root_path . 'libraries/{$name}'][] = 'type:npm-asset';
}
return \json_encode($composer_json, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES) . "\n";
}
/**
* Requires a given package.
*
* @param array $section
* Section for the package (require|require-dev)
* @param string $package
* A package to be added.
*/
private function addPackage(array &$section, string $package): void {
if (!\array_key_exists($package, self::PACKAGES)) {
throw new \InvalidArgumentException("Package $package is unknown.");
}
$section[$package] = self::PACKAGES[$package];
}
}

View File

@ -0,0 +1,82 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
/**
* Implements module command.
*/
final class Module extends ModuleGenerator {
protected string $name = 'module';
protected string $description = 'Generates Drupal module';
protected bool $isNewExtension = TRUE;
protected string $templatePath = Application::TEMPLATE_PATH . '/module';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['description'] = $this->ask('Module description', 'Provides additional functionality for the site.', '::validateRequired');
$vars['package'] = $this->ask('Package', 'Custom');
$dependencies = $this->ask('Dependencies (comma separated)');
$vars['dependencies'] = $dependencies ?
\array_map('trim', \explode(',', \strtolower($dependencies))) : [];
$vars['class_prefix'] = '{machine_name|camelize}';
$this->addFile('{machine_name}/{machine_name}.info.yml', 'model.info.yml');
if ($this->confirm('Would you like to create module file?', FALSE)) {
$this->addFile('{machine_name}/{machine_name}.module', 'model.module');
}
if ($this->confirm('Would you like to create install file?', FALSE)) {
$this->addFile('{machine_name}/{machine_name}.install', 'model.install');
}
if ($this->confirm('Would you like to create libraries.yml file?', FALSE)) {
$this->addFile('{machine_name}/{machine_name}.libraries.yml', 'model.libraries.yml');
}
if ($this->confirm('Would you like to create permissions.yml file?', FALSE)) {
$this->addFile('{machine_name}/{machine_name}.permissions.yml', 'model.permissions.yml');
}
if ($this->confirm('Would you like to create event subscriber?', FALSE)) {
$this->addFile("{machine_name}/src/EventSubscriber/{class_prefix}Subscriber.php")
->template('src/EventSubscriber/ExampleSubscriber.php');
$this->addFile('{machine_name}/{machine_name}.services.yml', 'model.services.yml');
}
if ($this->confirm('Would you like to create block plugin?', FALSE)) {
$this->addFile('{machine_name}/src/Plugin/Block/ExampleBlock.php')
->template('src/Plugin/Block/ExampleBlock.php');
}
if ($vars['controller'] = $this->confirm('Would you like to create a controller?', FALSE)) {
$this->addFile("{machine_name}/src/Controller/{class_prefix}Controller.php")
->template('src/Controller/ExampleController.php');
}
if ($vars['form'] = $this->confirm('Would you like to create settings form?', FALSE)) {
$this->addFile('{machine_name}/src/Form/SettingsForm.php')
->template('src/Form/SettingsForm.php');
$this->addFile('{machine_name}/config/schema/{machine_name}.schema.yml')
->template('config/schema/model.schema.yml');
$this->addFile('{machine_name}/{machine_name}.links.menu.yml')
->template('model.links.menu');
}
if ($vars['controller'] || $vars['form']) {
$this->addFile('{machine_name}/{machine_name}.routing.yml')
->template('model.routing.yml');
}
}
}

View File

@ -0,0 +1,24 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
/**
* Implements module-file command.
*/
final class ModuleFile extends ModuleGenerator {
protected string $name = 'module-file';
protected string $description = 'Generates a module file';
protected string $templatePath = Application::TEMPLATE_PATH . '/module-file';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->addFile('{machine_name}.module', 'module');
}
}

View File

@ -0,0 +1,152 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Asset\Asset;
use Symfony\Component\Console\Question\Question;
/**
* Base class for module generators.
*/
abstract class ModuleGenerator extends DrupalGenerator {
protected ?string $nameQuestion = 'Module name';
protected ?string $machineNameQuestion = 'Module machine name';
protected ?int $extensionType = self::EXTENSION_TYPE_MODULE;
/**
* Adds an asset for service file.
*
* @param string $path
* (Optional) File path.
*
* @return \DrupalCodeGenerator\Asset\File
* The asset.
*/
protected function addServicesFile(string $path = '{machine_name}.services.yml'): Asset {
return $this->addFile($path)
->appendIfExists()
->headerSize(1);
}
/**
* Adds an asset for configuration schema file.
*
* @param string $path
* (Optional) File path.
*
* @return \DrupalCodeGenerator\Asset\File
* The asset.
*/
protected function addSchemaFile(string $path = 'config/schema/{machine_name}.schema.yml'): Asset {
return $this->addFile($path)
->appendIfExists();
}
/**
* Collects services.
*
* @param array $vars
* Template variables.
* @param bool $default
* (Optional) Default value for the confirmation question.
*
* @return array
* List of collected services.
*/
protected function collectServices(array &$vars, bool $default = TRUE): array {
if (!$this->confirm('Would you like to inject dependencies?', $default)) {
return $vars['services'] = [];
}
$service_ids = $this->getServiceIds();
$services = [];
while (TRUE) {
$question = new Question('Type the service name or use arrows up/down. Press enter to continue');
$question->setValidator([static::class, 'validateServiceName']);
$question->setAutocompleterValues($service_ids);
$service = $this->io()->askQuestion($question);
if (!$service) {
break;
}
$services[] = $service;
}
$vars['services'] = [];
foreach (\array_unique($services) as $service_id) {
$vars['services'][$service_id] = $this->getServiceDefinition($service_id);
}
return $vars['services'];
}
/**
* Gets service definitions.
*
* @return array
* List of service IDs.
*/
protected function getServiceIds(): array {
if ($this->drupalContext) {
$data = $this->drupalContext->getServicesIds();
}
else {
$service_definitions = self::getDumpedServiceDefinitions();
$data = \array_keys($service_definitions);
}
return $data;
}
/**
* Gets service definitions.
*
* @param string $service_id
* The service ID.
*
* @return array
* Service definition or null if service is unknown.
*/
protected function getServiceDefinition(string $service_id): array {
$service_definitions = self::getDumpedServiceDefinitions();
if (isset($service_definitions[$service_id])) {
$definition = $service_definitions[$service_id];
}
else {
// Make up service definition.
$name_parts = \explode('.', $service_id);
$definition = [
'name' => \end($name_parts),
'type' => 'Drupal\example\ExampleInterface',
'description' => "The $service_id service.",
];
if ($this->drupalContext) {
// Try to guess correct type of service instance.
$compiled_definition = $this->drupalContext->getServiceDefinition($service_id);
if ($compiled_definition && isset($compiled_definition['class'])) {
$interface = $compiled_definition['class'] . 'Interface';
$definition['type'] = \interface_exists($interface) ? $interface : $compiled_definition['class'];
}
}
}
$type_parts = \explode('\\', $definition['type']);
$definition['short_type'] = \end($type_parts);
return $definition;
}
/**
* Gets service definitions.
*
* @return array
* List of service definitions keyed by service ID.
*/
private static function getDumpedServiceDefinitions(): array {
$data_encoded = \file_get_contents(Application::ROOT . '/resources/service-definitions.json');
return \json_decode($data_encoded, TRUE);
}
}

View File

@ -0,0 +1,204 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\IOAwareInterface;
use DrupalCodeGenerator\IOAwareTrait;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Question\ChoiceQuestion;
/**
* Implements navigation command.
*/
final class Navigation extends Command implements IOAwareInterface, LoggerAwareInterface {
use IOAwareTrait;
use LoggerAwareTrait;
/**
* Menu tree.
*
* @var array
*/
private array $menuTree = [];
/**
* Menu labels.
*
* @var array
*/
private array $labels = [
'misc:d7' => 'Drupal 7',
'yml' => 'Yaml',
'misc' => 'Miscellaneous',
];
/**
* {@inheritdoc}
*/
protected function configure() {
// As the navigation is default command the help should be relevant to the
// entire DCG application.
$help = <<<'EOT'
<info>dcg</info> Display navigation
<info>dcg plugin:field:widget</info> Run a specific generator
<info>dcg list</info> List all available generators
EOT;
$this
->setName('navigation')
->setDescription('Command line code generator')
->setHelp($help)
->setHidden(TRUE);
}
/**
* {@inheritdoc}
*/
public function getSynopsis($short = FALSE): string {
return 'dcg [options] <generator>';
}
/**
* {@inheritdoc}
*/
protected function initialize(InputInterface $input, OutputInterface $output): void {
parent::initialize($input, $output);
// Build the menu structure.
$this->menuTree = [];
foreach ($this->getApplication()->all() as $command) {
if ($command instanceof GeneratorInterface && !$command->isHidden()) {
self::arraySetNestedValue($this->menuTree, \explode(':', $command->getName()));
// Collect command labels.
if ($label = $command->getLabel()) {
$this->labels[$command->getName()] = $label;
}
}
}
self::recursiveKsort($this->menuTree);
$style = new OutputFormatterStyle('white', 'blue', ['bold']);
$output->getFormatter()->setStyle('title', $style);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output): int {
if ($command_name = $this->selectGenerator($input, $output)) {
return $this->getApplication()
->find($command_name)
->run($input, $output);
}
return 0;
}
/**
* Returns a generator selected by the user from a multilevel console menu.
*
* @param \Symfony\Component\Console\Input\InputInterface $input
* Input instance.
* @param \Symfony\Component\Console\Output\OutputInterface $output
* Output instance.
* @param array $menu_trail
* (Optional) Menu trail.
*
* @return string|null
* Generator name or null if user decided to exit the navigation.
*/
private function selectGenerator(InputInterface $input, OutputInterface $output, array $menu_trail = []): ?string {
// Narrow down the menu tree using menu trail.
$active_menu_tree = $this->menuTree;
foreach ($menu_trail as $active_menu_item) {
$active_menu_tree = $active_menu_tree[$active_menu_item];
}
// The $active_menu_tree can be either an array of menu items or TRUE if the
// user has reached the final menu point.
if ($active_menu_tree === TRUE) {
return \implode(':', $menu_trail);
}
$sub_menu_labels = $command_labels = [];
foreach ($active_menu_tree as $menu_item => $subtree) {
$command_name = $menu_trail ? (\implode(':', $menu_trail) . ':' . $menu_item) : $menu_item;
$label = $this->labels[$command_name] ?? \str_replace(['-', '_'], ' ', \ucfirst($menu_item));
\is_array($subtree)
? $sub_menu_labels[$menu_item] = "<comment>$label</comment>"
: $command_labels[$menu_item] = $label;
}
// Generally the choices array consists of the following parts:
// - Reference to the parent menu level.
// - Sorted list of nested menu levels.
// - Sorted list of commands.
\natcasesort($sub_menu_labels);
\natcasesort($command_labels);
$choices = ['..' => '..'] + $sub_menu_labels + $command_labels;
$question = new ChoiceQuestion('<title> Select generator </title>', \array_values($choices));
$answer_label = $this->getHelper('question')->ask($input, $output, $question);
$answer = \array_search($answer_label, $choices);
if ($answer == '..') {
// Exit the application if a user selected zero on the top menu level.
if (\count($menu_trail) == 0) {
return NULL;
}
// Level up.
\array_pop($menu_trail);
}
else {
// Level down.
$menu_trail[] = $answer;
}
return $this->selectGenerator($input, $output, $menu_trail);
}
/**
* Sort multi-dimensional array by keys.
*
* @param array $array
* An array being sorted.
*/
private static function recursiveKsort(array &$array): void {
foreach ($array as &$value) {
if (\is_array($value)) {
self::recursiveKsort($value);
}
}
\ksort($array);
}
/**
* Sets the property to true in nested array.
*
* @param array $array
* A reference to the array to modify.
* @param array $parents
* An array of parent keys, starting with the outermost key.
*
* @see https://api.drupal.org/api/drupal/includes!common.inc/function/drupal_array_set_nested_value/7
*/
private static function arraySetNestedValue(array &$array, array $parents): void {
$ref = &$array;
foreach ($parents as $parent) {
if (isset($ref) && !\is_array($ref)) {
$ref = [];
}
$ref = &$ref[$parent];
}
$ref ??= TRUE;
}
}

View File

@ -0,0 +1,78 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Exception\RuntimeException;
use DrupalCodeGenerator\Helper\DrupalContext;
/**
* Implements phpstorm-metadata command.
*/
final class PhpStormMetadata extends DrupalGenerator {
protected string $name = 'phpstorm-metadata';
protected string $description = 'Generates PhpStorm metadata';
protected string $label = 'PhpStorm metadata';
protected string $templatePath = Application::TEMPLATE_PATH . '/phpstorm-metadata';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
if (!$this->drupalContext) {
throw new RuntimeException('Could not bootstrap Drupal to fetch metadata.');
}
$container = $this->drupalContext->getContainer();
$service_definitions = $this->drupalContext
->getContainer()
->get('kernel')
->getCachedContainerDefinition()['services'];
$service_definitions = \array_map('unserialize', $service_definitions);
foreach ($service_definitions as $service_id => $service_definition) {
if ($service_definition['class']) {
$vars['services'][$service_id] = $service_definition['class'];
}
}
$entity_type_manager = $container->get('entity_type.manager');
$vars['storages'] = [];
$vars['view_builders'] = [];
$vars['list_builders'] = [];
$vars['access_controls'] = [];
$vars['entity_classes'] = [];
foreach ($entity_type_manager->getDefinitions() as $type => $definition) {
/** @var \Drupal\Core\Entity\EntityTypeInterface $definition */
$vars['entity_classes'][] = $definition->getClass();
$vars['storages'][$type] = $definition->getStorageClass();
$vars['access_controls'][$type] = $definition->getAccessControlClass();
if ($definition->hasViewBuilderClass()) {
$vars['view_builders'][$type] = $definition->getViewBuilderClass();
}
if ($definition->hasListBuilderClass()) {
$vars['list_builders'][$type] = $definition->getListBuilderClass();
}
}
// Some classes does not have leading slash.
\array_walk_recursive($vars, static function (string &$class): void {
if ($class[0] != '\\') {
$class = '\\' . $class;
}
});
$this->addFile('.phpstorm.meta.php', 'phpstorm.meta.php');
}
/**
* Setter for Drupal context (for testing).
*/
public function setDrupalContext(DrupalContext $drupal_context): void {
$this->drupalContext = $drupal_context;
}
}

View File

@ -0,0 +1,41 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Plugin;
use DrupalCodeGenerator\Application;
/**
* Implements plugin:action command.
*/
final class Action extends PluginGenerator {
protected string $name = 'plugin:action';
protected string $description = 'Generates action plugin';
protected string $alias = 'action';
protected string $pluginLabelDefault = 'Update node title';
protected string $templatePath = Application::TEMPLATE_PATH . '/plugin/action';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['category'] = $this->ask('Action category', 'Custom');
$vars['configurable'] = $this->confirm('Make the action configurable?', FALSE);
$this->addFile('src/Plugin/Action/{class}.php', 'action');
if ($vars['configurable']) {
$this->addSchemaFile()->template('schema');
}
}
/**
* {@inheritdoc}
*/
protected function askPluginLabelQuestion(): ?string {
return $this->ask('Action label', $this->pluginLabelDefault, '::validateRequired');
}
}

View File

@ -0,0 +1,45 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Plugin;
use DrupalCodeGenerator\Application;
/**
* Implements plugin:block command.
*/
final class Block extends PluginGenerator {
protected string $name = 'plugin:block';
protected string $description = 'Generates block plugin';
protected string $alias = 'block';
protected string $pluginClassSuffix = 'Block';
protected string $templatePath = Application::TEMPLATE_PATH . '/plugin/block';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['category'] = $this->ask('Block category', 'Custom');
$vars['configurable'] = $this->confirm('Make the block configurable?', FALSE);
$this->collectServices($vars, FALSE);
$vars['access'] = $this->confirm('Create access callback?', FALSE);
$this->addFile('src/Plugin/Block/{class}.php', 'block');
if ($vars['configurable']) {
$this->addSchemaFile()->template('schema');
}
}
/**
* {@inheritdoc}
*/
protected function askPluginLabelQuestion(): ?string {
return $this->ask('Block admin label', 'Example', '::validateRequired');
}
}

View File

@ -0,0 +1,40 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Plugin;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Utils;
/**
* Implements plugin:ckeditor command.
*/
final class CKEditor extends PluginGenerator {
protected string $name = 'plugin:ckeditor';
protected string $description = 'Generates CKEditor plugin';
protected string $alias = 'ckeditor';
protected string $label = 'CKEditor';
protected string $templatePath = Application::TEMPLATE_PATH . '/plugin/ckeditor';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$unprefixed_plugin_id = \preg_replace('/^' . $vars['machine_name'] . '_/', '', $vars['plugin_id']);
// Convert plugin ID to hyphen case.
$vars['short_plugin_id'] = \str_replace('_', '-', $unprefixed_plugin_id);
$vars['command_name'] = Utils::camelize($unprefixed_plugin_id, FALSE);
$this->addFile('src/Plugin/CKEditorPlugin/{class}.php', 'ckeditor');
$this->addFile('js/plugins/{short_plugin_id}/plugin.js', 'plugin');
$this->addFile('js/plugins/{short_plugin_id}/dialogs/{short_plugin_id}.js', 'dialog');
$this->addFile('js/plugins/{short_plugin_id}/icons/{short_plugin_id}.png')
->content(\file_get_contents(Application::TEMPLATE_PATH . '/plugin/ckeditor/icon.png'))
->appendIfExists();
}
}

View File

@ -0,0 +1,26 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Plugin;
use DrupalCodeGenerator\Application;
/**
* Implements plugin:condition command.
*/
final class Condition extends PluginGenerator {
protected string $name = 'plugin:condition';
protected string $description = 'Generates condition plugin';
protected string $alias = 'condition';
protected string $templatePath = Application::TEMPLATE_PATH . '/plugin/condition';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$this->addFile('src/Plugin/Condition/{class}.php', 'condition');
$this->addSchemaFile()->template('schema');
}
}

View File

@ -0,0 +1,63 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Plugin;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Utils;
/**
* Implements plugin:constraint command.
*/
final class Constraint extends PluginGenerator {
protected string $name = 'plugin:constraint';
protected string $description = 'Generates constraint plugin';
protected string $alias = 'constraint';
protected string $templatePath = Application::TEMPLATE_PATH . '/plugin/constraint';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$input_types = [
'entity' => 'Entity',
'item_list' => 'Item list',
'item' => 'Item',
'raw_value' => 'Raw value',
];
$vars['input_type'] = $this->choice('Type of data to validate', $input_types, 'Item list');
$this->addFile('src/Plugin/Validation/Constraint/{class}.php')
->template('constraint');
$this->addFile('src/Plugin/Validation/Constraint/{class}Validator.php')
->template('validator');
}
/**
* {@inheritdoc}
*/
protected function askPluginIdQuestion(): string {
// Unlike other plugin types. Constraint IDs use camel case.
$default_plugin_id = '{name|camelize}{plugin_label|camelize}';
$plugin_id_validator = static function ($value) {
if (!\preg_match('/^[a-z][a-z0-9_]*[a-z0-9]$/i', $value)) {
throw new \UnexpectedValueException('The value is not correct constraint ID.');
}
return $value;
};
return $this->ask('Constraint ID', $default_plugin_id, $plugin_id_validator);
}
/**
* {@inheritdoc}
*/
protected function askPluginClassQuestion(array $vars): string {
$unprefixed_plugin_id = \preg_replace('/^' . Utils::camelize($vars['machine_name']) . '/', '', $vars['plugin_id']);
$default_class = Utils::camelize($unprefixed_plugin_id) . 'Constraint';
return $this->ask('Plugin class', $default_class);
}
}

View File

@ -0,0 +1,80 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Plugin;
use DrupalCodeGenerator\Application;
use Symfony\Component\Console\Question\Question;
/**
* Implements plugin:entity-reference-selection command.
*/
final class EntityReferenceSelection extends PluginGenerator {
protected string $name = 'plugin:entity-reference-selection';
protected string $description = 'Generates entity reference selection plugin';
protected string $alias = 'entity-reference-selection';
protected string $templatePath = Application::TEMPLATE_PATH . '/plugin/entity-reference-selection';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['configurable'] = $this->confirm('Provide additional plugin configuration?', FALSE);
$vars['base_class_full'] = self::baseClasses()[$vars['entity_type']] ??
'Drupal\Core\Entity\Plugin\EntityReferenceSelection\DefaultSelection';
$vars['base_class'] = \explode('EntityReferenceSelection\\', $vars['base_class_full'])[1];
$this->addFile('src/Plugin/EntityReferenceSelection/{class}.php')
->template('entity-reference-selection');
$this->addSchemaFile()->template('schema');
}
/**
* {@inheritdoc}
*/
protected function collectDefault(array &$vars): void {
$vars['name'] = $this->askName();
$vars['machine_name'] = $this->askMachineName($vars);
$entity_type_question = new Question('Entity type that can be referenced by this plugin', 'node');
$entity_type_question->setValidator([self::class, 'validateRequiredMachineName']);
$entity_type_question->setAutocompleterValues(\array_keys(self::baseClasses()));
$vars['entity_type'] = $this->io->askQuestion($entity_type_question);
$vars['plugin_label'] = $this->askPluginLabelQuestion();
$vars['plugin_id'] = $this->askPluginIdQuestion();
$vars['class'] = $this->askPluginClassQuestion($vars);
}
/**
* Asks plugin label question.
*/
protected function askPluginLabelQuestion(): ?string {
return $this->ask('Plugin label', 'Advanced {entity_type} selection', '::validateRequired');
}
/**
* Asks plugin class question.
*/
protected function askPluginClassQuestion(array $vars): string {
return $this->ask('Plugin class', '{entity_type|camelize}Selection');
}
/**
* Base classes for the plugin.
*/
private static function baseClasses(): array {
return [
'comment' => 'Drupal\comment\Plugin\EntityReferenceSelection\CommentSelection',
'file' => 'Drupal\file\Plugin\EntityReferenceSelection\FileSelection',
'node' => 'Drupal\node\Plugin\EntityReferenceSelection\NodeSelection',
'taxonomy_term' => 'Drupal\taxonomy\Plugin\EntityReferenceSelection\TermSelection',
'user' => 'Drupal\user\Plugin\EntityReferenceSelection\UserSelection',
];
}
}

View File

@ -0,0 +1,32 @@
<?php declare(strict_types=1);
namespace DrupalCodeGenerator\Command\Plugin\Field;
use DrupalCodeGenerator\Application;
use DrupalCodeGenerator\Command\Plugin\PluginGenerator;
/**
* Implements plugin:field:formatter command.
*/
final class Formatter extends PluginGenerator {
protected string $name = 'plugin:field:formatter';
protected string $description = 'Generates field formatter plugin';
protected string $alias = 'field-formatter';
protected string $templatePath = Application::TEMPLATE_PATH . '/plugin/field/formatter';
protected string $pluginClassSuffix = 'Formatter';
/**
* {@inheritdoc}
*/
protected function generate(array &$vars): void {
$this->collectDefault($vars);
$vars['configurable'] = $this->confirm('Make the formatter configurable?', FALSE);
$this->addFile('src/Plugin/Field/FieldFormatter/{class}.php', 'formatter');
if ($vars['configurable']) {
$this->addSchemaFile()->template('schema');
}
}
}

Some files were not shown because too many files have changed in this diff Show More