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,则加入「软链接」。