From 029a8b9586888cff44f621b7e4299eddd0f6528d Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 26 Jan 2017 17:20:08 +1300 Subject: [PATCH] API Substitute Zend_Currency with NumberFormatter based solution API Substitute Zend_Locale with Locale / NumberFormatter API Substitute Zend_Date with IntlDateFormatter API Added DBTIme::Nice12, FormatFromSettings API Added Short() method to DBDate / DBTime / DBDatetime API Add Date::getTimestamp() API Added setSubmittedValue api for FormField API Add second arg to base FormField::setValue() API Major refactor of i18n into component data parts API Implement Resettable interface to reset objects between tests ENHANCEMENT Changed DBField::create_field return type to `static` to support better type hinting ENHANCEMENT i18nTextCollector supports __CLASS__ --- _config/i18n.yml | 10 +- admin/client/dist/js/bundle.js | 631 +- admin/client/src/legacy/DateField.js | 61 +- admin/code/CMSMenu.php | 3 +- admin/code/LeftAndMain.php | 3 +- .../SilverStripe/Forms/DatetimeField.ss | 2 +- .../03_Forms/Field_types/02_DateField.md | 2 +- docs/en/04_Changelogs/4.0.0.md | 85 +- src/Control/CookieJar.php | 2 +- src/Control/Director.php | 2 +- src/Control/Email/Email.php | 2 +- src/Control/FlushRequestFilter.php | 3 +- src/Core/Manifest/ClassManifest.php | 27 + src/Core/Resettable.php | 17 + src/Dev/SapphireTest.php | 15 +- src/Forms/CheckboxField.php | 4 +- src/Forms/CountryDropdownField.php | 29 +- src/Forms/CurrencyField.php | 11 +- src/Forms/DateField.php | 935 ++- src/Forms/DateField_View_JQuery.php | 93 +- src/Forms/DatetimeField.php | 420 +- src/Forms/Form.php | 29 +- src/Forms/FormField.php | 27 +- src/Forms/GridField/GridState.php | 6 +- src/Forms/HTMLEditor/HTMLEditorConfig.php | 6 +- src/Forms/HTMLEditor/HTMLEditorField.php | 2 +- src/Forms/HTMLEditor/TinyMCEConfig.php | 184 + src/Forms/LiteralField.php | 4 +- src/Forms/MemberDatetimeOptionsetField.php | 56 +- src/Forms/MoneyField.php | 198 +- src/Forms/MultiSelectField.php | 6 +- src/Forms/NumericField.php | 269 +- src/Forms/PhoneNumberField.php | 2 +- src/Forms/SelectionGroup_Item.php | 2 +- src/Forms/TimeField.php | 434 +- src/ORM/DataObject.php | 3 +- src/ORM/FieldType/DBDate.php | 435 +- src/ORM/FieldType/DBDatetime.php | 180 +- src/ORM/FieldType/DBDecimal.php | 10 +- src/ORM/FieldType/DBField.php | 2 +- src/ORM/FieldType/DBFloat.php | 4 +- src/ORM/FieldType/DBInt.php | 6 +- src/ORM/FieldType/DBLocale.php | 17 +- src/ORM/FieldType/DBMoney.php | 195 +- src/ORM/FieldType/DBString.php | 13 +- src/ORM/FieldType/DBTime.php | 200 +- src/ORM/Hierarchy/Hierarchy.php | 3 +- src/ORM/Versioning/Versioned.php | 3 +- src/Security/Member.php | 246 +- src/View/Requirements_Backend.php | 4 +- src/i18n/Data/Intl/IntlLocales.php | 1622 +++++ src/i18n/Data/Locales.php | 101 + src/i18n/Data/Sources.php | 185 + .../Messages/Symfony/ModuleYamlLoader.php | 32 +- .../Symfony/SymfonyMessageProvider.php | 4 +- src/i18n/Messages/YamlWriter.php | 2 +- src/i18n/TextCollection/i18nTextCollector.php | 66 +- src/i18n/i18n.php | 2205 +----- templates/SilverStripe/Forms/DatetimeField.ss | 2 +- tests/php/Dev/CSVParserTest.php | 22 +- .../csv/LargeListOfPlayers.csv | 20 +- .../php/Dev/CsvBulkLoaderTest/csv/Players.csv | 5 +- .../csv/PlayersWithHeader.csv | 8 +- .../CsvBulkLoaderTest/csv/PlayersWithId.csv | 4 +- .../csv/PlayersWithIdUpdated.csv | 4 +- tests/php/Forms/DateFieldTest.php | 170 +- tests/php/Forms/DatetimeFieldTest.php | 139 +- tests/php/Forms/FormTest.php | 4 +- tests/php/Forms/GridField/GridFieldTest.php | 4 +- .../MemberDatetimeOptionsetFieldTest.php | 85 +- tests/php/Forms/MoneyFieldTest.php | 104 +- tests/php/Forms/NumericFieldTest.php | 342 +- tests/php/Forms/TimeFieldTest.php | 54 +- tests/php/ORM/DBDateTest.php | 231 +- tests/php/ORM/DBDatetimeTest.php | 145 +- tests/php/ORM/DBFieldTest.php | 199 +- tests/php/ORM/DBLocaleTest.php | 30 +- tests/php/ORM/DBMoneyTest.php | 167 +- tests/php/ORM/DBTimeTest.php | 102 + tests/php/ORM/TimeTest.php | 20 - .../php/Security/MemberAuthenticatorTest.php | 14 +- tests/php/Security/MemberTest.php | 8 +- tests/php/Security/SecurityTest.php | 2 +- tests/php/i18n/i18nTest.php | 28 +- tests/php/i18n/i18nTextCollectorTest.php | 23 + thirdparty/Zend/Currency.php | 894 --- .../Zend/Currency/CurrencyInterface.php | 39 - thirdparty/Zend/Currency/Exception.php | 37 - thirdparty/Zend/Date.php | 4957 -------------- thirdparty/Zend/Date/Cities.php | 322 - thirdparty/Zend/Date/DateObject.php | 1089 --- thirdparty/Zend/Date/Exception.php | 49 - thirdparty/Zend/Locale.php | 1111 --- thirdparty/Zend/Locale/Data.php | 1518 ----- thirdparty/Zend/Locale/Data/Translation.php | 285 - thirdparty/Zend/Locale/Data/aa.xml | 220 - thirdparty/Zend/Locale/Data/aa_DJ.xml | 25 - thirdparty/Zend/Locale/Data/aa_ER.xml | 10 - thirdparty/Zend/Locale/Data/aa_ER_SAAHO.xml | 38 - thirdparty/Zend/Locale/Data/aa_ET.xml | 9 - thirdparty/Zend/Locale/Data/af.xml | 821 --- thirdparty/Zend/Locale/Data/af_NA.xml | 74 - thirdparty/Zend/Locale/Data/af_ZA.xml | 9 - thirdparty/Zend/Locale/Data/ak.xml | 189 - thirdparty/Zend/Locale/Data/ak_GH.xml | 9 - thirdparty/Zend/Locale/Data/am.xml | 1409 ---- thirdparty/Zend/Locale/Data/am_ET.xml | 9 - thirdparty/Zend/Locale/Data/ar.xml | 3118 --------- thirdparty/Zend/Locale/Data/ar_AE.xml | 14 - thirdparty/Zend/Locale/Data/ar_BH.xml | 14 - thirdparty/Zend/Locale/Data/ar_DZ.xml | 43 - thirdparty/Zend/Locale/Data/ar_EG.xml | 9 - thirdparty/Zend/Locale/Data/ar_IQ.xml | 14 - thirdparty/Zend/Locale/Data/ar_JO.xml | 65 - thirdparty/Zend/Locale/Data/ar_KW.xml | 14 - thirdparty/Zend/Locale/Data/ar_LB.xml | 66 - thirdparty/Zend/Locale/Data/ar_LY.xml | 14 - thirdparty/Zend/Locale/Data/ar_MA.xml | 46 - thirdparty/Zend/Locale/Data/ar_OM.xml | 14 - thirdparty/Zend/Locale/Data/ar_QA.xml | 49 - thirdparty/Zend/Locale/Data/ar_SA.xml | 49 - thirdparty/Zend/Locale/Data/ar_SD.xml | 14 - thirdparty/Zend/Locale/Data/ar_SY.xml | 81 - thirdparty/Zend/Locale/Data/ar_TN.xml | 74 - thirdparty/Zend/Locale/Data/ar_YE.xml | 49 - thirdparty/Zend/Locale/Data/as.xml | 274 - thirdparty/Zend/Locale/Data/as_IN.xml | 9 - thirdparty/Zend/Locale/Data/az.xml | 2715 -------- thirdparty/Zend/Locale/Data/az_AZ.xml | 10 - thirdparty/Zend/Locale/Data/az_Cyrl.xml | 84 - thirdparty/Zend/Locale/Data/az_Cyrl_AZ.xml | 10 - thirdparty/Zend/Locale/Data/az_Latn.xml | 9 - thirdparty/Zend/Locale/Data/az_Latn_AZ.xml | 10 - thirdparty/Zend/Locale/Data/be.xml | 1090 --- thirdparty/Zend/Locale/Data/be_BY.xml | 9 - thirdparty/Zend/Locale/Data/bg.xml | 3960 ----------- thirdparty/Zend/Locale/Data/bg_BG.xml | 9 - thirdparty/Zend/Locale/Data/bn.xml | 2834 -------- thirdparty/Zend/Locale/Data/bn_BD.xml | 9 - thirdparty/Zend/Locale/Data/bn_IN.xml | 35 - thirdparty/Zend/Locale/Data/bo.xml | 440 -- thirdparty/Zend/Locale/Data/bo_CN.xml | 9 - thirdparty/Zend/Locale/Data/bo_IN.xml | 9 - thirdparty/Zend/Locale/Data/bs.xml | 329 - thirdparty/Zend/Locale/Data/bs_BA.xml | 9 - thirdparty/Zend/Locale/Data/byn.xml | 501 -- thirdparty/Zend/Locale/Data/byn_ER.xml | 9 - thirdparty/Zend/Locale/Data/ca.xml | 3252 --------- thirdparty/Zend/Locale/Data/ca_ES.xml | 9 - thirdparty/Zend/Locale/Data/cch.xml | 188 - thirdparty/Zend/Locale/Data/cch_NG.xml | 9 - thirdparty/Zend/Locale/Data/characters.xml | 489 -- thirdparty/Zend/Locale/Data/cop.xml | 197 - thirdparty/Zend/Locale/Data/cs.xml | 2439 ------- thirdparty/Zend/Locale/Data/cs_CZ.xml | 9 - thirdparty/Zend/Locale/Data/cy.xml | 782 --- thirdparty/Zend/Locale/Data/cy_GB.xml | 9 - thirdparty/Zend/Locale/Data/da.xml | 3129 --------- thirdparty/Zend/Locale/Data/da_DK.xml | 9 - thirdparty/Zend/Locale/Data/de.xml | 2948 -------- thirdparty/Zend/Locale/Data/de_AT.xml | 69 - thirdparty/Zend/Locale/Data/de_BE.xml | 114 - thirdparty/Zend/Locale/Data/de_CH.xml | 64 - thirdparty/Zend/Locale/Data/de_DE.xml | 9 - thirdparty/Zend/Locale/Data/de_LI.xml | 22 - thirdparty/Zend/Locale/Data/de_LU.xml | 18 - thirdparty/Zend/Locale/Data/dv.xml | 225 - thirdparty/Zend/Locale/Data/dv_MV.xml | 9 - thirdparty/Zend/Locale/Data/dz.xml | 406 -- thirdparty/Zend/Locale/Data/dz_BT.xml | 9 - thirdparty/Zend/Locale/Data/ee.xml | 192 - thirdparty/Zend/Locale/Data/ee_GH.xml | 9 - thirdparty/Zend/Locale/Data/ee_TG.xml | 9 - thirdparty/Zend/Locale/Data/el.xml | 4123 ------------ thirdparty/Zend/Locale/Data/el_CY.xml | 19 - thirdparty/Zend/Locale/Data/el_GR.xml | 9 - thirdparty/Zend/Locale/Data/el_POLYTON.xml | 541 -- thirdparty/Zend/Locale/Data/en.xml | 4106 ------------ thirdparty/Zend/Locale/Data/en_AS.xml | 9 - thirdparty/Zend/Locale/Data/en_AU.xml | 160 - thirdparty/Zend/Locale/Data/en_BE.xml | 158 - thirdparty/Zend/Locale/Data/en_BW.xml | 130 - thirdparty/Zend/Locale/Data/en_BZ.xml | 160 - thirdparty/Zend/Locale/Data/en_CA.xml | 126 - thirdparty/Zend/Locale/Data/en_Dsrt.xml | 1024 --- thirdparty/Zend/Locale/Data/en_Dsrt_US.xml | 10 - thirdparty/Zend/Locale/Data/en_GB.xml | 169 - thirdparty/Zend/Locale/Data/en_GU.xml | 9 - thirdparty/Zend/Locale/Data/en_HK.xml | 54 - thirdparty/Zend/Locale/Data/en_IE.xml | 149 - thirdparty/Zend/Locale/Data/en_IN.xml | 145 - thirdparty/Zend/Locale/Data/en_JM.xml | 26 - thirdparty/Zend/Locale/Data/en_MH.xml | 9 - thirdparty/Zend/Locale/Data/en_MP.xml | 9 - thirdparty/Zend/Locale/Data/en_MT.xml | 154 - thirdparty/Zend/Locale/Data/en_NA.xml | 26 - thirdparty/Zend/Locale/Data/en_NZ.xml | 157 - thirdparty/Zend/Locale/Data/en_PH.xml | 16 - thirdparty/Zend/Locale/Data/en_PK.xml | 153 - thirdparty/Zend/Locale/Data/en_SG.xml | 47 - thirdparty/Zend/Locale/Data/en_Shaw.xml | 185 - thirdparty/Zend/Locale/Data/en_TT.xml | 26 - thirdparty/Zend/Locale/Data/en_UM.xml | 9 - thirdparty/Zend/Locale/Data/en_US.xml | 9 - thirdparty/Zend/Locale/Data/en_US_POSIX.xml | 44 - thirdparty/Zend/Locale/Data/en_VI.xml | 9 - thirdparty/Zend/Locale/Data/en_ZA.xml | 152 - thirdparty/Zend/Locale/Data/en_ZW.xml | 144 - thirdparty/Zend/Locale/Data/eo.xml | 632 -- thirdparty/Zend/Locale/Data/es.xml | 3714 ---------- thirdparty/Zend/Locale/Data/es_AR.xml | 129 - thirdparty/Zend/Locale/Data/es_BO.xml | 9 - thirdparty/Zend/Locale/Data/es_CL.xml | 159 - thirdparty/Zend/Locale/Data/es_CO.xml | 127 - thirdparty/Zend/Locale/Data/es_CR.xml | 9 - thirdparty/Zend/Locale/Data/es_DO.xml | 15 - thirdparty/Zend/Locale/Data/es_EC.xml | 122 - thirdparty/Zend/Locale/Data/es_ES.xml | 9 - thirdparty/Zend/Locale/Data/es_GT.xml | 114 - thirdparty/Zend/Locale/Data/es_HN.xml | 114 - thirdparty/Zend/Locale/Data/es_MX.xml | 20 - thirdparty/Zend/Locale/Data/es_NI.xml | 15 - thirdparty/Zend/Locale/Data/es_PA.xml | 109 - thirdparty/Zend/Locale/Data/es_PE.xml | 111 - thirdparty/Zend/Locale/Data/es_PR.xml | 114 - thirdparty/Zend/Locale/Data/es_PY.xml | 18 - thirdparty/Zend/Locale/Data/es_SV.xml | 15 - thirdparty/Zend/Locale/Data/es_US.xml | 147 - thirdparty/Zend/Locale/Data/es_UY.xml | 23 - thirdparty/Zend/Locale/Data/es_VE.xml | 18 - thirdparty/Zend/Locale/Data/et.xml | 1983 ------ thirdparty/Zend/Locale/Data/et_EE.xml | 9 - thirdparty/Zend/Locale/Data/eu.xml | 1003 --- thirdparty/Zend/Locale/Data/eu_ES.xml | 9 - thirdparty/Zend/Locale/Data/fa.xml | 3309 --------- thirdparty/Zend/Locale/Data/fa_AF.xml | 304 - thirdparty/Zend/Locale/Data/fa_IR.xml | 9 - thirdparty/Zend/Locale/Data/fi.xml | 3828 ----------- thirdparty/Zend/Locale/Data/fi_FI.xml | 9 - thirdparty/Zend/Locale/Data/fil.xml | 1009 --- thirdparty/Zend/Locale/Data/fil_PH.xml | 9 - thirdparty/Zend/Locale/Data/fo.xml | 794 --- thirdparty/Zend/Locale/Data/fo_FO.xml | 9 - thirdparty/Zend/Locale/Data/fr.xml | 5457 --------------- thirdparty/Zend/Locale/Data/fr_BE.xml | 103 - thirdparty/Zend/Locale/Data/fr_CA.xml | 151 - thirdparty/Zend/Locale/Data/fr_CH.xml | 124 - thirdparty/Zend/Locale/Data/fr_FR.xml | 9 - thirdparty/Zend/Locale/Data/fr_LU.xml | 22 - thirdparty/Zend/Locale/Data/fr_MC.xml | 9 - thirdparty/Zend/Locale/Data/fr_SN.xml | 9 - thirdparty/Zend/Locale/Data/fur.xml | 1221 ---- thirdparty/Zend/Locale/Data/fur_IT.xml | 9 - thirdparty/Zend/Locale/Data/ga.xml | 1501 ----- thirdparty/Zend/Locale/Data/ga_IE.xml | 9 - thirdparty/Zend/Locale/Data/gaa.xml | 193 - thirdparty/Zend/Locale/Data/gaa_GH.xml | 9 - thirdparty/Zend/Locale/Data/gez.xml | 503 -- thirdparty/Zend/Locale/Data/gez_ER.xml | 9 - thirdparty/Zend/Locale/Data/gez_ET.xml | 9 - thirdparty/Zend/Locale/Data/gl.xml | 1361 ---- thirdparty/Zend/Locale/Data/gl_ES.xml | 9 - thirdparty/Zend/Locale/Data/gsw.xml | 2918 -------- thirdparty/Zend/Locale/Data/gsw_CH.xml | 9 - thirdparty/Zend/Locale/Data/gu.xml | 1160 ---- thirdparty/Zend/Locale/Data/gu_IN.xml | 9 - thirdparty/Zend/Locale/Data/gv.xml | 200 - thirdparty/Zend/Locale/Data/gv_GB.xml | 9 - thirdparty/Zend/Locale/Data/ha.xml | 370 - thirdparty/Zend/Locale/Data/ha_Arab.xml | 94 - thirdparty/Zend/Locale/Data/ha_Arab_NG.xml | 10 - thirdparty/Zend/Locale/Data/ha_Arab_SD.xml | 10 - thirdparty/Zend/Locale/Data/ha_GH.xml | 10 - thirdparty/Zend/Locale/Data/ha_Latn.xml | 9 - thirdparty/Zend/Locale/Data/ha_Latn_GH.xml | 10 - thirdparty/Zend/Locale/Data/ha_Latn_NE.xml | 10 - thirdparty/Zend/Locale/Data/ha_Latn_NG.xml | 10 - thirdparty/Zend/Locale/Data/ha_NE.xml | 10 - thirdparty/Zend/Locale/Data/ha_NG.xml | 10 - thirdparty/Zend/Locale/Data/ha_SD.xml | 10 - thirdparty/Zend/Locale/Data/haw.xml | 218 - thirdparty/Zend/Locale/Data/haw_US.xml | 9 - thirdparty/Zend/Locale/Data/he.xml | 2757 -------- thirdparty/Zend/Locale/Data/he_IL.xml | 9 - thirdparty/Zend/Locale/Data/hi.xml | 3343 --------- thirdparty/Zend/Locale/Data/hi_IN.xml | 9 - thirdparty/Zend/Locale/Data/hr.xml | 3703 ---------- thirdparty/Zend/Locale/Data/hr_HR.xml | 9 - thirdparty/Zend/Locale/Data/hu.xml | 3745 ----------- thirdparty/Zend/Locale/Data/hu_HU.xml | 9 - thirdparty/Zend/Locale/Data/hy.xml | 593 -- thirdparty/Zend/Locale/Data/hy_AM.xml | 9 - thirdparty/Zend/Locale/Data/hy_AM_REVISED.xml | 39 - thirdparty/Zend/Locale/Data/ia.xml | 552 -- thirdparty/Zend/Locale/Data/id.xml | 1334 ---- thirdparty/Zend/Locale/Data/id_ID.xml | 9 - thirdparty/Zend/Locale/Data/ig.xml | 191 - thirdparty/Zend/Locale/Data/ig_NG.xml | 9 - thirdparty/Zend/Locale/Data/ii.xml | 254 - thirdparty/Zend/Locale/Data/ii_CN.xml | 9 - thirdparty/Zend/Locale/Data/in.xml | 9 - thirdparty/Zend/Locale/Data/is.xml | 2179 ------ thirdparty/Zend/Locale/Data/is_IS.xml | 9 - thirdparty/Zend/Locale/Data/it.xml | 3002 --------- thirdparty/Zend/Locale/Data/it_CH.xml | 116 - thirdparty/Zend/Locale/Data/it_IT.xml | 9 - thirdparty/Zend/Locale/Data/iu.xml | 181 - thirdparty/Zend/Locale/Data/iw.xml | 9 - thirdparty/Zend/Locale/Data/ja.xml | 3798 ----------- thirdparty/Zend/Locale/Data/ja_JP.xml | 9 - thirdparty/Zend/Locale/Data/ka.xml | 2157 ------ thirdparty/Zend/Locale/Data/ka_GE.xml | 9 - thirdparty/Zend/Locale/Data/kaj.xml | 188 - thirdparty/Zend/Locale/Data/kaj_NG.xml | 9 - thirdparty/Zend/Locale/Data/kam.xml | 187 - thirdparty/Zend/Locale/Data/kam_KE.xml | 9 - thirdparty/Zend/Locale/Data/kcg.xml | 188 - thirdparty/Zend/Locale/Data/kcg_NG.xml | 9 - thirdparty/Zend/Locale/Data/kfo.xml | 188 - thirdparty/Zend/Locale/Data/kfo_CI.xml | 9 - thirdparty/Zend/Locale/Data/kk.xml | 388 -- thirdparty/Zend/Locale/Data/kk_Cyrl.xml | 9 - thirdparty/Zend/Locale/Data/kk_Cyrl_KZ.xml | 10 - thirdparty/Zend/Locale/Data/kk_KZ.xml | 10 - thirdparty/Zend/Locale/Data/kl.xml | 207 - thirdparty/Zend/Locale/Data/kl_GL.xml | 9 - thirdparty/Zend/Locale/Data/km.xml | 667 -- thirdparty/Zend/Locale/Data/km_KH.xml | 9 - thirdparty/Zend/Locale/Data/kn.xml | 1157 ---- thirdparty/Zend/Locale/Data/kn_IN.xml | 9 - thirdparty/Zend/Locale/Data/ko.xml | 3848 ----------- thirdparty/Zend/Locale/Data/ko_KR.xml | 9 - thirdparty/Zend/Locale/Data/kok.xml | 363 - thirdparty/Zend/Locale/Data/kok_IN.xml | 9 - thirdparty/Zend/Locale/Data/kpe.xml | 183 - thirdparty/Zend/Locale/Data/kpe_GN.xml | 16 - thirdparty/Zend/Locale/Data/kpe_LR.xml | 9 - thirdparty/Zend/Locale/Data/ku.xml | 158 - thirdparty/Zend/Locale/Data/ku_Arab.xml | 9 - thirdparty/Zend/Locale/Data/ku_Arab_IQ.xml | 10 - thirdparty/Zend/Locale/Data/ku_Arab_IR.xml | 10 - thirdparty/Zend/Locale/Data/ku_Arab_SY.xml | 10 - thirdparty/Zend/Locale/Data/ku_IQ.xml | 10 - thirdparty/Zend/Locale/Data/ku_IR.xml | 10 - thirdparty/Zend/Locale/Data/ku_Latn.xml | 189 - thirdparty/Zend/Locale/Data/ku_Latn_TR.xml | 10 - thirdparty/Zend/Locale/Data/ku_SY.xml | 10 - thirdparty/Zend/Locale/Data/ku_TR.xml | 10 - thirdparty/Zend/Locale/Data/kw.xml | 200 - thirdparty/Zend/Locale/Data/kw_GB.xml | 9 - thirdparty/Zend/Locale/Data/ky.xml | 275 - thirdparty/Zend/Locale/Data/ky_KG.xml | 9 - thirdparty/Zend/Locale/Data/lc.xml | 4106 ------------ thirdparty/Zend/Locale/Data/lc_XX.xml | 9 - thirdparty/Zend/Locale/Data/likelySubtags.xml | 462 -- thirdparty/Zend/Locale/Data/ln.xml | 485 -- thirdparty/Zend/Locale/Data/ln_CD.xml | 9 - thirdparty/Zend/Locale/Data/ln_CG.xml | 9 - thirdparty/Zend/Locale/Data/lo.xml | 623 -- thirdparty/Zend/Locale/Data/lo_LA.xml | 9 - thirdparty/Zend/Locale/Data/lt.xml | 3327 --------- thirdparty/Zend/Locale/Data/lt_LT.xml | 9 - thirdparty/Zend/Locale/Data/lv.xml | 2986 --------- thirdparty/Zend/Locale/Data/lv_LV.xml | 9 - thirdparty/Zend/Locale/Data/metazoneInfo.xml | 1403 ---- thirdparty/Zend/Locale/Data/mi.xml | 51 - thirdparty/Zend/Locale/Data/mi_NZ.xml | 10 - thirdparty/Zend/Locale/Data/mk.xml | 2321 ------- thirdparty/Zend/Locale/Data/mk_MK.xml | 9 - thirdparty/Zend/Locale/Data/ml.xml | 4423 ------------ thirdparty/Zend/Locale/Data/ml_IN.xml | 9 - thirdparty/Zend/Locale/Data/mn.xml | 315 - thirdparty/Zend/Locale/Data/mn_CN.xml | 10 - thirdparty/Zend/Locale/Data/mn_Cyrl.xml | 9 - thirdparty/Zend/Locale/Data/mn_Cyrl_MN.xml | 10 - thirdparty/Zend/Locale/Data/mn_MN.xml | 10 - thirdparty/Zend/Locale/Data/mn_Mong.xml | 16 - thirdparty/Zend/Locale/Data/mn_Mong_CN.xml | 10 - thirdparty/Zend/Locale/Data/mo.xml | 9 - thirdparty/Zend/Locale/Data/mr.xml | 1191 ---- thirdparty/Zend/Locale/Data/mr_IN.xml | 9 - thirdparty/Zend/Locale/Data/ms.xml | 557 -- thirdparty/Zend/Locale/Data/ms_BN.xml | 134 - thirdparty/Zend/Locale/Data/ms_MY.xml | 9 - thirdparty/Zend/Locale/Data/mt.xml | 1140 ---- thirdparty/Zend/Locale/Data/mt_MT.xml | 9 - thirdparty/Zend/Locale/Data/my.xml | 1580 ----- thirdparty/Zend/Locale/Data/my_MM.xml | 9 - thirdparty/Zend/Locale/Data/nb.xml | 3081 --------- thirdparty/Zend/Locale/Data/nb_NO.xml | 9 - thirdparty/Zend/Locale/Data/nds.xml | 1148 ---- thirdparty/Zend/Locale/Data/nds_DE.xml | 9 - thirdparty/Zend/Locale/Data/ne.xml | 1383 ---- thirdparty/Zend/Locale/Data/ne_IN.xml | 9 - thirdparty/Zend/Locale/Data/ne_NP.xml | 9 - thirdparty/Zend/Locale/Data/nl.xml | 3797 ----------- thirdparty/Zend/Locale/Data/nl_BE.xml | 80 - thirdparty/Zend/Locale/Data/nl_NL.xml | 9 - thirdparty/Zend/Locale/Data/nn.xml | 2824 -------- thirdparty/Zend/Locale/Data/nn_NO.xml | 9 - thirdparty/Zend/Locale/Data/no.xml | 9 - thirdparty/Zend/Locale/Data/nr.xml | 226 - thirdparty/Zend/Locale/Data/nr_ZA.xml | 9 - thirdparty/Zend/Locale/Data/nso.xml | 228 - thirdparty/Zend/Locale/Data/nso_ZA.xml | 9 - .../Zend/Locale/Data/numberingSystems.xml | 41 - thirdparty/Zend/Locale/Data/ny.xml | 188 - thirdparty/Zend/Locale/Data/ny_MW.xml | 9 - thirdparty/Zend/Locale/Data/oc.xml | 279 - thirdparty/Zend/Locale/Data/oc_FR.xml | 9 - thirdparty/Zend/Locale/Data/om.xml | 327 - thirdparty/Zend/Locale/Data/om_ET.xml | 9 - thirdparty/Zend/Locale/Data/om_KE.xml | 9 - thirdparty/Zend/Locale/Data/or.xml | 1141 ---- thirdparty/Zend/Locale/Data/or_IN.xml | 9 - thirdparty/Zend/Locale/Data/pa.xml | 309 - thirdparty/Zend/Locale/Data/pa_Arab.xml | 133 - thirdparty/Zend/Locale/Data/pa_Arab_PK.xml | 10 - thirdparty/Zend/Locale/Data/pa_Guru.xml | 9 - thirdparty/Zend/Locale/Data/pa_Guru_IN.xml | 10 - thirdparty/Zend/Locale/Data/pa_IN.xml | 10 - thirdparty/Zend/Locale/Data/pa_PK.xml | 10 - thirdparty/Zend/Locale/Data/pl.xml | 3095 --------- thirdparty/Zend/Locale/Data/pl_PL.xml | 9 - .../Zend/Locale/Data/postalCodeData.xml | 165 - thirdparty/Zend/Locale/Data/ps.xml | 416 -- thirdparty/Zend/Locale/Data/ps_AF.xml | 9 - thirdparty/Zend/Locale/Data/pt.xml | 4337 ------------ thirdparty/Zend/Locale/Data/pt_BR.xml | 9 - thirdparty/Zend/Locale/Data/pt_PT.xml | 1865 ------ thirdparty/Zend/Locale/Data/ro.xml | 2478 ------- thirdparty/Zend/Locale/Data/ro_MD.xml | 9 - thirdparty/Zend/Locale/Data/ro_RO.xml | 9 - thirdparty/Zend/Locale/Data/root.xml | 3178 --------- thirdparty/Zend/Locale/Data/ru.xml | 3949 ----------- thirdparty/Zend/Locale/Data/ru_RU.xml | 9 - thirdparty/Zend/Locale/Data/ru_UA.xml | 116 - thirdparty/Zend/Locale/Data/rw.xml | 305 - thirdparty/Zend/Locale/Data/rw_RW.xml | 9 - thirdparty/Zend/Locale/Data/sa.xml | 150 - thirdparty/Zend/Locale/Data/sa_IN.xml | 9 - thirdparty/Zend/Locale/Data/se.xml | 740 -- thirdparty/Zend/Locale/Data/se_FI.xml | 246 - thirdparty/Zend/Locale/Data/se_NO.xml | 9 - thirdparty/Zend/Locale/Data/sh.xml | 9 - thirdparty/Zend/Locale/Data/sh_BA.xml | 10 - thirdparty/Zend/Locale/Data/sh_CS.xml | 10 - thirdparty/Zend/Locale/Data/sh_YU.xml | 10 - thirdparty/Zend/Locale/Data/si.xml | 241 - thirdparty/Zend/Locale/Data/si_LK.xml | 9 - thirdparty/Zend/Locale/Data/sid.xml | 248 - thirdparty/Zend/Locale/Data/sid_ET.xml | 9 - thirdparty/Zend/Locale/Data/sk.xml | 1899 ------ thirdparty/Zend/Locale/Data/sk_SK.xml | 9 - thirdparty/Zend/Locale/Data/sl.xml | 2543 ------- thirdparty/Zend/Locale/Data/sl_SI.xml | 9 - thirdparty/Zend/Locale/Data/so.xml | 559 -- thirdparty/Zend/Locale/Data/so_DJ.xml | 9 - thirdparty/Zend/Locale/Data/so_ET.xml | 9 - thirdparty/Zend/Locale/Data/so_KE.xml | 9 - thirdparty/Zend/Locale/Data/so_SO.xml | 9 - thirdparty/Zend/Locale/Data/sq.xml | 656 -- thirdparty/Zend/Locale/Data/sq_AL.xml | 9 - thirdparty/Zend/Locale/Data/sr.xml | 5948 ----------------- thirdparty/Zend/Locale/Data/sr_BA.xml | 10 - thirdparty/Zend/Locale/Data/sr_CS.xml | 10 - thirdparty/Zend/Locale/Data/sr_Cyrl.xml | 9 - thirdparty/Zend/Locale/Data/sr_Cyrl_BA.xml | 103 - thirdparty/Zend/Locale/Data/sr_Cyrl_CS.xml | 11 - thirdparty/Zend/Locale/Data/sr_Cyrl_ME.xml | 10 - thirdparty/Zend/Locale/Data/sr_Cyrl_RS.xml | 10 - thirdparty/Zend/Locale/Data/sr_Cyrl_YU.xml | 11 - thirdparty/Zend/Locale/Data/sr_Latn.xml | 5378 --------------- thirdparty/Zend/Locale/Data/sr_Latn_BA.xml | 10 - thirdparty/Zend/Locale/Data/sr_Latn_CS.xml | 11 - thirdparty/Zend/Locale/Data/sr_Latn_ME.xml | 54 - thirdparty/Zend/Locale/Data/sr_Latn_RS.xml | 10 - thirdparty/Zend/Locale/Data/sr_Latn_YU.xml | 11 - thirdparty/Zend/Locale/Data/sr_ME.xml | 10 - thirdparty/Zend/Locale/Data/sr_RS.xml | 10 - thirdparty/Zend/Locale/Data/sr_YU.xml | 10 - thirdparty/Zend/Locale/Data/ss.xml | 230 - thirdparty/Zend/Locale/Data/ss_SZ.xml | 10 - thirdparty/Zend/Locale/Data/ss_ZA.xml | 9 - thirdparty/Zend/Locale/Data/st.xml | 320 - thirdparty/Zend/Locale/Data/st_LS.xml | 16 - thirdparty/Zend/Locale/Data/st_ZA.xml | 9 - .../Zend/Locale/Data/supplementalData.xml | 5359 --------------- thirdparty/Zend/Locale/Data/sv.xml | 4722 ------------- thirdparty/Zend/Locale/Data/sv_FI.xml | 47 - thirdparty/Zend/Locale/Data/sv_SE.xml | 9 - thirdparty/Zend/Locale/Data/sw.xml | 399 -- thirdparty/Zend/Locale/Data/sw_KE.xml | 18 - thirdparty/Zend/Locale/Data/sw_TZ.xml | 9 - thirdparty/Zend/Locale/Data/syr.xml | 128 - thirdparty/Zend/Locale/Data/syr_SY.xml | 9 - thirdparty/Zend/Locale/Data/ta.xml | 1190 ---- thirdparty/Zend/Locale/Data/ta_IN.xml | 9 - thirdparty/Zend/Locale/Data/te.xml | 1154 ---- thirdparty/Zend/Locale/Data/te_IN.xml | 9 - .../Zend/Locale/Data/telephoneCodeData.xml | 746 --- thirdparty/Zend/Locale/Data/tg.xml | 220 - thirdparty/Zend/Locale/Data/tg_Cyrl.xml | 9 - thirdparty/Zend/Locale/Data/tg_Cyrl_TJ.xml | 10 - thirdparty/Zend/Locale/Data/tg_TJ.xml | 10 - thirdparty/Zend/Locale/Data/th.xml | 4437 ------------ thirdparty/Zend/Locale/Data/th_TH.xml | 9 - thirdparty/Zend/Locale/Data/ti.xml | 325 - thirdparty/Zend/Locale/Data/ti_ER.xml | 123 - thirdparty/Zend/Locale/Data/ti_ET.xml | 9 - thirdparty/Zend/Locale/Data/tig.xml | 500 -- thirdparty/Zend/Locale/Data/tig_ER.xml | 9 - thirdparty/Zend/Locale/Data/tl.xml | 9 - thirdparty/Zend/Locale/Data/tn.xml | 307 - thirdparty/Zend/Locale/Data/tn_ZA.xml | 9 - thirdparty/Zend/Locale/Data/to.xml | 611 -- thirdparty/Zend/Locale/Data/to_TO.xml | 9 - thirdparty/Zend/Locale/Data/tr.xml | 3770 ----------- thirdparty/Zend/Locale/Data/tr_TR.xml | 9 - thirdparty/Zend/Locale/Data/trv.xml | 561 -- thirdparty/Zend/Locale/Data/trv_TW.xml | 9 - thirdparty/Zend/Locale/Data/ts.xml | 172 - thirdparty/Zend/Locale/Data/ts_ZA.xml | 9 - thirdparty/Zend/Locale/Data/tt.xml | 104 - thirdparty/Zend/Locale/Data/tt_RU.xml | 9 - thirdparty/Zend/Locale/Data/ug.xml | 35 - thirdparty/Zend/Locale/Data/ug_Arab.xml | 9 - thirdparty/Zend/Locale/Data/ug_Arab_CN.xml | 10 - thirdparty/Zend/Locale/Data/ug_CN.xml | 10 - thirdparty/Zend/Locale/Data/uk.xml | 3813 ----------- thirdparty/Zend/Locale/Data/uk_UA.xml | 9 - thirdparty/Zend/Locale/Data/ur.xml | 1020 --- thirdparty/Zend/Locale/Data/ur_IN.xml | 32 - thirdparty/Zend/Locale/Data/ur_PK.xml | 9 - thirdparty/Zend/Locale/Data/uz.xml | 234 - thirdparty/Zend/Locale/Data/uz_AF.xml | 10 - thirdparty/Zend/Locale/Data/uz_Arab.xml | 166 - thirdparty/Zend/Locale/Data/uz_Arab_AF.xml | 10 - thirdparty/Zend/Locale/Data/uz_Cyrl.xml | 9 - thirdparty/Zend/Locale/Data/uz_Cyrl_UZ.xml | 10 - thirdparty/Zend/Locale/Data/uz_Latn.xml | 169 - thirdparty/Zend/Locale/Data/uz_Latn_UZ.xml | 10 - thirdparty/Zend/Locale/Data/uz_UZ.xml | 10 - thirdparty/Zend/Locale/Data/ve.xml | 193 - thirdparty/Zend/Locale/Data/ve_ZA.xml | 9 - thirdparty/Zend/Locale/Data/vi.xml | 1055 --- thirdparty/Zend/Locale/Data/vi_VN.xml | 9 - thirdparty/Zend/Locale/Data/wal.xml | 348 - thirdparty/Zend/Locale/Data/wal_ET.xml | 9 - thirdparty/Zend/Locale/Data/wo.xml | 36 - thirdparty/Zend/Locale/Data/wo_Latn.xml | 9 - thirdparty/Zend/Locale/Data/wo_Latn_SN.xml | 10 - thirdparty/Zend/Locale/Data/wo_SN.xml | 9 - thirdparty/Zend/Locale/Data/xh.xml | 260 - thirdparty/Zend/Locale/Data/xh_ZA.xml | 9 - thirdparty/Zend/Locale/Data/yo.xml | 229 - thirdparty/Zend/Locale/Data/yo_NG.xml | 9 - thirdparty/Zend/Locale/Data/zh.xml | 4248 ------------ thirdparty/Zend/Locale/Data/zh_CN.xml | 10 - thirdparty/Zend/Locale/Data/zh_HK.xml | 10 - thirdparty/Zend/Locale/Data/zh_Hans.xml | 9 - thirdparty/Zend/Locale/Data/zh_Hans_CN.xml | 10 - thirdparty/Zend/Locale/Data/zh_Hans_HK.xml | 17 - thirdparty/Zend/Locale/Data/zh_Hans_MO.xml | 10 - thirdparty/Zend/Locale/Data/zh_Hans_SG.xml | 87 - thirdparty/Zend/Locale/Data/zh_Hant.xml | 3784 ----------- thirdparty/Zend/Locale/Data/zh_Hant_HK.xml | 190 - thirdparty/Zend/Locale/Data/zh_Hant_MO.xml | 116 - thirdparty/Zend/Locale/Data/zh_Hant_TW.xml | 10 - thirdparty/Zend/Locale/Data/zh_MO.xml | 10 - thirdparty/Zend/Locale/Data/zh_SG.xml | 10 - thirdparty/Zend/Locale/Data/zh_TW.xml | 10 - thirdparty/Zend/Locale/Data/zu.xml | 328 - thirdparty/Zend/Locale/Data/zu_ZA.xml | 9 - thirdparty/Zend/Locale/Exception.php | 37 - thirdparty/Zend/Locale/Format.php | 1313 ---- thirdparty/Zend/Locale/Math.php | 355 - thirdparty/Zend/Locale/Math/Exception.php | 53 - thirdparty/Zend/Locale/Math/PhpMath.php | 247 - 579 files changed, 6204 insertions(+), 255494 deletions(-) create mode 100644 src/Core/Resettable.php create mode 100644 src/i18n/Data/Intl/IntlLocales.php create mode 100644 src/i18n/Data/Locales.php create mode 100644 src/i18n/Data/Sources.php create mode 100644 tests/php/ORM/DBTimeTest.php delete mode 100644 tests/php/ORM/TimeTest.php delete mode 100644 thirdparty/Zend/Currency.php delete mode 100644 thirdparty/Zend/Currency/CurrencyInterface.php delete mode 100644 thirdparty/Zend/Currency/Exception.php delete mode 100644 thirdparty/Zend/Date.php delete mode 100644 thirdparty/Zend/Date/Cities.php delete mode 100644 thirdparty/Zend/Date/DateObject.php delete mode 100644 thirdparty/Zend/Date/Exception.php delete mode 100755 thirdparty/Zend/Locale.php delete mode 100644 thirdparty/Zend/Locale/Data.php delete mode 100644 thirdparty/Zend/Locale/Data/Translation.php delete mode 100644 thirdparty/Zend/Locale/Data/aa.xml delete mode 100644 thirdparty/Zend/Locale/Data/aa_DJ.xml delete mode 100644 thirdparty/Zend/Locale/Data/aa_ER.xml delete mode 100644 thirdparty/Zend/Locale/Data/aa_ER_SAAHO.xml delete mode 100644 thirdparty/Zend/Locale/Data/aa_ET.xml delete mode 100644 thirdparty/Zend/Locale/Data/af.xml delete mode 100644 thirdparty/Zend/Locale/Data/af_NA.xml delete mode 100644 thirdparty/Zend/Locale/Data/af_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/ak.xml delete mode 100644 thirdparty/Zend/Locale/Data/ak_GH.xml delete mode 100644 thirdparty/Zend/Locale/Data/am.xml delete mode 100644 thirdparty/Zend/Locale/Data/am_ET.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_AE.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_BH.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_DZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_EG.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_IQ.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_JO.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_KW.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_LB.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_LY.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_MA.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_OM.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_QA.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_SA.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_SD.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_SY.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_TN.xml delete mode 100644 thirdparty/Zend/Locale/Data/ar_YE.xml delete mode 100644 thirdparty/Zend/Locale/Data/as.xml delete mode 100644 thirdparty/Zend/Locale/Data/as_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/az.xml delete mode 100644 thirdparty/Zend/Locale/Data/az_AZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/az_Cyrl.xml delete mode 100644 thirdparty/Zend/Locale/Data/az_Cyrl_AZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/az_Latn.xml delete mode 100644 thirdparty/Zend/Locale/Data/az_Latn_AZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/be.xml delete mode 100644 thirdparty/Zend/Locale/Data/be_BY.xml delete mode 100644 thirdparty/Zend/Locale/Data/bg.xml delete mode 100644 thirdparty/Zend/Locale/Data/bg_BG.xml delete mode 100644 thirdparty/Zend/Locale/Data/bn.xml delete mode 100644 thirdparty/Zend/Locale/Data/bn_BD.xml delete mode 100644 thirdparty/Zend/Locale/Data/bn_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/bo.xml delete mode 100644 thirdparty/Zend/Locale/Data/bo_CN.xml delete mode 100644 thirdparty/Zend/Locale/Data/bo_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/bs.xml delete mode 100644 thirdparty/Zend/Locale/Data/bs_BA.xml delete mode 100644 thirdparty/Zend/Locale/Data/byn.xml delete mode 100644 thirdparty/Zend/Locale/Data/byn_ER.xml delete mode 100644 thirdparty/Zend/Locale/Data/ca.xml delete mode 100644 thirdparty/Zend/Locale/Data/ca_ES.xml delete mode 100644 thirdparty/Zend/Locale/Data/cch.xml delete mode 100644 thirdparty/Zend/Locale/Data/cch_NG.xml delete mode 100644 thirdparty/Zend/Locale/Data/characters.xml delete mode 100644 thirdparty/Zend/Locale/Data/cop.xml delete mode 100644 thirdparty/Zend/Locale/Data/cs.xml delete mode 100644 thirdparty/Zend/Locale/Data/cs_CZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/cy.xml delete mode 100644 thirdparty/Zend/Locale/Data/cy_GB.xml delete mode 100644 thirdparty/Zend/Locale/Data/da.xml delete mode 100644 thirdparty/Zend/Locale/Data/da_DK.xml delete mode 100644 thirdparty/Zend/Locale/Data/de.xml delete mode 100644 thirdparty/Zend/Locale/Data/de_AT.xml delete mode 100644 thirdparty/Zend/Locale/Data/de_BE.xml delete mode 100644 thirdparty/Zend/Locale/Data/de_CH.xml delete mode 100644 thirdparty/Zend/Locale/Data/de_DE.xml delete mode 100644 thirdparty/Zend/Locale/Data/de_LI.xml delete mode 100644 thirdparty/Zend/Locale/Data/de_LU.xml delete mode 100644 thirdparty/Zend/Locale/Data/dv.xml delete mode 100644 thirdparty/Zend/Locale/Data/dv_MV.xml delete mode 100644 thirdparty/Zend/Locale/Data/dz.xml delete mode 100644 thirdparty/Zend/Locale/Data/dz_BT.xml delete mode 100644 thirdparty/Zend/Locale/Data/ee.xml delete mode 100644 thirdparty/Zend/Locale/Data/ee_GH.xml delete mode 100644 thirdparty/Zend/Locale/Data/ee_TG.xml delete mode 100644 thirdparty/Zend/Locale/Data/el.xml delete mode 100644 thirdparty/Zend/Locale/Data/el_CY.xml delete mode 100644 thirdparty/Zend/Locale/Data/el_GR.xml delete mode 100644 thirdparty/Zend/Locale/Data/el_POLYTON.xml delete mode 100644 thirdparty/Zend/Locale/Data/en.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_AS.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_AU.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_BE.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_BW.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_BZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_CA.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_Dsrt.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_Dsrt_US.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_GB.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_GU.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_HK.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_IE.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_JM.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_MH.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_MP.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_MT.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_NA.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_NZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_PH.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_PK.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_SG.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_Shaw.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_TT.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_UM.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_US.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_US_POSIX.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_VI.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/en_ZW.xml delete mode 100644 thirdparty/Zend/Locale/Data/eo.xml delete mode 100644 thirdparty/Zend/Locale/Data/es.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_AR.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_BO.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_CL.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_CO.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_CR.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_DO.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_EC.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_ES.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_GT.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_HN.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_MX.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_NI.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_PA.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_PE.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_PR.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_PY.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_SV.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_US.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_UY.xml delete mode 100644 thirdparty/Zend/Locale/Data/es_VE.xml delete mode 100644 thirdparty/Zend/Locale/Data/et.xml delete mode 100644 thirdparty/Zend/Locale/Data/et_EE.xml delete mode 100644 thirdparty/Zend/Locale/Data/eu.xml delete mode 100644 thirdparty/Zend/Locale/Data/eu_ES.xml delete mode 100644 thirdparty/Zend/Locale/Data/fa.xml delete mode 100644 thirdparty/Zend/Locale/Data/fa_AF.xml delete mode 100644 thirdparty/Zend/Locale/Data/fa_IR.xml delete mode 100644 thirdparty/Zend/Locale/Data/fi.xml delete mode 100644 thirdparty/Zend/Locale/Data/fi_FI.xml delete mode 100644 thirdparty/Zend/Locale/Data/fil.xml delete mode 100644 thirdparty/Zend/Locale/Data/fil_PH.xml delete mode 100644 thirdparty/Zend/Locale/Data/fo.xml delete mode 100644 thirdparty/Zend/Locale/Data/fo_FO.xml delete mode 100644 thirdparty/Zend/Locale/Data/fr.xml delete mode 100644 thirdparty/Zend/Locale/Data/fr_BE.xml delete mode 100644 thirdparty/Zend/Locale/Data/fr_CA.xml delete mode 100644 thirdparty/Zend/Locale/Data/fr_CH.xml delete mode 100644 thirdparty/Zend/Locale/Data/fr_FR.xml delete mode 100644 thirdparty/Zend/Locale/Data/fr_LU.xml delete mode 100644 thirdparty/Zend/Locale/Data/fr_MC.xml delete mode 100644 thirdparty/Zend/Locale/Data/fr_SN.xml delete mode 100644 thirdparty/Zend/Locale/Data/fur.xml delete mode 100644 thirdparty/Zend/Locale/Data/fur_IT.xml delete mode 100644 thirdparty/Zend/Locale/Data/ga.xml delete mode 100644 thirdparty/Zend/Locale/Data/ga_IE.xml delete mode 100644 thirdparty/Zend/Locale/Data/gaa.xml delete mode 100644 thirdparty/Zend/Locale/Data/gaa_GH.xml delete mode 100644 thirdparty/Zend/Locale/Data/gez.xml delete mode 100644 thirdparty/Zend/Locale/Data/gez_ER.xml delete mode 100644 thirdparty/Zend/Locale/Data/gez_ET.xml delete mode 100644 thirdparty/Zend/Locale/Data/gl.xml delete mode 100644 thirdparty/Zend/Locale/Data/gl_ES.xml delete mode 100644 thirdparty/Zend/Locale/Data/gsw.xml delete mode 100644 thirdparty/Zend/Locale/Data/gsw_CH.xml delete mode 100644 thirdparty/Zend/Locale/Data/gu.xml delete mode 100644 thirdparty/Zend/Locale/Data/gu_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/gv.xml delete mode 100644 thirdparty/Zend/Locale/Data/gv_GB.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_Arab.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_Arab_NG.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_Arab_SD.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_GH.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_Latn.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_Latn_GH.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_Latn_NE.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_Latn_NG.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_NE.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_NG.xml delete mode 100644 thirdparty/Zend/Locale/Data/ha_SD.xml delete mode 100644 thirdparty/Zend/Locale/Data/haw.xml delete mode 100644 thirdparty/Zend/Locale/Data/haw_US.xml delete mode 100644 thirdparty/Zend/Locale/Data/he.xml delete mode 100644 thirdparty/Zend/Locale/Data/he_IL.xml delete mode 100644 thirdparty/Zend/Locale/Data/hi.xml delete mode 100644 thirdparty/Zend/Locale/Data/hi_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/hr.xml delete mode 100644 thirdparty/Zend/Locale/Data/hr_HR.xml delete mode 100644 thirdparty/Zend/Locale/Data/hu.xml delete mode 100644 thirdparty/Zend/Locale/Data/hu_HU.xml delete mode 100644 thirdparty/Zend/Locale/Data/hy.xml delete mode 100644 thirdparty/Zend/Locale/Data/hy_AM.xml delete mode 100644 thirdparty/Zend/Locale/Data/hy_AM_REVISED.xml delete mode 100644 thirdparty/Zend/Locale/Data/ia.xml delete mode 100644 thirdparty/Zend/Locale/Data/id.xml delete mode 100644 thirdparty/Zend/Locale/Data/id_ID.xml delete mode 100644 thirdparty/Zend/Locale/Data/ig.xml delete mode 100644 thirdparty/Zend/Locale/Data/ig_NG.xml delete mode 100644 thirdparty/Zend/Locale/Data/ii.xml delete mode 100644 thirdparty/Zend/Locale/Data/ii_CN.xml delete mode 100644 thirdparty/Zend/Locale/Data/in.xml delete mode 100644 thirdparty/Zend/Locale/Data/is.xml delete mode 100644 thirdparty/Zend/Locale/Data/is_IS.xml delete mode 100644 thirdparty/Zend/Locale/Data/it.xml delete mode 100644 thirdparty/Zend/Locale/Data/it_CH.xml delete mode 100644 thirdparty/Zend/Locale/Data/it_IT.xml delete mode 100644 thirdparty/Zend/Locale/Data/iu.xml delete mode 100644 thirdparty/Zend/Locale/Data/iw.xml delete mode 100644 thirdparty/Zend/Locale/Data/ja.xml delete mode 100644 thirdparty/Zend/Locale/Data/ja_JP.xml delete mode 100644 thirdparty/Zend/Locale/Data/ka.xml delete mode 100644 thirdparty/Zend/Locale/Data/ka_GE.xml delete mode 100644 thirdparty/Zend/Locale/Data/kaj.xml delete mode 100644 thirdparty/Zend/Locale/Data/kaj_NG.xml delete mode 100644 thirdparty/Zend/Locale/Data/kam.xml delete mode 100644 thirdparty/Zend/Locale/Data/kam_KE.xml delete mode 100644 thirdparty/Zend/Locale/Data/kcg.xml delete mode 100644 thirdparty/Zend/Locale/Data/kcg_NG.xml delete mode 100644 thirdparty/Zend/Locale/Data/kfo.xml delete mode 100644 thirdparty/Zend/Locale/Data/kfo_CI.xml delete mode 100644 thirdparty/Zend/Locale/Data/kk.xml delete mode 100644 thirdparty/Zend/Locale/Data/kk_Cyrl.xml delete mode 100644 thirdparty/Zend/Locale/Data/kk_Cyrl_KZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/kk_KZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/kl.xml delete mode 100644 thirdparty/Zend/Locale/Data/kl_GL.xml delete mode 100644 thirdparty/Zend/Locale/Data/km.xml delete mode 100644 thirdparty/Zend/Locale/Data/km_KH.xml delete mode 100644 thirdparty/Zend/Locale/Data/kn.xml delete mode 100644 thirdparty/Zend/Locale/Data/kn_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/ko.xml delete mode 100644 thirdparty/Zend/Locale/Data/ko_KR.xml delete mode 100644 thirdparty/Zend/Locale/Data/kok.xml delete mode 100644 thirdparty/Zend/Locale/Data/kok_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/kpe.xml delete mode 100644 thirdparty/Zend/Locale/Data/kpe_GN.xml delete mode 100644 thirdparty/Zend/Locale/Data/kpe_LR.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_Arab.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_Arab_IQ.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_Arab_IR.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_Arab_SY.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_IQ.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_IR.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_Latn.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_Latn_TR.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_SY.xml delete mode 100644 thirdparty/Zend/Locale/Data/ku_TR.xml delete mode 100644 thirdparty/Zend/Locale/Data/kw.xml delete mode 100644 thirdparty/Zend/Locale/Data/kw_GB.xml delete mode 100644 thirdparty/Zend/Locale/Data/ky.xml delete mode 100644 thirdparty/Zend/Locale/Data/ky_KG.xml delete mode 100755 thirdparty/Zend/Locale/Data/lc.xml delete mode 100755 thirdparty/Zend/Locale/Data/lc_XX.xml delete mode 100644 thirdparty/Zend/Locale/Data/likelySubtags.xml delete mode 100644 thirdparty/Zend/Locale/Data/ln.xml delete mode 100644 thirdparty/Zend/Locale/Data/ln_CD.xml delete mode 100644 thirdparty/Zend/Locale/Data/ln_CG.xml delete mode 100644 thirdparty/Zend/Locale/Data/lo.xml delete mode 100644 thirdparty/Zend/Locale/Data/lo_LA.xml delete mode 100644 thirdparty/Zend/Locale/Data/lt.xml delete mode 100644 thirdparty/Zend/Locale/Data/lt_LT.xml delete mode 100644 thirdparty/Zend/Locale/Data/lv.xml delete mode 100644 thirdparty/Zend/Locale/Data/lv_LV.xml delete mode 100644 thirdparty/Zend/Locale/Data/metazoneInfo.xml delete mode 100644 thirdparty/Zend/Locale/Data/mi.xml delete mode 100644 thirdparty/Zend/Locale/Data/mi_NZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/mk.xml delete mode 100644 thirdparty/Zend/Locale/Data/mk_MK.xml delete mode 100644 thirdparty/Zend/Locale/Data/ml.xml delete mode 100644 thirdparty/Zend/Locale/Data/ml_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/mn.xml delete mode 100644 thirdparty/Zend/Locale/Data/mn_CN.xml delete mode 100644 thirdparty/Zend/Locale/Data/mn_Cyrl.xml delete mode 100644 thirdparty/Zend/Locale/Data/mn_Cyrl_MN.xml delete mode 100644 thirdparty/Zend/Locale/Data/mn_MN.xml delete mode 100644 thirdparty/Zend/Locale/Data/mn_Mong.xml delete mode 100644 thirdparty/Zend/Locale/Data/mn_Mong_CN.xml delete mode 100644 thirdparty/Zend/Locale/Data/mo.xml delete mode 100644 thirdparty/Zend/Locale/Data/mr.xml delete mode 100644 thirdparty/Zend/Locale/Data/mr_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/ms.xml delete mode 100644 thirdparty/Zend/Locale/Data/ms_BN.xml delete mode 100644 thirdparty/Zend/Locale/Data/ms_MY.xml delete mode 100644 thirdparty/Zend/Locale/Data/mt.xml delete mode 100644 thirdparty/Zend/Locale/Data/mt_MT.xml delete mode 100644 thirdparty/Zend/Locale/Data/my.xml delete mode 100644 thirdparty/Zend/Locale/Data/my_MM.xml delete mode 100644 thirdparty/Zend/Locale/Data/nb.xml delete mode 100644 thirdparty/Zend/Locale/Data/nb_NO.xml delete mode 100644 thirdparty/Zend/Locale/Data/nds.xml delete mode 100644 thirdparty/Zend/Locale/Data/nds_DE.xml delete mode 100644 thirdparty/Zend/Locale/Data/ne.xml delete mode 100644 thirdparty/Zend/Locale/Data/ne_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/ne_NP.xml delete mode 100644 thirdparty/Zend/Locale/Data/nl.xml delete mode 100644 thirdparty/Zend/Locale/Data/nl_BE.xml delete mode 100644 thirdparty/Zend/Locale/Data/nl_NL.xml delete mode 100644 thirdparty/Zend/Locale/Data/nn.xml delete mode 100644 thirdparty/Zend/Locale/Data/nn_NO.xml delete mode 100644 thirdparty/Zend/Locale/Data/no.xml delete mode 100644 thirdparty/Zend/Locale/Data/nr.xml delete mode 100644 thirdparty/Zend/Locale/Data/nr_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/nso.xml delete mode 100644 thirdparty/Zend/Locale/Data/nso_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/numberingSystems.xml delete mode 100644 thirdparty/Zend/Locale/Data/ny.xml delete mode 100644 thirdparty/Zend/Locale/Data/ny_MW.xml delete mode 100644 thirdparty/Zend/Locale/Data/oc.xml delete mode 100644 thirdparty/Zend/Locale/Data/oc_FR.xml delete mode 100644 thirdparty/Zend/Locale/Data/om.xml delete mode 100644 thirdparty/Zend/Locale/Data/om_ET.xml delete mode 100644 thirdparty/Zend/Locale/Data/om_KE.xml delete mode 100644 thirdparty/Zend/Locale/Data/or.xml delete mode 100644 thirdparty/Zend/Locale/Data/or_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/pa.xml delete mode 100644 thirdparty/Zend/Locale/Data/pa_Arab.xml delete mode 100644 thirdparty/Zend/Locale/Data/pa_Arab_PK.xml delete mode 100644 thirdparty/Zend/Locale/Data/pa_Guru.xml delete mode 100644 thirdparty/Zend/Locale/Data/pa_Guru_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/pa_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/pa_PK.xml delete mode 100644 thirdparty/Zend/Locale/Data/pl.xml delete mode 100644 thirdparty/Zend/Locale/Data/pl_PL.xml delete mode 100644 thirdparty/Zend/Locale/Data/postalCodeData.xml delete mode 100644 thirdparty/Zend/Locale/Data/ps.xml delete mode 100644 thirdparty/Zend/Locale/Data/ps_AF.xml delete mode 100644 thirdparty/Zend/Locale/Data/pt.xml delete mode 100644 thirdparty/Zend/Locale/Data/pt_BR.xml delete mode 100644 thirdparty/Zend/Locale/Data/pt_PT.xml delete mode 100644 thirdparty/Zend/Locale/Data/ro.xml delete mode 100644 thirdparty/Zend/Locale/Data/ro_MD.xml delete mode 100644 thirdparty/Zend/Locale/Data/ro_RO.xml delete mode 100644 thirdparty/Zend/Locale/Data/root.xml delete mode 100644 thirdparty/Zend/Locale/Data/ru.xml delete mode 100644 thirdparty/Zend/Locale/Data/ru_RU.xml delete mode 100644 thirdparty/Zend/Locale/Data/ru_UA.xml delete mode 100644 thirdparty/Zend/Locale/Data/rw.xml delete mode 100644 thirdparty/Zend/Locale/Data/rw_RW.xml delete mode 100644 thirdparty/Zend/Locale/Data/sa.xml delete mode 100644 thirdparty/Zend/Locale/Data/sa_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/se.xml delete mode 100644 thirdparty/Zend/Locale/Data/se_FI.xml delete mode 100644 thirdparty/Zend/Locale/Data/se_NO.xml delete mode 100644 thirdparty/Zend/Locale/Data/sh.xml delete mode 100644 thirdparty/Zend/Locale/Data/sh_BA.xml delete mode 100644 thirdparty/Zend/Locale/Data/sh_CS.xml delete mode 100644 thirdparty/Zend/Locale/Data/sh_YU.xml delete mode 100644 thirdparty/Zend/Locale/Data/si.xml delete mode 100644 thirdparty/Zend/Locale/Data/si_LK.xml delete mode 100644 thirdparty/Zend/Locale/Data/sid.xml delete mode 100644 thirdparty/Zend/Locale/Data/sid_ET.xml delete mode 100644 thirdparty/Zend/Locale/Data/sk.xml delete mode 100644 thirdparty/Zend/Locale/Data/sk_SK.xml delete mode 100644 thirdparty/Zend/Locale/Data/sl.xml delete mode 100644 thirdparty/Zend/Locale/Data/sl_SI.xml delete mode 100644 thirdparty/Zend/Locale/Data/so.xml delete mode 100644 thirdparty/Zend/Locale/Data/so_DJ.xml delete mode 100644 thirdparty/Zend/Locale/Data/so_ET.xml delete mode 100644 thirdparty/Zend/Locale/Data/so_KE.xml delete mode 100644 thirdparty/Zend/Locale/Data/so_SO.xml delete mode 100644 thirdparty/Zend/Locale/Data/sq.xml delete mode 100644 thirdparty/Zend/Locale/Data/sq_AL.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_BA.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_CS.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Cyrl.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Cyrl_BA.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Cyrl_CS.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Cyrl_ME.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Cyrl_RS.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Cyrl_YU.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Latn.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Latn_BA.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Latn_CS.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Latn_ME.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Latn_RS.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_Latn_YU.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_ME.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_RS.xml delete mode 100644 thirdparty/Zend/Locale/Data/sr_YU.xml delete mode 100644 thirdparty/Zend/Locale/Data/ss.xml delete mode 100644 thirdparty/Zend/Locale/Data/ss_SZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/ss_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/st.xml delete mode 100644 thirdparty/Zend/Locale/Data/st_LS.xml delete mode 100644 thirdparty/Zend/Locale/Data/st_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/supplementalData.xml delete mode 100644 thirdparty/Zend/Locale/Data/sv.xml delete mode 100644 thirdparty/Zend/Locale/Data/sv_FI.xml delete mode 100644 thirdparty/Zend/Locale/Data/sv_SE.xml delete mode 100644 thirdparty/Zend/Locale/Data/sw.xml delete mode 100644 thirdparty/Zend/Locale/Data/sw_KE.xml delete mode 100644 thirdparty/Zend/Locale/Data/sw_TZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/syr.xml delete mode 100644 thirdparty/Zend/Locale/Data/syr_SY.xml delete mode 100644 thirdparty/Zend/Locale/Data/ta.xml delete mode 100644 thirdparty/Zend/Locale/Data/ta_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/te.xml delete mode 100644 thirdparty/Zend/Locale/Data/te_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/telephoneCodeData.xml delete mode 100644 thirdparty/Zend/Locale/Data/tg.xml delete mode 100644 thirdparty/Zend/Locale/Data/tg_Cyrl.xml delete mode 100644 thirdparty/Zend/Locale/Data/tg_Cyrl_TJ.xml delete mode 100644 thirdparty/Zend/Locale/Data/tg_TJ.xml delete mode 100644 thirdparty/Zend/Locale/Data/th.xml delete mode 100644 thirdparty/Zend/Locale/Data/th_TH.xml delete mode 100644 thirdparty/Zend/Locale/Data/ti.xml delete mode 100644 thirdparty/Zend/Locale/Data/ti_ER.xml delete mode 100644 thirdparty/Zend/Locale/Data/ti_ET.xml delete mode 100644 thirdparty/Zend/Locale/Data/tig.xml delete mode 100644 thirdparty/Zend/Locale/Data/tig_ER.xml delete mode 100644 thirdparty/Zend/Locale/Data/tl.xml delete mode 100644 thirdparty/Zend/Locale/Data/tn.xml delete mode 100644 thirdparty/Zend/Locale/Data/tn_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/to.xml delete mode 100644 thirdparty/Zend/Locale/Data/to_TO.xml delete mode 100644 thirdparty/Zend/Locale/Data/tr.xml delete mode 100644 thirdparty/Zend/Locale/Data/tr_TR.xml delete mode 100644 thirdparty/Zend/Locale/Data/trv.xml delete mode 100644 thirdparty/Zend/Locale/Data/trv_TW.xml delete mode 100644 thirdparty/Zend/Locale/Data/ts.xml delete mode 100644 thirdparty/Zend/Locale/Data/ts_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/tt.xml delete mode 100644 thirdparty/Zend/Locale/Data/tt_RU.xml delete mode 100644 thirdparty/Zend/Locale/Data/ug.xml delete mode 100644 thirdparty/Zend/Locale/Data/ug_Arab.xml delete mode 100644 thirdparty/Zend/Locale/Data/ug_Arab_CN.xml delete mode 100644 thirdparty/Zend/Locale/Data/ug_CN.xml delete mode 100644 thirdparty/Zend/Locale/Data/uk.xml delete mode 100644 thirdparty/Zend/Locale/Data/uk_UA.xml delete mode 100644 thirdparty/Zend/Locale/Data/ur.xml delete mode 100644 thirdparty/Zend/Locale/Data/ur_IN.xml delete mode 100644 thirdparty/Zend/Locale/Data/ur_PK.xml delete mode 100644 thirdparty/Zend/Locale/Data/uz.xml delete mode 100644 thirdparty/Zend/Locale/Data/uz_AF.xml delete mode 100644 thirdparty/Zend/Locale/Data/uz_Arab.xml delete mode 100644 thirdparty/Zend/Locale/Data/uz_Arab_AF.xml delete mode 100644 thirdparty/Zend/Locale/Data/uz_Cyrl.xml delete mode 100644 thirdparty/Zend/Locale/Data/uz_Cyrl_UZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/uz_Latn.xml delete mode 100644 thirdparty/Zend/Locale/Data/uz_Latn_UZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/uz_UZ.xml delete mode 100644 thirdparty/Zend/Locale/Data/ve.xml delete mode 100644 thirdparty/Zend/Locale/Data/ve_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/vi.xml delete mode 100644 thirdparty/Zend/Locale/Data/vi_VN.xml delete mode 100644 thirdparty/Zend/Locale/Data/wal.xml delete mode 100644 thirdparty/Zend/Locale/Data/wal_ET.xml delete mode 100644 thirdparty/Zend/Locale/Data/wo.xml delete mode 100644 thirdparty/Zend/Locale/Data/wo_Latn.xml delete mode 100644 thirdparty/Zend/Locale/Data/wo_Latn_SN.xml delete mode 100644 thirdparty/Zend/Locale/Data/wo_SN.xml delete mode 100644 thirdparty/Zend/Locale/Data/xh.xml delete mode 100644 thirdparty/Zend/Locale/Data/xh_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Data/yo.xml delete mode 100644 thirdparty/Zend/Locale/Data/yo_NG.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_CN.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_HK.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_Hans.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_Hans_CN.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_Hans_HK.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_Hans_MO.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_Hans_SG.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_Hant.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_Hant_HK.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_Hant_MO.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_Hant_TW.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_MO.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_SG.xml delete mode 100644 thirdparty/Zend/Locale/Data/zh_TW.xml delete mode 100644 thirdparty/Zend/Locale/Data/zu.xml delete mode 100644 thirdparty/Zend/Locale/Data/zu_ZA.xml delete mode 100644 thirdparty/Zend/Locale/Exception.php delete mode 100644 thirdparty/Zend/Locale/Format.php delete mode 100644 thirdparty/Zend/Locale/Math.php delete mode 100644 thirdparty/Zend/Locale/Math/Exception.php delete mode 100644 thirdparty/Zend/Locale/Math/PhpMath.php diff --git a/_config/i18n.yml b/_config/i18n.yml index a5a0bfe08c9..83d38e1ca01 100644 --- a/_config/i18n.yml +++ b/_config/i18n.yml @@ -2,7 +2,7 @@ Name: basei18n Before: '/i18n' --- -SilverStripe\i18n\i18n: +SilverStripe\i18n\Data\Sources: module_priority: - admin - framework @@ -10,7 +10,7 @@ SilverStripe\i18n\i18n: --- Name: defaulti18n --- -SilverStripe\i18n\i18n: +SilverStripe\i18n\Data\Sources: module_priority: - other_modules --- @@ -58,3 +58,9 @@ SilverStripe\Core\Injector\Injector: properties: Reader: %$SilverStripe\i18n\Messages\Reader Writer: %$SilverStripe\i18n\Messages\Writer +--- +Name: i18ndata +--- +SilverStripe\Core\Injector\Injector: + SilverStripe\i18n\Data\Locales: + class: SilverStripe\i18n\Data\Intl\IntlLocales diff --git a/admin/client/dist/js/bundle.js b/admin/client/dist/js/bundle.js index 945ccaeccfb..fdcda5d93fb 100644 --- a/admin/client/dist/js/bundle.js +++ b/admin/client/dist/js/bundle.js @@ -13,10 +13,10 @@ return this.lang&&this.lang[this.getLocale()]&&this.lang[this.getLocale()][e]?th }},{key:"addDictionary",value:function o(e,t){"undefined"==typeof this.lang[e]&&(this.lang[e]={}) for(var n in t)this.lang[e][n]=t[n]}},{key:"getDictionary",value:function s(e){return this.lang[e]}},{key:"stripStr",value:function l(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}},{key:"stripStrML", value:function u(e){for(var t=e.split("\n"),n=0;n1?t-1:0),i=1;i1?t-1:0),i=1;i-1?t:e}function c(e,t){t=t||{} +return g.indexOf(t)>-1?t:e}function d(e,t){t=t||{} var n=t.body -if(c.prototype.isPrototypeOf(e)){if(e.bodyUsed)throw new TypeError("Already read") +if(d.prototype.isPrototypeOf(e)){if(e.bodyUsed)throw new TypeError("Already read") this.url=e.url,this.credentials=e.credentials,t.headers||(this.headers=new r(e.headers)),this.method=e.method,this.mode=e.mode,n||(n=e._bodyInit,e.bodyUsed=!0)}else this.url=e -if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new r(t.headers)),this.method=d(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null, +if(this.credentials=t.credentials||this.credentials||"omit",!t.headers&&this.headers||(this.headers=new r(t.headers)),this.method=c(t.method||this.method||"GET"),this.mode=t.mode||this.mode||null,this.referrer=null, ("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests") this._initBody(n)}function f(e){var t=new FormData return e.trim().split("&").forEach(function(e){if(e){var n=e.split("="),i=n.shift().replace(/\+/g," "),r=n.join("=").replace(/\+/g," ") @@ -134,15 +134,15 @@ return this.forEach(function(t,n){e.push(n)}),i(e)},r.prototype.values=function( return this.forEach(function(t){e.push(t)}),i(e)},r.prototype.entries=function(){var e=[] return this.forEach(function(t,n){e.push([n,t])}),i(e)},m.iterable&&(r.prototype[Symbol.iterator]=r.prototype.entries) var g=["DELETE","GET","HEAD","OPTIONS","POST","PUT"] -c.prototype.clone=function(){return new c(this)},u.call(c.prototype),u.call(h.prototype),h.prototype.clone=function(){return new h(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new r(this.headers), +d.prototype.clone=function(){return new d(this)},u.call(d.prototype),u.call(h.prototype),h.prototype.clone=function(){return new h(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new r(this.headers), url:this.url})},h.error=function(){var e=new h(null,{status:0,statusText:""}) return e.type="error",e} var v=[301,302,303,307,308] h.redirect=function(e,t){if(v.indexOf(t)===-1)throw new RangeError("Invalid status code") -return new h(null,{status:t,headers:{location:e}})},e.Headers=r,e.Request=c,e.Response=h,e.fetch=function(e,t){return new Promise(function(n,i){function r(){return"responseURL"in o?o.responseURL:/^X-Request-URL:/m.test(o.getAllResponseHeaders())?o.getResponseHeader("X-Request-URL"):void 0 +return new h(null,{status:t,headers:{location:e}})},e.Headers=r,e.Request=d,e.Response=h,e.fetch=function(e,t){return new Promise(function(n,i){function r(){return"responseURL"in o?o.responseURL:/^X-Request-URL:/m.test(o.getAllResponseHeaders())?o.getResponseHeader("X-Request-URL"):void 0 }var a -a=c.prototype.isPrototypeOf(e)&&!t?e:new c(e,t) +a=d.prototype.isPrototypeOf(e)&&!t?e:new d(e,t) var o=new XMLHttpRequest o.onload=function(){var e={status:o.status,statusText:o.statusText,headers:p(o),url:r()},t="response"in o?o.response:o.responseText n(new h(t,e))},o.onerror=function(){i(new TypeError("Network request failed"))},o.ontimeout=function(){i(new TypeError("Network request failed"))},o.open(a.method,a.url,!0),"include"===a.credentials&&(o.withCredentials=!0), @@ -150,8 +150,8 @@ n(new h(t,e))},o.onerror=function(){i(new TypeError("Network request failed"))}, },function(e,t,n){var i;(function(t,r){!function(t,n){e.exports=n()}(this,function(){"use strict" function e(e){return"function"==typeof e||"object"==typeof e&&null!==e}function a(e){return"function"==typeof e}function o(e){K=e}function s(e){J=e}function l(){return function(){return t.nextTick(p)}} -function u(){return function(){Q(p)}}function d(){var e=0,t=new ee(p),n=document.createTextNode("") -return t.observe(n,{characterData:!0}),function(){n.data=e=++e%2}}function c(){var e=new MessageChannel +function u(){return function(){Q(p)}}function c(){var e=0,t=new ee(p),n=document.createTextNode("") +return t.observe(n,{characterData:!0}),function(){n.data=e=++e%2}}function d(){var e=new MessageChannel return e.port1.onmessage=p,function(){return e.port2.postMessage(0)}}function f(){var e=setTimeout return function(){return e(p,1)}}function p(){for(var e=0;e-1})?null:"action_save"===this.props.name||e.find(function(e){return"ss-ui-action-constructive"===e})?"primary":"secondary"}},{key:"getIcon",value:function f(){ -return this.props.icon||this.props.data.icon||null}},{key:"getLoadingIcon",value:function p(){return this.props.loading?d["default"].createElement("div",{className:"btn__loading-icon"},d["default"].createElement("span",{ -className:"btn__circle btn__circle--1"}),d["default"].createElement("span",{className:"btn__circle btn__circle--2"}),d["default"].createElement("span",{className:"btn__circle btn__circle--3"})):null}},{ +return this.props.icon||this.props.data.icon||null}},{key:"getLoadingIcon",value:function p(){return this.props.loading?c["default"].createElement("div",{className:"btn__loading-icon"},c["default"].createElement("span",{ +className:"btn__circle btn__circle--1"}),c["default"].createElement("span",{className:"btn__circle btn__circle--2"}),c["default"].createElement("span",{className:"btn__circle btn__circle--3"})):null}},{ key:"handleClick",value:function h(e){"function"==typeof this.props.handleClick&&this.props.handleClick(e,this.props.name||this.props.id)}}]),t}(f["default"]) -p.propTypes={id:d["default"].PropTypes.string,name:d["default"].PropTypes.string,handleClick:d["default"].PropTypes.func,title:d["default"].PropTypes.string,type:d["default"].PropTypes.string,loading:d["default"].PropTypes.bool, -icon:d["default"].PropTypes.string,disabled:d["default"].PropTypes.bool,data:d["default"].PropTypes.oneOfType([d["default"].PropTypes.array,d["default"].PropTypes.shape({buttonStyle:d["default"].PropTypes.string -})]),extraClass:d["default"].PropTypes.string,attributes:d["default"].PropTypes.object},p.defaultProps={title:"",icon:"",extraClass:"",attributes:{},data:{},disabled:!1},t["default"]=p},function(e,t,n){ +p.propTypes={id:c["default"].PropTypes.string,name:c["default"].PropTypes.string,handleClick:c["default"].PropTypes.func,title:c["default"].PropTypes.string,type:c["default"].PropTypes.string,loading:c["default"].PropTypes.bool, +icon:c["default"].PropTypes.string,disabled:c["default"].PropTypes.bool,data:c["default"].PropTypes.oneOfType([c["default"].PropTypes.array,c["default"].PropTypes.shape({buttonStyle:c["default"].PropTypes.string +})]),extraClass:c["default"].PropTypes.string,attributes:c["default"].PropTypes.object},p.defaultProps={title:"",icon:"",extraClass:"",attributes:{},data:{},disabled:!1},t["default"]=p},function(e,t,n){ (function(t){e.exports=t.SchemaActions=n(32)}).call(t,function(){return this}())},function(e,t,n){"use strict" function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){return{type:u["default"].SET_SCHEMA,payload:s({id:e},t)}}function a(e,t){return{type:u["default"].SET_SCHEMA_STATE_OVERRIDES,payload:{ id:e,stateOverride:t}}}function o(e,t){return{type:u["default"].SET_SCHEMA_LOADING,payload:{id:e,loading:t}}}Object.defineProperty(t,"__esModule",{value:!0}) @@ -357,22 +357,22 @@ for(var i in n)Object.prototype.hasOwnProperty.call(n,i)&&(e[i]=n[i])}return e}, try{for(var o=e[Symbol.iterator](),s;!(i=(s=o.next()).done)&&(n.push(s.value),!t||n.length!==t);i=!0);}catch(l){r=!0,a=l}finally{try{!i&&o["return"]&&o["return"]()}finally{if(r)throw a}}return n}return function(t,n){ if(Array.isArray(t))return t if(Symbol.iterator in Object(t))return e(t,n) -throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),d=function(){function e(e,t){for(var n=0;n1}},{key:"handleChange",value:function c(e){"function"==typeof this.props.onChange&&this.props.onChange(e,{ +}):s(e,{componentClass:"input",type:this.props.type.toLowerCase()})),e}},{key:"isMultiline",value:function u(){return this.props.data&&this.props.data.rows>1}},{key:"handleChange",value:function d(e){"function"==typeof this.props.onChange&&this.props.onChange(e,{ id:this.props.id,value:e.target.value})}}]),t}(f["default"]) -g.propTypes={extraClass:d["default"].PropTypes.string,id:d["default"].PropTypes.string,name:d["default"].PropTypes.string.isRequired,onChange:d["default"].PropTypes.func,value:d["default"].PropTypes.oneOfType([d["default"].PropTypes.string,d["default"].PropTypes.number]), -readOnly:d["default"].PropTypes.bool,disabled:d["default"].PropTypes.bool,placeholder:d["default"].PropTypes.string,type:d["default"].PropTypes.string},g.defaultProps={value:"",extraClass:"",className:"", +g.propTypes={extraClass:c["default"].PropTypes.string,id:c["default"].PropTypes.string,name:c["default"].PropTypes.string.isRequired,onChange:c["default"].PropTypes.func,value:c["default"].PropTypes.oneOfType([c["default"].PropTypes.string,c["default"].PropTypes.number]), +readOnly:c["default"].PropTypes.bool,disabled:c["default"].PropTypes.bool,placeholder:c["default"].PropTypes.string,type:c["default"].PropTypes.string},g.defaultProps={value:"",extraClass:"",className:"", type:"text"},t.TextField=g,t["default"]=(0,h["default"])(g)},function(e,t){e.exports=FieldHolder},function(e,t,n){(function(t){e.exports=t.LiteralField=n(137)}).call(t,function(){return this}())},function(e,t,n){ "use strict" function i(e){return e&&e.__esModule?e:{"default":e}}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function a(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called") @@ -735,11 +735,11 @@ e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,wri value:!0}) var s=Object.assign||function(e){for(var t=1;t=0?n[o]=r:n.unshift(r)}},{key:"findChildRoute",value:function s(e){var t=this.childRoutes return e&&e.forEach(function(e){var n=t.find(function(t){return t.path===e}) if(!n)throw new Error("Parent path "+e+" could not be found.") -t=n.childRoutes}),t}},{key:"getRootRoute",value:function l(){return this.rootRoute}},{key:"getChildRoutes",value:function u(){return this.childRoutes}},{key:"remove",value:function d(e){var t=arguments.length<=1||void 0===arguments[1]?[]:arguments[1],n=this.findChildRoute(t),i=n.findIndex(function(t){ +t=n.childRoutes}),t}},{key:"getRootRoute",value:function l(){return this.rootRoute}},{key:"getChildRoutes",value:function u(){return this.childRoutes}},{key:"remove",value:function c(e){var t=arguments.length<=1||void 0===arguments[1]?[]:arguments[1],n=this.findChildRoute(t),i=n.findIndex(function(t){ return t.path===e}) return i<0?null:n.splice(i,1)[0]}}]),e}() window.ss=window.ss||{},window.ss.routeRegister=window.ss.routeRegister||new a,t["default"]=window.ss.routeRegister},function(e,t,n){(function(t){e.exports=t.Injector=n(103)}).call(t,function(){return this }())},function(e,t,n){(function(t){e.exports=t.Router=n(156)}).call(t,function(){return this}())},function(e,t,n){"use strict" -function i(e){return e&&e.__esModule?e:{"default":e}}function r(e){var t=d["default"].getAbsoluteBase(),n=f["default"].resolve(t,e) -return 0!==n.indexOf(t)?n:n.substring(t.length-1)}function a(e){return function(t,n,i,r){return e(d["default"].resolveURLToBase(t),n,i,r)}}function o(e){var t=new d["default"].Route(e) -return t.match(d["default"].current,{})}function s(){return d["default"].absoluteBaseURL}function l(e){d["default"].absoluteBaseURL=e +function i(e){return e&&e.__esModule?e:{"default":e}}function r(e){var t=c["default"].getAbsoluteBase(),n=f["default"].resolve(t,e) +return 0!==n.indexOf(t)?n:n.substring(t.length-1)}function a(e){return function(t,n,i,r){return e(c["default"].resolveURLToBase(t),n,i,r)}}function o(e){var t=new c["default"].Route(e) +return t.match(c["default"].current,{})}function s(){return c["default"].absoluteBaseURL}function l(e){c["default"].absoluteBaseURL=e var t=document.createElement("a") t.href=e var n=t.pathname -n=n.replace(/\/$/,""),n.match(/^[^\/]/)&&(n="/"+n),d["default"].base(n)}Object.defineProperty(t,"__esModule",{value:!0}) -var u=n(157),d=i(u),c=n(158),f=i(c) -d["default"].oldshow||(d["default"].oldshow=d["default"].show),d["default"].setAbsoluteBase=l.bind(d["default"]),d["default"].getAbsoluteBase=s.bind(d["default"]),d["default"].resolveURLToBase=r.bind(d["default"]), -d["default"].show=a(d["default"].oldshow),d["default"].routeAppliesToCurrentLocation=o,window.ss=window.ss||{},window.ss.router=window.ss.router||d["default"],t["default"]=window.ss.router},function(e,t){ +n=n.replace(/\/$/,""),n.match(/^[^\/]/)&&(n="/"+n),c["default"].base(n)}Object.defineProperty(t,"__esModule",{value:!0}) +var u=n(157),c=i(u),d=n(158),f=i(d) +c["default"].oldshow||(c["default"].oldshow=c["default"].show),c["default"].setAbsoluteBase=l.bind(c["default"]),c["default"].getAbsoluteBase=s.bind(c["default"]),c["default"].resolveURLToBase=r.bind(c["default"]), +c["default"].show=a(c["default"].oldshow),c["default"].routeAppliesToCurrentLocation=o,window.ss=window.ss||{},window.ss.router=window.ss.router||c["default"],t["default"]=window.ss.router},function(e,t){ e.exports=Page},function(e,t,n){"use strict" function i(){this.protocol=null,this.slashes=null,this.auth=null,this.host=null,this.port=null,this.hostname=null,this.hash=null,this.search=null,this.query=null,this.pathname=null,this.path=null,this.href=null @@ -839,14 +839,14 @@ return r.parse(e,t,n),r}function a(e){return u.isString(e)&&(e=r(e)),e instanceo }var l=n(159),u=n(160) t.parse=r,t.resolve=o,t.resolveObject=s,t.format=a,t.Url=i -var d=/^([a-z0-9.+-]+:)/i,c=/:[0-9]*$/,f=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,p=["<",">",'"',"`"," ","\r","\n","\t"],h=["{","}","|","\\","^","`"].concat(p),m=["'"].concat(h),g=["%","/","?",";","#"].concat(m),v=["/","?","#"],y=255,b=/^[+a-z0-9A-Z_-]{0,63}$/,_=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,w={ +var c=/^([a-z0-9.+-]+:)/i,d=/:[0-9]*$/,f=/^(\/\/?(?!\/)[^\?\s]*)(\?[^\s]*)?$/,p=["<",">",'"',"`"," ","\r","\n","\t"],h=["{","}","|","\\","^","`"].concat(p),m=["'"].concat(h),g=["%","/","?",";","#"].concat(m),v=["/","?","#"],y=255,b=/^[+a-z0-9A-Z_-]{0,63}$/,_=/^([+a-z0-9A-Z_-]{0,63})(.*)$/,w={ javascript:!0,"javascript:":!0},C={javascript:!0,"javascript:":!0},T={http:!0,https:!0,ftp:!0,gopher:!0,file:!0,"http:":!0,"https:":!0,"ftp:":!0,"gopher:":!0,"file:":!0},E=n(161) i.prototype.parse=function(e,t,n){if(!u.isString(e))throw new TypeError("Parameter 'url' must be a string, not "+typeof e) var i=e.indexOf("?"),r=i!==-1&&i0)&&n.host.split("@") P&&(n.auth=P.shift(),n.host=n.hostname=P.shift())}return b=b||n.host&&w.length,b&&!x&&w.unshift(""),w.length?n.pathname=w.join("/"):(n.pathname=null,n.path=null),u.isNull(n.pathname)&&u.isNull(n.search)||(n.path=(n.pathname?n.pathname:"")+(n.search?n.search:"")), -n.auth=e.auth||n.auth,n.slashes=n.slashes||e.slashes,n.href=n.format(),n},i.prototype.parseHost=function(){var e=this.host,t=c.exec(e) +n.auth=e.auth||n.auth,n.slashes=n.slashes||e.slashes,n.href=n.format(),n},i.prototype.parseHost=function(){var e=this.host,t=d.exec(e) t&&(t=t[0],":"!==t&&(this.port=t.substr(1)),e=e.substr(0,e.length-t.length)),e&&(this.hostname=e)}},function(e,t,n){var i;(function(e,r){!function(a){function o(e){throw RangeError(A[e])}function s(e,t){ for(var n=e.length,i=[];n--;)i[n]=t(e[n]) return i}function l(e,t){var n=e.split("@"),i="" @@ -915,24 +915,24 @@ var r=e.split("."),a=s(r,t).join(".") return i+a}function u(e){for(var t=[],n=0,i=e.length,r,a;n=55296&&r<=56319&&n65535&&(e-=65536,t+=N(e>>>10&1023|55296),e=56320|1023&e),t+=N(e)}).join("")}function c(e){return e-48<10?e-22:e-65<26?e-65:e-97<26?e-97:T}function f(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function p(e,t,n){ +return t}function c(e){return s(e,function(e){var t="" +return e>65535&&(e-=65536,t+=N(e>>>10&1023|55296),e=56320|1023&e),t+=N(e)}).join("")}function d(e){return e-48<10?e-22:e-65<26?e-65:e-97<26?e-97:T}function f(e,t){return e+22+75*(e<26)-((0!=t)<<5)}function p(e,t,n){ var i=0 for(e=n?M(e/S):e>>1,e+=M(e/t);e>D*P>>1;i+=T)e=M(e/D) return M(i+(D+1)*e/(e+O))}function h(e){var t=[],n=e.length,i,r=0,a=j,s=k,l,u,f,h,m,g,v,y,b for(l=e.lastIndexOf(x),l<0&&(l=0),u=0;u=128&&o("not-basic"),t.push(e.charCodeAt(u)) -for(f=l>0?l+1:0;f=n&&o("invalid-input"),v=c(e.charCodeAt(f++)),(v>=T||v>M((C-r)/m))&&o("overflow"),r+=v*m,y=g<=s?E:g>=s+P?P:g-s,!(vM(C/b)&&o("overflow"),m*=b -i=t.length+1,s=p(r-h,i,0==h),M(r/i)>C-a&&o("overflow"),a+=M(r/i),r%=i,t.splice(r++,0,a)}return d(t)}function m(e){var t,n,i,r,a,s,l,d,c,h,m,g=[],v,y,b,_ +for(f=l>0?l+1:0;f=n&&o("invalid-input"),v=d(e.charCodeAt(f++)),(v>=T||v>M((C-r)/m))&&o("overflow"),r+=v*m,y=g<=s?E:g>=s+P?P:g-s,!(vM(C/b)&&o("overflow"),m*=b +i=t.length+1,s=p(r-h,i,0==h),M(r/i)>C-a&&o("overflow"),a+=M(r/i),r%=i,t.splice(r++,0,a)}return c(t)}function m(e){var t,n,i,r,a,s,l,c,d,h,m,g=[],v,y,b,_ for(e=u(e),v=e.length,t=j,n=0,a=k,s=0;s=t&&mM((C-n)/y)&&o("overflow"),n+=(l-t)*y,t=l,s=0;sC&&o("overflow"),m==t){for(d=n,c=T;h=c<=a?E:c>=a+P?P:c-a,!(dM((C-n)/y)&&o("overflow"),n+=(l-t)*y,t=l,s=0;sC&&o("overflow"),m==t){for(c=n,d=T;h=d<=a?E:d>=a+P?P:d-a,!(c= 0x80 (not a basic code point)", "invalid-input":"Invalid input"},D=T-E,M=Math.floor,N=String.fromCharCode,U -w={version:"1.3.2",ucs2:{decode:u,encode:d},decode:h,encode:m,toASCII:v,toUnicode:g},i=function(){return w}.call(t,n,t,e),!(void 0!==i&&(e.exports=i))}(this)}).call(t,n(15)(e),function(){return this}()) +w={version:"1.3.2",ucs2:{decode:u,encode:c},decode:h,encode:m,toASCII:v,toUnicode:g},i=function(){return w}.call(t,n,t,e),!(void 0!==i&&(e.exports=i))}(this)}).call(t,n(15)(e),function(){return this}()) },function(e,t){"use strict" e.exports={isString:function(e){return"string"==typeof e},isObject:function(e){return"object"==typeof e&&null!==e},isNull:function(e){return null===e},isNullOrUndefined:function(e){return null==e}}},function(e,t,n){ @@ -947,8 +947,8 @@ var s=1e3 r&&"number"==typeof r.maxKeys&&(s=r.maxKeys) var l=e.length s>0&&l>s&&(l=s) -for(var u=0;u=0?(f=d.substr(0,c),p=d.substr(c+1)):(f=d,p=""),h=decodeURIComponent(f),m=decodeURIComponent(p),n(a,h)?Array.isArray(a[h])?a[h].push(m):a[h]=[a[h],m]:a[h]=m}return a}},function(e,t){"use strict" +for(var u=0;u=0?(f=c.substr(0,d),p=c.substr(d+1)):(f=c,p=""),h=decodeURIComponent(f),m=decodeURIComponent(p),n(a,h)?Array.isArray(a[h])?a[h].push(m):a[h]=[a[h],m]:a[h]=m}return a}},function(e,t){"use strict" var n=function(e){switch(typeof e){case"string":return e case"boolean":return e?"true":"false" case"number":return isFinite(e)?e:"" @@ -957,10 +957,10 @@ e.exports=function(e,t,i,r){return t=t||"&",i=i||"=",null===e&&(e=void 0),"objec return Array.isArray(e[r])?e[r].map(function(e){return a+encodeURIComponent(n(e))}).join(t):a+encodeURIComponent(n(e[r]))}).join(t):r?encodeURIComponent(n(r))+i+encodeURIComponent(n(e)):""}},function(e,t,n){ "use strict" function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r),o=(0,a["default"])(window),s=(0,a["default"])("html"),l=(0,a["default"])("head"),u={urlParseRE:/^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/, -parseUrl:function d(e){if("object"===a["default"].type(e))return e +parseUrl:function c(e){if("object"===a["default"].type(e))return e var t=u.urlParseRE.exec(e||"")||[] return{href:t[0]||"",hrefNoHash:t[1]||"",hrefNoSearch:t[2]||"",domain:t[3]||"",protocol:t[4]||"",doubleSlash:t[5]||"",authority:t[6]||"",username:t[8]||"",password:t[9]||"",host:t[10]||"",hostname:t[11]||"", -port:t[12]||"",pathname:t[13]||"",directory:t[14]||"",filename:t[15]||"",search:t[16]||"",hash:t[17]||""}},makePathAbsolute:function c(e,t){if(e&&"/"===e.charAt(0))return e +port:t[12]||"",pathname:t[13]||"",directory:t[14]||"",filename:t[15]||"",search:t[16]||"",hash:t[17]||""}},makePathAbsolute:function d(e,t){if(e&&"/"===e.charAt(0))return e e=e||"",t=t?t.replace(/^\/|(\/[^\/]*|[^\/]+)$/g,""):"" for(var n=t?t.split("/"):[],i=e.split("/"),r=0;r').addClass("ui-dialog-tit var l=(0,r["default"])("").addClass("ui-dialog-title").attr("id",i).html(n).prependTo(a) -a.find("*").add(a).disableSelection()},destroy:function d(){this.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),this.originalTitle&&this.element.attr("title",this.originalTitle) +a.find("*").add(a).disableSelection()},destroy:function c(){this.element.unbind(".dialog").removeData("dialog").removeClass("ui-dialog-content ui-widget-content").hide().appendTo("body"),this.originalTitle&&this.element.attr("title",this.originalTitle) -}}),r["default"].extend(r["default"].ssui.titlebar,{version:"0.0.1",options:{title:"",closeButton:!1,closeText:"close"},uuid:0,getTitleId:function c(e){return"ui-dialog-title-"+(e.attr("id")||++this.uuid) +}}),r["default"].extend(r["default"].ssui.titlebar,{version:"0.0.1",options:{title:"",closeButton:!1,closeText:"close"},uuid:0,getTitleId:function d(e){return"ui-dialog-title-"+(e.attr("id")||++this.uuid) }})}).call(t,n(1))},,function(module,exports,__webpack_require__){(function(jQuery){"use strict" function _interopRequireDefault(e){return e&&e.__esModule?e:{"default":e}}var _typeof="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol?"symbol":typeof e @@ -1042,7 +1042,7 @@ void this._super())},onwindowresize:function r(){this.redraw()},"from .cms-panel var t=this.getLayoutOptions(),n=!1 for(var i in e)t[i]!==e[i]&&(t[i]=e[i],n=!0) n&&this.redraw()},clearViewMode:function l(){this.removeClass("cms-container--split-mode"),this.removeClass("cms-container--preview-mode"),this.removeClass("cms-container--content-mode")},splitViewMode:function u(){ -this.updateLayoutOptions({mode:"split"})},contentViewMode:function d(){this.updateLayoutOptions({mode:"content"})},previewMode:function c(){this.updateLayoutOptions({mode:"preview"})},RedrawSuppression:!1, +this.updateLayoutOptions({mode:"split"})},contentViewMode:function c(){this.updateLayoutOptions({mode:"content"})},previewMode:function d(){this.updateLayoutOptions({mode:"preview"})},RedrawSuppression:!1, redraw:function f(){if(!this.getRedrawSuppression()){window.debug&&console.log("redraw",this.attr("class"),this.get(0)) var e=this.setProperMode() e||(this.find(".cms-panel-layout").redraw(),this.find(".cms-content-fields[data-layout-type]").redraw(),this.find(".cms-edit-form[data-layout-type]").redraw(),this.find(".cms-preview").redraw(),this.find(".cms-content").redraw()) @@ -1068,7 +1068,7 @@ var a=e.validate(),o=function l(){$(t).removeClass("btn--loading loading"),$(t). "undefined"==typeof a||a||(statusMessage("Validation failed.","bad"),o()) var s=e.serializeArray() return s.push({name:$(t).attr("name"),value:"1"}),s.push({name:"BackURL",value:document.URL.replace(/\/$/,"")}),this.saveTabState(),jQuery.ajax(jQuery.extend({headers:{"X-Pjax":"CurrentForm,Breadcrumbs" -},url:e.attr("action"),data:s,type:"POST",complete:function u(){o()},success:function d(t,i,a){o(),e.removeClass("changed"),n&&n(t,i,a) +},url:e.attr("action"),data:s,type:"POST",complete:function u(){o()},success:function c(t,i,a){o(),e.removeClass("changed"),n&&n(t,i,a) var l=r.handleAjaxResponse(t,i,a) l&&l.filter("form").trigger("aftersubmitform",{status:i,xhr:a,formData:s})}},i)),!1},LastState:null,PauseState:!1,handleStateChange:function y(e){var t=arguments.length<=1||void 0===arguments[1]?window.history.state:arguments[1] @@ -1079,15 +1079,15 @@ if(this.setStateChangeCount(this.getStateChangeCount()+1),!this.checkCanNavigate return this.setPauseState(!0),s&&s.path?window.ss.router.show(s.path):window.ss.router.back(),void this.setPauseState(!1)}if(this.setLastState(t),o.length0&&r.each(function(t,n){e(n).contents().on("click.ss-ui-action-tabset",a)})},riseUp:function o(t,n){ -var i,r,a,o,s,l,u,d,c +var i,r,a,o,s,l,u,c,d return i=e(this).find(".ui-tabs-panel").outerHeight(),r=e(this).find(".ui-tabs-nav").outerHeight(),a=e(window).height()+e(document).scrollTop()-r,o=e(this).find(".ui-tabs-nav").offset().top,s=n.newPanel, -l=n.newTab,o+i>=a&&o-i>0?(this.addClass("rise-up"),null!==l.position()&&(u=-s.outerHeight(),d=s.parents(".toolbar--south"),d&&(c=l.offset().top-d.offset().top,u-=c),e(s).css("top",u+"px"))):(this.removeClass("rise-up"), +l=n.newTab,o+i>=a&&o-i>0?(this.addClass("rise-up"),null!==l.position()&&(u=-s.outerHeight(),c=s.parents(".toolbar--south"),c&&(d=l.offset().top-c.offset().top,u-=d),e(s).css("top",u+"px"))):(this.removeClass("rise-up"), null!==l.position()&&e(s).css("bottom","100%")),!1}}),e(".cms-content-actions .ss-tabset.ss-ui-action-tabset").entwine({ontabsbeforeactivate:function s(t,n){this._super(t,n),e(n.newPanel).length>0&&e(n.newPanel).css("left",n.newTab.position().left+"px") }}),e(".cms-actions-row.ss-tabset.ss-ui-action-tabset").entwine({ontabsbeforeactivate:function l(t,n){this._super(t,n),e(this).closest(".ss-ui-action-tabset").removeClass("tabset-open tabset-open-last") @@ -1204,9 +1204,9 @@ null!==l.position()&&e(s).css("bottom","100%")),!1}}),e(".cms-content-actions .s }}),e(".cms-content-fields .ss-tabset.ss-ui-action-tabset").entwine({ontabsbeforeactivate:function u(t,n){this._super(t,n),e(n.newPanel).length>0&&(e(n.newTab).hasClass("last")?(e(n.newPanel).css({left:"auto", right:"0px"}),e(n.newPanel).parent().addClass("tabset-open-last")):(e(n.newPanel).css("left",n.newTab.position().left+"px"),e(n.newTab).hasClass("first")&&(e(n.newPanel).css("left","0px"),e(n.newPanel).parent().addClass("tabset-open")))) -}}),e(".cms-tree-view-sidebar .cms-actions-row.ss-tabset.ss-ui-action-tabset").entwine({"from .ui-tabs-nav li":{onhover:function d(t){e(t.target).parent().find("li .active").removeClass("active"),e(t.target).find("a").addClass("active") +}}),e(".cms-tree-view-sidebar .cms-actions-row.ss-tabset.ss-ui-action-tabset").entwine({"from .ui-tabs-nav li":{onhover:function c(t){e(t.target).parent().find("li .active").removeClass("active"),e(t.target).find("a").addClass("active") -}},ontabsbeforeactivate:function c(t,n){this._super(t,n),e(n.newPanel).css({left:"auto",right:"auto"}),e(n.newPanel).length>0&&e(n.newPanel).parent().addClass("tabset-open")}})})},function(e,t,n){"use strict" +}},ontabsbeforeactivate:function d(t,n){this._super(t,n),e(n.newPanel).css({left:"auto",right:"auto"}),e(n.newPanel).length>0&&e(n.newPanel).parent().addClass("tabset-open")}})})},function(e,t,n){"use strict" function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r) @@ -1226,7 +1226,7 @@ t||(this.trigger("beforetoggle.sspanel",e),this.trigger(e?"beforeexpand":"before r.length&&(this.find(".cms-panel-content")[e?"show":"hide"](),this.find(".cms-panel-content-collapsed")[e?"hide":"show"]()),n!==!1&&this.setPersistedCollapsedState(!e),this.trigger("toggle",e),this.trigger(e?"expand":"collapse") },expandPanel:function l(e){(e||this.hasClass("collapsed"))&&this.togglePanel(!0)},collapsePanel:function u(e){!e&&this.hasClass("collapsed")||this.togglePanel(!1)}}),e(".cms-panel.collapsed .cms-panel-toggle").entwine({ -onclick:function d(e){this.expandPanel(),e.preventDefault()}}),e(".cms-panel *").entwine({getPanel:function c(){return this.parents(".cms-panel:first")}}),e(".cms-panel .toggle-expand").entwine({onclick:function f(e){ +onclick:function c(e){this.expandPanel(),e.preventDefault()}}),e(".cms-panel *").entwine({getPanel:function d(){return this.parents(".cms-panel:first")}}),e(".cms-panel .toggle-expand").entwine({onclick:function f(e){ e.preventDefault(),e.stopPropagation(),this.getPanel().expandPanel(),this._super(e)}}),e(".cms-panel .toggle-collapse").entwine({onclick:function p(e){e.preventDefault(),e.stopPropagation(),this.getPanel().collapsePanel(), this._super(e)}}),e(".cms-content-tools.collapsed").entwine({onclick:function h(e){this.expandPanel(),this._super(e)}})})},function(e,t,n){"use strict" function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r) @@ -1241,17 +1241,17 @@ i.inst.hide_checkboxes()}).bind("before.jstree",function(t,i){if("start_drag"==i if(e.inArray(i.func,["check_node","uncheck_node"])){var r=e(i.args[0]).parents("li:first"),a=r.find("li:not(.disabled)") if(r.hasClass("disabled")&&0==a)return t.stopImmediatePropagation(),!1}}).bind("move_node.jstree",function(t,i){if(!n.getIsUpdatingTree()){var r=i.rslt.o,a=i.rslt.np,o=i.inst._get_parent(r),s=e(a).data("id")||0,l=e(r).data("id"),u=e.map(e(r).siblings().andSelf(),function(t){ return e(t).data("id")}) -e.ajax({url:e.path.addSearchParams(n.data("urlSavetreenode"),n.data("extraParams")),type:"POST",data:{ID:l,ParentID:s,SiblingIDs:u},success:function d(){e(".cms-edit-form :input[name=ID]").val()==l&&e(".cms-edit-form :input[name=ParentID]").val(s), -n.updateNodesFromServer([l])},statusCode:{403:function c(){e.jstree.rollback(i.rlbk)}}})}}).bind("select_node.jstree check_node.jstree uncheck_node.jstree",function(t,n){e(document).triggerHandler(t,n) +e.ajax({url:e.path.addSearchParams(n.data("urlSavetreenode"),n.data("extraParams")),type:"POST",data:{ID:l,ParentID:s,SiblingIDs:u},success:function c(){e(".cms-edit-form :input[name=ID]").val()==l&&e(".cms-edit-form :input[name=ParentID]").val(s), +n.updateNodesFromServer([l])},statusCode:{403:function d(){e.jstree.rollback(i.rlbk)}}})}}).bind("select_node.jstree check_node.jstree uncheck_node.jstree",function(t,n){e(document).triggerHandler(t,n) })}},onremove:function n(){this.jstree("destroy"),this._super()},"from .cms-container":{onafterstatechange:function i(e){this.updateFromEditForm()}},"from .cms-container form":{onaftersubmitform:function r(t){ var n=e(".cms-edit-form :input[name=ID]").val() this.updateNodesFromServer([n])}},getTreeConfig:function a(){var t=this -return{core:{initially_open:["record-0"],animation:0,html_titles:!0},html_data:{},ui:{select_limit:1,initially_select:[this.find(".current").attr("id")]},crrm:{move:{check_move:function n(i){var r=e(i.o),a=e(i.np),o=i.ot.get_container()[0]==i.np[0],s=r.getClassname(),l=a.getClassname(),u=t.getHints(),d=[],c=l?l:"Root",f=u&&"undefined"!=typeof u[c]?u[c]:null +return{core:{initially_open:["record-0"],animation:0,html_titles:!0},html_data:{},ui:{select_limit:1,initially_select:[this.find(".current").attr("id")]},crrm:{move:{check_move:function n(i){var r=e(i.o),a=e(i.np),o=i.ot.get_container()[0]==i.np[0],s=r.getClassname(),l=a.getClassname(),u=t.getHints(),c=[],d=l?l:"Root",f=u&&"undefined"!=typeof u[d]?u[d]:null -f&&r.attr("class").match(/VirtualPage-([^\s]*)/)&&(s=RegExp.$1),f&&(d="undefined"!=typeof f.disallowedChildren?f.disallowedChildren:[]) -var p=!(0===r.data("id")||r.hasClass("status-archived")||o&&"inside"!=i.p||a.hasClass("nochildren")||d.length&&e.inArray(s,d)!=-1) +f&&r.attr("class").match(/VirtualPage-([^\s]*)/)&&(s=RegExp.$1),f&&(c="undefined"!=typeof f.disallowedChildren?f.disallowedChildren:[]) +var p=!(0===r.data("id")||r.hasClass("status-archived")||o&&"inside"!=i.p||a.hasClass("nochildren")||c.length&&e.inArray(s,c)!=-1) return p}}},dnd:{drop_target:!1,drag_target:!1},checkbox:{two_state:!0},themes:{theme:"apple",url:e("body").data("frameworkpath")+"/admin/thirdparty/jstree/themes/apple/style.css"},plugins:["html_data","ui","dnd","crrm","themes","checkbox"] }},search:function o(e,t){e?this.data("searchparams",e):this.removeData("searchparams"),this.jstree("refresh",-1,t)},getNodeByID:function s(e){return this.find("*[data-id="+e+"]")},createNode:function l(t,n,i){ var r=this,a=void 0!==n.ParentID&&r.getNodeByID(n.ParentID),o=e(t),s={data:""} @@ -1262,10 +1262,10 @@ e.attr(r.name,r.value)}e.addClass(t).html(o.html()),i(e)})},updateNode:function e.each(["id","style","class","data-pagetype"],function(e,n){t.attr(n,a.attr(n))}) var u=t.children("ul").detach() -t.html(a.html()).append(u),o&&o.length?this.jstree("move_node",t,o,"before"):s&&s.length?this.jstree("move_node",t,s,"after"):this.jstree("move_node",t,l.length?l:-1)},updateFromEditForm:function d(){var t,n=e(".cms-edit-form :input[name=ID]").val() +t.html(a.html()).append(u),o&&o.length?this.jstree("move_node",t,o,"before"):s&&s.length?this.jstree("move_node",t,s,"after"):this.jstree("move_node",t,l.length?l:-1)},updateFromEditForm:function c(){var t,n=e(".cms-edit-form :input[name=ID]").val() -n?(t=this.getNodeByID(n),t.length?(this.jstree("deselect_all"),this.jstree("select_node",t)):this.updateNodesFromServer([n])):this.jstree("deselect_all")},updateNodesFromServer:function c(t){if(!this.getIsUpdatingTree()&&this.getIsLoaded()){ +n?(t=this.getNodeByID(n),t.length?(this.jstree("deselect_all"),this.jstree("select_node",t)):this.updateNodesFromServer([n])):this.jstree("deselect_all")},updateNodesFromServer:function d(t){if(!this.getIsUpdatingTree()&&this.getIsLoaded()){ var n=this,i,r=!1 this.setIsUpdatingTree(!0),n.jstree("save_selected") var a=function o(e){n.getNodeByID(e.data("id")).not(e).remove(),n.jstree("deselect_all"),n.jstree("select_node",e)} @@ -1306,10 +1306,10 @@ var n=t.closest(".ss-tabset") n.length||(n=t.closest(".cms-tabset")),n.length?n.tabs("option","active",t.index(".tab")):this.getValidationErrorShown()||(this.setValidationErrorShown(!0),s(ss.i18n._t("ModelAdmin.VALIDATIONERROR","Validation Error"))) }}},onremove:function i(){this.changetracker("destroy"),this._super()},onmatch:function r(){this._super()},onunmatch:function a(){this._super()},redraw:function l(){window.debug&&console.log("redraw",this.attr("class"),this.get(0)), -this.add(this.find(".cms-tabset")).redrawTabs(),this.find(".cms-content-header").redraw()},_setupChangeTracker:function u(){this.changetracker(this.getChangeTrackerOptions())},confirmUnsavedChanges:function d(){ +this.add(this.find(".cms-tabset")).redrawTabs(),this.find(".cms-content-header").redraw()},_setupChangeTracker:function u(){this.changetracker(this.getChangeTrackerOptions())},confirmUnsavedChanges:function c(){ if(this.trigger("beforesubmitform"),!this.is(".changed")||this.is(".discardchanges"))return!0 var e=confirm(o["default"]._t("LeftAndMain.CONFIRMUNSAVED")) -return e&&this.addClass("discardchanges"),e},onsubmit:function c(e,t){if("_blank"!=this.prop("target"))return t&&this.closest(".cms-container").submitForm(this,t),!1},validate:function f(){var e=!0 +return e&&this.addClass("discardchanges"),e},onsubmit:function d(e,t){if("_blank"!=this.prop("target"))return t&&this.closest(".cms-container").submitForm(this,t),!1},validate:function f(){var e=!0 return this.trigger("validate",{isValid:e}),e},"from .htmleditor":{oneditorinit:function p(t){var n=this,i=e(t.target).closest(".field.htmleditor"),r=i.find("textarea.htmleditor").getEditor().getInstance() @@ -1320,13 +1320,13 @@ this.saveFieldFocus(n.attr("id"))}},"from .cms-edit-form .dropdown .chosen-conta this.saveFieldFocus(n.attr("id"))}},"from .cms-container":{ontabstaterestored:function y(e){this.restoreFieldFocus()}},saveFieldFocus:function b(t){if("undefined"!=typeof window.sessionStorage&&null!==window.sessionStorage){ var n=e(this).attr("id"),i=[] if(i.push({id:n,selected:t}),i)try{window.sessionStorage.setItem(n,JSON.stringify(i))}catch(r){if(r.code===DOMException.QUOTA_EXCEEDED_ERR&&0===window.sessionStorage.length)return -throw r}}},restoreFieldFocus:function _(){if("undefined"!=typeof window.sessionStorage&&null!==window.sessionStorage){var t=this,n="undefined"!=typeof window.sessionStorage&&window.sessionStorage,i=n?window.sessionStorage.getItem(this.attr("id")):null,r=!!i&&JSON.parse(i),a,o=0!==this.find(".ss-tabset").length,s,l,u,d +throw r}}},restoreFieldFocus:function _(){if("undefined"!=typeof window.sessionStorage&&null!==window.sessionStorage){var t=this,n="undefined"!=typeof window.sessionStorage&&window.sessionStorage,i=n?window.sessionStorage.getItem(this.attr("id")):null,r=!!i&&JSON.parse(i),a,o=0!==this.find(".ss-tabset").length,s,l,u,c if(n&&r.length>0){if(e.each(r,function(n,i){t.is("#"+i.id)&&(a=e("#"+i.selected))}),e(a).length<1)return void this.focusFirstInput() if(s=e(a).closest(".ss-tabset").find(".ui-tabs-nav .ui-tabs-active .ui-tabs-anchor").attr("id"),l="tab-"+e(a).closest(".ss-tabset .ui-tabs-panel").attr("id"),o&&l!==s)return -u=e(a).closest(".togglecomposite"),u.length>0&&u.accordion("activate",u.find(".ui-accordion-header")),d=e(a).position().top,e(a).is(":visible")||(a="#"+e(a).closest(".field").attr("id"),d=e(a).position().top), -e(a).focus(),d>e(window).height()/2&&t.find(".cms-content-fields").scrollTop(d)}else this.focusFirstInput()}},focusFirstInput:function w(){this.find(':input:not(:submit)[data-skip-autofocus!="true"]').filter(":visible:first").focus() +u=e(a).closest(".togglecomposite"),u.length>0&&u.accordion("activate",u.find(".ui-accordion-header")),c=e(a).position().top,e(a).is(":visible")||(a="#"+e(a).closest(".field").attr("id"),c=e(a).position().top), +e(a).focus(),c>e(window).height()/2&&t.find(".cms-content-fields").scrollTop(c)}else this.focusFirstInput()}},focusFirstInput:function w(){this.find(':input:not(:submit)[data-skip-autofocus!="true"]').filter(":visible:first").focus() }}),e(".cms-edit-form .btn-toolbar input.action[type=submit], .cms-edit-form .btn-toolbar button.action").entwine({onclick:function C(e){return this.is(":disabled")?(e.preventDefault(),!1):this._super(e)===!1||e.defaultPrevented||e.isDefaultPrevented()?void 0:(this.parents("form").trigger("submit",[this]), e.preventDefault(),!1)}}),e(".cms-edit-form .btn-toolbar input.action[type=submit].ss-ui-action-cancel, .cms-edit-form .btn-toolbar button.action.ss-ui-action-cancel").entwine({onclick:function T(e){window.history.length>1?window.history.back():this.parents("form").trigger("submit",[this]), @@ -1348,9 +1348,9 @@ return t=void 0===n?r:n!==r&&i?n:r},onadd:function s(){var t=this setTimeout(function(){t.togglePanel(!t.getEvaluatedCollapsedState(),!1,!1)},0),e(window).on("ajaxComplete",function(e){setTimeout(function(){t.togglePanel(!t.getEvaluatedCollapsedState(),!1,!1)},0)}),this._super() }}),e(".cms-menu-list").entwine({onmatch:function l(){var e=this -this.find("li.current").select(),this.updateItems(),this._super()},onunmatch:function u(){this._super()},updateMenuFromResponse:function d(e){var t=e.getResponseHeader("X-Controller") +this.find("li.current").select(),this.updateItems(),this._super()},onunmatch:function u(){this._super()},updateMenuFromResponse:function c(e){var t=e.getResponseHeader("X-Controller") if(t){var n=this.find("li#Menu-"+t.replace(/\\/g,"-").replace(/[^a-zA-Z0-9\-_:.]+/,"")) -n.hasClass("current")||n.select()}this.updateItems()},"from .cms-container":{onafterstatechange:function c(e,t){this.updateMenuFromResponse(t.xhr)},onaftersubmitform:function f(e,t){this.updateMenuFromResponse(t.xhr) +n.hasClass("current")||n.select()}this.updateItems()},"from .cms-container":{onafterstatechange:function d(e,t){this.updateMenuFromResponse(t.xhr)},onaftersubmitform:function f(e,t){this.updateMenuFromResponse(t.xhr) }},"from .cms-edit-form":{onrelodeditform:function p(e,t){this.updateMenuFromResponse(t.xmlhttp)}},getContainingPanel:function h(){return this.closest(".cms-panel")},fromContainingPanel:{ontoggle:function m(t){ this.toggleClass("collapsed",e(t.target).hasClass("collapsed")),e(".cms-container").trigger("windowresize"),this.hasClass("collapsed")&&this.find("li.children.opened").removeClass("opened"),this.hasClass("collapsed")||e(".toggle-children.opened").closest("li").addClass("opened") @@ -1410,8 +1410,8 @@ return i&&this.find(".preview-size-selector").changeVisibleSize(this.getCurrentS },loadState:function o(e){if(this._supportsLocalStorage())return window.localStorage.getItem("cms-preview-state-"+e)},disablePreview:function l(){return this.setPendingURL(null),this._loadUrl("about:blank"), this._block(),this.changeMode("content",!1),this.setIsPreviewEnabled(!1),this},enablePreview:function u(){return this.getIsPreviewEnabled()||(this.setIsPreviewEnabled(!0),e.browser.msie&&e.browser.version.slice(0,3)<=7?this.changeMode("content"):this.changeMode(this.getDefaultMode(),!1)), -this},getOrAppendFontFixStyleElement:function d(){var t=e("#FontFixStyleElement") -return t.length||(t=e('').appendTo("head")),t},onadd:function c(){var t=this,n=this.find("iframe") +this},getOrAppendFontFixStyleElement:function c(){var t=e("#FontFixStyleElement") +return t.length||(t=e('').appendTo("head")),t},onadd:function d(){var t=this,n=this.find("iframe") n.addClass("center"),n.bind("load",function(){t._adjustIframeForPreview(),t._loadCurrentPage(),e(this).removeClass("loading")}),e.browser.msie&&8===parseInt(e.browser.version,10)&&n.bind("readystatechange",function(e){ @@ -1476,17 +1476,17 @@ return!!t&&e}),this.register("delete",function(e){var t=confirm(o["default"].inj num:e.length})) return!!t&&e}),this.register("restore",function(e){var t=confirm(o["default"].inject(o["default"]._t("CMSMAIN.BATCH_RESTORE_PROMPT","You have {num} page(s) selected.\n\nDo you really want to restore to stage?\n\nChildren of archived pages will be restored to the root level, unless those pages are also being restored."),{ num:e.length})) -return!!t&&e})},onadd:function u(){this.registerDefault(),this._super()},register:function d(e,t){this.trigger("register",{type:e,callback:t}) +return!!t&&e})},onadd:function u(){this.registerDefault(),this._super()},register:function c(e,t){this.trigger("register",{type:e,callback:t}) var n=this.getActions() -n[e]=t,this.setActions(n)},unregister:function c(e){this.trigger("unregister",{type:e}) +n[e]=t,this.setActions(n)},unregister:function d(e){this.trigger("unregister",{type:e}) var t=this.getActions() t[e]&&delete t[e],this.setActions(t)},refreshSelected:function f(n){var i=this,r=this.getTree(),a=this.getIDs(),o=[],s=t(".cms-content-batchactions-button"),l=this.find(":input[name=Action]").val() null==n&&(n=r) for(var u in a)t(t(r).getNodeByID(u)).addClass("selected").attr("selected","selected") if(!l||l==-1||!s.hasClass("active"))return void t(n).find("li").each(function(){t(this).setEnabled(!0)}) t(n).find("li").each(function(){o.push(t(this).data("id")),t(this).addClass("treeloading").setEnabled(!1)}) -var d=t.path.parseUrl(l),c=d.hrefNoSearch+"/applicablepages/" -c=t.path.addSearchParams(c,d.search),c=t.path.addSearchParams(c,{csvIDs:o.join(",")}),e.getJSON(c,function(r){e(n).find("li").each(function(){t(this).removeClass("treeloading") +var c=t.path.parseUrl(l),d=c.hrefNoSearch+"/applicablepages/" +d=t.path.addSearchParams(d,c.search),d=t.path.addSearchParams(d,{csvIDs:o.join(",")}),e.getJSON(d,function(r){e(n).find("li").each(function(){t(this).removeClass("treeloading") var e=t(this).data("id") 0==e||t.inArray(e,r)>=0?t(this).setEnabled(!0):(t(this).removeClass("selected").setEnabled(!1),t(this).prop("selected",!1))}),i.serializeFromTree()})},serializeFromTree:function p(){var e=this.getTree(),t=e.getSelectedIDs() @@ -1499,8 +1499,8 @@ if(!l)return n.preventDefault(),!1 var u=l.split("/").filter(function(e){return!!e}).pop() if(s[u]&&(r=s[u].apply(this,[r])),!r||!r.length)return n.preventDefault(),!1 this.setIDs(r),a.find("li").removeClass("failed") -var d=this.find(":submit:first") -return d.addClass("loading"),e.ajax({url:l,type:"POST",data:this.serializeArray(),complete:function c(e,t){d.removeClass("loading"),a.jstree("refresh",-1),i.setIDs([]),i.find(":input[name=Action]").val("").change() +var c=this.find(":submit:first") +return c.addClass("loading"),e.ajax({url:l,type:"POST",data:this.serializeArray(),complete:function d(e,t){c.removeClass("loading"),a.jstree("refresh",-1),i.setIDs([]),i.find(":input[name=Action]").val("").change() var n=e.getResponseHeader("X-Status") @@ -1530,13 +1530,13 @@ this.addClass("description-toggle-enabled"),n.on("click",function(){i[e?"hide":" function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r) a["default"].entwine("ss",function(e){e(".TreeDropdownField").entwine({"from .cms-container form":{onaftersubmitform:function t(e){this.find(".tree-holder").empty(),this._super()}}})})},function(e,t,n){ "use strict" -function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r),o=n(5),s=i(o),l=n(180),u=i(l),d=n(106),c=n(181),f=i(c) +function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r),o=n(5),s=i(o),l=n(180),u=i(l),c=n(106),d=n(181),f=i(d) a["default"].entwine("ss",function(e){e(".cms-content-actions .add-to-campaign-action,#add-to-campaign__action").entwine({onclick:function t(){var t=e("#add-to-campaign__dialog-wrapper") return t.length||(t=e('
'),e("body").append(t)),t.open(),!1}}),e("#add-to-campaign__dialog-wrapper").entwine({onunmatch:function n(){this._clearModal()},open:function i(){ -this._renderModal(!0)},close:function r(){this._renderModal(!1)},_renderModal:function a(t){var n=this,i=function h(){return n.close()},r=function m(){return n._handleSubmitModal.apply(n,arguments)},a=e("form.cms-edit-form :input[name=ID]").val(),o=window.ss.store,l="SilverStripe\\CMS\\Controllers\\CMSPageEditController",c=o.getState().config.sections[l],p=c.form.AddToCampaignForm.schemaUrl+"/"+a +this._renderModal(!0)},close:function r(){this._renderModal(!1)},_renderModal:function a(t){var n=this,i=function h(){return n.close()},r=function m(){return n._handleSubmitModal.apply(n,arguments)},a=e("form.cms-edit-form :input[name=ID]").val(),o=window.ss.store,l="SilverStripe\\CMS\\Controllers\\CMSPageEditController",d=o.getState().config.sections[l],p=d.form.AddToCampaignForm.schemaUrl+"/"+a -u["default"].render(s["default"].createElement(d.Provider,{store:o},s["default"].createElement(f["default"],{show:t,handleSubmit:r,handleHide:i,schemaUrl:p,bodyClassName:"modal__dialog",responseClassBad:"modal__response modal__response--error", +u["default"].render(s["default"].createElement(c.Provider,{store:o},s["default"].createElement(f["default"],{show:t,handleSubmit:r,handleHide:i,schemaUrl:p,bodyClassName:"modal__dialog",responseClassBad:"modal__response modal__response--error", responseClassGood:"modal__response modal__response--good"})),this[0])},_clearModal:function o(){u["default"].unmountComponentAtNode(this[0])},_handleSubmitModal:function l(e,t,n){return n()}})})},,function(e,t){ e.exports=FormBuilderModal},function(e,t,n){"use strict" function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r) @@ -1576,7 +1576,7 @@ function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r);(0,a["def },onunmatch:function s(){this._super()},onfileuploadadd:function l(e){this.find(".ss-uploadfield-editandorganize").show()},onfileuploadstart:function u(e){this.find(".ss-uploadfield-editandorganize").show() -}}),(0,a["default"])(".ss-uploadfield-view-allowed-extensions .toggle").entwine({onclick:function d(e){var t=this.closest(".ss-uploadfield-view-allowed-extensions"),n=this.closest(".ui-tabs-panel").height()+20 +}}),(0,a["default"])(".ss-uploadfield-view-allowed-extensions .toggle").entwine({onclick:function c(e){var t=this.closest(".ss-uploadfield-view-allowed-extensions"),n=this.closest(".ui-tabs-panel").height()+20 t.toggleClass("active"),t.find(".toggle-content").css("minHeight",n)}})},function(e,t,n){"use strict" @@ -1586,9 +1586,9 @@ return e.toggle("fast",function(){e.find('input[type="hidden"]').val(e.is(":visi function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r),o=n(114),s=i(o) window.tmpl=n(188),n(189),n(190),a["default"].widget("blueimpUIX.fileupload",a["default"].blueimpUI.fileupload,{_initTemplates:function l(){this.options.templateContainer=document.createElement(this._files.prop("nodeName")), this.options.uploadTemplate=window.tmpl(this.options.uploadTemplateName),this.options.downloadTemplate=window.tmpl(this.options.downloadTemplateName)},_enableFileInputButton:function u(){a["default"].blueimpUI.fileupload.prototype._enableFileInputButton.call(this), -this.element.find(".ss-uploadfield-addfile").show()},_disableFileInputButton:function d(){a["default"].blueimpUI.fileupload.prototype._disableFileInputButton.call(this),this.element.find(".ss-uploadfield-addfile").hide() +this.element.find(".ss-uploadfield-addfile").show()},_disableFileInputButton:function c(){a["default"].blueimpUI.fileupload.prototype._disableFileInputButton.call(this),this.element.find(".ss-uploadfield-addfile").hide() -},_onAdd:function c(e,t){var n=a["default"].blueimpUI.fileupload.prototype._onAdd.call(this,e,t),i=this._files.find(".ss-uploadfield-item").slice(t.files.length*-1).first(),r="+="+(i.position().top-parseInt(i.css("marginTop"),10)||0-parseInt(i.css("borderTopWidth"),10)||0) +},_onAdd:function d(e,t){var n=a["default"].blueimpUI.fileupload.prototype._onAdd.call(this,e,t),i=this._files.find(".ss-uploadfield-item").slice(t.files.length*-1).first(),r="+="+(i.position().top-parseInt(i.css("marginTop"),10)||0-parseInt(i.css("borderTopWidth"),10)||0) i.offsetParent().animate({scrollTop:r},1e3) @@ -1642,8 +1642,8 @@ o.children().hide(),o.append(a),e.ajax({type:"POST",url:r.urlAttach,data:{ids:t} files:t,options:i.fileupload("option"),replaceFileID:n})}})}}),e("div.ss-upload *").entwine({getUploadField:function a(){return this.parents("div.ss-upload:first")}}),e("div.ss-upload .ss-uploadfield-files .ss-uploadfield-item").entwine({ onadd:function o(){this._super(),this.closest(".ss-upload").find(".ss-uploadfield-addfile").addClass("borderTop")},onremove:function l(){e(".ss-uploadfield-files:not(:has(.ss-uploadfield-item))").closest(".ss-upload").find(".ss-uploadfield-addfile").removeClass("borderTop"), this._super()}}),e("div.ss-upload .ss-uploadfield-startall").entwine({onclick:function u(e){return this.closest(".ss-upload").find(".ss-uploadfield-item-start button").click(),e.preventDefault(),!1}}), -e("div.ss-upload .ss-uploadfield-item-cancelfailed").entwine({onclick:function d(e){return this.closest(".ss-uploadfield-item").remove(),e.preventDefault(),!1}}),e("div.ss-upload .ss-uploadfield-item-remove:not(.ui-state-disabled), .ss-uploadfield-item-delete:not(.ui-state-disabled)").entwine({ -onclick:function c(e){var t=this.closest("div.ss-upload"),n=t.getConfig("changeDetection"),i=t.data("fileupload"),r=this.closest(".ss-uploadfield-item"),a="" +e("div.ss-upload .ss-uploadfield-item-cancelfailed").entwine({onclick:function c(e){return this.closest(".ss-uploadfield-item").remove(),e.preventDefault(),!1}}),e("div.ss-upload .ss-uploadfield-item-remove:not(.ui-state-disabled), .ss-uploadfield-item-delete:not(.ui-state-disabled)").entwine({ +onclick:function d(e){var t=this.closest("div.ss-upload"),n=t.getConfig("changeDetection"),i=t.data("fileupload"),r=this.closest(".ss-uploadfield-item"),a="" return this.is(".ss-uploadfield-item-delete")?confirm(s["default"]._t("UploadField.ConfirmDelete"))&&(n.changeDetection&&this.closest("form").trigger("dirty"),i&&i._trigger("destroy",e,{context:r,url:this.data("href"), type:"get",dataType:i.options.dataType})):(n.changeDetection&&this.closest("form").trigger("dirty"),i&&i._trigger("destroy",e,{context:r})),e.preventDefault(),!1}}),e("div.ss-upload .ss-uploadfield-item-edit-all").entwine({ onclick:function f(t){return e(this).hasClass("opened")?(e(".ss-uploadfield-item .ss-uploadfield-item-edit .toggle-details-icon.opened").each(function(t){e(this).closest(".ss-uploadfield-item-edit").click() @@ -1694,11 +1694,10 @@ t.length&&t.removeClass("selected") var n=e.nextAll("li.selected") n.length&&n.removeClass("selected"),(0,a["default"])(this).focus()})})},function(e,t,n){"use strict" function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r) -n(166),a["default"].fn.extend({ssDatepicker:function o(e){return(0,a["default"])(this).each(function(){if(!((0,a["default"])(this).prop("disabled")||(0,a["default"])(this).prop("readonly")||(0,a["default"])(this).data("datepicker"))){ +n(166),a["default"].fn.extend({ssDatepicker:function o(e){return(0,a["default"])(this).each(function(){if(!((0,a["default"])(this).prop("disabled")||(0,a["default"])(this).prop("readonly")||(0,a["default"])(this).hasClass("hasDatepicker"))){ (0,a["default"])(this).siblings("button").addClass("ui-icon ui-icon-calendar") -var t=(0,a["default"])(this).closest(".field.date"),n=a["default"].extend(e||{},(0,a["default"])(this).data(),(0,a["default"])(this).data("jqueryuiconfig"),{}) -n.showcalendar&&(n.locale&&a["default"].datepicker.regional[n.locale]&&(n=a["default"].extend(n,a["default"].datepicker.regional[n.locale],{})),n.min&&(n.minDate=a["default"].datepicker.parseDate("yy-mm-dd",n.min)), -n.max&&(n.maxDate=a["default"].datepicker.parseDate("yy-mm-dd",n.max)),n.dateFormat=n.jquerydateformat,(0,a["default"])(this).datepicker(n))}})}}),(0,a["default"])(document).on("click",".field.date input.text,input.text.date",function(){ +var t=a["default"].extend({},e||{},(0,a["default"])(this).data(),(0,a["default"])(this).data("jqueryuiconfig")) +t.showcalendar&&(t.locale&&a["default"].datepicker.regional[t.locale]&&(t=a["default"].extend({},a["default"].datepicker.regional[t.locale],t)),(0,a["default"])(this).datepicker(t))}})}}),(0,a["default"])(document).on("click",".field.date input.text,input.text.date",function(){ (0,a["default"])(this).ssDatepicker(),(0,a["default"])(this).data("datepicker")&&(0,a["default"])(this).datepicker("show")})},function(e,t,n){"use strict" function i(e){return e&&e.__esModule?e:{"default":e}}var r=n(1),a=i(r) n(166),a["default"].entwine("ss",function(e){e(".ss-toggle").entwine({onadd:function t(){this._super(),this.accordion({heightStyle:"content",collapsible:!0,active:!this.hasClass("ss-toggle-start-closed")&&0 @@ -1723,12 +1722,12 @@ t(".TreeDropdownField").entwine({CurrentXhr:null,onadd:function l(){this.append( var e=r.openLink e&&this.find("treedropdownfield-toggle-panel-link a").attr("title",e),this.data("title")&&this.setTitle(this.data("title")),this.getPanel().hide(),this._super()},getPanel:function u(){return this.find(".treedropdownfield-panel") -},openPanel:function d(){t(".TreeDropdownField").closePanel(),t("body").bind("click",a) +},openPanel:function c(){t(".TreeDropdownField").closePanel(),t("body").bind("click",a) var e=this.getPanel(),n=this.find(".tree-holder") e.css("width",this.width()),e.show() var i=this.find(".treedropdownfield-toggle-panel-link") i.addClass("treedropdownfield-open-tree"),this.addClass("treedropdownfield-open-tree"),i.find("a").removeClass("ui-icon-triangle-1-s").addClass("ui-icon-triangle-1-n"),n.is(":empty")&&!e.hasClass("loading")?this.loadTree(null,this._riseUp):this._riseUp(), -this.trigger("panelshow")},_riseUp:function c(){var e=this,n=this.getPanel(),i=this.find(".treedropdownfield-toggle-panel-link"),r=i.innerHeight(),a,o,s +this.trigger("panelshow")},_riseUp:function d(){var e=this,n=this.getPanel(),i=this.find(".treedropdownfield-toggle-panel-link"),r=i.innerHeight(),a,o,s i.length>0&&(s=t(window).height()+t(document).scrollTop()-i.innerHeight(),o=i.offset().top,a=n.innerHeight(),o+a>s&&o-a>0?(e.addClass("treedropdownfield-with-rise"),r=-n.outerHeight()):e.removeClass("treedropdownfield-with-rise")), n.css({top:r+"px"})},closePanel:function f(){e("body").unbind("click",a) var t=this.find(".treedropdownfield-toggle-panel-link") @@ -1788,9 +1787,9 @@ return{init:function e(t){editorID=t,this.create()},destroy:function t(){tinymce },onopen:function i(){},onclose:function r(){},getConfig:function a(){var e="#"+editorID,t=(0,_jQuery2["default"])(e).data("config"),n=this return t.selector=e,t.setup=function(e){e.on("change",function(){n.save()})},t},save:function o(){var e=this.getInstance() e.save(),(0,_jQuery2["default"])(e.getElement()).trigger("change")},create:function s(){var e=this.getConfig() -"undefined"!=typeof e.baseURL&&(tinymce.EditorManager.baseURL=e.baseURL),tinymce.init(e)},repaint:function l(){},isDirty:function u(){return this.getInstance().isDirty()},getContent:function d(){return this.getInstance().getContent() +"undefined"!=typeof e.baseURL&&(tinymce.EditorManager.baseURL=e.baseURL),tinymce.init(e)},repaint:function l(){},isDirty:function u(){return this.getInstance().isDirty()},getContent:function c(){return this.getInstance().getContent() -},getDOM:function c(){return this.getInstance().getElement()},getContainer:function f(){return this.getInstance().getContainer()},getSelectedNode:function p(){return this.getInstance().selection.getNode() +},getDOM:function d(){return this.getInstance().getElement()},getContainer:function f(){return this.getInstance().getContainer()},getSelectedNode:function p(){return this.getInstance().selection.getNode() },selectNode:function h(e){this.getInstance().selection.select(e)},setContent:function m(e,t){this.getInstance().setContent(e,t)},insertContent:function g(e,t){this.getInstance().insertContent(e,t)},replaceContent:function v(e,t){ this.getInstance().execCommand("mceReplaceContent",!1,e,t)},insertLink:function y(e,t){this.getInstance().execCommand("mceInsertLink",!1,e,t)},removeLink:function b(){this.getInstance().execCommand("unlink",!1) @@ -1813,7 +1812,7 @@ o.length?(o.getForm().setElement(this),o.html(""),o.addClass("loading"),o.open() o.removeClass("loading")},success:function u(e){o.html(e),o.getForm().setElement(r),o.trigger("ssdialogopen")}})}}),e(".htmleditorfield-dialog").entwine({onadd:function s(){this.is(".ui-dialog-content")||this.ssdialog({ autoOpen:!0,buttons:{insert:{text:_i18n2["default"]._t("HtmlEditorField.INSERT","Insert"),"data-icon":"accept","class":"btn action btn-primary media-insert",click:function t(){e(this).find("form").submit() -}}}}),this._super()},getForm:function l(){return this.find("form")},open:function u(){this.ssdialog("open")},close:function d(){this.ssdialog("close")},toggle:function c(e){this.is(":visible")?this.close():this.open() +}}}}),this._super()},getForm:function l(){return this.find("form")},open:function u(){this.ssdialog("open")},close:function c(){this.ssdialog("close")},toggle:function d(e){this.is(":visible")?this.close():this.open() },onscroll:function f(){this.animate({scrollTop:this.find("form").height()},500)}}),e("form.htmleditorfield-form").entwine({Selection:null,Bookmark:null,Element:null,setSelection:function p(t){return this._super(e(t)) @@ -1863,7 +1862,7 @@ if(a&&a.length)for(var o=0;o").append(e("").attr({href:this.data("url")}).text(this.find(".name").text())).html()},insertHTML:function le(e){ -e.replaceContent(this.getHTML())},updateFromNode:function ue(e){},updateDimensions:function de(e,t,n){var i=this.find(":input[name=Width]"),r=this.find(":input[name=Height]"),a=i.val(),o=r.val(),s +e.replaceContent(this.getHTML())},updateFromNode:function ue(e){},updateDimensions:function ce(e,t,n){var i=this.find(":input[name=Width]"),r=this.find(":input[name=Height]"),a=i.val(),o=r.val(),s a&&o&&(e?(s=r.getOrigVal()/i.getOrigVal(),"Width"==e?(t&&a>t&&(a=t),o=Math.floor(a*s)):"Height"==e&&(n&&o>n&&(o=n),a=Math.ceil(o/s))):(t&&a>t&&(a=t),n&&o>n&&(o=n)),i.val(a),r.val(o))}}),e("form.htmleditorfield-mediaform .ss-htmleditorfield-file.image").entwine({ -getAttributes:function ce(){var e=this.find(":input[name=Width]").val(),t=this.find(":input[name=Height]").val() +getAttributes:function de(){var e=this.find(":input[name=Width]").val(),t=this.find(":input[name=Height]").val() return{src:this.find(":input[name=URL]").val(),alt:this.find(":input[name=AltText]").val(),width:e?parseInt(e,10):null,height:t?parseInt(t,10):null,title:this.find(":input[name=Title]").val(),"class":this.find(":input[name=CSSClass]").val(), "data-id":this.find(":input[name=FileID]").val()}},getExtraData:function fe(){return{CaptionText:this.find(":input[name=CaptionText]").val()}},getHTML:function pe(){},insertHTML:function he(t){var n=this.closest("form"),i=n.getSelection() @@ -1946,8 +1945,8 @@ var l=s.parent(".captionImage"),u=l.find(".caption") a.CaptionText?(l.length||(l=e("
")),l.attr("class","captionImage "+r["class"]).css("width",r.width),u.length||(u=e('

').appendTo(l)),u.attr("class","caption "+r["class"]).text(a.CaptionText)):l=u=null -var d=l?l:s -o&&o.not(d).length&&o.replaceWith(d),l&&l.prepend(s),o||(t.repaint(),t.insertContent(e("
").append(d).html(),{skip_undo:1})),t.addUndo(),t.repaint()},updateFromNode:function me(e){this.find(":input[name=AltText]").val(e.attr("alt")), +var c=l?l:s +o&&o.not(c).length&&o.replaceWith(c),l&&l.prepend(s),o||(t.repaint(),t.insertContent(e("
").append(c).html(),{skip_undo:1})),t.addUndo(),t.repaint()},updateFromNode:function me(e){this.find(":input[name=AltText]").val(e.attr("alt")), this.find(":input[name=Title]").val(e.attr("title")),this.find(":input[name=CSSClass]").val(e.attr("class")),this.find(":input[name=Width]").val(e.width()),this.find(":input[name=Height]").val(e.height()), this.find(":input[name=CaptionText]").val(e.siblings(".caption:first").text()),this.find(":input[name=FileID]").val(e.data("id"))}}),e("form.htmleditorfield-mediaform .ss-htmleditorfield-file.flash").entwine({ getAttributes:function ge(){var e=this.find(":input[name=Width]").val(),t=this.find(":input[name=Height]").val() @@ -2012,8 +2011,8 @@ var l=s.parent(".captionImage"),u=l.find(".caption") a.CaptionText?(l.length||(l=e("
")),l.attr("class","captionImage "+r["class"]).css("width",r.width),u.length||(u=e('

').appendTo(l)),u.attr("class","caption "+r["class"]).text(a.CaptionText)):l=u=null -var d=l||s -return o&&o.not(d).length&&o.replaceWith(d),l&&l.prepend(s),o||(n.repaint(),n.insertContent(e("
").append(d).html(),{skip_undo:1})),n.addUndo(),n.repaint(),!0},statusMessage:function Ve(t,n){var i=e("
").text(t).html() +var c=l||s +return o&&o.not(c).length&&o.replaceWith(c),l&&l.prepend(s),o||(n.repaint(),n.insertContent(e("
").append(c).html(),{skip_undo:1})),n.addUndo(),n.repaint(),!0},statusMessage:function Ve(t,n){var i=e("
").text(t).html() e.noticeAdd({text:i,type:n,stayTime:5e3,inEffect:{left:"0",opacity:"show"}})}})})},function(e,t){e.exports=ReactApollo},function(e,t,n){"use strict" @@ -2033,14 +2032,14 @@ n(166),n(196),a["default"].entwine("ss",function(e){e(".grid-field").entwine({re n||(n={}),n.data||(n.data=[]),n.data=n.data.concat(l),window.location.search&&(n.data=window.location.search.replace(/^\?/,"")+"&"+e.param(n.data)),a.addClass("loading"),e.ajax(e.extend({},{headers:{"X-Pjax":"CurrentField" },type:"POST",url:this.data("url"),dataType:"html",success:function u(t){if(r.empty().append(e(t).children()),o&&r.find(':input[name="'+o+'"]').focus(),r.find(".filter-header").length){var s "show"==n.data[0].filter?(s='',r.addClass("show-filter").find(".filter-header").show()):(s='', -r.removeClass("show-filter").find(".filter-header").hide()),r.find(".sortable-header th:last").html(s)}a.removeClass("loading"),i&&i.apply(this,arguments),r.trigger("reload",r)},error:function d(e){alert(s["default"]._t("GRIDFIELD.ERRORINTRANSACTION")), +r.removeClass("show-filter").find(".filter-header").hide()),r.find(".sortable-header th:last").html(s)}a.removeClass("loading"),i&&i.apply(this,arguments),r.trigger("reload",r)},error:function c(e){alert(s["default"]._t("GRIDFIELD.ERRORINTRANSACTION")), a.removeClass("loading")}},n))},showDetailView:function n(e){window.location.href=e},getItems:function i(){return this.find(".ss-gridfield-item")},setState:function r(e,t){var n=this.getState() n[e]=t,this.find(':input[name="'+this.data("name")+'[GridState]"]').val(JSON.stringify(n))},getState:function a(){return JSON.parse(this.find(':input[name="'+this.data("name")+'[GridState]"]').val())}}), e(".grid-field *").entwine({getGridField:function o(){return this.closest(".grid-field")}}),e(".grid-field :button[name=showFilter]").entwine({onclick:function l(e){this.closest(".grid-field__table").find(".filter-header").show().find(":input:first").focus(), this.closest(".grid-field").addClass("show-filter"),this.parent().html(''),e.preventDefault()}}),e(".grid-field .ss-gridfield-item").entwine({onclick:function u(t){if(e(t.target).closest(".action").length)return this._super(t), !1 var n=this.find(".edit-link") -n.length&&this.getGridField().showDetailView(n.prop("href"))},onmouseover:function d(){this.find(".edit-link").length&&this.css("cursor","pointer")},onmouseout:function c(){this.css("cursor","default") +n.length&&this.getGridField().showDetailView(n.prop("href"))},onmouseover:function c(){this.find(".edit-link").length&&this.css("cursor","pointer")},onmouseout:function d(){this.css("cursor","default") }}),e(".grid-field .action.action_import:button").entwine({onclick:function f(e){e.preventDefault(),this.openmodal()},onmatch:function p(){this._super(),"open"===this.data("state")&&this.openmodal()},onunmatch:function h(){ this._super()},openmodal:function m(){var t=e(this.data("target")),n=e(this.data("modal")) @@ -2095,21 +2094,21 @@ t.use([{applyMiddleware:function k(e,t){var n=(0,A.printRequest)(e.request) e.options.headers=o({},e.options.headers,{"Content-Type":"application/x-www-form-urlencoded;charset=UTF-8"}),e.options.body=M["default"].stringify(o({},n,{variables:JSON.stringify(n.variables)})),t()}}]), v["default"].add("config",w["default"]),v["default"].add("form",f.reducer),v["default"].add("schemas",T["default"]),v["default"].add("records",P["default"]),v["default"].add("campaign",S["default"]),v["default"].add("breadcrumbs",j["default"]), v["default"].add("routing",p.routerReducer),v["default"].add("apollo",n.reducer()),R["default"].start() -var i={},r=(0,u.combineReducers)(v["default"].getAll()),a=[c["default"],n.middleware()],s=m["default"].get("environment"),d=m["default"].get("debugging"),h=u.applyMiddleware.apply(void 0,a),g=window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__,y=window.__REDUX_DEVTOOLS_EXTENSION__||window.devToolsExtension +var i={},r=(0,u.combineReducers)(v["default"].getAll()),a=[d["default"],n.middleware()],s=m["default"].get("environment"),c=m["default"].get("debugging"),h=u.applyMiddleware.apply(void 0,a),g=window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__,y=window.__REDUX_DEVTOOLS_EXTENSION__||window.devToolsExtension -"dev"===s&&d&&("function"==typeof g?h=g(u.applyMiddleware.apply(void 0,a)):"function"==typeof y&&(h=(0,u.compose)(u.applyMiddleware.apply(void 0,a),y()))) +"dev"===s&&c&&("function"==typeof g?h=g(u.applyMiddleware.apply(void 0,a)):"function"==typeof y&&(h=(0,u.compose)(u.applyMiddleware.apply(void 0,a),y()))) var _=h(u.createStore),C=_(r,i) C.dispatch(b.setConfig(m["default"].getAll())),window.ss=window.ss||{},window.ss.store=C,window.ss=window.ss||{},window.ss.apolloClient=n var E=new l["default"](C,n) E.start(window.location.pathname),window.jQuery&&window.jQuery("body").addClass("js-react-boot")}var o=Object.assign||function(e){for(var t=1;t-1}).map(function(e){return""+e.value }) -n.props.onChange(i)}()}},{key:"getOptionProps",value:function d(e,t){var n=this.getValues(),i=this.getItemKey(e,t) +n.props.onChange(i)}()}},{key:"getOptionProps",value:function c(e,t){var n=this.getValues(),i=this.getItemKey(e,t) return{key:i,id:i,name:this.props.name,className:this.props.itemClass,disabled:e.disabled||this.props.disabled,readOnly:this.props.readOnly,onChange:this.handleChange,value:n.indexOf(""+e.value)>-1,title:e.title, -type:"checkbox"}}},{key:"render",value:function c(){var e=this -return this.props.source?u["default"].createElement("div",null,this.props.source.map(function(t,n){return u["default"].createElement(p["default"],e.getOptionProps(t,n))})):null}}]),t}(c["default"]) +type:"checkbox"}}},{key:"render",value:function d(){var e=this +return this.props.source?u["default"].createElement("div",null,this.props.source.map(function(t,n){return u["default"].createElement(p["default"],e.getOptionProps(t,n))})):null}}]),t}(d["default"]) g.propTypes={className:u["default"].PropTypes.string,extraClass:u["default"].PropTypes.string,itemClass:u["default"].PropTypes.string,id:u["default"].PropTypes.string,name:u["default"].PropTypes.string.isRequired, source:u["default"].PropTypes.arrayOf(u["default"].PropTypes.shape({value:u["default"].PropTypes.oneOfType([u["default"].PropTypes.string,u["default"].PropTypes.number]),title:u["default"].PropTypes.any, disabled:u["default"].PropTypes.bool})),onChange:u["default"].PropTypes.func,value:u["default"].PropTypes.any,readOnly:u["default"].PropTypes.bool,disabled:u["default"].PropTypes.bool},g.defaultProps={ @@ -2275,7 +2274,7 @@ return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function o(e,t){if("funct e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{ value:!0}),t.OptionsetField=void 0 var s=function(){function e(e,t){for(var n=0;ngetManifest()->getOwnerModule($cmsClass); $entities["{$cmsClass}.MENUTITLE"] = [ 'default' => $defaultTitle, 'module' => $ownerModule diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index faf6f1c6c46..66baed982b0 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -23,6 +23,7 @@ use SilverStripe\Dev\Deprecation; use SilverStripe\Forms\Form; use SilverStripe\Forms\HiddenField; +use SilverStripe\Forms\HTMLEditor\TinyMCEConfig; use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\FormAction; use SilverStripe\Forms\FieldList; @@ -576,7 +577,7 @@ protected function init() // Set default values in the config if missing. These things can't be defined in the config // file because insufficient information exists when that is being processed $htmlEditorConfig = HTMLEditorConfig::get_active(); - $htmlEditorConfig->setOption('language', i18n::get_tinymce_lang()); + $htmlEditorConfig->setOption('language', TinyMCEConfig::get_tinymce_lang()); Requirements::customScript(" window.ss = window.ss || {}; diff --git a/admin/themes/cms-forms/templates/SilverStripe/Forms/DatetimeField.ss b/admin/themes/cms-forms/templates/SilverStripe/Forms/DatetimeField.ss index a9d474b8819..e028d698fc1 100644 --- a/admin/themes/cms-forms/templates/SilverStripe/Forms/DatetimeField.ss +++ b/admin/themes/cms-forms/templates/SilverStripe/Forms/DatetimeField.ss @@ -1,5 +1,5 @@ $DateField.SmallFieldHolder $TimeField.SmallFieldHolder -<% if $HasTimezone %> +<% if $TimeZone %> $TimezoneField.Field <% end_if %> diff --git a/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md b/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md index 3de53d88f1b..d02519acadf 100644 --- a/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md +++ b/docs/en/02_Developer_Guides/03_Forms/Field_types/02_DateField.md @@ -37,7 +37,7 @@ A custom date format for a [api:DateField] can be provided through `setConfig`. :::php // will display a date in the following format: 31-06-2012 - DateField::create('MyDate')->setConfig('dateformat', 'dd-MM-yyyy'); + DateField::create('MyDate')->setDateFormat('dd-MM-yyyy');
The formats are based on [Zend_Date constants](http://framework.zend.com/manual/1.12/en/zend.date.constants.html). diff --git a/docs/en/04_Changelogs/4.0.0.md b/docs/en/04_Changelogs/4.0.0.md index 839b4155156..1938f200c2d 100644 --- a/docs/en/04_Changelogs/4.0.0.md +++ b/docs/en/04_Changelogs/4.0.0.md @@ -941,6 +941,7 @@ specific functions. service name from the filename. Now the service name will either by the array key, or the `class` parameter value. * Uniqueness checks for `File.Name` is performed on write only (not in `setName()`) +* Created `Resettable` interface to better declare objects which should be reset between tests. #### General and Core Removed API @@ -1051,6 +1052,26 @@ A very small number of methods were chosen for deprecation, and will be removed * `ChangeSet` and `ChangeSetItem` have been added for batch publishing of versioned dataobjects. * `DataObject.table_name` config can now be used to customise the database table for any record. * `DataObjectSchema` class added to assist with mapping between classes and tables. +* `DBMoney` values are now treated as empty only Amount is null. Values without Currency + will be formatted in the default locale. + +The below methods have been added or had their functionality updated to `DBDate`, `DBTime` and `DBDatetime` +* `getTimestamp()` added to get the respective date / time as unix timestamp (seconds since 1970-01-01) +* `Format()` method now use CLDR format strings, rather than PHP format strings. + See http://userguide.icu-project.org/formatparse/datetime. +* getISOFormat() added which returns the standard date/time ISO 8601 pattern in CLDR format. +* Dates passed in m/d/y format will now raise a notice but will be parsed. + Dates passed to constructors should follow ISO 8601 (y-m-d). +* 2-digit years will raise a notice. +* `FormatFromSettings` will default to `Nice()` format if no member is logged in. +* `Nice`, `Long` and `Full` methods will now follow standard formatting rules for the + current locale, rather than pre-defined formats. +* `Short` added to format concise date/time values, as a shorter version than `Nice` +* `getFormatter` method added, which returns a locale-specific date/time formatter. + +`DBTime` specific changes: +* Added `DBTime::FormatFromSettings` +* Added `DBTime::Nice12` #### ORM Removed API @@ -1085,6 +1106,24 @@ A very small number of methods were chosen for deprecation, and will be removed * `DataObject::validateModelDefinitions()` has been removed. Validation and parsing of config is now handled within `DataObjectSchema`. * `CMSBatchAction_Delete` removed. Use `CMSBatchAction_Archive` instead. +* Removed several DBDate methods: + - `past_date` + - `prior_monday` + - `weekday` + - `next_day` + - `day_before` + - `days_between` +* `nice_format` has been removed from `DBDate` / `DBTime` / `DBDatetime` has been removed in favour of + locale-specific formatting for Nice() +* Removed `DBTime::TwelveHour` +* Removed some `DBMoney` methods due to lack of support in php-intl. + - `NiceWithShortname` + - `NiceWithName` + - `getShortName` + - `getCurrencyName` +* Removed additional arguments from `DBMoney::getSymbol`. The result of this value is + now localised based on the currency code assigned to the `DBMoney` instance +* Removed `DBMoney::getAllowedCurrencies`. Apply validation to `MoneyField` instead. ### Filesystem API @@ -1275,6 +1314,28 @@ handle field-level and form-level messages. This has the following properties: * `$message` second constructor parameter is removed. Constructor only accepts `$result`, which may be a string, and optional `$code` + +New `DatetimeField` methods replace `getConfig()` / `setConfig()`: + +* `getTimezone()` / `setTimezone()` +* `getDateTimeOrder()` / `setDateTimeOrder()` +* `getLocale()` / `setLocale()` + +New `DateField` methods replace `getConfig()` / `setConfig()`: + +* `getShowCalendar()` / `setShowCalendar()` +* `getDateFormat()` / `setShowCalendar()` +* `getMinDate()` / `setMinDate()` +* `getMaxDate()` / `setMaxDate()` +* `getPlaceholders()` / `setPlaceholders()` +* `getSeparateDMYFields()` / `setSeparateDMYFields()` +* `getClientLocale` / `setClientLocale` +* `getLocale()` / `setLocale()` + +New `TimeField` methods replace `getConfig()` / `setConfig()` + +* `getTimeFormat()` / `setTimeFormat()` +* `getLocale()` / `setLocale()` #### Template and Form Removed API @@ -1307,6 +1368,8 @@ handle field-level and form-level messages. This has the following properties: * Removed `PermissionCheckboxSetField::getAssignedPermissionCodes()` (never implemented) * `Requirements::delete_combined_files()` and `Requirements::delete_combined_files()` methods have been removed as they are obsolete. +* Removed `DatetimeField`, `DateField` and `TimeField` methods `getConfig` and `setConfig`. Individual + getters and setters for individual options are provided instead. See above for list of new methods. ### i18n API @@ -1320,12 +1383,30 @@ handle field-level and form-level messages. This has the following properties: for all DataObject subclasses, rather than just the basename without namespace. * i18n key for locale-respective pluralisation rules added as '.PLURALS'. These can be configured within yaml in array format as per [ruby i18n pluralization rules](http://guides.rubyonrails.org/i18n.html#pluralization). +* `i18n.all_locales` config moved to `Locales.locales` +* `i18n.common_languages` config moved to `Locales.languages` +* `i18n.likely_subtags` config moved to `Locales.likely_subtags` +* `i18n.tinymce_lang` config moved to `TinyMCEConfig.tinymce_lang` +* `i18n::get_tinymce_lang()` moved to `TinyMCEConfig::get_tinymce_lang()` +* `i18n::get_locale_from_lang()` moved to `Locales::localeFromLang()` +* `i18n::get_lange_from_locale()` moved to `Locales::langFromLocale()` +* `i18n::validate_locale()` moved to `Locales::validate()` +* `i18n::get_common_languages()` moved to `Locales::getLanguages()` +* `i18n::get_locale_name()` moved to `Locales::localeName()` +* `i18n::get_language_name()` moved to `Locales::languageName()` +* `i18n.module_priority` config moved to `Sources.module_priority` +* `i18n::get_owner_module()` moved to `ClassManifest::getOwnerModule()` +* `i18n::get_existing_translations()` moved to `Sources::getKnownLocales()` #### i18n API Removed API * `Zend_Translate` removed -* `i18n::_t` Support for sprintf-style `%s` arguments deprecated -* `i18n::_t` Using non-associative injection with named parameters is now an error +* `i18n::_t()` Support for sprintf-style `%s` arguments deprecated +* `i18n::_t()` Using non-associative injection with named parameters is now an error +* `i18n::get_language_name()` removed. +* `i18n::get_language_code()` removed. +* `i18n::get_common_locales()` removed. +* `i18n.common_locales` config removed ### Email and Mailer diff --git a/src/Control/CookieJar.php b/src/Control/CookieJar.php index e1a4ffadfaa..33ac83675b1 100644 --- a/src/Control/CookieJar.php +++ b/src/Control/CookieJar.php @@ -81,7 +81,7 @@ public function set($name, $value, $expiry = 90, $path = null, $domain = null, $ //expiry === 0 is a special case where we set a cookie for the current user session if ($expiry !== 0) { //don't do the maths if we are clearing - $expiry = $clear ? -1 : DBDatetime::now()->Format('U') + (86400 * $expiry); + $expiry = $clear ? -1 : DBDatetime::now()->getTimestamp() + (86400 * $expiry); } //set the path up $path = $path ? $path : Director::baseURL(); diff --git a/src/Control/Director.php b/src/Control/Director.php index ba8fc2ac503..67a0f649691 100644 --- a/src/Control/Director.php +++ b/src/Control/Director.php @@ -733,7 +733,7 @@ public static function setBaseURL($baseURL) */ public static function baseFolder() { - $alternate = Config::inst()->get('SilverStripe\\Control\\Director', 'alternate_base_folder'); + $alternate = static::config()->get('alternate_base_folder'); return ($alternate) ? $alternate : BASE_PATH; } diff --git a/src/Control/Email/Email.php b/src/Control/Email/Email.php index 44c0f97c87a..69e8545c33b 100644 --- a/src/Control/Email/Email.php +++ b/src/Control/Email/Email.php @@ -196,7 +196,7 @@ public function getSwiftMessage() */ public function setSwiftMessage($swiftMessage) { - $swiftMessage->setDate(DBDatetime::now()->Format('U')); + $swiftMessage->setDate(DBDatetime::now()->getTimestamp()); if (!$swiftMessage->getFrom() && ($defaultFrom = $this->config()->admin_email)) { $swiftMessage->setFrom($defaultFrom); } diff --git a/src/Control/FlushRequestFilter.php b/src/Control/FlushRequestFilter.php index 13678d80882..594f5aaacea 100644 --- a/src/Control/FlushRequestFilter.php +++ b/src/Control/FlushRequestFilter.php @@ -2,6 +2,7 @@ namespace SilverStripe\Control; +use SilverStripe\Core\Flushable; use SilverStripe\ORM\DataModel; use SilverStripe\Core\ClassInfo; @@ -23,7 +24,7 @@ class FlushRequestFilter implements RequestFilter public function preRequest(HTTPRequest $request, Session $session, DataModel $model) { if (array_key_exists('flush', $request->getVars())) { - foreach (ClassInfo::implementorsOf('SilverStripe\\Core\\Flushable') as $class) { + foreach (ClassInfo::implementorsOf(Flushable::class) as $class) { $class::flush(); } } diff --git a/src/Core/Manifest/ClassManifest.php b/src/Core/Manifest/ClassManifest.php index 51a689791fd..6a6acd1fe0a 100644 --- a/src/Core/Manifest/ClassManifest.php +++ b/src/Core/Manifest/ClassManifest.php @@ -3,6 +3,7 @@ namespace SilverStripe\Core\Manifest; use Exception; +use SilverStripe\Control\Director; /** * A utility class which builds a manifest of all classes, interfaces and some @@ -354,6 +355,32 @@ public function getModules() return $modules; } + /** + * Get module that owns this class + * + * @param string $class Class name + * @return string + */ + public function getOwnerModule($class) + { + $path = realpath($this->getItemPath($class)); + if (!$path) { + return null; + } + + // Find based on loaded modules + foreach ($this->getModules() as $parent => $module) { + if (stripos($path, realpath($parent)) === 0) { + return $module; + } + } + + // Assume top level folder is the module name + $relativePath = substr($path, strlen(realpath(Director::baseFolder()))); + $parts = explode('/', trim($relativePath, '/')); + return array_shift($parts); + } + /** * Used to set up files that we want to exclude from parsing for performance reasons. */ diff --git a/src/Core/Resettable.php b/src/Core/Resettable.php new file mode 100644 index 00000000000..2b04f92f180 --- /dev/null +++ b/src/Core/Resettable.php @@ -0,0 +1,17 @@ +setSession(Session::create(array())); } diff --git a/src/Forms/CheckboxField.php b/src/Forms/CheckboxField.php index 1e628fbca99..5050f5a79b1 100644 --- a/src/Forms/CheckboxField.php +++ b/src/Forms/CheckboxField.php @@ -9,8 +9,8 @@ class CheckboxField extends FormField { protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_BOOLEAN; - - public function setValue($value) + + public function setValue($value, $data = null) { $this->value = ($value) ? 1 : 0; return $this; diff --git a/src/Forms/CountryDropdownField.php b/src/Forms/CountryDropdownField.php index 8b4d194a0bb..882e787443c 100644 --- a/src/Forms/CountryDropdownField.php +++ b/src/Forms/CountryDropdownField.php @@ -4,8 +4,6 @@ use SilverStripe\i18n\i18n; use SilverStripe\Security\Member; -use Zend_Locale; -use Collator; /** * A simple extension to dropdown field, pre-configured to list countries. @@ -53,23 +51,8 @@ public function setSource($source) return parent::setSource($source); } - // map empty source to country list - // Get a list of countries from Zend - $source = Zend_Locale::getTranslationList('territory', $this->locale(), 2); - - // We want them ordered by display name, not country code - - // PHP 5.3 has an extension that sorts UTF-8 strings correctly - if (class_exists('Collator') && ($collator = Collator::create($this->locale()))) { - $collator->asort($source); - } else { - // Otherwise just put up with them being weirdly ordered for now - asort($source); - } - - // We don't want "unknown country" as an option - unset($source['ZZ']); - + // Get sorted countries + $source = i18n::getData()->i18nCountries(); return parent::setSource($source); } @@ -83,10 +66,10 @@ public function Field($properties = array()) && (!$value || !isset($source[$value])) && $this->locale() ) { - $locale = new Zend_Locale(); - $locale->setLocale($this->locale()); - $value = $locale->getRegion(); - $this->setValue($value); + $value = i18n::getData()->countryFromLocale(i18n::get_locale()); + if ($value) { + $this->setValue($value); + } } // Default to default country otherwise diff --git a/src/Forms/CurrencyField.php b/src/Forms/CurrencyField.php index 4f77de5a6dc..82b394c7f35 100644 --- a/src/Forms/CurrencyField.php +++ b/src/Forms/CurrencyField.php @@ -18,16 +18,17 @@ class CurrencyField extends TextField * allows the value to be set. removes the first character * if it is not a number (probably a currency symbol) * - * @param mixed $val + * @param mixed $value + * @param mixed $data * @return $this */ - public function setValue($val) + public function setValue($value, $data = null) { - if (!$val) { - $val = 0.00; + if (!$value) { + $value = 0.00; } $this->value = DBCurrency::config()->get('currency_symbol') - . number_format((double)preg_replace('/[^0-9.\-]/', '', $val), 2); + . number_format((double)preg_replace('/[^0-9.\-]/', '', $value), 2); return $this; } /** diff --git a/src/Forms/DateField.php b/src/Forms/DateField.php index 1453c81b423..8089a536034 100644 --- a/src/Forms/DateField.php +++ b/src/Forms/DateField.php @@ -2,53 +2,28 @@ namespace SilverStripe\Forms; -use SilverStripe\Core\Convert; -use SilverStripe\Core\Injector\Injector; +use IntlDateFormatter; use SilverStripe\i18n\i18n; use InvalidArgumentException; -use Zend_Locale; -use Zend_Date; - -require_once 'Zend/Date.php'; +use SilverStripe\ORM\FieldType\DBDatetime; /** * Form field to display an editable date string, * either in a single `` field, * or in three separate fields for day, month and year. * - * # Configuration - * - * - 'showcalendar' (boolean): Determines if a calendar picker is shown. - * By default, jQuery UI datepicker is used (see {@link DateField_View_JQuery}). - * - 'jslocale' (string): Overwrites the "Locale" value set in this class. - * Only useful in combination with {@link DateField_View_JQuery}. - * - 'dmyfields' (boolean): Show three input fields for day, month and year separately. - * CAUTION: Might not be useable in combination with 'showcalendar', depending on the used javascript library - * - 'dmyseparator' (string): HTML markup to separate day, month and year fields. - * Only applicable with 'dmyfields'=TRUE. Use 'dateformat' to influence date representation with 'dmyfields'=FALSE. - * - 'dmyplaceholders': Show HTML5 placehoder text to allow identification of the three separate input fields - * - 'dateformat' (string): Date format compatible with Zend_Date. - * Usually set to default format for {@link locale} through {@link Zend_Locale_Format::getDateFormat()}. - * - 'datavalueformat' (string): Internal ISO format string used by {@link dataValue()} to save the - * date to a database. - * - 'min' (string): Minimum allowed date value (in ISO format, or strtotime() compatible). - * Example: '2010-03-31', or '-7 days' - * - 'max' (string): Maximum allowed date value (in ISO format, or strtotime() compatible). - * Example: '2010-03-31', or '1 year' - * - * Depending which UI helper is used, further namespaced configuration options are available. - * For the default jQuery UI, all options prefixed/namespaced with "jQueryUI." will be respected as well. - * Example: $myDateField->setConfig('jQueryUI.showWeek', true); - * See http://docs.jquery.com/UI/Datepicker for details. - * * Caution: The form field does not include any JavaScript or CSS when used outside of the CMS context, * since the required frontend dependencies are included through CMS bundling. * * # Localization * - * The field will get its default locale from {@link i18n::get_locale()}, and set the `dateformat` - * configuration accordingly. Changing the locale through {@link setLocale()} will not update the - * `dateformat` configuration automatically. + * Date formatting can be controlled in the below order of priority: + * - Format set via setDateFormat() + * - Format generated from current locale set by setLocale() and setDateLength() + * - Format generated from current locale in i18n + * + * You can also specify a setClientLocale() to set the javascript to a specific locale + * on the frontend. However, this will not override the date format string. * * See http://doc.silverstripe.org/framework/en/topics/i18n for more information about localizing form fields. * @@ -58,175 +33,320 @@ * * $f = new DateField('MyDate'); * $f->setLocale('de_DE'); - * $f->setConfig('dmyfields', true); + * $f->setSeparateDMYFields(true); * * # Validation * * Caution: JavaScript validation is only supported for the 'en_NZ' locale at the moment, * it will be disabled automatically for all other locales. + * + * # Formats + * + * All format strings should follow the CLDR standard as per + * http://userguide.icu-project.org/formatparse/datetime. These will be converted + * automatically to jquery UI format. + * + * The value of this field in PHP will be ISO 8601 standard (e.g. 2004-02-12), and + * stores this as a timestamp internally. + * + * Note: Do NOT use php date format strings. Date format strings follow the date + * field symbol table as below. + * + * @see http://userguide.icu-project.org/formatparse/datetime + * @see http://api.jqueryui.com/datepicker/#utility-formatDate */ class DateField extends TextField { - protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DATE; /** - * @config - * @var array + * Override locale. If empty will default to current locale + * + * @var string + */ + protected $locale = null; + + /** + * Override date format. If empty will default to that used by the current locale. + * + * @var null */ - private static $default_config = array( - 'showcalendar' => false, - 'jslocale' => null, - 'dmyfields' => false, - 'dmyseparator' => ' / ', - 'dmyplaceholders' => true, - 'dateformat' => null, - 'datavalueformat' => 'yyyy-MM-dd', - 'min' => null, - 'max' => null, - ); + protected $dateFormat = null; /** - * @var array + * Set if js calendar should popup + * + * @var bool */ - protected $config; + protected $showCalendar = false; /** - * @var String + * Length of this date (full, short, etc). + * + * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants + * @var int */ - protected $locale = null; + protected $dateLength = null; + + /** + * Set whether to show placeholders + * + * @var bool + */ + protected $placeholders = true; + + /** + * Declare whether D, M and Y fields should be separate inputs. + * If set then only numeric values will be accepted. + * + * @var bool + */ + protected $separateDMYFields = false; + + /** + * Override locale for client side. + * + * @var string + */ + protected $clientLocale = null; + + /** + * Min date + * + * @var string ISO 8601 date for min date + */ + protected $minDate = null; + + /** + * Max date + * + * @var string ISO 860 date for max date + */ + protected $maxDate = null; /** - * @var Zend_Date Just set if the date is valid. - * {@link $value} will always be set to aid validation, - * and might contain invalid values. + * Unparsed value, used exclusively for comparing with internal value + * to detect invalid values. + * + * @var mixed */ - protected $valueObj = null; + protected $rawValue = null; - public function __construct($name, $title = null, $value = null) + /** + * Check if calendar should be shown on the frontend + * + * @return bool + */ + public function getShowCalendar() { - if (!$this->locale) { - $this->locale = i18n::get_locale(); - } + return $this->showCalendar; + } - $this->config = $this->config()->default_config; - if (!$this->getConfig('dateformat')) { - $this->setConfig('dateformat', i18n::config()->get('date_format')); + /** + * Set if calendar should be shown on the frontend. + * + * If set to true, disables separate DMY fields + * + * @param bool $show + * @return $this + */ + public function setShowCalendar($show) + { + if ($show && $this->getSeparateDMYFields()) { + throw new InvalidArgumentException("Can't separate DMY fields and show calendar popup"); } + $this->showCalendar = $show; + return $this; + } - foreach ($this->config()->default_config as $defaultK => $defaultV) { - if ($defaultV) { - if ($defaultK=='locale') { - $this->locale = $defaultV; - } else { - $this->setConfig($defaultK, $defaultV); - } - } + /** + * Get length of the date format to use. One of: + * + * - IntlDateFormatter::SHORT + * - IntlDateFormatter::MEDIUM + * - IntlDateFormatter::LONG + * - IntlDateFormatter::FULL + * + * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants + * @return int + */ + public function getDateLength() + { + if ($this->dateLength) { + return $this->dateLength; } + return IntlDateFormatter::MEDIUM; + } - parent::__construct($name, $title, $value); + /** + * Get length of the date format to use. One of: + * + * - IntlDateFormatter::SHORT + * - IntlDateFormatter::MEDIUM + * - IntlDateFormatter::LONG + * - IntlDateFormatter::FULL + * + * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants + * + * @param int $length + * @return $this + */ + public function setDateLength($length) + { + $this->dateLength = $length; + return $this; } - public function FieldHolder($properties = array()) + /** + * Get date format in CLDR standard format + * + * This can be set explicitly. If not, this will be generated from the current locale + * with the current date length. + * + * @see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Field-Symbol-Table + */ + public function getDateFormat() { - if ($this->getConfig('showcalendar')) { - // TODO Replace with properly extensible view helper system - $d = DateField_View_JQuery::create($this); - if (!$d->regionalSettingsExist()) { - $dateformat = $this->getConfig('dateformat'); - - // if no localefile is present, the jQuery DatePicker - // month- and daynames will default to English, so the date - // will not pass Zend validatiobn. We provide a fallback - if (preg_match('/(MMM+)|(EEE+)/', $dateformat)) { - $this->setConfig('dateformat', $this->getConfig('datavalueformat')); - } - } - $d->onBeforeRender(); + if ($this->dateFormat) { + return $this->dateFormat; } - $html = parent::FieldHolder(); - if (!empty($d)) { - $html = $d->onAfterRender($html); - } - return $html; + // Get from locale + return $this->getFormatter()->getPattern(); } - function SmallFieldHolder($properties = array()) + /** + * Set date format in CLDR standard format. + * + * @see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Field-Symbol-Table + * @param string $format + * @return $this + */ + public function setDateFormat($format) { - $d = DateField_View_JQuery::create($this); - $d->onBeforeRender(); - $html = parent::SmallFieldHolder($properties); - $html = $d->onAfterRender($html); - return $html; + $this->dateFormat = $format; + return $this; } - public function Field($properties = array()) + /** + * Get date formatter with the standard locale / date format + * + * @return IntlDateFormatter + */ + protected function getFormatter() { - $config = array( - 'showcalendar' => $this->getConfig('showcalendar'), - 'isoDateformat' => $this->getConfig('dateformat'), - 'jquerydateformat' => DateField_View_JQuery::convert_iso_to_jquery_format($this->getConfig('dateformat')), - 'min' => $this->getConfig('min'), - 'max' => $this->getConfig('max') + $formatter = IntlDateFormatter::create( + $this->getLocale(), + $this->getDateLength(), + IntlDateFormatter::NONE ); - // Add other jQuery UI specific, namespaced options (only serializable, no callbacks etc.) - // TODO Move to DateField_View_jQuery once we have a properly extensible HTML5 attribute system for FormField - $jqueryUIConfig = array(); - foreach ($this->getConfig() as $k => $v) { - if (preg_match('/^jQueryUI\.(.*)/', $k, $matches)) { - $jqueryUIConfig[$matches[1]] = $v; + // Don't invoke getDateFormat() directly to avoid infinite loop + if ($this->dateFormat) { + $ok = $formatter->setPattern($this->dateFormat); + if (!$ok) { + throw new InvalidArgumentException("Invalid date format {$this->dateFormat}"); } } - if ($jqueryUIConfig) { - $config['jqueryuiconfig'] = Convert::array2json(array_filter($jqueryUIConfig)); + return $formatter; + } + + /** + * Get a date formatter for the ISO 8601 format + * + * @return IntlDateFormatter + */ + protected function getISO8601Formatter() + { + $formatter = IntlDateFormatter::create( + i18n::config()->get('default_locale'), + IntlDateFormatter::MEDIUM, + IntlDateFormatter::NONE + ); + $formatter->setLenient(false); + // CLDR iso8601 date. + $formatter->setPattern('y-MM-dd'); + return $formatter; + } + + public function FieldHolder($properties = array()) + { + return $this->renderWithClientView(function () use ($properties) { + return parent::FieldHolder($properties); + }); + } + + public function SmallFieldHolder($properties = array()) + { + return $this->renderWithClientView(function () use ($properties) { + return parent::SmallFieldHolder($properties); + }); + } + + /** + * Generate field with client view enabled + * + * @param callable $callback + * @return string + */ + protected function renderWithClientView($callback) + { + $clientView = null; + if ($this->getShowCalendar()) { + $clientView = $this->getClientView(); + $clientView->onBeforeRender(); + } + $html = $callback(); + if ($clientView) { + $html = $clientView->onAfterRender($html); } - $config = array_filter($config); - foreach ($config as $k => $v) { - $this->setAttribute('data-' . $k, $v); + return $html; + } + + public function getAttributes() + { + $attributes = parent::getAttributes(); + + // Merge with client config + $config = $this->getClientConfig(); + foreach ($config as $key => $value) { + $attributes["data-{$key}"] = $value; + } + + return $attributes; + } + + public function Field($properties = array()) + { + if (!$this->getSeparateDMYFields()) { + return parent::Field($properties); } // Three separate fields for day, month and year - if ($this->getConfig('dmyfields')) { - // values - $valArr = ($this->valueObj) ? $this->valueObj->toArray() : null; - - // fields - $fieldNames = Zend_Locale::getTranslationList('Field', $this->locale); - $fieldDay = NumericField::create($this->name . '[day]', false, ($valArr) ? $valArr['day'] : null) - ->addExtraClass('day') - ->setAttribute('placeholder', $this->getConfig('dmyplaceholders') ? $fieldNames['day'] : null) - ->setMaxLength(2); - - $fieldMonth = NumericField::create($this->name . '[month]', false, ($valArr) ? $valArr['month'] : null) - ->addExtraClass('month') - ->setAttribute('placeholder', $this->getConfig('dmyplaceholders') ? $fieldNames['month'] : null) - ->setMaxLength(2); - - $fieldYear = NumericField::create($this->name . '[year]', false, ($valArr) ? $valArr['year'] : null) - ->addExtraClass('year') - ->setAttribute('placeholder', $this->getConfig('dmyplaceholders') ? $fieldNames['year'] : null) - ->setMaxLength(4); - - // order fields depending on format - $sep = $this->getConfig('dmyseparator'); - $format = $this->getConfig('dateformat'); - $fields = array(); - $fields[stripos($format, 'd')] = $fieldDay->Field(); - $fields[stripos($format, 'm')] = $fieldMonth->Field(); - $fields[stripos($format, 'y')] = $fieldYear->Field(); - ksort($fields); - $html = implode($sep, $fields); - - // dmyfields doesn't work with showcalendar - $this->setConfig('showcalendar', false); - } // Default text input field - else { - $html = parent::Field(); + $valArr = $this->iso8601ToArray($this->Value()); + $fieldDay = NumericField::create($this->name . '[day]', false, $valArr ? $valArr['day'] : null) + ->addExtraClass('day') + ->setMaxLength(2); + $fieldMonth = NumericField::create($this->name . '[month]', false, $valArr ? $valArr['month'] : null) + ->addExtraClass('month') + ->setMaxLength(2); + $fieldYear = NumericField::create($this->name . '[year]', false, $valArr ? $valArr['year'] : null) + ->addExtraClass('year') + ->setMaxLength(4); + + // Set placeholders + if ($this->getPlaceholders()) { + $fieldDay->setAttribute('placeholder', _t(__CLASS__ . '.DAY', 'Day')); + $fieldMonth->setAttribute('placeholder', _t(__CLASS__ . '.MONTH', 'Month')); + $fieldYear->setAttribute('placeholder', _t(__CLASS__ . '.YEAR', 'Year')); } - return $html; + // Join all fields + // @todo custom ordering based on locale + $sep = ' / '; + return $fieldDay->Field() . $sep + . $fieldMonth->Field() . $sep + . $fieldYear->Field(); } public function Type() @@ -235,123 +355,69 @@ public function Type() } /** - * Sets the internal value to ISO date format. + * Assign value posted from form submission * - * @param mixed $val + * @param mixed $value + * @param mixed $data * @return $this */ - public function setValue($val) + public function setSubmittedValue($value, $data = null) { - $locale = new Zend_Locale($this->locale); + // Save raw value for later validation + if ($this->isEmptyArray($value)) { + $this->rawValue = null; + } else { + $this->rawValue = $value; + } - if (empty($val)) { + // Null case + if (!$value) { $this->value = null; - $this->valueObj = null; - } else { - if ($this->getConfig('dmyfields')) { - // Setting in correct locale - if (is_array($val) && $this->validateArrayValue($val)) { - // set() gets confused with custom date formats when using array notation - if (!(empty($val['day']) || empty($val['month']) || empty($val['year']))) { - $this->valueObj = new Zend_Date($val, null, $locale); - $this->value = $this->valueObj->toArray(); - } else { - $this->value = $val; - $this->valueObj = null; - } - } // load ISO date from database (usually through Form->loadDataForm()) - elseif (!empty($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'), $locale)) { - $this->valueObj = new Zend_Date($val, $this->getConfig('datavalueformat'), $locale); - $this->value = $this->valueObj->toArray(); - } else { - $this->value = $val; - $this->valueObj = null; - } - } else { - // Setting in correct locale. - // Caution: Its important to have this check *before* the ISO date fallback, - // as some dates are falsely detected as ISO by isDate(), e.g. '03/04/03' - // (en_NZ for 3rd of April, definetly not yyyy-MM-dd) - if (!empty($val) && Zend_Date::isDate($val, $this->getConfig('dateformat'), $locale)) { - $this->valueObj = new Zend_Date($val, $this->getConfig('dateformat'), $locale); - $this->value = $this->valueObj->get($this->getConfig('dateformat'), $locale); - } // load ISO date from database (usually through Form->loadDataForm()) - elseif (!empty($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'))) { - $this->valueObj = new Zend_Date($val, $this->getConfig('datavalueformat')); - $this->value = $this->valueObj->get($this->getConfig('dateformat'), $locale); - } else { - $this->value = $val; - $this->valueObj = null; - } - } + return $this; + } + + // If loading from array convert + if (is_array($value)) { + $this->value = $this->arrayToISO8601($value); + return $this; } + // Parse from submitted value + $this->value = $this->localisedToISO8601($value); return $this; } - /** - * @return String ISO 8601 date, suitable for insertion into database - */ - public function dataValue() + public function setValue($value, $data = null) { - if ($this->valueObj) { - return $this->valueObj->toString($this->getConfig('datavalueformat')); - } else { - return null; + // Save raw value for later validation + $this->rawValue = $value; + + // Null case + if (!$value) { + $this->value = null; + return $this; } - } - public function performReadonlyTransformation() - { - $field = $this->castedCopy('SilverStripe\\Forms\\DateField_Disabled'); - $field->setValue($this->dataValue()); - $field->readonly = true; + if (is_array($value)) { + throw new InvalidArgumentException("Use setSubmittedValue to assign by array"); + } - return $field; + // Re-run through formatter to tidy up (e.g. remove time component) + $this->value = $this->tidyISO8601($value); + return $this; } - /** - * @param mixed $class - * @return FormField - */ - public function castedCopy($class) + public function Value() { - /** @var FormField $copy */ - $copy = Injector::inst()->create($class, $this->name); - if ($copy->hasMethod('setConfig')) { - /** @var DateField $copy */ - $config = $this->getConfig(); - foreach ($config as $k => $v) { - $copy->setConfig($k, $v); - } - } - - return parent::castedCopy($copy); + return $this->iso8601ToLocalised($this->value); } - /** - * Validate an array with expected keys 'day', 'month' and 'year. - * Used because Zend_Date::isDate() doesn't provide this. - * - * @param array $val - * @return bool - */ - public function validateArrayValue($val) + public function performReadonlyTransformation() { - if (!is_array($val)) { - return false; - } - - // Validate against Zend_Date, - // but check for empty array keys (they're included in standard form submissions) - return ( - array_key_exists('year', $val) - && (!$val['year'] || Zend_Date::isDate($val['year'], 'yyyy', $this->locale)) - && array_key_exists('month', $val) - && (!$val['month'] || Zend_Date::isDate($val['month'], 'MM', $this->locale)) - && array_key_exists('day', $val) - && (!$val['day'] || Zend_Date::isDate($val['day'], 'dd', $this->locale)) - ); + $field = $this->castedCopy(DateField_Disabled::class); + $field->setValue($this->dataValue()); + $field->setReadonly(true); + return $field; } /** @@ -361,66 +427,52 @@ public function validateArrayValue($val) public function validate($validator) { // Don't validate empty fields - if (empty($this->value)) { + if (empty($this->rawValue)) { return true; } - // date format - if ($this->getConfig('dmyfields')) { - $valid = (!$this->value || $this->validateArrayValue($this->value)); - } else { - $valid = (Zend_Date::isDate($this->value, $this->getConfig('dateformat'), $this->locale)); - } - if (!$valid) { + // We submitted a value, but it couldn't be parsed + if (empty($this->value)) { $validator->validationError( $this->name, _t( 'DateField.VALIDDATEFORMAT2', "Please enter a valid date format ({format})", - array('format' => $this->getConfig('dateformat')) - ), - "validation" + ['format' => $this->getDateFormat()] + ) ); return false; } - // min/max - Assumes that the date value was valid in the first place - if ($min = $this->getConfig('min')) { - // ISO or strtotime() - if (Zend_Date::isDate($min, $this->getConfig('datavalueformat'))) { - $minDate = new Zend_Date($min, $this->getConfig('datavalueformat')); - } else { - $minDate = new Zend_Date(strftime('%Y-%m-%d', strtotime($min)), $this->getConfig('datavalueformat')); - } - if (!$this->valueObj || (!$this->valueObj->isLater($minDate) && !$this->valueObj->equals($minDate))) { + // Check min date + $min = $this->getMinDate(); + if ($min) { + $oops = strtotime($this->value) < strtotime($min); + if ($oops) { $validator->validationError( $this->name, _t( 'DateField.VALIDDATEMINDATE', "Your date has to be newer or matching the minimum allowed date ({date})", - array('date' => $minDate->toString($this->getConfig('dateformat'))) - ), - "validation" + ['date' => $this->iso8601ToLocalised($min)] + ) ); return false; } } - if ($max = $this->getConfig('max')) { - // ISO or strtotime() - if (Zend_Date::isDate($min, $this->getConfig('datavalueformat'))) { - $maxDate = new Zend_Date($max, $this->getConfig('datavalueformat')); - } else { - $maxDate = new Zend_Date(strftime('%Y-%m-%d', strtotime($max)), $this->getConfig('datavalueformat')); - } - if (!$this->valueObj || (!$this->valueObj->isEarlier($maxDate) && !$this->valueObj->equals($maxDate))) { + + // Check max date + $max = $this->getMaxDate(); + if ($max) { + $oops = strtotime($this->value) > strtotime($max); + if ($oops) { $validator->validationError( $this->name, _t( 'DateField.VALIDDATEMAXDATE', "Your date has to be older or matching the maximum allowed date ({date})", - array('date' => $maxDate->toString($this->getConfig('dateformat'))) - ), - "validation" + ['date' => $this->iso8601ToLocalised($max)] + ) ); return false; } @@ -430,11 +482,13 @@ public function validate($validator) } /** + * Get locale to use for this field + * * @return string */ public function getLocale() { - return $this->locale; + return $this->locale ?: i18n::get_locale(); } /** @@ -450,54 +504,26 @@ public function setLocale($locale) } /** - * @param string $name - * @param mixed $val - * @return $this + * Get locale code for client-side. Will default to getLocale() if omitted. + * + * @return string */ - public function setConfig($name, $val) - { - switch ($name) { - case 'min': - $format = $this->getConfig('datavalueformat'); - if ($val && !Zend_Date::isDate($val, $format) && !strtotime($val)) { - throw new InvalidArgumentException( - sprintf( - 'Date "%s" is not a valid minimum date format (%s) or strtotime() argument', - $val, - $format - ) - ); - } - break; - case 'max': - $format = $this->getConfig('datavalueformat'); - if ($val && !Zend_Date::isDate($val, $format) && !strtotime($val)) { - throw new InvalidArgumentException( - sprintf( - 'Date "%s" is not a valid maximum date format (%s) or strtotime() argument', - $val, - $format - ) - ); - } - break; - } - - $this->config[$name] = $val; - return $this; + public function getClientLocale() + { + if ($this->clientLocale) { + return $this->clientLocale; + } + return $this->getLocale(); } /** - * @param String $name Optional, returns the whole configuration array if empty - * @return mixed|array + * @param string $clientLocale + * @return DateField */ - public function getConfig($name = null) + public function setClientLocale($clientLocale) { - if ($name) { - return isset($this->config[$name]) ? $this->config[$name] : null; - } else { - return $this->config; - } + $this->clientLocale = $clientLocale; + return $this; } public function getSchemaValidation() @@ -506,4 +532,259 @@ public function getSchemaValidation() $rules['date'] = true; return $rules; } + + /** + * If placeholders are shown + * + * @return bool + */ + public function getPlaceholders() + { + return $this->placeholders; + } + + /** + * Set if placeholders are shown + * + * @param bool $placeholders + * @return $this + */ + public function setPlaceholders($placeholders) + { + $this->placeholders = $placeholders; + return $this; + } + + /** + * Declare whether D, M and Y fields should be separate inputs. + * If set then only numeric values will be accepted. + * + * @return bool + */ + public function getSeparateDMYFields() + { + return $this->separateDMYFields; + } + + /** + * Set if we should separate D M and Y fields. If set to true, disabled calendar + * popup. + * + * @param bool $separate + * @return $this + */ + public function setSeparateDMYFields($separate) + { + if ($separate && $this->getShowCalendar()) { + throw new InvalidArgumentException("Can't separate DMY fields and show calendar popup"); + } + $this->separateDMYFields = $separate; + return $this; + } + + /** + * @return string + */ + public function getMinDate() + { + return $this->minDate; + } + + /** + * @param string $minDate + * @return $this + */ + public function setMinDate($minDate) + { + $this->minDate = $this->tidyISO8601($minDate); + return $this; + } + + /** + * @return string + */ + public function getMaxDate() + { + return $this->maxDate; + } + + /** + * @param string $maxDate + * @return $this + */ + public function setMaxDate($maxDate) + { + $this->maxDate = $this->tidyISO8601($maxDate); + return $this; + } + + /** + * Get client data properties for this field + * + * @return array + */ + public function getClientConfig() + { + $view = $this->getClientView(); + $config = [ + 'showcalendar' => $this->getShowCalendar() ? 'true' : null, + 'date-format' => $view->getDateFormat(), // https://api.jqueryui.com/datepicker/#option-dateFormat + 'locale' => $view->getLocale(), + ]; + + // Format min/maxDate in format expected by jquery datepicker + $min = $this->getMinDate(); + if ($min) { + // https://api.jqueryui.com/datepicker/#option-minDate + $config['min-date'] = $this->iso8601ToLocalised($min); + } + $max = $this->getMaxDate(); + if ($max) { + // https://api.jqueryui.com/datepicker/#option-maxDate + $config['max-date'] = $this->iso8601ToLocalised($max); + } + + return $config; + } + + /** + * Convert iso 8601 date to array (day / month / year) + * + * @param string $date + * @return array|null Array form, or null if not valid + */ + public function iso8601ToArray($date) + { + if (!$date) { + return null; + } + $formatter = $this->getISO8601Formatter(); + $timestamp = $formatter->parse($date); + if ($timestamp === false) { + return null; + } + + // Format time manually into an array + return [ + 'day' => date('j', $timestamp), + 'month' => date('n', $timestamp), + 'year' => date('Y', $timestamp), + ]; + } + + /** + * Convert array to timestamp + * + * @param array $value + * @return string + */ + public function arrayToISO8601($value) + { + if ($this->isEmptyArray($value)) { + return null; + } + + // ensure all keys are specified + if (!isset($value['month']) || !isset($value['day']) || !isset($value['year'])) { + return null; + } + + // Ensure valid range + if (!checkdate($value['month'], $value['day'], $value['year'])) { + return null; + } + + // Note: Set formatter to strict for array input + $formatter = $this->getISO8601Formatter(); + $timestamp = mktime(0, 0, 0, $value['month'], $value['day'], $value['year']); + if ($timestamp === false) { + return null; + } + return $formatter->format($timestamp); + } + + /** + * Convert date localised in the current locale to ISO 8601 date + * + * @param string $date + * @return string The formatted date, or null if not a valid date + */ + public function localisedToISO8601($date) + { + if (!$date) { + return null; + } + $fromFormatter = $this->getFormatter(); + $toFormatter = $this->getISO8601Formatter(); + $timestamp = $fromFormatter->parse($date); + if ($timestamp === false) { + return null; + } + return $toFormatter->format($timestamp) ?: null; + } + + /** + * Convert an ISO 8601 localised date into the format specified by the + * current date format. + * + * @param string $date + * @return string The formatted date, or null if not a valid date + */ + public function iso8601ToLocalised($date) + { + $date = $this->tidyISO8601($date); + if (!$date) { + return null; + } + $fromFormatter = $this->getISO8601Formatter(); + $toFormatter = $this->getFormatter(); + $timestamp = $fromFormatter->parse($date); + if ($timestamp === false) { + return null; + } + return $toFormatter->format($timestamp) ?: null; + } + + /** + * Tidy up iso8601-ish date, or approximation + * + * @param string $date Date in iso8601 or approximate form + * @return string iso8601 date, or null if not valid + */ + public function tidyISO8601($date) + { + if (!$date) { + return null; + } + // Re-run through formatter to tidy up (e.g. remove time component) + $formatter = $this->getISO8601Formatter(); + $timestamp = $formatter->parse($date); + if ($timestamp === false) { + // Fallback to strtotime + $timestamp = strtotime($date, DBDatetime::now()->getTimestamp()); + if ($timestamp === false) { + return null; + } + } + return $formatter->format($timestamp); + } + + /** + * @return DateField_View_JQuery + */ + protected function getClientView() + { + return DateField_View_JQuery::create($this); + } + + /** + * Check if this array is empty + * + * @param $value + * @return bool + */ + public function isEmptyArray($value) + { + return is_array($value) && !array_filter($value); + } } diff --git a/src/Forms/DateField_View_JQuery.php b/src/Forms/DateField_View_JQuery.php index c4d502f9114..4e5a164b1c1 100644 --- a/src/Forms/DateField_View_JQuery.php +++ b/src/Forms/DateField_View_JQuery.php @@ -2,8 +2,9 @@ namespace SilverStripe\Forms; -use SilverStripe\Control\Director; -use SilverStripe\Core\Object; +use InvalidArgumentException; +use SilverStripe\Core\Config\Configurable; +use SilverStripe\Core\Injector\Injectable; use SilverStripe\i18n\i18n; use SilverStripe\View\Requirements; @@ -13,16 +14,16 @@ * * Caution: This API is highly volatile, and might change without prior deprecation. */ -class DateField_View_JQuery extends Object +class DateField_View_JQuery { + use Injectable; + use Configurable; + /** + * @var DateField + */ protected $field; - /* - * the current jQuery UI DatePicker locale file - */ - protected $jqueryLocaleFile = ''; - /** * @var array Maps values from {@link i18n::$all_locales} to * localizations existing in jQuery UI. @@ -44,8 +45,12 @@ class DateField_View_JQuery extends Object */ public function __construct($field) { - parent::__construct(); $this->field = $field; + + // Health check + if (!$this->localePath('en')) { + throw new InvalidArgumentException("Missing jquery config"); + } } /** @@ -57,21 +62,18 @@ public function getField() } /** - * Check if jQuery UI locale settings exists for the current locale - * @return boolean + * Get path to localisation file for a given locale, if it exists + * + * @param string $lang + * @return string Relative path to file, or null if it isn't available */ - function regionalSettingsExist() + protected function localePath($lang) { - $lang = $this->getLang(); - $localeFile = THIRDPARTY_DIR . "/jquery-ui/datepicker/i18n/jquery.ui.datepicker-{$lang}.js"; - if (file_exists(Director::baseFolder() . '/' . $localeFile)) { - $this->jqueryLocaleFile = $localeFile; - return true; - } else { - // file goes before internal en_US settings, - // but both will validate - return ($lang == 'en'); + $path = ADMIN_THIRDPARTY_DIR . "/jquery-ui/datepicker/i18n/jquery.ui.datepicker-{$lang}.js"; + if (file_exists(BASE_PATH . '/' . $path)) { + return $path; } + return null; } public function onBeforeRender() @@ -84,10 +86,12 @@ public function onBeforeRender() */ public function onAfterRender($html) { - if ($this->getField()->getConfig('showcalendar')) { - // Include language files (if required) - if ($this->jqueryLocaleFile) { - Requirements::javascript($this->jqueryLocaleFile); + if ($this->getField()->getShowCalendar()) { + // Load config for this locale if available + $locale = $this->getLocale(); + $localeFile = $this->localePath($locale); + if ($localeFile) { + Requirements::javascript($localeFile); } } @@ -98,26 +102,20 @@ public function onAfterRender($html) * Determines which language to use for jQuery UI, which * can be different from the value set in i18n. * - * @return String + * @return string */ - protected function getLang() + public function getLocale() { - $locale = $this->getField()->getLocale(); + $locale = $this->getField()->getClientLocale(); + + // Check standard mappings $map = $this->config()->locale_map; - if ($this->getField()->getConfig('jslocale')) { - // Undocumented config property for now, might move to the jQuery view helper - $lang = $this->getField()->getConfig('jslocale'); - } else { - if (array_key_exists($locale, $map)) { - // Specialized mapping for combined lang properties - $lang = $map[$locale]; - } else { - // Fall back to default lang (meaning "en_US" turns into "en") - $lang = i18n::get_lang_from_locale($locale); - } + if (array_key_exists($locale, $map)) { + return $map[$locale]; } - return $lang; + // Fall back to default lang (meaning "en_US" turns into "en") + return i18n::getData()->langFromLocale($locale); } /** @@ -125,9 +123,10 @@ protected function getLang() * Needs to be consistent with Zend formatting, otherwise validation will fail. * Removes all time settings like hour/minute/second from the format. * See http://docs.jquery.com/UI/Datepicker/formatDate + * From http://userguide.icu-project.org/formatparse/datetime * - * @param String $format - * @return String + * @param string $format + * @return string */ public static function convert_iso_to_jquery_format($format) { @@ -184,4 +183,14 @@ public static function convert_iso_to_jquery_format($format) return preg_replace($patterns, $replacements, $format); } + + /** + * Get client date format + * + * @return string + */ + public function getDateFormat() + { + return static::convert_iso_to_jquery_format($this->getField()->getDateFormat()); + } } diff --git a/src/Forms/DatetimeField.php b/src/Forms/DatetimeField.php index d12d9c6f77c..a92213de611 100644 --- a/src/Forms/DatetimeField.php +++ b/src/Forms/DatetimeField.php @@ -2,11 +2,9 @@ namespace SilverStripe\Forms; -use SilverStripe\Core\Convert; -use SilverStripe\View\Requirements; -use Zend_Locale; -use Zend_Date; +use IntlDateFormatter; use InvalidArgumentException; +use SilverStripe\i18n\i18n; /** * A composite field for date and time entry, @@ -17,15 +15,14 @@ * * # Configuration * - * The {@link setConfig()} method is only used to configure common properties of this field. - * To configure the {@link DateField} and {@link TimeField} instances contained within, use their own - * {@link setConfig()} methods. + * Individual options are configured either on the DatetimeField, or on individual + * sub-fields accessed via getDateField() or getTimeField() * * Example: * * $field = new DatetimeField('Name', 'Label'); - * $field->setConfig('datavalueformat', 'yyyy-MM-dd HH:mm'); // global setting - * $field->getDateField()->setConfig('showcalendar', 1); // field-specific setting + * $field->getDateField()->setDateFormat('yyyy-MM-dd HH:mm'); + * $field->getDateField()->setShowCalendar(true); // field-specific setting * * * - "timezone": Set a different timezone for viewing. {@link dataValue()} will still save @@ -48,199 +45,211 @@ class DatetimeField extends FormField */ protected $timeField = null; - /** - * @var HiddenField - */ - protected $timezoneField = null; - protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DATETIME; /** - * @config - * @var array - */ - private static $default_config = array( - 'datavalueformat' => 'yyyy-MM-dd HH:mm:ss', - 'usertimezone' => null, - 'datetimeorder' => '%s %s', - ); - - /** - * @var array + * Date time order + * + * @var string */ - protected $config; + protected $dateTimeOrder = '{date} {time}'; public function __construct($name, $title = null, $value = "") { - $this->config = $this->config()->default_config; - $this->timeField = TimeField::create($name . '[time]', false); $this->dateField = DateField::create($name . '[date]', false); - $this->timezoneField = new HiddenField($name . '[timezone]'); - parent::__construct($name, $title, $value); } public function setForm($form) { parent::setForm($form); - $this->dateField->setForm($form); $this->timeField->setForm($form); - $this->timezoneField->setForm($form); - return $this; } public function setName($name) { parent::setName($name); - $this->dateField->setName($name . '[date]'); $this->timeField->setName($name . '[time]'); - $this->timezoneField->setName($name . '[timezone]'); - return $this; } /** - * @param array $properties - * @return string + * Sets value from a submitted form array + * + * @param array $value Expected submission value is either an empty value, + * or an array with the necessary components keyed against 'date' and 'time', each value + * localised according to each's localisation setting. + * @param mixed $data + * @return $this */ - public function FieldHolder($properties = array()) + public function setSubmittedValue($value, $data = null) { - $config = array( - 'datetimeorder' => $this->getConfig('datetimeorder'), - ); - $config = array_filter($config); - $this->addExtraClass(Convert::raw2json($config)); + // Empty value + if (empty($value)) { + $this->value = null; + $this->dateField->setValue(null); + $this->timeField->setValue(null); + return $this; + } + + // Validate value is submitted in array format + if (!is_array($value)) { + throw new InvalidArgumentException("Value is not submitted array"); + } + + // Save each field, and convert from array to iso8601 string + $this->dateField->setSubmittedValue($value['date'], $value); + $this->timeField->setSubmittedValue($value['time'], $value); - return parent::FieldHolder($properties); + // Combine date components back into iso8601 string for the root value + $this->value = $this->dataValue(); + return $this; } /** - * @param array $properties - * @return string + * Get formatter for converting to the target timezone, if timezone is set + * Can return null if no timezone set + * + * @return IntlDateFormatter|null */ - public function Field($properties = array()) + protected function getTimezoneFormatter() { - return parent::Field($properties); + $timezone = $this->getTimezone(); + if (!$timezone) { + return null; + } + + // Build new formatter with the altered timezone + $formatter = clone $this->getISO8601Formatter(); + $formatter->setTimeZone($timezone); + return $formatter; } /** - * Sets the internal value to ISO date format, based on either a database value in ISO date format, - * or a form submssion in the user date format. Uses the individual date and time fields - * to take care of the actual formatting and value conversion. - * - * Value setting happens *before* validation, so we have to set the value even if its not valid. + * Get a date formatter for the ISO 8601 format * - * Caution: Only converts user timezones when value is passed as array data (= form submission). - * Weak indication, but unfortunately the framework doesn't support a distinction between - * setting a value from the database, application logic, and user input. + * @return IntlDateFormatter + */ + protected function getISO8601Formatter() + { + $formatter = IntlDateFormatter::create( + i18n::config()->get('default_locale'), + IntlDateFormatter::MEDIUM, + IntlDateFormatter::MEDIUM, + date_default_timezone_get() // Default to server timezone + ); + $formatter->setLenient(false); + // CLDR iso8601 date. + // Note we omit timezone from this format, and we assume server TZ always. + $formatter->setPattern('y-MM-dd HH:mm:ss'); + return $formatter; + } + + /** + * Assign value from iso8601 string * - * @param string|array $val String expects an ISO date format. Array notation with 'date' and 'time' - * keys can contain localized strings. If the 'dmyfields' option is used for {@link DateField}, - * the 'date' value may contain array notation was well (see {@link DateField->setValue()}). + * @param mixed $value + * @param mixed $data * @return $this */ - public function setValue($val) + public function setValue($value, $data = null) { - $locale = new Zend_Locale($this->locale); - - // If timezones are enabled, assume user data needs to be reverted to server timezone - if ($this->getConfig('usertimezone')) { - // Accept user input on timezone, but only when timezone support is enabled - $userTz = (is_array($val) && array_key_exists('timezone', $val)) ? $val['timezone'] : null; - if (!$userTz) { - $userTz = $this->getConfig('usertimezone'); // fall back to defined timezone - } - } else { - $userTz = null; - } - - if (empty($val)) { + // Empty value + if (empty($value)) { $this->value = null; $this->dateField->setValue(null); $this->timeField->setValue(null); - } else { - // Case 1: String setting from database, in ISO date format - if (is_string($val) && Zend_Date::isDate($val, $this->getConfig('datavalueformat'), $locale)) { - $this->value = $val; - } // Case 2: Array form submission with user date format - elseif (is_array($val) && array_key_exists('date', $val) && array_key_exists('time', $val)) { - $dataTz = date_default_timezone_get(); - // If timezones are enabled, assume user data needs to be converted to server timezone - if ($userTz) { - date_default_timezone_set($userTz); - } - - // Uses sub-fields to temporarily write values and delegate dealing with their normalization, - // actual sub-field value setting happens later - $this->dateField->setValue($val['date']); - $this->timeField->setValue($val['time']); - if ($this->dateField->dataValue() && $this->timeField->dataValue()) { - $userValueObj = new Zend_Date(null, null, $locale); - $userValueObj->setDate( - $this->dateField->dataValue(), - $this->dateField->getConfig('datavalueformat') - ); - $userValueObj->setTime( - $this->timeField->dataValue(), - $this->timeField->getConfig('datavalueformat') - ); - if ($userTz) { - $userValueObj->setTimezone($dataTz); - } - $this->value = $userValueObj->get($this->getConfig('datavalueformat'), $locale); - unset($userValueObj); - } else { - // Validation happens later, so set the raw string in case Zend_Date doesn't accept it - $this->value = trim(sprintf($this->getConfig('datetimeorder'), $val['date'], $val['time'])); - } - - if ($userTz) { - date_default_timezone_set($dataTz); - } - } // Case 3: Value is invalid, but set it anyway to allow validation by the fields later on - else { - $this->dateField->setValue($val); - if (is_string($val)) { - $this->timeField->setValue($val); - } - $this->value = $val; - } - - // view settings (dates might differ from $this->value based on user timezone settings) - if (Zend_Date::isDate($this->value, $this->getConfig('datavalueformat'), $locale)) { - $valueObj = new Zend_Date($this->value, $this->getConfig('datavalueformat'), $locale); - if ($userTz) { - $valueObj->setTimezone($userTz); - } - - // Set view values in sub-fields - if ($this->dateField->getConfig('dmyfields')) { - $this->dateField->setValue($valueObj->toArray()); - } else { - $this->dateField->setValue( - $valueObj->get($this->dateField->getConfig('dateformat'), $locale) - ); - } - $this->timeField->setValue($valueObj->get($this->timeField->getConfig('timeformat'), $locale)); - } + return $this; + } + if (is_array($value)) { + throw new InvalidArgumentException("Use setSubmittedValue to assign by array"); + }; + + // Validate iso 8601 date + // If invalid, assign for later validation failure + $isoFormatter = $this->getISO8601Formatter(); + $timestamp = $isoFormatter->parse($value); + if ($timestamp === false) { + $this->dateField->setSubmittedValue($value); + $this->timeField->setValue(null); + return $this; } + // Cleanup date + $value = $isoFormatter->format($timestamp); + + // Save value + $this->value = $value; + + // Shift iso date into timezone before assignment to subfields + $timezoneFormatter = $this->getTimezoneFormatter(); + if ($timezoneFormatter) { + $value = $timezoneFormatter->format($timestamp); + } + + // Set date / time components, which are unaware of their timezone + list($date, $time) = explode(' ', $value); + $this->dateField->setValue($date, $data); + $this->timeField->setValue($time, $data); return $this; } + /** + * localised time value + * + * @return string + */ public function Value() { - $valDate = $this->dateField->Value(); - $valTime = $this->timeField->Value(); - if (!$valTime) { - $valTime = '00:00:00'; + $date = $this->dateField->Value(); + $time = $this->timeField->Value(); + return $this->joinDateTime($date, $time); + } + + /** + * @param string $date + * @param string $time + * @return string + */ + protected function joinDateTime($date, $time) + { + $format = $this->getDateTimeOrder(); + return strtr($format, [ + '{date}' => $date, + '{time}' => $time + ]); + } + + /** + * Get ISO8601 formatted string in the local server timezone + * + * @return string|null + */ + public function dataValue() + { + // No date means no value (even if time is specified) + $dateDataValue = $this->getDateField()->dataValue(); + if (empty($dateDataValue)) { + return null; + } + + // Build iso8601 timestamp from combined date and time + $timeDataValue = $this->getTimeField()->dataValue() ?: '00:00:00'; + $value = $dateDataValue . ' ' . $timeDataValue; + + // If necessary, convert timezone + $timezoneFormatter = $this->getTimezoneFormatter(); + if ($timezoneFormatter) { + $timestamp = $timezoneFormatter->parse($value); + $isoFormatter = $this->getISO8601Formatter(); + $value = $isoFormatter->format($timestamp); } - return sprintf($this->getConfig('datetimeorder'), $valDate, $valTime); + return $value; } public function setDisabled($bool) @@ -248,9 +257,6 @@ public function setDisabled($bool) parent::setDisabled($bool); $this->dateField->setDisabled($bool); $this->timeField->setDisabled($bool); - if ($this->timezoneField) { - $this->timezoneField->setDisabled($bool); - } return $this; } @@ -259,9 +265,6 @@ public function setReadonly($bool) parent::setReadonly($bool); $this->dateField->setReadonly($bool); $this->timeField->setReadonly($bool); - if ($this->timezoneField) { - $this->timezoneField->setReadonly($bool); - } return $this; } @@ -288,8 +291,8 @@ public function setDateField($field) } $field->setForm($this->getForm()); + $field->setValue($this->dateField->dataValue()); $this->dateField = $field; - $this->setValue($this->value); // update value } /** @@ -315,28 +318,16 @@ public function setTimeField($field) } $field->setForm($this->getForm()); + $field->setValue($this->timeField->dataValue()); $this->timeField = $field; - $this->setValue($this->value); // update value } /** - * Check if timezone field is included + * Set default locale for this field. If omitted will default to the current locale. * - * @return bool - */ - public function getHasTimezone() - { - return $this->getConfig('usertimezone'); - } - - /** - * @return FormField + * @param string $locale + * @return $this */ - public function getTimezoneField() - { - return $this->timezoneField; - } - public function setLocale($locale) { $this->dateField->setLocale($locale); @@ -344,45 +335,14 @@ public function setLocale($locale) return $this; } - public function getLocale() - { - return $this->dateField->getLocale(); - } - - /** - * Note: Use {@link getDateField()} and {@link getTimeField()} - * to set field-specific config options. - * - * @param string $name - * @param mixed $val - * @return $this - */ - public function setConfig($name, $val) - { - $this->config[$name] = $val; - - if ($name == 'usertimezone') { - $this->timezoneField->setValue($val); - $this->setValue($this->dataValue()); - } - - return $this; - } - /** - * Note: Use {@link getDateField()} and {@link getTimeField()} - * to get field-specific config options. + * Get locale for this field * - * @param String $name Optional, returns the whole configuration array if empty - * @return mixed + * @return string */ - public function getConfig($name = null) + public function getLocale() { - if ($name) { - return isset($this->config[$name]) ? $this->config[$name] : null; - } else { - return $this->config; - } + return $this->dateField->getLocale(); } public function validate($validator) @@ -390,7 +350,8 @@ public function validate($validator) $dateValid = $this->dateField->validate($validator); $timeValid = $this->timeField->validate($validator); - return ($dateValid && $timeValid); + // Validate if both subfields are valid + return $dateValid && $timeValid; } public function performReadonlyTransformation() @@ -404,6 +365,55 @@ public function __clone() { $this->dateField = clone $this->dateField; $this->timeField = clone $this->timeField; - $this->timezoneField = clone $this->timezoneField; + } + + /** + * @return string + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * Custom timezone + * + * @var string + */ + protected $timezone = null; + + /** + * @param string $timezone + * @return $this + */ + public function setTimezone($timezone) + { + if ($this->value && $timezone !== $this->timezone) { + throw new \BadMethodCallException("Can't change timezone after setting a value"); + } + // Note: DateField has no timezone option, and TimeField::setTimezone + // should be ignored + $this->timezone = $timezone; + return $this; + } + + /** + * @return string + */ + public function getDateTimeOrder() + { + return $this->dateTimeOrder; + } + + /** + * Set date time order format string. Use {date} and {time} as placeholders. + * + * @param string $dateTimeOrder + * @return $this + */ + public function setDateTimeOrder($dateTimeOrder) + { + $this->dateTimeOrder = $dateTimeOrder; + return $this; } } diff --git a/src/Forms/Form.php b/src/Forms/Form.php index 8ee70b5479e..031c6e430eb 100644 --- a/src/Forms/Form.php +++ b/src/Forms/Form.php @@ -1617,9 +1617,13 @@ public function loadDataFrom($data, $mergeStrategy = 0, $fieldList = null) $mergeStrategy = 0; } - // if an object is passed, save it for historical reference through {@link getRecord()} + // If an object is passed, save it for historical reference through {@link getRecord()} + // Also use this to determine if we are loading a submitted form, or loading + // from a dataobject + $submitted = true; if (is_object($data)) { $this->record = $data; + $submitted = false; } // dont include fields without data @@ -1649,10 +1653,10 @@ public function loadDataFrom($data, $mergeStrategy = 0, $fieldList = null) if (is_object($data)) { $exists = ( - isset($data->$name) || - $data->hasMethod($name) || - ($data->hasMethod('hasField') && $data->hasField($name)) - ); + isset($data->$name) || + $data->hasMethod($name) || + ($data->hasMethod('hasField') && $data->hasField($name)) + ); if ($exists) { $val = $data->__get($name); @@ -1690,13 +1694,22 @@ public function loadDataFrom($data, $mergeStrategy = 0, $fieldList = null) } // save to the field if either a value is given, or loading of blank/undefined values is forced + $setValue = false; if ($exists) { if ($val != false || ($mergeStrategy & self::MERGE_IGNORE_FALSEISH) != self::MERGE_IGNORE_FALSEISH) { - // pass original data as well so composite fields can act on the additional information - $field->setValue($val, $data); + $setValue = true; } } elseif (($mergeStrategy & self::MERGE_CLEAR_MISSING) == self::MERGE_CLEAR_MISSING) { - $field->setValue($val, $data); + $setValue = true; + } + + // pass original data as well so composite fields can act on the additional information + if ($setValue) { + if ($submitted) { + $field->setSubmittedValue($val, $data); + } else { + $field->setValue($val, $data); + } } } return $this; diff --git a/src/Forms/FormField.php b/src/Forms/FormField.php index 415686b7f25..ebcdf4d854d 100644 --- a/src/Forms/FormField.php +++ b/src/Forms/FormField.php @@ -449,6 +449,7 @@ public function getName() /** * Returns the field value. * + * @see FormField::setSubmittedValue() * @return mixed */ public function Value() @@ -472,7 +473,7 @@ public function saveInto(DataObjectInterface $record) /** * Returns the field value suitable for insertion into the data object. - * + * @see Formfield::setValue() * @return mixed */ public function dataValue() @@ -769,16 +770,33 @@ public function attrValue() /** * Set the field value. * - * @param mixed $value - * @param null|array|DataObject $data {@see Form::loadDataFrom} + * If a FormField requires specific behaviour for loading content from either the database + * or a submitted form value they should override setSubmittedValue() instead. + * + * @param mixed $value Either the parent object, or array of source data being loaded + * @param array|DataObject $data {@see Form::loadDataFrom} * @return $this */ - public function setValue($value) + public function setValue($value, $data = null) { $this->value = $value; return $this; } + /** + * Set value assigned from a submitted form postback. + * Can be overridden to handle custom behaviour for user-localised + * data formats. + * + * @param mixed $value + * @param array|DataObject $data + * @return $this + */ + public function setSubmittedValue($value, $data = null) + { + return $this->setValue($value, $data); + } + /** * Set the field name. * @@ -1129,7 +1147,6 @@ public function isReadonly() public function setReadonly($readonly) { $this->readonly = $readonly; - return $this; } diff --git a/src/Forms/GridField/GridState.php b/src/Forms/GridField/GridState.php index 8685acda0fa..b6d30d0198e 100644 --- a/src/Forms/GridField/GridState.php +++ b/src/Forms/GridField/GridState.php @@ -57,11 +57,7 @@ public static function array_to_object($d) return $d; } - /** - * @param mixed $value - * @return $this - */ - public function setValue($value) + public function setValue($value, $data = null) { if (is_string($value)) { $this->data = new GridState_Data(json_decode($value, true)); diff --git a/src/Forms/HTMLEditor/HTMLEditorConfig.php b/src/Forms/HTMLEditor/HTMLEditorConfig.php index 81981ed1560..77de7ea4969 100644 --- a/src/Forms/HTMLEditor/HTMLEditorConfig.php +++ b/src/Forms/HTMLEditor/HTMLEditorConfig.php @@ -2,6 +2,8 @@ namespace SilverStripe\Forms\HTMLEditor; +use SilverStripe\Core\Config\Configurable; +use SilverStripe\Core\Injector\Injectable; use SilverStripe\Core\Object; /** @@ -22,8 +24,10 @@ * * @author "Hamish Friedlander" */ -abstract class HTMLEditorConfig extends Object +abstract class HTMLEditorConfig { + use Configurable; + use Injectable; /** * Array of registered configurations diff --git a/src/Forms/HTMLEditor/HTMLEditorField.php b/src/Forms/HTMLEditor/HTMLEditorField.php index 85322762a09..2ed31aa0086 100644 --- a/src/Forms/HTMLEditor/HTMLEditorField.php +++ b/src/Forms/HTMLEditor/HTMLEditorField.php @@ -142,7 +142,7 @@ public function saveInto(DataObjectInterface $record) $record->{$this->name} = $htmlValue->getContent(); } - public function setValue($value) + public function setValue($value, $data = null) { // Regenerate links prior to preview, so that the editor can see them. $value = Image::regenerate_html_links($value); diff --git a/src/Forms/HTMLEditor/TinyMCEConfig.php b/src/Forms/HTMLEditor/TinyMCEConfig.php index 94c5436736c..3fc83c33cc4 100644 --- a/src/Forms/HTMLEditor/TinyMCEConfig.php +++ b/src/Forms/HTMLEditor/TinyMCEConfig.php @@ -2,9 +2,11 @@ namespace SilverStripe\Forms\HTMLEditor; +use SilverStripe\Core\Config\Configurable; use SilverStripe\Core\Convert; use SilverStripe\Control\Controller; use SilverStripe\Control\Director; +use SilverStripe\i18n\i18n; use SilverStripe\View\Requirements; use SilverStripe\View\SSViewer; use SilverStripe\View\ThemeResourceLoader; @@ -15,6 +17,172 @@ */ class TinyMCEConfig extends HTMLEditorConfig { + /** + * @config + * @var array + */ + private static $tinymce_lang = [ + 'ar_EG' => 'ar', + 'ca_AD' => 'ca', + 'ca_ES' => 'ca', + 'cs_CZ' => 'cs', + 'cy_GB' => 'cy', + 'da_DK' => 'da', + 'da_GL' => 'da', + 'de_AT' => 'de', + 'de_BE' => 'de', + 'de_CH' => 'de', + 'de_DE' => 'de', + 'de_LI' => 'de', + 'de_LU' => 'de', + 'de_BR' => 'de', + 'de_US' => 'de', + 'el_CY' => 'el', + 'el_GR' => 'el', + 'es_AR' => 'es', + 'es_BO' => 'es', + 'es_CL' => 'es', + 'es_CO' => 'es', + 'es_CR' => 'es', + 'es_CU' => 'es', + 'es_DO' => 'es', + 'es_EC' => 'es', + 'es_ES' => 'es', + 'es_GQ' => 'es', + 'es_GT' => 'es', + 'es_HN' => 'es', + 'es_MX' => 'es', + 'es_NI' => 'es', + 'es_PA' => 'es', + 'es_PE' => 'es', + 'es_PH' => 'es', + 'es_PR' => 'es', + 'es_PY' => 'es', + 'es_SV' => 'es', + 'es_UY' => 'es', + 'es_VE' => 'es', + 'es_AD' => 'es', + 'es_BZ' => 'es', + 'es_US' => 'es', + 'fa_AF' => 'fa', + 'fa_IR' => 'fa', + 'fa_PK' => 'fa', + 'fi_FI' => 'fi', + 'fi_SE' => 'fi', + 'fr_BE' => 'fr', + 'fr_BF' => 'fr', + 'fr_BI' => 'fr', + 'fr_BJ' => 'fr', + 'fr_CA' => 'fr_ca', + 'fr_CF' => 'fr', + 'fr_CG' => 'fr', + 'fr_CH' => 'fr', + 'fr_CI' => 'fr', + 'fr_CM' => 'fr', + 'fr_DJ' => 'fr', + 'fr_DZ' => 'fr', + 'fr_FR' => 'fr', + 'fr_GA' => 'fr', + 'fr_GF' => 'fr', + 'fr_GN' => 'fr', + 'fr_GP' => 'fr', + 'fr_HT' => 'fr', + 'fr_KM' => 'fr', + 'fr_LU' => 'fr', + 'fr_MA' => 'fr', + 'fr_MC' => 'fr', + 'fr_MG' => 'fr', + 'fr_ML' => 'fr', + 'fr_MQ' => 'fr', + 'fr_MU' => 'fr', + 'fr_NC' => 'fr', + 'fr_NE' => 'fr', + 'fr_PF' => 'fr', + 'fr_PM' => 'fr', + 'fr_RE' => 'fr', + 'fr_RW' => 'fr', + 'fr_SC' => 'fr', + 'fr_SN' => 'fr', + 'fr_SY' => 'fr', + 'fr_TD' => 'fr', + 'fr_TG' => 'fr', + 'fr_TN' => 'fr', + 'fr_VU' => 'fr', + 'fr_WF' => 'fr', + 'fr_YT' => 'fr', + 'fr_GB' => 'fr', + 'fr_US' => 'fr', + 'he_IL' => 'he', + 'hu_HU' => 'hu', + 'hu_AT' => 'hu', + 'hu_RO' => 'hu', + 'hu_RS' => 'hu', + 'is_IS' => 'is', + 'it_CH' => 'it', + 'it_IT' => 'it', + 'it_SM' => 'it', + 'it_FR' => 'it', + 'it_HR' => 'it', + 'it_US' => 'it', + 'it_VA' => 'it', + 'ja_JP' => 'ja', + 'ko_KP' => 'ko', + 'ko_KR' => 'ko', + 'ko_CN' => 'ko', + 'mi_NZ' => 'mi_NZ', + 'nb_NO' => 'nb', + 'nb_SJ' => 'nb', + 'nl_AN' => 'nl', + 'nl_AW' => 'nl', + 'nl_BE' => 'nl', + 'nl_NL' => 'nl', + 'nl_SR' => 'nl', + 'nn_NO' => 'nn', + 'pl_PL' => 'pl', + 'pl_UA' => 'pl', + 'pt_AO' => 'pt', + 'pt_BR' => 'pt', + 'pt_CV' => 'pt', + 'pt_GW' => 'pt', + 'pt_MZ' => 'pt', + 'pt_PT' => 'pt', + 'pt_ST' => 'pt', + 'pt_TL' => 'pt', + 'ro_MD' => 'ro', + 'ro_RO' => 'ro', + 'ro_RS' => 'ro', + 'ru_BY' => 'ru', + 'ru_KG' => 'ru', + 'ru_KZ' => 'ru', + 'ru_RU' => 'ru', + 'ru_SJ' => 'ru', + 'ru_UA' => 'ru', + 'si_LK' => 'si', + 'sk_SK' => 'sk', + 'sk_RS' => 'sk', + 'sq_AL' => 'sq', + 'sr_BA' => 'sr', + 'sr_ME' => 'sr', + 'sr_RS' => 'sr', + 'sv_FI' => 'sv', + 'sv_SE' => 'sv', + 'tr_CY' => 'tr', + 'tr_TR' => 'tr', + 'tr_DE' => 'tr', + 'tr_MK' => 'tr', + 'uk_UA' => 'uk', + 'vi_VN' => 'vi', + 'vi_US' => 'vi', + 'zh_CN' => 'zh-cn', + 'zh_HK' => 'zh-cn', + 'zh_MO' => 'zh-cn', + 'zh_SG' => 'zh-cn', + 'zh_TW' => 'zh-tw', + 'zh_ID' => 'zh-cn', + 'zh_MY' => 'zh-cn', + 'zh_TH' => 'zh-cn', + 'zh_US' => 'zn-cn', + ]; /** * Location of module relative to BASE_DIR. This must contain the following dirs @@ -492,4 +660,20 @@ public function init() // include TinyMCE Javascript Requirements::javascript($this->getScriptURL()); } + + + /** + * Get the current tinyMCE language + * + * @return string Language + */ + public static function get_tinymce_lang() + { + $lang = static::config()->get('tinymce_lang'); + $locale = i18n::get_locale(); + if (isset($lang[$locale])) { + return $lang[$locale]; + } + return 'en'; + } } diff --git a/src/Forms/LiteralField.php b/src/Forms/LiteralField.php index 45661e39019..027b2324379 100644 --- a/src/Forms/LiteralField.php +++ b/src/Forms/LiteralField.php @@ -101,10 +101,10 @@ public function getContent() * Synonym of {@link setContent()} so that LiteralField is more compatible with other field types. * * @param string|FormField $content - * + * @param mixed $data * @return $this */ - public function setValue($content) + public function setValue($content, $data = null) { $this->setContent($content); diff --git a/src/Forms/MemberDatetimeOptionsetField.php b/src/Forms/MemberDatetimeOptionsetField.php index 682dbcd9335..8e0913da7ca 100644 --- a/src/Forms/MemberDatetimeOptionsetField.php +++ b/src/Forms/MemberDatetimeOptionsetField.php @@ -2,28 +2,35 @@ namespace SilverStripe\Forms; +use InvalidArgumentException; use SilverStripe\ORM\ArrayList; use SilverStripe\Core\Convert; +use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\View\ArrayData; -use SilverStripe\View\Requirements; -use Zend_Date; class MemberDatetimeOptionsetField extends OptionsetField { - + /** + * Option value for custom date format option + */ const CUSTOM_OPTION = '__custom__'; /** * Non-ambiguous date to use for the preview. - * Must be in 'y-MM-dd HH:mm:ss' format + * Must be in ISO 8601 'y-MM-dd HH:mm:ss' format * * @var string */ - private static $preview_date = '25-12-2011 17:30:00'; + private static $preview_date = '2011-12-25 17:30:00'; private static $casting = ['Description' => 'HTMLText']; - private $descriptionTemplate = ''; + /** + * Template name to use for rendering the field description + * + * @var string + */ + protected $descriptionTemplate = ''; public function Field($properties = array()) { @@ -88,9 +95,8 @@ protected function getCustomFieldOption($isChecked, $odd) */ protected function previewFormat($format) { - $date = $this->config()->preview_date; - $zendDate = new Zend_Date($date, 'y-MM-dd HH:mm:ss'); - return $zendDate->toString($format); + $date = DBDatetime::create_field('Datetime', $this->config()->preview_date); + return $date->Format($format); } public function getOptionName() @@ -111,17 +117,30 @@ public function getDescription() return parent::getDescription(); } + /** + * Get template name used to render description + * + * @return string + */ public function getDescriptionTemplate() { return $this->descriptionTemplate; } + /** + * Assign a template to use for description. If assigned the description + * value will be ignored. + * + * @param string $template + * @return $this + */ public function setDescriptionTemplate($template) { $this->descriptionTemplate = $template; + return $this; } - public function setValue($value) + public function setSubmittedValue($value, $data = null) { // Extract custom option from postback if (is_array($value)) { @@ -134,9 +153,18 @@ public function setValue($value) } } - return parent::setValue($value); + return parent::setSubmittedValue($value); + } + + public function setValue($value, $data = null) + { + if (is_array($value)) { + throw new InvalidArgumentException("Invalid value"); + } + return parent::setValue($value, $data); } + /** * Validate this field * @@ -151,10 +179,8 @@ public function validate($validator) } // Check that the current date with the date format is valid or not - require_once 'Zend/Date.php'; - $date = Zend_Date::now()->toString($value); - $valid = Zend_Date::isDate($date, $value); - if ($valid) { + $date = DBDatetime::now()->Format($value); + if ($date && $date !== $value) { return true; } diff --git a/src/Forms/MoneyField.php b/src/Forms/MoneyField.php index 4d235c0a950..58fe123ad62 100644 --- a/src/Forms/MoneyField.php +++ b/src/Forms/MoneyField.php @@ -2,8 +2,8 @@ namespace SilverStripe\Forms; +use InvalidArgumentException; use SilverStripe\ORM\ArrayLib; -use SilverStripe\ORM\FieldType\DBField; use SilverStripe\ORM\FieldType\DBMoney; use SilverStripe\ORM\DataObjectInterface; @@ -20,16 +20,12 @@ class MoneyField extends FormField // TODO replace with `FormField::SCHEMA_DATA_TYPE_TEXT` when MoneyField is implemented protected $schemaDataType = 'MoneyField'; - /** - * @var string $_locale - */ - protected $_locale; - /** * Limit the currencies - * @var array $allowedCurrencies + * + * @var array */ - protected $allowedCurrencies; + protected $allowedCurrencies = []; /** * @var NumericField @@ -64,10 +60,12 @@ public function getAmountField() public function __construct($name, $title = null, $value = "") { $this->setName($name); - - // naming with underscores to prevent values from actually being saved somewhere - $this->fieldAmount = new NumericField("{$name}[Amount]", _t('MoneyField.FIELDLABELAMOUNT', 'Amount')); - $this->fieldCurrency = $this->buildCurrencyField(); + $this->fieldAmount = NumericField::create( + "{$name}[Amount]", + _t('MoneyField.FIELDLABELAMOUNT', 'Amount') + ) + ->setScale(2); + $this->buildCurrencyField(); parent::__construct($name, $title, $value); } @@ -86,17 +84,25 @@ public function __clone() protected function buildCurrencyField() { $name = $this->getName(); + + // Validate allowed currencies + $currencyValue = $this->fieldCurrency ? $this->fieldCurrency->dataValue() : null; $allowedCurrencies = $this->getAllowedCurrencies(); - if ($allowedCurrencies) { - $field = new DropdownField( + if (count($allowedCurrencies) === 1) { + // Dropdown field for multiple currencies + $field = HiddenField::create("{$name}[Currency]"); + reset($allowedCurrencies); + $currencyValue = key($allowedCurrencies); + } elseif ($allowedCurrencies) { + // Dropdown field for multiple currencies + $field = DropdownField::create( "{$name}[Currency]", _t('MoneyField.FIELDLABELCURRENCY', 'Currency'), - ArrayLib::is_associative($allowedCurrencies) - ? $allowedCurrencies - : array_combine($allowedCurrencies, $allowedCurrencies) + $allowedCurrencies ); } else { - $field = new TextField( + // Free-text entry for currency value + $field = TextField::create( "{$name}[Currency]", _t('MoneyField.FIELDLABELCURRENCY', 'Currency') ); @@ -104,29 +110,97 @@ protected function buildCurrencyField() $field->setReadonly($this->isReadonly()); $field->setDisabled($this->isDisabled()); + if ($currencyValue) { + $field->setValue($currencyValue); + } + $this->fieldCurrency = $field; return $field; } - public function setValue($val) + public function setSubmittedValue($value, $data = null) { - $this->value = $val; + if (empty($value)) { + $this->value = null; + $this->fieldCurrency->setValue(null); + $this->fieldAmount->setValue(null); + return $this; + } - if (is_array($val)) { - $this->fieldCurrency->setValue($val['Currency']); - $this->fieldAmount->setValue($val['Amount']); - } elseif ($val instanceof DBMoney) { - $this->fieldCurrency->setValue($val->getCurrency()); - $this->fieldAmount->setValue($val->getAmount()); + // Handle submitted array value + if (!is_array($value)) { + throw new InvalidArgumentException("Value is not submitted array"); } - // @todo Format numbers according to current locale, incl. - // decimal and thousands signs, while respecting the stored - // precision in the database without truncating it during display - // and subsequent save operations + // Update each field + $this->fieldCurrency->setSubmittedValue($value['Currency'], $value); + $this->fieldAmount->setSubmittedValue($value['Amount'], $value); + // Get data value + $this->value = $this->dataValue(); + return $this; + } + + public function setValue($value, $data = null) + { + if (empty($value)) { + $this->value = null; + $this->fieldCurrency->setValue(null); + $this->fieldAmount->setValue(null); + return $this; + } + + // Convert string to array + // E.g. `44.00 NZD` + if (is_string($value) && + preg_match('/^(?[\\d\\.]+)( (?\w{3}))?$/i', $value, $matches) + ) { + $currency = isset($matches['currency']) ? strtoupper($matches['currency']) : null; + $value = [ + 'Currency' => $currency, + 'Amount' => (float)$matches['amount'], + ]; + } elseif ($value instanceof DBMoney) { + $value = [ + 'Currency' => $value->getCurrency(), + 'Amount' => $value->getAmount(), + ]; + } else { + throw new InvalidArgumentException("Invalid currency format"); + } + + // Save value + $this->fieldCurrency->setValue($value['Currency'], $value); + $this->fieldAmount->setValue($value['Amount'], $value); + $this->value = $this->dataValue(); return $this; } + /** + * Get value as DBMoney object useful for formatting the number + * + * @return DBMoney + */ + protected function getDBMoney() + { + return DBMoney::create_field('Money', [ + 'Currency' => $this->fieldCurrency->dataValue(), + 'Amount' => $this->fieldAmount->dataValue() + ]) + ->setLocale($this->getLocale()); + } + + public function dataValue() + { + // Non-localised money + return $this->getDBMoney()->getValue(); + } + + public function Value() + { + // Localised money + return $this->getDBMoney()->Nice(); + } + /** * 30/06/2009 - Enhancement: * SaveInto checks if set-methods are available and use them @@ -142,10 +216,7 @@ public function saveInto(DataObjectInterface $dataObject) { $fieldName = $this->getName(); if ($dataObject->hasMethod("set$fieldName")) { - $dataObject->$fieldName = DBField::create_field('Money', array( - "Currency" => $this->fieldCurrency->dataValue(), - "Amount" => $this->fieldAmount->dataValue() - )); + $dataObject->$fieldName = $this->getDBMoney(); } else { $currencyField = "{$fieldName}Currency"; $amountField = "{$fieldName}Amount"; @@ -188,18 +259,29 @@ public function setDisabled($bool) } /** - * @param array $arr + * Set list of currencies. Currencies should be in the 3-letter ISO 4217 currency code. + * + * @param array $currencies * @return $this */ - public function setAllowedCurrencies($arr) + public function setAllowedCurrencies($currencies) { - $this->allowedCurrencies = $arr; + if (empty($currencies)) { + $currencies = []; + } elseif (is_string($currencies)) { + $currencies = [ + $currencies => $currencies + ]; + } elseif (!is_array($currencies)) { + throw new InvalidArgumentException("Invalid currency list"); + } elseif (!ArrayLib::is_associative($currencies)) { + $currencies = array_combine($currencies, $currencies); + } - // @todo Has to be done twice in case allowed currencies changed since construction - $oldVal = $this->fieldCurrency->dataValue(); - $this->fieldCurrency = $this->buildCurrencyField(); - $this->fieldCurrency->setValue($oldVal); + $this->allowedCurrencies = $currencies; + // Rebuild currency field + $this->buildCurrencyField(); return $this; } @@ -211,15 +293,27 @@ public function getAllowedCurrencies() return $this->allowedCurrencies; } + /** + * Assign locale to format this currency in + * + * @param string $locale + * @return $this + */ public function setLocale($locale) { - $this->_locale = $locale; + $this->fieldAmount->setLocale($locale); return $this; } + /** + * Get locale to format this currency in. + * Defaults to current locale. + * + * @return string + */ public function getLocale() { - return $this->_locale; + return $this->fieldAmount->getLocale(); } /** @@ -230,7 +324,23 @@ public function getLocale() */ public function validate($validator) { - return !(is_null($this->fieldAmount) || is_null($this->fieldCurrency)); + // Validate currency + $currencies = $this->getAllowedCurrencies(); + $currency = $this->fieldCurrency->dataValue(); + if ($currency && $currencies && !in_array($currency, $currencies)) { + $validator->validationError( + $this->getName(), + _t( + __CLASS__.'.INVALID_CURRENCY', + 'Currency {currency} is not in the list of allowed currencies', + ['currency' => $currency] + ) + ); + return false; + } + + // Field-specific validation + return $this->fieldAmount->validate($validator) && $this->fieldCurrency->validate($validator); } public function setForm($form) diff --git a/src/Forms/MultiSelectField.php b/src/Forms/MultiSelectField.php index 027459ab9f8..69e1d7cd292 100644 --- a/src/Forms/MultiSelectField.php +++ b/src/Forms/MultiSelectField.php @@ -60,18 +60,18 @@ public function getDefaultItems() /** * Load a value into this MultiSelectField * - * @param mixed $val + * @param mixed $value * @param null|array|DataObject $obj {@see Form::loadDataFrom} * @return $this */ - public function setValue($val, $obj = null) + public function setValue($value, $obj = null) { // If we're not passed a value directly, // we can look for it in a relation method on the object passed as a second arg if ($obj instanceof DataObject) { $this->loadFrom($obj); } else { - parent::setValue($val); + parent::setValue($value); } return $this; } diff --git a/src/Forms/NumericField.php b/src/Forms/NumericField.php index fd1231eeef9..122b149eef6 100644 --- a/src/Forms/NumericField.php +++ b/src/Forms/NumericField.php @@ -2,11 +2,8 @@ namespace SilverStripe\Forms; -use SilverStripe\ORM\DataObject; +use NumberFormatter; use SilverStripe\i18n\i18n; -use Zend_Locale; -use Zend_Locale_Exception; -use Zend_Locale_Format; /** * Text input field with validation for numeric values. Supports validating @@ -18,6 +15,13 @@ class NumericField extends TextField protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_DECIMAL; + /** + * Used to determine if the number given is in the correct format when validating + * + * @var mixed + */ + protected $originalValue = null; + /** * Override locale for this field. * @@ -26,65 +30,129 @@ class NumericField extends TextField protected $locale = null; /** - * @param mixed $value - * @param array $data + * Use HTML5 number input type. + * Note that enabling html5 disables certain localisation features. * - * @return $this + * @var bool + */ + protected $html5 = false; + + /** + * Number of decimal places allowed, if bound. + * Null means unbound. + * Defaults to 0, which is integer value. + * + * @var string + */ + protected $scale = 0; + + /** + * Get number formatter for localising this field * - * @throws Zend_Locale_Exception + * @return NumberFormatter */ - public function setValue($value, $data = array()) + protected function getFormatter() { - require_once "Zend/Locale/Format.php"; + if ($this->getHTML5()) { + // Locale-independent html5 number formatter + $formatter = NumberFormatter::create(i18n::config()->get('default_locale'), NumberFormatter::DECIMAL); + $formatter->setAttribute(NumberFormatter::GROUPING_USED, false); + $formatter->setSymbol(NumberFormatter::DECIMAL_SEPARATOR_SYMBOL, '.'); + } else { + // Locale-specific number formatter + $formatter = NumberFormatter::create($this->getLocale(), NumberFormatter::DECIMAL); + } - // If passing in a non-string number, or a value - // directly from a DataObject then localise this number + // Set decimal precision + $scale = $this->getScale(); + if ($scale === 0) { + $formatter->setAttribute(NumberFormatter::DECIMAL_ALWAYS_SHOWN, false); + $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, 0); + } else { + $formatter->setAttribute(NumberFormatter::DECIMAL_ALWAYS_SHOWN, true); + if ($scale === null) { + // At least one digit to distinguish floating point from integer + $formatter->setAttribute(NumberFormatter::MIN_FRACTION_DIGITS, 1); + } else { + $formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $scale); + } + } + return $formatter; + } - if (is_int($value) || is_float($value) || $data instanceof DataObject) { - $locale = new Zend_Locale($this->getLocale()); + /** + * Get type argument for parse / format calls. one of TYPE_INT32, TYPE_INT64 or TYPE_DOUBLE + * + * @return int + */ + protected function getNumberType() + { + $scale = $this->getScale(); + if ($scale === 0) { + return PHP_INT_SIZE > 4 + ? NumberFormatter::TYPE_INT64 + : NumberFormatter::TYPE_INT32; + } + return NumberFormatter::TYPE_DOUBLE; + } - $this->value = Zend_Locale_Format::toNumber( - $value, - array('locale' => $locale) - ); - } else { - $this->value = $this->clean($value); + public function setSubmittedValue($value, $data = null) + { + // Save original value in case parse fails + $value = trim($value); + $this->originalValue = $value; + + // Empty string is no-number (not 0) + if (strlen($value) === 0) { + $this->value = null; + return $this; } + // Format number + $formatter = $this->getFormatter(); + $parsed = 0; + $this->value = $formatter->parse($value, $this->getNumberType(), $parsed); // Note: may store literal `false` for invalid values + // Ensure that entire string is parsed + if ($parsed < strlen($value)) { + $this->value = false; + } return $this; } /** - * In some cases and locales, validation expects non-breaking spaces. - * - * Returns the value, with all spaces replaced with non-breaking spaces. - * - * @param string $input + * Format value for output * * @return string */ - protected function clean($input) + public function Value() { - $replacement = html_entity_decode(' ', null, 'UTF-8'); + // Show invalid value back to user in case of error + if ($this->value === false) { + return $this->originalValue; + } + $formatter = $this->getFormatter(); + return $formatter->format($this->value, $this->getNumberType()); + } - return str_replace(' ', $replacement, trim($input)); + public function setValue($value, $data = null) + { + $this->originalValue = $value; + $this->value = $this->cast($value); + return $this; } /** - * Determine if the current value is a valid number in the current locale. + * Helper to cast non-localised strings to their native type * - * @return bool + * @param string $value + * @return float|int */ - protected function isNumeric() + protected function cast($value) { - require_once "Zend/Locale/Format.php"; - - $locale = new Zend_Locale($this->getLocale()); - - return Zend_Locale_Format::isNumber( - $this->clean($this->value), - array('locale' => $locale) - ); + if ($this->getScale() === 0) { + return (int)$value; + } + return (float)$value; } /** @@ -95,6 +163,16 @@ public function Type() return 'numeric text'; } + public function getAttributes() + { + $attributes = parent::getAttributes(); + if ($this->getHTML5()) { + $attributes['type'] = 'number'; + $attributes['step'] = $this->getStep(); + } + return $attributes; + } + /** * Validate this field * @@ -103,11 +181,8 @@ public function Type() */ public function validate($validator) { - if (!$this->value) { - return true; - } - - if ($this->isNumeric()) { + // false signifies invalid value due to failed parse() + if ($this->value !== false) { return true; } @@ -116,11 +191,9 @@ public function validate($validator) _t( 'NumericField.VALIDATION', "'{value}' is not a number, only numbers can be accepted for this field", - array('value' => $this->value) - ), - "validation" + ['value' => $this->originalValue] + ) ); - return false; } @@ -132,71 +205,105 @@ public function getSchemaValidation() } /** - * Extracts the number value from the localised string value. + * Get internal database value * - * @return string + * @return int|float */ public function dataValue() { - require_once "Zend/Locale/Format.php"; + return $this->cast($this->value); + } - if (!$this->isNumeric()) { - return 0; + /** + * Gets the current locale this field is set to. + * + * @return string + */ + public function getLocale() + { + if ($this->locale) { + return $this->locale; } - $locale = new Zend_Locale($this->getLocale()); + return i18n::get_locale(); + } - $number = Zend_Locale_Format::getNumber( - $this->clean($this->value), - array('locale' => $locale) - ); + /** + * Override the locale for this field. + * + * @param string $locale + * + * @return $this + */ + public function setLocale($locale) + { + $this->locale = $locale; - return $number; + return $this; } /** - * Creates a read-only version of the field. + * Determine if we should use html5 number input * - * @return NumericField_Readonly + * @return bool */ - public function performReadonlyTransformation() + public function getHTML5() { - $field = new NumericField_Readonly( - $this->name, - $this->title, - $this->value - ); - - $field->setForm($this->form); + return $this->html5; + } - return $field; + /** + * Set whether this field should use html5 number input type. + * Note: If setting to true this will disable all number localisation. + * + * @param bool $html5 + * @return $this + */ + public function setHTML5($html5) + { + $this->html5 = $html5; + return $this; } /** - * Gets the current locale this field is set to. + * Step attribute for html5. E.g. '0.01' to enable two decimal places. + * Ignored if html5 isn't enabled. * * @return string */ - public function getLocale() + public function getStep() { - if ($this->locale) { - return $this->locale; + $scale = $this->getScale(); + if ($scale === null) { + return 'any'; } - - return i18n::get_locale(); + if ($scale === 0) { + return '1'; + } + return '0.'.str_repeat('0', $scale - 1).'1'; } /** - * Override the locale for this field. + * Get number of digits to show to the right of the decimal point. + * 0 for integer, any number for floating point, or null to flexible * - * @param string $locale + * @return int|null + */ + public function getScale() + { + return $this->scale; + } + + /** + * Get number of digits to show to the right of the decimal point. + * 0 for integer, any number for floating point, or null to flexible * + * @param int|null $scale * @return $this */ - public function setLocale($locale) + public function setScale($scale) { - $this->locale = $locale; - + $this->scale = $scale; return $this; } } diff --git a/src/Forms/PhoneNumberField.php b/src/Forms/PhoneNumberField.php index d183f188da4..4e28a71b137 100644 --- a/src/Forms/PhoneNumberField.php +++ b/src/Forms/PhoneNumberField.php @@ -151,7 +151,7 @@ public function Field($properties = array()) return parent::Field($properties); } - public function setValue($value) + public function setValue($value, $data = null) { $this->value = self::joinPhoneNumber($value); $parts = $this->parseValue(); diff --git a/src/Forms/SelectionGroup_Item.php b/src/Forms/SelectionGroup_Item.php index c1cb7e15535..03e6c7e9ef3 100644 --- a/src/Forms/SelectionGroup_Item.php +++ b/src/Forms/SelectionGroup_Item.php @@ -48,7 +48,7 @@ function getValue() return $this->value; } - function setValue($Value) + function setValue($Value, $data = null) { $this->value = $Value; return $this; diff --git a/src/Forms/TimeField.php b/src/Forms/TimeField.php index 189a33075ca..1ea49bfb146 100644 --- a/src/Forms/TimeField.php +++ b/src/Forms/TimeField.php @@ -2,23 +2,14 @@ namespace SilverStripe\Forms; -use SilverStripe\Core\Convert; +use IntlDateFormatter; +use InvalidArgumentException; use SilverStripe\i18n\i18n; -use Zend_Date; - -require_once 'Zend/Date.php'; +use SilverStripe\ORM\FieldType\DBDatetime; /** * Form field to display editable time values in an field. * - * # Configuration - * - * - 'timeformat' (string): Time format compatible with Zend_Date. - * Usually set to default format for {@link locale} - * through {@link Zend_Locale_Format::getTimeFormat()}. - * - 'use_strtotime' (boolean): Accept values in PHP's built-in strtotime() notation, in addition - * to the format specified in `timeformat`. Example inputs: 'now', '11pm', '23:59:59'. - * * # Localization * * See {@link DateField} @@ -27,148 +18,251 @@ */ class TimeField extends TextField { + protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_TIME; /** - * @config - * @var array + * Override locale. If empty will default to current locale + * + * @var string */ - private static $default_config = array( - 'timeformat' => null, - 'use_strtotime' => true, - 'datavalueformat' => 'HH:mm:ss' - ); + protected $locale = null; /** - * @var array + * Override time format. If empty will default to that used by the current locale. + * + * @var string */ - protected $config; + protected $timeFormat = null; /** - * @var String + * Length of this date (full, short, etc). + * + * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants + * @var int */ - protected $locale = null; + protected $timeLength = null; /** - * @var Zend_Date Just set if the date is valid. - * {@link $value} will always be set to aid validation, - * and might contain invalid values. + * Unparsed value, used exclusively for comparing with internal value + * to detect invalid values. + * + * @var mixed */ - protected $valueObj = null; + protected $rawValue = null; - protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_TIME; + /** + * Set custom timezone + * + * @var string + */ + protected $timezone = null; - public function __construct($name, $title = null, $value = "") + /** + * Get time format in CLDR standard format + * + * This can be set explicitly. If not, this will be generated from the current locale + * with the current time length. + * + * @see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Field-Symbol-Table + */ + public function getTimeFormat() { - if (!$this->locale) { - $this->locale = i18n::get_locale(); + if ($this->timeFormat) { + return $this->timeFormat; } - $this->config = $this->config()->default_config; - - if (!$this->getConfig('timeformat')) { - $this->setConfig('timeformat', i18n::config()->get('time_format')); - } - - parent::__construct($name, $title, $value); + // Get from locale + return $this->getFormatter()->getPattern(); } - public function Field($properties = array()) + /** + * Set time format in CLDR standard format. + * + * @see http://userguide.icu-project.org/formatparse/datetime#TOC-Date-Field-Symbol-Table + * @param string $format + * @return $this + */ + public function setTimeFormat($format) { - $config = array( - 'timeformat' => $this->getConfig('timeformat') - ); - $config = array_filter($config); - $this->addExtraClass(Convert::raw2json($config)); - return parent::Field($properties); + $this->timeFormat = $format; + return $this; } - public function Type() + /** + * Get length of the time format to use. One of: + * + * - IntlDateFormatter::SHORT E.g. '6:31 PM' + * - IntlDateFormatter::MEDIUM E.g. '6:30:48 PM' + * - IntlDateFormatter::LONG E.g. '6:32:09 PM NZDT' + * - IntlDateFormatter::FULL E.g. '6:32:24 PM New Zealand Daylight Time' + * + * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants + * @return int + */ + public function getTimeLength() { - return 'time text'; + if ($this->timeLength) { + return $this->timeLength; + } + return IntlDateFormatter::MEDIUM; } /** - * Parses a time into a Zend_Date object + * Get length of the time format to use. One of: + * + * - IntlDateFormatter::SHORT E.g. '6:31 PM' + * - IntlDateFormatter::MEDIUM E.g. '6:30:48 PM' + * - IntlDateFormatter::LONG E.g. '6:32:09 PM NZDT' + * - IntlDateFormatter::FULL E.g. '6:32:24 PM New Zealand Daylight Time' * - * @param string $value Raw value - * @param string $format Format string to check against - * @param string $locale Optional locale to parse against - * @param boolean $exactMatch Flag indicating that the date must be in this - * exact format, and is unchanged after being parsed and written out + * @see http://php.net/manual/en/class.intldateformatter.php#intl.intldateformatter-constants * - * @return Zend_Date Returns the Zend_Date, or null if not in the specified format + * @param int $length + * @return $this */ - protected function parseTime($value, $format, $locale = null, $exactMatch = false) + public function setTimeLength($length) { - // Check if the date is in the correct format - if (!Zend_Date::isDate($value, $format)) { - return null; + $this->timeLength = $length; + return $this; + } + + /** + * Get time formatter with the standard locale / date format + * + * @return IntlDateFormatter + */ + protected function getFormatter() + { + $formatter = IntlDateFormatter::create( + $this->getLocale(), + IntlDateFormatter::NONE, + $this->getTimeLength(), + $this->getTimezone() + ); + + // Don't invoke getDateFormat() directly to avoid infinite loop + if ($this->timeFormat) { + $ok = $formatter->setPattern($this->timeFormat); + if (!$ok) { + throw new InvalidArgumentException("Invalid time format {$this->timeFormat}"); + } } + return $formatter; + } + + /** + * Get a time formatter for the ISO 8601 format + * + * @return IntlDateFormatter + */ + protected function getISO8601Formatter() + { + $formatter = IntlDateFormatter::create( + i18n::config()->get('default_locale'), + IntlDateFormatter::NONE, + IntlDateFormatter::MEDIUM, + date_default_timezone_get() // Default to server timezone + ); + $formatter->setLenient(false); + // CLDR iso8601 time + // Note we omit timezone from this format, and we assume server TZ always. + $formatter->setPattern('HH:mm:ss'); + return $formatter; + } - // Parse the value - $valueObject = new Zend_Date($value, $format, $locale); + public function getAttribute($name) + { + $attributes = parent::getAttributes(); - // For exact matches, ensure the value preserves formatting after conversion - if ($exactMatch && ($value !== $valueObject->get($format))) { - return null; - } else { - return $valueObject; + // Merge with client config + $config = $this->getClientConfig(); + foreach ($config as $key => $value) { + $attributes["data-{$key}"] = $value; } + return $attributes; } + /** + * Get client config options for this field + * + * @return array + */ + public function getClientConfig() + { + return [ + // @todo - Support javascript time picker + 'timeformat' => $this->getTimeFormat(), + ]; + } + + public function Type() + { + return 'time text'; + } /** - * Sets the internal value to ISO date format. + * Assign value posted from form submission * - * @param mixed $val + * @param mixed $value + * @param mixed $data * @return $this */ - public function setValue($val) + public function setSubmittedValue($value, $data = null) { + // Save raw value for later validation + $this->rawValue = $value; - // Fuzzy matching through strtotime() to support a wider range of times, - // e.g. 11am. This means that validate() might not fire. - // Note: Time formats are assumed to be less ambiguous than dates across locales. - if ($this->getConfig('use_strtotime') && !empty($val)) { - if ($parsedTimestamp = strtotime($val)) { - $parsedObj = new Zend_Date($parsedTimestamp, Zend_Date::TIMESTAMP); - $val = $parsedObj->get($this->getConfig('timeformat')); - unset($parsedObj); - } - } + // Parse from submitted value + $this->value = $this->localisedToISO8601($value); + return $this; + } - if (empty($val)) { - $this->value = null; - $this->valueObj = null; - } // load ISO time from database (usually through Form->loadDataForm()) - // Requires exact format to prevent false positives from locale specific times - elseif ($this->valueObj = $this->parseTime($val, $this->getConfig('datavalueformat'), null, true)) { - $this->value = $this->valueObj->get($this->getConfig('timeformat')); - } // Set in current locale (as string) - elseif ($this->valueObj = $this->parseTime($val, $this->getConfig('timeformat'), $this->locale)) { - $this->value = $this->valueObj->get($this->getConfig('timeformat')); - } // Fallback: Set incorrect value so validate() can pick it up - elseif (is_string($val)) { - $this->value = $val; - $this->valueObj = null; - } else { + /** + * Set time assigned from database value + * + * @param mixed $value + * @param mixed $data + * @return $this + */ + public function setValue($value, $data = null) + { + // Save raw value for later validation + $this->rawValue = $value; + + // Null case + if (!$value) { $this->value = null; - $this->valueObj = null; + return $this; } + // Re-run through formatter to tidy up (e.g. remove date component) + $this->value = $this->tidyISO8601($value); return $this; } + public function Value() + { + $localised = $this->iso8601ToLocalised($this->value); + if ($localised) { + return $localised; + } + + // Show midnight in localised format + return $this->getMidnight(); + } + /** - * @return String ISO 8601 date, suitable for insertion into database + * Show midnight in current format (adjusts for timezone) + * + * @return string */ - public function dataValue() + public function getMidnight() { - if ($this->valueObj) { - return $this->valueObj->toString($this->getConfig('datavalueformat')); - } else { - return null; - } + $formatter = $this->getFormatter(); + $timestamp = $this->withTimezone($this->getTimezone(), function () { + return strtotime('midnight'); + }); + return $formatter->format($timestamp); } /** @@ -179,21 +273,20 @@ public function dataValue() */ public function validate($validator) { - // Don't validate empty fields - if (empty($this->value)) { + if (empty($this->rawValue)) { return true; } - if (!Zend_Date::isDate($this->value, $this->getConfig('timeformat'), $this->locale)) { + // We submitted a value, but it couldn't be parsed + if (empty($this->value)) { $validator->validationError( $this->name, _t( 'TimeField.VALIDATEFORMAT', "Please enter a valid time format ({format})", - array('format' => $this->getConfig('timeformat')) - ), - "validation" + ['format' => $this->getTimeFormat()] + ) ); return false; } @@ -205,7 +298,7 @@ public function validate($validator) */ public function getLocale() { - return $this->locale; + return $this->locale ?: i18n::get_locale(); } /** @@ -219,48 +312,125 @@ public function setLocale($locale) } /** - * @param string $name - * @param mixed $val - * @return $this + * Creates a new readonly field specified below + * + * @return TimeField_Readonly */ - public function setConfig($name, $val) + public function performReadonlyTransformation() { - $this->config[$name] = $val; - return $this; + /** @var TimeField_Readonly $result */ + $result = $this->castedCopy(TimeField_Readonly::class); + return $result; } /** - * @param String $name Optional, returns the whole configuration array if empty - * @return mixed|array + * Convert time localised in the current locale to ISO 8601 time + * + * @param string $time + * @return string The formatted time, or null if not a valid time */ - public function getConfig($name = null) + public function localisedToISO8601($time) { - if ($name) { - return isset($this->config[$name]) ? $this->config[$name] : null; - } else { - return $this->config; + if (!$time) { + return null; + } + $fromFormatter = $this->getFormatter(); + $toFormatter = $this->getISO8601Formatter(); + $timestamp = $fromFormatter->parse($time); + if ($timestamp === false) { + return null; } + return $toFormatter->format($timestamp); } /** - * Creates a new readonly field specified below + * Format iso time to localised form + * + * @param string $time + * @return string */ - public function performReadonlyTransformation() + public function iso8601ToLocalised($time) { - return $this->castedCopy('SilverStripe\\Forms\\TimeField_Readonly'); + $time = $this->tidyISO8601($time); + if (!$time) { + return null; + } + $fromFormatter = $this->getISO8601Formatter(); + $toFormatter = $this->getFormatter(); + $timestamp = $fromFormatter->parse($time); + if ($timestamp === false) { + return null; + } + return $toFormatter->format($timestamp); } - public function castedCopy($class) + + + /** + * Tidy up iso8601-ish time, or approximation + * + * @param string $time Time in iso8601 or approximate form + * @return string iso8601 time, or null if not valid + */ + public function tidyISO8601($time) { - $copy = parent::castedCopy($class); - if ($copy->hasMethod('setConfig')) { - $config = $this->getConfig(); - foreach ($config as $k => $v) { - /** @var TimeField $copy */ - $copy->setConfig($k, $v); + if (!$time) { + return null; + } + // Re-run through formatter to tidy up (e.g. remove date component) + $formatter = $this->getISO8601Formatter(); + $timestamp = $formatter->parse($time); + if ($timestamp === false) { + // Fallback to strtotime + $timestamp = strtotime($time, DBDatetime::now()->getTimestamp()); + if ($timestamp === false) { + return null; } } + return $formatter->format($timestamp); + } + + /** + * @return string + */ + public function getTimezone() + { + return $this->timezone; + } + + /** + * @param string $timezone + * @return $this + */ + public function setTimezone($timezone) + { + if ($this->value && $timezone !== $this->timezone) { + throw new \BadMethodCallException("Can't change timezone after setting a value"); + } + $this->timezone = $timezone; + return $this; + } - return $copy; + + /** + * Run a callback within a specific timezone + * + * @param string $timezone + * @param callable $callback + */ + protected function withTimezone($timezone, $callback) + { + $currentTimezone = date_default_timezone_get(); + try { + if ($timezone) { + date_default_timezone_set($timezone); + } + return $callback(); + } finally { + // Restore timezone + if ($timezone) { + date_default_timezone_set($currentTimezone); + } + } } } diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index 613dc14112e..b0577a71cbf 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -6,6 +6,7 @@ use SilverStripe\Core\Config\Config; use SilverStripe\Core\Object; use SilverStripe\Core\Injector\Injector; +use SilverStripe\Core\Resettable; use SilverStripe\Dev\Deprecation; use SilverStripe\Dev\Debug; use SilverStripe\Control\HTTP; @@ -100,7 +101,7 @@ * @property string LastEdited Date and time of DataObject's last modification. * @property string Created Date and time of DataObject creation. */ -class DataObject extends ViewableData implements DataObjectInterface, i18nEntityProvider +class DataObject extends ViewableData implements DataObjectInterface, i18nEntityProvider, Resettable { /** diff --git a/src/ORM/FieldType/DBDate.php b/src/ORM/FieldType/DBDate.php index f336d63e471..461530acc6f 100644 --- a/src/ORM/FieldType/DBDate.php +++ b/src/ORM/FieldType/DBDate.php @@ -2,18 +2,17 @@ namespace SilverStripe\ORM\FieldType; -use SilverStripe\Core\Convert; +use IntlDateFormatter; +use InvalidArgumentException; +use NumberFormatter; use SilverStripe\Forms\DateField; +use SilverStripe\i18n\i18n; use SilverStripe\ORM\DB; use SilverStripe\Security\Member; -use DateTime; -use Exception; -use Zend_Date; /** * Represents a date field. - * The field currently supports New Zealand date format (DD/MM/YYYY), - * or an ISO 8601 formatted date (YYYY-MM-DD). + * Dates should be stored using ISO 8601 formatted date (y-MM-dd). * Alternatively you can set a timestamp that is evaluated through * PHP's built-in date() function according to your system locale. * @@ -24,76 +23,73 @@ * ); * * - * @todo Add localization support, see http://open.silverstripe.com/ticket/2931 + * Date formats all follow CLDR standard format codes + * @link http://userguide.icu-project.org/formatparse/datetime */ class DBDate extends DBField { - /** - * @config - * @see DBDateTime::nice_format - * @see DBTime::nice_format - * @return $this + * Standard ISO format string for date in CLDR standard format */ - private static $nice_format = 'd/m/Y'; + const ISO_DATE = 'y-MM-dd'; public function setValue($value, $record = null, $markChanged = true) { - if ($value === false || $value === null || (is_string($value) && !strlen($value))) { - // don't try to evaluate empty values with strtotime() below, as it returns "1970-01-01" when it should be - // saved as NULL in database - $this->value = null; - return $this; - } - - // @todo This needs tidy up (what if you only specify a month and a year, for example?) - if (is_array($value)) { - if (!empty($value['Day']) && !empty($value['Month']) && !empty($value['Year'])) { - $this->value = $value['Year'] . '-' . $value['Month'] . '-' . $value['Day']; - } - /* - * return $this whether successfully set values from array based - * input or not (so checks below don't fail on an empty array) - */ - return $this; - } - - // Default to NZ date format - strtotime expects a US date - if (preg_match('#^([0-9]+)/([0-9]+)/([0-9]+)$#', $value, $parts)) { - $value = "$parts[2]/$parts[1]/$parts[3]"; - } - - if (is_numeric($value)) { - $this->value = date('Y-m-d', $value); - } elseif (is_string($value)) { - try { - $date = new DateTime($value); - $this->value = $date->format('Y-m-d'); - } catch (Exception $e) { - $this->value = null; - } + $value = $this->parseDate($value); + if ($value === false) { + throw new InvalidArgumentException( + "Invalid date passed. Use " . self::ISO_DATE . " to prevent this error." + ); } + $this->value = $value; return $this; } /** - * Returns the date in the format specified by the config value nice_format, or dd/mm/yy by default + * Parse timestamp or iso8601-ish date into standard iso8601 format * - * @return string + * @param mixed $value + * @return string|null|false Formatted date, null if empty but valid, or false if invalid */ - public function Nice() + protected function parseDate($value) { - return $this->Format($this->config()->nice_format); + // Skip empty values + if (empty($value) && !is_numeric($value)) { + return null; + } + + // Determine value to parse + if (is_array($value)) { + $source = $value; // parse array + } elseif (is_numeric($value)) { + $source = $value; // parse timestamp + } else { + // Convert US date -> iso, fix y2k, etc + $value = $this->fixInputDate($value); + $source = strtotime($value); // convert string to timestamp + } + if ($value === false) { + return false; + } + + // Format as iso8601 + $formatter = $this->getFormatter(); + $formatter->setPattern($this->getISOFormat()); + return $formatter->format($source); } /** - * Returns the date in US format: “01/18/2006” + * Returns the standard localised medium date * * @return string */ - public function NiceUS() + public function Nice() { - return $this->Format('m/d/Y'); + if (!$this->value) { + return null; + } + $formatter = $this->getFormatter(); + return $formatter->format($this->getTimestamp()); } /** @@ -103,17 +99,17 @@ public function NiceUS() */ public function Year() { - return $this->Format('Y'); + return $this->Format('y'); } /** - * Returns the Full day, of the given date. + * Returns the day of the week * * @return string */ - public function Day() + public function DayOfWeek() { - return $this->Format('l'); + return $this->Format('cccc'); } /** @@ -123,7 +119,7 @@ public function Day() */ public function Month() { - return $this->Format('F'); + return $this->Format('LLLL'); } /** @@ -133,73 +129,116 @@ public function Month() */ public function ShortMonth() { - return $this->Format('M'); + return $this->Format('LLL'); } /** * Returns the day of the month. + * * @param bool $includeOrdinal Include ordinal suffix to day, e.g. "th" or "rd" * @return string */ public function DayOfMonth($includeOrdinal = false) { - $format = 'j'; - if ($includeOrdinal) { - $format .= 'S'; + $number = $this->Format('d'); + if ($includeOrdinal && $number) { + $formatter = NumberFormatter::create(i18n::get_locale(), NumberFormatter::ORDINAL); + return $formatter->format((int)$number); } - return $this->Format($format); + return $number; } /** - * Returns the date in the format 24 December 2006 + * Returns the date in the localised short format + * + * @return string + */ + public function Short() + { + if (!$this->value) { + return null; + } + $formatter = $this->getFormatter(IntlDateFormatter::SHORT); + return $formatter->format($this->getTimestamp()); + } + + /** + * Returns the date in the localised long format * * @return string */ public function Long() { - return $this->Format('j F Y'); + if (!$this->value) { + return null; + } + $formatter = $this->getFormatter(IntlDateFormatter::LONG); + return $formatter->format($this->getTimestamp()); } /** - * Returns the date in the format 24 Dec 2006 + * Returns the date in the localised full format * * @return string */ public function Full() { - return $this->Format('j M Y'); + if (!$this->value) { + return null; + } + $formatter = $this->getFormatter(IntlDateFormatter::FULL); + return $formatter->format($this->getTimestamp()); + } + + /** + * Get date formatter + * + * @param int $dateLength + * @param int $timeLength + * @return IntlDateFormatter + */ + public function getFormatter($dateLength = IntlDateFormatter::MEDIUM, $timeLength = IntlDateFormatter::NONE) + { + return new IntlDateFormatter(i18n::get_locale(), $dateLength, $timeLength); + } + + /** + * Get standard ISO date format string + * + * @return string + */ + public function getISOFormat() + { + return self::ISO_DATE; } /** * Return the date using a particular formatting string. * - * @param string $format Format code string. e.g. "d M Y" (see http://php.net/date) + * @param string $format Format code string. See http://userguide.icu-project.org/formatparse/datetime * @return string The date in the requested format */ public function Format($format) { - if ($this->value) { - $date = new DateTime($this->value); - return $date->format($format); + if (!$this->value) { + return null; } - return null; + $formatter = $this->getFormatter(); + $formatter->setPattern($format); + return $formatter->format($this->getTimestamp()); } /** - * Return the date formatted using the given strftime formatting string. - * - * strftime obeys the current LC_TIME/LC_ALL when printing lexical values - * like day- and month-names + * Get unix timestamp for this date * - * @param string $formattingString - * @return string + * @return int */ - public function FormatI18N($formattingString) + public function getTimestamp() { if ($this->value) { - return strftime($formattingString, strtotime($this->value)); + return strtotime($this->value); } - return null; + return 0; } /** @@ -210,19 +249,18 @@ public function FormatI18N($formattingString) */ public function FormatFromSettings($member = null) { - require_once 'Zend/Date.php'; - if (!$member) { - if (!Member::currentUserID()) { - return false; - } $member = Member::currentUser(); } - $formatD = $member->getDateFormat(); - $zendDate = new Zend_Date($this->getValue(), 'y-MM-dd'); + // Fall back to nice + if (!$member) { + return $this->Nice(); + } - return $zendDate->toString($formatD); + // Get user format + $format = $member->getDateFormat(); + return $this->Format($format); } /** @@ -258,7 +296,7 @@ public function RangeString($otherDateObj, $includeOrdinals = false) public function Rfc822() { if ($this->value) { - return date('r', strtotime($this->value)); + return date('r', $this->getTimestamp()); } return null; } @@ -271,18 +309,23 @@ public function Rfc822() public function Rfc2822() { if ($this->value) { - return date('Y-m-d H:i:s', strtotime($this->value)); + return date('Y-m-d H:i:s', $this->getTimestamp()); } return null; } + /** + * Date in RFC3339 format + * + * @return string + */ public function Rfc3339() { - $timestamp = ($this->value) ? strtotime($this->value) : false; - if (!$timestamp) { - return false; + if (!$this->value) { + return null; } + $timestamp = $this->getTimestamp(); $date = date('Y-m-d\TH:i:s', $timestamp); $matches = array(); @@ -300,15 +343,16 @@ public function Rfc3339() * * @param boolean $includeSeconds Show seconds, or just round to "less than a minute". * @param int $significance Minimum significant value of X for "X units ago" to display - * @return String + * @return string */ public function Ago($includeSeconds = true, $significance = 2) { if (!$this->value) { return null; } - $time = DBDatetime::now()->Format('U'); - if (strtotime($this->value) == $time || $time > strtotime($this->value)) { + $timestamp = $this->getTimestamp(); + $now = DBDatetime::now()->getTimestamp(); + if ($timestamp <= $now) { return _t( 'Date.TIMEDIFFAGO', "{difference} ago", @@ -336,8 +380,9 @@ public function TimeDiff($includeSeconds = true, $significance = 2) return false; } - $time = DBDatetime::now()->Format('U'); - $ago = abs($time - strtotime($this->value)); + $now = DBDatetime::now()->getTimestamp(); + $time = $this->getTimestamp(); + $ago = abs($time - $now); if ($ago < 60 && !$includeSeconds) { return _t('Date.LessThanMinuteAgo', 'less than a minute'); } elseif ($ago < $significance * 60 && $includeSeconds) { @@ -368,33 +413,57 @@ public function TimeDiffIn($format) return null; } - $time = DBDatetime::now()->Format('U'); - $ago = abs($time - strtotime($this->value)); - + $now = DBDatetime::now()->getTimestamp(); + $time = $this->getTimestamp(); + $ago = abs($time - $now); switch ($format) { case "seconds": $span = $ago; - return ($span != 1) ? "{$span} "._t("Date.SECS", "secs") : "{$span} "._t("Date.SEC", "sec"); + return _t( + __CLASS__.'.SECONDS_SHORT_PLURALS', + '{count} sec|{count} secs', + ['count' => $span] + ); case "minutes": $span = round($ago/60); - return ($span != 1) ? "{$span} "._t("Date.MINS", "mins") : "{$span} "._t("Date.MIN", "min"); + return _t( + __CLASS__.'.MINUTES_SHORT_PLURALS', + '{count} min|{count} mins', + ['count' => $span] + ); case "hours": $span = round($ago/3600); - return ($span != 1) ? "{$span} "._t("Date.HOURS", "hours") : "{$span} "._t("Date.HOUR", "hour"); + return _t( + __CLASS__.'.HOURS_SHORT_PLURALS', + '{count} hour|{count} hours', + ['count' => $span] + ); case "days": $span = round($ago/86400); - return ($span != 1) ? "{$span} "._t("Date.DAYS", "days") : "{$span} "._t("Date.DAY", "day"); + return _t( + __CLASS__.'.DAYS_SHORT_PLURALS', + '{count} day|{count} days', + ['count' => $span] + ); case "months": $span = round($ago/86400/30); - return ($span != 1) ? "{$span} "._t("Date.MONTHS", "months") : "{$span} "._t("Date.MONTH", "month"); + return _t( + __CLASS__.'.MONTHS_SHORT_PLURALS', + '{count} month|{count} months', + ['count' => $span] + ); case "years": $span = round($ago/86400/365); - return ($span != 1) ? "{$span} "._t("Date.YEARS", "years") : "{$span} "._t("Date.YEAR", "year"); + return _t( + __CLASS__.'.YEARS_SHORT_PLURALS', + '{count} year|{count} years', + ['count' => $span] + ); default: throw new \InvalidArgumentException("Invalid format $format"); @@ -414,7 +483,7 @@ public function requireField() */ public function InPast() { - return strtotime($this->value) < DBDatetime::now()->Format('U'); + return strtotime($this->value) < DBDatetime::now()->getTimestamp(); } /** @@ -423,7 +492,7 @@ public function InPast() */ public function InFuture() { - return strtotime($this->value) > DBDatetime::now()->Format('U'); + return strtotime($this->value) > DBDatetime::now()->getTimestamp(); } /** @@ -432,85 +501,129 @@ public function InFuture() */ public function IsToday() { - return (date('Y-m-d', strtotime($this->value)) == DBDatetime::now()->Format('Y-m-d')); + return $this->Format(self::ISO_DATE) === DBDatetime::now()->Format(self::ISO_DATE); } /** * Returns a date suitable for insertion into a URL and use by the system. + * + * @return string */ public function URLDate() { - return date('Y-m-d', strtotime($this->value)); + return rawurlencode($this->Format(self::ISO_DATE)); } - - public function days_between($fyear, $fmonth, $fday, $tyear, $tmonth, $tday) + public function scaffoldFormField($title = null, $params = null) { - return abs((mktime(0, 0, 0, $fmonth, $fday, $fyear) - mktime(0, 0, 0, $tmonth, $tday, $tyear))/(60*60*24)); - } + $field = DateField::create($this->name, $title); + $format = $field->getDateFormat(); - public function day_before($fyear, $fmonth, $fday) - { - return date("Y-m-d", mktime(0, 0, 0, $fmonth, $fday-1, $fyear)); - } + // Show formatting hints for better usability + $now = DBDatetime::now()->Format($format); + $field->setDescription(_t( + 'FormField.EXAMPLE', + 'e.g. {format}', + 'Example format', + [ 'format' => $now ] + )); + $field->setAttribute('placeholder', $format); - public function next_day($fyear, $fmonth, $fday) - { - return date("Y-m-d", mktime(0, 0, 0, $fmonth, $fday+1, $fyear)); + return $field; } - public function weekday($fyear, $fmonth, $fday) + /** + * Fix non-iso dates + * + * @param string $value + * @return string + */ + protected function fixInputDate($value) { - // 0 is a Monday - return (((mktime(0, 0, 0, $fmonth, $fday, $fyear) - mktime(0, 0, 0, 7, 17, 2006))/(60*60*24))+700000) % 7; - } + // split + list($day, $month, $year, $time) = $this->explodeDateString($value); - public function prior_monday($fyear, $fmonth, $fday) - { - return date("Y-m-d", mktime(0, 0, 0, $fmonth, $fday-$this->weekday($fyear, $fmonth, $fday), $fyear)); + // Detect invalid year order + if (!checkdate($month, $day, $year) && checkdate($month, $year, $day)) { + trigger_error( + "Unexpected date order. Use " . self::ISO_DATE . " to prevent this notice.", + E_USER_NOTICE + ); + list($day, $year) = [$year, $day]; + } + + // Fix y2k year + $year = $this->guessY2kYear($year); + + // Validate date + if (!checkdate($month, $day, $year)) { + throw new InvalidArgumentException("Invalid date passed. Use " . self::ISO_DATE . " to prevent this error."); + } + + // Convert to y-m-d + return sprintf('%d-%02d-%02d%s', $year, $month, $day, $time); } /** - * Return the nearest date in the past, based on day and month. - * Automatically attaches the correct year. - * - * This is useful for determining a financial year start or end date. + * Attempt to split date string into day, month, year, and timestamp components. + * Don't read this code without a drink in hand! * - * @param $fmonth int The number of the month (e.g. 3 is March, 4 is April) - * @param $fday int The day of the month - * @param $fyear int Determine historical value - * @return string Date in YYYY-MM-DD format + * @param string $value + * @return array */ - public static function past_date($fmonth, $fday = 1, $fyear = null) + protected function explodeDateString($value) { - if (!$fyear) { - $fyear = date('Y'); + // US date format with 4-digit year first + if (preg_match('#^(?\\d{4})/(?\\d+)/(?\\d+)(?