├── .gitignore ├── 2019-10-21_10h02_50.jpg ├── 2019-10-21_10h03_47.jpg ├── 2019-10-23_13h23_19.jpg ├── README.md ├── ansible └── readme.md ├── apache.PNG ├── composer └── install_composer.sh ├── drupal-blt.PNG ├── drupal-code-style.jpg ├── drupal-enable.jpg ├── druplicon-small.png ├── enable_wsl.PNG ├── field.md ├── hook_node_update_info.png ├── images ├── hook_node_update_info.png └── readme.md ├── mysql └── readme.md ├── phpcs-1.jpg ├── phpcs-2.jpg ├── phpcs-3.jpg ├── phpcs4.jpg ├── phpstorm └── readme.md ├── style-checkbox.png ├── ubuntu-logo.png ├── ubuntu_install.PNG ├── vagrant └── readme.md ├── virtulamin └── readme.md ├── windows-logo.png ├── winver.PNG ├── wsl2.PNG ├── wsl_kernel.PNG ├── wsl_lamp_drupal_setup.md ├── xdebug-config-0.jpg ├── xdebug-config-2.jpg ├── xdebug-config-3.jpg ├── xdebug-verify.jpg ├── xdebug_wsl_1.jpg ├── xdebug_wsl_2.jpg ├── xdebug_wsl_3.jpg └── xdebugconfig-1.jpg /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore configuration files that may contain sensitive information. 2 | sites/*/*settings*.php 3 | sites/example.sites.php 4 | 5 | # Ignore paths that contain generated content. 6 | files/ 7 | sites/*/files 8 | sites/*/private 9 | sites/*/translations 10 | 11 | # Ignore default text files 12 | robots.txt 13 | /CHANGELOG.txt 14 | /COPYRIGHT.txt 15 | /INSTALL*.txt 16 | /LICENSE.txt 17 | /MAINTAINERS.txt 18 | /UPGRADE.txt 19 | /README.txt 20 | sites/README.txt 21 | sites/all/libraries/README.txt 22 | sites/all/modules/README.txt 23 | sites/all/themes/README.txt 24 | 25 | # Ignore everything but the "sites" folder ( for non core developer ) 26 | .htaccess 27 | web.config 28 | authorize.php 29 | cron.php 30 | index.php 31 | install.php 32 | update.php 33 | xmlrpc.php 34 | /includes 35 | /misc 36 | /modules 37 | /profiles 38 | /scripts 39 | /themes 40 | -------------------------------------------------------------------------------- /2019-10-21_10h02_50.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flashvnn/drupal-snippets/8990f5ac79e402c86e3c98f8b9e273ddb531a5fa/2019-10-21_10h02_50.jpg -------------------------------------------------------------------------------- /2019-10-21_10h03_47.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flashvnn/drupal-snippets/8990f5ac79e402c86e3c98f8b9e273ddb531a5fa/2019-10-21_10h03_47.jpg -------------------------------------------------------------------------------- /2019-10-23_13h23_19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flashvnn/drupal-snippets/8990f5ac79e402c86e3c98f8b9e273ddb531a5fa/2019-10-23_13h23_19.jpg -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Alter the Drupal token with ECA Token Alter 2 | 3 | https://www.drupal.org/project/token_eca_alter 4 | 5 | ``` 6 | [token-eca:{existing token}#{key}#{other_key}] 7 | 8 | [token-eca:node:title#truncate] 9 | [token-eca:node:title#truncate{length:100}] 10 | 11 | ``` 12 | 13 | ## Install php 8.3 with extensions for Drupal 14 | 15 | ``` 16 | sudo apt install php8.3 php8.3-cli php8.3-common php8.3-fpm php8.3-curl php8.3-gd php8.3-mbstring php8.3-xml php8.3-zip php8.3-soap php8.3-intl php8.3-bcmath php8.3-opcache php8.3-mysql php8.3-pgsql php8.3-sqlite3 php8.3-imap php8.3-readline -y 17 | ``` 18 | 19 | ## Config default PHP version when installed multiple PHP versions 20 | 21 | ``` 22 | sudo apt update && sudo apt upgrade -y 23 | sudo apt install software-properties-common -y 24 | sudo add-apt-repository ppa:ondrej/php -y 25 | sudo apt update 26 | 27 | ## Install php 8.3 above 28 | 29 | sudo update-alternatives --config php 30 | ## Choose default php version 31 | 32 | 33 | ``` 34 | 35 | ## DNS, nameserver not working on WSL2 36 | 37 | ``` 38 | Update content for /etc/wsl.conf 39 | 40 | [network] 41 | generateResolvConf = false 42 | 43 | Update content for /etc/resolv.conf 44 | 45 | nameserver 8.8.8.8 46 | nameserver 1.1.1.1 47 | 48 | make it not change 49 | 50 | sudo chattr +i /etc/resolv.conf 51 | 52 | ``` 53 | 54 | ## Drush get all field name of content type 55 | 56 | ``` 57 | $ vendor/bin/drush field-info node article 58 | +-------------+----------+-------------------+-------------+ 59 | | Field name | Required | Field type | Cardinality | 60 | +-------------+----------+-------------------+-------------+ 61 | | body | | text_with_summary | 1 | 62 | | comment | | comment | 1 | 63 | | field_image | | image | 1 | 64 | | field_tags | | entity_reference | -1 | 65 | +-------------+----------+-------------------+-------------+ 66 | 67 | ``` 68 | 69 | 70 | 71 | 72 | ## Fix Git newline issue on MAC 73 | 74 | ```bash 75 | git config --global core.autocrlf input 76 | 77 | # On the project has git newline issue 78 | git rm -rf --cached . 79 | git reset --hard HEAD 80 | 81 | ``` 82 | 83 | ## Using Drush command with VBO 84 | 85 | ``` 86 | # drush vbo-execute some_view some_action --args=arg1/arg2 --batch-size=50 --display-id=page_1 --user-id=1 87 | # publish unpublished content 88 | 89 | drush vbo-exec view_unpublish_content entity:publish_action:node --display-id=page_1 --user-id=1 90 | ``` 91 | 92 | ## Export CSV with Vietnamese language 93 | 94 | ```php 95 | getActiveSheet(); 102 | 103 | // Thêm dữ liệu vào bảng tính 104 | $sheet->setCellValue('A1', 'Họ tên'); 105 | $sheet->setCellValue('A2', 'Nguyễn Văn A'); 106 | 107 | // Tạo writer cho CSV 108 | $writer = new Csv($spreadsheet); 109 | $writer->setDelimiter(','); // Ký tự phân cách 110 | $writer->setEnclosure('"'); // Ký tự bao quanh 111 | $writer->setLineEnding("\r\n"); // Ký tự kết thúc dòng 112 | $writer->setUseBOM(true); // Sử dụng BOM cho UTF-8, Important 113 | 114 | // Xuất file CSV 115 | header('Content-Type: text/csv; charset=utf-8'); // Important 116 | header('Content-Disposition: attachment; filename="data.csv"'); 117 | $writer->save('php://output'); 118 | ``` 119 | 120 | 121 | ## Composer ignore custom repositories 122 | 123 | ``` 124 | composer config repositories.packagist false 125 | ``` 126 | 127 | ## Check node in preview mode with twig 128 | 129 | ```twig 130 | {% if node.in_preview %} 131 |

PREVIEW MODE

132 | {% endif %} 133 | 134 | ``` 135 | 136 | ## Twig get file/image url of media 137 | 138 | ``` 139 | node-->[field_of_node]-->entity-->[field_of_media]-->entity-->fileuri 140 | 141 | {% set fileuri = node.field_banner.entity.field_media_image.entity.fileuri %} 142 | {{ file_url(fileuri) }} 143 | 144 | ``` 145 | 146 | ## Twig get link url of reference content 147 | 148 | ``` 149 | content-->[field_of_node]-->[0][field_of_reference][0]['#url'] 150 | 151 | {% set link_url = content.field_banner[0]['field_banner_link'][0]['#url']|render %} 152 | 153 | ``` 154 | 155 | ## Drupal twig get custom field module data 156 | 157 | https://www.drupal.org/project/custom_field 158 | 159 | ``` 160 | {% set attributes_data = [] %} 161 | {% for k, v in content.field_attributes[0]['#items'] %} 162 | {% set attributes_data = attributes_data|merge({(v['#name'] ~ ''):v['#value']}) %} 163 | {% endfor %} 164 | 165 | # Render the id 166 | {{ attributes_data['id'] }} 167 | ``` 168 | 169 | ## Bash script install composer for linux user 170 | 171 | ``` 172 | curl -s https://raw.githubusercontent.com/flashvnn/drupal-snippets/master/composer/install_composer.sh | bash 173 | source ~/.bashrc 174 | ``` 175 | 176 | 177 | ```bash 178 | #!/bin/bash 179 | 180 | # Define variables 181 | COMPOSER_DIR="$HOME/.local/bin" 182 | COMPOSER_PATH="$COMPOSER_DIR/composer" 183 | INSTALLER_PATH="composer-setup.php" 184 | 185 | # Create the Composer directory if it doesn't exist 186 | mkdir -p $COMPOSER_DIR 187 | 188 | # Download the Composer installer script 189 | php -r "copy('https://getcomposer.org/installer', '$INSTALLER_PATH');" 190 | 191 | # Verify the installer SHA-384 to ensure it is not corrupted 192 | HASH="$(wget -q -O - https://composer.github.io/installer.sig)" 193 | php -r "if (hash_file('SHA384', '$INSTALLER_PATH') === '$HASH') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('$INSTALLER_PATH'); exit(1); } echo PHP_EOL;" 194 | 195 | # Run the installer script 196 | php $INSTALLER_PATH --install-dir=$COMPOSER_DIR --filename=composer 197 | 198 | # Remove the installer script 199 | php -r "unlink('$INSTALLER_PATH');" 200 | 201 | # Ensure the install directory is in the PATH 202 | if ! grep -q "$COMPOSER_DIR" <<< "$PATH"; then 203 | echo "export PATH=\"$COMPOSER_DIR:\$PATH\"" >> ~/.bashrc 204 | fi 205 | 206 | echo "Composer installed successfully in $COMPOSER_DIR" 207 | 208 | ``` 209 | 210 | ## Drupal update module and install custom entity 211 | 212 | ``` 213 | /** 214 | * Install custom entity my_custom_entity. 215 | */ 216 | function my_module_update_10002(&$sandbox): void { 217 | // Check if the table exists first. If not, then create the entity. 218 | if (!\Drupal::database()->schema()->tableExists('my_custom_entity')) { 219 | \Drupal::entityTypeManager()->clearCachedDefinitions(); 220 | \Drupal::entityDefinitionUpdateManager() 221 | ->installEntityType(\Drupal::entityTypeManager()->getDefinition('my_custom_entity')); 222 | } 223 | } 224 | 225 | ``` 226 | 227 | 228 | ## Apache proxy pass to nodejs application with websocket 229 | 230 | ``` 231 | ProxyPass / http://localhost:8080/ 232 | ProxyPassReverse / http://localhost:8080/ 233 | 234 | RewriteCond %{HTTP:Upgrade} websocket [NC] 235 | RewriteCond %{HTTP:Connection} upgrade [NC] 236 | RewriteRule ^/?(.*) "ws://localhost:8080/$1" [P,L] 237 | 238 | ``` 239 | 240 | ## Install nvm and nodejs per user on linux without root 241 | 242 | ``` 243 | curl https://raw.githubusercontent.com/creationix/nvm/master/install.sh | bash 244 | source ~/.bashrc 245 | nvm install node 246 | ``` 247 | 248 | ## Composer don't copy to libraries folder 249 | 250 | Config npm and bower 251 | 252 | https://drupaljournal.com/gist/install-npm-and-bower-packages-using-composer-drupal 253 | 254 | ``` 255 | composer require --no-update oomphinc/composer-installers-extender:^2 256 | composer update oomphinc/composer-installers-extender npm-asset/jquery-validation 257 | ``` 258 | 259 | ## What is .well-known/traffic-advice directory? 260 | 261 | ``` 262 | https://webmasters.stackexchange.com/questions/138033/what-is-well-known-traffic-advice-directory 263 | 264 | # Create file .well-known/traffic-advice 265 | # Insert content 266 | 267 | [{ 268 | "user_agent": "prefetch-proxy", 269 | "google_prefetch_proxy_eap": { 270 | "fraction": 1.0 271 | } 272 | }] 273 | 274 | # Config htaccess for Apache 275 | 276 | RewriteRule ^\.well-known/traffic-advice$ - [T=application/trafficadvice+json,END] 277 | 278 | # Config Nginx 279 | 280 | # Private Prefetch Proxy 281 | # https://developer.chrome.com/blog/private-prefetch-proxy/ 282 | location /.well-known/traffic-advice { 283 | types { } default_type "application/trafficadvice+json; charset=utf-8"; 284 | } 285 | 286 | ``` 287 | 288 | 289 | ## Drupal drush sql dump/import with compress 290 | 291 | ``` 292 | Drush sql dump and compress 293 | 294 | vendor/bin/drush sql-dump --gzip --result-file=./../db.sql 295 | 296 | Import compress file 297 | 298 | vendor/bin/drush sqlq --file=db.sql.gz 299 | 300 | ``` 301 | 302 | 303 | ## Only allow access with Cloudflare proxy with .htaccess, prevent ddos 304 | ![2024-03-20__16-41-48](https://github.com/flashvnn/drupal-snippets/assets/1784547/6771f9b1-f265-4fa1-99f1-b17c4e7bb747) 305 | 306 | 307 | Add to .htaccess or apache .conf 308 | 309 | ``` 310 | RewriteCond %{HTTP:X-MY-TOKEN} ^$ 311 | RewriteRule ^ - [F,L] 312 | ``` 313 | 314 | Change X-MY-TOKEN with your private name. 315 | 316 | ## Javascript clear client data, cahce, cookie, session 317 | 318 | ```js 319 | sessionStorage.clear() 320 | 321 | localStorage.clear() 322 | 323 | caches.keys().then(keys => { 324 | keys.forEach(key => caches.delete(key)) 325 | }) 326 | 327 | indexedDB.databases().then(dbs => { 328 | dbs.forEach(db => indexedDB.deleteDatabase(db.name)) 329 | }) 330 | 331 | document.cookie = document.cookie.split(';').reduce((newCookie1, keyVal) => { 332 | var pair = keyVal.trim().split('=') 333 | if (pair[0]) { 334 | if (pair[0] !== 'path' && pair[0] !== 'expires') { 335 | newCookie1 += pair[0] + '=;' 336 | } 337 | } 338 | return newCookie1 339 | }, 'expires=Thu, 01 Jan 1970 00:00:00 UTC; path:/;') 340 | ``` 341 | 342 | ## PHP quick convert string to number 343 | 344 | ```php 345 | 346 | $str_num = '3.14'; 347 | $number = is_number($str_num) ? $str_num + 0 : $str_num; 348 | 349 | ``` 350 | 351 | ## Workflow label from state key 352 | 353 | ``` 354 | $workflow_state = $entity->get('moderation_state')->value; 355 | /** @var \Drupal\content_moderation\ModerationInformation $moderation_information_service */ 356 | $moderation_information_service = \Drupal::service('content_moderation.moderation_information'); 357 | $label = ''; 358 | if ($workflow = $moderation_information_service->getWorkflowForEntity($entity)) { 359 | $label = $workflow->getTypePlugin()->getState($workflow_state)?->label(); 360 | } 361 | ``` 362 | 363 | ## Clear cache with drush sql query 364 | 365 | ```bash 366 | vendor/bin/drush sql:query "TRUNCATE cachetags;TRUNCATE cache_bootstrap;TRUNCATE cache_config;TRUNCATE cache_container;TRUNCATE cache_data;TRUNCATE cache_default;TRUNCATE cache_discovery;TRUNCATE cache_dynamic_page_cache;TRUNCATE cache_entity;TRUNCATE cache_menu;TRUNCATE cache_page;TRUNCATE cache_render;TRUNCATE cache_toolbar;" 367 | 368 | ``` 369 | 370 | ## Drupal Views alter Filter to CAST() string as Float 371 | 372 | ```php 373 | //https://stackoverflow.com/questions/33417033/drupal-views-alter-filter-to-cast-string-as-float 374 | 375 | function custom_helpers_views_query_alter(&$view, &$query) { 376 | if ( $view->name == 'mietangebote' ) { 377 | 378 | // Miete 379 | if(!empty($view->exposed_raw_input['field_baserent_value'])){ 380 | 381 | foreach($query->where[1]['conditions'] as $key => $condition) { 382 | if ( $condition['field'] == 'field_data_field_baserent.field_baserent_value' ) { 383 | $query->where[1]['conditions'][$key]['operator'] = 'formula'; // important. 384 | $query->where[1]['conditions'][$key]['value'] = array(':val' => (double)$query->where[1]['conditions'][$key]['value']); 385 | $query->where[1]['conditions'][$key]['field'] = 'CAST(' . $query->where[1]['conditions'][$key]['field'] . ' AS UNSIGNED) >= :val'; 386 | //dpm($view->query->where[1]['conditions'][$key]); 387 | break; 388 | } 389 | } 390 | 391 | // For sorting as well 392 | foreach($query->orderby as $key => $condition) { 393 | if ( $condition['field'] == 'field_data_field_count_field_count_value' ) { 394 | $query->orderby[$key]['field'] = 'CAST(' . $query->orderby[$key]['field'] . ' AS UNSIGNED)'; 395 | break; 396 | } 397 | } 398 | } 399 | } 400 | } 401 | ``` 402 | 403 | ## Config settings.local.php for local development with pantheon.io 404 | 405 | ```php 406 | 'drupal10', 410 | 'username' => 'drupal10', 411 | 'password' => 'drupal10', 412 | 'host' => 'database', 413 | 'port' => '3306', 414 | 'driver' => 'mysql', 415 | 'prefix' => '', 416 | 'collation' => 'utf8mb4_general_ci', 417 | ]; 418 | 419 | $settings['hash_salt'] = '$HASH_SALT'; 420 | $settings["file_temp_path"] = 'sites/default/files/tmp'; 421 | $settings['config_sync_directory'] = '../config'; 422 | $settings['trusted_host_patterns'][] = '.*'; 423 | 424 | $config['system.logging']['error_level'] = 'verbose'; 425 | 426 | /** 427 | * Disable CSS and JS aggregation. 428 | */ 429 | $config['system.performance']['css']['preprocess'] = FALSE; 430 | $config['system.performance']['js']['preprocess'] = FALSE; 431 | 432 | ``` 433 | 434 | ## Move cursor to begin and end of line on MacOS 435 | 436 | ``` 437 | Control + A for goto the beginning and Control + E to goto the end 438 | ``` 439 | 440 | ## Custom Entity list builder with view 441 | ``` 442 | https://git.drupalcode.org/project/entity_extra/blob/HEAD/src/Controller/ViewsEntityListBuilder.php 443 | ``` 444 | 445 | 446 | ## Remove unwanted tab on node view/edit/delete page 447 | 448 | ``` 449 | /** 450 | * Hide view tab from node edit. 451 | */ 452 | function MYMODULE_menu_local_tasks_alter(&$data, $route_name, \Drupal\Core\Cache\RefinableCacheableDependencyInterface &$cacheability) { 453 | if($route_name == 'entity.node.edit_form'){ 454 | unset($data['tabs'][0]['entity.node.canonical']); 455 | } 456 | } 457 | 458 | ``` 459 | 460 | ## Find hook node update information in database 461 | 462 | ``` 463 | table: key_value 464 | column: collection 465 | find for: 'system.schema' 466 | 467 | ``` 468 | 469 | ![hook_node_update](images/hook_node_update_info.png) 470 | 471 | ## Bash script fix error with \r 472 | 473 | ``` 474 | sed -i 's/\r$//' filename 475 | ``` 476 | 477 | 478 | ## Linux find command examples 479 | 480 | ```bash 481 | 482 | #Find by name 483 | find . -name “*.jpg” 484 | ... 485 | ./Pictures/iPhoto Library/Data/2006/Roll 20/00697_bluewaters_1440x900.jpg 486 | ./Pictures/iPhoto Library/Data/2006/Roll 20/00705_cloudyday_1440x900.jpg 487 | ./Pictures/iPhoto Library/Data/2006/Roll 20/00710_fragile_1600x1200.jpg 488 | ./Pictures/iPhoto Library/Data/2006/Roll 20/00713_coolemoticon_1440x900.jpg 489 | ./Pictures/iPhoto Library/Data/2006/Roll 20/00714_cloudyday_1440x900.jpg 490 | ... 491 | 492 | #Find by User 493 | find . -user daniel 494 | ... 495 | ./Music/iTunes/iTunes Music/Tool/Undertow/01 Intolerance.m4a 496 | ./Music/iTunes/iTunes Music/Tool/Undertow/02 Prison Sex.m4a 497 | ./Music/iTunes/iTunes Music/Tool/Undertow/03 Sober.m4a 498 | ... 499 | 500 | #Find only directories 501 | find . -type d 502 | ... 503 | ./Development/envelope 504 | ./Development/mhp 505 | ... 506 | 507 | #Find everything over a megabyte in size 508 | find ~/Movies/ -size +1024M 509 | 510 | ... 511 | /Movies/Comedy/Funny.mpg 512 | /Movies/Drama/Sad.avi 513 | ... 514 | 515 | #Show me what content owned by root have been modified within the last minute 516 | find /etc/ -user root -mtime 1 517 | 518 | ... 519 | /etc/passwd 520 | /etc/shadow 521 | ... 522 | 523 | #The checks you can use here are: 524 | #-atime: when the file was last accessed 525 | #-ctime: when the file’s permissions were last changed 526 | #-mtime: when the file’s data was last modified 527 | 528 | 529 | # find all files in my directory with open permissions 530 | find ~ -perm 777 531 | ... 532 | ~/testfile.txt 533 | ~/lab.rtf 534 | ... 535 | 536 | #Find all files on your system that are world writeable 537 | find / – perm -0002 538 | 539 | #Correct the permissions on your web directory 540 | find /your/webdir/ -type d -print0 | xargs -0 chmod 755;find /your/webdir -type f | xargs chmod 644 541 | 542 | #Find files that have been modified within the last month and copy them somewhere 543 | find /etc/ -mtime -30 | xargs -0 cp /a/path 544 | 545 | #Daniel’s files of type jpeg 546 | find . -user daniel -type f -name *.jpg 547 | 548 | ... 549 | ./Pictures/iPhoto Library/autumn_woods.jpg 550 | ./Pictures/iPhoto Library/blue_forest.jpg 551 | ./Pictures/iPhoto Library/brothers.jpg 552 | ... 553 | 554 | Daniel’s jpeg files without autumn in the name 555 | find . -user daniel -type f -name *.jpg ! -name autumn* 556 | 557 | ... 558 | ./Pictures/iPhoto Library/blue_forest.jpg 559 | ./Pictures/iPhoto Library/brothers.jpg 560 | ... 561 | 562 | #Root’s ruby files accessed in the last two minutes 563 | find /apps/ -user root -type f -amin -2 -name *.rb 564 | 565 | ... 566 | /apps/testing.rb 567 | /apps/runme.rb 568 | ... 569 | 570 | ``` 571 | 572 | ## install nodejs on linux without sudo 573 | ``` 574 | https://www.johnpapa.net/node-and-npm-without-sudo/ 575 | 576 | Install Node.js from https://nodejs.org/en/download/ 577 | 578 | Update to the latest version of npm npm install npm -g 579 | 580 | Make a new folder for the npm global packages mkdir ~/.npm-packages 581 | 582 | Tell npm where to find/store them npm config set prefix ~/.npm-packages 583 | 584 | Verify the install 585 | 586 | Make a folder for your npm packages and tell your computer about it. 587 | 588 | mkdir "${HOME}/.npm-packages" 589 | echo NPM_PACKAGES="${HOME}/.npm-packages" >> ${HOME}/.bashrc 590 | echo prefix=${HOME}/.npm-packages >> ${HOME}/.npmrc 591 | echo NODE_PATH=\"\$NPM_PACKAGES/lib/node_modules:\$NODE_PATH\" >> ${HOME}/.bashrc 592 | echo PATH=\"\$NPM_PACKAGES/bin:\$PATH\" >> ${HOME}/.bashrc 593 | echo source "~/.bashrc" >> ${HOME}/.bash_profile 594 | source ~/.bashrc 595 | 596 | ``` 597 | 598 | ## Intercepting JavaScript Fetch API requests and responses 599 | 600 | ``` 601 | https://blog.logrocket.com/intercepting-javascript-fetch-api-requests-responses/ 602 | ``` 603 | 604 | **Request interceptor**: 605 | 606 | ```js 607 | const { fetch: originalFetch } = window; 608 | window.fetch = async (...args) => { 609 | let [resource, config ] = args; 610 | 611 | // request interceptor starts 612 | resource = 'https://jsonplaceholder.typicode.com/todos/2'; 613 | // request interceptor ends 614 | 615 | const response = await originalFetch(resource, config); 616 | 617 | // response interceptor here 618 | return response; 619 | }; 620 | 621 | 622 | fetch('https://jsonplaceholder.typicode.com/todos/1') 623 | .then((response) => response.json()) 624 | .then((json) => console.log(json)); 625 | 626 | // log 627 | // { 628 | // "userId": 1, 629 | // "id": 2, 630 | // "title": "quis ut nam facilis et officia qui", 631 | // "completed": false 632 | // } 633 | ``` 634 | 635 | **Response interceptor:** 636 | 637 | ```js 638 | const { fetch: originalFetch } = window; 639 | window.fetch = async (...args) => { 640 | let [resource, config] = args; 641 | 642 | let response = await originalFetch(resource, config); 643 | 644 | // response interceptor 645 | const json = () => 646 | response 647 | .clone() 648 | .json() 649 | .then((data) => function(){ 650 | console.log('Intercepted:', data) 651 | return { ...data, title: `Intercepted: ${data.title}` } 652 | }); 653 | 654 | response.json = json; 655 | return response; 656 | }; 657 | 658 | fetch('https://jsonplaceholder.typicode.com/todos/1') 659 | .then((response) => response.json()) 660 | .then((json) => console.log(json)); 661 | 662 | // log 663 | // { 664 | // "userId": 1, 665 | // "id": 1, 666 | // "title": "Intercepted: delectus aut autem", 667 | // "completed": false 668 | // } 669 | ``` 670 | 671 | ## Drupal print pretty json output 672 | 673 | ```php 674 | 675 | $form['json_data'] = [ 676 | '#type' => 'item', 677 | '#title' => $this->t('JSON'), 678 | '#markup' => '

' . json_encode($json_object, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . '
', 679 | ]; 680 | 681 | ``` 682 | 683 | 684 | ## Fix Drupal/PHP/Composer patch not working on Mac OS 685 | 686 | ``` 687 | brew install gpatch 688 | ``` 689 | 690 | ## Docker: Add a restart policy to a container that was already created 691 | 692 | ``` 693 | docker update --restart=always 694 | ``` 695 | 696 | 697 | ## Drupal custom entity basefield 698 | 699 | ``` 700 | https://fivejars.com/blog/entity-basefielddefinitions-fields-examples-drupal-8 701 | ``` 702 | 703 | ### Drupal core has the following field types: 704 | 705 | + boolean 706 | + changed 707 | + created 708 | + decimal 709 | + email 710 | + entity_reference 711 | + float 712 | + integer 713 | + language 714 | + map 715 | + password 716 | + string 717 | + string_long 718 | + timestamp 719 | + uri 720 | + uuid 721 | + comment 722 | + datetime 723 | + file 724 | + image 725 | + link 726 | + list_float 727 | + list_integer 728 | + list_string 729 | + path 730 | + telephone 731 | + text 732 | + text_long 733 | + text_with_summary 734 | 735 | __entity_reference__ 736 | 737 | ```php 738 | 739 | $fields['article'] = BaseFieldDefinition::create('entity_reference') 740 | ->setLabel(t('Article')) 741 | ->setDescription(t('Article related to demo entity.')) 742 | ->setSetting('target_type', 'node') 743 | ->setSetting('handler', 'default:node') 744 | ->setSetting('handler_settings', [ 745 | 'target_bundles' => ['article' => 'article'], 746 | 'auto_create' => FALSE, 747 | ]) 748 | ->setRequired(TRUE) 749 | ->setTranslatable(FALSE) 750 | ->setDisplayOptions('view', [ 751 | 'label' => 'visible', 752 | 'type' => 'string', 753 | 'weight' => 2, 754 | ]) 755 | ->setDisplayOptions('form', [ 756 | 'type' => 'entity_reference_autocomplete', 757 | 'weight' => 2, 758 | 'settings' => [ 759 | 'match_operator' => 'CONTAINS', 760 | 'size' => '60', 761 | 'placeholder' => 'Enter here article title...', 762 | ], 763 | ]) 764 | ->setDisplayConfigurable('view', TRUE) 765 | ->setDisplayConfigurable('form', TRUE); 766 | ``` 767 | 768 | __string_long__ 769 | 770 | ```php 771 | $fields['notes'] = BaseFieldDefinition::create('string_long') 772 | ->setLabel(t('Notes')) 773 | ->setDescription(t('Example of string_long field.')) 774 | ->setDefaultValue('') 775 | ->setRequired(FALSE) 776 | ->setDisplayOptions('view', [ 777 | 'label' => 'visible', 778 | 'type' => 'basic_string', 779 | 'weight' => 5, 780 | ]) 781 | ->setDisplayOptions('form', [ 782 | 'type' => 'string_textarea', 783 | 'weight' => 5, 784 | 'settings' => ['rows' => 4], 785 | ]) 786 | ->setDisplayConfigurable('view', TRUE) 787 | ->setDisplayConfigurable('form', TRUE); 788 | ``` 789 | 790 | 791 | ## Add new views filter for exist field 792 | 793 | https://www.lilengine.co/articles/custom-views-filter-existing-daterange-field 794 | 795 | **myfilter.module** 796 | 797 | ```php 798 | t('Event status'), 808 | 'filter' => [ 809 | 'title' => t('Event status'), 810 | 'group' => t('Content'), 811 | 'help' => t('Show future/current/past events with a filter.'), 812 | // This matches the real field. 813 | 'field' => 'field_myevent_value', 814 | // This matches the plugin machine name. 815 | 'id' => 'myevent_filter', 816 | ], 817 | ]; 818 | 819 | } 820 | ``` 821 | 822 | This is the the filter plugin. It lives in your module at src/Plugin/views/filter/MyEventFilter.php. 823 | 824 | ```php 825 | [ 851 | 'title' => $this->t('The event is'), 852 | 'method' => 'opStateIs', 853 | 'short' => $this->t('Is'), 854 | 'values' => 1, 855 | ], 856 | ]; 857 | } 858 | 859 | /** 860 | * The form that is show (including the exposed form). 861 | */ 862 | protected function valueForm(&$form, FormStateInterface $form_state) { 863 | $form['value'] = [ 864 | '#tree' => TRUE, 865 | 'state' => [ 866 | '#type' => 'select', 867 | '#title' => $this->t('Event status'), 868 | '#options' => [ 869 | 'all' => $this->t('All'), 870 | 'current' => $this->t('Current'), 871 | 'past' => $this->t('Past'), 872 | 'future' => $this->t('Future'), 873 | ], 874 | '#default_value' => !empty($this->value['state']) ? $this->value['state'] : 'all', 875 | ] 876 | ]; 877 | } 878 | 879 | /** 880 | * Applying query filter. If you turn on views query debugging you should see 881 | * these clauses applied. If the filter is optional, and nothing is selected, this 882 | * code will never be called. 883 | */ 884 | public function query() { 885 | $this->ensureMyTable(); 886 | $start_field_name = "$this->tableAlias.$this->realField"; 887 | $end_field_name = substr($start_field_name, 0, -6) . '_end_value'; 888 | 889 | // Prepare sql clauses for each field. 890 | $date_start = $this->query->getDateFormat($this->query->getDateField($start_field_name, TRUE), 'Y-m-d H:i:s', FALSE); 891 | $date_end = $this->query->getDateFormat($this->query->getDateField($end_field_name, TRUE), 'Y-m-d H:i:s', FALSE); 892 | $date_now = $this->query->getDateFormat('FROM_UNIXTIME(***CURRENT_TIME***)', 'Y-m-d H:i:s', FALSE); 893 | 894 | switch ($this->value['state']) { 895 | case 'current': 896 | $this->query->addWhereExpression($this->options['group'], "$date_now BETWEEN $date_start AND $date_end"); 897 | break; 898 | 899 | case 'past': 900 | $this->query->addWhereExpression($this->options['group'], "$date_now > $date_end"); 901 | break; 902 | 903 | case 'future': 904 | $this->query->addWhereExpression($this->options['group'], "$date_now < $date_start"); 905 | break; 906 | } 907 | } 908 | 909 | /** 910 | * Admin summary makes it nice for editors. 911 | */ 912 | public function adminSummary() { 913 | 914 | if ($this->isAGroup()) { 915 | return $this->t('grouped'); 916 | } 917 | if (!empty($this->options['exposed'])) { 918 | return $this->t('exposed') . ', ' . $this->t('default state') . ': ' . $this->value['state']; 919 | } 920 | else { 921 | return $this->t('state') . ': ' . $this->value['state']; 922 | } 923 | } 924 | 925 | } 926 | ``` 927 | 928 | **Schema config** 929 | 930 | ```yml 931 | # Lives in the module at ./config/schema/myfilter.views.schema.yml 932 | 933 | # 'myevent_filter' should match your plugin name. 934 | views.filter_value.myevent_filter: 935 | type: mapping 936 | label: 'Event status' 937 | mapping: 938 | state: 939 | type: string 940 | label: 'Event status' 941 | ``` 942 | 943 | ## Drupal apply patch with composer.json 944 | 945 | Run command: 946 | 947 | ``` 948 | composer require cweagans/composer-patches 949 | ``` 950 | 951 | Add patch infomation 952 | 953 | ``` 954 | "extra": { 955 | "patches": { 956 | "drupal/core": { 957 | "Undocumented title variable in feed-icon.html.twig": "patches/3156260-11.patch" 958 | } 959 | } 960 | } 961 | ``` 962 | 963 | Run composer install 964 | 965 | ``` 966 | composer install 967 | ``` 968 | 969 | 970 | ## Drupal Select2 use ajax callback after select item 971 | 972 | ``` 973 | public function form(array $form, FormStateInterface $form_state) { 974 | $form['my_item'] = [ 975 | '#type' => 'item', 976 | '#markup' => 'Before Ajax', 977 | '#prefix' => '
', 978 | '#suffix' => '
', 979 | ]; 980 | 981 | $form['select2'] = [ 982 | '#type' => 'select2', 983 | '#title' => t('My select2 form element'), 984 | '#options' => ['foo', 'bar'], 985 | '#ajax' => [ 986 | 'callback' => [self::class, 'ajaxCallback'], 987 | 'wrapper' => 'ajax_item_wrapper', 988 | 'event' => 'select2:close', 989 | ], 990 | ]; 991 | } 992 | 993 | public static function ajaxCallback($form, FormStateInterface $form_state) { 994 | $form['my_item']['#markup'] = 'Selected item: ' . $form_state->getValue('select2'); 995 | return $form['my_item']; 996 | } 997 | 998 | ``` 999 | 1000 | ## Change default shell user's PHP version 1001 | 1002 | ``` 1003 | nano ~/.bash_profile 1004 | 1005 | # Add end of file this line with /usr/local/php82/bin is location of PHP8.2 1006 | export PATH=/usr/local/php82/bin:$PATH 1007 | 1008 | # Save file 1009 | 1010 | . ~/.bash_profile 1011 | 1012 | ``` 1013 | 1014 | 1015 | ## Fix openssl not working on Windows xampp 1016 | 1017 | ``` 1018 | Click on the START button 1019 | Click on CONTROL PANEL 1020 | Click on SYSTEM AND SECURITY 1021 | Click on SYSTEM 1022 | Click on ADVANCED SYSTEM SETTINGS 1023 | Click on ENVIRONMENT VARIABLES 1024 | Under "System Variables" click on "NEW" 1025 | Enter the "Variable name" OPENSSL_CONF 1026 | Enter the "Variable value". My is - C:\xampp\apache\conf\openssl.cnf 1027 | Click "OK" and close all the windows and RESTART your computer. 1028 | The OPENSSL should be correctly working. 1029 | ``` 1030 | 1031 | ## XDEBUG 3 1032 | ``` 1033 | [xdebug] 1034 | zend_extension="C:\xampp\php\ext\php_xdebug-3.2.1-8.0-vs16-x86_64.dll" 1035 | xdebug.mode=debug 1036 | xdebug.client_host=127.0.0.1 1037 | xdebug.client_port="9003" 1038 | ``` 1039 | 1040 | ## Change custom entity collection page title. 1041 | ``` 1042 | # update file CustomEntityHtmlRouteProvider 1043 | public function getRoutes(EntityTypeInterface $entity_type) { 1044 | $collection = parent::getRoutes($entity_type); 1045 | 1046 | $entity_type_id = $entity_type->id(); 1047 | 1048 | if ($settings_form_route = $this->getSettingsFormRoute($entity_type)) { 1049 | $collection->add("$entity_type_id.settings", $settings_form_route); 1050 | } 1051 | 1052 | if ($entity_collection_route = $collection->get('entity.' . $entity_type_id . '.collection')) { 1053 | $entity_collection_route->setDefault('_title', '@label'); 1054 | } 1055 | 1056 | return $collection; 1057 | } 1058 | 1059 | ``` 1060 | 1061 | ## Drupal.t translate in Vue js 1062 | 1063 | ``` 1064 | # Create Drupal.ts 1065 | export class Drupal { 1066 | static t(str: string) { 1067 | if ((window as any)?.Drupal) { 1068 | return (window as any)?.Drupal.t(str); 1069 | } 1070 | return str; 1071 | } 1072 | } 1073 | 1074 | #in App.vue or other .vue files: 1075 | 1076 | 1080 | 1081 | 1084 | 1085 | ``` 1086 | 1087 | ## Node delete confirm form alter 1088 | 1089 | ```php 1090 | /** 1091 | * Implements hook_form_BASE_FORM_ID_alter(). 1092 | */ 1093 | function mymodule_form_node_confirm_form_alter(&$form, FormStateInterface $form_state, $form_id) { 1094 | // Only need to alter the delete operation form. 1095 | if ($form_state->getFormObject()->getOperation() !== 'delete') { 1096 | return; 1097 | } 1098 | /** @var \Drupal\node\NodeInterface $node */ 1099 | $node = $form_state->getFormObject()->getEntity(); 1100 | if ($node->getType() === 'page') { 1101 | // action with node page. 1102 | } 1103 | } 1104 | ``` 1105 | 1106 | ## Vue 3 Eventbus with Drupal 1107 | 1108 | Install mitt module 1109 | 1110 | ```bash 1111 | npm i mitt 1112 | ``` 1113 | 1114 | Create lib/EventBus.ts 1115 | 1116 | ```ts 1117 | import mitt from "mitt"; 1118 | export default mitt() 1119 | ``` 1120 | 1121 | Update main.ts 1122 | ``` 1123 | import { createApp } from 'vue' 1124 | import App from './App.vue' 1125 | import EventBus from "./lib/EventBus" 1126 | 1127 | (window as any).EventBus = EventBus; 1128 | 1129 | let app = createApp(App) 1130 | app.mount('#app') 1131 | 1132 | ``` 1133 | 1134 | Emit event in Vue component 1135 | 1136 | ```vue 1137 |