mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-10-31 10:39:59 +08:00 
			
		
		
		
	Merge branch 'master' into bugfix/20002-Superfluous_query_on_HEAD_request_in_serializer
This commit is contained in:
		| @ -76,8 +76,8 @@ | |||||||
|         "ezyang/htmlpurifier": "^4.6", |         "ezyang/htmlpurifier": "^4.6", | ||||||
|         "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", |         "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", | ||||||
|         "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", |         "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", | ||||||
|         "bower-asset/inputmask": "~3.2.2 | ~3.3.5 | ~5.0.8 ", |         "bower-asset/inputmask": "^5.0.8 ", | ||||||
|         "bower-asset/punycode": "1.3.* | 2.2.*", |         "bower-asset/punycode": "^2.2", | ||||||
|         "bower-asset/yii2-pjax": "~2.0.1", |         "bower-asset/yii2-pjax": "~2.0.1", | ||||||
|         "paragonie/random_compat": ">=1" |         "paragonie/random_compat": ">=1" | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -4,6 +4,8 @@ Yii Framework 2 Change Log | |||||||
| 2.0.50 under development | 2.0.50 under development | ||||||
| ------------------------ | ------------------------ | ||||||
|  |  | ||||||
|  | - Bug #13920: Fixed erroneous validation for specific cases (tim-fischer-maschinensucher) | ||||||
|  | - Bug #19927: Fixed `console\controllers\MessageController` when saving translations to database: fixed FK error when adding new string and language at the same time, checking/regenerating all missing messages and dropping messages for unused languages (atrandafir) | ||||||
| - Enh #12743: Added new methods `BaseActiveRecord::loadRelations()` and `BaseActiveRecord::loadRelationsFor()` to eager load related models for existing primary model instances (PowerGamer1) | - Enh #12743: Added new methods `BaseActiveRecord::loadRelations()` and `BaseActiveRecord::loadRelationsFor()` to eager load related models for existing primary model instances (PowerGamer1) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -395,9 +395,11 @@ | |||||||
|                         data: $form.serialize() + extData, |                         data: $form.serialize() + extData, | ||||||
|                         dataType: data.settings.ajaxDataType, |                         dataType: data.settings.ajaxDataType, | ||||||
|                         complete: function (jqXHR, textStatus) { |                         complete: function (jqXHR, textStatus) { | ||||||
|  |                             currentAjaxRequest = null; | ||||||
|                             $form.trigger(events.ajaxComplete, [jqXHR, textStatus]); |                             $form.trigger(events.ajaxComplete, [jqXHR, textStatus]); | ||||||
|                         }, |                         }, | ||||||
|                         beforeSend: function (jqXHR, settings) { |                         beforeSend: function (jqXHR, settings) { | ||||||
|  |                             currentAjaxRequest = jqXHR; | ||||||
|                             $form.trigger(events.ajaxBeforeSend, [jqXHR, settings]); |                             $form.trigger(events.ajaxBeforeSend, [jqXHR, settings]); | ||||||
|                         }, |                         }, | ||||||
|                         success: function (msgs) { |                         success: function (msgs) { | ||||||
| @ -563,6 +565,9 @@ | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  |         if (currentAjaxRequest !== null) { | ||||||
|  |             currentAjaxRequest.abort(); | ||||||
|  |         } | ||||||
|         if (data.settings.timer !== undefined) { |         if (data.settings.timer !== undefined) { | ||||||
|             clearTimeout(data.settings.timer); |             clearTimeout(data.settings.timer); | ||||||
|         } |         } | ||||||
| @ -929,4 +934,7 @@ | |||||||
|             $form.find(attribute.input).attr('aria-invalid', hasError ? 'true' : 'false'); |             $form.find(attribute.input).attr('aria-invalid', hasError ? 'true' : 'false'); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     var currentAjaxRequest = null; | ||||||
|  |  | ||||||
| })(window.jQuery); | })(window.jQuery); | ||||||
|  | |||||||
| @ -71,8 +71,8 @@ | |||||||
|         "ezyang/htmlpurifier": "^4.6", |         "ezyang/htmlpurifier": "^4.6", | ||||||
|         "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", |         "cebe/markdown": "~1.0.0 | ~1.1.0 | ~1.2.0", | ||||||
|         "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", |         "bower-asset/jquery": "3.7.*@stable | 3.6.*@stable | 3.5.*@stable | 3.4.*@stable | 3.3.*@stable | 3.2.*@stable | 3.1.*@stable | 2.2.*@stable | 2.1.*@stable | 1.11.*@stable | 1.12.*@stable", | ||||||
|         "bower-asset/inputmask": "~3.2.2 | ~3.3.5 | ~5.0.8 ", |         "bower-asset/inputmask": "^5.0.8 ", | ||||||
|         "bower-asset/punycode": "1.3.* | 2.2.*", |         "bower-asset/punycode": "^2.2", | ||||||
|         "bower-asset/yii2-pjax": "~2.0.1", |         "bower-asset/yii2-pjax": "~2.0.1", | ||||||
|         "paragonie/random_compat": ">=1" |         "paragonie/random_compat": ">=1" | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -353,17 +353,7 @@ EOD; | |||||||
|         foreach ($rows as $row) { |         foreach ($rows as $row) { | ||||||
|             $currentMessages[$row['category']][$row['id']] = $row['message']; |             $currentMessages[$row['category']][$row['id']] = $row['message']; | ||||||
|         } |         } | ||||||
|  |          | ||||||
|         $currentLanguages = []; |  | ||||||
|         $rows = (new Query())->select(['language'])->from($messageTable)->groupBy('language')->all($db); |  | ||||||
|         foreach ($rows as $row) { |  | ||||||
|             $currentLanguages[] = $row['language']; |  | ||||||
|         } |  | ||||||
|         $missingLanguages = []; |  | ||||||
|         if (!empty($currentLanguages)) { |  | ||||||
|             $missingLanguages = array_diff($languages, $currentLanguages); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         $new = []; |         $new = []; | ||||||
|         $obsolete = []; |         $obsolete = []; | ||||||
|  |  | ||||||
| @ -372,89 +362,130 @@ EOD; | |||||||
|  |  | ||||||
|             if (isset($currentMessages[$category])) { |             if (isset($currentMessages[$category])) { | ||||||
|                 $new[$category] = array_diff($msgs, $currentMessages[$category]); |                 $new[$category] = array_diff($msgs, $currentMessages[$category]); | ||||||
|  |                 // obsolete messages per category | ||||||
|                 $obsolete += array_diff($currentMessages[$category], $msgs); |                 $obsolete += array_diff($currentMessages[$category], $msgs); | ||||||
|             } else { |             } else { | ||||||
|                 $new[$category] = $msgs; |                 $new[$category] = $msgs; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |          | ||||||
|  |         // obsolete categories | ||||||
|         foreach (array_diff(array_keys($currentMessages), array_keys($messages)) as $category) { |         foreach (array_diff(array_keys($currentMessages), array_keys($messages)) as $category) { | ||||||
|             $obsolete += $currentMessages[$category]; |             $obsolete += $currentMessages[$category]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!$removeUnused) { |         if (!$removeUnused) { | ||||||
|             foreach ($obsolete as $pk => $msg) { |             foreach ($obsolete as $pk => $msg) { | ||||||
|  |                 // skip already marked unused | ||||||
|                 if (strncmp($msg, '@@', 2) === 0 && substr($msg, -2) === '@@') { |                 if (strncmp($msg, '@@', 2) === 0 && substr($msg, -2) === '@@') { | ||||||
|                     unset($obsolete[$pk]); |                     unset($obsolete[$pk]); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         }         | ||||||
|  |          | ||||||
|         $obsolete = array_keys($obsolete); |  | ||||||
|         $this->stdout('Inserting new messages...'); |         $this->stdout('Inserting new messages...'); | ||||||
|         $savedFlag = false; |         $insertCount = 0; | ||||||
|  |  | ||||||
|         foreach ($new as $category => $msgs) { |         foreach ($new as $category => $msgs) { | ||||||
|             foreach ($msgs as $msg) { |             foreach ($msgs as $msg) { | ||||||
|                 $savedFlag = true; |                 $insertCount++; | ||||||
|                 $lastPk = $db->schema->insert($sourceMessageTable, ['category' => $category, 'message' => $msg]); |                 $db->schema->insert($sourceMessageTable, ['category' => $category, 'message' => $msg]); | ||||||
|                 foreach ($languages as $language) { |  | ||||||
|                     $db->createCommand() |  | ||||||
|                        ->insert($messageTable, ['id' => $lastPk['id'], 'language' => $language]) |  | ||||||
|                        ->execute(); |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |          | ||||||
|         if (!empty($missingLanguages)) { |         $this->stdout($insertCount ? "{$insertCount} saved.\n" : "Nothing to save.\n"); | ||||||
|             $updatedMessages = []; |          | ||||||
|             $rows = (new Query())->select(['id', 'category', 'message'])->from($sourceMessageTable)->all($db); |  | ||||||
|             foreach ($rows as $row) { |  | ||||||
|                 $updatedMessages[$row['category']][$row['id']] = $row['message']; |  | ||||||
|             } |  | ||||||
|             foreach ($updatedMessages as $category => $msgs) { |  | ||||||
|                 foreach ($msgs as $id => $msg) { |  | ||||||
|                     $savedFlag = true; |  | ||||||
|                     foreach ($missingLanguages as $language) { |  | ||||||
|                         $db->createCommand() |  | ||||||
|                             ->insert($messageTable, ['id' => $id, 'language' => $language]) |  | ||||||
|                             ->execute(); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         $this->stdout($savedFlag ? "saved.\n" : "Nothing to save.\n"); |  | ||||||
|         $this->stdout($removeUnused ? 'Deleting obsoleted messages...' : 'Updating obsoleted messages...'); |         $this->stdout($removeUnused ? 'Deleting obsoleted messages...' : 'Updating obsoleted messages...'); | ||||||
|  |  | ||||||
|         if (empty($obsolete)) { |         if (empty($obsolete)) { | ||||||
|             $this->stdout("Nothing obsoleted...skipped.\n"); |             $this->stdout("Nothing obsoleted...skipped.\n"); | ||||||
|             return; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if ($removeUnused) { |         if ($obsolete) { | ||||||
|             $db->createCommand() |             if ($removeUnused) { | ||||||
|                ->delete($sourceMessageTable, ['in', 'id', $obsolete]) |                 $affected = $db->createCommand() | ||||||
|                ->execute(); |                    ->delete($sourceMessageTable, ['in', 'id', array_keys($obsolete)]) | ||||||
|             $this->stdout("deleted.\n"); |                    ->execute(); | ||||||
|         } elseif ($markUnused) { |                 $this->stdout("{$affected} deleted.\n"); | ||||||
|             $rows = (new Query()) |             } elseif ($markUnused) { | ||||||
|                 ->select(['id', 'message']) |                 $marked=0; | ||||||
|                 ->from($sourceMessageTable) |                 $rows = (new Query()) | ||||||
|                 ->where(['in', 'id', $obsolete]) |                     ->select(['id', 'message']) | ||||||
|                 ->all($db); |                     ->from($sourceMessageTable) | ||||||
|  |                     ->where(['in', 'id', array_keys($obsolete)]) | ||||||
|             foreach ($rows as $row) { |                     ->all($db); | ||||||
|                 $db->createCommand()->update( |      | ||||||
|                     $sourceMessageTable, |                 foreach ($rows as $row) { | ||||||
|                     ['message' => '@@' . $row['message'] . '@@'], |                     $marked++; | ||||||
|                     ['id' => $row['id']] |                     $db->createCommand()->update( | ||||||
|                 )->execute(); |                         $sourceMessageTable, | ||||||
|  |                         ['message' => '@@' . $row['message'] . '@@'], | ||||||
|  |                         ['id' => $row['id']] | ||||||
|  |                     )->execute(); | ||||||
|  |                 } | ||||||
|  |                 $this->stdout("{$marked} updated.\n"); | ||||||
|  |             } else { | ||||||
|  |                 $this->stdout("kept untouched.\n"); | ||||||
|             } |             } | ||||||
|             $this->stdout("updated.\n"); |  | ||||||
|         } else { |  | ||||||
|             $this->stdout("kept untouched.\n"); |  | ||||||
|         } |         } | ||||||
|  |          | ||||||
|  |         // get fresh message id list | ||||||
|  |         $freshMessagesIds = []; | ||||||
|  |         $rows = (new Query())->select(['id'])->from($sourceMessageTable)->all($db); | ||||||
|  |         foreach ($rows as $row) { | ||||||
|  |             $freshMessagesIds[] = $row['id']; | ||||||
|  |         } | ||||||
|  |              | ||||||
|  |         $this->stdout("Generating missing rows..."); | ||||||
|  |         $generatedMissingRows = []; | ||||||
|  |          | ||||||
|  |         foreach ($languages as $language) { | ||||||
|  |           $count = 0; | ||||||
|  |            | ||||||
|  |           // get list of ids of translations for this language | ||||||
|  |           $msgRowsIds = []; | ||||||
|  |           $msgRows = (new Query())->select(['id'])->from($messageTable)->where([ | ||||||
|  |               'language'=>$language, | ||||||
|  |           ])->all($db); | ||||||
|  |           foreach ($msgRows as $row) { | ||||||
|  |               $msgRowsIds[] = $row['id']; | ||||||
|  |           } | ||||||
|  |            | ||||||
|  |           // insert missing | ||||||
|  |           foreach ($freshMessagesIds as $id) { | ||||||
|  |             if (!in_array($id, $msgRowsIds)) { | ||||||
|  |               $db->createCommand() | ||||||
|  |                  ->insert($messageTable, ['id' => $id, 'language' => $language]) | ||||||
|  |                  ->execute(); | ||||||
|  |               $count++; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           if ($count) { | ||||||
|  |             $generatedMissingRows[] = "{$count} for {$language}"; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         $this->stdout($generatedMissingRows ? implode(", ", $generatedMissingRows).".\n" : "Nothing to do.\n"); | ||||||
|  |          | ||||||
|  |         $this->stdout("Dropping unused languages..."); | ||||||
|  |         $droppedLanguages=[]; | ||||||
|  |          | ||||||
|  |         $currentLanguages = []; | ||||||
|  |         $rows = (new Query())->select(['language'])->from($messageTable)->groupBy('language')->all($db); | ||||||
|  |         foreach ($rows as $row) { | ||||||
|  |             $currentLanguages[] = $row['language']; | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         foreach ($currentLanguages as $currentLanguage) { | ||||||
|  |           if (!in_array($currentLanguage, $languages)) { | ||||||
|  |             $deleted=$db->createCommand()->delete($messageTable, "language=:language", [ | ||||||
|  |                 'language'=>$currentLanguage, | ||||||
|  |             ])->execute(); | ||||||
|  |             $droppedLanguages[] = "removed {$deleted} rows for $currentLanguage"; | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |          | ||||||
|  |         $this->stdout($droppedLanguages ? implode(", ", $droppedLanguages).".\n" : "Nothing to do.\n"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -21,7 +21,7 @@ class MaskedInputAsset extends AssetBundle | |||||||
| { | { | ||||||
|     public $sourcePath = '@bower/inputmask/dist'; |     public $sourcePath = '@bower/inputmask/dist'; | ||||||
|     public $js = [ |     public $js = [ | ||||||
|         'jquery.inputmask.bundle.js', |         'jquery.inputmask.js', | ||||||
|     ]; |     ]; | ||||||
|     public $depends = [ |     public $depends = [ | ||||||
|         'yii\web\YiiAsset', |         'yii\web\YiiAsset', | ||||||
|  | |||||||
| @ -48,3 +48,15 @@ | |||||||
|         <div class="help-block"></div> |         <div class="help-block"></div> | ||||||
|     </div> |     </div> | ||||||
| </form> | </form> | ||||||
|  | <form id="w3"> | ||||||
|  |     <div class="form-group field-test-text2 required"> | ||||||
|  |         <label class="control-label" for="test-text2">Test text</label> | ||||||
|  |         <input type="text" id="test-text2" class="form-control" name="Test[text2]" aria-required="true"> | ||||||
|  |         <div class="help-block"></div> | ||||||
|  |     </div> | ||||||
|  |     <div class="form-group field-test-text3 required"> | ||||||
|  |         <label class="control-label" for="test-text3">Test text</label> | ||||||
|  |         <input type="text" id="test-text3" class="form-control" name="Test[text3]" aria-required="true"> | ||||||
|  |         <div class="help-block"></div> | ||||||
|  |     </div> | ||||||
|  | </form> | ||||||
|  | |||||||
| @ -27,6 +27,21 @@ describe('yii.activeForm', function () { | |||||||
|         var script = new vm.Script(code); |         var script = new vm.Script(code); | ||||||
|         var context = new vm.createContext({window: window, document: window.document, yii: yii}); |         var context = new vm.createContext({window: window, document: window.document, yii: yii}); | ||||||
|         script.runInContext(context); |         script.runInContext(context); | ||||||
|  |         /** This is a workaround for a jsdom issue, that prevents :hidden and :visible from working as expected. | ||||||
|  |          * @see https://github.com/jsdom/jsdom/issues/1048 */ | ||||||
|  |         context.window.Element.prototype.getClientRects = function () { | ||||||
|  |             var node = this; | ||||||
|  |             while(node) { | ||||||
|  |                 if(node === document) { | ||||||
|  |                     break; | ||||||
|  |                 } | ||||||
|  |                 if (!node.style || node.style.display === 'none' || node.style.visibility === 'hidden') { | ||||||
|  |                     return []; | ||||||
|  |                 } | ||||||
|  |                 node = node.parentNode; | ||||||
|  |             } | ||||||
|  |             return [{width: 100, height: 100}]; | ||||||
|  |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     var activeFormHtml = fs.readFileSync('tests/js/data/yii.activeForm.html', 'utf-8'); |     var activeFormHtml = fs.readFileSync('tests/js/data/yii.activeForm.html', 'utf-8'); | ||||||
| @ -117,6 +132,60 @@ describe('yii.activeForm', function () { | |||||||
|                 assert.isFalse($activeForm.data('yiiActiveForm').validated); |                 assert.isFalse($activeForm.data('yiiActiveForm').validated); | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         describe('with ajax validation', function () { | ||||||
|  |             describe('with rapid validation of multiple fields', function () { | ||||||
|  |                 it('should cancel overlapping ajax requests and not display outdated validation results', function () { | ||||||
|  |                     $activeForm = $('#w3'); | ||||||
|  |                     $activeForm.yiiActiveForm([{ | ||||||
|  |                         id: 'test-text2', | ||||||
|  |                         input: '#test-text2', | ||||||
|  |                         container: '.field-test-text2', | ||||||
|  |                         enableAjaxValidation: true | ||||||
|  |                     }, { | ||||||
|  |                         id: 'test-text3', | ||||||
|  |                         input: '#test-text3', | ||||||
|  |                         container: '.field-test-text3', | ||||||
|  |                         enableAjaxValidation: true | ||||||
|  |                     }], { | ||||||
|  |                         validationUrl: '' | ||||||
|  |                     }); | ||||||
|  |  | ||||||
|  |                     let requests = []; | ||||||
|  |                     function fakeAjax(object) { | ||||||
|  |                         const request = { | ||||||
|  |                             jqXHR: { | ||||||
|  |                                 abort: function () { | ||||||
|  |                                     request.aborted = true; | ||||||
|  |                                 } | ||||||
|  |                             }, | ||||||
|  |                             aborted: false, | ||||||
|  |                             respond: function (response) { | ||||||
|  |                                 if (this.aborted) { | ||||||
|  |                                     return; | ||||||
|  |                                 } | ||||||
|  |                                 object.success(response); | ||||||
|  |                                 object.complete(this.jqXHR, ''); | ||||||
|  |                             } | ||||||
|  |                         }; | ||||||
|  |                         requests.push(request); | ||||||
|  |                         object.beforeSend(request.jqXHR, ''); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|  |                     const ajaxStub = sinon.stub($, 'ajax', fakeAjax); | ||||||
|  |                     $activeForm.yiiActiveForm('validateAttribute', 'test-text2'); | ||||||
|  |                     assert.isTrue(requests.length === 1); | ||||||
|  |                     $activeForm.yiiActiveForm('validateAttribute', 'test-text3'); | ||||||
|  |                     // When validateAttribute was called on text2, its value was valid. | ||||||
|  |                     // The value of text3 wasn't. | ||||||
|  |                     requests[0].respond({'test-text3': ['Field cannot be empty']}); | ||||||
|  |                     // When validateAttribute was called on text3, its value was valid. | ||||||
|  |                     requests[1].respond([]); | ||||||
|  |                     assert.isTrue($activeForm.find('.field-test-text3').hasClass('has-success')); | ||||||
|  |                     ajaxStub.restore(); | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  |         }) | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     describe('resetForm method', function () { |     describe('resetForm method', function () { | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 xicond
					xicond