Bug(バグ) #940
未完了プロフィール項目の日付やテキストの最小値を最大値より大きくして設定できてしまう
Mutsumi Imamura さんが14年以上前に追加. 12年以上前に更新.
0%
説明
現象¶
プロフィール項目の登録/編集画面にある、
フォームタイプのテキストと日付にある最小値/最大値の設定で、最小値>最大値という設定ができてしまう。エラー表示もされない。
再現方法¶
例:日付の場合- 「pc_backend.php/profile/edit」を開く
- プルダウンから「自分で入力する」を選択する
- フォームタイプで「日付」を選択する
- 最小値「2010/04/07」と最大値に「2010/04/05」と入力し、追加ボタンを押す
- 設定できてしまう。
最小値、最大値をstrotime()が解釈できる値でも同じように最小値>最大値の設定が出来ます。
再現バージョン¶
- OpenPNE3.5.x
- OpenPNE3.4.x
- OpenPNE3.2.x
- OpenPNE3.0.x
修正内容¶
Mutsumi Imamura さんが14年以上前に更新
- 題名 を プロフィール項目の日付やテキストの最小値/最大値を から プロフィール項目の日付やテキストの最小値を最大値より大きくして設定できてしまう に変更
- 担当者 を削除 (
Masato Nagasawa) - 対象バージョン を削除 (
OpenPNE 3.4.3)
Minoru Takai さんが14年以上前に更新
- ステータス を Accepted(着手) から Pending Review(レビュー待ち) に変更
- 進捗率 を 0 から 50 に変更
更新履歴 d78ecd88d1a7a606310d83873a7e517b6a25766e で適用されました。
Minoru Takai さんが14年以上前に更新
設計時のメモを示しておきます。
#837 のチケットで示されているが、2系からのコンバートを行うと (min, max) が (0, 0) になってしまうため、これが文字列長 [0, 0] として働かないように修正する必要がある。
このため、逆に文字列長 [0, 0] を指定することができない仕様にする必要があるため、文字列長に関する (min, max) の下限を (0, 1) と定めることにした。
■ フォームタイプが「テキスト (input)」または「テキスト(複数行) (textarea)」 # テストケースの組み合わせ * 負値、0、正値、空、不正値をそれぞれ (-, 0, +, N, B) とする * 負値、0、正値(数値と総称)は、/^\s*[+-]?\d+\s*$/ で表現される文字列を指す * 空(N: NULL) は、空文字列あるいは空白類のみ(/^\s*$/)の文字列を指す * 不正値(B: Bad value)は、数値でも空でもない文字列を指す # テスト結果のカラムについて * 文字列のカラムは、入力値タイプが文字列・メールアドレス・URL・正規表現の場合 * 文字列カラムにおける最小値、最大値は、文字列の長さを意味する * 数値のカラムは、入力値タイプが数値の場合 * 数値カラムにおける最小値、最大値は、数値自体の下限、上限を意味する # テスト結果(出力されるエラー)について * x<0 : min は 0 以上でなければなりません * y<1 : max は 1 以上でなければなりません * !=INT : 整数ではありません * y<x : max は min 以上でなければなりません * (ignore) : (不要な項目,テストの必要なし) * Accepted : 受理された(エラーなし) ▼ テスト結果 +----------+----------------+----------------+----------------+----------------+ | x y | x <= y 文字列 | x > y 文字列 | x <= y 数値 | x > y 数値 | +----------+----------------+----------------+----------------+----------------+ | - - (1) | x<0, y<1 | x<0, y<1 | Accepted | y<x | | - 0 (2) | x<0, y<1 | (ignore) | Accepted | (ignore) | | - + (3) | x<0 | (ignore) | Accepted | (ignore) | | - N (4) | x<0 | (ignore) | Accepted | (ignore) | | - B (5) | x<0, y!=INT | (ignore) | y!=INT | (ignore) | | 0 - (6) | (ignore) | y<1 | (ignore) | y<x | | 0 0 (7) | y<1 | (ignore) | Accepted | (ignore) | | 0 + (8) | Accepted | (ignore) | Accepted | (ignore) | | 0 N (9) | Accepted | (ignore) | Accepted | (ignore) | | 0 B (10) | y!=INT | (ignore) | y!=INT | (ignore) | | + - (11) | (ignore) | y<1 | (ignore) | y<x | | + 0 (12) | (ignore) | y<1 | (ignore) | y<x | | + + (13) | Accepted | y<x | Accepted | y<x | | + N (14) | Accepted | (ignore) | Accepted | (ignore) | | + B (15) | y!=INT | (ignore) | y!=INT | (ignore) | | N - (16) | y<1 | (ignore) | Accepted | (ignore) | | N 0 (17) | y<1 | (ignore) | Accepted | (ignore) | | N + (18) | Accepted | (ignore) | Accepted | (ignore) | | N N (19) | Accepted | (ignore) | Accepted | (ignore) | | N B (20) | y!=INT | (ignore) | y!=INT | (ignore) | | B - (21) | x!=INT, y<1 | (ignore) | x!=INT | (ignore) | | B 0 (22) | x!=INT, y<1 | (ignore) | x!=INT | (ignore) | | B + (23) | x!=INT | (ignore) | x!=INT | (ignore) | | B N (24) | x!=INT | (ignore) | x!=INT | (ignore) | | B B (25) | x!=INT, y!=INT | (ignore) | x!=INT, y!=INT | (ignore) | +----------+----------------+----------------+----------------+----------------+ ■ フォームタイプが「日付 (date)」 # テストケースの組み合わせ * 正しい日付、空、不正な日付をそれぞれ (G, N, B) とする #### 日付として許される形式を変更しています:以下は正しくありません #### * 正しい日付(G: Good value) は、2010/04/01 や 2010/10/01 20:00:00 などの文字列 * 時間(time)は無視され、日付(date)のみ評価される * 2009/8/7 (2009/08/07) も受け付けることができる * 09/08/07 (2007/08/09) といった表記も不正な日付とはならない * yesterday なども不正な日付とはならない * YYYY/MM/DD 以外の表記は受け付ける保障をしなくてよい * MM: 01~12, DD: 01~31 が許され、2010/02/31 は 2010/03/03 と見なされる * 2010/04/01 を 2010/4/1 のように入力することも許される * 10/04/01 は 2010/04/01 と解釈されない(2001/04/10 となる) * 日付として不正な文字列のようでも受け付けられることがある * 参考 http://www.php.net/manual/ja/datetime.formats.php * PHPマニュアルにあるフォーマット全てに対応している保証はない * 空(N: NULL) は、空文字列あるいは空白類のみ(/^\s*$/)の文字列を指す * 不正な日付(B: Bad value)は、日付として認識されない文字列を指す * +2010/10/10 や -0001/01/01 など符号の付いた日付は不正となる * 2010/13/01 など月日が範囲外の日付も不正となる #### 日付として許される形式を変更しています:以下に新しい仕様を示します #### ####(更に仕様について変更する可能性があります。新しいコメントを確認してください) #### * 正しい日付は、YYYY/MM/DD の形式のみ。 * MM, DD は2桁でも1桁でもよいが、YYYY は4桁に限る(年は 0 詰めが必要) * 厳密には次の正規表現にマッチする表記のみ: * 'date_format' => '/^(?P<year>\d{4})\/(?P<month>\d{1,2})\/(?P<day>\d{1,2})$/' * 日付として指定できる範囲は制限をかけていません * 環境によって strtotime() の値がオーバーフローするような範囲に関してはチェックしていません # テスト結果(出力されるエラー)について * !=DATE : 正しくありません * y<x : max は min 以上でなければなりません * (ignore) : (不要な項目,テストの必要なし) * Accepted : 受理された(エラーなし) # テストケースの詳細 * 正しい日付として確認したもの * 2010/07/20 : 形式に沿ったもの * 2010/7/20 : 先頭の0を省略したもの * 1010/10/10 : 4桁だが 32bits 環境下で strtotime() の扱える範囲外の日付 * 0001/01/01 : 同上 * 8100/07/10 : 同上 * なお、以下の形式は受け付けられません * 20/07/10 : 2010/07/20 の別記法 ▼ テスト結果 +----------+------------------+------------------+ | x y | x <= y 日付 | x > y 日付 | +----------+------------------+------------------+ | G G (1) | Accepted | y<x | | G N (2) | Accepted | (ignore) | | G B (3) | y!=DATE | (ignore) | | N G (4) | Accepted | (ignore) | | N N (5) | Accepted | (ignore) | | N B (6) | y!=DATE | (ignore) | | B G (7) | x!=DATE | (ignore) | | B N (8) | x!=DATE | (ignore) | | B B (9) | x!=DATE, y!=DATE | (ignore) | +----------+------------------+------------------+
Minoru Takai さんが14年以上前に更新
- ステータス を Pending Review(レビュー待ち) から Accepted(着手) に変更
opValidatorDate が 13月 や 32日 を弾くのでこれに任せてしまっていましたが、0月 や 0日 が指定できることが分かりました。その日付自体が不自然という問題もありますが、それ以上に 0年 を指定するとバリデート後に -1年 と評価され、ユーザ側で致命的エラーが生じる場合があることが分かりました。
管理画面からの入力時に日付の最小値を 0001/01/01 と指定することで対応します。なお、指定できる日付の範囲は、西暦1年1月1日から西暦9999年12月31日までという暗黙的な仕様に基づいて実装します。
Minoru Takai さんが14年以上前に更新
- ステータス を Accepted(着手) から Pending Review(レビュー待ち) に変更
更新履歴 e8f879a320b81e986bda7f402bc03d887b712304 で適用されました。
日付型の仕様を厳密にしました。
YYYY/MM/DD の形式しか受け付けません。より厳密には次の正規表現にマッチする文字列です。
/^\s*\d{4}\/\d{1,2}\/\d{1,2}\s*$/
年が4桁でなかったり、年月日の順でないものは不正となります。また、0月0日を不正としました。(前回のテストで受理されてしまったのは opValidatorDate で実装されていたものが不完全だったことが原因です。)
Minoru Takai さんが14年以上前に更新
- ステータス を Pending Review(レビュー待ち) から Accepted(着手) に変更
管理画面側のバリデータやエラーについては十分対応できたと思いますが、ユーザ側のバリデータやエラーが若干不適切なままでした。別チケットを作って問題を分散させると参照が困難になると思われるのでこのチケットで併せて修正したいと思います。
Minoru Takai さんが約14年前に更新
対応すべき項目
- (1) プロフィールIDが重複していないかを検証するバリデータが元々設定されていたが、最小値最大値の大小を検証するバリデータを設定したことで前者を上書きしていた(かき消していた)ため、プロフィールIDが重複していた場合にfatal errorが発生している
- (2) 管理画面側のバリデートはよいが、メンバー画面側のバリデートが不完全である
- (2010/09/30追記)範囲外のエラーメッセージの表記が YYYY/MM/DD ではなく DD/MM/YYYY 00:00:00 となってしまっていることが問題と捉えています
- 補足として、プロフィール確認画面で、誕生日以外の日付プロフィールの表記が YYYY-MM-DD とハイフン繋ぎになっていることも気になります
Minoru Takai さんが約14年前に更新
- ステータス を Accepted(着手) から Pending Review(レビュー待ち) に変更
(2) は、関連していますが、本チケットの内容と分離して考えることができるので、別チケットでの対応とすることにします。
管理画面側のプロフィール項目(数値、日付のmin,max問題)は修正したということで、テスト待ちにします。
Shogo Kawahara さんが約14年前に更新
Issue #1630 を作成しました。
(2) について pc_frontend でのバリデーションエラーのメッセージが不適切であることに関するIssueです。
Shogo Kawahara さんが約14年前に更新
Issue #1631 を作成しました
(2) について、 pc_frontend でのプロフィールの表示に関する不整合についてのIssueです。
Shogo Kawahara さんが約14年前に更新
- lib/validator/opValidatorDate.class.php に関して指摘します。
91 // all elements must be empty or a number 92 foreach (array('year', 'month', 'day', 'hour', 'minute', 'second') as $key) 93 { 94 if (isset($value[$key]) && !preg_match('#^\d+$#', $value[$key]) && 0 !== (int)$value[$key]) 95 { 96 throw new sfValidatorError($this, 'invalid', array('value' => $value)); 97 } 98 }
ここで !empty($value[$key]) を 0 !== (int)$value[$key] に変更した意図がよくわかりません。
- 現状必須ではありませんが、できればUnitテストのカバレッジを維持・向上してください。
Minoru Takai さんが約14年前に更新
その変更については、管理画面側から実際に日付入力を行った際に 0年 という入力が可能だったために修正した記憶があります。
しかしながら今見返したら修正が正しいと言い切れない(記憶が曖昧な)ので不適切な修正である可能性があります。
Shogo Kawahara さんが約14年前に更新
文字列で、"1989-00-08" とした場合、 DateTime が "1989-12-08" と解釈するため
バリデーションに成功します。
しかし、今回のように正規表現でのチェックを行うように設定した場合 "1989-00-08" のようなケースは、convertDateArrayToDateTime() が 0 を拒否しています。
よって今回の箇所で、 0 !== (int)$value[$key] を、する必要がありません。
この部分を元に戻すことを薦めます。
(ここを直すと、ユニットテストのカバレッジは100%になります。)
Shogo Kawahara さんが約14年前に更新
- ステータス を Pending Review(レビュー待ち) から Pending Testing(テスト待ち) に変更
- 進捗率 を 50 から 70 に変更
Kousuke Ebihara さんが約14年前に更新
- ステータス を Pending Testing(テスト待ち) から Pending Review(レビュー待ち) に変更
- 進捗率 を 70 から 50 に変更
バックポートの検証のために軽く動作を確認しましたが、疑問が発生したので海老原の方でレビューし直します。
Kousuke Ebihara さんが約14年前に更新
- ステータス を Pending Review(レビュー待ち) から Rejected(差し戻し) に変更
修正内容に対する指摘等に入る前に、まず、このチケットに関する作業において日付の最大値、最小値として許容する値のパターンを狭めていますが、そのような選択をした理由がこのチケットからは読み取れなかったので説明をお願いしたいと思います。
Minoru Takai さんがほぼ14年前に更新
- ステータス を Rejected(差し戻し) から Accepted(着手) に変更
note-22 での指摘に対する直接的な回答は、以下「▼ date_format(YYYY/MM/DD) 制限をした理由は次の通りである。」に示しています。関連する全ての情報を示します。
このチケットでの変更内容¶
- 1. editSuccess.php
- 1-1. 文言が翻訳ファイルと異なっているのを修正(format 直後のドット)
- 日付型に対する文言:『YYYY/MM/DD 形式で入力(例:2010/07/18, 0001/01/01)
その他、 PHP の strtotime() 関数が解釈することのできる特殊な文字列が利用可能』
- 日付型に対する文言:『YYYY/MM/DD 形式で入力(例:2010/07/18, 0001/01/01)
- 1-2. 日付は年月日(date)情報しか持たないため、フォーマットからtimeを削除
- 1-3. 具体例を '2009/01/01 23:59:21' から '2010/07/18, 0001/01/01' に変更した
- この日付に意味はない:(4-1.)に併せて、年が4桁でない場合に0埋めが必要なことを示す例を含めた
- 1-1. 文言が翻訳ファイルと異なっているのを修正(format 直後のドット)
- 2. messages.ja.xml
- 2-1. 「最小値」「最大値」として入力できる数値に制限を指定するため、エラーメッセージを追加
- これは「文字列長」と「数値」に対するメッセージ(「日付」は無関係)
- 2-1. 「最小値」「最大値」として入力できる数値に制限を指定するため、エラーメッセージを追加
- 3. ProfileForm.class.php
- 3-1. 新L103,110: コーディング規約に準拠
- 3-2. 新L111-115: 文字列長の場合は、(min, max)の下限を(0, 1)に設定
- 文字列長として負値や(0, 0)を許さない
- (0, 0)を許さない理由は #837 問題を考慮したため
- 3-3. 新L125-126: datetime_output を設定
- 設定しないとプロフィール編集画面で YYYY-MM-DD などとハイフン区切りになるためスラッシュ区切りにした
- ※with_time, datetime 型が不適切であるため、後に date_output に書き換えている
- 3-4. 新L139-142: 大小比較のバリデータを追加
- ※setPostValidator() は誤りなので後に修正
- 3-5. 新L147-164: 大小比較バリデートメソッドの定義
- 4. ProfileForm.class.php
- 4-1. 新L125: 入力として受け付けられる日付のフォーマットを YYYY/MM/DD に制限(理由は後述)
- 4-2. 新L126: (3-3.)を修正、date型に変更
- 4-3. 新L127: 入力値が(4-1.)でない場合のエラーメッセージ中に示すフォーマットを記述
- 5. opValidatorDate
- 5-1. L59,68: エラーメッセージ中のフォーマットがハードコーディングされていたので修正
- 日付の入力が不正だった場合に、(4-3.)のフォーマットでの再入力を促す
- 5-2: L94: 年月日が 0 な場合を弾く
- ※この修正は不適切だったため後に元に戻した(参照7-1.)
- 5-1. L59,68: エラーメッセージ中のフォーマットがハードコーディングされていたので修正
- 6. ProfileForm.class.php
- 6-1. (3-4.)を修正
- 同ファイル56行目 のバリデートを打ち消していたため修正
- 6-1. (3-4.)を修正
- 7. opValidatorDate.class.php
- 7-1. 条件式を書き換えた修正(5-2.)を元に戻した(revert)
- 8. messages.ja.xml, ProfileForm.class.php
- 8-1. 「最大値:最小値以下でなければなりません。」と(2-1.)で逆のことを言っていたので修正
変更箇所に関する考察¶
修正内容にチケットタイトル以上の修正が含まれていることや修正の流れが不自然に見えるかもしれません。これは、この問題が #837 や #1595 など他にも関連する問題があったことと、修正実施者が適切な修正の全容を認識出来ていない状態で修正を行っていたためです。
- 3-2. (min, max) に関して (0, 1) を下限にした理由
- 2系から3系へのコンバートを行うと、2系の仕様により (0, 0) という制限値が「制限なし」を意味してしまっていたため、これを考慮して、文字列長 1 以上という前提の下限値として (0, 1) を設定した
- しかしながら、この問題は #1478 で解消されている
- 3-3. datetime型にしていた理由
- 修正前が datetime 型であり、この修正時点で date 型にすべきだということに気づいていなかった
- 4-1. YYYY/MM/DD (Y{4}/M{1,2}/D{1,2}) に制限した理由
- 1: このチケットの修正を始めた時点で、"today" などの「YYYY/MM/DDといった形式以外の入力値」が「YYYY/MM/DD形式」に変換されてからDBに保存されるという(編集時には"today"ではなくなっている)実装となっていて、これを仕様だと思っていた(後に作成された #1595 ではバグ扱いされている)。
- "today" といった入力値が "YYYY/MM/DD" に変換されてから保存されるのが仕様だと思った理由
- "today" や "tomorrow" などと相対値を指定し、相対値がDBに保存されるのが正しいとしたら、それはいったい min, max としていつの日付を指すのか特定できないのではないかと思った。
- 例えば、2010年10月10日に (min,max)=("yesterday", "tomorrow") と管理画面で登録したら、その日付型プロフィールには 2010/10/09 <= date <= 2010/10/11 となる date しか正しくないと判断されるべきではないのか。
- メンバーがプロフィールを入力する時に yesterday, tomorrow を日付に変換するとなると、入力日の前日から翌日までの入力しか正しくないという判断をすることになり、これは意図する制限値として採用されるべきではないと思った。
- "today" といった入力値が "YYYY/MM/DD" に変換されてから保存されるのが仕様だと思った理由
- 2: このチケットの本題であるmin-max比較に関して、(3-4.)(3-5.)の修正を行っていたとき、始めに setPost-(mergePost-) メソッドではなく setPre- メソッドで試していた。Preの場合は min,max の値として(月や日が1桁のような場合でも) "YYYY/MM/DD" という形式の固定10文字長ではなく、入力された値そのもの("today", "2010/1/10" など)であったため比較が難しいと思い(※) YYYY/MM/DD という形式のみを許可するようにしようと思った。
- 3(※2): strtotime() に入力値を渡せば、 "today" や月部分などが1桁の "2010/1/10" のような値でも比較できるようになるが、そもそもこのフォームの日付として入力できる下限・上限に制限があるのかというところを考え、 strtotime() が扱える日付の範囲に依存しないようにしたいと思い、文字列比較による辞書順での大小比較をするという考えに辿りついた(※)。
- 4(※3): 文字列比較の場合 ($min = "2010/01/10", $max = "2010/01/02") のように文字列長が同じ(で区切り文字も同じ)ならうまくいくはずだが、文字列長が異なるとうまくいかない気もする。
- このあたりまで考えて setPre- ではなく setPost-(mergePost-) にすべきだということに気づいた。*Post- であれば日付がバリデート(クリーン)された後なので "2010/1/9" という入力だったとしても "2010/01/09" となり日付型 min,max の文字列比較がうまくできる。
- 5: (2:)(3:)(4:)の流れから、考察がしっかりしていないまま YYYY/MM/DD という形式に制限したままにしてしまっていた。また、(1:)があるためこれが不自然だとは思っていなかった。
- 1: このチケットの修正を始めた時点で、"today" などの「YYYY/MM/DDといった形式以外の入力値」が「YYYY/MM/DD形式」に変換されてからDBに保存されるという(編集時には"today"ではなくなっている)実装となっていて、これを仕様だと思っていた(後に作成された #1595 ではバグ扱いされている)。
- YYYY/MM/DD に制限している状況に対する考察
- 仮に "today" や "YYYY-MM-DD" などの(YYYY/MM/DD以外の)文字列を受け付けず、YYYY/MM/DD のみを受け付けるという仕様でよいとした場合、冒頭でも示した次の注意書きが不適切である。
- 日付型に対する文言:『YYYY/MM/DD 形式で入力(例:2010/07/18, 0001/01/01)
その他、 PHP の strtotime() 関数が解釈することのできる特殊な文字列が利用可能』
- 日付型に対する文言:『YYYY/MM/DD 形式で入力(例:2010/07/18, 0001/01/01)
- このチケットコメントで指摘されているとおり、YYYY/MM/DD に制限すること自体が(これまで可能だったのに変更するという点でも)不自然であるため、この制限は付けるべきではないと現時点では思っている。が、制限を外すべきだという理由も少し弱いため検討している。
- ▼ date_format(YYYY/MM/DD) 制限をした理由は次の通りである。
- "today" などの(相対的な)入力があれば、その入力を(絶対的な) "2010/11/24" などに変換してから保存すべきだと思っていた(理由は上記(4-1.1:項)参照)。
- 日付が自動的に計算されることにより次に示すような意図しない日付指定ができてしまう。以下(C)は(Case)の略。
- (C1) "2010/02/31" が受け付けられ "2010/03/03" となる。(2010年02月は28日までなので、2月29日が3月1日と計算される)
- (C2) "2010/02/00" が受け付けられ "2010/01/31" となる。(2月1日の前日と計算され、1月31日になる)
- (C3) "2010/01/00" が受け付けられ "2009/12/31" となる。(C2)と同じ原理。
- (C4) "2010/00/01" が受け付けられ "2009/12/01" となる。(C2)と同じ原理。月の場合も同様。
- (C5) "0000/01/01" が受け付けられる。日付はこのまま保存される。
- (C6) "0000/00/01" が受け付けられ "-0001/12/01" となる。この結果は異常であり、min-max比較を文字列比較でやろうと思った場合に予期しない結果となりうる。
- YYYY/MM/DD 形式以外の入力を想定していないと思っていた。
- "today" 云々の話もあるが、"2010-01-01" のようにハイフン区切りを許しているのも不自然に感じた。
- こうした経緯があり、 YYYY/MM/DD に限定してしまってもよいと判断した(この考察・判断が正しいかは自分自身でも疑問であり検討したい)。
- (4-1.)の(1:)項で考察している通りだが "today" などをそのまま保持するという点について認識ができていない(この問題は #1595 を参照)
- 仮に "today" や "YYYY-MM-DD" などの(YYYY/MM/DD以外の)文字列を受け付けず、YYYY/MM/DD のみを受け付けるという仕様でよいとした場合、冒頭でも示した次の注意書きが不適切である。
- 5-2. "!empty($value[$key])" を "0 !== (int)$value[$key]" と変更した理由
- 1: 上記(4-1.)対応過程で、0年(0000/MM/DD) など年月日のいずれかに 0 が設定できてしまったことに対して、それを意図的に対処しようとした。
- 2: しかしこの(1:)の修正は、その意図に対して適切なものではなく、取り消されるべきであると指摘された(対応している)。
- 3: この「0年問題」に対しては date_format 制限(YYYY/MM/DD)によって制約が掛けられている
関連チケットの状況と関係について¶
- #837 : 上記の対応前から認識しており並行して修正した -- 「(min,max)=(0,0) という設定ができてしまい0文字制限されてしまうのは不自然」
- 2系では (min,max)=(null,null) のような意味を (0,0) として表現していた。この問題はコンバート時に制限なしの項目が0文字制限されてしまうという問題を含んでいる。
- #940 : このチケットです -- 「min > max のまま設定できてしまう」
- #1053 : 上記の対応前からあったようだがチケットの存在を認識していなかった -- 「文字列長の制限値なのに自然数でない負値などが入力できてしまう」
- #1449 : (#837)対応中に派生して作られた -- 「(0)を制限なしとしたため(min,max)=(0,1)の場合でも(-1)などが通ってしまう」
- #1478 : #837 の本質的な対策として作られた -- 「2系は(0,0)が制限なしだが、3系は(0,0)が0制限を意味するので意図しない結果となる」
- #1595 : #1449 の修正チェック時に発覚して作られた -- 「"today" などを入力しても "YYYY/MM/DD" の形式の具体的な日付に変換されてから入力値が保存されている」
- #1630 : #940 で管理側の値の表記修正を入れているが、それに対するメンバー側の修正として作られた -- 「メンバー側のプロフィール入力時のエラーメッセージのフォーマットが適切でない」
以上、 #940 で多くの修正を行った私が認識している状況についてまとめました。私の修正にも不適切な箇所がある可能性が高いため、在るべき仕様を検討した上で、どこをどう修正すべきか指摘があればお願いします。
Minoru Takai さんがほぼ14年前に更新
- ステータス を Accepted(着手) から Pending Review(レビュー待ち) に変更
このチケットは「プロフィール項目の最小値(min)・最大値(max)の大小比較が行なわれていない」という内容でしたが、その内容を超える修正を同時に行ってしまっています。この点で修正内容が追いにくくなってしまっているかもしれません。
このチケットでの変更内容 に経緯を示していますが、より簡単に現在の状況についてまとめておきます。
- Y{4}/M{1,2}/D{1,2} のような正規表現で YYYY/MM/DD 形式の文字列しか受け付けないようにした
- 0年や0月や0日を指定できなくしている
- "today" や "YYYY-MM-DD" などを指定できなくしている
- 文字列長としての (min, max) に対し、下限を (0, 1) に設定した
- min, max の大小を(文字列)比較し、min <= max でなければエラーを返すようにした
- 日付入力時の注釈が翻訳されていなかったのを修正した
- 保存時のフォーマットを date_output' => 'Y/m/d' とスラッシュ区切りになるように指定した
note-23 が長文なので簡単に対応内容を整理しました。
現在の修正内容に対して差し戻しなどがあれば指摘お願いします。レビュー待ちにステータスを変更します。
Minoru Takai さんが13年以上前に更新
管理画面のプロフィール項目設定において、プロフィール項目値の範囲設定(文字数の下限上限、日付値の下限上限)として、
- どのような値を入力することができるか(文字数制限値に負の数、0はありなのか、日付値に "today" や "2000-1-2" や "0/0/0" や "1/1/1" はありなのか等)
- どのような値を設定することができるか(制限値なしは可能か、0以上0以下**つまり0のみ**は可能か、昨日から明日までなど相対的な日付は可能か、西暦は何年から指定可能か)
といった仕様が明確でないため、このチケット、およびこれに関連するチケットをより適切に対応するには先に仕様策定が必要です。
現状は仕様が明確でないまま実装されており、不自然な挙動(結果的に不具合とされているもの)がバグとして報告(チケット化)されています。
このチケットでは master ブランチに変更を加えてしまっているため、この修正を(一先ず)取り消す必要があるかは別のどなたかの判断を待ちます(レビュー待ちとしておきます)。
Kousuke Ebihara さんが13年以上前に更新
- ステータス を Pending Review(レビュー待ち) から Rejected(差し戻し) に変更
説明ありがとうございます(7 ヶ月間もかかってしまいすいません……)。日付型プロフィールのバリデーションに関して差し戻しますので、以下 2 点について対応をお願いします。
- #930 により混入した問題(プロフィール項目登録時に strtotime() によって求めた値が登録される問題)に基づく書式の決定はそもそも前提が誤りなので取り消してください
- その上で、以下の「2. ではどうするべきか」で提示した実装方法について検討してください
#930 で混入した問題については既に作られているバグチケットである #1595 で対応してもよいですし、それが紛らわしくなるのであればこのチケットで対応しても構わないと思います。
1. 現状判明している問題点について¶
#1595 で指摘しているとおり、プロフィール項目登録時に strtotime() によって求めた値を DB に格納している問題は、後方互換性を壊しているという点で重大です。
この変更が具体的に影響を受ける例として、誕生日があります。誕生日は min の値として "-100years" が、 max の値として "now" が指定されています。最大の年齢にある程度の制約を設けるのはまあそれなりに妥当ですし、誕生日として未来の日付を指定できないようにするのは仕様として適切です。この挙動が、 #930 によって、プロフィール登録時の -100years と now に固定されてしまったため、プロフィール登録時以降の日付で登録がおこなえなくなってしまいました。
プリセットプロフィール導入前にはこの値が変更できたため、たとえば 18 歳未満の登録を制限する目的でこの min の値を変更していた場合、満 18 歳である(が、プロフィール項目登録時には満 17 歳だった)ユーザが登録できなくなる可能性が出てきます。
プリセットプロフィールは今のところ min や max の値を変更することはできませんが、今後の機能改善によって変更がおこなえるようになった場合に影響を受けやすくなるかもしれません。
もちろん、誕生日以外の独自のプロフィール項目で、 min や max を設定している場合にも、影響を受けます。普通に考えれば日付を入力させようと思った場合に、未来の日付を入力させてよい場合というのは少ないはずですので、このような場合に max に "today" や "-10days" などの値を入れて入力値を制限したいというニーズは当然考えられます。現在の OpenPNE は #930 の対応以降、このような場合に対処できない実装になっています。
ここまでで述べたことは、すべて、 #930 により混入した問題に関することで、本チケットでおこなわれた修正により発生したものではありませんが、 このチケットでは #930 で誤って混入した実装に従って、誤った入力書式の決定がおこなわれてしまいました。この変更は改められるべきです。
// 余談ですが、 ISO-8601 に従うとすれば、 00 月や 00 日は異常な値(MySQL などのシステムでは未入力値として扱っているが)ですが、 0000 年は異常な値ではなく、紀元前 1 年として扱われるのが通常です。さらにさらに余談ですが、 PHP 5.3.x 以降における strtotime() の仕様(http://jp2.php.net/manual/ja/datetime.formats.php)でも年については ISO-8601 に従い 0000 年を紀元前 1 年として扱っているようですが、月や日におけるアンダーフローを許していたり、うるう秒を受け入れていない(?)などの細やかな違いがあるようです。
// まあ、しかし、プロフィール項目で紀元前の日付が必要になる状況はさすがに考えにくいので、バリデーションの際に支障が出るようならこのまま制限してしまってもよいかもしれません(が、書式チェックのことを考えなければ大小の比較はおこなえるようになるため、一応紀元前の日付やアンダーフローした月日も受け入れられるようにはなります)。
2. ではどうするべきか¶
上にあげた問題を回避したうえでこのチケットへの対応をおこなう方法として、以下のようなものが考えられます。
1. min と max で入力できる値の種類を制限し、両方に絶対日指定がおこなわれている場合はそのタイムスタンプを、両方に相対日指定がおこなわれている場合はそれぞれのキーワードを独自のルールに基づき比較する。どちらか一方が絶対日指定でもう一方が相対日指定であった場合はエラーとする- 1988/04/23 に min に today を、 max に 1988/04/24 を指定してプロフィール項目を登録した場合、翌日にはどの値も受け入れなくなることになるため
- 独自のルールによる比較ではなく、入力値をその時点での strtotime() に通した結果同士で比較をおこなうことで min と max の値が矛盾していないかを検証してもよいが、入力値によっては、時間の経過によって矛盾する結果になるものがありうるかもしれない
- プロフィール項目として許容しうる相対日指定のパターンはある程度限定できると思うが、それ以外の書式を既に使用しているユーザがいるかもしれないことを考えると踏み切りにくい(開発版でのみ種類を限定するというのはアリ)
- ただ、そもそも、絶対日指定か相対日指定かを判別するのは困難である。前述のマニュアルや、 http://lxr.php.net/xref/PHP_5_3/ext/date/lib/parse_date.re を参考に絶対日指定であるかどうかを判別する処理を書くことは、やってやれないことはないとは思うが……
- MM/DD/YYYY は受け入れないのかとかも考えないといけない
2. 1. とほぼ同様だが、入力できる値の種類の制限は設けない
3. 日付型についてはあえてバリデーションをおこなわない(もしくは strtotime() の結果が false にならないかどうかについてのみ確認する)- 元々の実装をほとんど維持する形になる
- これが一番現実的かも
- 心もとないなら、注意書きを充実させるなり、大雑把に strtotime() の結果どうしで比較してマズそうなら登録はするものの警告出すとか
3. 安定版には取り込まれるべきか¶
このチケットのバックポートチケットである #1265 にて安定版における取り込みについて述べられていたのでこの点についても言及します。
バグチケットでの対応方針が明確にはなっておらず、安定版での取り込みは難しいので対応しない方針とします
とありますが、バックポートチケットへの対応を検討した時点で対応方針が明確になっていないからといって安定版での取り込みが難しいというのは論理が飛躍している、というより意味がわかりません(別チケット対応にするなどということならわかりますが……)。この問題は明らかにバグで、しかもこの問題によって長期間後方互換性が壊れています。安定版で対応しない理由がありません。安定版の安定性を損なわない形で変更を取り込んでください。
Minoru Takai さんが13年以上前に更新
- ステータス を Rejected(差し戻し) から Accepted(着手) に変更
http://redmine.openpne.jp/issues/940#note-26 でのコメントありがとうございます。以前着手してから時間が経ってしまっていましたが、近々この問題を解消あるいは整理したいと考えていました。
#930 で混入した問題と、想定的な日付指定に対する仕様について認識を改めたので、 note-26 を参考に後方互換性等の問題を解消することを前提に、より好ましい仕様を検討して修正を行いたいと思います。
少なくとも、このチケットで私が既にコミットした内容は note-26 で指摘されている通り、あるべき仕様を誤解した状態での修正であるため、少なくともこれらの不適切な修正を取り消すことを対応します。更に適切な修正が可能そうであれば修正方針を示した上でコミットしたいと思います。
Minoru Takai さんが13年以上前に更新
対応方針を示しておきます。
まず、このチケットで(誤った方針の)コミットを行ってしまっているので、コミットを全て revert します。不適切な一部の修正だけ revert してもよいのですが、分かりやすくするために全てまとめて revert することにします。
その後、 #1595 を先に修正し、また関連する他のチケットの問題を解消してから、このチケットを進めます。
- b265772c12f62449346270facda172c075d1172c でこれまでのコミットを全て元に戻しました
Minoru Takai さんが約13年前に更新
- ステータス を Accepted(着手) から Pending Fixing(修正待ち) に変更
- 担当者 を削除 (
Minoru Takai)
現時点で、このチケットで扱おうとしていた内容を示します。
- (このチケットに関連付けされている多くのチケットなど)関連する問題はいくつか修正完了している
- このチケットでは次の問題のいくつか(あるいは全て)を修正し、必要があれば残りの問題の別チケットを作る
- プロフィール項目の最小値と最大値の比較をしていない(文字数の制限、数値の制限に関して)
- 非負整数のみが受け付けられるべき箇所で負値が入力できる(別チケットがあったかもしれないが、ここで扱ってもよいと思った)
- プロフィール管理画面(編集画面ではなく、一覧画面)で、各プロフィールの制限値を表示するようにする
- これは最小値と最大値の比較について、日付型の制限のバリデートを書けない(相対的日付と絶対的日付の比較は、その観測時点によって妥当性が変わる)ので、プロフィール管理画面を開いた時点でバリデートし、日付に問題があれば管理者に「おかしい」ことを分かるようにする
- 別のチケットで「矛盾する最小値、最大値による制限」がある場合に、その制限がなかったことにするような修正が入っている(可能性がある)ので、バリデートが通らない(本チケットに基づく最小値最大値の妥当性の矛盾がある)場合に管理者に気づかせる必要がある。これは note-26 の「心もとないなら、注意書きを充実させるなり、大雑把に strtotime() の結果どうしで比較してマズそうなら登録はするものの警告出すとか」のことである。
なお、安定版 3.6 向けにこの問題を修正する必要性があるかについては、この問題は管理者にしか関係せず、管理者にとって不便を強いる不具合ではない(このチケットの指摘通り直す必要性はあまりない)と考えられるので、対応の必要性は弱いだろうということを示しておきます。