Drupal 維護

Drupal 組態設定

由於 Drupal 組態可不少,先列出所有組態名稱,再猜測可能是那一個。
drush config-get
命令執行時輸入數字,可顯示該組態:
 Choose a configuration:
  [0  ] Cancel
  [1  ] antibot.settings
  [2  ] automated_cron.settings
  [3  ] block.block.bannerspace
...
顯示站台資訊組態
drush config:get system.site
langcode: en
uuid: d01d9a08-4a68-87bb-a98f-f21552ec5f2e
name: 'Vicsys Systems'
mail:
slogan: ''
page:
  403: ''
  404: ''
  front: /home.html
...
顯示站台 uuid,在組態的結構中主鍵為 uuid
drush config:get system.site uuid
顯示站台主頁,在組態的結構中是在 page 內的 front,主鍵值為 page.front
drush config:get system.site page.front
改變站台名稱
drush config-set system.site name 'Vicsys Systems' -y

修改 Performance 中的 Aggregate CSS filesAggregate JavaScript files (<Site>/zh-hant/admin/config/development/performance)。

先列出組態的結構
drush config-get system.performance
cache:
  page:
    max_age: 600
    use_internal: false
css:
  preprocess: false
  gzip: false
fast_404:
  ...
js:
  preprocess: false
  gzip: false
...
得知主鍵值為 css.preprocessjs.preprocess,另外設定值為 Boolean,需以 01 來設定。
drush config:set system.performance css.preprocess 1 -y
drush config:set system.performance js.preprocess 1 -y

Drupal 組態匯出及匯入

匯出全部的組態檔案,使用 drush config-export (cex)config-export 可用縮寫 cex
mkdir /var/www/files/config/all -p
drush config-export --destination=/var/www/files/config/all

匯入時可能只需要其中一些配置,而 drush config:import (cim) 會匯入所有配置,這不是我們想要的,匯入時採用 --partial 參數可允許從來源目錄中匯入部分配置。

假設要匯入站台名稱,那麼搜尋站台名稱,得到檔名:/var/www/files/config/all/system.site.yml

建立新資料夾並且只複製預計要匯入的組態檔案 (system.site.yml)
mkdir /var/www/files/config/site -p
cp  /var/www/files/config/all/system.site.yml /var/www/files/config/site/system.site.yml
從該資料夾匯入所有組態檔案
drush config:import --partial --source=/var/www/files/config/site --preview=diff (1)
1 --preview=diff 可得知組態檔案跟站台配置有那些不同。

匯入單一檔案時,可執行 Drupal console (drupal) 的 config:import:single (cis) 來匯入,其組態是由匯入檔名來決定;如 system.site.yml 是匯入至組態 system.site

匯入單一檔案 (組態)
drupal config:import:single --directory=/var/www/files/config/all --file=system.site.yml (1)
drupal cis --file=/var/www/files/config/all/system.site.yml (1)
1 上面兩個指令是相同的。
Drupal 9 會出現下列錯誤,不過檔案會正確匯入。
ArgumentCountError: Too few arguments to function Drupal\Core\Config\ConfigImporter::__construct(), 9

以 Drupal console 匯出單一配置時,依據命令中的參數說明,實測運作方式不如預期!
--directory 無作用,不指定參數時為詢問。

drupal config:export:single -h

Usage:
 config:export:single [options]
 ces

Options:
     --name[=NAME]            Configuration name. (multiple values allowed)
     --directory[=DIRECTORY]  Define the export directory to save the configuration output.
     --module[=MODULE]        The Module name.
     --include-dependencies   Export dependencies of the configuration as well.
     --optional               Export config as an optional YAML configuration in your module
     --remove-uuid            If set, the configuration will be exported without uuid key.
     --remove-config-hash     If set, the configuration will be exported without the default site hash key.
匯出 system.performance,把全部的參數填入 (除了 --include-dependencies)
drupal config:export:single --name=system.performance --directory=dummy --module=system --optional --remove-uuid --remove-config-hash

找不到有 dummy 的資料夾,--directory 並無作用。

--optional 會固定輸出至 core/modules/system/config/optional
 Configuration(s) exported successfully
- core/modules/system/config/optional/system.performance.yml
去掉 --optional 則會訊問:
drupal config:export:single --name=system.performance --module=system --remove-uuid --remove-config-hash
 Export config in module as an optional configuration (yes/no) [yes]:
 > no

 Configuration(s) exported successfully
- core/modules/system/config/install/system.performance.yml (1)
1 optional 則輸出至 core/modules/system/config/install

上述的 system.performance.yml 內容都是 YAML 格式,跟 drush config-export 匯出的檔案大致相同,檔案內容缺少 default_config_hashuuid

缺少 default_config_hash 還是可以正確匯入:
drupal config:import:single --file=core/modules/system/config/optional/system.performance.yml
 [OK] Configuration(s) "system.performance", has been imported successfully.
查詢匯入後的結果
drush config-get system.performance

cache:
  page:
    max_age: 600
    use_internal: false
css:
  preprocess: false
  gzip: false
...
image.settings 匯出,其中的 --module=system 不需要更改。
printf '%s\n' no no | drupal config:export:single --name=image.settings --module=system --optional
 Do you want to remove the uuid from this export? (yes/no) [yes]:
 >
 Do you want to remove the default site hash from this export? (yes/no) [yes]:
 >
 Configuration(s) exported successfully
- core/modules/system/config/optional/image.settings.yml
將具有 default_config_hashimage.settings.yml 複製成 system.performance.yml,再匯入,會發生什麼?
cp core/modules/system/config/optional/image.settings.yml system.performance.yml

drupal config:import:single --file=system.performance.yml
[OK] Configuration(s) "system.performance", has been imported successfully.
查詢匯入後的結果
drush config-get system.performance

_core:
  default_config_hash: k-yDFHbqNfpe-Srg4sdCSqaosCl2D8uwyEY5esF8gEw
preview_image: core/modules/image/sample.png
allow_insecure_derivatives: false
suppress_itok_output: false

結果是 system.performance 是依據 system.performance.yml 檔案內容來變更,並不依據 default_config_hash


system.site 的組態比較特殊,system.site.yml 檔案內必須要 uuid,而且必須跟網站的 uuid 相同。否則會出現下列錯誤:

[ERROR] An error occurred while trying to write the config file: "commands.config.import.messages.import-fail
         Site UUID in source storage does not match the target storage."

解決方式是把 UUID 調整一致才能匯入。

可將組態檔中存在的 uuid 覆蓋當前站台的 uuid。

使用以下命令取得 UUID
drush config-get system.site uuid
改變 UUID 可以用
drush config-set system.site uuid 'd01d9a08-4a68-87bb-a98f-f21552ec5f2e' -y

Configuration Split

匯出模組組態或翻譯,採用 Configuration Split 的原因在於時能選取某些模組或項目,不像 drush config-export 只能全部匯出。

安裝 Configuration Split
modi config_split
建立一個組態設定檔
<site>/admin/config/development/configuration/config-split
Configuration Split setting

+Add Configuration Split setting


Add configuration split setting
Label

csbak

Folder

/var/www/files/config/split

COMPLETE SPLIT

選取要備份的模組。 (採用這個比較容易理解)

Configuration items

選取要備份的模組項目。

# 找出命令是什麼
drush | grep config-split
# config-split:
#  config-split:export (csex) Export only split configuration to a directory.
#  config-split:import (csim) Import only config from a split.

mkdir /var/www/files/config/split -p

# 匯出
drush csex csbak -y

# 匯入
drush csim csbak -y

建置測試環境

建置測試(備份)環境問題分析與解決
  • 建置環境需要測試,Drupal 安裝組件或更新時有時會出狀況。
    drupal 在測試前,先 clone-site from drupal to newsite,有問題回復 clone-site from newsite to drupal。

  • 刪除 newsite 則執行 drop-site newsite。

  • Apache alias 命名
    測試站台命名:try1 ~ try9
    範例站台命名:demo1 ~ demo9
    備份站台命名:bak1 ~ bak9
    Apache trySites.conf

    Alias /try1 /var/www/try1
    ...
    Alias /try9 /var/www/try9
    
    Alias /demo1 /var/www/bak1
    ...
    Alias /demo9 /var/www/bak9
    
    Alias /bak1 /var/www/demo1
    ...
    Alias /bak9 /var/www/demo9
    
    # 開放 www
    <Directory /var/www>
      Options FollowSymLinks
      AllowOverride All
      Require ip 192.168.1.1/255.255.255.0
    </Directory>

複製站台

複製站台命令格式
clone-site [OPTION] [<remote>:]<source> [<destination>]

options:
  -y	Never prompt
複製站台 drupal 至 try1 (複製至本機的 /var/www/drupal 至 /var/www/try1)
clone-site drupal try1
同步遠端站台至本地站台 (192.168.1.1 站台 druapl 複製至本機的 /var/www/druapl)
clone-site 192.168.1.1:druapl
MySQL 不可小於遠端站台的版本,否則會出現下列錯誤

ERROR 1118 (42000) at line 259: The size of BLOB/TEXT data inserted in one transaction is greater than 10% of redo log size. Increase the redo log size using innodb_log_file_size.

clone-site
bind "set disable-completion on"
bash -c "cat > /var/www/bin/clone-site" << "EOF2"
#!/bin/bash

usage="Usage: ${0##*/} [OPTION] [<remote>:]<source> [<destination>]

Clone site

options:
  -y		Never prompt"

Prompt=1
POSITIONAL=()

while (( "$#" )); do
  case "$1" in
    -h|--h*) echo "$usage"; exit 1;;
    -y|--y) Prompt=0; shift;;
    *)
    POSITIONAL+=("$1") # save it in an array for later
    shift;;
  esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

if [[ $1 = *:* ]]; then
  remoteHost=${1%%:*}
  remoteHost=${remoteHost,,}
  sourceSite=${1##*:}

  if [[ -z $sourceSite ]]; then
    echo "$usage"
    exit 1
  fi

  if [[ -z $2 ]]; then
    localSite=$sourceSite
  else
    localSite=$2
  fi

  sourceLocation=$remoteHost:$sourceSite

  # Will continue when resolveip fails
  if [[ $remoteHost = ${HOSTNAME,,} ]] || [[ $HOSTNAME = $(resolveip -s $remoteHost) ]]; then
    echo "Error: The remote host is the current host!" >&2
    exit 1
  fi
else
  if [[ -z $1 ]] || [[ -z $2 ]]; then
    echo "$usage"
    exit 1
  fi

  if [[ $1 = $2 ]]; then
    echo "Error: The source destination is the same!" >&2
    exit 1
  fi

  sourceSite=$1
  localSite=$2
  sourceLocation=$sourceSite
fi

if [[ $Prompt -eq 1 ]]; then
  read -p "clone-site $sourceLocation to $localSite? [Y/n] " yn
  yn=${yn:-y}

  if [[ $yn != Y ]] && [[ $yn != y ]]; then
    exit 1
  fi
fi

if [[ -z $remoteHost ]]; then
  sudo sh -c "chown -R www-data:www-data /var/www/$localSite; chmod -Rf 774 /var/www/$localSite" 2> /dev/null
fi

# Exit immediately on error
set -e

echo rsync $sourceLocation to $localSite
if [[ -n $remoteHost ]]; then
  sudo rsync -az --info=progress2 root@$remoteHost:/var/www/$sourceSite/ \
  --delete --chmod=775 --chown=www-data:www-data /var/www/$localSite

  echo Export soruce database $sourceLocation
  ssh root@$remoteHost "mysqldump -uroot -p$mysqlpw --host=localhost --port=3306 $sourceSite" > /var/www/$localSite/_srcdb.sql
else
  sudo rsync -a --chown=www-data:www-data --chmod=775 --info=progress2 --delete /var/www/$sourceSite/ /var/www/$localSite
  echo Export soruce database $sourceLocation
  mysqldump -uroot -p$mysqlpw --host=localhost --port=3306 $sourceSite > /var/www/$localSite/_srcdb.sql
fi

NC='\033[0m' # No Color
YELLOW='\033[1;33m'

echo -e "${YELLOW}***Now you can maintain the original site.***${NC}"

echo Create database $localSite
mysql -uroot -p$mysqlpw --host=localhost --port=3306 --protocol=tcp -n<<EOF
drop database if exists $localSite;
create database $localSite;
grant all privileges on $localSite.* to '$localSite'@'localhost' identified by '$webdbpw';
use mysql;
update user set plugin='mysql_native_password' where user='$localSite';
flush privileges;
EOF

echo Import table $localSite
mysql -u$localSite -p$webdbpw --host=localhost --port=3306 $localSite < /var/www/$localSite/_srcdb.sql
echo rm /var/www/$localSite/_srcdb.sql

if [[ $sourceSite != $localSite ]]; then
  echo -e "${YELLOW}"
  if [ -f "/var/www/$localSite/sites/default/settings.php" ]; then
    echo change settings.php
    src="\n *\$databases *\[ *'default' *\] *\[ *'default' *\] *= *array *(\n *'database' *=> *'[0-9a-zA-Z]\+' *, *\n *'username' *=> *'[0-9a-zA-Z]\+' *, *"
    trs="\n\$databases\['default'\]\['default'\] = array (\n  'database' => '$localSite',\n  'username' => '$localSite',"
    sed -z -i "s/$src/$trs/" /var/www/$localSite/sites/default/settings.php
    sudo chown www-data:www-data /var/www/$localSite/sites/default/settings.php
    dbProfile=$(sed -n "/\$databases\['default'\]\['default'\] = array (/ {N;/'database' => '$localSite'/!q;N;/'username' => '$localSite'/!q;N;p}" /var/www/$localSite/sites/default/settings.php)
    if [[ -z $dbProfile ]]; then
      echo "*** Can not find database parameters! ***"
      echo $(grep "^ *\$databases\[ *'default' *\] *\[ *'default' *\] *= *array *(" -A3 /var/www/$localSite/sites/default/settings.php)
    else
      echo $dbProfile
      cd /var/www/$localSite
      drush cr
    fi
  else
    echo "*** Can not find /var/www/$localSite/sites/default/settings.php! ***"
  fi
fi
EOF2
bind "set disable-completion off"
sudo chmod +x /var/www/bin/clone-site

刪除站台

drop-site
bind "set disable-completion on"
bash -c "cat > /var/www/bin/drop-site" << "EOF2"
#!/bin/bash

tab='	'
nl='
'
IFS=" $tab$nl"

usage="Usage: ${0##*/} [OPTION] site

Drop site

options:
  -y		Never prompt"

Prompt=1
POSITIONAL=()

while (( "$#" )); do
  case "$1" in
    -h|--h*) echo "$usage"; exit 1;;
    -y|--y) Prompt=0; shift;;
    *)
    POSITIONAL+=("$1") # save it in an array for later
    shift;;
  esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

if [ "$1" = "" ]; then
  echo "$usage"
  exit 1
fi

if [ $Prompt -eq 1 ]; then
  read -p "drop-site $1? [Y/n] " yn
  yn=${yn:-y}
  if [ "$yn" != "Y" ] && [ "$yn" != "y" ]; then
    exit 1
  fi
fi

echo rm $1
sudo rm /var/www/$1 -r

echo Drop database $1
mysql -uroot -p$mysqlpw --host=localhost --port=3306 --protocol=tcp -N<<EOF
drop database if exists $1;
drop user if exists '$1'@'localhost';
flush privileges;
EOF
EOF2
bind "set disable-completion off"
sudo chmod +x /var/www/bin/drop-site

站台重建

站台更新失敗,或者站台在更新 (含核心) 模組之後,可能會發現某個模組的運作不如預期,高度懷疑是因為模組出錯。

若模組的運作不如預期,先執行 drush cr 再次檢查避免誤判。
重建站台
  • 建立新站台,安裝跟原站台一樣模組及版本,並啟用跟原站台一樣模組。
    執行 modc-site 比對站台模組。註:該指令碼亦可比對測試站台安裝了那些新模組。

  • 測試新站台的運作是否合乎預期。

  • 將原始資料 以 clone-db 匯入至新新站台。

以 diff 比對檔案
site=try1
refsite=drupal

diff /var/www/$site /var/www/$refsite \
  -qr --exclude="*.yml" --exclude="*.po" \
  | sort > _diff.txt (1)

grep -v -E  "files/js|files/css|files/color|default/files: config|files/languages: |default/settings.php" \
  _diff.txt (2)

grep ".php" _diff.txt (3)
1 先建立 _diff.txt
2 比對檔案 (排除已知不同檔案)。
3 只比對 .php

比對站台模組

modc-site 比對站台模組
bind "set disable-completion on"
bash -c "cat > /var/www/bin/modc-site" << "EOF"
#!/bin/bash -e

tab='	'
nl='
'
IFS=" $tab$nl"

usage="Usage: ${0##*/} [OPTION] source target

Compare modules

options:
  -c, --cr   		Clear cache"

clearCache=0
POSITIONAL=()

while (( "$#" )); do
  case "$1" in
    -h|--h*) echo "$usage"; exit 1;;
    -c|--cr) clearCache=1; shift;;
    *)
    POSITIONAL+=("$1") # save it in an array for later
    shift;;
  esac
done

set -- "${POSITIONAL[@]}" # restore positional parameters

if [ -z "$1" ] || [ -z "$2" ]; then
  echo "$usage"
  exit 1
fi

if [ "$1" = "$2" ]; then
  echo "Error: The source target is the same!" >&2
  exit 1
fi

fileName=_Module.txt

cd /var/www/$1
if [ $clearCache -eq 1 ]; then
  drush cr
fi
drush pm-list --fields display_name,version,status --type=module > $fileName

cd /var/www/$2
if [ $clearCache -eq 1 ]; then
  drush cr
fi
drush pm-list --fields display_name,version,status --type=module > $fileName

diff -y <(sed 's/\([ -]\)*/\1/g' /var/www/$1/$fileName | fold -s -w80) \
<(sed 's/\([ -]\)*/\1/g' /var/www/$2/$fileName | fold -s -w80) -W 160 | grep '[<|>]'

rm /var/www/$1/$fileName
rm /var/www/$2/$fileName
EOF

bind "set disable-completion off"
sudo chmod +x /var/www/bin/modc-site

複製站台資料庫

clone-db
bind "set disable-completion on"
bash -c "cat > /var/www/bin/clone-db" << "EOF2"
#!/bin/bash -e

tab='	'
nl='
'
IFS=" $tab$nl"

usage="Usage: ${0##*/} [OPTION] source target

Clone database

options:
  -y		Never prompt"

Prompt=1
POSITIONAL=()

while (( "$#" )); do
  case "$1" in
    -h|--h*) echo "$usage"; exit 1;;
    -y|--y) Prompt=0; shift;;
    *)
    POSITIONAL+=("$1") # save it in an array for later
    shift;;
  esac
done
set -- "${POSITIONAL[@]}" # restore positional parameters

if [ "$1" = "" ] || [ "$2" = "" ]; then
  echo "$usage"
  exit 1
fi

if [ "$1" = "$2" ]; then
  echo "Error: The source target is the same!" >&2
  exit 1
fi

if [ $Prompt -eq 1 ]; then
  read -p "clone-db $1 to $2? [Y/n] " yn
  yn=${yn:-y}
  if [ "$yn" != "Y" ] && [ "$yn" != "y" ]; then
    exit 1
  fi
fi

echo mysqldump $1
mysqldump -uroot -p$mysqlpw --host=localhost --port=3306 $1 > /var/www/$2/_srcdb.sql

echo create database $2
mysql -uroot -p$mysqlpw --host=localhost --port=3306 --protocol=tcp -n<<EOF
drop database if exists $2;
create database $2;
grant all privileges on $2.* to '$2'@'localhost' identified by '$webdbpw';
use mysql;
update user set plugin='mysql_native_password' where user='$2';
flush privileges;
EOF

echo Import table $2
mysql -u$2 -p$webdbpw --host=localhost --port=3306 $2 < /var/www/$2/_srcdb.sql

rm /var/www/$2/_srcdb.sql

cd /var/www/$2
drush cr
EOF2
bind "set disable-completion off"

sudo chmod +x /var/www/bin/clone-db

Drupal 8 昇級至 Drupal 9

先在測試站台準備昇級(重要)
安裝 upgrade_status
clone-site drupal try9
composer require drupal/upgrade_status (1)
drush en upgrade_status
drush cr
1 安裝 Upgrade Status 可事先檢查相容情況。但實際的站台昇級又可能因為 upgrade_status 造成昇級失敗,upgrade_status 僅可用來檢查相容性,依據相容性調整原始站台,當然若不放心可 clone-site 原始站台至另一站台再調整。
進入 <site>/admin/reports/upgrade-status 檢查一下情況。

When using MariaDB, minimum version is 10.3.7 Alternatively, install the MariaDB 10.1 driver for Drupal 9 for now.

MariaDB 最低需求版本為 10.3.7 可昇級至 10.3.27 或者安裝 MariaDB 10.1 驅動程式。

昇級至 Drupal 9
# 先昇級 composer.json
composer require drupal/core-recommended:^9.0.0 drupal/core-composer-scaffold:^9.0.0 \
drupal/core-project-message:^9.0.0 --update-with-dependencies --no-update
# ./composer.json has been updated

# If you have drupal/core-dev installed.
# composer require drupal/core-dev:^9.0.0 --dev --update-with-dependencies --no-update

composer update
若需要安裝 MariaDB 10.1 驅動程式
安裝驅動程式
composer require drupal/mysql56
在 sites/default/settings.php 加入下列
$databases['default']['default']['namespace'] = 'Drupal\\Driver\\Database\\mysql';
更新資料庫
drush updatedb (1)
drush cr
chwww .
1 注意如果出現紅底訊息 [error] * The database server version x.x.x is less than the minimum required version x.x.x. 表示有誤,千萬別繼續執行。
Reference

Upgrading from Drupal 8 to Drupal 9 (or higher) | Upgrading Drupal | Drupal guide on Drupal.org
Upgrading Drupal 8 to Drupal 9: The real-world experience | Touch4IT

Drupal core 9.0.9/9.1.0 有下列錯誤
drupal site:mode dev
PHP Fatal error:  Uncaught Error: Call to undefined method Drupal\Core\DrupalKernel::prepareLegacyRequest() in /var/www/try9/vendor/drupal/console/src/Utils/DrupalApi.php:266
在 vendor/drupal/console/src/Utils/DrupalApi.php:266 刪除該行原始碼
$kernel->prepareLegacyRequest($request);

Drupal patch

當發現某個模組的運作不如預期,除了 Google 之外,可進入 https://www.drupal.org/project/<Machine name>,如 ds (Display Suite) 為 https://www.drupal.org/project/ds 按下網頁中的 Bug report (Issues for Display Suite | Drupal.org )。

#9 2887778-9.patch
#3 ds-2887778-3-D8.patch (1)
1 有點奇怪,啟用 NoScript 有 ds-2887778-3-D8.patch。
Needs review
Project: Display Suite
Version: 8.x-4.x-dev (1)
1 8.x-4.x-dev 版本中尚未修正,安裝 8.x-4.x-dev 也無作用。
查看一下安裝的版本
composer show | grep drupal/ds
# drupal/ds 3.9.0
下載 2887778-9.patch
mkdir -p /var/www/files/patch (1)
wget -P /var/www/files/patch "https://www.drupal.org/files/issues/2018-08-27/2887778-9.patch"
1 建立 patch 目錄保留 patch 檔案及被 patch 的檔案,當下次更新時,若不正確可試著修改。
cat 2887778-9.patch
---
 ds.module | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/ds.module b/ds.module (1)
index 8dc1de5d..0d03e4c6 100644
--- a/ds.module
+++ b/ds.module
...
1 得知是 patch ds.module
先備份 ds.module
cp modules/contrib/ds/ds.module /var/www/files/patch/ds.module.src (1)
1 重要!重新 patch 時,有原始檔案可隨時回復或測試。
git apply 2887778-9.patch
git apply /var/www/files/patch/2887778-9.patch --directory modules/contrib/ds
# warning: modules/contrib/ds/ds.module has type 100755, expected 100644
# 上述有警告, 實際上已執行 patch。

# 修改模組應重建快取
drush cr

# 回復 patch
# git apply /var/www/files/patch/2887778-9.patch --directory modules/contrib/ds --reverse
測試結果不正確, 以 ds-2887778-3-D8.patch 來 patch
# 回復 patch
git apply /var/www/files/patch/2887778-9.patch --directory modules/contrib/ds --reverse

wget -P /var/www/files/patch "https://www.drupal.org/files/issues/ds-2887778-3-D8.patch"

git apply /var/www/files/patch/ds-2887778-3-D8.patch --directory modules/contrib/ds
# error: patch fragment without header at line 5: @@ -14,13 +14,13 @@

# git 出錯改用 patch
patch -i /var/www/files/patch/ds-2887778-3-D8.patch modules/contrib/ds/ds.module
# patching file modules/contrib/ds/ds.module
# patch 結果無誤

drush cr
#重建快取後測試,不正確。

# 執行 2887778-9.patch
patch -i /var/www/files/patch/2887778-9.patch modules/contrib/ds/ds.module
# patching file modules/contrib/ds/ds.module
# Hunk #1 succeeded at 635 (offset 2 lines).

drush cr
#重建快取後測試,結果正確。

# 保留本次修正的結果
cp modules/contrib/ds/ds.module /var/www/files/patch/ds.module.patched
怎麼 git apply 是錯誤的?再測一次
cp /var/www/files/patch/ds.module.src modules/contrib/ds/ds.module
git apply /var/www/files/patch/2887778-9.patch --directory modules/contrib/ds
drush cr (1)

cp /var/www/files/patch/ds.module.src modules/contrib/ds/ds.module
patch -i /var/www/files/patch/ds-2887778-3-D8.patch modules/contrib/ds/ds.module (2)
patch -i /var/www/files/patch/2887778-9.patch modules/contrib/ds/ds.module
drush cr (3)
1 重建快取後測試,確定不正確
2 再次 patch 兩個檔案
3 重建快取後測試,確定正確,要 patch 兩個檔案。

Admin toolbar z-index

由於網頁的 z-index 必須為 1031, 而 Druapl 的 Admin toolbar 管理介面為 502,網頁會覆蓋 Admin toolbar,將 Admin toolbar 的 z-index 修改該為 1032。

core/modules/toolbar/css/toolbar.module.css 中的 z-index 跟下列有關
/* Layer the bar just above the trays and above contextual link triggers. */
.toolbar-oriented .toolbar-bar {
  z-index: 502; (1)
}
修改如下
/* Layer the bar just above the trays and above contextual link triggers. */
/*** MY-PATCH 502 to 1032 ***/
.toolbar-oriented .toolbar-bar {
  z-index: 1032;
}
# 先備份原始檔案
cp core/modules/toolbar/css/toolbar.module.css /var/www/files/patch/toolbar.module.css.src

nano core/modules/toolbar/css/toolbar.module.css
# 502 修改為  1032

# 產生 patch
diff -u /var/www/files/patch/toolbar.module.css.src \
core/modules/toolbar/css/toolbar.module.css \
> /var/www/files/patch/toolbar.module.css.patch

# 備份本次修改結果
cp core/modules/toolbar/css/toolbar.module.css /var/www/files/patch/toolbar.module.css.patched

# 下次更新時, 再次執行
patch -i /var/www/files/patch/toolbar.module.css.patch core/modules/toolbar/css/toolbar.module.css

# 檢查 .z-index
grep -A 4 'Layer the bar just above the trays and above contextual link triggers' \
core/modules/toolbar/css/toolbar.module.css

重導網頁 (Redirect page)

網頁狀態碼
401 Authorization failed  授權失敗。使用者輸入的帳號密碼未得到授權。
403 Forbidden             存取控制機制拒絕使用者的請求,不可讀取該檔案。
404 File not found        被要求的網頁不存在於這個服務器上,找不到檔案。
500 Internal Server Error 服務器內部錯誤;可能是網站伺服器出錯。
501 Not Implemented       服務器不瞭解資料傳遞的方式。
503 Service Unavailable   服務器暫停服務

禁止進入管理頁面

由 Durpal 正規將 403 轉成 404,網頁雖然可顯示成 404 的樣式,但狀態碼還是 403,只能由 apache 重導至 php,再由 php 改變狀態碼為 404。

drupal.conf
<VirtualHost *:80>
  DocumentRoot /var/www/drupal
  RewriteEngine on

  ErrorDocument 404 /html/404.php (1)
  ErrorDocument 403 /html/404.php (1)

  RewriteCond expr "%{REMOTE_ADDR} -ipmatch '192.168.1.0/24'" (2)
  RewriteRule .? - [L] (2)
  RewriteCond %{REQUEST_URI} (/[uU][sS][eE][rR]|/[aA][dD][mM][iI][nN]) (3)
  RewriteRule .? - [L,R=404] (3)

  <FilesMatch "(install|update).php"> (4)
    Order deny,allow
    deny from all
    Allow from 192.168.1.0/24 (5)
  </FilesMatch>

  <Directory /var/www/drupal>
    AllowOverride None
    Include /var/www/drupal/.htaccess
  </Directory>
</VirtualHost>
1 404 及 403 網頁為 /var/www/drupal/html/404.php
2 允許區域網路進入 user 或 admin。
3 若不為區域網路則重導至 404。
4 若不為區域網路,禁止進入 install.php update.php …​,apache 會重導至 403,不過有語言 url 就破功了。
5 若為區域網路,可進入「禁止」網頁,不過,如果採用 composer drush 實際上並不需要由網頁來處理。
區域網路語法
Require ip 192.168.1.1
Require ip 192.168.1.0/255.255.255.0
Require ip 192.168.1.0/24

由於 drupal 並不允許在其他路徑中執行自定的 php,需修改根目錄中的 .htaccess 檔案。

解決無法在 Drupal 目錄中執行自定的 php
# For security reasons, deny access to other PHP files on public sites.
# Note: The following URI conditions are not anchored at the start (^),
# because Drupal may be located in a subdirectory. To further improve
# security, you can replace '!/' with '!^/'.
# Allow access to PHP files in /core (like authorize.php or install.php):
RewriteCond %{REQUEST_URI} !/core/[^/]*\.php$
# Allow access to test-specific PHP files:
RewriteCond %{REQUEST_URI} !/core/modules/system/tests/https?.php
# Allow access to Statistics module's custom front controller.
# Copy and adapt this rule to directly execute PHP files in contributed or
# custom modules or to run another PHP application in the same directory.
RewriteCond %{REQUEST_URI} !/core/modules/statistics/statistics.php$

### MY-PATCH Allow for my_custom_directory folder to run php ###  (1)
RewriteCond %{REQUEST_URI} !/html/[^/]*\.php$ (1)
1 加入允許 php 規則。
產生 patch 以便 druapl 更新後修正
# 先備份原始檔案
cp .htaccess /var/www/files/patch/.htaccess.src

nano .htaccess
# 加入允許 php 規則。

# 產生 patch
diff -u /var/www/files/patch/.htaccess.src \
.htaccess \
> /var/www/files/patch/.htaccess.patch

# 備份本次修改結果
cp .htaccess /var/www/files/patch/.htaccess.patched

# 下次 drupal 更新時, 再次執行
patch -i /var/www/files/patch/.htaccess.patch .htaccess

# 檢查結果
grep -A 5 '# custom modules or to run another PHP application in the same directory.' \
.htaccess
404.php
<?php
  http_response_code(404); (1)
  $lant='en';
  $url=$_SERVER['REQUEST_URI'];
  if (preg_match('~^/zh-hant(?:/|$)~i', $url)) {
      $lang='zh-TW';
  } elseif (preg_match('~^/zh-hans(?:/|$)~i', $url)) {
      $lang='zh-CN';
  } else {
      $lang=strtok($_SERVER['HTTP_ACCEPT_LANGUAGE'], ',');
  }
  switch ($lang) {
      case 'zh-TW':
          include '404_tw.html'; (2)
          break;
      case 'zh-CN':
          include '404_cn.html';
          break;
      default:
          include '404_en.html';
  }
1 一律回傳 404 狀態碼給瀏覽器,跟網頁一致 (403 也回傳 404)。
2 按 drupal.conf 中的 DocumentRoot 及 ErrorDocument 的路徑,/var/www/drupal/html/404_tw.html 為正體中文的 404 網頁。

那麼要寫 404 網頁?不用!只需產生網站的 404 網頁即可。

產生 404 網頁
wget --content-on-error <site>/zh-hant/notexists -O /var/www/drupal/html/404_tw.html
wget --content-on-error <site>/zh-hans/notexists -O /var/www/drupal/html/404_cn.html
wget --content-on-error <site>/en/notexists -O /var/www/drupal/html/404_en.html

維護頁面

當網站在「維護」時,不進實際網站頁面,其原因在於維護時會更新檔案及資料庫,若再進入網站頁面會造成不預期的結果,可能進入 install.php 的頁面,比較理想的方式是由 Apache 重導至維護頁面 (不進入實際網站)。

drupal503.conf
<VirtualHost *:80>
  DocumentRoot /var/www/files/site/html (1)
  RewriteEngine on
  RewriteCond %{REQUEST_URI} !=/favicon.ico (2)
  ErrorDocument 503 /503.php (3)
  RewriteCond %{REQUEST_URI} !/503.php$ [NC] (4)
  RewriteRule .* - [R=503,L] (5)
</VirtualHost>
1 維護站台的根目錄是在 /var/www/files/site/html
2 允許讀取 favicon.ico。如果 url 不為 favicon.ico,進行後續規則,另一說法是如果 url 為 favicon.ico,則直接讀取。
3 維護頁面採用 php 來實作具彈性。
4 如果 url 為 503.php,則直接讀取,503.php 要跟 ErrorDocument 一樣,不一樣會出現 Service Unavailable。
5 不為 503.php 則重導 503。
<?php
  $lant='en';
  $url=$_SERVER['REQUEST_URI'];
  if (preg_match('~^/zh-hant(?:/|$)~i', $url)) {
      $lang='zh-TW'; (1)
  } elseif (preg_match('~^/zh-hans(?:/|$)~i', $url)) {
      $lang='zh-CN'; (2)
  } else {
      $lang=strtok($_SERVER['HTTP_ACCEPT_LANGUAGE'], ','); (3)
  }

  switch ($lang) {
      case 'zh-TW':
          include '503_tw.html'; (4)
          break;
      case 'zh-CN':
          include '503_cn.html';
          break;
      default:
          include '503_en.html';
  }
1 網址為 <site>\zh-hant 則為 zh-TW。
2 網址為 <site>\zh-hans 則為 zh-CN。
3 其他情況,取瀏覽器的語言。
4 按 drupal503.conf 中的 DocumentRoot,則 /var/www/files/site/html/503_tw.html 為正體中文的 503 網頁。

那麼要寫 503 網頁?不用!只需產生網站的 503 網頁即可。

產生 503 網頁
# 設定網站維護
drush state:set system.maintenance_mode 1 --input-format=integer
drush cr

wget --content-on-error <site>/zh-hant/ -O /var/www/files/site/html/503_tw.html
wget --content-on-error <site>/zh-hans/ -O /var/www/files/site/html/503_cn.html
wget --content-on-error <site>/en/ -O /var/www/files/site/html/503_en.html

# 回復正常
drush state:set system.maintenance_mode 0 --input-format=integer
drush cr

不過,由於「維護網頁」並未由 Drupal 提供 css,需要將 css 改成由網頁提供。

維護網頁範例
<!DOCTYPE html>
<html lang="zh-hant" dir="ltr"
  prefix="content: http://purl.org/rss/1.0/modules/content/  dc: http://purl.org/dc/terms/  foaf: http://xmlns.com/foaf/0.1/  og: http://ogp.me/ns#  rdfs: http://www.w3.org/2000/01/rdf-schema#  schema: http://schema.org/  sioc: http://rdfs.org/sioc/ns#  sioct: http://rdfs.org/sioc/types#  skos: http://www.w3.org/2004/02/skos/core#  xsd: http://www.w3.org/2001/XMLSchema# ">

<head>
  <meta charset="utf-8" />
  <meta name="MobileOptimized" content="width" />
  <meta name="HandheldFriendly" content="true" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <meta http-equiv="x-ua-compatible" content="ie=edge" />
  <link rel="shortcut icon" href="/favicon.ico" type="image/vnd.microsoft.icon" />

  <title>網站正在進行維護 | 我的網站</title>

  <style type="text/css">
    body {
      font-family: helvetica, arial, sans-serif;
      font-size: 16px;
    }

    #maintenance-page {
      margin-top: 40px;
      border: 0px;
      width: 800px;
      margin-left: auto;
      margin-right: auto;
    }
  </style>
</head>

<body>
  <div id="maintenance-page">
    <strong>
      <span style="color: #9d408d">我的網站</span>
    </strong>
    <section>
      <h1>網站正在進行維護</h1>
      我的網站 正在維護中,很快就會回來,請稍候。
    </section>
  </div>
</body>

</html>
切換成維護
sudo a2dissite drupal
sudo a2ensite drupal503
sudo service apache2 reload
切換成正常
sudo a2dissite drupal503
sudo a2ensite drupal
sudo service apache2 reload

Xdebug

ubuntu 18 安裝 Xdebug
sudo apt install php-xdebug (1)
find /etc/php -iname '*xdebug.ini' | sort (2)
find /usr/lib/php -iname 'xdebug.so' (3)
1 各種不同的系統及版本可參閱 Xdebug: Documentation > Installation
2 安裝後 php 各版本內有 xdebug.ini 不過,還是要自行設定,可參閱 Xdebug: Documentation > All settings
3 有不少以日期為目錄的版本,但 php 7.3 只有 /usr/lib/php/20180731/xdebug.so (版本 3.0.2) 能正常執行。
設置 PHP 7.3 xdebug.ini
sudo bash -c 'cat > /etc/php/7.3/mods-available/xdebug.ini' << 'EOF'
zend_extension=/usr/lib/php/20180731/xdebug.so
xdebug.mode=debug (1)
xdebug.discover_client_host=1 (2)
# xdebug.client_host=192.168.0.1
xdebug.client_port=9000
xdebug.start_with_request=yes
EOF
1 xdebug.mode=debug 啟用偵錯,xdebug.mode=off 取消偵錯。
2 如果 PHP / Xdebug 在同一子網中的另一台主機上執行,並且您的瀏覽器與 IDE 在同一主機上執行,可將 xdebug.discover_client_host 設置為 1,或者設置 xdebug.client_host=192.168.0.1 指定用戶電腦 IP。
建立測試檔及啟動 php
cd ~ (1)
echo "<?php phpinfo();" > phpinfo.php

cat > debug.php  << 'EOF'
<?php
$myvar1 = 1;
$myvar2 = 2;
echo $myvar1.' '.$myvar2;
EOF

# sudo service nginx stop
# sudo service apache2 stop

sudo php -S 0.0.0.0:3000 (2)
1 範例為家目錄。
2 執行 PHP 內建的 Web server。

進入 http://<YourSite>:3000/phpinfo.php 網頁,搜尋 xdebug 如果無誤會看到下列圖例。

phpinfo

phpxdebug

Visual Studio Code 設置 Xdebug 偵錯

VSCODE 先安裝 PHP Debug
在 Windows 的「檔案總管」執行「以 Code 開啟」網站資料夾,該目錄即為 Samba 的分享資料夾。
VSCODE 會將該資料夾作為 工作區 (workspace)。

VSCODE 設置「以 Code 開啟」

若沒有「以 Code 開啟」則先移除 VSCODE 再重新安裝 (重新安裝時所有的 Extensions 會保留),在安裝畫面會有下列選項:

  • 將「以 Code 開啟」動作加入 Windows 檔案總管目錄的操作功能表中

在 VSCOD 的選單 (menu) 按下 Run  Add Configuration…​ 出現 Select Environment 選 (輸入) PHP,VSCOD 會在 工作區 中建立一個子目錄 .vscode 及在子目錄中建立檔案 launch.json

launch.json 範例
{
    // Use IntelliSense to learn about possible attributes.
    // Hover to view descriptions of existing attributes.
    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Listen for XDebug",
            "type": "php",
            "request": "launch",
            "port": 9000,
            "pathMappings": { (1)
                "/home/ubuntu": "${workspaceRoot}",
              }
        },
        {
            "name": "Launch currently open script",
            "type": "php",
            "request": "launch",
            "program": "${file}",
            "cwd": "${fileDirname}",
            "port": 9000
        }
    ]
}
1 加入 pathMappings 的設定,其他為原始內容,pathMappings 的格式為 主機路徑 對應 本機路徑
主機路徑 很明確就是 Linux 的檔案路徑如 /var/wwww/drupal、/home/ubuntu,
本機路徑 採用變數 ${workspaceRoot} 這表示需將偵錯的網站資料夾「以 Code 開啟」。
瀏覽網頁時會在執行 PHP Web server 的 CLI 會看到下列訊息

Xdebug: [Step Debug] Could not connect to debugging client. Tried: <YourSite>:9000 (from REMOTE_ADDR HTTP header), localhost:9000 (fallback through xdebug.client_host/xdebug.client_port) :-(
這表示客戶端沒有啟動偵錯。

VSCODE 執行偵錯,在選單 (menu) 按下 Run  Start Debugging
再次瀏覽網頁 http://<YourSite>:3000/debug.php,應該就沒有上述訊息了,這也表示 Xdebug 已建置完成了。

如果中斷點無效,可能是因為 pathMappings 沒設置正確,重設置後,在 VSCODE 的選單按下 Run  Restart Debugging 即可,不需要去重啟 PHP Web server。

將 Druapl .module 檔案加入 PHP 關連

由於 VSCODE 並不知道 Druapl 的 .module 也是 PHP 檔案,在 VSCODE 中開啟 .module 檔案後按下 Ctrl+Shift+p 後輸入 change language mode,再選 Configure File Association for '.module'…​ 最後輸入 PHP

VSCODE 會將設定檔儲存在 %USERPROFILE%\AppData\Roaming\Code\User\settings.json,可將 settings.json 移至工作區內的 .vscode 子目錄。

停用 Xdebug

看看 Xdebug 配置了那些檔案

find /etc/php -iname '*xdebug.ini' | grep 7.3 | sort
/etc/php/7.3/apache2/conf.d/20-xdebug.ini (1)
/etc/php/7.3/cli/conf.d/20-xdebug.ini (1)
/etc/php/7.3/fpm/conf.d/20-xdebug.ini (1)
/etc/php/7.3/mods-available/xdebug.ini
1 這些檔案是是以「軟連結」連接至 mods-available/xdebug.ini 可以刪除它,或者用 phpdismod
停用所有 Xdebug
sudo phpdismod xdebug
sudo service php7.3-fpm restart
重啟 Web 服務器
sudo netstat -tlpn
# 確定已關閉 PHP Web server 再重啟 Web 服務器
# sudo service nginx start
# sudo service apache2 start
啟用 Xdebug
sudo phpenmod xdebug (1)
sudo ln -s /etc/php/7.3/mods-available/xdebug.ini /etc/php/7.3/cli/conf.d/20-xdebug.ini (2)
sudo service php7.3-fpm restart
1 啟用所有 Xdebug
2 或者您可以只在 CLI 啟動 Xdebug,則加入「軟連結」。