sso looks working

This commit is contained in:
msyu
2025-09-14 12:09:18 +03:00
commit 89ac26325d
149 changed files with 7763 additions and 0 deletions
+354
View File
@@ -0,0 +1,354 @@
<!---
bean (child tag)
msyu@mail.ru
2013-08-29 v0.1
2013-09-04 v0.2 tested on CF8 and Railo
2014-01-20 v0.3 added exception for unsupported parameter type
2014-10-24 v0.5 ATTRIBUTES.field became default for ATTRIBUTES.param
2014-11-18 v0.6 rework
2015-07-21 v0.7 EXITTAG moved to the end of file
2015-07-22 v0.8 Changed protocol for skipInsert, skipUpdate, key, readonly, autoincrement, required attributes, now we can set value at runtime like skipInsert="#boolean#" instead of just including or omitting the attribute
2015-12-02 v0.9 Added preprocessor attribute. Fixed timestamp bug ('01.12.2015' treated as 12 JAN 2015)
2015-12-04 v0.12 for dates, submitted value is now back-overwritten
2015-12-04 v0.13 bug fix
v0.94
2017-03-10 v0.15 - scope resolution order changed to CALLER-FORM-URL
2018-05-31 v0.16 - strip whitespace from numbers
2019-02-09 v0.17 - call preprocessor before checking required field
2022-01-06 v0.18 - added BIT type (for PostgreSQL) - в базе представляется boolean!
2023-04-17 v0.19 - форматирование, добавлен other к idstamp (для postgres)
2023-04-18 v0.20 - добавлен атрибут format, для даты
2024-03-22 v0.21 - добавлены синонимы int,boolean,bool. Перенумерованы версии 91->11
2024-10-16 v0.23 - добавлены атрибуты minlength, maxlength, regex, validate
--->
<!--- проверка на неподдерживаемые поля --->
<cfloop collection=#ATTRIBUTES# item="a">
<cfif NOT ListFindNoCase("param,field,type,displayName,default,init,size,scale,skipInsert,skipUpdate,key,readonly,autoincrement,value,null,forNull,eval,required,preprocessor,dtlocale,format,maxlength,minlength,regex,validate", #a#)>
<cfthrow type="UnsupportedAttribute" message='Attribute #a# is unsupported by bean/param' detail='Specified attribute "#a#" is not supported by custom tag bean/param'/>
</cfif>
</cfloop>
<cfparam name="ATTRIBUTES.default" default=""/><!--- подставляется при сохранении, если параметр не передавался. Важно для чекбоксов, они не передаются, если не отмечены --->
<cfparam name="ATTRIBUTES.init" default=#ATTRIBUTES.default#/><!--- передается в форму для новой записи (предзаполнение). Для чекбокса может быть init=1, default=0 - отмеченный по умолчанию --->
<cfparam name="ATTRIBUTES.field"/><!--- имя поля в таблице БД --->
<cfparam name="ATTRIBUTES.param" default="#ATTRIBUTES.field#"/><!--- имя поля в URL/FORM и экспортируемой переменной --->
<cfparam name="ATTRIBUTES.type" default="varchar"/><!--- тип поля в терминах SQL (CF_SQL_*) --->
<cfparam name="ATTRIBUTES.displayName" default=#ATTRIBUTES.param#/><!--- для сообщений об ошибках--->
<cfparam name="ATTRIBUTES.size" default="0"/><!--- for strings, soft limit (causes truncation). 0=unlimited --->
<cfparam name="ATTRIBUTES.scale" default="1"/> <!--- Множитель. Только для чисел, только при записи. --->
<!--- если атрибут записан без значения, он получает значение true! --->
<cfparam name="ATTRIBUTES.skipInsert" type="boolean" default="false"/>
<cfparam name="ATTRIBUTES.skipUpdate" type="boolean" default="false"/>
<cfparam name="ATTRIBUTES.key" type="boolean" default="false"/>
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="false"/>
<cfparam name="ATTRIBUTES.autoincrement" type="boolean" default="false"/><!--- правильнее autogenerated, потому что GUID тоже бывает генеренным --->
<cfparam name="ATTRIBUTES.required" type="boolean" default="false"/>
<cfparam name="ATTRIBUTES.dtlocale" type="string" default="ru"/><!--- datetime locale--->
<cfparam name="ATTRIBUTES.format" type="string" default=""/><!--- datetime format, normally yyyy-MM-dd, CASE SENSITIVE, NOT YYYY-DD-MM!!!--->
<cfparam name="ATTRIBUTES.maxlength" default="0"/><!--- max length hard limit, throws exception unlike ATTRIBUTES.size. 0=unlimited, специально не указываем тип - то есть это по умолчанию строка - чтобы не ломалось от пустой строки--->
<cfparam name="ATTRIBUTES.minlength" default="0"/><!--- min length hard limit, throws exception unlike ATTRIBUTES.size. 0=unlimited, специально не указываем тип - то есть это по умолчанию строка - чтобы не ломалось от пустой строки--->
<!--- <cfparam name="ATTRIBUTES.regex" type="string" default=""/> мы его закомментировали и оставили, чтобы про него не забыть, а дальше мы проверяем его наличие в скоупе атрибутов ---><!--- validation regex --->
<cfparam name="ATTRIBUTES.validate" type="any" default=#function(x){return ""}#/><!--- non-empty return indicates error --->
<!--- ATTRIBUTES.forNull ---><!--- если forNull не задано, параметр считается обязательным ***?--->
<!--- ATTRIBUTES.preprocessor ---><!--- функция --->
<!--- in-out --->
<cfparam name="ATTRIBUTES.null" type="boolean" default="false"/>
<!--- ATTRIBUTES.value - входной-выходной параметр. --->
<!---/in-out --->
<!--- *** выходные параметры. Странно выглядит. Хоть как-то в имени это отразить, что выходные... --->
<cfparam name="ATTRIBUTES.hasInput" type="boolean" default="false" />
<cfparam name="ATTRIBUTES.submittedValue" type="string" default=""/><!--- необработанное входное значение --->
<cfparam name="ATTRIBUTES.errorState" type="boolean" default="false"/>
<cfparam name="ATTRIBUTES.errorMessage" type="string" default=""/>
<cfparam name="ATTRIBUTES.warningState" type="boolean" default="false"/>
<cfparam name="ATTRIBUTES.warningMessage" type="string" default=""/>
<!--- Тут намешано 2 стиля, и оба не вызывают восторга
Атрибут декларируется с дефолтом (потому что, в отличие от аргумента функции, мы не можем декларировать необязательный атрибут, так как используем cfparam)
Или атрибут не декларируется, а проверяется его наличие в скоупе атрибутов, что не улучшает читаемость кода --->
<cfassociate basetag="cf_bean" datacollection="params" />
<!--- Поле "только для чтения" не обрабатываем никак, если только оно не ключевое. Зачем может понадобиться readonly ключевое поле - непонятно, но вдруг --->
<cfif NOT (ATTRIBUTES.key)>
<cfif (ATTRIBUTES.readonly)>
<cfexit method="exittag"/>
</cfif>
</cfif>
<!--- Если атрибуту явно указан NULL, дальше ничего не делаем --->
<cfif ATTRIBUTES.null>
<cfset ATTRIBUTES.value="0"/><!--- значение не используется, но переменная должна быть определена --->
<cfexit method="exittag" />
</cfif>
<!--- принимаем значение из передаваемых данных или подставляем значение по умолчанию --->
<!--- можно было бы попробовать cfparam CALLER.#ATTRIBUTES.param# и обрабатывать исключение, но так обходимся без исключений, хотя и не изящно --->
<cfif StructKeyExists(ATTRIBUTES,"value")>
<cfset submittedValue=ATTRIBUTES.value/>
<cfset ATTRIBUTES.hasInput="true"/>
<cfelseif StructKeyExists(CALLER, ATTRIBUTES.param)>
<cfset submittedValue=structFind(CALLER, ATTRIBUTES.param)/>
<cfset ATTRIBUTES.hasInput="true"/>
<cfelseif StructKeyExists(FORM, ATTRIBUTES.param)>
<cfset submittedValue=structFind(FORM, ATTRIBUTES.param)/>
<cfset ATTRIBUTES.hasInput="true"/>
<cfelseif StructKeyExists(URL, ATTRIBUTES.param)>
<cfset submittedValue=structFind(URL, ATTRIBUTES.param)/>
<cfset ATTRIBUTES.hasInput="true"/>
<cfelse>
<!--- если параметр отсутствует в передаваемых данных --->
<cfset submittedValue=ATTRIBUTES.default />
</cfif>
<cfif !StructKeyExists(ATTRIBUTES,"value")>
<cfset ATTRIBUTES.value="" /><!---*** dirty fix--->
</cfif>
<cfset ATTRIBUTES.submittedValue=submittedValue />
<cfif StructKeyExists(ATTRIBUTES,"preprocessor")>
<cfset submittedValue=#ATTRIBUTES.preprocessor(submittedValue)#/>
</cfif>
<!--- check required fields --->
<cfif ATTRIBUTES.required>
<cfif submittedValue EQ "">
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = "Не заполнено обязательное поле #ATTRIBUTES.displayName# "/>
<cfset ATTRIBUTES.value="" />
<cfexit method="exittag" />
</cfif>
</cfif>
<cfif StructKeyExists(ATTRIBUTES, "forNull")>
<cfif #submittedValue# EQ #ATTRIBUTES.forNull#>
<cfset ATTRIBUTES.null="true" />
<cfset ATTRIBUTES.value="" />
<cfexit method="exittag" />
</cfif>
</cfif>
<!--- type validation --->
<cfswitch expression=#ATTRIBUTES.type#>
<!--- для integer, bigint наличие атрибутa autoincrement подавляет сообщение об ошибке --->
<!--- *** можно было сделать для всех, но тогда надо обрабатывать исключения --->
<cfcase value="integer,int,bigint"><!--- *** формат с экспонентой не будет работать --->
<cfset s=reReplace(submittedValue, "[[:space:]]", "", "ALL")/>
<cfif FindOneOf(",.",s)>
<cfset s=ListFirst(s,",.")/>
<cfset ATTRIBUTES.warningState = "true"/>
<cfset ATTRIBUTES.warningMessage = "Number is truncated #ATTRIBUTES.displayName#: #submittedValue#-&gt;#s#"/>
</cfif>
<!---*** не слишком явно - подавление ошибки для автоинкрементного поля --->
<cfif (NOT isNumeric(s))>
<!---<cfif (NOT structKeyExists(ATTRIBUTES, "autoincrement"))>
<cfif (NOT structKeyExists(ATTRIBUTES, "autoincrement")) AND (NOT structKeyExists(ATTRIBUTES, "key"))> --->
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = "Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>
<!---<cfthrow type="custom" message="Invalid number" detail="Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>--->
<!--- по-хорошему, выбрасывать исключение нужно только при попытке сохранения
Но можно и не разбирать в некоторых режимах
--->
<cfelse>
<cfset ATTRIBUTES.value=s*ATTRIBUTES.scale/>
</cfif>
</cfcase>
<cfcase value="guid,uuid,idstamp">
<!--- guid = PostgreSQL hack --->
<cfif NOT isValid("guid", submittedValue) AND len(attributes.submittedvalue) GT 0>
<cfset ATTRIBUTES.errorState = "true" />
<cfset ATTRIBUTES.errorMessage = "[Error]: Value is not a GUID type." />
<!--- <cfthrow type="UnsupportedParameterType"
message="[Error]: Value is not a GUID type."
detail='Value is: #attributes.submittedvalue#'/> --->
<cfelse>
<cfset ATTRIBUTES.value = submittedValue />
</cfif>
</cfcase>
<cfcase value="numeric,decimal,float,real,double">
<cfset s=replace(reReplace(submittedValue, "[[:space:]]", "", "ALL"), ",", ".", "ALL")/>
<cfif NOT isNumeric(s)>
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = "Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>
<!---<cfthrow type="custom" message="Invalid number" detail="Invalid number (#ATTRIBUTES.displayName#: #submittedValue#)"/>--->
<cfelse>
<cfset ATTRIBUTES.value=s*ATTRIBUTES.scale/>
</cfif>
</cfcase>
<cfcase value="bit,boolean,bool"><!--- потому что транслируем примитивно в CF_SQL_BIT--->
<cfset s=trim(submittedValue)/> <!--- нужно ли это? пробелы действительно дают ошибку --->
<cfif NOT isBoolean(s)>
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = "Invalid boolean (#ATTRIBUTES.displayName#: #submittedValue#)"/>
<cfelse>
<!---<cfif s>
<cfset ATTRIBUTES.value=true/>
<cfelse>
<cfset ATTRIBUTES.value=false/>
</cfif>--->
<cfset ATTRIBUTES.value=s/>
</cfif>
</cfcase>
<cfcase value="date,time,timestamp">
<cftry>
<!--- <cfset ATTRIBUTES.value=LSParseDateTime(submittedValue,"#ATTRIBUTES.dtlocale#")/> --->
<cfset ATTRIBUTES.value=LSParseDateTime(submittedValue,"#ATTRIBUTES.dtlocale#","#ATTRIBUTES.format#")/>
<cfset ATTRIBUTES.submittedValue=ATTRIBUTES.value/><!--- *** dirty fix--->
<cfcatch type="ANY">
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = "Invalid datetime (#ATTRIBUTES.displayName#: #submittedValue#)"/>
<!---<cfthrow type="custom" message="Invalid datetime" detail="Invalid datetime (#ATTRIBUTES.displayName#: #submittedValue#)"/>--->
</cfcatch>
</cftry>
<!--- пробрасывать сырое значение при ошибке разбора плохо, потому что функция форматирования даты споткнется--->
</cfcase>
<cfcase value="varchar">
<cfset submittedValue=trim(submittedValue) />
<cfparam name="ATTRIBUTES.default" type="string" default=""/>
<cfset ATTRIBUTES.value=submittedValue/>
<cfif ATTRIBUTES.maxlength GT 0>
<cfif len(submittedValue) GT ATTRIBUTES.maxlength>
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = "Длина #ATTRIBUTES.displayName# превышает #ATTRIBUTES.maxlength#"/>
<cfset ATTRIBUTES.value="" />
<cfexit method="exittag" />
</cfif>
</cfif>
<cfif ATTRIBUTES.minlength GT 0>
<cfif len(submittedValue) LT ATTRIBUTES.minlength>
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = "Длина #ATTRIBUTES.displayName# меньше #ATTRIBUTES.minlength#"/>
<cfset ATTRIBUTES.value="" />
<cfexit method="exittag" />
</cfif>
</cfif>
<cfif ATTRIBUTES.size GT 0>
<cfif len(submittedValue) GT ATTRIBUTES.size>
<!--- truncate input --->
<cfset ATTRIBUTES.value=left(submittedValue, ATTRIBUTES.size)/>
<cfset ATTRIBUTES.warningState = "true"/>
<cfset ATTRIBUTES.warningMessage = "String is truncated (#ATTRIBUTES.displayName#: #len(submittedValue)#-&gt;#ATTRIBUTES.size#)"/>
</cfif>
</cfif>
</cfcase>
<!---<cfcase value="guid,uuid,uniqueidentifier,idstamp">--->
<cfcase value="other">
<!--- other = old PostgreSQL hack (incompatibility) --->
<!--- validation skipped --->
<cfset ATTRIBUTES.value = submittedValue />
</cfcase>
<cfcase value="varbinary">
<cfset ATTRIBUTES.value=submittedValue/>
</cfcase>
<cfdefaultcase>
<cfset ATTRIBUTES.value=""/>
<cfthrow type="UnsupportedParameterType" message="Specified parameter type is unsupported by current version of bean/param" detail='Parameter type "#ATTRIBUTES.type#" is not supported by custom tags bean/param'/>
</cfdefaultcase>
<!---<cfcase value="hex">
<cfset s=submittedValue/>
<cfif reFindNoCase("[^0-9A-F]", s)>
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = "Invalid HEXADECIMAL number (#ATTRIBUTES.displayName#: #submittedValue#)"/>
<cfelse>
<cftry>
<cfset ATTRIBUTES.value=inputBaseN(s, 16)/>
<cfcatch type="any">
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = "Error converting from HEXADECIMAL format (#ATTRIBUTES.displayName#: #submittedValue#)"/>
</cfcatch>
</cftry>
</cfif>
</cfcase>--->
</cfswitch>
<!--- regular expression validation --->
<cfif StructKeyExists(ATTRIBUTES,"regex")>
<cfif reFind(ATTRIBUTES.regex,submittedValue) EQ 0>
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = 'Pattern "#ATTRIBUTES.regex#" does not match "#submittedValue#"'/>
<cfset ATTRIBUTES.value="" />
<cfexit method="exittag" />
</cfif>
</cfif>
<!--- custom validation --->
<cfif StructKeyExists(ATTRIBUTES,"validate")>
<cfset validationMessage=ATTRIBUTES.validate(submittedValue)/>
<cfif len(validationMessage)>
<cfset ATTRIBUTES.errorState = "true"/>
<cfset ATTRIBUTES.errorMessage = validationMessage/>
<cfset ATTRIBUTES.value="" />
<cfexit method="exittag" />
</cfif>
</cfif>
<cfexit method="EXITTAG" />
<!--- это искусственный прием
<cfif ATTRIBUTES.errorState AND NOT structKeyExists(ATTRIBUTES, "autoincrement") AND NOT structKeyExists(ATTRIBUTES, "key")>
<cfset getBaseTagData("cf_bean").validationOk="false"/>
</cfif>
--->
<!---
CF_SQL_BIGINT
CF_SQL_BIT
CF_SQL_CHAR
CF_SQL_BLOB
CF_SQL_CLOB
CF_SQL_DATE
CF_SQL_DECIMAL
CF_SQL_DOUBLE
CF_SQL_FLOAT
CF_SQL_IDSTAMP
CF_SQL_INTEGER
CF_SQL_LONGVARCHAR
CF_SQL_MONEY
CF_SQL_MONEY4
CF_SQL_NUMERIC
CF_SQL_REAL
CF_SQL_REFCURSOR
CF_SQL_SMALLINT
CF_SQL_TIME
CF_SQL_TIMESTAMP
CF_SQL_TINYINT
CF_SQL_VARCHAR
*** CF_SQL_OTHER - используется с Postgre для GUID и jsonb
--->
<!--- при ошибочном вводе, например, некорректном формате, возможно два пути
- сохранить, подставив дефолт (в том числе нулл). Можно генерировать ворнинг, цепляя его прямо к полю. В этом случае надо требовать корректные дефолты
- можно ввести раздельные понятия дефолт и инит.
Разница между дефолтом и инитом. При инициализации новой
Как дефолт, так и инит имеют целевой тип (число, дата...)
Дефолт и инит используются для начального заполнения полей нового объекта. Для строки дефолт-дефолт - пустая, для остальных нулл
Рассмотреть обязательные и необязательные параметры?
Можно
- выгрузить некорректный набор данных для редактирования. При втором варианте нужно все выгружать в стринги? Тогда как будут работать функции форматирования в теле страницы, ожидающе не строку? Скажем, дату? По ближайшей догадке? Пустую строку?
Попытка вставить нулл в колонку NOT NULL должна пресекаться заранее (до ошибки БД)
Нулловые строки можно не рассматривать, приводя к пустой строке.
Тэг принимает параметр от формы, парсит и отдает в атрибуте value. Если не получает, не отдает, можно это специально указывать флагом. Можно ему лишнего не делегировать.
Остальное оставить вызывающему тегу?
--->