initial
This commit is contained in:
+291
@@ -0,0 +1,291 @@
|
|||||||
|
<cfcomponent
|
||||||
|
displayname="Application"
|
||||||
|
output="true"
|
||||||
|
hint="Handle the application.">
|
||||||
|
|
||||||
|
|
||||||
|
<!--- Set up the application. --->
|
||||||
|
<cfset this.Name = "DWH" />
|
||||||
|
<cfset this.applicationTimeout = createTimeSpan( 0, 0, 3, 0 ) />
|
||||||
|
<cfset this.sessionmanagement="Yes"/>
|
||||||
|
<cfset this.clientmanagement="No"/>
|
||||||
|
<cfset this.sessiontimeout=CreateTimeSpan(0, 0, 120, 0)/>
|
||||||
|
<cfset this.setclientcookies="No"/>
|
||||||
|
<cfset this.mappings = structNew() />
|
||||||
|
<cfset this.mappings["/mod"] = getDirectoryFromPath(getCurrentTemplatePath()) & "mod/" />
|
||||||
|
<cfset this.customTagPaths = expandPath(getDirectoryFromPath(getCurrentTemplatePath())&'mod')/>
|
||||||
|
<cfset this.datasource = "dwh"/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfset this.orchestrator_authorization_string=createObject("java", "java.lang.System").getEnv("ORCHESTRATOR_AUTH")/>
|
||||||
|
<!--- <cfif isEmpty(this.orchestrator_authorization_string)>
|
||||||
|
<cfset this.orchestrator_authorization_string = "Basic c3ZjX2xrdGVzdDoxMTgzZDIyNzc1OWExYjFkZmVjM2M3NTFiMzIyNzdiYTQ3"/><!--- *** remove --->
|
||||||
|
<cfset this.orchestrator_authorization_string = "Basic c3ZjX2xrcHJvZDoxMTBlMWJhMTFjOGE3YjAyMGFmY2FmNzdlODU3MTRmYzQx"/><!--- *** remove --->
|
||||||
|
</cfif> --->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<cfset COOKIEENCKEY = "PREVED!Y2"/>
|
||||||
|
<!--- Define the page request properties. --->
|
||||||
|
<cfsetting
|
||||||
|
requesttimeout="20"
|
||||||
|
showdebugoutput="false"
|
||||||
|
enablecfoutputonly="false"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<cffunction
|
||||||
|
name="OnApplicationStart"
|
||||||
|
access="public"
|
||||||
|
returntype="boolean"
|
||||||
|
output="false"
|
||||||
|
hint="Fires when the application is first created.">
|
||||||
|
|
||||||
|
<cfreturn true />
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction
|
||||||
|
name="OnRequest"
|
||||||
|
access="public"
|
||||||
|
returntype="void"
|
||||||
|
output="true"
|
||||||
|
hint="Fires after pre page processing is complete.">
|
||||||
|
|
||||||
|
<cfargument name="template" type="string" required="true"/>
|
||||||
|
|
||||||
|
<cfset request.startTickCount=getTickCount()/>
|
||||||
|
|
||||||
|
<cfset setEncoding("FORM", "UTF-8")>
|
||||||
|
<cfset setEncoding("URL", "UTF-8")>
|
||||||
|
|
||||||
|
<cfset request.PERMISSION_NONE=0/>
|
||||||
|
<cfset request.PERMISSION_READ=1/>
|
||||||
|
<cfset request.PERMISSION_WRITE=2/>
|
||||||
|
|
||||||
|
<!--- https://www.jenkins.io/doc/book/system-administration/authenticating-scripted-clients/
|
||||||
|
The API token is available in your personal configuration page. Click your name on the top right corner on every page, then click "Configure" to see your API token. (The URL $root/me/configure is a good shortcut.) You can also change your API token from here.
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfset ORCHESTRATOR_AUTH=this.orchestrator_authorization_string/>
|
||||||
|
|
||||||
|
<cfset request.UNDEFINED_USR_ID=-1/>
|
||||||
|
<!--- request.ROOT_USR_ID=1 not used --->
|
||||||
|
<cfset request.ANONYMOUS_USR_ID=2/>
|
||||||
|
<cfset request.GUEST_USR_ID=3/>
|
||||||
|
|
||||||
|
<!--- global settings --->
|
||||||
|
<cfset request.DS=this.datasource/>
|
||||||
|
<cfset request.RECORDS_PER_PAGE=500/>
|
||||||
|
<cfset request.APP_VERSION="0.00.000"/>
|
||||||
|
<cfset request.STAND=getStand()/>
|
||||||
|
|
||||||
|
<cflock scope="application" type="readonly" timeout=3>
|
||||||
|
<cfset request.APP_NAME=this.Name/>
|
||||||
|
</cflock>
|
||||||
|
|
||||||
|
<!--- application constants --->
|
||||||
|
|
||||||
|
<!---<cfset request.SMTP_SERVER="172.16.16.16"/>
|
||||||
|
<cfset request.MAIL_FROM_FOR_NOTIFICATION="Pipeline tracker <pipeline@mail.ru>"/>
|
||||||
|
<cfset request.SUBJECT_PREFIX="[PIPELINE TRACKER] "/>--->
|
||||||
|
<!---<cfset request.BASE_URL="https://smtest.dtln.local/pipeline/"/>*** сделать нормально. Внимание! При наличии прокси CGI.SERVER_NAME может быть заменено --->
|
||||||
|
|
||||||
|
<cfset local = {} />
|
||||||
|
|
||||||
|
<cfset local.basePath = getDirectoryFromPath(
|
||||||
|
getCurrentTemplatePath()
|
||||||
|
) />
|
||||||
|
|
||||||
|
<cfset local.targetPath = getDirectoryFromPath(
|
||||||
|
expandPath( arguments.template )
|
||||||
|
) />
|
||||||
|
|
||||||
|
<cfset local.requestDepth = (
|
||||||
|
listLen( local.targetPath, "\/" ) -
|
||||||
|
listLen( local.basePath, "\/" )
|
||||||
|
) />
|
||||||
|
|
||||||
|
<cfset request.webRoot = repeatString(
|
||||||
|
"../",
|
||||||
|
local.requestDepth
|
||||||
|
) />
|
||||||
|
|
||||||
|
<!---
|
||||||
|
While we wouldn't normally do this for every page
|
||||||
|
request (it would normally be cached in the
|
||||||
|
application initialization), I'm going to calculate
|
||||||
|
the site URL based on the web root.
|
||||||
|
--->
|
||||||
|
<cfset request.siteUrl = (
|
||||||
|
IIF(
|
||||||
|
(CGI.server_port_secure <!--- CGI.https EQ "On" does not work with apache+tomcat and nginx+tomcat --->),
|
||||||
|
DE( "https://" ),
|
||||||
|
DE( "http://" )
|
||||||
|
) &
|
||||||
|
cgi.http_host &
|
||||||
|
reReplace(
|
||||||
|
getDirectoryFromPath( arguments.template ), "([^\\/]+[\\/]){#local.requestDepth#}$",
|
||||||
|
"",
|
||||||
|
"one"
|
||||||
|
)
|
||||||
|
) />
|
||||||
|
|
||||||
|
<cfset request.thisPage=Replace(ReplaceNoCase(expandPath(ARGUMENTS.template), local.basePath, ""), "\", "/")/>
|
||||||
|
|
||||||
|
<cfinclude template="inc/functions.cfm"/>
|
||||||
|
|
||||||
|
<cfcookie name="CFID" value="#session.CFID#">
|
||||||
|
<cfcookie name="CFTOKEN" value="#session.CFTOKEN#">
|
||||||
|
|
||||||
|
<!--- check saved login --->
|
||||||
|
<!---
|
||||||
|
если в сессии есть usr_id, ничего делать не нужно
|
||||||
|
если его нет (то есть сессия новая или свежесброшенная),
|
||||||
|
- проверить куки.
|
||||||
|
Дополнительное условие: если происходит процесс установки-сброса кук,
|
||||||
|
(в процессе установки-сброса определена переменная session.save_login) - не вмешиваемся
|
||||||
|
--->
|
||||||
|
<!--- <cfset request.usr_id=#request.UNDEFINED_USR_ID#/> --->
|
||||||
|
|
||||||
|
<cflock scope="session" type="exclusive" timeout="3">
|
||||||
|
<!---<cfset session.usr_id=1/>***--->
|
||||||
|
|
||||||
|
<cfif NOT structKeyExists(session,"usr_id") OR (structKeyExists(session,"usr_id") AND NOT isNumeric(session.usr_id))>
|
||||||
|
<cfset session.usr_id=#request.UNDEFINED_USR_ID#>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif session.usr_id EQ request.UNDEFINED_USR_ID>
|
||||||
|
<cfif NOT structKeyExists(session, "save_login")>
|
||||||
|
<cfif structKeyExists(cookie, "portalUser")>
|
||||||
|
<cftry>
|
||||||
|
<cfset login = Decrypt(cookie.portalUser,COOKIEENCKEY)>
|
||||||
|
<cfoutput>#login#</cfoutput>
|
||||||
|
|
||||||
|
<cfquery name="qGetCredentials" datasource="#request.DS#">
|
||||||
|
select usr_id from usr where login='#login#'
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfset session.usr_id = qGetCredentials.usr_id>
|
||||||
|
<m:settings action="create"/>
|
||||||
|
|
||||||
|
<cfcatch type="Any">
|
||||||
|
<cfoutput>#cfcatch.message#</cfoutput>
|
||||||
|
<cfset session.usr_id = request.UNDEFINED_USR_ID>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- <cfdump var=#request.usr_id#/> --->
|
||||||
|
<cfif session.usr_id EQ request.UNDEFINED_USR_ID>
|
||||||
|
<cfset session.usr_id = request.ANONYMOUS_USR_ID>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset request.usr_id=session.usr_id/>
|
||||||
|
<!--- <cfdump var=#request.usr_id#/> --->
|
||||||
|
<cfif structKeyExists(session, "username")>
|
||||||
|
<cfset request.username=#session.username#/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset request.username=""/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfparam name="language" default=""/>
|
||||||
|
<cfif language EQ "ru" OR language EQ "en">
|
||||||
|
<cfset session.language=language/>
|
||||||
|
<cfelse>
|
||||||
|
<cfparam name="session.language" default="ru"/>
|
||||||
|
</cfif>
|
||||||
|
<cfset request.language=session.language/>
|
||||||
|
|
||||||
|
</cflock><!--- session exclusive --->
|
||||||
|
|
||||||
|
<cfset request.usr_is_admin=0/>
|
||||||
|
|
||||||
|
<!--- <cfparam name="URL.token" default=""/>
|
||||||
|
<cfif URL.token EQ '9b15d2ab-f044-49dc-88cb-3f6301a55e42'>
|
||||||
|
<cfset request.usr_id=5/>
|
||||||
|
</cfif> --->
|
||||||
|
|
||||||
|
|
||||||
|
<!--- ----------------------- --->
|
||||||
|
|
||||||
|
<!--- --------------------------- --->
|
||||||
|
|
||||||
|
<cfif request.usr_id GT 0 > <!---AND request.usr_id NEQ request.ANONYMOUS_USR_ID в данном случае закрыли анонимный доступ --->
|
||||||
|
<cfinclude template="#ARGUMENTS.template#"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset currentDir = (
|
||||||
|
lCase(
|
||||||
|
replace(
|
||||||
|
getDirectoryFromPath(
|
||||||
|
replaceNoCase(
|
||||||
|
expandPath(
|
||||||
|
ARGUMENTS.template
|
||||||
|
),
|
||||||
|
getDirectoryFromPath(
|
||||||
|
getCurrentTemplatePath()
|
||||||
|
),
|
||||||
|
""
|
||||||
|
)
|
||||||
|
), "\", "/", "ALL"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)/>
|
||||||
|
<cfif currentDir EQ "saml/"><!--- note traling slash --->
|
||||||
|
<cfinclude template="#ARGUMENTS.template#"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfinclude template="login.cfm" />
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<cfreturn />
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction
|
||||||
|
name="OnRequestEnd"
|
||||||
|
access="public"
|
||||||
|
returntype="void"
|
||||||
|
output="true"
|
||||||
|
hint="Fires after the page processing is complete.">
|
||||||
|
<!--- Attention! Before CF9, OnrequestEnd is not executed in case of redirect. That is why we use session.save_login --->
|
||||||
|
|
||||||
|
<cfif structKeyExists(session, "save_login")>
|
||||||
|
<cfif session.save_login EQ "">
|
||||||
|
<!--- unset (expire) cookie --->
|
||||||
|
<cfcookie expires="-1" name="portalUser" value="">
|
||||||
|
<cfelse>
|
||||||
|
<!--- set persistent cookie with no expiration --->
|
||||||
|
<cfcookie expires="NEVER" name="portalUser" value="#encrypt(session.save_login,COOKIEENCKEY)#">
|
||||||
|
</cfif>
|
||||||
|
<cfset structDelete(session, "save_login")>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfreturn />
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction
|
||||||
|
name="getStand"
|
||||||
|
access="private"
|
||||||
|
returntype="string"
|
||||||
|
output="true"><!--- *** duplicated in svc-api --->
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfquery name=qConfig>
|
||||||
|
select value as stand from config
|
||||||
|
where name='STAND'
|
||||||
|
</cfquery>
|
||||||
|
<cfreturn qConfig.stand/>
|
||||||
|
|
||||||
|
<cfcatch type="ANY">
|
||||||
|
<!--- do nothing, default will be returned --->
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfreturn ""/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
</cfcomponent>
|
||||||
@@ -0,0 +1,356 @@
|
|||||||
|
<cfscript>
|
||||||
|
/* Все глобальные функции экспортированы в request scope */
|
||||||
|
function plain2htm(s) {
|
||||||
|
return replace(replace(s, chr(13),'',"ALL"),chr(10),'<br/>', "ALL");
|
||||||
|
}
|
||||||
|
request.plain2htm = plain2htm;
|
||||||
|
|
||||||
|
function htm2plain(s) {
|
||||||
|
return replaceNoCase(s, '<br/>', '#chr(13)##chr(10)#', "ALL");
|
||||||
|
}
|
||||||
|
request.htm2plain = htm2plain;
|
||||||
|
|
||||||
|
function cleanHtm(s) {
|
||||||
|
return replaceList(s, '<,>,"', '<,>,"');
|
||||||
|
}
|
||||||
|
request.cleanHtm = cleanHtm;
|
||||||
|
|
||||||
|
function clean4CDATA(s) {
|
||||||
|
LF = chr(10);
|
||||||
|
return replaceNoCase(replaceList(s, '[,]', '&91;,&93;'),"<br>",LF,"ALL");
|
||||||
|
}
|
||||||
|
request.clean4CDATA = clean4CDATA;
|
||||||
|
|
||||||
|
function numFmt(num, decimalPlaces = 0){
|
||||||
|
var sFormat = ",";
|
||||||
|
if (decimalPlaces GT 0) {sFormat = sFormat & "." & repeatString("0", decimalPlaces);}
|
||||||
|
return replace(NumberFormat(num, sFormat), ",", " ", "ALL");
|
||||||
|
}
|
||||||
|
request.numFmt = numFmt;
|
||||||
|
|
||||||
|
function nFmt(num, decimalPlaces = 2){
|
||||||
|
if (num EQ 0) return "";
|
||||||
|
return numFmt(num, decimalPlaces);
|
||||||
|
}
|
||||||
|
request.nFmt = nFmt;
|
||||||
|
|
||||||
|
function dateFmt(date, sFormat = 'YYYY-MM-DD'){
|
||||||
|
return dateFormat(date, sFormat);
|
||||||
|
}
|
||||||
|
request.dateFmt = dateFmt;
|
||||||
|
|
||||||
|
function dateTimeFmt(date, sDateFormat = 'YYYY-MM-DD', sTimeFormat='HH:MM'){
|
||||||
|
return dateFormat(date, sDateFormat) & ' ' & timeFormat(date, sTimeFormat);
|
||||||
|
}
|
||||||
|
request.dateTimeFmt = dateTimeFmt;
|
||||||
|
|
||||||
|
function timeFmt(date, sFormat = 'HH:MM'){
|
||||||
|
return timeFormat(date, sFormat);
|
||||||
|
}
|
||||||
|
request.timeFmt = timeFmt;
|
||||||
|
|
||||||
|
function usrAuthenticated() {
|
||||||
|
return request.usr_id GT 0 AND NOT (request.usr_id EQ request.ANONYMOUS_USR_ID);
|
||||||
|
}
|
||||||
|
request.usrAuthenticated = usrAuthenticated;
|
||||||
|
|
||||||
|
function filterOn(filter) {
|
||||||
|
return (isStruct(filter) AND NOT structIsEmpty(filter));
|
||||||
|
}
|
||||||
|
request.filterOn = filterOn;
|
||||||
|
|
||||||
|
function stripWhiteSpace(sNumber) {
|
||||||
|
return reReplace(sNumber, "[[:space:]]", "", "ALL");
|
||||||
|
}
|
||||||
|
request.stripWhiteSpace = stripWhiteSpace;
|
||||||
|
/*
|
||||||
|
// never used
|
||||||
|
function cleanNumber(sNumber) {
|
||||||
|
var s = reReplace(sNumber, "[[:space:]]", "", "ALL");
|
||||||
|
if ( find(",", s) ) { //watch argument order!!!
|
||||||
|
// assume Russian format with decimal comma and spaces for thousand separator
|
||||||
|
return replace(s, ",", ".");
|
||||||
|
} else {
|
||||||
|
// assume English format with decimal point and commas for thousand separator
|
||||||
|
return replace(s, ",", "", "ALL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.cleanNumber = cleanNumber;
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*https://cflib.org/udf/queryColumnToArray*/
|
||||||
|
/**
|
||||||
|
* Takes a selected column of data from a query and converts it into an array.
|
||||||
|
*
|
||||||
|
* @param query The query to scan. (Required)
|
||||||
|
* @param column The name of the column to return data from. (Required)
|
||||||
|
* @return Returns an array.
|
||||||
|
* @author Peter J. Farrell (pjf@maestropublishing.com)
|
||||||
|
* @version 1, July 22, 2005
|
||||||
|
*/
|
||||||
|
function queryColumnToArray(qry, column) {
|
||||||
|
var arr = arrayNew(1);
|
||||||
|
var ii = "";
|
||||||
|
var loop_len = arguments.qry.recordcount;
|
||||||
|
for (ii=1; ii lte loop_len; ii=ii+1) {
|
||||||
|
arrayAppend(arr, arguments.qry[arguments.column][ii]);
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
request.queryColumnToArray = queryColumnToArray;
|
||||||
|
|
||||||
|
/* ************************************* */
|
||||||
|
|
||||||
|
function loginPasswordPolicyCheck(usr_id, login, passwd, passwd2) {
|
||||||
|
msg = "";
|
||||||
|
// для старого логина длина не проверяется, все равно ничего не поделаешь
|
||||||
|
if (NOT(usr_id GT 0) AND len(login) LE 3) {
|
||||||
|
msg="Слишком короткий логин (минимальная длина логина 3 символа)";
|
||||||
|
} else {
|
||||||
|
if (passwd NEQ passwd2) {
|
||||||
|
msg="Пароль и подтверждение не совпадают";
|
||||||
|
} else {
|
||||||
|
if (len(passwd) LT "8") msg="Слишком короткий пароль (минимальная длина пароля 8 символов)";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
request.loginPasswordPolicyCheck = loginPasswordPolicyCheck;
|
||||||
|
|
||||||
|
function i18(label,label_en=label) {
|
||||||
|
//примитивная 2-язычная версия
|
||||||
|
if (request.language EQ 'en') {
|
||||||
|
return label_en;
|
||||||
|
} else {
|
||||||
|
return label;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
request.i18 = i18;
|
||||||
|
|
||||||
|
function passwordHashCreate(password) {
|
||||||
|
return GenerateArgon2Hash('#ARGUMENTS.password#', 'argon2id', 1, 16000, 2);
|
||||||
|
}
|
||||||
|
request.passwordHashCreate = passwordHashCreate;
|
||||||
|
|
||||||
|
function passwordHashCheck(password, passwordHash) {
|
||||||
|
return Argon2CheckHash('#ARGUMENTS.password#', ARGUMENTS.passwordHash, 'argon2id');
|
||||||
|
}
|
||||||
|
request.passwordHashCheck = passwordHashCheck;
|
||||||
|
/*
|
||||||
|
function skuCode(area_code="", abstract_service_code="") {
|
||||||
|
var s="#arguments[1]#.#arguments[2]#";
|
||||||
|
if (arrayLen(arguments) GE 3) {
|
||||||
|
if (len(arguments[3]) GT 0) {
|
||||||
|
s="#s#.#arguments[3]#";
|
||||||
|
} else {
|
||||||
|
s="#s#.0";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (arrayLen(arguments) GE 4) {
|
||||||
|
if (len(arguments[4]) GT 0) {
|
||||||
|
s="#s#.#arguments[4]#"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (arrayLen(arguments) GE 5) {
|
||||||
|
var pricingModelSuffix=arguments[5];
|
||||||
|
s="#s#-#pricingModelSuffix#";
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
request.skuCode = skuCode;
|
||||||
|
*/
|
||||||
|
function roundSafe(a, precision) {
|
||||||
|
return (isNumeric(a) AND isNumeric(precision)) ? round(a,precision) : a;
|
||||||
|
}
|
||||||
|
request.roundSafe = roundSafe;
|
||||||
|
|
||||||
|
function timeUnitLabel(n, value=0, locale="ru") {
|
||||||
|
var labels = ["сек.","мин.","ч.","д.","мес."];
|
||||||
|
if (n LE 5 AND n GE 1) {return labels[n];}
|
||||||
|
else if (n EQ 6) { // в зависимости от числа - "год" г. или "лет" л.
|
||||||
|
var m = value MOD 100;
|
||||||
|
if (m GE 11 AND m LE 20) {
|
||||||
|
return "л.";
|
||||||
|
}
|
||||||
|
m = m MOD 10;
|
||||||
|
if (m GE 1 AND m LE 4) {
|
||||||
|
return "г.";
|
||||||
|
}
|
||||||
|
return "л.";
|
||||||
|
}
|
||||||
|
throw (message="timeUnitLabelRu: index out of bounds", detail="Index must be from 1 to 6");
|
||||||
|
}
|
||||||
|
request.timeUnitLabel = timeUnitLabel;
|
||||||
|
|
||||||
|
function dateDiffVerbose(dateFrom, dateTo, maxParts=6, maxPrecision=1, locale="ru") {
|
||||||
|
// returns formatted datetime interval duration, e.g. 1 hour 5 minutes or 3 years 0 months 12 days
|
||||||
|
//(currently, only Russian supported)
|
||||||
|
//
|
||||||
|
//maxParts specifies, how many parts to display
|
||||||
|
//maxPrecision starts froms seconds: 1=seconds, 2=minutes, 3=hours, 4=days, 5=months, 6=years
|
||||||
|
//if maxPrecision specified is greater than given time interval, output is nothing
|
||||||
|
//locale=ru just for interface, not used
|
||||||
|
//attention: truncates the tail, not rounds
|
||||||
|
//example: request.dateDiffVerbose(Now(),Now()+373.1234567, 3, 1) produces 1 г. 0 мес. 8 д.
|
||||||
|
//example: request.dateDiffVerbose(Now(),Now()+373.1234567, 6, 1) produces 1 г. 0 мес. 8 д. 2 ч. 57 мин. 46 сек.
|
||||||
|
//intended common usage: request.dateDiffVerbose(dateFrom, dateTo, 2, 1) - prints 2 senior parts of the interval,
|
||||||
|
//with presision not better than second
|
||||||
|
//
|
||||||
|
// this is not the same task as converting number of seconds to days-hours-minutes-seconds,
|
||||||
|
// because months contain different number of days, and days number of february depends on year
|
||||||
|
try {
|
||||||
|
if (NOT (isDate(dateFrom) AND isDate(dateTo))) return "";
|
||||||
|
var UNIT_CODES = ["s","n","h","d","m","yyyy"];
|
||||||
|
var start = dateFrom;
|
||||||
|
var finish = dateTo;
|
||||||
|
|
||||||
|
var sOut = "";
|
||||||
|
var nUnits = 0;
|
||||||
|
var nPrintedPositions = 0;
|
||||||
|
var printStart = false;
|
||||||
|
|
||||||
|
for (i=ArrayLen(UNIT_CODES); i GT 0; i=i-1) {
|
||||||
|
nUnits = dateDiff(UNIT_CODES[i], start, finish);
|
||||||
|
start = dateAdd(UNIT_CODES[i], nUnits, start);
|
||||||
|
if (nPrintedPositions GT 0) {nPrintedPositions=nPrintedPositions+1;} //print started, so increment position
|
||||||
|
if (nPrintedPositions GT maxParts) {break;}
|
||||||
|
if (i LT maxPrecision) {break;}
|
||||||
|
if (nUnits NEQ 0 AND nPrintedPositions EQ 0) {nPrintedPositions=1;}
|
||||||
|
if (nPrintedPositions GT 0) {
|
||||||
|
if (len(sOut)) {sOut=sOut & " ";}
|
||||||
|
sOut=sOut & nUnits & " " & request.timeUnitLabel(i, nUnits, "ru");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sOut;
|
||||||
|
} catch(e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
request.dateDiffVerbose = dateDiffVerbose;
|
||||||
|
|
||||||
|
/*
|
||||||
|
function secondsToDHMS(nSeconds,partsToKeep) {
|
||||||
|
//format seconds to days-hours-minutes-seconds and truncate to specified precision
|
||||||
|
{
|
||||||
|
days = n / (24 * 3600);
|
||||||
|
|
||||||
|
n = n % (24 * 3600);
|
||||||
|
hours = n / 3600;
|
||||||
|
|
||||||
|
n %= 3600;
|
||||||
|
minutes = n / 60 ;
|
||||||
|
|
||||||
|
n %= 60;
|
||||||
|
seconds = n;
|
||||||
|
|
||||||
|
return days & " " & "days "
|
||||||
|
& hours & " " & "hours "
|
||||||
|
& minutes & " " & "minutes "
|
||||||
|
& seconds & " " & "seconds ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Formats a JSON string with indents and new lines.
|
||||||
|
* v1.0 by Ben Koshy (modified)
|
||||||
|
*
|
||||||
|
* @param str JSON string (Required)
|
||||||
|
* @return Returns a string of indent-formated JSON
|
||||||
|
* @author Ben Koshy (cf@animex.com)
|
||||||
|
* @version 0, September 16, 2012
|
||||||
|
*/
|
||||||
|
// formatJson() :: formats and indents JSON string
|
||||||
|
// based on blog post @ http://ketanjetty.com/coldfusion/javascript/format-json/
|
||||||
|
// modified for CFScript By Ben Koshy @animexcom
|
||||||
|
// usage: result = formatJson('STRING TO BE FORMATTED') OR result = formatJson(StringVariableToFormat);
|
||||||
|
|
||||||
|
// генерирует html разметку, чтобы не использовать <pre> и выровнять пробелы
|
||||||
|
// содержит ошибку: делает перевод строки после запятой в строке в кавычках
|
||||||
|
public string function formatJsonString(str) {
|
||||||
|
var fjson = '';
|
||||||
|
var pos = 0;
|
||||||
|
var strLen = len(arguments.str);
|
||||||
|
var indentStr = ' '; // Adjust Indent Token If you Like (замыкающий пробел - для выравнивания при пробелах после запятой)
|
||||||
|
var newLine = '<br/>'; // Adjust New Line Token If you Like <BR>
|
||||||
|
|
||||||
|
for (var i=1; i<=strLen; i++) { //fix: <=
|
||||||
|
var char = mid(arguments.str,i,1);
|
||||||
|
|
||||||
|
if (char == '}' || char == ']') {
|
||||||
|
fjson &= newLine;
|
||||||
|
pos = pos - 1;
|
||||||
|
|
||||||
|
for (var j=1; j <= pos; j++) {
|
||||||
|
fjson &= indentStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fjson &= char;
|
||||||
|
|
||||||
|
if (char == '{' || char == '[' || char == ',') {
|
||||||
|
fjson &= newLine;
|
||||||
|
|
||||||
|
if (char == '{' || char == '[') {
|
||||||
|
pos = pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var k=1; k <= pos; k++) {
|
||||||
|
fjson &= indentStr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fjson;
|
||||||
|
}
|
||||||
|
request.formatJsonString = formatJsonString;
|
||||||
|
//нужна еще функция, которая сериализует структуру как pretty printed json. См. https://github.com/bennadel/JsonSerializer.cfc (все не совсем просто, например, не заранее не определен формат даты)
|
||||||
|
</cfscript>
|
||||||
|
|
||||||
|
<cffunction name=structGetKey>
|
||||||
|
<cfargument name="struct" type="struct"/>
|
||||||
|
<cfargument name="key" type="string"/>
|
||||||
|
<cfargument name="default" type="string" default=""/>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfif StructKeyExists(ARGUMENTS.struct, ARGUMENTS.key)>
|
||||||
|
<cfreturn structFind(ARGUMENTS.struct, ARGUMENTS.key)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn ARGUMENTS.default/>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch type="any">
|
||||||
|
<cfreturn ARGUMENTS.default/>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<!--- можно проще - просто обработать исключение. вряд ли будет сильно хуже --->
|
||||||
|
</cffunction>
|
||||||
|
<cfset request.structGetKey = structGetKey/>
|
||||||
|
|
||||||
|
<cffunction name="setError">
|
||||||
|
<cfargument name="container" type="struct"/>
|
||||||
|
<cfargument name="message" type="string"/>
|
||||||
|
|
||||||
|
<cfset arguments.container.errorState=true/>
|
||||||
|
<cfset arguments.container.errorMessage=arguments.message/>
|
||||||
|
</cffunction>
|
||||||
|
<cfset request.setError = setError/>
|
||||||
|
|
||||||
|
<cffunction name="snake2camel"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="convert snake style name to camel style name">
|
||||||
|
<cfargument name="snake" type="string" required="true" />
|
||||||
|
<cfreturn #reReplace(ARGUMENTS.snake,"_([a-z])","\u\1","ALL")#/>
|
||||||
|
</cffunction>
|
||||||
|
<cfset request.snake2camel = snake2camel/>
|
||||||
|
|
||||||
|
<cffunction name="camel2snake"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="convert camel style name to snake style name">
|
||||||
|
<cfargument name="snake" type="string" required="true" />
|
||||||
|
<cfreturn #reReplace(ARGUMENTS.snake,"([A-Z])","_\l\1","ALL")#/>
|
||||||
|
</cffunction>
|
||||||
|
<cfset request.camel2snake = camel2snake/>
|
||||||
|
|
||||||
@@ -0,0 +1,241 @@
|
|||||||
|
|
||||||
|
<cfset dt_finish=createDateTime(year(Now()),month(Now()),1,0,0,0)/>
|
||||||
|
<cfset dt_start=dateAdd('m',-1,dt_finish)/>
|
||||||
|
|
||||||
|
Нужно еще применить политики бесплатного минимума
|
||||||
|
<!---
|
||||||
|
<cfquery name="qCode" datasource="dwh">
|
||||||
|
select distinct p.code
|
||||||
|
from elma.deals d
|
||||||
|
join elma.deals_services siv on (d.__id=ANY(siv.deal))
|
||||||
|
join elma.service_parametrs p on (p.__id = ANY(siv.params))
|
||||||
|
join elma.companies k on (d._companies=k.__id)
|
||||||
|
join elma.additional_agreements aa on (aa.__id = ANY(siv.additional_agreement))
|
||||||
|
join elma.aggrements a on (aa.contract_uid = a.__id)
|
||||||
|
where p.type=3 AND aa.is_actual
|
||||||
|
order by p.code
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qCode#/>
|
||||||
|
|
||||||
|
<cfquery name="qSpec" datasource="dwh">
|
||||||
|
select k.id_klienta, p.code, p.price, p.discount, k.__name as contragent, a.__name as contract, d.__name as deal, siv.abstractive_service as abstract_service, siv.modifier, siv.user_description
|
||||||
|
,p.__name as metric, p.ed_izm
|
||||||
|
--, aa.is_actual
|
||||||
|
from elma.deals d
|
||||||
|
join elma.deals_services siv on (d.__id=ANY(siv.deal))
|
||||||
|
join elma.service_parametrs p on (p.__id = ANY(siv.params))
|
||||||
|
join elma.companies k on (d._companies=k.__id)
|
||||||
|
join elma.additional_agreements aa on (aa.__id = ANY(siv.additional_agreement))
|
||||||
|
join elma.aggrements a on (aa.contract_uid = a.__id)
|
||||||
|
where p.type=3 AND aa.is_actual
|
||||||
|
order by k.id_klienta, p.code
|
||||||
|
</cfquery>
|
||||||
|
здесь имеем список кодов с ценами и WZ. Каждому коду можно поставить в соответствие запрос, который даст по нему выборку. Изредка один запрос может вывести 2 кода, но разбить на 2 запроса легко. Кроме того, запросы можно объединить через union в один - может быть, так будет лучше читаться (быстрее выполняться не будет)
|
||||||
|
<cfdump var=#qSpec#/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfquery name="qComputing" datasource="billing-vc">
|
||||||
|
select 'WZ'||to_char(tenant.wzcode,'FM00000') as wz, vdc.name as vdc_name,
|
||||||
|
case
|
||||||
|
when vdc.name like '%-v1cl1-%' then 'iaas.ngc.i29'
|
||||||
|
when vdc.name like '%-v1cl2-%' then 'iaas.ngc.i31'
|
||||||
|
when vdc.name like '%-v1cl4-%' then 'iaas.ngc.a28'
|
||||||
|
when vdc.name like '%-v1cl6-%' then 'iaas.ngc.i28'
|
||||||
|
else 'unknown' end as code,
|
||||||
|
sum(cpu_used/cpu_speed)/4. as core_h,
|
||||||
|
sum(mem_used)/1024/4. as gb_h
|
||||||
|
from ngcloud_ru.capacity_resource
|
||||||
|
join ngcloud_ru.vdc on capacity_resource.vdc_id=vdc.id
|
||||||
|
join ngcloud_ru.tenant on tenant.id=vdc.tenant_id
|
||||||
|
where tenant.wzcode > 0
|
||||||
|
AND capacity_resource.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND capacity_resource.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by tenant.wzcode, vdc.name
|
||||||
|
order by tenant.wzcode, vdc.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qComputing#/>
|
||||||
|
|
||||||
|
<cfquery name="qStorage" datasource="billing-vc">
|
||||||
|
select 'WZ'||to_char(tenant.wzcode,'FM00000') as wz, vdc.name as vdc_name,
|
||||||
|
case
|
||||||
|
when vdc.name like '%-v1cl1-%' then 'iaas.ngc.i29'
|
||||||
|
when vdc.name like '%-v1cl2-%' then 'iaas.ngc.i31'
|
||||||
|
when vdc.name like '%-v1cl4-%' then 'iaas.ngc.a28'
|
||||||
|
when vdc.name like '%-v1cl6-%' then 'iaas.ngc.i28'
|
||||||
|
else 'unknown' end as code,
|
||||||
|
storage_profile_types.name as storage_profile_type_name,
|
||||||
|
sum("limit")/1024./4 as gb_h_limit, -- 4 потому, что сбор раз в 15 минут
|
||||||
|
sum(used)/1024./4 as gb_h_used
|
||||||
|
from ngcloud_ru.capacity_storage
|
||||||
|
join ngcloud_ru.vdc on capacity_storage.vdc_id=vdc.id
|
||||||
|
join ngcloud_ru.tenant on tenant.id=vdc.tenant_id
|
||||||
|
join ngcloud_ru.storage_profile_types on capacity_storage.storage_profile_types_id=storage_profile_types.id
|
||||||
|
where tenant.wzcode > 0
|
||||||
|
AND capacity_storage.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND capacity_storage.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by tenant.wzcode, vdc.name, storage_profile_types.name
|
||||||
|
order by tenant.wzcode, vdc.name, storage_profile_types.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qStorage#/>
|
||||||
|
|
||||||
|
<cfquery name="qS3Vol" datasource="billing-s3">
|
||||||
|
select
|
||||||
|
sum(bucket_stat.usage_rgw_main_size_actual) as vol,
|
||||||
|
case
|
||||||
|
when bucket_stat.placement_id = 1 then 'paas.s3.ceph.vol'
|
||||||
|
when bucket_stat.placement_id = 2 then 'paas.s3.cphc.vol'
|
||||||
|
else 'unknown' end as code,
|
||||||
|
'WZ'||lpad(bucket_stat.owner, 5, '0') as wz -- *** здесь код приведен к целому, а потом к строке
|
||||||
|
from s3billing.bucket_stat
|
||||||
|
join s3billing.placement ON s3billing.placement.id=bucket_stat.placement_id
|
||||||
|
join s3billing.bucket_info ON bucket_info.id=bucket_stat.bucket_id
|
||||||
|
where bucket_stat.placement_id in (1,2)
|
||||||
|
AND bucket_stat.timestamp_addition >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND bucket_stat.timestamp_addition < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by bucket_stat.owner, bucket_stat.placement_id
|
||||||
|
order by bucket_stat.owner, bucket_stat.placement_id;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qS3Vol#/>
|
||||||
|
|
||||||
|
<cfquery name="qS3OpsDefaultPlacement" datasource="billing-s3">
|
||||||
|
select sum(ops) as ops,
|
||||||
|
case
|
||||||
|
when category.name like 'get%' then 'get'
|
||||||
|
when category.name like 'put%' then 'put'
|
||||||
|
when category.name like 'post%' then 'post'
|
||||||
|
when category.name like 'list%' then 'list'
|
||||||
|
else 'unknown' end as op,
|
||||||
|
'WZ'||lpad(user_info.name, 5, '0') as wz
|
||||||
|
from s3billing.usage_bucket_by_user
|
||||||
|
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id
|
||||||
|
join s3billing.category on usage_bucket_by_user.category_id=category.id
|
||||||
|
join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
|
||||||
|
join s3billing.placement on bucket_info.placement_id=placement.id and placement.name='default-placement'
|
||||||
|
where 1=1
|
||||||
|
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by user_info.name, category.name
|
||||||
|
order by user_info.name, category.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qS3OpsDefaultPlacement#/>
|
||||||
|
|
||||||
|
<cfquery name="qS3OpsDefault" datasource="billing-s3">
|
||||||
|
select sum(ops) as ops,
|
||||||
|
case
|
||||||
|
--when category.name like 'get%' then 'get' /*get встречается*/
|
||||||
|
when category.name like 'put%' then 'put'
|
||||||
|
--when category.name like 'post%' then 'post'
|
||||||
|
when category.name like 'list%' then 'list'
|
||||||
|
else 'unknown' end as op,
|
||||||
|
'WZ'||lpad(user_info.name, 5, '0') as wz
|
||||||
|
from s3billing.usage_bucket_by_user
|
||||||
|
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id
|
||||||
|
join s3billing.category on usage_bucket_by_user.category_id=category.id
|
||||||
|
--join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
|
||||||
|
--join s3billing.placement on bucket_info.placement_id=placement.id and placement.name='default-placement'
|
||||||
|
where s3billing.usage_bucket_by_user.bucketid='0'
|
||||||
|
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by user_info.name, category.name
|
||||||
|
order by user_info.name, category.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qS3OpsDefault#/>
|
||||||
|
--->
|
||||||
|
|
||||||
|
Трафик хранится в той же таблице, что и остальной юзадж, можно было бы доставать в общем резалтсете
|
||||||
|
|
||||||
|
<cfquery name="qS3TrafficDefaultPlacement" datasource="billing-s3">
|
||||||
|
select sum(bytes_sent) as bytes,
|
||||||
|
placement.name as placement
|
||||||
|
'WZ'||lpad(user_info.name, 5, '0') as wz
|
||||||
|
from s3billing.usage_bucket_by_user
|
||||||
|
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id
|
||||||
|
join s3billing.category on usage_bucket_by_user.category_id=category.id
|
||||||
|
join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
|
||||||
|
join s3billing.placement on bucket_info.placement_id=placement.id
|
||||||
|
where 1=1
|
||||||
|
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by user_info.name
|
||||||
|
order by user_info.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qS3TrafficDefaultPlacement#/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfquery name="qS3TrafficDefault" datasource="billing-s3">
|
||||||
|
select sum(bytes_sent) as bytes,
|
||||||
|
'WZ'||lpad(user_info.name, 5, '0') as wz
|
||||||
|
from s3billing.usage_bucket_by_user
|
||||||
|
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id
|
||||||
|
join s3billing.category on usage_bucket_by_user.category_id=category.id
|
||||||
|
--join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
|
||||||
|
--join s3billing.placement on bucket_info.placement_id=placement.id AND placement.name='default-placement'
|
||||||
|
where s3billing.usage_bucket_by_user.bucketid='0'
|
||||||
|
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by user_info.name
|
||||||
|
order by user_info.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qS3TrafficDefault#/>
|
||||||
|
|
||||||
|
<!---
|
||||||
|
задача (вызванная сыростью базы биллинга)
|
||||||
|
смапить код параметра с VDC (1:n)
|
||||||
|
iaas.ngc.i31.ram-m
|
||||||
|
iaas.ngc.a28.ram-m
|
||||||
|
|
||||||
|
cl01:
|
||||||
|
7 хостов Lenovo ThinkSystem SR650 -[7X06CTO1WW]-
|
||||||
|
2 процессора Xeon(R) Gold 6226R 16 ядер 32 потока каждый 2.9 ГГц
|
||||||
|
1.25тб озу в каждом.
|
||||||
|
В 4 из 7 есть по 4 GPU NVIDIA A16 16GB в каждом.
|
||||||
|
|
||||||
|
cl01-vsan:
|
||||||
|
16 хостов Lenovo ThinkSystem SR650 -[7X06CTO1WW]-
|
||||||
|
2 процессора Xeon(R) Gold 6226R 16 ядер 32 потока каждый 2.9 ГГц
|
||||||
|
1.25тб озу в каждом.
|
||||||
|
В 9 из 16 есть по 4 GPU NVIDIA A16 16GB в каждом.
|
||||||
|
|
||||||
|
cl02:
|
||||||
|
10 Huawei 2488H V5
|
||||||
|
4 процессора Xeon(R) Gold 6254 18 ядер 36 потоков каждый. 3.1 ГГц
|
||||||
|
2тб озу в каждом.
|
||||||
|
|
||||||
|
5 Huawei Atlas 800 (Model 3010)
|
||||||
|
2 процессора Xeon(R) Gold 6254 18 ядер 36 потоков каждый. 3.1 ГГц
|
||||||
|
1.5тб озу в каждом.
|
||||||
|
|
||||||
|
cl04-vsan:
|
||||||
|
10 Apex AR320-12
|
||||||
|
2 процессора AMD EPYC 7543 32 ядра 64 потока каждый. 2.8 ГГц
|
||||||
|
2тб озу в каждом.
|
||||||
|
18 Apex AR320-12-E7543
|
||||||
|
2 процессора AMD EPYC 7543 32 ядра 64 потока каждый. 2.8 ГГц
|
||||||
|
2тб озу в каждом.
|
||||||
|
|
||||||
|
cl05-1c:
|
||||||
|
1 Asus RS720A-E12-RS24U
|
||||||
|
2 процессора AMD EPYC 9274F 24 ядра 48 потоков каждый. 4.05 ГГц
|
||||||
|
1тб озу.
|
||||||
|
|
||||||
|
7 ручная сборка
|
||||||
|
MB Asus ProArt B650-Creator
|
||||||
|
Процессор AMD Ryzen 9 7950X 16 ядер 32 потока каждый. 4.5 ГГц
|
||||||
|
192гб озу каждый.
|
||||||
|
|
||||||
|
1 ручная сборка
|
||||||
|
процессор AMD Ryzen Threadripper PRO 5995WX 64 ядра 128 потоков каждый. 2.7 ГГц
|
||||||
|
512гб озу.
|
||||||
|
|
||||||
|
cl06-vsan:
|
||||||
|
15 R320
|
||||||
|
2 процессора Xeon(R) Gold 6342 24 ядра 48 потоков каждый. 2.8 ГГц
|
||||||
|
1тб озу каждый.
|
||||||
|
|
||||||
|
Xeon(R) Gold 6226R 2.9 ГГц
|
||||||
|
Xeon(R) Gold 6254 3.1 ГГц
|
||||||
|
AMD EPYC 7543 2.8 ГГц
|
||||||
|
AMD EPYC 9274F 4.05 ГГц
|
||||||
|
AMD Ryzen 9 7950X 4.5 ГГц
|
||||||
|
AMD Ryzen Threadripper PRO 5995WX 2.7 ГГц
|
||||||
|
Xeon(R) Gold 6342 2.8 ГГц --->
|
||||||
@@ -0,0 +1,457 @@
|
|||||||
|
|
||||||
|
<cfset dt_finish=createDateTime(year(Now()),month(Now()),1,0,0,0)/>
|
||||||
|
<cfset dt_start=dateAdd('m',-1,dt_finish)/>
|
||||||
|
<cfset hours=dateDiff('h',dt_start,dt_finish)/>
|
||||||
|
|
||||||
|
Внимание! округление вверх при расчете метрик
|
||||||
|
|
||||||
|
<cfdump var=#dt_start#/>
|
||||||
|
<cfdump var=#dt_finish#/>
|
||||||
|
<cfdump var=#hours#/>
|
||||||
|
|
||||||
|
<!--- Нужно еще применить политики бесплатного минимума --->
|
||||||
|
<!---
|
||||||
|
<cfquery name="qCode" datasource="dwh">
|
||||||
|
select distinct p.code
|
||||||
|
from elma.deals d
|
||||||
|
join elma.deals_services siv on (d.__id=ANY(siv.deal))
|
||||||
|
join elma.service_parametrs p on (p.__id = ANY(siv.params))
|
||||||
|
join elma.companies k on (d._companies=k.__id)
|
||||||
|
join elma.additional_agreements aa on (aa.__id = ANY(siv.additional_agreement))
|
||||||
|
join elma.aggrements a on (aa.contract_uid = a.__id)
|
||||||
|
where p.type=3 AND aa.is_actual
|
||||||
|
order by p.code
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qCode#/>
|
||||||
|
|
||||||
|
<cfquery name="qSpec" datasource="dwh">
|
||||||
|
select k.id_klienta, p.code, p.price, p.discount, k.__name as contragent, a.__name as contract, d.__name as deal, siv.abstractive_service as abstract_service, siv.modifier, siv.user_description
|
||||||
|
,p.__name as metric, p.ed_izm
|
||||||
|
--, aa.is_actual
|
||||||
|
from elma.deals d
|
||||||
|
join elma.deals_services siv on (d.__id=ANY(siv.deal))
|
||||||
|
join elma.service_parametrs p on (p.__id = ANY(siv.params))
|
||||||
|
join elma.companies k on (d._companies=k.__id)
|
||||||
|
join elma.additional_agreements aa on (aa.__id = ANY(siv.additional_agreement))
|
||||||
|
join elma.aggrements a on (aa.contract_uid = a.__id)
|
||||||
|
where p.type=3 AND aa.is_actual
|
||||||
|
order by k.id_klienta, p.code
|
||||||
|
</cfquery>
|
||||||
|
здесь имеем список кодов с ценами и WZ. Каждому коду можно поставить в соответствие запрос, который даст по нему выборку. Изредка один запрос может вывести 2 кода, но разбить на 2 запроса легко. Кроме того, запросы можно объединить через union в один - может быть, так будет лучше читаться (быстрее выполняться не будет)
|
||||||
|
<cfdump var=#qSpec#/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfquery name="qComputing" datasource="billing-vc">
|
||||||
|
select 'WZ'||to_char(tenant.wzcode,'FM00000') as wz, vdc.name as vdc_name,
|
||||||
|
case
|
||||||
|
when vdc.name like '%-v1cl1-%' then 'iaas.ngc.i29'
|
||||||
|
when vdc.name like '%-v1cl2-%' then 'iaas.ngc.i31'
|
||||||
|
when vdc.name like '%-v1cl4-%' then 'iaas.ngc.a28'
|
||||||
|
when vdc.name like '%-v1cl6-%' then 'iaas.ngc.i28'
|
||||||
|
else 'unknown' end as code,
|
||||||
|
sum(cpu_used/cpu_speed)/4. as core_h,
|
||||||
|
sum(mem_used)/1024/4. as gb_h
|
||||||
|
from ngcloud_ru.capacity_resource
|
||||||
|
join ngcloud_ru.vdc on capacity_resource.vdc_id=vdc.id
|
||||||
|
join ngcloud_ru.tenant on tenant.id=vdc.tenant_id
|
||||||
|
where tenant.wzcode > 0
|
||||||
|
AND capacity_resource.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND capacity_resource.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by tenant.wzcode, vdc.name
|
||||||
|
order by tenant.wzcode, vdc.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qComputing#/>
|
||||||
|
|
||||||
|
<cfquery name="qStorage" datasource="billing-vc">
|
||||||
|
select 'WZ'||to_char(tenant.wzcode,'FM00000') as wz, vdc.name as vdc_name,
|
||||||
|
case
|
||||||
|
when vdc.name like '%-v1cl1-%' then 'iaas.ngc.i29'
|
||||||
|
when vdc.name like '%-v1cl2-%' then 'iaas.ngc.i31'
|
||||||
|
when vdc.name like '%-v1cl4-%' then 'iaas.ngc.a28'
|
||||||
|
when vdc.name like '%-v1cl6-%' then 'iaas.ngc.i28'
|
||||||
|
else 'unknown' end as code,
|
||||||
|
storage_profile_types.name as storage_profile_type_name,
|
||||||
|
sum("limit")/1024./4 as gb_h_limit, -- 4 потому, что сбор раз в 15 минут
|
||||||
|
sum(used)/1024./4 as gb_h_used
|
||||||
|
from ngcloud_ru.capacity_storage
|
||||||
|
join ngcloud_ru.vdc on capacity_storage.vdc_id=vdc.id
|
||||||
|
join ngcloud_ru.tenant on tenant.id=vdc.tenant_id
|
||||||
|
join ngcloud_ru.storage_profile_types on capacity_storage.storage_profile_types_id=storage_profile_types.id
|
||||||
|
where tenant.wzcode > 0
|
||||||
|
AND capacity_storage.timestamp >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND capacity_storage.timestamp < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by tenant.wzcode, vdc.name, storage_profile_types.name
|
||||||
|
order by tenant.wzcode, vdc.name, storage_profile_types.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qStorage#/>
|
||||||
|
--->
|
||||||
|
<!---
|
||||||
|
"placement" "size_gb" "get_10k" "put_10k" "bytes_sent_gb"
|
||||||
|
HOT_FREE_LIMIT 1 10 1 100
|
||||||
|
--->
|
||||||
|
<!--- надо дисконтировать не ГБ-часы а ГБ-месяцы --->
|
||||||
|
<cfquery name="qS3Vol" datasource="billing-s3">
|
||||||
|
select /* *** ниже дублирование кода агрегации round ceil etc, обратить внимание при правке */
|
||||||
|
(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>) as vol_B
|
||||||
|
,round(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>/1024/1024/1024) as vol_GB
|
||||||
|
,greatest(round(sum(bucket_stat.usage_rgw_main_size_actual)/<cfqueryparam cfsqltype="cf_sql_integer" value=#hours#/>
|
||||||
|
/1024/1024/1024)-
|
||||||
|
case when bucket_stat.placement_id = 1 then 1 else 0 end, 0) as vol_GB_free_tier_substracted /*считается не в точности так, как в функции*/
|
||||||
|
|
||||||
|
,case
|
||||||
|
when bucket_stat.placement_id = 1 then 'paas.s3.ceph'
|
||||||
|
when bucket_stat.placement_id = 2 then 'paas.s3.cphc'
|
||||||
|
else 'unknown' end as code
|
||||||
|
--,bucket_stat.placement_id
|
||||||
|
,'WZ'||lpad(bucket_stat.owner, 5, '0') as wz -- *** здесь не число, а строка, поэтому другая формула обратной сборки WZ
|
||||||
|
|
||||||
|
from s3billing.bucket_stat
|
||||||
|
join s3billing.placement ON s3billing.placement.id=bucket_stat.placement_id
|
||||||
|
join s3billing.bucket_info ON bucket_info.id=bucket_stat.bucket_id
|
||||||
|
where bucket_stat.placement_id in (1,2)
|
||||||
|
AND bucket_stat.timestamp_addition >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND bucket_stat.timestamp_addition < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by bucket_stat.owner, bucket_stat.placement_id
|
||||||
|
order by bucket_stat.owner, bucket_stat.placement_id;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qS3Vol#/>
|
||||||
|
|
||||||
|
<cfquery name="qS3OpsTrf" datasource="billing-s3">
|
||||||
|
select
|
||||||
|
sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'get%'
|
||||||
|
OR category.name LIKE 'head%'
|
||||||
|
OR category.name LIKE 'options%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
) as get
|
||||||
|
,ceil(sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'get%'
|
||||||
|
OR category.name LIKE 'head%'
|
||||||
|
OR category.name LIKE 'options%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
)/10000) as get_10k
|
||||||
|
,greatest(ceil(sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'get%'
|
||||||
|
OR category.name LIKE 'head%'
|
||||||
|
OR category.name LIKE 'options%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
)/10000) - CASE WHEN bucket_info.placement_id = 1 THEN 10 ELSE 0 END, 0
|
||||||
|
) as get_10k_free_tier_subtracted
|
||||||
|
|
||||||
|
,sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'put%'
|
||||||
|
OR category.name LIKE 'post%'
|
||||||
|
OR category.name LIKE 'patch%'
|
||||||
|
OR category.name LIKE 'list%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
) as put
|
||||||
|
,ceil(sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'put%'
|
||||||
|
OR category.name LIKE 'post%'
|
||||||
|
OR category.name LIKE 'patch%'
|
||||||
|
OR category.name LIKE 'list%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
)/10000) as put_10k
|
||||||
|
,greatest(ceil(sum(
|
||||||
|
CASE WHEN
|
||||||
|
(category.name LIKE 'put%'
|
||||||
|
OR category.name LIKE 'post%'
|
||||||
|
OR category.name LIKE 'patch%'
|
||||||
|
OR category.name LIKE 'list%'
|
||||||
|
) THEN ops ELSE 0 END
|
||||||
|
)/10000) - CASE WHEN bucket_info.placement_id = 1 THEN 1 ELSE 0 END, 0
|
||||||
|
) as put_10k_free_tier_subtracted
|
||||||
|
|
||||||
|
,(sum(bytes_sent)) as bytes_sent
|
||||||
|
,ceil(sum(bytes_sent)/1024/1024/1024) as bytes_sent_GB
|
||||||
|
,greatest(ceil(sum(bytes_sent)/1024/1024/1024) - CASE WHEN bucket_info.placement_id = 1 THEN 100 ELSE 0 END, 0
|
||||||
|
) as bytes_sent_GB_free_tier_subtracted
|
||||||
|
|
||||||
|
,case
|
||||||
|
when bucket_info.placement_id = 1 then 'paas.s3.ceph'
|
||||||
|
when bucket_info.placement_id = 2 then 'paas.s3.cphc'
|
||||||
|
else 'unknown' end as code
|
||||||
|
,bucket_info.placement_id
|
||||||
|
--category.name, bucket_info.name
|
||||||
|
,'WZ'||lpad(user_info.name, 5, '0') as wz
|
||||||
|
from s3billing.usage_bucket_by_user
|
||||||
|
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id
|
||||||
|
join s3billing.category on usage_bucket_by_user.category_id=category.id
|
||||||
|
join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
|
||||||
|
join s3billing.placement on bucket_info.placement_id=placement.id
|
||||||
|
where 1=1
|
||||||
|
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by user_info.name, bucket_info.placement_id
|
||||||
|
order by user_info.name, bucket_info.placement_id;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qS3OpsTrf#/>
|
||||||
|
|
||||||
|
<!--- В одном месте user_id int в другом owner varchar --->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--- Трафик хранится в той же таблице, что и остальной юзадж, можно было бы доставать в общем резалтсете
|
||||||
|
<!--- расчет запихнут в функцию. Неделю этим не занимался - забыл все совершенно --->
|
||||||
|
<cfquery name="qS3TrafficDefaultPlacement" datasource="billing-s3">
|
||||||
|
select sum(bytes_sent) as bytes,
|
||||||
|
--placement.name as placement,
|
||||||
|
case
|
||||||
|
when placement.name = 'COLD' then 'paas.s3.cphc'
|
||||||
|
when placement.name = 'default-placement' then 'paas.s3.ceph'
|
||||||
|
else 'unknown' end as code,
|
||||||
|
'WZ'||lpad(user_info.name, 5, '0') as wz
|
||||||
|
from s3billing.usage_bucket_by_user
|
||||||
|
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id
|
||||||
|
join s3billing.category on usage_bucket_by_user.category_id=category.id
|
||||||
|
join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
|
||||||
|
join s3billing.placement on bucket_info.placement_id=placement.id
|
||||||
|
where 1=1
|
||||||
|
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by user_info.name, placement.name
|
||||||
|
order by user_info.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qS3TrafficDefaultPlacement#/> --->
|
||||||
|
|
||||||
|
|
||||||
|
<!--- <cfquery name="qS3TrafficDefault" datasource="billing-s3">
|
||||||
|
select sum(bytes_sent) as bytes,
|
||||||
|
case
|
||||||
|
when placement.name = 'COLD' then 'paas.s3.cphc'
|
||||||
|
when placement.name = 'default-placement' then 'paas.s3.ceph'
|
||||||
|
else 'unknown' end as code,
|
||||||
|
'WZ'||lpad(user_info.name, 5, '0') as wz
|
||||||
|
from s3billing.usage_bucket_by_user
|
||||||
|
join s3billing.user_info on usage_bucket_by_user.user_id=user_info.id
|
||||||
|
join s3billing.category on usage_bucket_by_user.category_id=category.id
|
||||||
|
join s3billing.bucket_info on usage_bucket_by_user.bucketid=bucket_info.id
|
||||||
|
join s3billing.placement on bucket_info.placement_id=placement.id AND placement.name='default-placement'
|
||||||
|
where s3billing.usage_bucket_by_user.bucketid='0'
|
||||||
|
AND usage_bucket_by_user.time >= <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_start#/>
|
||||||
|
AND usage_bucket_by_user.time < <cfqueryparam cfsqltype="cf_sql_timestamp" value=#dt_finish#/>
|
||||||
|
group by user_info.name
|
||||||
|
order by user_info.name;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qS3TrafficDefault#/> --->
|
||||||
|
|
||||||
|
<!---
|
||||||
|
задача (вызванная сыростью базы биллинга)
|
||||||
|
смапить код параметра с VDC (1:n)
|
||||||
|
iaas.ngc.i31.ram-m
|
||||||
|
iaas.ngc.a28.ram-m
|
||||||
|
|
||||||
|
cl01:
|
||||||
|
7 хостов Lenovo ThinkSystem SR650 -[7X06CTO1WW]-
|
||||||
|
2 процессора Xeon(R) Gold 6226R 16 ядер 32 потока каждый 2.9 ГГц
|
||||||
|
1.25тб озу в каждом.
|
||||||
|
В 4 из 7 есть по 4 GPU NVIDIA A16 16GB в каждом.
|
||||||
|
|
||||||
|
cl01-vsan:
|
||||||
|
16 хостов Lenovo ThinkSystem SR650 -[7X06CTO1WW]-
|
||||||
|
2 процессора Xeon(R) Gold 6226R 16 ядер 32 потока каждый 2.9 ГГц
|
||||||
|
1.25тб озу в каждом.
|
||||||
|
В 9 из 16 есть по 4 GPU NVIDIA A16 16GB в каждом.
|
||||||
|
|
||||||
|
cl02:
|
||||||
|
10 Huawei 2488H V5
|
||||||
|
4 процессора Xeon(R) Gold 6254 18 ядер 36 потоков каждый. 3.1 ГГц
|
||||||
|
2тб озу в каждом.
|
||||||
|
|
||||||
|
5 Huawei Atlas 800 (Model 3010)
|
||||||
|
2 процессора Xeon(R) Gold 6254 18 ядер 36 потоков каждый. 3.1 ГГц
|
||||||
|
1.5тб озу в каждом.
|
||||||
|
|
||||||
|
cl04-vsan:
|
||||||
|
10 Apex AR320-12
|
||||||
|
2 процессора AMD EPYC 7543 32 ядра 64 потока каждый. 2.8 ГГц
|
||||||
|
2тб озу в каждом.
|
||||||
|
18 Apex AR320-12-E7543
|
||||||
|
2 процессора AMD EPYC 7543 32 ядра 64 потока каждый. 2.8 ГГц
|
||||||
|
2тб озу в каждом.
|
||||||
|
|
||||||
|
cl05-1c:
|
||||||
|
1 Asus RS720A-E12-RS24U
|
||||||
|
2 процессора AMD EPYC 9274F 24 ядра 48 потоков каждый. 4.05 ГГц
|
||||||
|
1тб озу.
|
||||||
|
|
||||||
|
7 ручная сборка
|
||||||
|
MB Asus ProArt B650-Creator
|
||||||
|
Процессор AMD Ryzen 9 7950X 16 ядер 32 потока каждый. 4.5 ГГц
|
||||||
|
192гб озу каждый.
|
||||||
|
|
||||||
|
1 ручная сборка
|
||||||
|
процессор AMD Ryzen Threadripper PRO 5995WX 64 ядра 128 потоков каждый. 2.7 ГГц
|
||||||
|
512гб озу.
|
||||||
|
|
||||||
|
cl06-vsan:
|
||||||
|
15 R320
|
||||||
|
2 процессора Xeon(R) Gold 6342 24 ядра 48 потоков каждый. 2.8 ГГц
|
||||||
|
1тб озу каждый.
|
||||||
|
|
||||||
|
Xeon(R) Gold 6226R 2.9 ГГц
|
||||||
|
Xeon(R) Gold 6254 3.1 ГГц
|
||||||
|
AMD EPYC 7543 2.8 ГГц
|
||||||
|
AMD EPYC 9274F 4.05 ГГц
|
||||||
|
AMD Ryzen 9 7950X 4.5 ГГц
|
||||||
|
AMD Ryzen Threadripper PRO 5995WX 2.7 ГГц
|
||||||
|
Xeon(R) Gold 6342 2.8 ГГц --->
|
||||||
|
|
||||||
|
<!--- 2025-08-15 20:47:44 *** Операции S3 считаются не так, как написано в тарифе. Например, считается list. В тарифе вообще не описан минимальный лимит --->
|
||||||
|
|
||||||
|
<!--- BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
(
|
||||||
|
select
|
||||||
|
billing_per_user_fix_pl.placement as placement,
|
||||||
|
(CASE
|
||||||
|
WHEN ( billing_per_user_fix_pl.placement = 'HOT'
|
||||||
|
) THEN greatest(ceil(sum(size)/1024/1024/1024)-1,0)
|
||||||
|
WHEN ( not billing_per_user_fix_pl.placement = 'HOT'
|
||||||
|
) THEN ceil(sum(size)/1024/1024/1024)
|
||||||
|
END) AS size_GB,
|
||||||
|
(CASE
|
||||||
|
WHEN ( billing_per_user_fix_pl.placement = 'HOT'
|
||||||
|
) THEN greatest(ceil(sum(get)/10000)-10,0)
|
||||||
|
WHEN ( not billing_per_user_fix_pl.placement = 'HOT'
|
||||||
|
) THEN ceil(sum(get)/10000)
|
||||||
|
END) AS get_10k,
|
||||||
|
(CASE
|
||||||
|
WHEN ( billing_per_user_fix_pl.placement = 'HOT'
|
||||||
|
) THEN greatest(ceil(sum(put)/10000)-1,0)
|
||||||
|
WHEN ( not billing_per_user_fix_pl.placement = 'HOT'
|
||||||
|
) THEN ceil(sum(put)/10000)
|
||||||
|
END) AS put_10k,
|
||||||
|
(CASE
|
||||||
|
WHEN ( billing_per_user_fix_pl.placement = 'HOT'
|
||||||
|
) then greatest(ceil(sum(bytes_sent)/1024/1024/1024)-100,0)
|
||||||
|
WHEN ( not billing_per_user_fix_pl.placement = 'HOT'
|
||||||
|
) THEN ceil(sum(bytes_sent)/1024/1024/1024)
|
||||||
|
END) AS bytes_sent_GB
|
||||||
|
from s3billing.billing_per_user_fix_pl(_ds,_df,_user)
|
||||||
|
group by billing_per_user_fix_pl.placement
|
||||||
|
UNION all
|
||||||
|
select
|
||||||
|
'HOT_FREE_LIMIT' as placement,
|
||||||
|
'1' AS size_GB,
|
||||||
|
'10' AS get_10k,
|
||||||
|
'1' AS put_10k,
|
||||||
|
'100' AS bytes_sent_GB
|
||||||
|
);
|
||||||
|
END; --->
|
||||||
|
|
||||||
|
<!---
|
||||||
|
BEGIN
|
||||||
|
RETURN QUERY
|
||||||
|
|
||||||
|
SELECT
|
||||||
|
s3billing.bucket_info.name AS bucket_name,
|
||||||
|
(case
|
||||||
|
when ( s3billing.placement.name = 'default-placement') then 'HOT'
|
||||||
|
WHEN ( not s3billing.placement.name = 'default-placement' ) THEN s3billing.placement.name
|
||||||
|
end
|
||||||
|
) as placement,
|
||||||
|
coalesce(round(sum(usage_rgw_main_size_actual)/
|
||||||
|
(SELECT (DATE_PART('day',
|
||||||
|
((TO_TIMESTAMP(_df, 'YYYY-MM-DD HH24:MI:SS'))
|
||||||
|
-TO_TIMESTAMP(_ds, 'YYYY-MM-DD HH24:MI:SS'))
|
||||||
|
)*24+
|
||||||
|
DATE_PART('hour',
|
||||||
|
((TO_TIMESTAMP(_df, 'YYYY-MM-DD HH24:MI:SS'))
|
||||||
|
-TO_TIMESTAMP(_ds, 'YYYY-MM-DD HH24:MI:SS'))
|
||||||
|
)))
|
||||||
|
|
||||||
|
)::numeric,0) AS size
|
||||||
|
,coalesce(s2.get, 0) AS get
|
||||||
|
,coalesce(s2.put, 0) AS put
|
||||||
|
,coalesce(s2.bytes_sent, 0) as bytes_sent
|
||||||
|
FROM s3billing.bucket_stat
|
||||||
|
INNER JOIN s3billing.placement ON s3billing.bucket_stat.placement_id=s3billing.placement.id
|
||||||
|
INNER JOIN s3billing.bucket_info ON s3billing.bucket_stat.bucket_id=s3billing.bucket_info.id
|
||||||
|
LEFT JOIN (
|
||||||
|
SELECT
|
||||||
|
s1.bucket_id AS bucket_id
|
||||||
|
,s1.bucket_name AS bucket_name,
|
||||||
|
(case
|
||||||
|
when ( s1.placement = 'default-placement') then 'HOT'
|
||||||
|
WHEN ( not s1.placement = 'default-placement' ) THEN s1.placement
|
||||||
|
end
|
||||||
|
) as placement
|
||||||
|
,coalesce(sum(s1.get) ,0) AS get
|
||||||
|
,coalesce(sum(s1.put) ,0) AS put
|
||||||
|
,coalesce(sum(s1.bytes_sent), 0) AS bytes_sent
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
s3billing.bucket_info.id AS bucket_id
|
||||||
|
,s3billing.bucket_info.name AS bucket_name
|
||||||
|
,(case
|
||||||
|
when ( s3billing.placement.name = 'default-placement') then 'HOT'
|
||||||
|
WHEN ( not s3billing.placement.name = 'default-placement' ) THEN s3billing.placement.name
|
||||||
|
end
|
||||||
|
) as placement
|
||||||
|
,(CASE
|
||||||
|
WHEN ( s3billing.category.name LIKE 'get%'
|
||||||
|
OR s3billing.category.name LIKE 'head%'
|
||||||
|
OR s3billing.category.name LIKE 'options%'
|
||||||
|
) THEN coalesce(sum(ops), 0)
|
||||||
|
END) AS get
|
||||||
|
,(CASE
|
||||||
|
WHEN ( s3billing.category.name LIKE 'put%'
|
||||||
|
OR s3billing.category.name LIKE 'post%'
|
||||||
|
OR s3billing.category.name LIKE 'patch%'
|
||||||
|
OR s3billing.category.name LIKE 'list%'
|
||||||
|
) THEN coalesce(sum(ops), 0)
|
||||||
|
END) AS put
|
||||||
|
,coalesce(sum(usage_bucket_by_user.bytes_sent), 0) AS bytes_sent
|
||||||
|
|
||||||
|
FROM
|
||||||
|
s3billing.usage_bucket_by_user
|
||||||
|
INNER JOIN s3billing.bucket_info ON s3billing.usage_bucket_by_user.bucketid = s3billing.bucket_info.id
|
||||||
|
INNER JOIN s3billing.placement ON s3billing.bucket_info.placement_id = s3billing.placement.id
|
||||||
|
INNER JOIN s3billing.user_info ON s3billing.usage_bucket_by_user.user_id = s3billing.user_info.id
|
||||||
|
INNER JOIN s3billing.category ON s3billing.usage_bucket_by_user.category_id = s3billing.category.id
|
||||||
|
|
||||||
|
WHERE
|
||||||
|
s3billing.user_info.name=_user
|
||||||
|
AND s3billing.usage_bucket_by_user.time >=TO_TIMESTAMP(_ds, 'YYYY-MM-DD HH24:MI:SS')
|
||||||
|
AND s3billing.usage_bucket_by_user.time < TO_TIMESTAMP(_df, 'YYYY-MM-DD HH24:MI:SS')
|
||||||
|
GROUP BY
|
||||||
|
s3billing.bucket_info.name
|
||||||
|
,s3billing.placement.name
|
||||||
|
,s3billing.category.name
|
||||||
|
,s3billing.bucket_info.id
|
||||||
|
) AS s1
|
||||||
|
|
||||||
|
GROUP BY
|
||||||
|
s1.bucket_name
|
||||||
|
,s1.bucket_id
|
||||||
|
,s1.placement
|
||||||
|
|
||||||
|
ORDER BY
|
||||||
|
s1.placement
|
||||||
|
,s1.bucket_name
|
||||||
|
|
||||||
|
) AS s2 ON s2.bucket_id = s3billing.bucket_stat.bucket_id
|
||||||
|
WHERE owner=_user
|
||||||
|
AND timestamp_addition >= TO_TIMESTAMP(_ds, 'YYYY-MM-DD HH24:MI:SS')
|
||||||
|
AND timestamp_addition < TO_TIMESTAMP(_df, 'YYYY-MM-DD HH24:MI:SS')
|
||||||
|
GROUP BY
|
||||||
|
s3billing.bucket_info.name
|
||||||
|
,s3billing.placement.name
|
||||||
|
,s3billing.bucket_stat.bucket_id
|
||||||
|
,s2.get
|
||||||
|
,s2.put
|
||||||
|
,s2.bytes_sent
|
||||||
|
ORDER BY
|
||||||
|
s3billing.placement.name
|
||||||
|
,s3billing.bucket_info.name
|
||||||
|
;
|
||||||
|
END; --->
|
||||||
|
<!--- select * from s3billing.billing_per_user_fix_pl('2025-05-01 00:00:00','2025-06-01 00:00:00','1395'); --->
|
||||||
|
|
||||||
+79
@@ -0,0 +1,79 @@
|
|||||||
|
<!---
|
||||||
|
Access check aka проверка доступа.
|
||||||
|
Если доступа нет, код, заключенный внутри тега, не выполняется, текст не выводится.
|
||||||
|
|
||||||
|
Два варианта использования.
|
||||||
|
1. При заданном значении действия проверяется наличие этого доступа к объекту, ответ да-нет
|
||||||
|
2. Проверяется уровень доступа к заданному объекту
|
||||||
|
|
||||||
|
Для пустого объекта
|
||||||
|
|
||||||
|
Если задать строковый атрибут output, в переменную с таким именем запишется
|
||||||
|
TRUE(доступ разрешен)/FALSE(доступ запрещен).
|
||||||
|
Тег обязательно должен быть закрыт.
|
||||||
|
---><cfsilent>
|
||||||
|
<!--- проверка на закрытость тега - без закрытия возможна неоднозначность со вложенными тегами --->
|
||||||
|
<cfif NOT thisTag.hasEndTag>
|
||||||
|
<cfthrow
|
||||||
|
type="custom" message="Unclosed AC.CFM tag found."
|
||||||
|
detail="AC.CFM tag must be closed. Self-closed tag is acceptable, e.g.: <cfmodule template=""mod/ac.cfm"" />.">
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif thisTag.ExecutionMode is 'start'>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.obj" type="string" default=""/><!---
|
||||||
|
объект, к которому проверяется доступ --->
|
||||||
|
<cfparam name="ATTRIBUTES.act" type="numeric" default="#request.PERMISSION_READ#"/><!---
|
||||||
|
действие, которое проверяется --->
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.permissionOut" type="string" default=""/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<cfset permission=request.PERMISSION_NONE/>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.obj EQ "">
|
||||||
|
<cfif #request.usrAuthenticated()#>
|
||||||
|
<cfset permission=request.PERMISSION_WRITE/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset permission=request.PERMISSION_READ/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset permitted=(ATTRIBUTES.act EQ #request.PERMISSION_READ# OR #request.usrAuthenticated()#)/>
|
||||||
|
|
||||||
|
<cfelse>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset permitted=(len(ATTRIBUTES.obj) GT 0)>
|
||||||
|
|
||||||
|
<!---*** Максимальный доступ для перечисленных объектов и ролей
|
||||||
|
Можно подумать о минимальном--->
|
||||||
|
<cfquery name="qACL" datasource="#request.DS#">
|
||||||
|
select coalesce(max(permission),0) as permission
|
||||||
|
from usr_role ur
|
||||||
|
join acl on acl.role_id=ur.role_id
|
||||||
|
where ur.usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value="#request.usr_id#">
|
||||||
|
and acl.obj in (<cfqueryparam cfsqltype="cf_sql_varchar" value="#ATTRIBUTES.obj#" list="true">)
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfset permitted=(qACL.permission GE ATTRIBUTES.act)>
|
||||||
|
<cfset permission=#qACL.permission#/>
|
||||||
|
|
||||||
|
<cfcatch type="database">
|
||||||
|
<cfset permitted="Yes"/><!---***--->
|
||||||
|
<cfrethrow/>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.output)>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#", permitted)><!---boolean--->
|
||||||
|
</cfif>
|
||||||
|
<cfif len(ATTRIBUTES.permissionOut)>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.permissionOut#", permission)><!---integer 0 1 2--->
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif NOT permitted><cfexit method="EXITTAG"></cfif>
|
||||||
|
</cfif><!---thisTag.ExecutionMode is 'start'--->
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!---v0.2 19:01 06.10.2020
|
||||||
|
---><cfparam name="ATTRIBUTES.contenttype"/>
|
||||||
|
<cfswitch expression=#ATTRIBUTES.contenttype#>
|
||||||
|
<cfcase value="text/plain"><img src="img/mimetype/text.png"/></cfcase>
|
||||||
|
<cfcase value="text/html"><img src="img/mimetype/html.png"/></cfcase>
|
||||||
|
<cfcase value="application/pdf"><img src="img/mimetype/pdf.png"/></cfcase>
|
||||||
|
<cfcase value="image/gif,image/png,image/jpeg"><img src="img/mimetype/image.png"/></cfcase>
|
||||||
|
<cfcase value="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"><img src="img/mimetype/excel.png"/></cfcase>
|
||||||
|
<cfcase value="application/vnd.ms-powerpoint"><img src="img/mimetype/powerpoint.png"/></cfcase>
|
||||||
|
<cfcase value="application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"><img src="img/mimetype/word.png"/></cfcase>
|
||||||
|
<cfdefaultcase><img src="img/mimetype/document.png"/></cfdefaultcase>
|
||||||
|
</cfswitch><cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.name"/>
|
||||||
|
<cfparam name="ATTRIBUTES.value" default="1"/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ATTRIBUTES,"checked")>
|
||||||
|
<cfset checked=!(ATTRIBUTES.checked EQ "0" OR ATTRIBUTES.checked EQ "")/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset checked=!(ATTRIBUTES.container[ATTRIBUTES.name] EQ "0" OR ATTRIBUTES.container[ATTRIBUTES.name] EQ "")/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||||
|
<cfset attributeList="name,value,checked,container,suffix,id,disabled"/>
|
||||||
|
|
||||||
|
</cfsilent><cfoutput><input type="checkbox" name="#ATTRIBUTES.name#"<!---
|
||||||
|
---><cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#ATTRIBUTES.value#"<!---
|
||||||
|
---><cfif checked> checked="checked"</cfif><!---
|
||||||
|
---><cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||||
|
passthrough additional attributes
|
||||||
|
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||||
|
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||||
|
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<cfparam name="ATTRIBUTES.lang" type="string" default=""/>
|
||||||
|
<cfparam name="request.language" type="string" default=""/>
|
||||||
|
<cfif len(ATTRIBUTES.lang) AND #ATTRIBUTES.lang# NEQ #request.language#>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif thisTag.executionMode IS "end">
|
||||||
|
|
||||||
|
<!--- <cfloop collection=#CALLER# item="item">
|
||||||
|
<cfset structInsert(variables, item, CALLER[item], false)/>
|
||||||
|
</cfloop> --->
|
||||||
|
|
||||||
|
<cfassociate basetag="cf_table" datacollection="columns"/>
|
||||||
|
<cfset attributeList=""/> <!--- *** сделать проброс атрибутов --->
|
||||||
|
<cfparam name="thisTag.th" type="array" default=#[{content="",field=""}]#/><!---контейнер для данных вложенного элемента TH. Предусмотрен только один элемент массива, остальные игнорируются--->
|
||||||
|
<!---<cfparam name="thisTag.td" type="array" default=#[{content="",field=""}]#/>--->
|
||||||
|
<cfparam name="thisTag.td" type="array" default=#thisTag.th#/><!---контейнер для данных вложенного элемента TD. Предусмотрен только один элемент массива, остальные игнорируются--->
|
||||||
|
<!---Зачем массив? - потому что массив вложенных тегов, а у нас считается только первый вложенный тег TD--->
|
||||||
|
|
||||||
|
<cfset ATTRIBUTES.td=#thisTag.td#/><!---поле, видное включающему тегу table в коллекции columns--->
|
||||||
|
<cfset ATTRIBUTES.th=#thisTag.th#/><!---поле, видное включающему тегу table в коллекции columns--->
|
||||||
|
<cfset ATTRIBUTES.content=thisTag.generatedContent/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.field" default="#thisTag.td[1].field#"/><!---***---><!---настоящий атрибут - имя поля запроса (query) для ВЫВОДА, может задаваться как атрибут тега--->
|
||||||
|
<cfparam name="ATTRIBUTES.formatter" type="function" default=#function (x) {return x;}#/>
|
||||||
|
<cfparam name="ATTRIBUTES.sortable" type="boolean" default=true/>
|
||||||
|
<cfparam name="ATTRIBUTES.visible" type="boolean" default=true/>
|
||||||
|
<cfparam name="ATTRIBUTES.columnOrder" type="integer" default=0/>
|
||||||
|
<!--- Желательное поведение сортировки колонок: для некоторых колонок указан порядок, на этом месте они и оказываются. Не указан - идут в порядке появления в коде. Если 2 колонки с одним порядком сортировки, первой выводится та, которая первая по тексту. --->
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfimport prefix="c" taglib="."/>
|
||||||
|
|
||||||
|
|
||||||
|
<!---*** идея включить титул в метаданные была не совсем пустой, но когда титул определяется в селекте, это не слишком удобно --->
|
||||||
|
<cfassociate basetag="cf_grid_head" datacollection="headersArray"/>
|
||||||
|
<cfset envelope=getBaseTagData('cf_grid_head',1)/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.title" type="string" default="#envelope.ATTRIBUTES.titleMap[ATTRIBUTES.name].title#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.name" type="string"/><!--- Заголовок указвается на том поле, по которому будет сортировка, а не на том, которе отображается --->
|
||||||
|
<cfparam name="ATTRIBUTES.arrows" type="boolean" default="Yes"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<cfset attributeList="name,arrows,title"/>
|
||||||
|
|
||||||
|
<!--- В данном случае реализован подход: дополнительные атрибуты передаются вниз.
|
||||||
|
Другой вариант - создавать атрибут extra, в который писать суффикс QueryString.
|
||||||
|
Первый подход, использованный здесь, позволяет иметь доступ к дополнительным атрибутам, но непонятно, зачем - ведь их имена все равно неизвестны.
|
||||||
|
Очевидный минус этого подхода - возможный конфликт имен с именами существующих атрибутов --->
|
||||||
|
<cfset extraAttributes=structNew()/>
|
||||||
|
|
||||||
|
<!--- дополнительные атрибуты родительского тега--->
|
||||||
|
<cfif StructKeyExists(envelope, "extraAttributes") AND isStruct(envelope.extraAttributes)>
|
||||||
|
<cfloop collection=#envelope.extraAttributes# item="attr">
|
||||||
|
<cfif NOT listFindNoCase(attributeList, attr)><!--- проверка относительно локальных атрибутов, чтобы их не перетереть--->
|
||||||
|
<cfset val="#structFind(envelope.extraAttributes, attr)#"/>
|
||||||
|
<cfif IsSimpleValue(val)>
|
||||||
|
<cfset structInsert(extraAttributes, attr, val)/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfloop collection=#ATTRIBUTES# item="attr">
|
||||||
|
<cfif NOT listFindNoCase(attributeList, attr)>
|
||||||
|
<cfset val="#structFind(ATTRIBUTES, attr)#"/>
|
||||||
|
<cfif IsSimpleValue(val)>
|
||||||
|
<cfset structInsert(extraAttributes, attr, val, "Yes")/><!--- локальные атрибуты перетрут родительские --->
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
|
||||||
|
</cfsilent><cfif thisTag.executionMode is "end"><cfoutput>#ATTRIBUTES.title#
|
||||||
|
<cfif ATTRIBUTES.arrows><c:order_arrows sortArray=#envelope.ATTRIBUTES.sortArray# n=#envelope.ATTRIBUTES.titleMap[ATTRIBUTES.name].ordinal# attributeCollection=#extraAttributes#/></cfif></cfoutput></cfif>
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
<!--- combobox
|
||||||
|
v3.1 23.10.2020 support of list in multiple selection
|
||||||
|
v3.0 12.08.2016 ATTRIBUTES.readonly
|
||||||
|
v2.1 04.05.2016 ATTRIBUTES.disabled
|
||||||
|
v2 с форматным выводом
|
||||||
|
|
||||||
|
---><cfprocessingdirective suppresswhitespace="true">
|
||||||
|
<!---
|
||||||
|
<cfparam name="ATTRIBUTES.query" type="query"/>
|
||||||
|
<cfparam name="ATTRIBUTES.queryString" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.list" type="string"/>
|
||||||
|
--->
|
||||||
|
<cfparam name="ATTRIBUTES.DS" type="string" default="#request.DS#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.combo"/><!--- имя контрола (name)--->
|
||||||
|
<cfparam name="ATTRIBUTES.key"/><!--- Имя ключевого поля---><!--- ограничение: не поддерживаем составные ключи --->
|
||||||
|
<cfparam name="ATTRIBUTES.display" default=""/><!--- имя поля резалтсета --->
|
||||||
|
<cfparam name="ATTRIBUTES.displayf" default=""/><!--- форматный вывод, поля резалтсета писать: ##field##, например "*##field1##:##field2##*" --->
|
||||||
|
<cfparam name="ATTRIBUTES.selected" default=""/><!--- в случае base64 - завернуто в base64--->
|
||||||
|
<cfparam name="ATTRIBUTES.base64" default="No"/><!---*** это архитектурная ошибка. Base64 is url unsafe. Но поскольку все передается POST-ом, обычно не проявляется --->
|
||||||
|
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.style_selector" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.style_spec" type="struct" default=#structNew()#/>
|
||||||
|
<cfparam name="ATTRIBUTES.class_selector" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.class_spec" type="struct" default=#structNew()#/>
|
||||||
|
<cfparam name="ATTRIBUTES.flag_disabled" type="string" default=""/><!--- проверяется на > 0. Возможно, не следует отключать выбранное значение --->
|
||||||
|
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="false"/><!--- в случае readonly выводится текст и скрытое поле с ID, если множественное выделение, то надо наверно выводить все выделенные элементы? --->
|
||||||
|
<cfparam name="ATTRIBUTES.extra" type="string" default=""/><!--- --->
|
||||||
|
|
||||||
|
|
||||||
|
<cfset attributeList="template,query,queryString,combo,key,display,displayf,selected,empty,DS,base64,disabled,style_selector,style_spec,class_selector,class_spec,flag_disabled,readonly,extra"/>
|
||||||
|
|
||||||
|
<cffunction name="escape64" returnType="string">
|
||||||
|
<cfargument name="val" type="string">
|
||||||
|
<cfif #ATTRIBUTES.base64#>
|
||||||
|
<cfreturn ToBase64(val)>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn val>
|
||||||
|
</cfif>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="isSelected" returnType="boolean">
|
||||||
|
<!--- tests for presense of key (second arg) in passed array (first arg) or, if sel is simple value just compares key and sel.
|
||||||
|
Used for checking against multiple alternatives like "save" OR "saveAndClose"
|
||||||
|
--->
|
||||||
|
<cfargument name="sel">
|
||||||
|
<cfargument name="key">
|
||||||
|
<cfscript>
|
||||||
|
if (isArray(sel)) {
|
||||||
|
for(i=1; i <= ArrayLen(sel); i++){
|
||||||
|
if (sel[i]==key) {return true;}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else { //считаем, что из сложных типов тут бывает только массив
|
||||||
|
if (!isSimpleValue(sel)) {return false;}
|
||||||
|
/*return (sel == key);*/
|
||||||
|
return listFind(sel,key);
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="strSelected" returnType="string">
|
||||||
|
<cfargument name="sel">
|
||||||
|
<cfargument name="key">
|
||||||
|
<!---unsupported in CF8: <cfreturn (isSelected(sel,key)? " selected" : "")>--->
|
||||||
|
<cfif isSelected(sel,key)>
|
||||||
|
<cfreturn " selected">
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn "">
|
||||||
|
</cfif>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<!--- функция условного обертывания --->
|
||||||
|
<cffunction name="w" returnType="string">
|
||||||
|
<cfargument name="prefix"/>
|
||||||
|
<cfargument name="input"/>
|
||||||
|
<cfargument name="suffix" default=""/>
|
||||||
|
|
||||||
|
<cfif len(input) GT 0>
|
||||||
|
<cfreturn "#prefix##input##suffix#"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn ""/>
|
||||||
|
</cfif>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="strAttribute" returnType="string">
|
||||||
|
<!---returns formatted string ' attribute="value"',
|
||||||
|
where attribute name is the first argument,
|
||||||
|
and value is taken from passed structure(third argument) by key (second argument)--->
|
||||||
|
<cfargument name="name"/>
|
||||||
|
<cfargument name="selector"/>
|
||||||
|
<cfargument name="spec" type="struct"/>
|
||||||
|
|
||||||
|
<cfif StructKeyExists(spec, selector)>
|
||||||
|
<cfreturn ' #name#="#structFind(spec, selector)#"'/>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn "">
|
||||||
|
</cfif>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<!--- ---------------------------------------------------------------------------------- --->
|
||||||
|
|
||||||
|
<cfif structKeyExists(ATTRIBUTES,"query")>
|
||||||
|
<cfset qSel=ATTRIBUTES.query>
|
||||||
|
<cfelseif structKeyExists(ATTRIBUTES,"queryString")>
|
||||||
|
<cfquery name="qSel" datasource="#ATTRIBUTES.DS#">
|
||||||
|
#preserveSingleQuotes(ATTRIBUTES.queryString)#
|
||||||
|
</cfquery>
|
||||||
|
<cfelseif structKeyExists(ATTRIBUTES,"list")>
|
||||||
|
<cfset qSel=#queryNew("item")#/>
|
||||||
|
<cfloop list=#ATTRIBUTES.list# item="item">
|
||||||
|
<cfset queryAddRow(qSel,{"item":"#item#"})/>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
|
||||||
|
<!--- parse class specifications--->
|
||||||
|
<cfscript>
|
||||||
|
</cfscript>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.readonly>
|
||||||
|
<cfif isArray(ATTRIBUTES.selected)>
|
||||||
|
<cfset selectedList=ArrayToList(ATTRIBUTES.selected)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset selectedList=ATTRIBUTES.selected/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfquery name="qSelFiltered" dbtype="query">
|
||||||
|
select * from qSel where <cfif len(selectedList)>#ATTRIBUTES.key# in (#selectedList#)<cfelse>1=0</cfif>
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfoutput>
|
||||||
|
<div class="textbox-disabled" style="display:inline-block;"<cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||||
|
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||||
|
---></cfloop>>
|
||||||
|
<cfif qSelFiltered.recordCount GT 0>
|
||||||
|
<cfloop query="qSelFiltered"><!---
|
||||||
|
---><cfset key=evaluate("#ATTRIBUTES.key#")/><!---
|
||||||
|
---><cfset key=trim(escape64(key))/><!---
|
||||||
|
---><div><input type="hidden" name="#ATTRIBUTES.combo#" value="#key#"/>#Evaluate(ATTRIBUTES.display)#<cfset Evaluate("WriteOutput('#ATTRIBUTES.displayf#')")/></div>
|
||||||
|
</cfloop>
|
||||||
|
<cfelseif isDefined("ATTRIBUTES.empty")>
|
||||||
|
<input type="hidden" name="#ATTRIBUTES.combo#" value="#ATTRIBUTES.empty#"/><!-- #ATTRIBUTES.combo#: nothing selected -->
|
||||||
|
|
||||||
|
<cfelse>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</div>
|
||||||
|
</cfoutput>
|
||||||
|
<cfelse><!--- ATTRIBUTES.readonly --->
|
||||||
|
|
||||||
|
<cfoutput>
|
||||||
|
<select name="#ATTRIBUTES.combo#"<!---
|
||||||
|
passthrough additional attributes
|
||||||
|
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||||
|
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||||
|
---></cfloop><cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><cfif len(ATTRIBUTES.extra)>#ATTRIBUTES.extra#</cfif>>
|
||||||
|
<cfif isDefined("ATTRIBUTES.empty")>
|
||||||
|
<option value="#ATTRIBUTES.empty#"></option><!--- *** Надо было делать не пустое, а дефолтное значение, чтобы можно было ввести, например, {N/A, -1} --->
|
||||||
|
</cfif>
|
||||||
|
<cfloop query=qSel>
|
||||||
|
<cfset key=evaluate("#ATTRIBUTES.key#")/><!--- в контексте запроса, поэтому находится в цикле! *** не самый лучший способ в смысле производительности --->
|
||||||
|
<cfset key=trim(escape64(key))/>
|
||||||
|
<cfset str_sel=strSelected(ATTRIBUTES.selected, key)/>
|
||||||
|
<option value="#key#"#str_sel#<!---
|
||||||
|
---><cfif len(ATTRIBUTES.style_selector) GT 0>#strAttribute("style",
|
||||||
|
Evaluate(ATTRIBUTES.style_selector),ATTRIBUTES.style_spec)#</cfif><!---
|
||||||
|
---><cfif len(ATTRIBUTES.class_selector) GT 0>#strAttribute("class", Evaluate(ATTRIBUTES.class_selector),ATTRIBUTES.class_spec)#</cfif><!---
|
||||||
|
---><cfif len(ATTRIBUTES.flag_disabled) GT 0><cfif Evaluate(ATTRIBUTES.flag_disabled) GT 0> disabled="1"</cfif></cfif><!---
|
||||||
|
--->>#Evaluate(ATTRIBUTES.display)#<cfset Evaluate("WriteOutput('#ATTRIBUTES.displayf#')")/></option>
|
||||||
|
</cfloop>
|
||||||
|
</select>
|
||||||
|
</cfoutput>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfprocessingdirective><cfexit method="EXITTAG"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.name"/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ATTRIBUTES,"value")>
|
||||||
|
<cfset value=#ATTRIBUTES.value#/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset value=#ATTRIBUTES.container[ATTRIBUTES.name]#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.format" default="YYYY-MM-DD"/>
|
||||||
|
<cfparam name="ATTRIBUTES.size" default="#len(ATTRIBUTES.format)#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||||
|
<cfset attributeList="name,value,container,suffix,id,format,size,disabled"/>
|
||||||
|
|
||||||
|
</cfsilent><cfoutput><input type="text" name="#ATTRIBUTES.name#"<cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#dateFormat(value, ATTRIBUTES.format)#" size="#ATTRIBUTES.size#"<cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||||
|
passthrough additional attributes
|
||||||
|
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||||
|
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||||
|
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.name"/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ATTRIBUTES,"value")>
|
||||||
|
<cfset value=#ATTRIBUTES.value#/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset value=#ATTRIBUTES.container[ATTRIBUTES.name]#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.dformat" default="YYYY-MM-DD"/>
|
||||||
|
<cfparam name="ATTRIBUTES.tformat" default="HH:MM"/>
|
||||||
|
<cfparam name="ATTRIBUTES.size" default="#len(ATTRIBUTES.dformat)+len(ATTRIBUTES.tformat)+1#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.timeFirst" type="boolean" default="No"/>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.timeFirst>
|
||||||
|
<cfset value="#timeFormat(value, ATTRIBUTES.tformat)# #dateFormat(value, ATTRIBUTES.dformat)#"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset value="#dateFormat(value, ATTRIBUTES.dformat)# #timeFormat(value, ATTRIBUTES.tformat)#"/>
|
||||||
|
</cfif>
|
||||||
|
<cfset attributeList="name,value,container,suffix,id,dformat,tformat,size,disabled"/>
|
||||||
|
|
||||||
|
</cfsilent><cfoutput><input type="text" name="#ATTRIBUTES.name#"<cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#value#" size="#ATTRIBUTES.size#"<cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||||
|
passthrough additional attributes
|
||||||
|
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||||
|
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||||
|
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<!---moved to layout--->
|
||||||
|
<!---<cfif thisTag.executionMode IS "end" OR NOT thisTag.hasEndTag>
|
||||||
|
<cfparam name="ATTRIBUTES.backUrl" type="string" default="."/>
|
||||||
|
<cfparam name="ATTRIBUTES.enable" type="boolean" default="No"/>
|
||||||
|
<cfoutput>
|
||||||
|
<div class="row" style="margin:0px;">
|
||||||
|
<div class="col-sm-3" style="padding: 0px;">
|
||||||
|
<button type="submit" name="save" class="btn btn-secondary" style="width: 100%; padding: 6px 0px;"<cfif NOT ATTRIBUTES.enable> disabled</cfif>>#request.i18("Сохранить","Save")#</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-6" style="padding: 0px 5px;">
|
||||||
|
<button type="submit" name="saveAndClose" class="btn btn-secondary" style="width: 100%; padding: 6px 0px;"<cfif NOT ATTRIBUTES.enable> disabled</cfif>>#request.i18("Сохранить и закрыть","Save and Close")#</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3" style="padding: 0px;">
|
||||||
|
<button type="button" name="close" class="btn btn-secondary" style="width: 100%; padding: 6px 0px;" onClick="document.location.href='#ATTRIBUTES.backUrl#'">#request.i18("Закрыть","Close")#</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</cfoutput>
|
||||||
|
</cfif>--->
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.titleMap" default=#structNew()#/>
|
||||||
|
<cfparam name="ATTRIBUTES.sortArray" default=#arrayNew(1)#/>
|
||||||
|
|
||||||
|
<cfset attributeList="titleMap,sortArray"/>
|
||||||
|
|
||||||
|
<cfset extraAttributes=structNew()/>
|
||||||
|
<cfloop collection=#ATTRIBUTES# item="attr">
|
||||||
|
<cfif NOT listFindNoCase(attributeList, attr)>
|
||||||
|
<cfset val="#structFind(ATTRIBUTES, attr)#"/>
|
||||||
|
<cfif IsSimpleValue(val)>
|
||||||
|
<cfset structInsert(extraAttributes, attr, val)/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
</cfsilent><!---
|
||||||
|
---><cfif thisTag.executionMode is "start"><tr><cfelseif thisTag.executionMode is "end"></tr></cfif>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.canWrite" type="boolean" default="Yes"/>
|
||||||
|
<cfif NOT ATTRIBUTES.canWrite>
|
||||||
|
<cfexit method="exitTag"/>
|
||||||
|
</cfif>
|
||||||
|
<cfparam name="ATTRIBUTES.entity"/>
|
||||||
|
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.extra" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.fwx"/>
|
||||||
|
<cfparam name="ATTRIBUTES.title" default="создать"/>
|
||||||
|
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#.cfm?#ATTRIBUTES.key#=-1<cfif len(ATTRIBUTES.extra)>&#ATTRIBUTES.extra#</cfif>&#ATTRIBUTES.fwx#" title="#ATTRIBUTES.title#" class="add"></a></cfoutput><cfexit method="exitTag"/>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.canWrite" type="boolean" default="Yes"/>
|
||||||
|
<cfif NOT ATTRIBUTES.canWrite>
|
||||||
|
<cfexit method="exitTag"/>
|
||||||
|
</cfif>
|
||||||
|
<cfparam name="ATTRIBUTES.entity"/>
|
||||||
|
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.fwx"/>
|
||||||
|
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#_del.cfm?#ATTRIBUTES.key#=#ATTRIBUTES.id#&#ATTRIBUTES.fwx#" title="удалить" name="#ATTRIBUTES.id#" class="del"></a></cfoutput><cfexit method="exitTag"/>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<!---***deprecated. Use link_view_edit---><cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.canWrite" type="boolean" default="Yes"/>
|
||||||
|
<cfparam name="ATTRIBUTES.entity"/>
|
||||||
|
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.fwx"/>
|
||||||
|
<cfparam name="ATTRIBUTES.title_edit" default="редактировать"/>
|
||||||
|
<cfparam name="ATTRIBUTES.title_view" default="просмотреть"/>
|
||||||
|
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#.cfm?#ATTRIBUTES.key#=#ATTRIBUTES.id#&#ATTRIBUTES.fwx#" name="#ATTRIBUTES.id#" <cfif ATTRIBUTES.canWrite>title="#ATTRIBUTES.title_edit#" class="edit"<cfelse>title="#ATTRIBUTES.title_view#" class="view"</cfif>></a></cfoutput><cfexit method="exitTag"/>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.entity"/>
|
||||||
|
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.fwx"/>
|
||||||
|
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#.cfm?#ATTRIBUTES.key#=#ATTRIBUTES.id#&#ATTRIBUTES.fwx#" title="просмотреть" name="#ATTRIBUTES.id#" class="view"></a></cfoutput><cfexit method="exitTag"/>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.canWrite" type="boolean" default="Yes"/>
|
||||||
|
<cfparam name="ATTRIBUTES.entity"/>
|
||||||
|
<cfparam name="ATTRIBUTES.key" default="#ATTRIBUTES.entity#_id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.fwx"/>
|
||||||
|
<cfparam name="ATTRIBUTES.title_edit" default="редактировать"/>
|
||||||
|
<cfparam name="ATTRIBUTES.title_view" default="просмотреть"/>
|
||||||
|
</cfsilent><cfoutput><a href="#ATTRIBUTES.entity#.cfm?#ATTRIBUTES.key#=#ATTRIBUTES.id#&#ATTRIBUTES.fwx#" name="#ATTRIBUTES.id#" <cfif ATTRIBUTES.canWrite>title="#ATTRIBUTES.title_edit#" class="edit"<cfelse>title="#ATTRIBUTES.title_view#" class="view"</cfif>></a></cfoutput><cfexit method="exitTag"/>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<cfprocessingdirective suppressWhitespace="YES"><cfsilent>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.page" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.queryString" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.acl" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.label" type="string" default=""/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(#ATTRIBUTES#,"external")>
|
||||||
|
<cfset class="menu-item-ext"/>
|
||||||
|
<cfset target=' target="_blank"'/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset class="menu-item"/>
|
||||||
|
<cfset target=''/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfsilent><!---
|
||||||
|
---><cfmodule template="../ac.cfm" obj="#ATTRIBUTES.acl#"><cfif NOT (request.thisPage EQ ATTRIBUTES.page)><cfsilent>
|
||||||
|
<cfif len(ATTRIBUTES.queryString)>
|
||||||
|
<cfset pageUrl="#ATTRIBUTES.page#?#ATTRIBUTES.queryString#"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset pageUrl="#ATTRIBUTES.page#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfsilent><cfoutput><li class="#class#"><a href="#pageUrl#"#target#>#ATTRIBUTES.label#</a></li></cfoutput>
|
||||||
|
<cfelse><cfoutput><li class="selected">#ATTRIBUTES.label#</li></cfoutput></cfif><!---
|
||||||
|
---></cfmodule></cfprocessingdirective><cfexit method="EXITTAG"/>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.targetid"/>
|
||||||
|
<cfparam name="ATTRIBUTES.enabled" default="Yes"/>
|
||||||
|
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.title" default="Сейчас"/>
|
||||||
|
</cfsilent><cfoutput><button onclick="document.getElementById(#ATTRIBUTES.targetid#).value=""></</cfoutput><cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.n" type="numeric"/>
|
||||||
|
<cfset attributeList="n,sortArray"/>
|
||||||
|
|
||||||
|
<cfset img_up="arr_up.png"/>
|
||||||
|
<cfset img_dn="arr_dn.png"/>
|
||||||
|
<cfset HIGHLIGHTED_CNT=2/>
|
||||||
|
|
||||||
|
<cfif isDefined("ATTRIBUTES.sortArray")>
|
||||||
|
<cfset i=1>
|
||||||
|
<cfloop condition="i LE HIGHLIGHTED_CNT AND i LE arrayLen(ATTRIBUTES.sortArray)">
|
||||||
|
<cftry>
|
||||||
|
<cfif ATTRIBUTES.sortArray[i].fld EQ ATTRIBUTES.n>
|
||||||
|
<cfif ATTRIBUTES.sortArray[i].asc>
|
||||||
|
<cfset img_up="arr_up#i#.png">
|
||||||
|
<cfelse>
|
||||||
|
<cfset img_dn="arr_dn#i#.png">
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch type="Any"></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfset i=i+1>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset extra_query_string=""/>
|
||||||
|
<cfloop collection=#ATTRIBUTES# item="attr">
|
||||||
|
<cfif NOT listFindNoCase(attributeList,attr)>
|
||||||
|
<cfset val="#structFind(ATTRIBUTES,attr)#"/>
|
||||||
|
<cfif IsSimpleValue(val)>
|
||||||
|
<cfset extra_query_string="#extra_query_string#&#attr#=#val#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
</cfsilent><cfoutput><a href="?sort=#ATTRIBUTES.n#a#extra_query_string#" class="noprint" style="padding:2px;" title="по возрастанию"><img src="img/#img_up#" alt="по возрастанию"></a><a href="?sort=#ATTRIBUTES.n#d#extra_query_string#" title="по убыванию"><img src="img/#img_dn#" alt="по убыванию"></a></cfoutput><cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
<cfparam name="nStart" type="integer" default="1"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.recordCount" type="integer"/>
|
||||||
|
<cfparam name="ATTRIBUTES.recordsPerPage" type="integer"/>
|
||||||
|
<cfparam name="ATTRIBUTES.thisPage" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.addtionalUrlParams" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.self" type="string" default=""/>
|
||||||
|
<!---<cfparam name="ATTRIBUTES.fwx" type="string" default=""/>--->
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string" default="paginator"/>
|
||||||
|
|
||||||
|
<!--- Исключение: рассчитанный индекс страницы превышает количество существующих страниц - надо возвращаться на первую --->
|
||||||
|
|
||||||
|
<cfset nPages = Int((ATTRIBUTES.recordCount-1)/ATTRIBUTES.recordsPerPage)+1/>
|
||||||
|
<cfset nCurrentPage = Int((nStart-1)/ATTRIBUTES.recordsPerPage)+1/>
|
||||||
|
<cfif nStart GT ATTRIBUTES.recordCount>
|
||||||
|
<cfset nStart=1/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset UrlSuffix=""/>
|
||||||
|
<cfif len(ATTRIBUTES.addtionalUrlParams) GT 0>
|
||||||
|
<cfset UrlSuffix="&#ATTRIBUTES.addtionalUrlParams#"/>
|
||||||
|
</cfif>
|
||||||
|
<cfif len(ATTRIBUTES.self) GT 0>
|
||||||
|
<cfset UrlSuffix="&track=#ATTRIBUTES.self#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset somePages=3/>
|
||||||
|
|
||||||
|
<cfsavecontent variable="pages">
|
||||||
|
<cfif ATTRIBUTES.recordCount GT ATTRIBUTES.recordsPerPage>
|
||||||
|
<cfset doMerge = false/>
|
||||||
|
<cfloop index="i" from="1" to="#nPages#">
|
||||||
|
<cfset nPageStartRecordNum=(i-1)*ATTRIBUTES.recordsPerPage+1>
|
||||||
|
<!---
|
||||||
|
схема, которая обеспечивает дихотомический поиск
|
||||||
|
первые несколько страниц (не менее 2)
|
||||||
|
если есть разрыв - многоточие
|
||||||
|
посередине
|
||||||
|
если есть разрыв - многоточие
|
||||||
|
несколько страниц до текушей
|
||||||
|
текущую без ссылки
|
||||||
|
несколько страниц после текущей
|
||||||
|
если есть разрыв - многоточие
|
||||||
|
посередине
|
||||||
|
если есть разрыв - многоточие
|
||||||
|
последние несколько страниц
|
||||||
|
--->
|
||||||
|
<!---Можно сделать на многоточии ссылку на середину интервала!--->
|
||||||
|
<!---Хочется добиться, чтобы при хождении вперед-назад значения повторялись--->
|
||||||
|
<!---забавный эффект, когда остается разрыв в 1 страницу, а там все равно многоточие--->
|
||||||
|
<cfoutput>
|
||||||
|
<cfif i NEQ nCurrentPage>
|
||||||
|
<cfif i GT somePages AND (nCurrentPage-i) GT somePages>
|
||||||
|
<cfif NOT doMerge><!---*** неуклюже--->
|
||||||
|
<cfset nMidPage = ceiling(nCurrentPage/2)/>
|
||||||
|
<cfset nMidStartRecord = (nMidPage-1)*ATTRIBUTES.recordsPerPage+1/>
|
||||||
|
<a href="#ATTRIBUTES.thisPage#?nStart=#nMidStartRecord##UrlSuffix#" title="#nMidPage#">...</a> <cfset doMerge=true/>
|
||||||
|
</cfif>
|
||||||
|
<cfelseif (i-nCurrentPage) GT somePages AND (nPages-i) GT somePages>
|
||||||
|
<cfif NOT doMerge>
|
||||||
|
<cfset nMidPage = ceiling((nCurrentPage+nPages)/2)/>
|
||||||
|
<cfset nMidStartRecord = (nMidPage-1)*ATTRIBUTES.recordsPerPage+1/>
|
||||||
|
<a href="#ATTRIBUTES.thisPage#?nStart=#nMidStartRecord##UrlSuffix#" title="#nMidPage#">...</a> <cfset doMerge=true/>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<a href="#ATTRIBUTES.thisPage#?nStart=#nPageStartRecordNum##UrlSuffix#">#i#</a>
|
||||||
|
<cfset doMerge=false/>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<b>#i#</b>
|
||||||
|
</cfif>
|
||||||
|
</cfoutput>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
</cfsavecontent>
|
||||||
|
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.output#.links"=#pages#/>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.output#.recordsPerPage"=#ATTRIBUTES.recordsPerPage#/>
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<cfimport prefix="c" taglib="."/>
|
||||||
|
<cfif thisTag.executionMode IS "end">
|
||||||
|
<!--- <cfloop collection=#CALLER# item="item">
|
||||||
|
<cfset structInsert(variables, item, CALLER[item], false)/>
|
||||||
|
</cfloop> --->
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.query" type="query"/>
|
||||||
|
<cfparam name="ATTRIBUTES.recordsPerPage" type="integer" default="500"/>
|
||||||
|
<cfparam name="ATTRIBUTES.nStart" type="integer" default="1"/>
|
||||||
|
<cfparam name="ATTRIBUTES.titleMap" type="struct" default=srtuctNew()/><!---for definition see: field_set.cfm--->
|
||||||
|
<cfparam name="ATTRIBUTES.sortArray" type="array" default=arrayNew(1)/>
|
||||||
|
|
||||||
|
<cfset tableAttributeList="query,recordsPerPage,nStart,titleMap,sortArray"/>
|
||||||
|
<cfset colAttributeList="lang,th,td,content,field,formatter,sortable,visible,columnOrder"/>
|
||||||
|
<cfset tdAttributeList="content,field"/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfparam name="thisTag.columns" type="array"/>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name=reorderColumns output="No">
|
||||||
|
<!--- Вариант сортировки. Вынимаем все элементы с указанным порядком в отдельную коллекцию,
|
||||||
|
после чего вставляем их в оставшуюся коллекцию на указанные позиции.
|
||||||
|
нет смысла делать вид, что мы реализуем что-то универсальное
|
||||||
|
(поле для сортировки в совершенно конкретном поле структуры в массиве),
|
||||||
|
поэтому ничего не передаем в аргументах, спокойно ссылаемся на окружение--->
|
||||||
|
<cfset var tmpOrderedIndexes=ArrayNew(1)/>
|
||||||
|
<cfset var tmpUnorderedIndexes=ArrayNew(1)/>
|
||||||
|
|
||||||
|
<cfloop from=1 to=#arrayLen(thisTag.columns)# index="i">
|
||||||
|
<cfif thisTag.columns[i].columnOrder GT 0>
|
||||||
|
<cfset arrayAppend(tmpOrderedIndexes,i)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset arrayAppend(tmpUnorderedIndexes,i)/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfloop from=#arrayLen(tmpOrderedIndexes)# to=1 step=-1 index="i">
|
||||||
|
<!--- counting up-down preserves entry order for equal columnOrders --->
|
||||||
|
<cfset var pos=max(min(thisTag.columns[tmpOrderedIndexes[i]].columnOrder, len(tmpUnorderedIndexes)+1),1)/>
|
||||||
|
<cfset arrayInsertAt(tmpUnorderedIndexes,pos,tmpOrderedIndexes[i])/>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset var sortedColumns=ArrayNew(1)/>
|
||||||
|
<cfloop from=1 to=#arrayLen(tmpUnorderedIndexes)# index="i">
|
||||||
|
<cfset sortedColumns[i]=thisTag.columns[tmpUnorderedIndexes[i]]/>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfreturn sortedColumns/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cfset sortedColumns=reorderColumns()/>
|
||||||
|
|
||||||
|
<cfset thisTag.generatedContent=""/>
|
||||||
|
|
||||||
|
<table<!--- passthrough additional attributes
|
||||||
|
---><cfoutput><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||||
|
---><cfif NOT listFindNoCase(tableAttributeList,attr)<!--- AND isSimpleValue(structFind(ATTRIBUTES,attr))--->> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||||
|
---></cfloop></cfoutput>>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<cfloop array=#sortedColumns# index="column">
|
||||||
|
<cfset th=column.th[1]/>
|
||||||
|
<th<!--- атрибуты колонки передаются в TH, хотя это выглядит не очень нужным --->
|
||||||
|
<cfloop collection=#column# item="colAttr">
|
||||||
|
<cfif NOT listFindNoCase(colAttributeList,colAttr)><cfoutput>#colAttr#="#structFind(column,colAttr)#" </cfoutput></cfif>
|
||||||
|
</cfloop>>
|
||||||
|
<cfif len(column.field) AND column.sortable>
|
||||||
|
<c:order_arrows sortArray=#ATTRIBUTES.sortArray# n=#ATTRIBUTES.titleMap[column.field].ordinal#<!--- attributeCollection=#extraAttributes#--->/>
|
||||||
|
</cfif>
|
||||||
|
<cfif len(th.content)>
|
||||||
|
<cfset Evaluate("WriteOutput('#th.content#')")/>
|
||||||
|
<cfelseif len(column.field)>
|
||||||
|
<cfif structKeyExists(ATTRIBUTES.titleMap[column.field],"title")>
|
||||||
|
<cfoutput>#ATTRIBUTES.titleMap[column.field].title#</cfoutput>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</th>
|
||||||
|
</cfloop>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<tbody>
|
||||||
|
<cfoutput query=#ATTRIBUTES.query# maxrows=#ATTRIBUTES.recordsPerPage# startrow=#ATTRIBUTES.nStart#>
|
||||||
|
<tr>
|
||||||
|
<cfloop array=#sortedColumns# index="column">
|
||||||
|
<cfset td=column.td[1]/>
|
||||||
|
<td<!--- Если нужно передать атрибут в TD, можно ввести тег <C:TD class="r"/> только как плейсхолдер для атрибутов, а поле будет получено от колонки --->
|
||||||
|
<cfloop collection=#td# item="tdAttr">
|
||||||
|
<cfif NOT listFindNoCase(tdAttributeList,tdAttr)>#tdAttr#="#structFind(td,tdAttr)#" </cfif>
|
||||||
|
</cfloop>
|
||||||
|
><cfif len(td.content)><cfset Evaluate("WriteOutput('#td.content#')")/><!---#td.content#--->
|
||||||
|
<cfelseif len(td.field)>#ATTRIBUTES.query[td.field][currentRow]#
|
||||||
|
<cfelseif len(column.field)>#column.formatter(ATTRIBUTES.query[column.field][currentRow])#
|
||||||
|
</cfif></td>
|
||||||
|
</cfloop>
|
||||||
|
</tr>
|
||||||
|
</cfoutput>
|
||||||
|
</tbody>
|
||||||
|
|
||||||
|
</table>
|
||||||
|
<!--- <cfdump var=#variables#/> --->
|
||||||
|
</cfif>
|
||||||
|
<!--- <cfexit method="exitTag"/> --->
|
||||||
|
|
||||||
|
<cfscript>
|
||||||
|
/**
|
||||||
|
* Sorts an array of structures based on a key in the structures.
|
||||||
|
*
|
||||||
|
* @param aofS Array of structures.
|
||||||
|
* @param key Key to sort by.
|
||||||
|
* @param sortOrder Order to sort by, asc or desc.
|
||||||
|
* @param sortType Text, textnocase, or numeric.
|
||||||
|
* @param delim Delimiter used for temporary data storage. Must not exist in data. Defaults to a period.
|
||||||
|
* @return Returns a sorted array.
|
||||||
|
* @author Nathan Dintenfass (nathan@changemedia.com)
|
||||||
|
* @version 1, December 10, 2001
|
||||||
|
*/
|
||||||
|
function arrayOfStructsSort(aOfS,key){
|
||||||
|
//by default we'll use an ascending sort
|
||||||
|
var sortOrder = "asc";
|
||||||
|
//by default, we'll use a textnocase sort
|
||||||
|
var sortType = "textnocase";
|
||||||
|
//by default, use ascii character 30 as the delim
|
||||||
|
var delim = ".";
|
||||||
|
//make an array to hold the sort stuff
|
||||||
|
var sortArray = arraynew(1);
|
||||||
|
//make an array to return
|
||||||
|
var returnArray = arraynew(1);
|
||||||
|
//grab the number of elements in the array (used in the loops)
|
||||||
|
var count = arrayLen(aOfS);
|
||||||
|
//make a variable to use in the loop
|
||||||
|
var ii = 1;
|
||||||
|
//if there is a 3rd argument, set the sortOrder
|
||||||
|
if(arraylen(arguments) GT 2)
|
||||||
|
sortOrder = arguments[3];
|
||||||
|
//if there is a 4th argument, set the sortType
|
||||||
|
if(arraylen(arguments) GT 3)
|
||||||
|
sortType = arguments[4];
|
||||||
|
//if there is a 5th argument, set the delim
|
||||||
|
if(arraylen(arguments) GT 4)
|
||||||
|
delim = arguments[5];
|
||||||
|
//loop over the array of structs, building the sortArray
|
||||||
|
for(ii = 1; ii lte count; ii = ii + 1)
|
||||||
|
sortArray[ii] = aOfS[ii][key] & delim & ii;
|
||||||
|
//now sort the array
|
||||||
|
arraySort(sortArray,sortType,sortOrder);
|
||||||
|
//now build the return array
|
||||||
|
for(ii = 1; ii lte count; ii = ii + 1)
|
||||||
|
returnArray[ii] = aOfS[listLast(sortArray[ii],delim)];
|
||||||
|
//return the array
|
||||||
|
return returnArray;
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<cfif thisTag.executionMode IS "end">
|
||||||
|
<!--- <cfloop collection=#CALLER# item="item">
|
||||||
|
<cfset structInsert(variables, item, CALLER[item], false)/>
|
||||||
|
</cfloop> --->
|
||||||
|
|
||||||
|
<cfassociate basetag="cf_column" datacollection="td"/>
|
||||||
|
<cfset attributeList="field"/>
|
||||||
|
<cfparam name="ATTRIBUTES.field" default=""/>
|
||||||
|
<cfset ATTRIBUTES.content=thisTag.generatedContent/>
|
||||||
|
</cfif>
|
||||||
|
<!---<cfparam name="ATTRIBUTES.lang" type="string" default=""/>
|
||||||
|
<cfparam name="request.language" type="string" default=""/>
|
||||||
|
<cfif len(ATTRIBUTES.lang) AND #ATTRIBUTES.lang# NEQ #request.language#>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>--->
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.name"/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ATTRIBUTES,"value")>
|
||||||
|
<cfset value=#ATTRIBUTES.value#/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset value=#ATTRIBUTES.container[ATTRIBUTES.name]#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.size" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||||
|
<cfset attributeList="name,value,container,suffix,id,size,disabled"/>
|
||||||
|
|
||||||
|
</cfsilent><cfoutput><input type="text" name="#ATTRIBUTES.name#"<cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#value#"<cfif len(ATTRIBUTES.size)> size="#ATTRIBUTES.size#"</cfif><cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||||
|
passthrough additional attributes
|
||||||
|
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||||
|
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||||
|
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<cfif thisTag.executionMode IS "end">
|
||||||
|
<cfassociate basetag="cf_column" datacollection="th"/>
|
||||||
|
<cfset attributeList="field"/>
|
||||||
|
<cfparam name="ATTRIBUTES.field" default=""/>
|
||||||
|
<cfset ATTRIBUTES.content=thisTag.generatedContent/>
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.name"/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ATTRIBUTES,"value")>
|
||||||
|
<cfset value=#ATTRIBUTES.value#/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset value=#ATTRIBUTES.container[ATTRIBUTES.name]#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.suffix" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.id" default="#ATTRIBUTES.name##ATTRIBUTES.suffix#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.format" default="HH:MM"/>
|
||||||
|
<cfparam name="ATTRIBUTES.size" default="#len(ATTRIBUTES.format)#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.disabled" type="boolean" default="No"/>
|
||||||
|
<cfset attributeList="name,value,container,suffix,id,format,size,disabled"/>
|
||||||
|
|
||||||
|
</cfsilent><cfoutput><input type="text" name="#ATTRIBUTES.name#"<cfif len(ATTRIBUTES.id)> id=#ATTRIBUTES.id#</cfif> value="#timeFormat(value, ATTRIBUTES.format)#" size="#ATTRIBUTES.size#"<cfif ATTRIBUTES.disabled> disabled="disabled"</cfif><!---
|
||||||
|
passthrough additional attributes
|
||||||
|
---><cfloop collection=#ATTRIBUTES# item="attr"><!---
|
||||||
|
---><cfif NOT listFindNoCase(attributeList,attr)> #attr#="#structFind(ATTRIBUTES,attr)#"</cfif><!---
|
||||||
|
---></cfloop>/></cfoutput><cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,401 @@
|
|||||||
|
<!---
|
||||||
|
bean v0.96
|
||||||
|
msyu@mail.ru
|
||||||
|
2013-08-29 v0.1
|
||||||
|
2013-09-04 v0.2 tested with CF8 and Railo
|
||||||
|
2013-10-14 added meta attribute - can now export each parameter metadata
|
||||||
|
2014-12-26 POST-GET read-pass-save
|
||||||
|
2015-01-19 read-pass-save, добавлен formmarker, убрано определение режима по POST-GET
|
||||||
|
2015-01-19 убран formmarker, упразднен режим init (используется pass)
|
||||||
|
2015-07-16 добавлен passmarker для перерисовки формы с введенными значениями
|
||||||
|
2015-10-02 поддержка составных ключей
|
||||||
|
2015-10-09 v0.9 если задано value параметра, считается, что он hasInput (корректно работает в составе ключа)
|
||||||
|
2015-11-25 добавлен readmarker, mode (принудительная установка режима)
|
||||||
|
2015-11-26 добавлен mode=skip
|
||||||
|
2016-09-01 изменена трактовка processingMode. Теперь понимается как исходная команда, фиксируется до начала действий. Можно использовать для управления
|
||||||
|
2018-09-16 добавлена ограниченная поддержка нецелых первичных ключей (idstamp)
|
||||||
|
child tag: param
|
||||||
|
to save - should send parameter with name from list ATTRIBUTES.savemarker (value doesn't matter) in FORM, e.g. URL.save="trtrtr", while ATTRIBUTES.savemarker="save,saveAndClose"; for multiple forms on a page (and bean tags) different savemarkers should be used
|
||||||
|
2022-01-06 PostgreSQL (MSSQL incompatibility because of scope_identity)
|
||||||
|
2024-04-13 PostgreSQL GUID, autoincrement by RETURNING
|
||||||
|
2024-09-05 added status.recordExists (*** при вставке принудительно устанавливаем в true, веря, что если insert прошел, то запись появилась)
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Автогенерируемое поле GUID можно заполнять несколькими способами
|
||||||
|
- как default или init bean/param, но это не очень удобно, потому что может потеряться удобный признак новой записи - инвалидный ключ, и сейчас init-default взаимодействуют некорректно, по крайней мере, неочевидно
|
||||||
|
- генерируемое в БД (например, как default). Для PostgreSQL значение возвращается через Returning, как это делать для других БД, непонятно.
|
||||||
|
- можно генерировать при сохранении в bean, но для этого нужно расширение синтаксиса, зато не зависит от БД (*** TODO)
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfif thisTag.ExecutionMode is 'end'>
|
||||||
|
<cfparam name="ATTRIBUTES.readmarker" type="string" default="read"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда перечитывания. Имеет приоритет перед savemarker, passmarker. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.savemarker" type="string" default="save,saveAndClose"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда сохранения. Обычно это имя кнопки. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.passmarker" type="string" default="pass"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда заполнить форму отправленными данными без перечитывания. В форме такое поле обычно есть. Команда сохранения имеет приоритет перед пробросом. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.datasource" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.table" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string"/><!--- имя переменной (структуры) области CALLER, в которую возвращаются данные. Обычно "d" --->
|
||||||
|
<cfparam name="ATTRIBUTES.info" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.meta" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.status" type="string"/><!--- имя переменной (структуры) области CALLER, в которую возвращается статуc. Обычно "status" --->
|
||||||
|
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.mode" type="string" default=""/> <!--- Принудительный режим read|pass|save|skip. Имеет приоритет перед маркерами --->
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.mode EQ "skip">
|
||||||
|
<!---<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorState", false) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorMessage", "") />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.processingMode", "skip") />--->
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(false,"","skip",false))/><!--- *** можно было не создавать компонент, он все равно не имеет логики, это map из нескольких полей. С другой стороны, хоть поля декларированы. Не очень удобно, что в отдельном файле --->
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset validationOk="true" />
|
||||||
|
<cfset errorState="false" />
|
||||||
|
<cfset errorMessage="" />
|
||||||
|
<cfset warningState="false" />
|
||||||
|
<cfset warningMessage="" />
|
||||||
|
<!---<cfset keyField=""/>
|
||||||
|
<cfset keyValue=""/>--->
|
||||||
|
<cfset hasAutoIncrement="false"/>
|
||||||
|
<cfset autoIncrementKeyIndex=0/><!--- сгенерированное значение присваивается первому полю, отмеченному как autoincrement --->
|
||||||
|
<cfset recordExists = "false"/>
|
||||||
|
<cfset keyInvalid = "false"/>
|
||||||
|
|
||||||
|
<!--- разбор метаданных - поиск ключа --->
|
||||||
|
<!--- поддерживаются только простые целочисленные ключи --->
|
||||||
|
<cfset keys=arrayNew(1)/>
|
||||||
|
<!--- *** почему где-то копируем поля param в key, а где-то прямо цикл по параметрам --->
|
||||||
|
<!--- коллекция thistag.params формируется вложенными тегами param --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!---<cfdump var=#param#/>--->
|
||||||
|
<cfif structFind(param,"key")><!--- not just structKeyExists! Not exisis, OR exists, but FALSE! --->
|
||||||
|
<cfset key = structNew()/>
|
||||||
|
<cfset ArrayAppend(keys, key)/>
|
||||||
|
|
||||||
|
<cfset key.name = param.param />
|
||||||
|
<cfset key.field = param.field />
|
||||||
|
<cfset key.type = param.type />
|
||||||
|
<cfset key.value = param.value /><!--- должно уже быть присвоено --->
|
||||||
|
<cfset key.null = param.null />
|
||||||
|
|
||||||
|
<cfif param.errorState OR !param.hasInput>
|
||||||
|
<cfset keyInvalid="true"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset keyInvalid=!checkDataFormat(param.type, param.value)/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif !hasAutoIncrement><!--- take only first field with "autoincrement" attribute--->
|
||||||
|
<cfif structFind(param, "autoincrement")>
|
||||||
|
<cfset hasAutoIncrement = true/>
|
||||||
|
<cfset autoIncrementKeyIndex = arrayLen(keys)/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<!---<cfdump var=#keys#/>--->
|
||||||
|
|
||||||
|
<cfif !keyInvalid>
|
||||||
|
<!--- *** дублируется похожий cfqueryparam, перепутал один раз --->
|
||||||
|
<cfquery name="qCheckExistence" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
select count(*) as cnt
|
||||||
|
from #ATTRIBUTES.table#
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
<cfset recordExists = (qCheckExistence.cnt GT 0)/>
|
||||||
|
<!--- <cfdump var=#qCheckExistence#/> --->
|
||||||
|
<cfelse>
|
||||||
|
<cfset recordExists = "false"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFindNoCase("read,pass,save", ATTRIBUTES.mode)>
|
||||||
|
<cfset processingMode=ATTRIBUTES.mode/>
|
||||||
|
|
||||||
|
<cfelse>
|
||||||
|
|
||||||
|
<!--- ищем команду принудительного перечитывания --->
|
||||||
|
<cfset doRead = false/>
|
||||||
|
<cfloop list=#ATTRIBUTES.readmarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doRead = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- ищем команду сохранения --->
|
||||||
|
<cfset doSave = false/>
|
||||||
|
<cfif !doRead AND !ATTRIBUTES.readonly>
|
||||||
|
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doSave = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- ищем команду проброса --->
|
||||||
|
<cfset doPass = false/>
|
||||||
|
<cfif !doRead AND !doSave>
|
||||||
|
<cfloop list=#ATTRIBUTES.passmarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doPass = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- определяем режим --->
|
||||||
|
<cfif doRead>
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
<cfelseif doSave>
|
||||||
|
<cfset processingMode="save"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfif doPass OR !recordExists>
|
||||||
|
<cfset processingMode="pass"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset initialProcessingMode = processingMode/>
|
||||||
|
|
||||||
|
|
||||||
|
<!---debug <cfoutput>processingMode:#processingMode#</cfoutput>--->
|
||||||
|
<!--- ---------------------------------------------------------- --->
|
||||||
|
|
||||||
|
<!---*** Необходимость вставки (против апдейта) определяется по существованию записи. Это небезопасно, поскольку наличие записи с ключом -1 может прекратить создание новых записей. Конечно, можно добавить констрейнт на ключевое поле, но это лишняя зависимость.
|
||||||
|
Можно явно передавать команду вставки, а не заморачиваться с анализом ключа. Просто традиционно мы сигнализировали необходимость вставки, передавая невалидное значение ключа, обычно -1. Для целочисленных ключей можно было бы просто проверять положительность (что всю жизнь отлично работало), но хочется более общего.
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfif processingMode IS "save"> <!--- validation lookup --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
|
||||||
|
<cfif !param.key>
|
||||||
|
<cfif param.warningState>
|
||||||
|
<cfif NOT warningState><!--- catch the first fault --->
|
||||||
|
<cfset warningMessage=param.warningMessage />
|
||||||
|
</cfif>
|
||||||
|
<cfset warningState="true" />
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif param.errorState>
|
||||||
|
<cfif NOT errorState><!--- catch the first fault --->
|
||||||
|
<cfset errorMessage=param.errorMessage />
|
||||||
|
</cfif>
|
||||||
|
<cfset errorState="true" />
|
||||||
|
<cfset validationOk="false"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif errorState>
|
||||||
|
<!--- clear warning state --->
|
||||||
|
<cfset warningMessage="" />
|
||||||
|
<cfset warningState=false />
|
||||||
|
<!--- finish lookup --->
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfif validationOk>
|
||||||
|
<!---<cfdump var=#param#/> --->
|
||||||
|
<cftry>
|
||||||
|
<cfif recordExists><!--- update. Наличие keyValue гарантировано (уверен) --->
|
||||||
|
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#" result="qSaveResult">
|
||||||
|
update #ATTRIBUTES.table# set
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!--- не обновляем ключевые поля --->
|
||||||
|
<cfif NOT (StructFind(param, "key")
|
||||||
|
OR StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipUpdate")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
#param.field#=<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
<!---<cfdump var=#qSaveResult#/>--->
|
||||||
|
<cfelse><!--- insert --->
|
||||||
|
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
insert into #ATTRIBUTES.table#
|
||||||
|
(
|
||||||
|
<cfset i=0 />
|
||||||
|
<!--- ключевые поля без автоинкремента попадают в insert! --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfif NOT (StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipInsert")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
#param.field#
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
)
|
||||||
|
values
|
||||||
|
(
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfif NOT (StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipInsert")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
)
|
||||||
|
<!--- /*MSSQL syntax*/
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
select scope_identity() as id;
|
||||||
|
</cfif>
|
||||||
|
--->
|
||||||
|
<!---*** PostgreSQL incompatible hack follows--->
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
returning #thistag.params[autoIncrementKeyIndex].field# as id;
|
||||||
|
</cfif>;
|
||||||
|
</cfquery>
|
||||||
|
<!--- <cfdump var=#qSave#/> --->
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
<cfset keys[autoIncrementKeyIndex].value=qSave.id/>
|
||||||
|
</cfif>
|
||||||
|
<!--- здесь можно установить recordExists=true, если твердо верить, что вставка прошла, если нет исключения --->
|
||||||
|
<cfset recordExists=true/>
|
||||||
|
</cfif>
|
||||||
|
<!--- recordExists --->
|
||||||
|
|
||||||
|
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
|
||||||
|
<cfcatch type="database">
|
||||||
|
<cfset errorState="true" />
|
||||||
|
<cfset errorMessage="Ошибка при записи в базу данных. #cfcatch.message# #cfcatch.detail#" />
|
||||||
|
<cfset processingMode="pass" />
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfelse><!--- (not) validationOk --->
|
||||||
|
<cfset processingMode="pass"/>
|
||||||
|
</cfif><!--- validationOk --->
|
||||||
|
|
||||||
|
</cfif><!--- processingMode EQ "save" --->
|
||||||
|
|
||||||
|
<!--- экспортируем данные --->
|
||||||
|
<cfswitch expression=#processingMode#>
|
||||||
|
<cfcase value="read">
|
||||||
|
<cfquery name="qRead" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
select
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset i=i+1/>
|
||||||
|
#param.field#<cfif i LT ArrayLen(thistag.params)>,</cfif>
|
||||||
|
</cfloop>
|
||||||
|
from #ATTRIBUTES.table#
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
/*#sqlType(key.type)#*/
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfloop array=#thistag.params# index="param"><!--- *** --->
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", structFind(qRead, #param.field#)) />
|
||||||
|
</cfloop>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
|
||||||
|
<cfcase value="pass"><!--- заполнить все поля. Если значения переданы в форме или урле, тег параметра обязан их разобрать и подставить, в противном случае подставить инит-значения. Инит-значение по умолчанию пустая строка, дефолт по умолчанию тоже пустая строка --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!--- <cfdump var=#param#/>--->
|
||||||
|
<cfif param.hasInput>
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.submittedValue) /><!---*** дефект: 03.12.2015, передаваемая строкой, при обработке dateFormat неправильно парсится --->
|
||||||
|
<cfelse>
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.init) />
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfdefaultcase>
|
||||||
|
</cfdefaultcase>
|
||||||
|
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.info)><!--- экспортируем дополнительные сведения--->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.submittedValue", #param.submittedValue#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorState", #param.errorState#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorMessage", #param.errorMessage#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningState", #param.warningState#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningMessage", #param.warningMessage#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.null", #param.null#) />
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.meta)><!--- экспортируем метаданные параметра - для инвариантности вызовов --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.param", #param.param#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.field", #param.field#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.type", #param.type#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.displayName", #param.displayName#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.default", #param.default#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.size", #param.size#) />
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(errorState,errorMessage,initialProcessingMode,recordExists))/>
|
||||||
|
|
||||||
|
</cfif><!--- thisTag.ExecutionMode is 'end'--->
|
||||||
|
|
||||||
|
<!--- quick and dirty --->
|
||||||
|
<cffunction name="sqlType">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="guid,uuid,idstamp"><cfreturn "CF_SQL_OTHER"/></cfcase><!--- hack for PostgreSQL uuid --->
|
||||||
|
<cfdefaultcase><cfreturn uCase("CF_SQL_#type#")/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="checkDataFormat">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
<cfargument name="value"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="integer"><cfreturn isValid("integer",value)/></cfcase>
|
||||||
|
<cfcase value="guid,uuid,idstamp"><cfreturn isValid("guid",value)/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn true/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<!--- //quick and dirty
|
||||||
|
function sqlType(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "guid,idstamp": return "CF_SQL_OTHER"; /*hack for PostgreSQL uuid*/ /*break not needed!*/
|
||||||
|
default: return uCase("CF_SQL_#type#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDataFormat(type,value) {
|
||||||
|
switch (type) {
|
||||||
|
case "integer": return isValid("integer",value); /*break not needed!*/
|
||||||
|
case "guid,uuid,idstamp": return isValid("guid",value); /*break not needed!*/ /*INCORRECT cfscript switch-case does not support lists*/
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
} --->
|
||||||
|
<cfscript>
|
||||||
|
//readonly может транслироваться в pass!
|
||||||
|
function translateMode(readonly) {
|
||||||
|
if (readonly) {
|
||||||
|
return "read";
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
@@ -0,0 +1,400 @@
|
|||||||
|
<!---
|
||||||
|
bean v0.95
|
||||||
|
msyu@mail.ru
|
||||||
|
2013-08-29 v0.1
|
||||||
|
2013-09-04 v0.2 tested with CF8 and Railo
|
||||||
|
2013-10-14 added meta attribute - can now export each parameter metadata
|
||||||
|
2014-12-26 POST-GET read-pass-save
|
||||||
|
2015-01-19 read-pass-save, добавлен formmarker, убрано определение режима по POST-GET
|
||||||
|
2015-01-19 убран formmarker, упразднен режим init (используется pass)
|
||||||
|
2015-07-16 добавлен passmarker для перерисовки формы с введенными значениями
|
||||||
|
2015-10-02 поддержка составных ключей
|
||||||
|
2015-10-09 v0.9 если задано value параметра, считается, что он hasInput (корректно работает в составе ключа)
|
||||||
|
2015-11-25 добавлен readmarker, mode (принудительная установка режима)
|
||||||
|
2015-11-26 добавлен mode=skip
|
||||||
|
2016-09-01 изменена трактовка processingMode. Теперь понимается как исходная команда, фиксируется до начала действий. Можно использовать для управления
|
||||||
|
2018-09-16 добавлена ограниченная поддержка нецелых первичных ключей (idstamp)
|
||||||
|
child tag: param
|
||||||
|
to save - should send parameter with name from list ATTRIBUTES.savemarker (value doesn't matter) in FORM, e.g. URL.save="trtrtr", while ATTRIBUTES.savemarker="save,saveAndClose"; for multiple forms on a page (and bean tags) different savemarkers should be used
|
||||||
|
2022-01-06 PostgreSQL (MSSQL incompatibility because of scope_identity)
|
||||||
|
2024-04-13 PostgreSQL GUID, autoincrement by RETURNING
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Автогенерируемое поле GUID можно заполнять несколькими способами
|
||||||
|
- как default или init bean/param, но это не очень удобно, потому что может потеряться удобный признак новой записи - инвалидный ключ, и сейчас init-default взаимодействуют некорректно, по крайней мере, неочевидно
|
||||||
|
- генерируемое в БД (например, как default). Для PostgreSQL значение возвращается через Returning, как это делать для других БД, непонятно.
|
||||||
|
- можно генерировать при сохранении в bean, но для этого нужно расширение синтаксиса, зато не зависит от БД (*** TODO)
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfif thisTag.ExecutionMode is 'end'>
|
||||||
|
<cfparam name="ATTRIBUTES.readmarker" type="string" default="read"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда перечитывания. Имеет приоритет перед savemarker, passmarker. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.savemarker" type="string" default="save,saveAndClose"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда сохранения. Обычно это имя кнопки. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.passmarker" type="string" default="pass"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда заполнить форму отправленными данными без перечитывания. В форме такое поле обычно есть. Команда сохранения имеет приоритет перед пробросом. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.datasource" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.table" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.info" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.meta" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.status" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.mode" type="string" default=""/> <!--- Принудительный режим read|pass|save|skip. Имеет приоритет перед маркерами --->
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.mode EQ "skip">
|
||||||
|
<!---<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorState", false) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorMessage", "") />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.processingMode", "skip") />--->
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(false,"","skip"))/>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset validationOk="true" />
|
||||||
|
<cfset errorState="false" />
|
||||||
|
<cfset errorMessage="" />
|
||||||
|
<cfset warningState="false" />
|
||||||
|
<cfset warningMessage="" />
|
||||||
|
<!---<cfset keyField=""/>
|
||||||
|
<cfset keyValue=""/>--->
|
||||||
|
<cfset hasAutoIncrement="false"/>
|
||||||
|
<cfset autoIncrementKeyIndex=0/><!--- сгенерированное значение присваивается первому полю, отмеченному как autoincrement --->
|
||||||
|
<cfset recordExists = "false"/>
|
||||||
|
<cfset keyInvalid = "false"/>
|
||||||
|
|
||||||
|
<!--- разбор метаданных - поиск ключа --->
|
||||||
|
<!--- поддерживаются только простые целочисленные ключи --->
|
||||||
|
<cfset keys=arrayNew(1)/>
|
||||||
|
<!--- *** почему где-то копируем поля param в key, а где-то прямо цикл по параметрам --->
|
||||||
|
<!--- коллекция thistag.params формируется вложенными тегами param --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!---<cfdump var=#param#/>--->
|
||||||
|
<cfif structFind(param,"key")><!--- not just structKeyExists! Not exisis, OR exists, but FALSE! --->
|
||||||
|
<cfset key = structNew()/>
|
||||||
|
<cfset ArrayAppend(keys, key)/>
|
||||||
|
|
||||||
|
<cfset key.name = param.param />
|
||||||
|
<cfset key.field = param.field />
|
||||||
|
<cfset key.type = param.type />
|
||||||
|
<cfset key.value = param.value /><!--- должно уже быть присвоено --->
|
||||||
|
<cfset key.null = param.null />
|
||||||
|
|
||||||
|
<cfif param.errorState OR !param.hasInput>
|
||||||
|
<cfset keyInvalid="true"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset keyInvalid=!checkDataFormat(param.type, param.value)/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif !hasAutoIncrement><!--- take only first field with "autoincrement" attribute--->
|
||||||
|
<cfif structFind(param, "autoincrement")>
|
||||||
|
<cfset hasAutoIncrement = true/>
|
||||||
|
<cfset autoIncrementKeyIndex = arrayLen(keys)/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<!---<cfdump var=#keys#/>--->
|
||||||
|
|
||||||
|
<cfif !keyInvalid>
|
||||||
|
<!--- *** дублируется похожий cfqueryparam, перепутал один раз --->
|
||||||
|
<cfquery name="qCheckExistence" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
select count(*) as cnt
|
||||||
|
from #ATTRIBUTES.table#
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
<cfset recordExists = (qCheckExistence.cnt GT 0)/>
|
||||||
|
<!--- <cfdump var=#qCheckExistence#/> --->
|
||||||
|
<cfelse>
|
||||||
|
<cfset recordExists = "false"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFindNoCase("read,pass,save", ATTRIBUTES.mode)>
|
||||||
|
<cfset processingMode=ATTRIBUTES.mode/>
|
||||||
|
|
||||||
|
<cfelse>
|
||||||
|
|
||||||
|
<!--- ищем команду принудительного перечитывания --->
|
||||||
|
<cfset doRead = false/>
|
||||||
|
<cfloop list=#ATTRIBUTES.readmarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doRead = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- ищем команду сохранения --->
|
||||||
|
<cfset doSave = false/>
|
||||||
|
<cfif !doRead AND !ATTRIBUTES.readonly>
|
||||||
|
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doSave = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- ищем команду проброса --->
|
||||||
|
<cfset doPass = false/>
|
||||||
|
<cfif !doRead AND !doSave>
|
||||||
|
<cfloop list=#ATTRIBUTES.passmarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doPass = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- определяем режим --->
|
||||||
|
<cfif doRead>
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
<cfelseif doSave>
|
||||||
|
<cfset processingMode="save"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfif doPass OR !recordExists>
|
||||||
|
<cfset processingMode="pass"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset initialProcessingMode = processingMode/>
|
||||||
|
|
||||||
|
|
||||||
|
<!---debug <cfoutput>processingMode:#processingMode#</cfoutput>--->
|
||||||
|
<!--- ---------------------------------------------------------- --->
|
||||||
|
|
||||||
|
<!---*** Необходимость вставки (против апдейта) определяется по существованию записи. Это небезопасно, поскольку наличие записи с ключом -1 может прекратить создание новых записей. Конечно, можно добавить констрейнт на ключевое поле, но это лишняя зависимость.
|
||||||
|
Можно явно передавать команду вставки, а не заморачиваться с анализом ключа. Просто традиционно мы сигнализировали необходимость вставки, передавая невалидное значение ключа, обычно -1. Для целочисленных ключей можно было бы просто проверять положительность (что всю жизнь отлично работало), но хочется более общего.
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfif processingMode IS "save"> <!--- validation lookup --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
|
||||||
|
<cfif !param.key>
|
||||||
|
<cfif param.warningState>
|
||||||
|
<cfif NOT warningState><!--- catch the first fault --->
|
||||||
|
<cfset warningMessage=param.warningMessage />
|
||||||
|
</cfif>
|
||||||
|
<cfset warningState="true" />
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif param.errorState>
|
||||||
|
<cfif NOT errorState><!--- catch the first fault --->
|
||||||
|
<cfset errorMessage=param.errorMessage />
|
||||||
|
</cfif>
|
||||||
|
<cfset errorState="true" />
|
||||||
|
<cfset validationOk="false"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif errorState>
|
||||||
|
<!--- clear warning state --->
|
||||||
|
<cfset warningMessage="" />
|
||||||
|
<cfset warningState=false />
|
||||||
|
<!--- finish lookup --->
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfif validationOk>
|
||||||
|
<!---<cfdump var=#param#/> --->
|
||||||
|
<cftry>
|
||||||
|
<cfif recordExists><!--- update. Наличие keyValue гарантировано (уверен) --->
|
||||||
|
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#" result="qSaveResult">
|
||||||
|
update #ATTRIBUTES.table# set
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!--- не обновляем ключевые поля --->
|
||||||
|
<cfif NOT (StructFind(param, "key")
|
||||||
|
OR StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipUpdate")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
#param.field#=<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
<!---<cfdump var=#qSaveResult#/>--->
|
||||||
|
<cfelse><!--- insert --->
|
||||||
|
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
insert into #ATTRIBUTES.table#
|
||||||
|
(
|
||||||
|
<cfset i=0 />
|
||||||
|
<!--- ключевые поля без автоинкремента попадают в insert! --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfif NOT (StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipInsert")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
#param.field#
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
)
|
||||||
|
values
|
||||||
|
(
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfif NOT (StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipInsert")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
);
|
||||||
|
<!---
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
select scope_identity() as id;
|
||||||
|
</cfif>
|
||||||
|
--->
|
||||||
|
<!---
|
||||||
|
*** PostgreSQL incompatible hack
|
||||||
|
--->
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
select LASTVAL() as id;
|
||||||
|
</cfif>
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
<cfset keys[autoIncrementKeyIndex].value=qSave.id/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<!--- recordExists --->
|
||||||
|
|
||||||
|
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
|
||||||
|
<cfcatch type="database">
|
||||||
|
<cfset errorState="true" />
|
||||||
|
<cfset errorMessage="Ошибка при записи в базу данных. #cfcatch.message# #cfcatch.detail#" />
|
||||||
|
<cfset processingMode="pass" />
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfelse><!--- (not) validationOk --->
|
||||||
|
<cfset processingMode="pass"/>
|
||||||
|
</cfif><!--- validationOk --->
|
||||||
|
|
||||||
|
</cfif><!--- processingMode EQ "save" --->
|
||||||
|
|
||||||
|
<!--- экспортируем данные --->
|
||||||
|
<cfswitch expression=#processingMode#>
|
||||||
|
<cfcase value="read">
|
||||||
|
<cfquery name="qRead" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
select
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset i=i+1/>
|
||||||
|
#param.field#<cfif i LT ArrayLen(thistag.params)>,</cfif>
|
||||||
|
</cfloop>
|
||||||
|
from #ATTRIBUTES.table#
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
/*#sqlType(key.type)#*/
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfloop array=#thistag.params# index="param"><!--- *** --->
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", structFind(qRead, #param.field#)) />
|
||||||
|
</cfloop>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
|
||||||
|
<cfcase value="pass"><!--- заполнить все поля. Если значения переданы в форме или урле, тег параметра обязан их разобрать и подставить, в противном случае подставить инит-значения. Инит-значение по умолчанию пустая строка, дефолт по умолчанию тоже пустая строка --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!--- <cfdump var=#param#/>--->
|
||||||
|
<cfif param.hasInput>
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.submittedValue) /><!---*** дефект: 03.12.2015, передаваемая строкой, при обработке dateFormat неправильно парсится --->
|
||||||
|
<cfelse>
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.init) />
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfdefaultcase>
|
||||||
|
</cfdefaultcase>
|
||||||
|
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.info)><!--- экспортируем дополнительные сведения--->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.submittedValue", #param.submittedValue#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorState", #param.errorState#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorMessage", #param.errorMessage#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningState", #param.warningState#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningMessage", #param.warningMessage#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.null", #param.null#) />
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.meta)><!--- экспортируем метаданные параметра - для инвариантности вызовов --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.param", #param.param#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.field", #param.field#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.type", #param.type#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.displayName", #param.displayName#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.default", #param.default#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.size", #param.size#) />
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(errorState,errorMessage,initialProcessingMode))/>
|
||||||
|
|
||||||
|
</cfif><!--- thisTag.ExecutionMode is 'end'--->
|
||||||
|
|
||||||
|
<!--- quick and dirty --->
|
||||||
|
<cffunction name="sqlType">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="guid,uuid,idstamp"><cfreturn "CF_SQL_OTHER"/></cfcase><!--- hack for PostgreSQL uuid --->
|
||||||
|
<cfdefaultcase><cfreturn uCase("CF_SQL_#type#")/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="checkDataFormat">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
<cfargument name="value"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="integer"><cfreturn isValid("integer",value)/></cfcase>
|
||||||
|
<cfcase value="guid,uuid,idstamp"><cfreturn isValid("guid",value)/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn true/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<!--- //quick and dirty
|
||||||
|
function sqlType(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "guid,idstamp": return "CF_SQL_OTHER"; /*hack for PostgreSQL uuid*/ /*break not needed!*/
|
||||||
|
default: return uCase("CF_SQL_#type#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDataFormat(type,value) {
|
||||||
|
switch (type) {
|
||||||
|
case "integer": return isValid("integer",value); /*break not needed!*/
|
||||||
|
case "guid,uuid,idstamp": return isValid("guid",value); /*break not needed!*/ /*INCORRECT cfscript switch-case does not support lists*/
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
} --->
|
||||||
|
<cfscript>
|
||||||
|
//readonly может транслироваться в pass!
|
||||||
|
function translateMode(readonly) {
|
||||||
|
if (readonly) {
|
||||||
|
return "read";
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
<!---
|
||||||
|
bean v0.95
|
||||||
|
msyu@mail.ru
|
||||||
|
2013-08-29 v0.1
|
||||||
|
2013-09-04 v0.2 tested with CF8 and Railo
|
||||||
|
2013-10-14 added meta attribute - can now export each parameter metadata
|
||||||
|
2014-12-26 POST-GET read-pass-save
|
||||||
|
2015-01-19 read-pass-save, добавлен formmarker, убрано определение режима по POST-GET
|
||||||
|
2015-01-19 убран formmarker, упразднен режим init (используется pass)
|
||||||
|
2015-07-16 добавлен passmarker для перерисовки формы с введенными значениями
|
||||||
|
2015-10-02 поддержка составных ключей
|
||||||
|
2015-10-09 v0.9 если задано value параметра, считается, что он hasInput (корректно работает в составе ключа)
|
||||||
|
2015-11-25 добавлен readmarker, mode (принудительная установка режима)
|
||||||
|
2015-11-26 добавлен mode=skip
|
||||||
|
2016-09-01 изменена трактовка processingMode. Теперь понимается как исходная команда, фиксируется до начала действий. Можно использовать для управления
|
||||||
|
2018-09-16 добавлена ограниченная поддержка нецелых первичных ключей (idstamp)
|
||||||
|
child tag: param
|
||||||
|
to save - should send parameter with name from list ATTRIBUTES.savemarker (value doesn't matter) in FORM, e.g. URL.save="trtrtr", while ATTRIBUTES.savemarker="save,saveAndClose"; for multiple forms on a page (and bean tags) different savemarkers should be used
|
||||||
|
2022-01-06 PostgreSQL (MSSQL incompatibility because of scope_identity)
|
||||||
|
2024-04-13 PostgreSQL GUID, autoincrement by RETURNING
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Автогенерируемое поле GUID можно заполнять несколькими способами
|
||||||
|
- как default или init bean/param, но это не очень удобно, потому что может потеряться удобный признак новой записи - инвалидный ключ, и сейчас init-default взаимодействуют некорректно, по крайней мере, неочевидно
|
||||||
|
- генерируемое в БД (например, как default). Для PostgreSQL значение возвращается через Returning, как это делать для других БД, непонятно.
|
||||||
|
- можно генерировать при сохранении в bean, но для этого нужно расширение синтаксиса, зато не зависит от БД (*** TODO)
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfif thisTag.ExecutionMode is 'end'>
|
||||||
|
<cfparam name="ATTRIBUTES.readmarker" type="string" default="read"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда перечитывания. Имеет приоритет перед savemarker, passmarker. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.savemarker" type="string" default="save,saveAndClose"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда сохранения. Обычно это имя кнопки. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.passmarker" type="string" default="pass"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда заполнить форму отправленными данными без перечитывания. В форме такое поле обычно есть. Команда сохранения имеет приоритет перед пробросом. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.datasource" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.table" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.info" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.meta" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.status" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.mode" type="string" default=""/> <!--- Принудительный режим read|pass|save|skip. Имеет приоритет перед маркерами --->
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.mode EQ "skip">
|
||||||
|
<!---<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorState", false) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorMessage", "") />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.processingMode", "skip") />--->
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(false,"","skip"))/>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset validationOk="true" />
|
||||||
|
<cfset errorState="false" />
|
||||||
|
<cfset errorMessage="" />
|
||||||
|
<cfset warningState="false" />
|
||||||
|
<cfset warningMessage="" />
|
||||||
|
<!---<cfset keyField=""/>
|
||||||
|
<cfset keyValue=""/>--->
|
||||||
|
<cfset hasAutoIncrement="false"/>
|
||||||
|
<cfset autoIncrementKeyIndex=0/><!--- сгенерированное значение присваивается первому полю, отмеченному как autoincrement --->
|
||||||
|
<cfset recordExists = "false"/>
|
||||||
|
<cfset keyInvalid = "false"/>
|
||||||
|
|
||||||
|
<!--- разбор метаданных - поиск ключа --->
|
||||||
|
<!--- поддерживаются только простые целочисленные ключи --->
|
||||||
|
<cfset keys=arrayNew(1)/>
|
||||||
|
<!--- *** почему где-то копируем поля param в key, а где-то прямо цикл по параметрам --->
|
||||||
|
<!--- коллекция thistag.params формируется вложенными тегами param --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!---<cfdump var=#param#/>--->
|
||||||
|
<cfif structFind(param,"key")><!--- not just structKeyExists! Not exisis, OR exists, but FALSE! --->
|
||||||
|
<cfset key = structNew()/>
|
||||||
|
<cfset ArrayAppend(keys, key)/>
|
||||||
|
|
||||||
|
<cfset key.name = param.param />
|
||||||
|
<cfset key.field = param.field />
|
||||||
|
<cfset key.type = param.type />
|
||||||
|
<cfset key.value = param.value /><!--- должно уже быть присвоено --->
|
||||||
|
<cfset key.null = param.null />
|
||||||
|
|
||||||
|
<cfif param.errorState OR !param.hasInput>
|
||||||
|
<cfset keyInvalid="true"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset keyInvalid=!checkDataFormat(param.type, param.value)/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif !hasAutoIncrement><!--- take only first field with "autoincrement" attribute--->
|
||||||
|
<cfif structFind(param, "autoincrement")>
|
||||||
|
<cfset hasAutoIncrement = true/>
|
||||||
|
<cfset autoIncrementKeyIndex = arrayLen(keys)/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<!---<cfdump var=#keys#/>--->
|
||||||
|
|
||||||
|
<cfif !keyInvalid>
|
||||||
|
<!--- *** дублируется похожий cfqueryparam, перепутал один раз --->
|
||||||
|
<cfquery name="qCheckExistence" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
select count(*) as cnt
|
||||||
|
from #ATTRIBUTES.table#
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
<cfset recordExists = (qCheckExistence.cnt GT 0)/>
|
||||||
|
<!--- <cfdump var=#qCheckExistence#/> --->
|
||||||
|
<cfelse>
|
||||||
|
<cfset recordExists = "false"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFindNoCase("read,pass,save", ATTRIBUTES.mode)>
|
||||||
|
<cfset processingMode=ATTRIBUTES.mode/>
|
||||||
|
|
||||||
|
<cfelse>
|
||||||
|
|
||||||
|
<!--- ищем команду принудительного перечитывания --->
|
||||||
|
<cfset doRead = false/>
|
||||||
|
<cfloop list=#ATTRIBUTES.readmarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doRead = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- ищем команду сохранения --->
|
||||||
|
<cfset doSave = false/>
|
||||||
|
<cfif !doRead AND !ATTRIBUTES.readonly>
|
||||||
|
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doSave = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- ищем команду проброса --->
|
||||||
|
<cfset doPass = false/>
|
||||||
|
<cfif !doRead AND !doSave>
|
||||||
|
<cfloop list=#ATTRIBUTES.passmarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doPass = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- определяем режим --->
|
||||||
|
<cfif doRead>
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
<cfelseif doSave>
|
||||||
|
<cfset processingMode="save"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfif doPass OR !recordExists>
|
||||||
|
<cfset processingMode="pass"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset initialProcessingMode = processingMode/>
|
||||||
|
|
||||||
|
|
||||||
|
<!---debug <cfoutput>processingMode:#processingMode#</cfoutput>--->
|
||||||
|
<!--- ---------------------------------------------------------- --->
|
||||||
|
|
||||||
|
<!---*** Необходимость вставки (против апдейта) определяется по существованию записи. Это небезопасно, поскольку наличие записи с ключом -1 может прекратить создание новых записей. Конечно, можно добавить констрейнт на ключевое поле, но это лишняя зависимость.
|
||||||
|
Можно явно передавать команду вставки, а не заморачиваться с анализом ключа. Просто традиционно мы сигнализировали необходимость вставки, передавая невалидное значение ключа, обычно -1. Для целочисленных ключей можно было бы просто проверять положительность (что всю жизнь отлично работало), но хочется более общего.
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfif processingMode IS "save"> <!--- validation lookup --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
|
||||||
|
<cfif !param.key>
|
||||||
|
<cfif param.warningState>
|
||||||
|
<cfif NOT warningState><!--- catch the first fault --->
|
||||||
|
<cfset warningMessage=param.warningMessage />
|
||||||
|
</cfif>
|
||||||
|
<cfset warningState="true" />
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif param.errorState>
|
||||||
|
<cfif NOT errorState><!--- catch the first fault --->
|
||||||
|
<cfset errorMessage=param.errorMessage />
|
||||||
|
</cfif>
|
||||||
|
<cfset errorState="true" />
|
||||||
|
<cfset validationOk="false"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif errorState>
|
||||||
|
<!--- clear warning state --->
|
||||||
|
<cfset warningMessage="" />
|
||||||
|
<cfset warningState=false />
|
||||||
|
<!--- finish lookup --->
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfif validationOk>
|
||||||
|
<!---<cfdump var=#param#/> --->
|
||||||
|
<cftry>
|
||||||
|
<cfif recordExists><!--- update. Наличие keyValue гарантировано (уверен) --->
|
||||||
|
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#" result="qSaveResult">
|
||||||
|
update #ATTRIBUTES.table# set
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!--- не обновляем ключевые поля --->
|
||||||
|
<cfif NOT (StructFind(param, "key")
|
||||||
|
OR StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipUpdate")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
#param.field#=<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
<!---<cfdump var=#qSaveResult#/>--->
|
||||||
|
<cfelse><!--- insert --->
|
||||||
|
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
insert into #ATTRIBUTES.table#
|
||||||
|
(
|
||||||
|
<cfset i=0 />
|
||||||
|
<!--- ключевые поля без автоинкремента попадают в insert! --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfif NOT (StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipInsert")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
#param.field#
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
)
|
||||||
|
values
|
||||||
|
(
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfif NOT (StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipInsert")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
)
|
||||||
|
<!---
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
select scope_identity() as id;
|
||||||
|
</cfif>
|
||||||
|
--->
|
||||||
|
<!---*** PostgreSQL incompatible hack follows--->
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
returning #thistag.params[autoIncrementKeyIndex].field# as id;
|
||||||
|
</cfif>;
|
||||||
|
</cfquery>
|
||||||
|
<!--- <cfdump var=#qSave#/> --->
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
<cfset keys[autoIncrementKeyIndex].value=qSave.id/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<!--- recordExists --->
|
||||||
|
|
||||||
|
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
|
||||||
|
<cfcatch type="database">
|
||||||
|
<cfset errorState="true" />
|
||||||
|
<cfset errorMessage="Ошибка при записи в базу данных. #cfcatch.message# #cfcatch.detail#" />
|
||||||
|
<cfset processingMode="pass" />
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfelse><!--- (not) validationOk --->
|
||||||
|
<cfset processingMode="pass"/>
|
||||||
|
</cfif><!--- validationOk --->
|
||||||
|
|
||||||
|
</cfif><!--- processingMode EQ "save" --->
|
||||||
|
|
||||||
|
<!--- экспортируем данные --->
|
||||||
|
<cfswitch expression=#processingMode#>
|
||||||
|
<cfcase value="read">
|
||||||
|
<cfquery name="qRead" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
select
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset i=i+1/>
|
||||||
|
#param.field#<cfif i LT ArrayLen(thistag.params)>,</cfif>
|
||||||
|
</cfloop>
|
||||||
|
from #ATTRIBUTES.table#
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
/*#sqlType(key.type)#*/
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfloop array=#thistag.params# index="param"><!--- *** --->
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", structFind(qRead, #param.field#)) />
|
||||||
|
</cfloop>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
|
||||||
|
<cfcase value="pass"><!--- заполнить все поля. Если значения переданы в форме или урле, тег параметра обязан их разобрать и подставить, в противном случае подставить инит-значения. Инит-значение по умолчанию пустая строка, дефолт по умолчанию тоже пустая строка --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!--- <cfdump var=#param#/>--->
|
||||||
|
<cfif param.hasInput>
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.submittedValue) /><!---*** дефект: 03.12.2015, передаваемая строкой, при обработке dateFormat неправильно парсится --->
|
||||||
|
<cfelse>
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.init) />
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfdefaultcase>
|
||||||
|
</cfdefaultcase>
|
||||||
|
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.info)><!--- экспортируем дополнительные сведения--->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.submittedValue", #param.submittedValue#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorState", #param.errorState#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorMessage", #param.errorMessage#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningState", #param.warningState#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningMessage", #param.warningMessage#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.null", #param.null#) />
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.meta)><!--- экспортируем метаданные параметра - для инвариантности вызовов --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.param", #param.param#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.field", #param.field#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.type", #param.type#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.displayName", #param.displayName#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.default", #param.default#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.size", #param.size#) />
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(errorState,errorMessage,initialProcessingMode))/>
|
||||||
|
|
||||||
|
</cfif><!--- thisTag.ExecutionMode is 'end'--->
|
||||||
|
|
||||||
|
<!--- quick and dirty --->
|
||||||
|
<cffunction name="sqlType">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="guid,uuid,idstamp"><cfreturn "CF_SQL_OTHER"/></cfcase><!--- hack for PostgreSQL uuid --->
|
||||||
|
<cfdefaultcase><cfreturn uCase("CF_SQL_#type#")/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="checkDataFormat">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
<cfargument name="value"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="integer"><cfreturn isValid("integer",value)/></cfcase>
|
||||||
|
<cfcase value="guid,uuid,idstamp"><cfreturn isValid("guid",value)/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn true/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<!--- //quick and dirty
|
||||||
|
function sqlType(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "guid,idstamp": return "CF_SQL_OTHER"; /*hack for PostgreSQL uuid*/ /*break not needed!*/
|
||||||
|
default: return uCase("CF_SQL_#type#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDataFormat(type,value) {
|
||||||
|
switch (type) {
|
||||||
|
case "integer": return isValid("integer",value); /*break not needed!*/
|
||||||
|
case "guid,uuid,idstamp": return isValid("guid",value); /*break not needed!*/ /*INCORRECT cfscript switch-case does not support lists*/
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
} --->
|
||||||
|
<cfscript>
|
||||||
|
//readonly может транслироваться в pass!
|
||||||
|
function translateMode(readonly) {
|
||||||
|
if (readonly) {
|
||||||
|
return "read";
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
@@ -0,0 +1,398 @@
|
|||||||
|
<!---
|
||||||
|
bean v0.95
|
||||||
|
msyu@mail.ru
|
||||||
|
2013-08-29 v0.1
|
||||||
|
2013-09-04 v0.2 tested with CF8 and Railo
|
||||||
|
2013-10-14 added meta attribute - can now export each parameter metadata
|
||||||
|
2014-12-26 POST-GET read-pass-save
|
||||||
|
2015-01-19 read-pass-save, добавлен formmarker, убрано определение режима по POST-GET
|
||||||
|
2015-01-19 убран formmarker, упразднен режим init (используется pass)
|
||||||
|
2015-07-16 добавлен passmarker для перерисовки формы с введенными значениями
|
||||||
|
2015-10-02 поддержка составных ключей
|
||||||
|
2015-10-09 v0.9 если задано value параметра, считается, что он hasInput (корректно работает в составе ключа)
|
||||||
|
2015-11-25 добавлен readmarker, mode (принудительная установка режима)
|
||||||
|
2015-11-26 добавлен mode=skip
|
||||||
|
2016-09-01 изменена трактовка processingMode. Теперь понимается как исходная команда, фиксируется до начала действий. Можно использовать для управления
|
||||||
|
2018-09-16 добавлена ограниченная поддержка нецелых первичных ключей (idstamp)
|
||||||
|
child tag: param
|
||||||
|
to save - should send parameter with name from list ATTRIBUTES.savemarker (value doesn't matter) in FORM, e.g. URL.save="trtrtr", while ATTRIBUTES.savemarker="save,saveAndClose"; for multiple forms on a page (and bean tags) different savemarkers should be used
|
||||||
|
2022-01-06 PostgreSQL (MSSQL incompatibility because of scope_identity)
|
||||||
|
2024-04-13 PostgreSQL GUID, autoincrement by RETURNING
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Автогенерируемое поле GUID можно заполнять несколькими способами
|
||||||
|
- как default или init bean/param, но это не очень удобно, потому что может потеряться удобный признак новой записи - инвалидный ключ, и сейчас init-default взаимодействуют некорректно, по крайней мере, неочевидно
|
||||||
|
- генерируемое в БД (например, как default). Для PostgreSQL значение возвращается через Returning, как это делать для других БД, непонятно.
|
||||||
|
- можно генерировать при сохранении в bean, но для этого нужно расширение синтаксиса, зато не зависит от БД (*** TODO)
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfif thisTag.ExecutionMode is 'end'>
|
||||||
|
<cfparam name="ATTRIBUTES.readmarker" type="string" default="read"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда перечитывания. Имеет приоритет перед savemarker, passmarker. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.savemarker" type="string" default="save,saveAndClose"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда сохранения. Обычно это имя кнопки. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.passmarker" type="string" default="pass"/><!--- наличие в FORM или URL поля с таким именем воспринимается как команда заполнить форму отправленными данными без перечитывания. В форме такое поле обычно есть. Команда сохранения имеет приоритет перед пробросом. Можно передавать несколько вариантов списком через запятую. --->
|
||||||
|
<cfparam name="ATTRIBUTES.datasource" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.table" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.info" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.meta" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.status" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.mode" type="string" default=""/> <!--- Принудительный режим read|pass|save|skip. Имеет приоритет перед маркерами --->
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.mode EQ "skip">
|
||||||
|
<!---<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorState", false) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.errorMessage", "") />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#.processingMode", "skip") />--->
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(false,"","skip"))/>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset validationOk="true" />
|
||||||
|
<cfset errorState="false" />
|
||||||
|
<cfset errorMessage="" />
|
||||||
|
<cfset warningState="false" />
|
||||||
|
<cfset warningMessage="" />
|
||||||
|
<!---<cfset keyField=""/>
|
||||||
|
<cfset keyValue=""/>--->
|
||||||
|
<cfset hasAutoIncrement="false"/>
|
||||||
|
<cfset autoIncrementKeyIndex=0/><!--- сгенерированное значение присваивается первому полю, отмеченному как autoincrement --->
|
||||||
|
<cfset recordExists = "false"/>
|
||||||
|
<cfset keyInvalid = "false"/>
|
||||||
|
|
||||||
|
<!--- разбор метаданных - поиск ключа --->
|
||||||
|
<!--- поддерживаются только простые целочисленные ключи --->
|
||||||
|
<cfset keys=arrayNew(1)/>
|
||||||
|
<!--- *** почему где-то копируем поля param в key, а где-то прямо цикл по параметрам --->
|
||||||
|
<!--- коллекция thistag.params формируется вложенными тегами param --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!---<cfdump var=#param#/>--->
|
||||||
|
<cfif structFind(param,"key")><!--- not just structKeyExists! Not exisis, OR exists, but FALSE! --->
|
||||||
|
<cfset key = structNew()/>
|
||||||
|
<cfset ArrayAppend(keys, key)/>
|
||||||
|
|
||||||
|
<cfset key.name = param.param />
|
||||||
|
<cfset key.field = param.field />
|
||||||
|
<cfset key.type = param.type />
|
||||||
|
<cfset key.value = param.value /><!--- должно уже быть присвоено --->
|
||||||
|
<cfset key.null = param.null />
|
||||||
|
|
||||||
|
<cfif param.errorState OR !param.hasInput>
|
||||||
|
<cfset keyInvalid="true"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset keyInvalid=!checkDataFormat(param.type, param.value)/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif !hasAutoIncrement><!--- take only first field with "autoincrement" attribute--->
|
||||||
|
<cfif structFind(param, "autoincrement")>
|
||||||
|
<cfset hasAutoIncrement = true/>
|
||||||
|
<cfset autoIncrementKeyIndex = arrayLen(keys)/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<!---<cfdump var=#keys#/>--->
|
||||||
|
|
||||||
|
<cfif !keyInvalid>
|
||||||
|
<!--- *** дублируется похожий cfqueryparam, перепутал один раз --->
|
||||||
|
<cfquery name="qCheckExistence" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
select count(*) as cnt
|
||||||
|
from #ATTRIBUTES.table#
|
||||||
|
where 1=1
|
||||||
|
<!--- <cfloop array=#keys# index="key">
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||||
|
</cfloop> --->
|
||||||
|
</cfquery>
|
||||||
|
<cfset recordExists = (qCheckExistence.cnt GT 0)/>
|
||||||
|
<!--- <cfdump var=#qCheckExistence#/> --->
|
||||||
|
<cfelse>
|
||||||
|
<cfset recordExists = "false"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFindNoCase("read,pass,save", ATTRIBUTES.mode)>
|
||||||
|
<cfset processingMode=ATTRIBUTES.mode/>
|
||||||
|
|
||||||
|
<cfelse>
|
||||||
|
|
||||||
|
<!--- ищем команду принудительного перечитывания --->
|
||||||
|
<cfset doRead = false/>
|
||||||
|
<cfloop list=#ATTRIBUTES.readmarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doRead = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- ищем команду сохранения --->
|
||||||
|
<cfset doSave = false/>
|
||||||
|
<cfif !doRead AND !ATTRIBUTES.readonly>
|
||||||
|
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doSave = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- ищем команду проброса --->
|
||||||
|
<cfset doPass = false/>
|
||||||
|
<cfif !doRead AND !doSave>
|
||||||
|
<cfloop list=#ATTRIBUTES.passmarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<cfset doPass = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- определяем режим --->
|
||||||
|
<cfif doRead>
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
<cfelseif doSave>
|
||||||
|
<cfset processingMode="save"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfif doPass OR !recordExists>
|
||||||
|
<cfset processingMode="pass"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset initialProcessingMode = processingMode/>
|
||||||
|
|
||||||
|
|
||||||
|
<!---debug <cfoutput>processingMode:#processingMode#</cfoutput>--->
|
||||||
|
<!--- ---------------------------------------------------------- --->
|
||||||
|
|
||||||
|
<!---*** Необходимость вставки (против апдейта) определяется по существованию записи. Это небезопасно, поскольку наличие записи с ключом -1 может прекратить создание новых записей. Конечно, можно добавить констрейнт на ключевое поле, но это лишняя зависимость.
|
||||||
|
Можно явно передавать команду вставки, а не заморачиваться с анализом ключа. Просто традиционно мы сигнализировали необходимость вставки, передавая невалидное значение ключа, обычно -1. Для целочисленных ключей можно было бы просто проверять положительность (что всю жизнь отлично работало), но хочется более общего.
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfif processingMode IS "save"> <!--- validation lookup --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
|
||||||
|
<cfif !param.key>
|
||||||
|
<cfif param.warningState>
|
||||||
|
<cfif NOT warningState><!--- catch the first fault --->
|
||||||
|
<cfset warningMessage=param.warningMessage />
|
||||||
|
</cfif>
|
||||||
|
<cfset warningState="true" />
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif param.errorState>
|
||||||
|
<cfif NOT errorState><!--- catch the first fault --->
|
||||||
|
<cfset errorMessage=param.errorMessage />
|
||||||
|
</cfif>
|
||||||
|
<cfset errorState="true" />
|
||||||
|
<cfset validationOk="false"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif errorState>
|
||||||
|
<!--- clear warning state --->
|
||||||
|
<cfset warningMessage="" />
|
||||||
|
<cfset warningState=false />
|
||||||
|
<!--- finish lookup --->
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfif validationOk>
|
||||||
|
<!---<cfdump var=#param#/> --->
|
||||||
|
<cftry>
|
||||||
|
<cfif recordExists><!--- update. Наличие keyValue гарантировано (уверен) --->
|
||||||
|
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#" result="qSaveResult">
|
||||||
|
update #ATTRIBUTES.table# set
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!--- не обновляем ключевые поля --->
|
||||||
|
<cfif NOT (StructFind(param, "key")
|
||||||
|
OR StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipUpdate")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
#param.field#=<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
<!---<cfdump var=#qSaveResult#/>--->
|
||||||
|
<cfelse><!--- insert --->
|
||||||
|
<cfquery name="qSave" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
insert into #ATTRIBUTES.table#
|
||||||
|
(
|
||||||
|
<cfset i=0 />
|
||||||
|
<!--- ключевые поля без автоинкремента попадают в insert! --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfif NOT (StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipInsert")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
#param.field#
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
)
|
||||||
|
values
|
||||||
|
(
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfif NOT (StructFind(param, "autoincrement")
|
||||||
|
OR StructFind(param, "readonly")
|
||||||
|
OR StructFind(param, "skipInsert")
|
||||||
|
)><cfif i++ GT 0>,</cfif>
|
||||||
|
<cfqueryparam cfsqltype="#sqlType(param.type)#" value="#param.value#" null="#param.null#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
)
|
||||||
|
<!---
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
select scope_identity() as id;
|
||||||
|
</cfif>
|
||||||
|
--->
|
||||||
|
<!---*** PostgreSQL incompatible hack follows--->
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
returning #thistag.params[autoIncrementKeyIndex].field# as id;
|
||||||
|
</cfif>;
|
||||||
|
</cfquery>
|
||||||
|
<!--- <cfdump var=#qSave#/> --->
|
||||||
|
<cfif hasAutoIncrement>
|
||||||
|
<cfset keys[autoIncrementKeyIndex].value=qSave.id/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<!--- recordExists --->
|
||||||
|
|
||||||
|
|
||||||
|
<cfset processingMode="read"/>
|
||||||
|
|
||||||
|
<cfcatch type="database">
|
||||||
|
<cfset errorState="true" />
|
||||||
|
<cfset errorMessage="Ошибка при записи в базу данных. #cfcatch.message# #cfcatch.detail#" />
|
||||||
|
<cfset processingMode="pass" />
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfelse><!--- (not) validationOk --->
|
||||||
|
<cfset processingMode="pass"/>
|
||||||
|
</cfif><!--- validationOk --->
|
||||||
|
|
||||||
|
</cfif><!--- processingMode EQ "save" --->
|
||||||
|
|
||||||
|
<!--- экспортируем данные --->
|
||||||
|
<cfswitch expression=#processingMode#>
|
||||||
|
<cfcase value="read">
|
||||||
|
<cfquery name="qRead" datasource="#ATTRIBUTES.datasource#">
|
||||||
|
select
|
||||||
|
<cfset i=0 />
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset i=i+1/>
|
||||||
|
#param.field#<cfif i LT ArrayLen(thistag.params)>,</cfif>
|
||||||
|
</cfloop>
|
||||||
|
from #ATTRIBUTES.table#
|
||||||
|
where 1=1
|
||||||
|
<cfloop array=#keys# index="key">
|
||||||
|
/*#sqlType(key.type)#*/
|
||||||
|
AND #key.field#=<cfqueryparam cfsqltype="#sqlType(key.type)#" value="#key.value#" null="#key.null#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfloop array=#thistag.params# index="param"><!--- *** --->
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", structFind(qRead, #param.field#)) />
|
||||||
|
</cfloop>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
|
||||||
|
<cfcase value="pass"><!--- заполнить все поля. Если значения переданы в форме или урле, тег параметра обязан их разобрать и подставить, в противном случае подставить инит-значения. Инит-значение по умолчанию пустая строка, дефолт по умолчанию тоже пустая строка --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<!--- <cfdump var=#param#/>--->
|
||||||
|
<cfif param.hasInput>
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.submittedValue) /><!---*** дефект: 03.12.2015, передаваемая строкой, при обработке dateFormat неправильно парсится --->
|
||||||
|
<cfelse>
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.output#.#param.param#", param.init) />
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfdefaultcase>
|
||||||
|
</cfdefaultcase>
|
||||||
|
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.info)><!--- экспортируем дополнительные сведения--->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.submittedValue", #param.submittedValue#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorState", #param.errorState#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.errorMessage", #param.errorMessage#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningState", #param.warningState#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.warningMessage", #param.warningMessage#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.info#.#param.param#.null", #param.null#) />
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.meta)><!--- экспортируем метаданные параметра - для инвариантности вызовов --->
|
||||||
|
<cfloop array=#thistag.params# index="param">
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.param", #param.param#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.field", #param.field#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.type", #param.type#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.displayName", #param.displayName#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.default", #param.default#) />
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.meta#.#param.param#.size", #param.size#) />
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset SetVariable("CALLER.#ATTRIBUTES.status#", CreateObject("component","status").init(errorState,errorMessage,initialProcessingMode))/>
|
||||||
|
|
||||||
|
</cfif><!--- thisTag.ExecutionMode is 'end'--->
|
||||||
|
|
||||||
|
<!--- quick and dirty --->
|
||||||
|
<cffunction name="sqlType">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="guid,uuid,idstamp"><cfreturn "CF_SQL_OTHER"/></cfcase><!--- hack for PostgreSQL uuid --->
|
||||||
|
<cfdefaultcase><cfreturn uCase("CF_SQL_#type#")/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="checkDataFormat">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
<cfargument name="value"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="integer"><cfreturn isValid("integer",value)/></cfcase>
|
||||||
|
<cfcase value="guid,uuid,idstamp"><cfreturn isValid("guid",value)/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn true/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<!--- //quick and dirty
|
||||||
|
function sqlType(type) {
|
||||||
|
switch (type) {
|
||||||
|
case "guid,idstamp": return "CF_SQL_OTHER"; /*hack for PostgreSQL uuid*/ /*break not needed!*/
|
||||||
|
default: return uCase("CF_SQL_#type#");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDataFormat(type,value) {
|
||||||
|
switch (type) {
|
||||||
|
case "integer": return isValid("integer",value); /*break not needed!*/
|
||||||
|
case "guid,uuid,idstamp": return isValid("guid",value); /*break not needed!*/ /*INCORRECT cfscript switch-case does not support lists*/
|
||||||
|
default: return true;
|
||||||
|
}
|
||||||
|
} --->
|
||||||
|
<cfscript>
|
||||||
|
//readonly может транслироваться в pass!
|
||||||
|
function translateMode(readonly) {
|
||||||
|
if (readonly) {
|
||||||
|
return "read";
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<!--- 20181209 v0.3 --->
|
||||||
|
<!---v0.3 добавлен атрибут cfSqlType--->
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
<cfassociate basetag="cf_field_set" datacollection="fieldsArray"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.expression" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.name" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.cfSqlType" default="CF_SQL_VARCHAR"/>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.expression IS "">
|
||||||
|
<cfset ATTRIBUTES.expression=thisTag.generatedContent/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.name IS "">
|
||||||
|
<cfset regex="([[:word:]]+)[[:space:]]*$"/>
|
||||||
|
<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
|
||||||
|
<cfif arrayLen(matches.pos) GT 1>
|
||||||
|
<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<!--- 20181209 v0.3 --->
|
||||||
|
<!--- Используется в селекте для структурирования списка полей. Выводит вместо себя список через запятую, экспортирует Map со структурами выражение+заголовок, с ключом, соответствующим имени колонки в селекте (ключ вычисляется регулярным выражением) --->
|
||||||
|
<!---v0.3 добавлено поле cfSqlType--->
|
||||||
|
<cfif thisTag.executionMode is "end">
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.titleMapOut"/>
|
||||||
|
<cfparam name="ATTRIBUTES.lengthOut"/>
|
||||||
|
<cfparam name="ATTRIBUTES.listOut" default=""/>
|
||||||
|
|
||||||
|
<cfparam name="thisTag.fieldsArray" type="array"/><!--- Вложенные теги field отдают сюда свои данные --->
|
||||||
|
<cfset titleMap=structNew()/>
|
||||||
|
<cfset list=""/>
|
||||||
|
<cfset i=0/>
|
||||||
|
<cfloop array=#thisTag.fieldsArray# index="field">
|
||||||
|
<cfset i=i+1/>
|
||||||
|
<cfset list=listAppend(#list#, #field.expression#)/>
|
||||||
|
<cfif len(#field.name#)>
|
||||||
|
<cfset structInsert(#titleMap#, #field.name#, structNew())/>
|
||||||
|
<cfset "titleMap.#field.name#.ordinal"=#i#/>
|
||||||
|
<cfset "titleMap.#field.name#.title"=#field.title#/>
|
||||||
|
<cfset "titleMap.#field.name#.cfSqlType"=#field.cfSqlType#/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.lengthOut)>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.lengthOut#"=#arrayLen(thisTag.fieldsArray)#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.titleMapOut)>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.titleMapOut#"=#titleMap#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.listOut)>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.listOut#"=preserveSingleQuotes(list)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset thisTag.generatedContent=preserveSingleQuotes(list)/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,305 @@
|
|||||||
|
<!---
|
||||||
|
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
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!--- проверка на неподдерживаемые поля --->
|
||||||
|
<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", #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. 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!!!--->
|
||||||
|
|
||||||
|
<!--- 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=""/>
|
||||||
|
|
||||||
|
<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 isDefined("ATTRIBUTES.preprocessor")>
|
||||||
|
<cfset submittedValue=#ATTRIBUTES.preprocessor(submittedValue)#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- check required fields --->
|
||||||
|
<cfif StructFind(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>
|
||||||
|
|
||||||
|
<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#->#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.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)#->#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>
|
||||||
|
|
||||||
|
<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. Если не получает, не отдает, можно это специально указывать флагом. Можно ему лишнего не делегировать.
|
||||||
|
Остальное оставить вызывающему тегу?
|
||||||
|
--->
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,297 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<!--- v0.4---><!---9:50 06.10.2020--->
|
||||||
|
<!--- v0.5---><!---2024-12-11 15:10:07--->
|
||||||
|
<!--- DEL.CFM - унифицированный шаблон операции удаления записи --->
|
||||||
|
<cfimport prefix="m" taglib="../../lib"/>
|
||||||
|
<cfimport prefix="m" taglib="../../lib/controls"/>
|
||||||
|
</cfsilent><cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag><m:silent silent="No">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.savemarker" type="string" default="delete"/>
|
||||||
|
<cfparam name="ATTRIBUTES.readonly" type="boolean" default="No"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.checkQuery" type="string" default="select 0 as chk"/> <!--- запрос проверки целостности.
|
||||||
|
Простая строка (параметр преобразуется до вызова тега).
|
||||||
|
Если можно удалять, должен вернуть 0 в поле chk --->
|
||||||
|
<cfparam name="ATTRIBUTES.displayDependencies" type="boolean" default="Yes"/>
|
||||||
|
<cfparam name="ATTRIBUTES.ignoreDependencies" type="boolean" default="No"/><!--- будем удалять, несмотря на наличие зависимостей Предположительно, мы примем меры в запросе удаления, или расчитываем на каскадное удаление. Имеет смысл, видимо, только в комбинации с displayDependencies=TRUE, иначе зачем описывать зависимости и потом их игнорировать. --->
|
||||||
|
<cfparam name="ATTRIBUTES.entity" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.accessObj" type="string" default="#ATTRIBUTES.entity#"/> <!--- объект доступа, права к которому проверяем --->
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.deleteQuery" type="string" default=""/> <!--- запрос удаления *** надо бы параметризовать --->
|
||||||
|
<cfparam name="ATTRIBUTES.confirmMessage" type="string"/> <!--- текст для javascript:confirm() --->
|
||||||
|
<cfparam name="ATTRIBUTES.denyMessage" type="string" default="Удаление невозможно, существуют зависимые записи."/> <!---
|
||||||
|
сообщение о невозможности удаления записи (если проверка целостности говорит нельзя) --->
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.queryString" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.defaultBackUrl" type="string" default="#ATTRIBUTES.entity#_ls.cfm"/>
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.status" type="string" default=""/>
|
||||||
|
|
||||||
|
<cfparam name="thisTag.dependencies" type="array" default=#arrayNew(1)#/>
|
||||||
|
<cfparam name="thisTag.conditions" type="array"/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.status#"=CreateObject("component","status").init(false,"","")/>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.queryString) EQ 0>
|
||||||
|
<cfset i = 0 />
|
||||||
|
<cfloop array=#thisTag.conditions# index="condition">
|
||||||
|
<cfif i GT 0>
|
||||||
|
<cfset ATTRIBUTES.queryString="#ATTRIBUTES.queryString#&"/>
|
||||||
|
</cfif>
|
||||||
|
<cfset i = i+1/>
|
||||||
|
<cfset ATTRIBUTES.queryString="#ATTRIBUTES.queryString##condition.field#=#condition.value#"/>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<m:track
|
||||||
|
thisURL="#request.thisPage#?#ATTRIBUTES.queryString#"
|
||||||
|
defaultBackUrl="#ATTRIBUTES.defaultBackUrl#"
|
||||||
|
output="tr"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<cfquery name="qCheck" datasource="#request.DS#">
|
||||||
|
#preserveSingleQuotes(ATTRIBUTES.checkQuery)#
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfset sDenyMessage=""/>
|
||||||
|
|
||||||
|
<cfoutput query="qCheck">
|
||||||
|
<cfset sDenyMessage=evaluate("""#ATTRIBUTES.denyMessage#""")/>
|
||||||
|
</cfoutput>
|
||||||
|
|
||||||
|
<!--- быстрая проверка --->
|
||||||
|
<cfset dependentRecordsExist = false/>
|
||||||
|
<cfloop array=#thisTag.dependencies# index="dep">
|
||||||
|
<cfif dep.query.recordCount GT 0>
|
||||||
|
<cfset dependentRecordsExist = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- do delete --->
|
||||||
|
<m:ac obj="#ATTRIBUTES.accessObj#" act="#request.PERMISSION_WRITE#" output="writePermitted">
|
||||||
|
|
||||||
|
<cfset doSave = false/>
|
||||||
|
<cfif !ATTRIBUTES.readonly>
|
||||||
|
<cfloop list=#ATTRIBUTES.savemarker# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#")><!--- удаление можно делать только постом --->
|
||||||
|
<cfset doSave = true/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif doSave AND qCheck.chk EQ 0>
|
||||||
|
<cftry> <!--- <cfoutput> <cfset i = 0 />
|
||||||
|
<cfloop list=#ATTRIBUTES.deleteQuery# delimiters="?" index="queryPart">
|
||||||
|
|
||||||
|
<cfif i GT 0>
|
||||||
|
cfqueryparam
|
||||||
|
cfsqltype='#condition.cfsqltype#'
|
||||||
|
value='#condition.value#'
|
||||||
|
null='#condition.null#'/
|
||||||
|
</cfif><cfset i = i + 1/>*#queryPart#* #i#
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
</cfoutput> --->
|
||||||
|
|
||||||
|
|
||||||
|
<cfquery name="qDel" datasource="#request.DS#" result="result">
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.deleteQuery)>
|
||||||
|
<!--- запрос удаления должен быть сформулирован так,
|
||||||
|
чтобы условия удаления были в конце --->
|
||||||
|
<cfset i = 0 />
|
||||||
|
<cfloop list=#ATTRIBUTES.deleteQuery# delimiters="?" index="queryPart">
|
||||||
|
<cfif i GT 0>
|
||||||
|
<cfqueryparam
|
||||||
|
cfsqltype='#condition.cfsqltype#'
|
||||||
|
value='#condition.value#'
|
||||||
|
null='#condition.null#'/>
|
||||||
|
</cfif><cfset i = i + 1/>#queryPart#
|
||||||
|
</cfloop>
|
||||||
|
<cfelse>
|
||||||
|
delete from #ATTRIBUTES.entity# where /**/
|
||||||
|
<cfset i = 0 />
|
||||||
|
<cfloop array=#thisTag.conditions# index="condition">
|
||||||
|
<cfif i GT 0> AND </cfif>
|
||||||
|
<cfset i = i + 1/>
|
||||||
|
#condition.expression#=<cfqueryparam
|
||||||
|
cfsqltype='#condition.cfsqltype#'
|
||||||
|
value='#condition.value#'
|
||||||
|
null='#condition.null#'/>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<!--- *** неуклюжий костыль --->
|
||||||
|
<cfset CALLER.saveAndClose = true/><!--- сигнал для закрытия экрана --->
|
||||||
|
|
||||||
|
<cfcatch type="database">
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.status#.errorState"=true/>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.status#.errorMessage"="Ошибка при обращении к базе данных. #cfcatch.message# #cfcatch.detail# #cfcatch.tagcontext[1].template# line #cfcatch.tagcontext[1].line#"/>
|
||||||
|
<!--- <cfrethrow/> --->
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cfif>
|
||||||
|
</m:ac>
|
||||||
|
|
||||||
|
<cfif !writePermitted><!--- перетирания cfcatch не будет --->
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.status#.errorState"=true/>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.status#.errorMessage"="Ваших полномочий недостаточно для выполнения данной операции."/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
|
||||||
|
<!--- print output --->
|
||||||
|
<cfsavecontent variable="markup">
|
||||||
|
<cfoutput>
|
||||||
|
|
||||||
|
<div class="container-fluid">
|
||||||
|
<div class="row">
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<form name="frmDel" action="#request.thisPage#" method="POST">
|
||||||
|
<cfif NOT writePermitted>
|
||||||
|
<!--- ситуация возможна при прямом заходе по URL. Надо высказать юзарю свое неодобрение. --->
|
||||||
|
<!--- <h4 class="text-danger">Ваших полномочий недостаточно для выполнения данной операции.</h4>
|
||||||
|
<br/> --->
|
||||||
|
|
||||||
|
<cfelseif qCheck.chk GT 0>
|
||||||
|
<h4 class="text-danger">#ATTRIBUTES.denyMessage#</h4>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<cfelseif dependentRecordsExist>
|
||||||
|
<cfif ATTRIBUTES.displayDependencies>
|
||||||
|
|
||||||
|
<h4 class="text-danger">#ATTRIBUTES.denyMessage#</h4>
|
||||||
|
<br/>
|
||||||
|
<cftry>
|
||||||
|
<cfloop array=#thisTag.dependencies# index="dep">
|
||||||
|
<cfif dep.query.recordCount GT 0>
|
||||||
|
|
||||||
|
<h5>#dep.title#</h5>
|
||||||
|
|
||||||
|
<cfif dep.showDetails>
|
||||||
|
<table class="worktable">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th width="1%" class="c"></th>
|
||||||
|
<cfloop array=#dep.fields# item="fld">
|
||||||
|
<cfif len(fld.title)>
|
||||||
|
<th>#fld.title#</th>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<th width="1%"></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<cfoutput query=#dep.query#>
|
||||||
|
<cfset urlQueryString=""/>
|
||||||
|
<cfloop array=#dep.keys# index="keyName">
|
||||||
|
<cfif len(urlQueryString)>
|
||||||
|
<cfset urlQueryString="#urlQueryString#&"/><!---***--->
|
||||||
|
</cfif>
|
||||||
|
<cfset urlQueryString="#urlQueryString##keyName#=#dep.query[keyName][currentRow]#"/>
|
||||||
|
</cfloop>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="#dep.page#?#urlQueryString#&#tr.fwx#" <!---class="far fa-edit"--->><img src="img/edit.gif"/></a>
|
||||||
|
</td>
|
||||||
|
<cfloop array=#dep.fields# item="fld">
|
||||||
|
<cfif len(fld.title)>
|
||||||
|
<td>
|
||||||
|
<cfif isDefined("fld.formatter")>
|
||||||
|
#fld.formatter(dep.query[fld.name][currentRow])#
|
||||||
|
<cfelse>
|
||||||
|
#dep.query[fld.name][currentRow]#
|
||||||
|
</cfif>
|
||||||
|
</td>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<td>
|
||||||
|
<a href="#dep.delete_page#?#urlQueryString#&#tr.fwx#" <!---class="far fa-trash-alt text-danger"--->><img src="img/del.gif"/></a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</cfoutput>
|
||||||
|
</table>
|
||||||
|
<cfelse><!---dep.showDetails--->
|
||||||
|
<cfoutput><p>#dep.denyMessage# (#dep.query.recordCount#)</p></cfoutput>
|
||||||
|
</cfif><!---dep.showDetails--->
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<cfcatch type="any">
|
||||||
|
<cfoutput>#cfcatch.message# #cfcatch.detail# #cfcatch.tagcontext[1].template# line #cfcatch.tagcontext[1].line#/></cfoutput><cfrethrow/>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfelse><!---ATTRIBUTES.displayDependencies--->
|
||||||
|
<cfoutput>#ATTRIBUTES.denyMessage#</cfoutput>
|
||||||
|
</cfif><!---ATTRIBUTES.displayDependencies--->
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif NOT dependentRecordsExist OR ATTRIBUTES.ignoreDependencies><!--- qCheck.chk GT 0 | dependentRecordsExist --->
|
||||||
|
<cfif writePermitted>
|
||||||
|
<input type="hidden" name="track" value="#tr.self#">
|
||||||
|
<cfloop array=#thisTag.conditions# index="condition">
|
||||||
|
<!---<input type="hidden" name="#condition.expression#" value="#evaluate('#condition.expression#')#"/>--->
|
||||||
|
<input type="hidden" name="#condition.field#" value="#evaluate('#condition.field#')#"/><!--- по замыслу, берется из скоупа FORM (*** слишком неявно)--->
|
||||||
|
</cfloop>
|
||||||
|
<button type="submit" class="maincontrol btn btn-secondary" name="delete" onclick="if(!confirm('#evaluate("""#ATTRIBUTES.confirmMessage#""")#')) return false;">Удалить</button>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<button type="button" class="maincontrol btn btn-secondary" name="close" onclick="document.location.href='#tr.backURL#'">
|
||||||
|
Закрыть
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!--- <cfdump var=#dependentRecordsExist#/>
|
||||||
|
<cfdump var=#ATTRIBUTES.ignoreDependencies#/>
|
||||||
|
<cfdump var=#NOT dependentRecordsExist OR ATTRIBUTES.ignoreDependencies#/> --->
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</cfoutput>
|
||||||
|
</cfsavecontent>
|
||||||
|
|
||||||
|
</m:silent><cfif len(ATTRIBUTES.output)><cfset "CALLER.#ATTRIBUTES.output#" = #markup#/><cfelse>
|
||||||
|
<cfoutput>#markup#</cfoutput>
|
||||||
|
</cfif></cfif>
|
||||||
|
<!--- пример монструозной конструкции Postgre для каскадного удаления --->
|
||||||
|
<!--- with
|
||||||
|
inst as (
|
||||||
|
delete from instance
|
||||||
|
where instance_uid='e283e337-0ddc-459c-88b8-57d1963b4a7d'
|
||||||
|
returning instance_uid
|
||||||
|
),
|
||||||
|
st as (
|
||||||
|
delete from instance_state
|
||||||
|
where instance_uid in (select instance_uid from inst)
|
||||||
|
returning instance_state_uid
|
||||||
|
),
|
||||||
|
op as (
|
||||||
|
delete from instance_operation
|
||||||
|
where instance_uid in (select instance_uid from inst)
|
||||||
|
returning instance_operation_uid
|
||||||
|
),
|
||||||
|
par as (
|
||||||
|
delete from instance_operation_param
|
||||||
|
where instance_operation_uid in (select instance_operation_uid from op)
|
||||||
|
returning instance_operation_param_uid
|
||||||
|
),
|
||||||
|
cfs as (
|
||||||
|
delete from instance_operation_cfs_param
|
||||||
|
where instance_operation_uid in (select instance_operation_uid from op)
|
||||||
|
returning instance_operation_cfs_param_uid
|
||||||
|
)
|
||||||
|
select * from inst
|
||||||
|
left outer join st on (inst.instance_uid=st.instance_uid)
|
||||||
|
left outer join op on (inst.instance_uid=op.instance_uid)
|
||||||
|
left outer join par on (op.instance_operation_uid=par.instance_operation_uid)
|
||||||
|
left outer join cfs on (op.instance_operation_uid=cfs.instance_operation_uid)
|
||||||
|
; --->
|
||||||
|
<!--- такой вопрос: точно ли будут задействованы все CTE, если я на них не сошлюсь? На месте оптимизатора я бы проигнорил незадействованные. Наверное, надо все включить в джойн для уверенности? --->
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<!---v0.2 9:50 06.10.2020
|
||||||
|
---><cfsilent>
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
<cfassociate basetag="cf_del" datacollection="conditions" />
|
||||||
|
<cfparam name="ATTRIBUTES.field"/><!---имя параметра формы --->
|
||||||
|
<cfparam name="ATTRIBUTES.value"/>
|
||||||
|
<cfparam name="ATTRIBUTES.expression" default=""/><!---левая сторона выражения во where, обычно имя поля--->
|
||||||
|
<cfparam name="ATTRIBUTES.cfsqltype" default="cf_sql_integer"/>
|
||||||
|
<cfparam name="ATTRIBUTES.null" type="boolean" default="false"/>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.expression IS "">
|
||||||
|
<cfif len(thisTag.generatedContent)>
|
||||||
|
<cfset ATTRIBUTES.expression=#thisTag.generatedContent#/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset ATTRIBUTES.expression=#ATTRIBUTES.field#/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfset out=""/>
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
<cfassociate basetag="cf_dependency" datacollection="fieldsArray" />
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.expression" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.name" default=""/>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.expression IS "">
|
||||||
|
<cfset ATTRIBUTES.expression=thisTag.generatedContent/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.name IS "">
|
||||||
|
<cfset regex="([[:word:]]+)[[:space:]]*$"/>
|
||||||
|
<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
|
||||||
|
<cfif arrayLen(matches.pos) GT 1>
|
||||||
|
<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<cfif arrayLen(getBaseTagData("cf_dependency",1).thisTag.fieldsArray) GT 0>
|
||||||
|
<cfset thisTag.generatedContent=", #thisTag.generatedContent#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
<cfassociate basetag="cf_dependency" datacollection="keys" />
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.field"/>
|
||||||
|
<cfparam name="ATTRIBUTES.cfsqltype default="cf_sql_integer"/>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfif thisTag.executionMode IS "end">
|
||||||
|
<cfassociate basetag="cf_del" datacollection="dependencies" />
|
||||||
|
|
||||||
|
<!--- following are tag attribute placeholders. Coorresponding data are used by parent tags!--->
|
||||||
|
<cfparam name="ATTRIBUTES.entity" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.page" default="#ATTRIBUTES.entity#.cfm"/>
|
||||||
|
<cfparam name="ATTRIBUTES.delete_page" default="#ATTRIBUTES.entity#_del.cfm"/>
|
||||||
|
<cfparam name="ATTRIBUTES.showDetails" type="boolean" default="Yes"/>
|
||||||
|
<cfparam name="ATTRIBUTES.denyMessage" type="string" default="Необходимо удалить соответствующие записи"/><!--- displayed instead of details when ATTRIBUTES.showDetails IS "No"--->
|
||||||
|
<!---<cfparam name="ATTRIBUTES.ondelete" default="restrict"/> implement 1-level Cascade? --->
|
||||||
|
|
||||||
|
<cfparam name="thisTag.fields" type="array"/>
|
||||||
|
<cfparam name="thisTag.from" type="array"/>
|
||||||
|
<cfparam name="thisTag.conditions" type="array"/>
|
||||||
|
<cfparam name="thisTag.order_by" type="array"/>
|
||||||
|
|
||||||
|
<cfset ATTRIBUTES.fields=thisTag.fields/>
|
||||||
|
<cfset ATTRIBUTES.keys=arrayNew(1)/>
|
||||||
|
|
||||||
|
<cfset i=0/>
|
||||||
|
<cfloop array=#thisTag.fields# index="field">
|
||||||
|
<cfif len(field.name)>
|
||||||
|
<cfif structKeyExists(field, "key")>
|
||||||
|
<cfset arrayAppend(ATTRIBUTES.keys, #field.name#)/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfquery name="qDependency" datasource="#request.DS#">
|
||||||
|
select
|
||||||
|
<cfset i = 0 />
|
||||||
|
<cfloop array=#thisTag.fields# index="field">
|
||||||
|
<cfif i GT 0>, </cfif><cfset i = i+1/>#field.expression#
|
||||||
|
</cfloop>
|
||||||
|
from
|
||||||
|
<cfloop array=#thisTag.from# index="from">
|
||||||
|
#from.expression#
|
||||||
|
</cfloop>
|
||||||
|
where
|
||||||
|
<cfset i = 0 />
|
||||||
|
<cfloop array=#thisTag.conditions# index="condition">
|
||||||
|
<cfif i GT 0> AND </cfif><cfset i = i+1/>
|
||||||
|
#condition.expression#=<cfqueryparam cfsqltype='#condition.cfsqltype#' value='#condition.value#' null='#condition.null#'/>
|
||||||
|
</cfloop>
|
||||||
|
order by
|
||||||
|
<cfset i = 0 />
|
||||||
|
<cfloop array=#thisTag.order_by# index="order">
|
||||||
|
<cfif i GT 0>, </cfif><cfset i = i+1/>#order.expression#
|
||||||
|
</cfloop>
|
||||||
|
</cfquery>
|
||||||
|
<cfset ATTRIBUTES.query=#qDependency#/>
|
||||||
|
</cfif>
|
||||||
|
<cfset thisTag.generatedContent=""/>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
<cfassociate basetag="cf_dependency" datacollection="conditions" />
|
||||||
|
<cfparam name="ATTRIBUTES.expression" default=#thisTag.generatedContent#/>
|
||||||
|
<cfparam name="ATTRIBUTES.value"/>
|
||||||
|
<cfparam name="ATTRIBUTES.cfsqltype" default="cf_sql_integer"/>
|
||||||
|
<cfparam name="ATTRIBUTES.null" type="boolean" default="No"/>
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<cfsilent></cfsilent>
|
||||||
|
<cfset out=""/>
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
<cfassociate basetag="cf_dependency" datacollection="fields" />
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.expression" default=#thisTag.generatedContent#/>
|
||||||
|
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.name" default=""/>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.name IS "">
|
||||||
|
<cfset regex="([[:word:]]+)[[:space:]]*$"/>
|
||||||
|
<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
|
||||||
|
<cfif arrayLen(matches.pos) GT 1>
|
||||||
|
<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif arrayLen(getBaseTagData("cf_dependency",1).thisTag.fields) GT 1>
|
||||||
|
<cfset thisTag.generatedContent=", #thisTag.generatedContent#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
<cfassociate basetag="cf_dependency" datacollection="from" />
|
||||||
|
<cfparam name="ATTRIBUTES.expression" default=#thisTag.generatedContent# />
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
<cfassociate basetag="cf_dependency" datacollection="order_by" />
|
||||||
|
<cfparam name="ATTRIBUTES.expression" default=#thisTag.generatedContent# />
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<!--- 20181209 v0.3 --->
|
||||||
|
<!---v0.3 добавлен атрибут cfSqlType--->
|
||||||
|
<!---v0.4 добавлен атрибут container for json--->
|
||||||
|
<!---v0.5 добавлен атрибут type и трансляция в CF_SQL_--->
|
||||||
|
<!--- 20210326 v0.8 formatter --->
|
||||||
|
|
||||||
|
<cffunction name="passThrough"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="just return argument">
|
||||||
|
<cfargument name="x" type="ANY" required="true"/>
|
||||||
|
<cfreturn #ARGUMENTS.x#/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
<cfassociate basetag="cf_field_set" datacollection="fieldsArray"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.expression" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.title" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.name" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.type" default="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.cfSqlType" default="#getCfSqlType(ATTRIBUTES.type)#"/>
|
||||||
|
<cfparam name="ATTRIBUTES.container" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.formatter" default=#passThrough#/>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.expression IS "">
|
||||||
|
<cfset ATTRIBUTES.expression=thisTag.generatedContent/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.name IS "">
|
||||||
|
<cfset regex="([[:word:]]+)[[:space:]]*$"/>
|
||||||
|
<cfset matches=REFindNoCase(regex, ATTRIBUTES.expression, 1, true)/>
|
||||||
|
<cfif arrayLen(matches.pos) GT 1>
|
||||||
|
<cfset ATTRIBUTES.name=mid(ATTRIBUTES.expression, matches.pos[2], matches.len[2])/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cffunction name="getCfSqlType">
|
||||||
|
<!---Translate CF type understandable by isValid to CF_SQL_*--->
|
||||||
|
<cfargument name="type"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#ARGUMENTS.type#>
|
||||||
|
<cfcase value="string">
|
||||||
|
<cfreturn "CF_SQL_VARCHAR"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="integer">
|
||||||
|
<cfreturn "CF_SQL_INTEGER"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="boolean">
|
||||||
|
<cfreturn "CF_SQL_BIT"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="numeric">
|
||||||
|
<cfreturn "CF_SQL_NUMERIC"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="date,time">
|
||||||
|
<cfreturn "CF_SQL_TIMESTAMP"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfthrow type="custom" message="Unsupported type" detail="Type #ARGUMENTS.type# not supported"/>
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
</cffunction>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<!--- 20181209 v0.3 добавлено поле cfSqlType--->
|
||||||
|
<!--- 20201029 v0.5 все атрибуты необязательные--->
|
||||||
|
<!--- 20201114 v0.6 добавлен атрибут "container" for json--->
|
||||||
|
<!--- 20201201 v0.7 исправлен listOut--->
|
||||||
|
<!--- 20210326 v0.8 formatter --->
|
||||||
|
<!--- 20210326 v0.9 listOutKeepContent --->
|
||||||
|
<!--- Используется в селекте для структурирования списка полей. Выводит вместо себя список через запятую, экспортирует Map со структурами выражение+заголовок, с ключом, соответствующим имени колонки в селекте (ключ вычисляется регулярным выражением) --->
|
||||||
|
|
||||||
|
<cfif thisTag.executionMode is "end">
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.titleMapOut" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.lengthOut" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.listOut" default=""/><!---возвращаемый список полей через запятую для селекта. Если не задан (чаще всего так), этот список возвращается в виде контента тега --->
|
||||||
|
<cfparam name="ATTRIBUTES.listOutKeepContent" default=false/><!--- указывает listOut не очищать контент --->
|
||||||
|
<cfparam name="ATTRIBUTES.fieldsToInclude" default=""/><!--- список нужных полей, если не пустой - выбрасывать все поля, кроме тех, что в списке --->
|
||||||
|
|
||||||
|
<cfparam name="thisTag.fieldsArray" type="array"/><!--- Вложенные теги field отдают сюда свои данные --->
|
||||||
|
|
||||||
|
<cfset titleMap=structNew("linked")/><!---railo syntax--->
|
||||||
|
<cfset list=""/>
|
||||||
|
<cfset i=0/>
|
||||||
|
<cfloop array=#thisTag.fieldsArray# index="field">
|
||||||
|
<cfif len(#field.name#) EQ 0
|
||||||
|
OR listLen(ATTRIBUTES.fieldsToInclude) EQ 0
|
||||||
|
OR listFind(ATTRIBUTES.fieldsToInclude,field.name)>
|
||||||
|
<!--- пытаемся сохранить прежнюю логику - поля без имени не выбрасываются, но не попадают в titleMap --->
|
||||||
|
<cfset i=i+1/>
|
||||||
|
<cfset list=listAppend(#list#, #field.expression#)/>
|
||||||
|
|
||||||
|
<cfif len(#field.name#)>
|
||||||
|
<cfset structInsert(#titleMap#, #field.name#, structNew())/>
|
||||||
|
<cfset "titleMap.#field.name#.ordinal"=#i#/>
|
||||||
|
<cfset "titleMap.#field.name#.title"=#field.title#/>
|
||||||
|
<cfset "titleMap.#field.name#.type"=#field.type#/>
|
||||||
|
<cfset "titleMap.#field.name#.cfSqlType"=#field.cfSqlType#/>
|
||||||
|
<cfset "titleMap.#field.name#.container"=#field.container#/>
|
||||||
|
<cfset "titleMap.#field.name#.formatter"=#field.formatter#/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.lengthOut)>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.lengthOut#"= i/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.titleMapOut)>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.titleMapOut#"=#titleMap#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- <cfif len(ATTRIBUTES.listOut)>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.listOut#"=preserveSingleQuotes(list)/>
|
||||||
|
<cfset thisTag.generatedContent=""/>
|
||||||
|
</cfif> --->
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.listOut)><!--- *** некрасиво --->
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.listOut#"=list/>
|
||||||
|
<cfif ATTRIBUTES.listOutKeepContent>
|
||||||
|
<cfset thisTag.generatedContent=preserveSingleQuotes(list)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset thisTag.generatedContent=""/>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<cfset thisTag.generatedContent=preserveSingleQuotes(list)/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -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#->#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)#->#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. Если не получает, не отдает, можно это специально указывать флагом. Можно ему лишнего не делегировать.
|
||||||
|
Остальное оставить вызывающему тегу?
|
||||||
|
--->
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<cfcomponent displayname="Bean Status" output="false">
|
||||||
|
<!--- v 0.03 2024-09-05 12:36:40 added recordExists --->
|
||||||
|
|
||||||
|
<cfproperty name="errorState" type="boolean"/>
|
||||||
|
<cfproperty name="errorMessage" type="string"/>
|
||||||
|
<cfproperty name="processingMode" type="string"/>
|
||||||
|
<cfproperty name="recordExists" type="boolean"/>
|
||||||
|
|
||||||
|
<cffunction name="init">
|
||||||
|
<cfset this.errorState=false/>
|
||||||
|
<cfset this.errorMessage=""/>
|
||||||
|
<cfset this.processingMode=""/>
|
||||||
|
<cfset this.recordExists=false/>
|
||||||
|
|
||||||
|
<cfreturn this>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="init">
|
||||||
|
<cfargument name="errorState" type="boolean"/>
|
||||||
|
<cfargument name="errorMessage" type="string"/>
|
||||||
|
<cfargument name="processingMode" type="string"/>
|
||||||
|
|
||||||
|
<cfset this.errorState=arguments.errorState/>
|
||||||
|
<cfset this.errorMessage=arguments.errorMessage/>
|
||||||
|
<cfset this.processingMode=arguments.processingMode/>
|
||||||
|
|
||||||
|
<cfreturn this>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="init">
|
||||||
|
<cfargument name="errorState" type="boolean"/>
|
||||||
|
<cfargument name="errorMessage" type="string"/>
|
||||||
|
<cfargument name="processingMode" type="string"/>
|
||||||
|
<cfargument name="recordExists" type="boolean"/>
|
||||||
|
|
||||||
|
<cfset this.errorState=arguments.errorState/>
|
||||||
|
<cfset this.errorMessage=arguments.errorMessage/>
|
||||||
|
<cfset this.processingMode=arguments.processingMode/>
|
||||||
|
<cfset this.recordExists=arguments.recordExists/>
|
||||||
|
|
||||||
|
<cfreturn this>
|
||||||
|
</cffunction>
|
||||||
|
</cfcomponent>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<cfif thisTag.ExecutionMode is 'end'>
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string">
|
||||||
|
<cfparam name="ATTRIBUTES.outputType" type="string" default="map">
|
||||||
|
<cfparam name="thisTag.params" type="array"/>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="paramTypeToColumnType">
|
||||||
|
<cfargument name="paramType" type="string"/>
|
||||||
|
|
||||||
|
<cfswitch expression=#ARGUMENTS.paramType#>
|
||||||
|
<cfcase value="integer">
|
||||||
|
<cfreturn "integer"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="numeric">
|
||||||
|
<cfreturn "decimal"/><!---*** --->
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="datetime">
|
||||||
|
<cfreturn "timestamp"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="string">
|
||||||
|
<cfreturn "varchar"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfreturn "varchar"/>
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<cfswitch expression=#ATTRIBUTES.outputType#>
|
||||||
|
|
||||||
|
<cfcase value="map">
|
||||||
|
<cfloop from=1 to=#arrayLen(thisTag.params)# index="i">
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.output#.#thisTag.params[i].name#"=thisTag.params[i].value/>
|
||||||
|
</cfloop>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="query">
|
||||||
|
<cfset columnList=""/>
|
||||||
|
<cfset columnTypeList=""/>
|
||||||
|
<cfloop from=1 to=#arrayLen(thisTag.params)# index="i">
|
||||||
|
<cfset columnList=listAppend(columnList, thisTag.params[i].name)/>
|
||||||
|
<cfset columnTypeList=listAppend(columnTypeList, paramTypeToColumnType(thisTag.params[i].type))/>
|
||||||
|
</cfloop>
|
||||||
|
<cfset qOut=queryNew(columnList,columnTypeList)/>
|
||||||
|
<cfset queryAddRow(qOut)/>
|
||||||
|
<cfloop from=1 to=#arrayLen(thisTag.params)# index="i">
|
||||||
|
<cfset querySetCell(qOut, thisTag.params[i].name, thisTag.params[i].value)/>
|
||||||
|
</cfloop>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.output#"=#qOut#/>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfthrow type="custom" message="Unsupported output type '#ATTRIBUTES.outputType#'" detail=""/>
|
||||||
|
</cfdefaultcase>
|
||||||
|
|
||||||
|
</cfswitch>
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<cfsilent></cfsilent>
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
|
||||||
|
<cfassociate basetag="cf_submit_collection" datacollection="params" />
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.name"/>
|
||||||
|
<cfparam name="ATTRIBUTES.type" default="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.default" default=""/><!--- не валидируется, что позволяет использовать, например, пустую строку для индикации нулла в числовой колонке --->
|
||||||
|
|
||||||
|
<!--- output params --->
|
||||||
|
<cfparam name="ATTRIBUTES.submittedValue" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.value" type="any" default=""/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfif structKeyExists(CALLER.VARIABLES, "#ATTRIBUTES.name#")>
|
||||||
|
<cfset ATTRIBUTES.submittedValue=CALLER.VARIABLES["#ATTRIBUTES.name#"]/>
|
||||||
|
<cfelseif structKeyExists(FORM, "#ATTRIBUTES.name#")>
|
||||||
|
<cfset ATTRIBUTES.submittedValue=FORM["#ATTRIBUTES.name#"]/>
|
||||||
|
<cfelseif structKeyExists(URL, "#ATTRIBUTES.name#")>
|
||||||
|
<cfset ATTRIBUTES.submittedValue=URL["#ATTRIBUTES.name#"]/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset ATTRIBUTES.submittedValue=#ATTRIBUTES.default#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ATTRIBUTES,"preprocessor")>
|
||||||
|
<cfset preprocessedValue=#ATTRIBUTES.preprocessor(ATTRIBUTES.submittedValue)#/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset preprocessedValue=#ATTRIBUTES.submittedValue#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
|
||||||
|
<cfswitch expression=#ATTRIBUTES.type#>
|
||||||
|
<cfcase value="integer">
|
||||||
|
<cfif isValid('integer', preprocessedValue)>
|
||||||
|
<cfset ATTRIBUTES.value=preprocessedValue/>
|
||||||
|
<cfelseif isValid('number', preprocessedValue)>
|
||||||
|
<cfset ATTRIBUTES.value=round(preprocessedValue)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset ATTRIBUTES.value=#ATTRIBUTES.default#/>
|
||||||
|
</cfif>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="numeric">
|
||||||
|
<cfif isValid('numeric', preprocessedValue)>
|
||||||
|
<cfset ATTRIBUTES.value=preprocessedValue/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset ATTRIBUTES.value=#ATTRIBUTES.default#/>
|
||||||
|
</cfif>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="datetime">
|
||||||
|
<cfparam name="ATTRIBUTES.locale" default="ru"/>
|
||||||
|
<cfparam name="ATTRIBUTES.format" default=""/>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset ATTRIBUTES.value=LSParseDateTime(preprocessedValue, #ATTRIBUTES.locale#, #ATTRIBUTES.format#)/>
|
||||||
|
<cfcatch type="ANY">
|
||||||
|
<cfset ATTRIBUTES.value=#ATTRIBUTES.default#/>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="string">
|
||||||
|
<cfset ATTRIBUTES.value=preprocessedValue/>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<cfcomponent extends="form_page_info" displayname="information container for detail page" output="true">
|
||||||
|
|
||||||
|
<cfproperty name="formName" type="string"/>
|
||||||
|
<cfproperty name="formAction" type="string"/>
|
||||||
|
<cfproperty name="formMethod" type="string"/>
|
||||||
|
<cfproperty name="formEncType" type="string"/>
|
||||||
|
|
||||||
|
<cffunction name="init">
|
||||||
|
<cfargument name="entity" type="string" required="true"/>
|
||||||
|
<cfargument name="key" type="string" default="#arguments.entity#_id"/>
|
||||||
|
<cfargument name="track" type="struct" required="true"/>
|
||||||
|
<cfargument name="permission" type="number" required="true"/>
|
||||||
|
<cfargument name="formName" type="string" default=""/>
|
||||||
|
<cfargument name="formAction" type="string" default=""/>
|
||||||
|
<cfargument name="formMethod" type="string" default=""/>
|
||||||
|
<cfargument name="formEncType" type="string" default=""/>
|
||||||
|
|
||||||
|
<cfset super.init(arguments.entity, arguments.key, arguments.track, arguments.formName, arguments.formAction, arguments.formMethod,arguments.formEncType)/>
|
||||||
|
|
||||||
|
<cfset this.permission=arguments.permission/>
|
||||||
|
<cfset this.formName=arguments.formName/>
|
||||||
|
<cfset this.formAction=arguments.formAction/>
|
||||||
|
<cfset this.formMethod=arguments.formMethod/>
|
||||||
|
<cfset this.formEncType=arguments.formEncType/>
|
||||||
|
|
||||||
|
<cfreturn this>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="getType" returntype="string">
|
||||||
|
<cfreturn "detail"/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
</cfcomponent>
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<cfsilent><!--- dispatch --->
|
||||||
|
<cfimport prefix="m" taglib="../lib"/>
|
||||||
|
<cfimport prefix="c" taglib="../lib/controls"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.usePRG" type="boolean" default="Yes"/>
|
||||||
|
<cfparam name="ATTRIBUTES.pageInfo" type="struct"/>
|
||||||
|
<cfparam name="ATTRIBUTES.status" type="struct"/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfif structKeyExists(ATTRIBUTES,"id")>
|
||||||
|
<cfset id=#ATTRIBUTES.id#/>
|
||||||
|
<cfelse>
|
||||||
|
<cfparam name="ATTRIBUTES.data" type="struct"/>
|
||||||
|
<cfset id=#ATTRIBUTES.data[ATTRIBUTES.pageInfo.entity &"_id"]#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.defaultBackUrl" type="string" default="#ATTRIBUTES.pageInfo.entity#_ls.cfm"/>
|
||||||
|
<cfparam name="ATTRIBUTES.queryString" type="string" default="#ATTRIBUTES.pageInfo.key#=#id#"/><!---works only for entities with simple integer primary key--->
|
||||||
|
<cfparam name="ATTRIBUTES.trackOut" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.idAttributesOut" type="string" default=""/><!---works only for entities with simple integer primary key--->
|
||||||
|
|
||||||
|
<m:track
|
||||||
|
thisURL="#request.thisPage#?#ATTRIBUTES.queryString#"
|
||||||
|
defaultBackUrl="#ATTRIBUTES.defaultBackUrl#"
|
||||||
|
output="tr"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<!--- route --->
|
||||||
|
<cfif ATTRIBUTES.usePRG AND !ATTRIBUTES.status.errorState>
|
||||||
|
<!--- PRG pattern--->
|
||||||
|
<cfif structKeyExists(CALLER, "save")>
|
||||||
|
<m:location url="#tr.selfUrl#"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif structKeyExists(CALLER, "saveAndClose") AND !ATTRIBUTES.status.errorState>
|
||||||
|
<m:location url="#tr.backUrl#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif structKeyExists(CALLER, "saveAndNew") AND !ATTRIBUTES.status.errorState>
|
||||||
|
<m:location url="#tr.selfUrl#&doClone"/><!--- должен содержать Query String, чтобы указать ключ, иначе смысла нет--->
|
||||||
|
</cfif>
|
||||||
|
<!---/route --->
|
||||||
|
|
||||||
|
<cfset ATTRIBUTES.pageInfo.track=#tr#/>
|
||||||
|
<cfset ATTRIBUTES.pageInfo.status=#ATTRIBUTES.status#/><!---Передаем статус в pageInfo--->
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.trackOut)>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#tr#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.idAttributesOut)>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.idAttributesOut#.cfsqltype"="cf_sql_integer"/>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.idAttributesOut#.value"=#id#/>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.idAttributesOut#.null"=#!isNumeric(id)#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<!--- build query filter string --->
|
||||||
|
<!--- пустые и некорректные значения параметров вызвыают пропуск параметра --->
|
||||||
|
<cfparam name="ATTRIBUTES.filter" type="struct"/>
|
||||||
|
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
|
||||||
|
<cfset queryFilter="">
|
||||||
|
<cfloop collection=#ATTRIBUTES.filter# item="item">
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfset fltr=structFind(ATTRIBUTES.filter,item)>
|
||||||
|
|
||||||
|
<!--- checks and preformatting --->
|
||||||
|
<cfset skip=false><!--- *** неуклюжая имитация continue --->
|
||||||
|
|
||||||
|
<cfswitch expression=#fltr.ftype#>
|
||||||
|
<cfcase value="numeric">
|
||||||
|
<cfif NOT isNumeric(fltr.val)><cfset skip=true></cfif>
|
||||||
|
<cfset val=fltr.val>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="date">
|
||||||
|
<cfif NOT isDate(fltr.val)><cfset skip=true></cfif>
|
||||||
|
<cfset val=fltr.val>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="string">
|
||||||
|
<!--- special case --->
|
||||||
|
<!--- escape single quotes for security--->
|
||||||
|
<cfset val=replace(fltr.val,"'","''", "ALL")>
|
||||||
|
<!--- elclose in single quotes --->
|
||||||
|
<cfif NOT fltr.compare EQ "LIKE%"><!---*** криво --->
|
||||||
|
<cfset val="'#val#'">
|
||||||
|
</cfif>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="list"><!--- *** для простоты листы разных типов не различаются, что не есть хорошо, повышается риск инъекциии --->
|
||||||
|
<!--- данные списка хранятся в виде массива, чтобы исключить проблемы с запятыми--->
|
||||||
|
<cfif arrayLen(fltr.val) EQ 0>
|
||||||
|
<cfset skip=true>
|
||||||
|
<cfelse>
|
||||||
|
<cfset isNumericList=true>
|
||||||
|
|
||||||
|
<!--- test: maybe list is of numbers--->
|
||||||
|
<cfloop array=#fltr.val# index="item">
|
||||||
|
<cfif NOT isNumeric(#item#)>
|
||||||
|
<cfset isNumericList=false/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<!---<cfoutput>#isNumericList#</cfoutput>--->
|
||||||
|
|
||||||
|
<cfset lst="">
|
||||||
|
<cfif isNumericList>
|
||||||
|
<cfloop array=#fltr.val# index="item">
|
||||||
|
<cfset lst=listAppend(lst, item)><!---<cfoutput>**#item#</cfoutput> --->
|
||||||
|
</cfloop>
|
||||||
|
<cfelse>
|
||||||
|
<cfloop array=#fltr.val# index="item"><!--- escape single quotes to prevent SQL injection --->
|
||||||
|
<cfset cleanItem=replace(item, "'", "''", "ALL")>
|
||||||
|
<cfset lst=listAppend(lst, "'#cleanItem#'")><cfoutput>#item#</cfoutput>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
|
<cfset val=lst>
|
||||||
|
<!---<cfoutput>#val#</cfoutput>--->
|
||||||
|
</cfif>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfset skip=true>
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
<!--- query building --->
|
||||||
|
<!--- потенциально опасно --->
|
||||||
|
<!--- *** протестировать на sql injection --->
|
||||||
|
<cfif NOT skip>
|
||||||
|
<cfif structKeyExists(fltr,"expression") AND len(fltr.expression)>
|
||||||
|
<cfset cmpExpr=replace(fltr.expression,"?","#val#","ALL")/>
|
||||||
|
<cfelse>
|
||||||
|
<cfswitch expression=#fltr.compare#>
|
||||||
|
<cfcase value="EQ">
|
||||||
|
<cfset cmpExpr="#fltr.field#=#val#">
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="NE,NEQ">
|
||||||
|
<cfset cmpExpr="NOT #fltr.field#=#val#">
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="LE">
|
||||||
|
<cfset cmpExpr="#fltr.field#<=#val#">
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="LT">
|
||||||
|
<cfset cmpExpr="#fltr.field#<#val#">
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="GE">
|
||||||
|
<cfset cmpExpr="#fltr.field#>=#val#">
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="GT">
|
||||||
|
<cfset cmpExpr="#fltr.field#>#val#">
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="IN">
|
||||||
|
<cfset cmpExpr="#fltr.field# IN (#val#)">
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="LIKE">
|
||||||
|
<cfset cmpExpr="#fltr.field# LIKE #val#">
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="LIKE%">
|
||||||
|
<cfset cmpExpr="#fltr.field# LIKE '%#val#%'">
|
||||||
|
</cfcase>
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfset skip=true>
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<cfif NOT skip>
|
||||||
|
<cfset queryFilter="#queryFilter# AND #cmpExpr#">
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfcatch type="ANY"></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfif isDefined("ATTRIBUTES.output")>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.output#"=preserveSingleQuotes(queryFilter)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset thisTag.generatedContent=preserveSingleQuotes(queryFilter)/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<!--- version 2.01---><!---15:25 31.01.2019--->
|
||||||
|
<!--- build query filter string --->
|
||||||
|
<!--- пустые и некорректные значения параметров вызвыают пропуск параметра --->
|
||||||
|
<cfparam name="ATTRIBUTES.filter" type="struct"/>
|
||||||
|
|
||||||
|
<cfloop collection=#ATTRIBUTES.filter# item="item">
|
||||||
|
<cfsilent>
|
||||||
|
|
||||||
|
<cfset fltr=structFind(ATTRIBUTES.filter,item)>
|
||||||
|
<cfparam name="fltr.val" default=""/>
|
||||||
|
<cfparam name="fltr.prefix" default=""/>
|
||||||
|
<cfparam name="fltr.suffix" default=""/>
|
||||||
|
<cfparam name="fltr.list" default="No"/>
|
||||||
|
</cfsilent> <!---
|
||||||
|
|
||||||
|
---> AND <cfif structKeyExists(fltr,"expression") AND len(fltr.expression)><!---
|
||||||
|
---><cfset len=listLen(#fltr.expression#,"?")><!---
|
||||||
|
---><cfloop from=1 to=#len# index="i"><!---
|
||||||
|
---><cfoutput>#listGetAt(fltr.expression,i,"?")#</cfoutput><!---
|
||||||
|
---><cfif i LT #len#><cfqueryparam cfsqltype="#getCfSqLType(fltr.ftype)#" list="#fltr.list#" value="#fltr.prefix##fltr.val##fltr.suffix#"/></cfif><!---
|
||||||
|
---></cfloop><!---
|
||||||
|
---><cfelse><!---
|
||||||
|
---><cfoutput>#fltr.field#</cfoutput><cfswitch expression=#fltr.compare#><cfcase value="EQ"> = </cfcase><cfcase value="NE,NEQ"> <> </cfcase><cfcase value="LE"> <= </cfcase><cfcase value="LT"> < </cfcase><cfcase value="GE"> >= </cfcase><cfcase value="GT"> > </cfcase><cfcase value="LIKE"> LIKE </cfcase><cfcase value="NOTLIKE"> NOT LIKE </cfcase><cfcase value="LIKE%"> LIKE <cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="%#fltr.val#%"/><cfcontinue/></cfcase><cfcase value="NOTLIKE%"> NOT LIKE <cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="%#fltr.val#%"/><cfcontinue/></cfcase><cfcase value="IN"> IN (<cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# list="#fltr.list#" value="#fltr.val#"/>)<cfcontinue/></cfcase><cfdefaultcase> = <cfoutput> #fltr.field#</cfoutput><cfcontinue/><!---***криво---></cfdefaultcase></cfswitch><cfqueryparam cfsqltype=#getCfSqLType(fltr.ftype)# value="#fltr.val#"/><cfcontinue/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
|
||||||
|
<cffunction name="getCfSqlType" output="No">
|
||||||
|
<cfargument name="fieldType" default=""/>
|
||||||
|
<cfswitch expression=#ARGUMENTS.fieldType#>
|
||||||
|
<cfcase value="numeric">
|
||||||
|
<cfreturn "CF_SQL_NUMERIC"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="date,time,datetime">
|
||||||
|
<cfreturn "CF_SQL_TIMESTAMP"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfcase value="string">
|
||||||
|
<cfreturn "CF_SQL_VARCHAR"/>
|
||||||
|
</cfcase>
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfreturn "CF_SQL_VARCHAR"/>
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<cfcomponent extends="form_page_info" displayname="information container for form page" output="false">
|
||||||
|
|
||||||
|
<cfproperty name="settings" type="struct"/>
|
||||||
|
|
||||||
|
<cffunction name="getType" returntype="string">
|
||||||
|
<cfreturn "filter"/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
</cfcomponent>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<cfsilent></cfsilent>
|
||||||
|
<!--- version 2.01--->
|
||||||
|
<cfimport prefix="m" taglib="."/>
|
||||||
|
<cfparam name="ATTRIBUTES.target" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.scopeName" type="string" default="CALLER"/>
|
||||||
|
<cfparam name="ATTRIBUTES.structName" type="string" default="filter"/>
|
||||||
|
<cfparam name="ATTRIBUTES.markerList" type="string" default="applyFilter,saveAndClose"/>
|
||||||
|
|
||||||
|
<cfif thisTag.executionMode IS "start">
|
||||||
|
<cfset key="#ATTRIBUTES.target#.filter"/>
|
||||||
|
|
||||||
|
<cfif isDefined("reset") or isDefined("resetAndClose")>
|
||||||
|
<m:settings action="clear" key="#key#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<m:settings action="get" key="#key#" output="filter"/>
|
||||||
|
<cfset "#ATTRIBUTES.scopeName#.#ATTRIBUTES.structName#"=#filter#/>
|
||||||
|
<cfelse>
|
||||||
|
<!--- in fact, for any action --->
|
||||||
|
<cfloop list=#ATTRIBUTES.markerList# index="marker">
|
||||||
|
<cfif structKeyExists(FORM,"#marker#") OR structKeyExists(URL,"#marker#")>
|
||||||
|
<m:settings action="sync" key="#key#" obj=#CALLER.filter#>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfif>
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
<!--- version 2.01---><!---15:25 31.01.2019--->
|
||||||
|
<!--- 2017.06.16 исправлен баг с очисткой при числовом значении по умолчанию --->
|
||||||
|
<!---v0.5 10:02 02.01.2019--->
|
||||||
|
<cfassociate basetag="cf_filter_settings"/>
|
||||||
|
<cfparam name="ATTRIBUTES.filter" type="struct" default=#getBaseTagData("CF_FILTER_SETTINGS",1).filter#/><!--- *** possible Railo bug, getBaseTagData works only with second parameter --->
|
||||||
|
<cfparam name="ATTRIBUTES.param" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.default" type="string" default=""/><!--- список: значения, установленные по умолчанию. Обычно состоит из 1 значения даже для списка. В случае кодирования base64 этот список НЕ кодируется (проблемы с запятыми остаются на совести разработчика)--->
|
||||||
|
<!---*** И вообще Base64 нельзя использовать непосредственно, он не URLsafe --->
|
||||||
|
<cfparam name="ATTRIBUTES.ftype" type="string" default="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.list" type="boolean" default="No"/>
|
||||||
|
<cfparam name="ATTRIBUTES.compare" type="string" default="EQ"/>
|
||||||
|
<cfparam name="ATTRIBUTES.field" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.base64" type="boolean" default="No"/><!--- означает, что параметр нужно раскодировать из base64---><!---*** Переделать на URL-safe base64--->
|
||||||
|
|
||||||
|
<!---
|
||||||
|
CFQUERYPARAM CFSQLTYPE="CF_SQL_VARCHAR" ставит апострофы вокруг значения параметра.
|
||||||
|
Поэтому приходится любые wildcards засовывать в значение bind variable. Запрос должен иметь вид
|
||||||
|
SELECT A,B FROM C WHERE A LIKE ?
|
||||||
|
(без всяких апострофов), а значение параметра будет, например,
|
||||||
|
"%#submitted_string#%"
|
||||||
|
--->
|
||||||
|
<cfparam name="ATTRIBUTES.prefix" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.suffix" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.expression" type="string" default=""/>
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Хранение данных для списков
|
||||||
|
переделано обратно с массива на лист.
|
||||||
|
При этом случилась забавная, но неприятная и трудноуловимая ошибка
|
||||||
|
arrayAppend(array,item) механически заменил на listAppend(list,item)
|
||||||
|
а надо было на list=listAppend(list,item)
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!---<cfif ATTRIBUTES.list>
|
||||||
|
<cfset def = "">
|
||||||
|
<cfloop list=#ATTRIBUTES.default# index="item">
|
||||||
|
<cfset listAppend(def, item)>Список значений по умолчанию НЕ должен быть кодирован в base64 при ATTRIBUTES.base64
|
||||||
|
</cfloop>
|
||||||
|
<cfelse>
|
||||||
|
|
||||||
|
</cfif>--->
|
||||||
|
<cfset def=ATTRIBUTES.default/>
|
||||||
|
<!---<cfoutput>#ATTRIBUTES.param#:</cfoutput>--->
|
||||||
|
|
||||||
|
<!---<cfif evaluate("isDefined('CALLER.#ATTRIBUTES.param#')")>
|
||||||
|
<cfset evaluate("val=(CALLER.#ATTRIBUTES.param#)")>--->
|
||||||
|
|
||||||
|
<cfif structKeyExists(CALLER, '#ATTRIBUTES.param#')>
|
||||||
|
<cfset val=structFind(CALLER, '#ATTRIBUTES.param#')/>
|
||||||
|
<!--- parse --->
|
||||||
|
|
||||||
|
<cfset to=1/>
|
||||||
|
<cfset values = ""/>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.list>
|
||||||
|
<cfset to=listLen(val)/>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfloop from="1" to=#to# index="i">
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.list>
|
||||||
|
<cfset element=listGetAt(val,i)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset element=val/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfswitch expression=#ATTRIBUTES.ftype#>
|
||||||
|
|
||||||
|
<cfcase value="numeric">
|
||||||
|
<cfset element=trim(element)>
|
||||||
|
<cfif NOT isNumeric(element)>
|
||||||
|
<cfset element=def/>
|
||||||
|
</cfif>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="date">
|
||||||
|
<cftry>
|
||||||
|
<cfset element=CreateDateTime(ListGetAt(element,3,'.'), ListGetAt(element,2,'.'), ListGetAt(element,1,'.'),0,0,0)>
|
||||||
|
<cfcatch type="Any">
|
||||||
|
<!--- <cfoutput>#cfcatch.message# #cfcatch.detail#</cfoutput> --->
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="string">
|
||||||
|
<cfif ATTRIBUTES.base64>
|
||||||
|
<cfset element=ToString(BinaryDecode(element , "base64"))>
|
||||||
|
</cfif>
|
||||||
|
<cfset element=safeStr(element)/>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfthrow message="Filterparam: unsupported ftype value '#ATTRIBUTES.ftype#'" detail="Supported values are 'numeric', 'date', 'string'">
|
||||||
|
</cfdefaultcase>
|
||||||
|
|
||||||
|
</cfswitch>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.list>
|
||||||
|
<cfset values=listAppend(values, element)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset val=element/>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfif ATTRIBUTES.list>
|
||||||
|
<cfset val=values/><!---*** все это выглядит ужасно. Переиспользование переменных вперед-назад, IF вокруг листа--->
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!---/parse --->
|
||||||
|
|
||||||
|
<cfelseif structKeyExists(ATTRIBUTES.filter, ATTRIBUTES.param)><!--- если параметр есть в сохраненном фильтре, взять оттуда --->
|
||||||
|
|
||||||
|
<cfset filterItem = structFind(ATTRIBUTES.filter, ATTRIBUTES.param)>
|
||||||
|
<cfif isStruct(filterItem)>
|
||||||
|
<cfset val=filterItem.val>
|
||||||
|
<cfelse><!---not struct <cfoutput>#ATTRIBUTES.param#</cfoutput>--->
|
||||||
|
<cfset val=def>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfelse>
|
||||||
|
|
||||||
|
<cfset val=def>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.param#", "")/>
|
||||||
|
<cftry>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.param#",#val#)/>
|
||||||
|
<cfcatch type="Any"></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
||||||
|
|
||||||
|
<cfif shouldRemoveValue(val, def)>
|
||||||
|
<cfset structDelete(ATTRIBUTES.filter, ATTRIBUTES.param)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset fltItem=structNew()>
|
||||||
|
<cfset fltItem.ftype=ATTRIBUTES.ftype>
|
||||||
|
<cfset fltItem.list=ATTRIBUTES.list>
|
||||||
|
<cfset fltItem.field=ATTRIBUTES.field>
|
||||||
|
<cfset fltItem.compare=ATTRIBUTES.compare>
|
||||||
|
<cfset fltItem.val=val>
|
||||||
|
<cfset fltItem.prefix=ATTRIBUTES.prefix/>
|
||||||
|
<cfset fltItem.suffix=ATTRIBUTES.suffix/>
|
||||||
|
<cfset fltItem.expression=ATTRIBUTES.expression/>
|
||||||
|
<cfset structInsert(ATTRIBUTES.filter,ATTRIBUTES.param,fltItem, "TRUE")>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
|
||||||
|
<cffunction name="shouldRemoveValue" returnType="boolean">
|
||||||
|
<cfargument name="val"/>
|
||||||
|
<cfargument name="def"/>
|
||||||
|
<!---<cfdump var=#val#> <cfdump var=#def#>--->
|
||||||
|
<cfscript>
|
||||||
|
if (!IsSimpleValue(val)){
|
||||||
|
if (isArray(val)) {
|
||||||
|
return (arrayLen(val) == 0);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ((val==def) OR (val==""));
|
||||||
|
</cfscript>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="safeStr" returnType="string">
|
||||||
|
<cfargument name="val" type="string"/>
|
||||||
|
<cfreturn replaceList(val, "',"",<,>", ",,,")/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<cfcomponent extends="generic_page_info" displayname="information container for form page" output="true">
|
||||||
|
|
||||||
|
<cfproperty name="formName" type="string"/>
|
||||||
|
<cfproperty name="formAction" type="string"/>
|
||||||
|
<cfproperty name="formMethod" type="string"/>
|
||||||
|
<cfproperty name="formEncType" type="string"/>
|
||||||
|
|
||||||
|
<cffunction name="init">
|
||||||
|
<cfargument name="entity" type="string" required="true"/>
|
||||||
|
<cfargument name="key" type="string" default="#arguments.entity#_id"/><!--- *** некрасиво как выполнен проброс --->
|
||||||
|
<cfargument name="track" type="struct" required="true"/>
|
||||||
|
<cfargument name="formName" type="string" default=""/>
|
||||||
|
<cfargument name="formAction" type="string" default=""/>
|
||||||
|
<cfargument name="formMethod" type="string" default=""/>
|
||||||
|
<cfargument name="formEncType" type="string" default=""/>
|
||||||
|
|
||||||
|
<cfset super.init(arguments.entity, arguments.key, arguments.track)/>
|
||||||
|
|
||||||
|
<cfset this.formName=arguments.formName/>
|
||||||
|
<cfset this.formAction=arguments.formAction/>
|
||||||
|
<cfset this.formMethod=arguments.formMethod/>
|
||||||
|
<cfset this.formEncType=arguments.formEncType/>
|
||||||
|
|
||||||
|
<cfreturn this>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="getType" returntype="string">
|
||||||
|
<cfreturn "form"/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
</cfcomponent>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<cfcomponent displayname="Generic Page Info" output="true">
|
||||||
|
<cfproperty name="entity" type="string"/>
|
||||||
|
<cfproperty name="key" type="string"/>
|
||||||
|
<cfproperty name="status" type="struct"/>
|
||||||
|
<cfproperty name="track" type="struct"/>
|
||||||
|
<cfproperty name="permission" type="integer"/>
|
||||||
|
|
||||||
|
<cfset this.PERMISSION_NONE=0/><!---duplicated in request scope--->
|
||||||
|
<cfset this.PERMISSION_READ=1/><!---duplicated in request scope--->
|
||||||
|
<cfset this.PERMISSION_WRITE=2/><!---duplicated in request scope--->
|
||||||
|
|
||||||
|
<cffunction name="init" access="public">
|
||||||
|
<cfargument name="entity" type="string" required="true"/>
|
||||||
|
<cfargument name="key" type="string" default="#arguments.entity#_id"/>
|
||||||
|
<cfargument name="track" type="struct" required="true"/>
|
||||||
|
|
||||||
|
<cfset this.entity=arguments.entity/>
|
||||||
|
<cfset this.key=arguments.key/>
|
||||||
|
<cfset this.track=arguments.track/>
|
||||||
|
<cfset this.permission=this.PERMISSION_NONE/>
|
||||||
|
<cfset this.status.errorState=false/>
|
||||||
|
<cfset this.status.errorMessage=""/>
|
||||||
|
|
||||||
|
<cfreturn this>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="readPermitted" returntype="boolean">
|
||||||
|
<cfreturn (this.permission GE this.PERMISSION_READ)/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="writePermitted" returntype="boolean">
|
||||||
|
<cfreturn (this.permission GE this.PERMISSION_WRITE)/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="getType" returntype="string">
|
||||||
|
<cfreturn "generic"/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
</cfcomponent>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<cfparam name="ATTRIBUTES.container" type="struct">
|
||||||
|
<cfparam name="ATTRIBUTES.objName" type="string">
|
||||||
|
|
||||||
|
<cfif NOT structKeyExists(ATTRIBUTES.container, ATTRIBUTES.objName)>
|
||||||
|
<cfset structInsert(ATTRIBUTES.container,ATTRIBUTES.objName,structNew())>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset obj=structFind(ATTRIBUTES.container,ATTRIBUTES.objName)>
|
||||||
|
|
||||||
|
<!---
|
||||||
|
Раздельная инициализация позволяет "чинить" поврежденный объект,
|
||||||
|
но
|
||||||
|
*** при этом факт повреждения не обнаруживается
|
||||||
|
--->
|
||||||
|
<cfif NOT structKeyExists(obj, "sort")>
|
||||||
|
<cfset structInsert(obj,"sort", structNew())>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif NOT structKeyExists(obj, "filter")>
|
||||||
|
<cfset structInsert(obj,"filter", structNew())>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfexit method="EXITTAG" />
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<!--- used instead cflocation which, at least for railo, makes cyrillics in url corrupt --->
|
||||||
|
<cfparam name="ATTRIBUTES.url" type="string"/>
|
||||||
|
<cfheader statuscode="302" statustext="Moved Temporarily"/>
|
||||||
|
<cfheader charset="#GetEncoding('URL')#" name="Location" value="#ATTRIBUTES.url#"/>
|
||||||
|
<cfabort/>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.catch"/>
|
||||||
|
<cfparam name="ATTRIBUTES.output" default=""/><!--- deprecated, use instead ATTRIBUTES.status --->
|
||||||
|
|
||||||
|
<cfsavecontent variable="message">
|
||||||
|
<cfoutput>
|
||||||
|
Ошибка при обращении к базе данных. Возможно, программа некорректно сформировала условия фильтра или сортировки. В качестве временной меры попробуйте <a href="#request.thisPage#?reset_filter=yes&sort=reset">сбросить условия фильтра и сортировки</a>. Просьба сообщить об ошибке администратору.<br/>
|
||||||
|
#ATTRIBUTES.catch.message#<br/>
|
||||||
|
#ATTRIBUTES.catch.detail#<br/>
|
||||||
|
</cfoutput>
|
||||||
|
</cfsavecontent>
|
||||||
|
|
||||||
|
<!--- deprecated behavior --->
|
||||||
|
<cfif len(ATTRIBUTES.output)>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.output#"=#message#/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- modern behavior --->
|
||||||
|
<cfif structKeyExists(ATTRIBUTES,"status")>
|
||||||
|
<cfset ATTRIBUTES.status.errorState="Yes"/>
|
||||||
|
<cfset ATTRIBUTES.status.errorMessage="#message#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<cfcomponent extends="generic_page_info" displayname="information container for page with form (detail, filter)" output="false">
|
||||||
|
|
||||||
|
<cfproperty name="recordsPerPage" type="integer"/>
|
||||||
|
<cfproperty name="nStart" type="integer"/>
|
||||||
|
<cfproperty name="settings" type="struct"/>
|
||||||
|
|
||||||
|
<cffunction name="getType" returntype="string">
|
||||||
|
<cfreturn "ls"/>
|
||||||
|
</cffunction>
|
||||||
|
</cfcomponent>
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<cfprocessingdirective suppressWhitespace="YES"><cfsilent>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.page" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.queryString" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.acl" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.label" type="string" default=""/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(#ATTRIBUTES#,"external")>
|
||||||
|
<!--- <cfset class="menu-item-ext"/>--->
|
||||||
|
<cfset target=' target="_blank"'/>
|
||||||
|
<cfelse>
|
||||||
|
<!--- <cfset class="menu-item"/>--->
|
||||||
|
<cfset target=''/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(ATTRIBUTES.queryString)>
|
||||||
|
<cfset pageUrl="#ATTRIBUTES.page#?#ATTRIBUTES.queryString#"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset pageUrl="#ATTRIBUTES.page#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfsilent><!---
|
||||||
|
---><cfmodule template="ac.cfm" obj="#ATTRIBUTES.acl#"><cfoutput><li class="nav-item"><a class="nav-link<cfif (request.thisPage EQ ATTRIBUTES.page)> active</cfif>" href="#pageUrl#"#target#>#ATTRIBUTES.label#</a></li></cfoutput><!---
|
||||||
|
---></cfmodule></cfprocessingdirective><cfexit method="EXITTAG"/>
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<cfparam name="ATTRIBUTES.mimetype"/>
|
||||||
|
<cfswitch expression=#ATTRIBUTES.mimetype#>
|
||||||
|
<cfcase value="text/plain"><img src="img/mimetype/text.png"/></cfcase>
|
||||||
|
<cfcase value="text/html"><img src="img/mimetype/html.png"/></cfcase>
|
||||||
|
<cfcase value="application/pdf"><img src="img/mimetype/pdf.png"/></cfcase>
|
||||||
|
<cfcase value="image/gif,image/png,image/jpeg"><img src="img/mimetype/image.png"/></cfcase>
|
||||||
|
<cfcase value="application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"><img src="img/mimetype/excel.png"/></cfcase>
|
||||||
|
<cfcase value="application/vnd.ms-powerpoint"><img src="img/mimetype/powerpoint.png"/></cfcase>
|
||||||
|
<cfcase value="application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document"><img src="img/mimetype/word.png"/></cfcase>
|
||||||
|
<cfdefaultcase><img src="img/mimetype/document.png"/></cfdefaultcase>
|
||||||
|
</cfswitch><cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<cfsilent><!--- build query sort string --->
|
||||||
|
<!--- v0.3 ---><!--- 2025-04-29 09:13:19 --->
|
||||||
|
<cfparam name="ATTRIBUTES.sortArray" type="array"/>
|
||||||
|
<!--- <cfparam name="ATTRIBUTES.defaultSortArray" type="array" default=#[]#/> оказывается, это уже было --->
|
||||||
|
<cfparam name="ATTRIBUTES.fieldCount" type="integer" default=0/>
|
||||||
|
<cfparam name="ATTRIBUTES.defaultSortArray" type="array" default=#[]#/><!--- #[{"fld":1,"asc":0}]# --->
|
||||||
|
<cfparam name="ATTRIBUTES.defaultOrder" default="1 desc"/><!--- оба варианта не отображаются в реестре, чтобы отображались - надо работать с массивом вне этого модуля, модуль не модифицирует массив сортировки. В общем, оба варианта дефолтной сортировки не вызывают восторга, но пусть остаются - в конце концов, есть не только реестры --->
|
||||||
|
|
||||||
|
<cfif thisTag.executionMode IS "end" OR !thisTag.hasEndTag>
|
||||||
|
|
||||||
|
<cfif ArrayLen(ATTRIBUTES.sortArray) EQ 0>
|
||||||
|
<cfset ATTRIBUTES.sortArray=ATTRIBUTES.defaultSortArray/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset querySort="">
|
||||||
|
|
||||||
|
<cfset i=1>
|
||||||
|
<cfloop array=#ATTRIBUTES.sortArray# index="sort"><!--- index is not an ordinal, but the element of array--->
|
||||||
|
<cftry>
|
||||||
|
<cfif (ATTRIBUTES.fieldCount LE 0) OR (sort.fld LE ATTRIBUTES.fieldCount)>
|
||||||
|
<cfif sort.asc>
|
||||||
|
<cfset querySort=listAppend(querySort," #sort.fld# asc")>
|
||||||
|
<cfelse>
|
||||||
|
<cfset querySort=listAppend(querySort," #sort.fld# desc")>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch type="Any"></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfset i=i+1>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfif len(trim(querySort)) EQ 0>
|
||||||
|
<cfset querySort=#ATTRIBUTES.defaultOrder#>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset querySort=trim(querySort)>
|
||||||
|
|
||||||
|
<cfif isDefined("ATTRIBUTES.output")>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.output#"=querySort/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset thisTag.generatedContent=querySort/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<cfsilent></cfsilent><!--- prepare environment ---><!--- v02 17:35 28.06.2018---><!---14:55 29.06.2018 bug fixed--->
|
||||||
|
<cfimport prefix="m" taglib="../lib"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.entity" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.accessObject" type="string" default=#ATTRIBUTES.entity#/>
|
||||||
|
<cfparam name="ATTRIBUTES.key" type="string" default="#ATTRIBUTES.entity#_id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.pageInfoOut" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.formEncType" type="string" default=""/>
|
||||||
|
<cfparam name="ATTRIBUTES.trackOut" type="string" default="tr"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.thisUrl" default=""/>
|
||||||
|
<cfif len(ATTRIBUTES.thisUrl) EQ 0><!--- наугад используем типовое соглашение--->
|
||||||
|
<cfparam name="#ATTRIBUTES.key#" default="-1"/>
|
||||||
|
<cfset id=evaluate("#ATTRIBUTES.key#")/><!---*** неизящно, зато безопасно---><!--- *** как-то не слишком безопасно --->
|
||||||
|
<cfset ATTRIBUTES.thisUrl="#request.thisPage#?#ATTRIBUTES.key#=#id#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<m:ac obj="#ATTRIBUTES.accessObject#" permissionOut="permission"/>
|
||||||
|
<m:track thisUrl="#ATTRIBUTES.thisUrl#" defaultBackUrl="#ATTRIBUTES.entity#_ls.cfm" output="track"/>
|
||||||
|
|
||||||
|
<cfset pageInfo=CreateObject("component","detail_page_info").init("#ATTRIBUTES.entity#","#ATTRIBUTES.key#",#track#,"#permission#","frm",#request.thisPage#,"POST","#ATTRIBUTES.formEncType#")/>
|
||||||
|
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#track#/>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.pageInfoOut#"=#pageInfo#/>
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<cfsilent><!--- prepare environment --->
|
||||||
|
<cfimport prefix="m" taglib="../lib"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.entity" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.key" type="string" default="#ATTRIBUTES.entity#_id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.pageInfoOut" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.trackOut" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.lsPage" type="string" default="#ATTRIBUTES.entity#_ls"/>
|
||||||
|
|
||||||
|
<m:track thisUrl="#request.thisPage#" defaultBackUrl="#ATTRIBUTES.lsPage#.cfm" output="tr"/>
|
||||||
|
|
||||||
|
<cfset pageInfo=CreateObject("component","filter_page_info").init(#ATTRIBUTES.entity#,"#ATTRIBUTES.key#",#tr#,"frm",#request.thisPage#,"POST","")/>
|
||||||
|
|
||||||
|
<m:settings key="#ATTRIBUTES.lsPage#" output="settings" action="get"/>
|
||||||
|
<cfset pageInfo.settings=#settings#/>
|
||||||
|
|
||||||
|
<m:ac obj="#ATTRIBUTES.entity#" act="#request.PERMISSION_READ#" output="readPermitted"/>
|
||||||
|
<cfset pageInfo.readPermitted=#readPermitted#/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#tr#/>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.pageInfoOut#"=#pageInfo#/>
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<cfsilent><!--- prepare environment --->
|
||||||
|
<cfimport prefix="m" taglib="../lib"/>
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.pageInfoOut" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.trackOut" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.action" type="string" default="#request.thisPage#"/><!--- Если подстановка шаблона делается в application.cfc, то request.thisPage будет соответствовать URL, а не шаблону --->
|
||||||
|
|
||||||
|
<m:track thisUrl="#request.thisPage#" defaultBackUrl="." output="tr"/>
|
||||||
|
|
||||||
|
<cfset pageInfo=CreateObject("component","form_page_info").init("","",#tr#,"frm",#ATTRIBUTES.action#,"POST","")/>
|
||||||
|
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#tr#/>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.pageInfoOut#"=#pageInfo#/>
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
<cfsilent><!--- prepare environment --->
|
||||||
|
<!---v0.2 accessObject--->
|
||||||
|
<cfimport prefix="m" taglib="../lib"/></cfsilent><m:silent silent="No">
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.entity" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.accessObject" type="string" default=#ATTRIBUTES.entity#/>
|
||||||
|
<cfparam name="ATTRIBUTES.key" type="string" default="#ATTRIBUTES.entity#_id"/>
|
||||||
|
<cfparam name="ATTRIBUTES.settingsKey" type="string" default="#ATTRIBUTES.entity#_ls"/>
|
||||||
|
<cfparam name="ATTRIBUTES.pageInfoOut" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.trackOut" type="string"/>
|
||||||
|
<cfparam name="ATTRIBUTES.recordsPerPage" type="integer" default="500"/>
|
||||||
|
|
||||||
|
<cfparam name="nStart" default="1"/>
|
||||||
|
|
||||||
|
<cfif nStart EQ 1>
|
||||||
|
<cfset thisUrl="#request.thisPage#"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset thisUrl="#request.thisPage#?nStart=#nStart#"/>
|
||||||
|
</cfif>
|
||||||
|
<m:track thisUrl="#thisUrl#" defaultBackUrl="." output="tr"/>
|
||||||
|
|
||||||
|
<cfset pageInfo=CreateObject("component","ls_page_info").init("#ATTRIBUTES.entity#","#ATTRIBUTES.key#",#tr#)/>
|
||||||
|
|
||||||
|
<cfset pageInfo.recordsPerPage=#ATTRIBUTES.recordsPerPage#/>
|
||||||
|
<cfset pageInfo.nStart=#nStart#/>
|
||||||
|
<m:ac obj="#ATTRIBUTES.accessObject#" permissionOut="permission"/>
|
||||||
|
<cfset pageInfo.permission=#permission#/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfif isDefined("CALLER.reset_filter")> <!---*** --->
|
||||||
|
<m:settings action="clear" key="#ATTRIBUTES.settingsKey#.filter"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<m:settings key="#ATTRIBUTES.settingsKey#" output="settings"><!---*** грязно: мы работаем с сортировкой, а синхронизируем весь контейнер и получаем его побочным эффектом для дальнейшего использования --->
|
||||||
|
<m:sortparam sort=#settings.sort# sortlength=2/><!---*** вот где устанавливается длина массива сортировки--->
|
||||||
|
</m:settings>
|
||||||
|
<cfset pageInfo.settings=settings/><!--- unlike m:track, output="CALLER.settings" wouldnt't work for m:settings because of some StructFind --->
|
||||||
|
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.trackOut#"=#tr#/>
|
||||||
|
<cfset "CALLER.#ATTRIBUTES.pageInfoOut#"=#pageInfo#/>
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</m:silent>
|
||||||
@@ -0,0 +1,396 @@
|
|||||||
|
<cfcomponent
|
||||||
|
displayname="ReST API helper"
|
||||||
|
output="true"
|
||||||
|
hint="Static helper methods for ReST API">
|
||||||
|
<!--- v0.02 2024-10-15 --->
|
||||||
|
<!--- v0.03 2024-11-27 --->
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="empty2null"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="replace empty field with null">
|
||||||
|
|
||||||
|
<cfargument name="value" type="any" required="true" />
|
||||||
|
|
||||||
|
<cfif isEmpty(ARGUMENTS.value)><!---*** некорректно, но просто, пустые строки превращает в нулл--->
|
||||||
|
<cfreturn javacast('null','')/>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn ARGUMENTS.value />
|
||||||
|
</cfif>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="passThrough"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="just return argument">
|
||||||
|
<cfargument name="x" type="ANY" required="true"/>
|
||||||
|
<cfreturn #ARGUMENTS.x#/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="appendRecord"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="put data into struct, replace empty fields with null">
|
||||||
|
|
||||||
|
<cfargument name="struct" type="struct" required="true" />
|
||||||
|
<cfargument name="key" type="string" required="true" />
|
||||||
|
<cfargument name="fieldMap" type="struct" required="true" />
|
||||||
|
<cfargument name="query" type="query" required="true" />
|
||||||
|
<cfargument name="fieldNameDecorator" type="function" required="false" default=#passThrough#/>
|
||||||
|
|
||||||
|
<cfif len(ARGUMENTS.key)>
|
||||||
|
<cfset ARGUMENTS.struct[ARGUMENTS.key]=structNew()/>
|
||||||
|
<cfset var container=ARGUMENTS.struct[ARGUMENTS.key]/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset var container=ARGUMENTS.struct/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfloop list=#structKeyList(ARGUMENTS.fieldMap)# index="local.col">
|
||||||
|
<cfset var obj=container/>
|
||||||
|
<cfset var formatter = structFind(ARGUMENTS.fieldMap[local.col], "formatter")/>
|
||||||
|
<cfif structKeyExists(ARGUMENTS.fieldMap[#local.col#],"container")>
|
||||||
|
<cfset var subContainerName = structFind(ARGUMENTS.fieldMap[local.col], "container")/>
|
||||||
|
|
||||||
|
<cfif len(subContainerName)>
|
||||||
|
<cfif !structKeyExists(container, subContainerName)>
|
||||||
|
<cfset structInsert(container, subContainerName, structNew())/>
|
||||||
|
</cfif>
|
||||||
|
<cfset obj=container[subContainerName]/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif queryColumnExists(ARGUMENTS.query, local.col)>
|
||||||
|
<cfset obj[ARGUMENTS.fieldNameDecorator(local.col)]=empty2null(formatter(ARGUMENTS.query[local.col][ARGUMENTS.query.currentRow]))/>
|
||||||
|
</cfif>
|
||||||
|
<!---*** не уверен в надежности конструкции во всех реализациях CF, контекст query передастся ли--->
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfreturn ARGUMENTS.struct/>
|
||||||
|
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="parseFilterParams"
|
||||||
|
returntype="struct"
|
||||||
|
output="true"
|
||||||
|
hint="parse and collect filter params, for operators see filter_build">
|
||||||
|
<!---e.g. duration=NEQ:2--->
|
||||||
|
<!---*** add test for multiple values--->
|
||||||
|
<cfargument name="params" type="struct" required="true" />
|
||||||
|
|
||||||
|
<cfset var out=structNew()/>
|
||||||
|
<cfloop collection=#params# index="local.item">
|
||||||
|
|
||||||
|
<cfset var urlParamName=snake2camel(lCase(local.item))/>
|
||||||
|
<cfif structKeyExists(URL,urlParamName)>
|
||||||
|
<cfset var rawValue=URL[urlParamName]/>
|
||||||
|
<cfset var operator="EQ"/>
|
||||||
|
<cfset var value=#rawValue#/>
|
||||||
|
<cfif listLen(rawValue,":") GT 1>
|
||||||
|
<cfset var operator=listGetAt(rawValue,1,":")/>
|
||||||
|
<cfset var value=listGetAt(rawValue,2,":")/>
|
||||||
|
</cfif>
|
||||||
|
<cfif !isValid(ARGUMENTS.params[local.item].type,value)>
|
||||||
|
<cfthrow type="invalidParamValue" message="Filter parameter #urlParamName# value is not a valid #ARGUMENTS.params[local.item].type#"/><!---не выводим значение, чтобы исключить XSS--->
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ARGUMENTS.params[local.item], "prefix") AND len(ARGUMENTS.params[local.item].prefix)>
|
||||||
|
<cfset var fieldName="#ARGUMENTS.params[local.item].prefix#.#local.item#"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset var fieldName=#local.item#/>
|
||||||
|
</cfif>
|
||||||
|
<cfset var rec={field=#fieldName#, val=#value#, ftype=#ARGUMENTS.params[local.item].type#, compare=#operator#}/><!---*** add list--->
|
||||||
|
<cfset structInsert(out, local.item, rec, true)/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</cfloop>
|
||||||
|
<cfreturn out/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="parseOrderBy"
|
||||||
|
returntype="struct"
|
||||||
|
output="false"
|
||||||
|
hint="parse and collect sort order param"><!--- deprecated --->
|
||||||
|
|
||||||
|
<cfargument name="params" type="struct" required="true" />
|
||||||
|
<cfargument name="orderBy" type="string" required="true" />
|
||||||
|
|
||||||
|
<cfset var out={}/>
|
||||||
|
|
||||||
|
<cfloop list=#ARGUMENTS.orderBy# index="local.item">
|
||||||
|
<cfif listLen(local.item,".") GT 1>
|
||||||
|
<cfset var fld=camel2snake(listGetAt(local.item,1,"."))/>
|
||||||
|
<cfswitch expression=#uCase(listGetAt(local.item,2,"."))#>
|
||||||
|
<cfcase value="ASC"><cfset var asc=true/></cfcase>
|
||||||
|
<cfcase value="DESC"><cfset var asc=false/></cfcase>
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfthrow type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" /><!---*** XSS vulnerability, so we would not show the suffix--->
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
<cfelse>
|
||||||
|
<cfset var fld=camel2snake(local.item)/>
|
||||||
|
<cfset var asc=true/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ARGUMENTS.params, fld)>
|
||||||
|
<cfset structInsert(out,"#ARGUMENTS.params[fld].prefix#.#fld#", {fld="#ARGUMENTS.params[fld].prefix#.#fld#",asc=#asc#}, true)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfthrow type="InvalidParamValue" message="Invalid orderBy field"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<cfreturn out/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="parseNumericOrder"
|
||||||
|
returntype="array"
|
||||||
|
output="false"
|
||||||
|
hint="parse and collect sort order param with numeric notation">
|
||||||
|
|
||||||
|
<cfargument name="fieldSet" type="struct" required="true" />
|
||||||
|
<cfargument name="orderBy" type="string" required="true" />
|
||||||
|
|
||||||
|
<cfset var out=[]/>
|
||||||
|
|
||||||
|
<cfloop list=#ARGUMENTS.orderBy# index="local.item">
|
||||||
|
<cfif listLen(local.item,".") GT 1>
|
||||||
|
<cfset var fld=camel2snake(listGetAt(local.item,1,"."))/>
|
||||||
|
<cfswitch expression=#uCase(listGetAt(local.item,2,"."))#>
|
||||||
|
<cfcase value="ASC"><cfset var asc=true/></cfcase>
|
||||||
|
<cfcase value="DESC"><cfset var asc=false/></cfcase>
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfthrow type="InvalidParamValue" message="Invalid orderBy format" detail="orderBy suffix should be '.asc' or '.desc'" /><!---*** XSS vulnerability, so we would not show the suffix--->
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
<cfelse>
|
||||||
|
<cfset var fld=camel2snake(local.item)/>
|
||||||
|
<cfset var asc=true/>
|
||||||
|
</cfif>
|
||||||
|
<cfif structKeyExists(ARGUMENTS.fieldSet, fld)>
|
||||||
|
<cfset arrayAppend(out,{fld="#ARGUMENTS.fieldSet[fld].ordinal#",asc=#asc#})/>
|
||||||
|
<cfelse>
|
||||||
|
<cfthrow type="InvalidParamValue" message="Invalid orderBy field"/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
<cfreturn out/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<!---<cffunction name="snake2camel"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="convert snake style name to camel style name">
|
||||||
|
|
||||||
|
<cfargument name="snake" type="string" required="true" />
|
||||||
|
|
||||||
|
<cfset var snake=ARGUMENTS.snake/>
|
||||||
|
<cfset var camel=""/>
|
||||||
|
<cfset var pos=1/>
|
||||||
|
<cfloop condition="true">
|
||||||
|
<cfset next=reFind("(_[a-z])", snake, pos, false)/>
|
||||||
|
<cfif NOT next GT 0>
|
||||||
|
<cfbreak/>
|
||||||
|
</cfif>
|
||||||
|
<cfset camel="#camel##mid(snake,pos,next-pos)##uCase(mid(snake,next+1,1))#"/>
|
||||||
|
<cfset pos=next+2/>
|
||||||
|
</cfloop>
|
||||||
|
<cfset camel="#camel##mid(snake,pos,len(snake))#"/>
|
||||||
|
|
||||||
|
<cfreturn #camel#/>
|
||||||
|
</cffunction>--->
|
||||||
|
|
||||||
|
<!---not used any more--->
|
||||||
|
<cffunction name="query4json" access="public" returntype="any" output="false"
|
||||||
|
hint="Converts query to an array of structs. Key names are lowercase. Empty fields are treated as nulls">
|
||||||
|
|
||||||
|
<cfargument name="Query" type="query" required="true" />
|
||||||
|
|
||||||
|
<cfset var out=arrayNew(1)/>
|
||||||
|
<cfset var i=0/>
|
||||||
|
|
||||||
|
<cfloop query=ARGUMENTS.Query>
|
||||||
|
<cfset i=i+1/>
|
||||||
|
<cfset out[i]=structNew()/>
|
||||||
|
<cfloop list=#ARGUMENTS.Query.ColumnList# index="local.col">
|
||||||
|
<cfset var value=ARGUMENTS.Query[local.col][currentRow]/>
|
||||||
|
<cfif isEmpty(value)><!---*** некорректно, но просто, пустые строки превращает в нулл--->
|
||||||
|
<cfset out[i][local.col]=javacast('null','')/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset out[i][local.col]=ARGUMENTS.Query[local.col][currentRow]/>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfreturn(out)/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="snake2camel"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="convert snake style name to camel style name">
|
||||||
|
<cfargument name="snake" type="string" required="true" />
|
||||||
|
<cfreturn #reReplace(ARGUMENTS.snake,"_([a-z])","\u\1","ALL")#/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="camel2snake"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="convert camel style name to snake style name">
|
||||||
|
<cfargument name="snake" type="string" required="true" />
|
||||||
|
<cfreturn #reReplace(ARGUMENTS.snake,"([A-Z])","_\l\1","ALL")#/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="formatMessage"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="true"
|
||||||
|
hint="formats Exception for display">
|
||||||
|
|
||||||
|
<cfargument name="message" type="string" required="true" />
|
||||||
|
<cfargument name="title" type="string" required="false" />
|
||||||
|
|
||||||
|
<cfset var title=structKeyExists(ARGUMENTS,"title") ? #ARGUMENTS.title# : #ARGUMENTS.message# />
|
||||||
|
|
||||||
|
<cfreturn {type="about:blank", title="#title#", detail="#ARGUMENTS.message#"}/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="formatException"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="true"
|
||||||
|
hint="formats Exception for display">
|
||||||
|
|
||||||
|
<cfargument name="ex" type="struct" required="true" />
|
||||||
|
<cfargument name="title" type="string" required="false" />
|
||||||
|
|
||||||
|
<cfset var detail = listAppend(#ARGUMENTS.ex.message#,#ARGUMENTS.ex.detail#,": " )/>
|
||||||
|
<cfset var title = structKeyExists(ARGUMENTS,"title") ? #ARGUMENTS.title# : #ARGUMENTS.ex.message# />
|
||||||
|
|
||||||
|
<cfreturn formatMessage(detail, title) />
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="formatBadRequestError"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="true"
|
||||||
|
hint="formats Bad Request Exception">
|
||||||
|
|
||||||
|
<cfargument name="ex" type="struct" required="true" />
|
||||||
|
|
||||||
|
<cfset var detail=listAppend(#ARGUMENTS.ex.message#,#ARGUMENTS.ex.detail#,":" )/>
|
||||||
|
|
||||||
|
<cfreturn {type="about:blank", title="Bad Request", detail="#detail#"}/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<!---<cffunction name="wrapResultSet"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="true"
|
||||||
|
hint="format resultset as json">
|
||||||
|
|
||||||
|
<cfargument name="qRead" type="query" required="true" />
|
||||||
|
<cfargument name="titleMap" type="struct" required="true" />
|
||||||
|
<cfargument name="startrow" type="integer" required="false" default="1" />
|
||||||
|
|
||||||
|
<cfset var resultSet=[]/>
|
||||||
|
<cfloop query=#ARGUMENTS.qRead# startRow=#ARGUMENTS.startrow#>
|
||||||
|
<cfset var rec={}/>
|
||||||
|
|
||||||
|
<cfset appendRecord(rec, "", ARGUMENTS.titleMap, ARGUMENTS.qRead, snake2camel)/>
|
||||||
|
<cfset arrayAppend(resultSet, rec)/>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<cfset var out={
|
||||||
|
pageSize=
|
||||||
|
}/>
|
||||||
|
|
||||||
|
|
||||||
|
<cfcontent type="application/json"/>
|
||||||
|
<cfoutput>{
|
||||||
|
"pageSize":#pageSize#,
|
||||||
|
"page":#page#,
|
||||||
|
"orderBy":"#orderBy#",
|
||||||
|
"size":"#arrayLen(resultSet)#",
|
||||||
|
"total":"#qTotal.cnt#",
|
||||||
|
"results":#serializeJson(resultSet)#,
|
||||||
|
"queryDurationMs":#queryDurationMs#, <cfset runDurationMs=getTickCount()-request.startTickCount/>
|
||||||
|
"runDurationMs":#runDurationMs#
|
||||||
|
}</cfoutput>--->
|
||||||
|
<cffunction name="validateField"
|
||||||
|
access="public"
|
||||||
|
returntype="any"
|
||||||
|
output="false"
|
||||||
|
hint="validate field of a structure, if exisits">
|
||||||
|
|
||||||
|
<cfargument name="struct" type="struct" required=true/>
|
||||||
|
<cfargument name="name" required=true/>
|
||||||
|
<cfargument name="type" default="string"/>
|
||||||
|
<cfargument name="required" type="boolean" default="false"/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(ARGUMENTS.struct, ARGUMENTS.name)>
|
||||||
|
<cfif NOT isValid(ARGUMENTS.type, ARGUMENTS.struct[ARGUMENTS.name])>
|
||||||
|
<cfthrow type="invalidParamValue" message="Parameter validation failed" detail="Supplied value of '#ARGUMENTS.name#' is not a valid #ARGUMENTS.type#"/>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<cfif ARGUMENTS.required>
|
||||||
|
<cfthrow type="invalidParamValue" message="Required field not supplied" detail="Required field #ARGUMENTS.name# not found"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<!--- returns false if the key does not exist and throws exception on invalid format--->
|
||||||
|
<cffunction name="keyExistsAndValid" access="public" returntype="boolean">
|
||||||
|
<cfargument name="struct" type="struct" required=true/>
|
||||||
|
<cfargument name="key" type="string" required=true/>
|
||||||
|
<cfargument name="type" type="string" required=false/>
|
||||||
|
<cfargument name="maxLength" type="numeric" required=false hint="Checked for strings only, negative value means no limit"/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(arguments.struct, arguments.key)>
|
||||||
|
<cfif structKeyExists(arguments, "type")>
|
||||||
|
<cfset var x=arguments.struct[arguments.key]/>
|
||||||
|
<cfif isValid(arguments.type, x)>
|
||||||
|
<cfif lCase(arguments.type) EQ "string">
|
||||||
|
<cfif structKeyExists(arguments, "maxLength")>
|
||||||
|
<cfif ARGUMENTS.maxLength GE 0>
|
||||||
|
<cfif len(x) GT #arguments.maxLength#>
|
||||||
|
<cfthrow type="invalidParamValue" message="Invalid parameter value (string too long)" detail="Parameter #arguments.key# length #len(x)# is greater than #arguments.maxLength#" errorcode=400/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<cfthrow type="invalidParamValue" message="Invalid parameter value" detail="Parameter #arguments.key# is not a valid #arguments.type#" errorcode=400/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<cfreturn true/>
|
||||||
|
<cfelse>
|
||||||
|
<cfreturn false/>
|
||||||
|
</cfif>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<!--- returns false if the key does not exist or has invalid format--->
|
||||||
|
<cffunction name="keyExistsAndValidGraceful" access="public" returntype="boolean">
|
||||||
|
<cfargument name="struct" type="struct" required=true/>
|
||||||
|
<cfargument name="key" type="string" required=true/>
|
||||||
|
<cfargument name="type" type="string" required=false/>
|
||||||
|
<cfargument name="maxLength" type="numeric" required=false default="-1" hint="Checked for strings only"/>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfreturn keyExistsAndValid(ARGUMENTS.struct, ARGUMENTS.key, ARGUMENTS.type, ARGUMENTS.maxLength)/>
|
||||||
|
<cfcatch type="ANY"><!---<cfrethrow/>---></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfreturn false/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
</cfcomponent>
|
||||||
@@ -0,0 +1,216 @@
|
|||||||
|
<!--- *** Refactoring required --->
|
||||||
|
<!--- v0.3
|
||||||
|
2017-08-30
|
||||||
|
Настройки вынесены в таблицу usr_settings в связи с миграцией репозитория пользователей в мастер справочники
|
||||||
|
Заодно cfqueryparam
|
||||||
|
|
||||||
|
модуль должен инкапсулировать работу с настройками
|
||||||
|
- хранение в сессии и в БД, синхронность с базой
|
||||||
|
- чтение и модификацию
|
||||||
|
- очистку(сброс)
|
||||||
|
При этом модуль ничего не должен знать о структуре самих настроек (фильтрах, сортировках и т.д.)
|
||||||
|
|
||||||
|
Жизненный цикл контейнера settings
|
||||||
|
1. Живет в сессии
|
||||||
|
2. Если в сессии нет settings - вычитывается из БД
|
||||||
|
3. Если и в БД нет - инициализируется и сразу сохраняется в БД (пустой)
|
||||||
|
4. При модификации синхронизируется с базой (пишет в нее) - нужно либо снаружи вызывать сохранение, либо инкапсулировать запись
|
||||||
|
(инкапсулировать запись - сильно усложнит код)
|
||||||
|
5. Для работы выдает КОПИЮ данных, соответственно, при сохранении должен ее синхронизировать???
|
||||||
|
Пока решили, что будем выдавать ссылку на объект в сессии, при этом нужно блок кода синхронизировать CFLOCK
|
||||||
|
При этом 2 проблемы:
|
||||||
|
- некрасивый код (лок на сессию руками, связь совершенно неявная)
|
||||||
|
- избыточная блокировка
|
||||||
|
- избыточное сохранение (можно было бы проверять на изменение)
|
||||||
|
чтобы действовать без блокировки, можно экспортировать объект в безопасный scope - переменные или реквест
|
||||||
|
Альтернативный синтаксис вызова
|
||||||
|
<m:settings key="page_name" output="settings"><!--- implicit get --->
|
||||||
|
<m:sortparam sort=#settings.sort# fieldcount=9 sortlength=2/><!--- any modification --->
|
||||||
|
</m:settings><!--- implicit sync --->
|
||||||
|
|
||||||
|
|
||||||
|
Разрешение конфликта версий при обновлении софта
|
||||||
|
Конфликт версий: код требует параметр, отсутствующий в сохраненной версии. Например, одна версия фильтра сохранила свои настройки,
|
||||||
|
а другая, более новая, пытается прочесть несуществующий ключ.
|
||||||
|
Другой вариант - несуществующее поле (убранное в очередной версии) - этот конфликт проявится только при запросе к БД.
|
||||||
|
Варианты решения
|
||||||
|
- создание параметра с дефолтными значениями при запросе (во все запросы добавить значения по умолчанию). Недостаток - возможно создание помойки
|
||||||
|
- контроль номера версии. Выглядит сложным в сопровождении
|
||||||
|
*** Возможно, это была плохая идея - разрешить доступ по длинному ключу. Когда доставали и записывали целиком объект, было меньше возможностей для ошибок
|
||||||
|
--->
|
||||||
|
|
||||||
|
<!---*** обработка вызова get-sync c закрывающим тегом --->
|
||||||
|
<cfparam name="ATTRIBUTES.action" type="string" default=""/>
|
||||||
|
<cfparam name="obj" type="struct" default=#structNew()#/>
|
||||||
|
<cfparam name="doSyncOnClose" type="boolean" default="No"/>
|
||||||
|
|
||||||
|
<cfif thisTag.executionMode IS "start">
|
||||||
|
<cfif ATTRIBUTES.action IS "">
|
||||||
|
<cfset ATTRIBUTES.action="get"/>
|
||||||
|
<cfset doSyncOnClose="Yes"/>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<cfif doSyncOnClose>
|
||||||
|
<cfset ATTRIBUTES.action="sync"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset useDB="false"/><!--- --->
|
||||||
|
|
||||||
|
<cfswitch expression=#lCase(ATTRIBUTES.action)#>
|
||||||
|
|
||||||
|
<cfcase value="create"> <!--- конструктор --->
|
||||||
|
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||||||
|
<cfset session.settings = retrieveSettings()>
|
||||||
|
</cflock>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="sync">
|
||||||
|
<!--- Синхронизировать измененный объект (с сессией и персистенсом) --->
|
||||||
|
<cfparam name="ATTRIBUTES.key" type="string">
|
||||||
|
<cfif doSyncOnClose>
|
||||||
|
<cfif thisTag.executionMode IS "end">
|
||||||
|
<cfset obj=StructFind(CALLER, "#ATTRIBUTES.output#")/>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<cfparam name="ATTRIBUTES.obj" type="struct">
|
||||||
|
<cfset obj=#ATTRIBUTES.obj#/>
|
||||||
|
</cfif>
|
||||||
|
<!---<cfdump var=#obj#/>--->
|
||||||
|
|
||||||
|
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||||||
|
<!--- помещаем объект на свое место в структуре --->
|
||||||
|
<cfset container=#session.settings#>
|
||||||
|
<cfset cnt = listLen("#ATTRIBUTES.key#", ".")>
|
||||||
|
<cfloop index="i" from=1 to=#cnt#>
|
||||||
|
<cfset item = listGetAt(#ATTRIBUTES.key#, i, ".")>
|
||||||
|
<cfif i EQ cnt><!--- last element --->
|
||||||
|
<cfset structInsert(#container#, #item#, #obj#, "YES")>
|
||||||
|
<cfelse>
|
||||||
|
<!--- попытка перебдеть--->
|
||||||
|
<cfif NOT structKeyExists(#container#,#item#)>
|
||||||
|
<cfset structInsert(#container#,#item#,#structNew()#)>
|
||||||
|
</cfif>
|
||||||
|
<cfset container=structFind(container,item)>
|
||||||
|
</cfif>
|
||||||
|
</cfloop>
|
||||||
|
</cflock>
|
||||||
|
<!--- сохраняем настройки в персистенс --->
|
||||||
|
<cfset saveSettings(container)>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
|
||||||
|
<cfcase value="reset"><!--- *** надо было назвать reset_all или как-то так --->
|
||||||
|
<!--- Сбросить все пользовательские настройки --->
|
||||||
|
<cfset session.settings=structNew()>
|
||||||
|
<cfset saveSettings(session.settings)>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="clear">
|
||||||
|
<!--- Очистить секцию пользовательских настроек (sort или filter)--->
|
||||||
|
<cfparam name="ATTRIBUTES.key" type="string">
|
||||||
|
<cfset targetFullName = "session.settings.#ATTRIBUTES.key#">
|
||||||
|
<cfset objName = listGetAt(ATTRIBUTES.key,1,".")>
|
||||||
|
<cfif isDefined(targetFullName)>
|
||||||
|
<cflock scope="SESSION" type="EXCLUSIVE" timeout="3">
|
||||||
|
<cfset evaluate("structClear(#targetFullName#)")>
|
||||||
|
<cfset obj = evaluate(#targetFullName#)>
|
||||||
|
<cfmodule template="settings.cfm" action="sync" key=#ATTRIBUTES.key# obj=#obj#><!--- синхронизация при очистке используется ---><!--- хорошо ли это? А вообще надо?--->
|
||||||
|
</cflock>
|
||||||
|
</cfif>
|
||||||
|
</cfcase>
|
||||||
|
|
||||||
|
<cfcase value="get">
|
||||||
|
<!---
|
||||||
|
Получить объект или секцию
|
||||||
|
Обязательный параметр key - имя ключа в структуре, начинающееся с имени объекта, через точку
|
||||||
|
В параметре output имя переменной, принимающей результат
|
||||||
|
--->
|
||||||
|
<cfparam name="ATTRIBUTES.key" type="string">
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string">
|
||||||
|
|
||||||
|
<cfif NOT isDefined("session.settings")><!--- *** container--->
|
||||||
|
<cfmodule template="settings.cfm" action="create"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset targetFullName = "session.settings.#ATTRIBUTES.key#">
|
||||||
|
<cfset objName = listGetAt(ATTRIBUTES.key, 1, ".")>
|
||||||
|
<cfif (NOT structKeyExists(#session.settings#, objName))
|
||||||
|
OR structIsEmpty(structFind(#session.settings#, objName))>
|
||||||
|
<cfmodule template="init_settings.cfm" container=#session.settings# objName=#objName#/>
|
||||||
|
<!--- несколько неуклюже, лучше просто возвращать объект и потом класть в сессию --->
|
||||||
|
</cfif>
|
||||||
|
<cftry>
|
||||||
|
<cfset obj=evaluate(targetFullName)>
|
||||||
|
<cfcatch type="ANY">
|
||||||
|
<cfmodule template="init_settings.cfm" container=#session.settings# objName=#objName#/><!---*** грубо --->
|
||||||
|
<cfset obj=evaluate(targetFullName)>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#", #duplicate(obj)#)>
|
||||||
|
</cfcase>
|
||||||
|
<cfdefaultcase>
|
||||||
|
<cfthrow message="Unsupported action = #ATTRIBUTES.action# in settings.cfm">
|
||||||
|
</cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
<!---***debug <cfoutput>#ATTRIBUTES.action# #thisTag.executionMode# #thisTag.hasendtag#</cfoutput>--->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!--- --------------------------------------------------------------------------------- --->
|
||||||
|
<!--- private methods --->
|
||||||
|
<cffunction name="retrieveSettings" access="private">
|
||||||
|
<cfif useDB><!--- можно перегрузить методы для разных провайдеров персистенса, но пока смысла в этом не видно, код достаточно примитивен и управляем --->
|
||||||
|
<cfquery name="qGet" datasource="#request.DS#">
|
||||||
|
select settings from usr where usr_id='#request.usr_id#'
|
||||||
|
</cfquery>
|
||||||
|
<cfif len(qGet.settings) GT 0>
|
||||||
|
<cftry>
|
||||||
|
<CFWDDX ACTION='wddx2cfml' input=#qGet.settings# output='settings'/>
|
||||||
|
<cfreturn #settings#/>
|
||||||
|
<cfcatch type="Any">Err:
|
||||||
|
<cfoutput>#cfcatch.message#</cfoutput>
|
||||||
|
<cfabort/>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
<cfreturn #structNew()#/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="saveSettings" access="private">
|
||||||
|
<cfargument name=settings>
|
||||||
|
|
||||||
|
<cfif !isNumeric(request.usr_id)
|
||||||
|
OR !(request.usr_id GT 0)
|
||||||
|
OR request.usr_id EQ request.ANONYMOUS_USR_ID
|
||||||
|
OR request.usr_id EQ request.UNDEFINED_USR_ID>
|
||||||
|
<cfreturn />
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif useDB>
|
||||||
|
<cfquery name="qCheckExistence" datasource="#request.DS#">
|
||||||
|
select count(*) as cnt from usr_serrings where usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<CFWDDX ACTION='cfml2wddx' input=#session.settings# output='strSettings'/>
|
||||||
|
|
||||||
|
<cfif qCheckExistence.cnt EQ 0>
|
||||||
|
<cfquery name="qSave" datasource="#request.DS#">
|
||||||
|
insert int usr_settings (usr_id, settings)
|
||||||
|
values(<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>, <cfqueryparam cfsqltype="cf_sql_varchar" value='#strSettings#'/>)
|
||||||
|
</cfquery>
|
||||||
|
<cfelse>
|
||||||
|
<cfquery name="qSave" datasource="#request.DS#">
|
||||||
|
update usr_settings
|
||||||
|
set settings=<cfqueryparam cfsqltype="cf_sql_varchar" value='#strSettings#'/>
|
||||||
|
where usr_id=<cfqueryparam cfsqltype="cf_sql_integer" value='#request.usr_id#'/>
|
||||||
|
</cfquery>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<!--- do nothing --->
|
||||||
|
</cfif>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<!--- Управляемая имитация cfsilent (с возможностью отключения для отладки) --->
|
||||||
|
<cfif thisTag.executionMode IS "end">
|
||||||
|
<cfparam name="ATTRIBUTES.silent" type="boolean" default="Yes"/>
|
||||||
|
<cfif ATTRIBUTES.silent>
|
||||||
|
<cfset thisTag.generatedContent=""/>
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
<cfsilent>
|
||||||
|
<cfparam name="ATTRIBUTES.sort" type="struct">
|
||||||
|
<cfparam name="ATTRIBUTES.fieldcount" type="numeric" default="0">
|
||||||
|
<cfparam name="ATTRIBUTES.sortlength" type="numeric" default="1">
|
||||||
|
|
||||||
|
<cfif NOT isDefined("ATTRIBUTES.sort.sortArray")>
|
||||||
|
<cfset structInsert(ATTRIBUTES.sort,"sortArray",arrayNew(1),"TRUE")>
|
||||||
|
</cfif>
|
||||||
|
<cfset sortArray=ATTRIBUTES.sort.sortArray>
|
||||||
|
|
||||||
|
<cfif evaluate("isDefined('CALLER.sort')")>
|
||||||
|
<cfset evaluate("val=(CALLER.sort)")>
|
||||||
|
<!--- parse --->
|
||||||
|
<cfif val EQ "reset">
|
||||||
|
<cfset ATTRIBUTES.sort.sortArray=arrayNew(1)>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
<cfset pos=find("a",val)>
|
||||||
|
<cfif pos LE 0>
|
||||||
|
<cfset pos=find("d",val)>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif pos LE 1>
|
||||||
|
<cfexit>
|
||||||
|
<cfelseif pos GT 1>
|
||||||
|
<cfset fldNum=left(val,pos-1)>
|
||||||
|
<cfset fldAsc=mid(val,pos,1)>
|
||||||
|
<cfelse>
|
||||||
|
<cfset fldNum=val>
|
||||||
|
<cfset fldAsc="a">
|
||||||
|
</cfif>
|
||||||
|
<!---/parse --->
|
||||||
|
<!--- validate --->
|
||||||
|
<cfif NOT isNumeric(fldNum)>
|
||||||
|
<cfexit>
|
||||||
|
</cfif>
|
||||||
|
<cfif (ATTRIBUTES.fieldcount GT 0) AND (fldNum GT ATTRIBUTES.fieldcount)>
|
||||||
|
<cfexit>
|
||||||
|
</cfif>
|
||||||
|
<!---/validate --->
|
||||||
|
<cfelse>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset sortItem=structNew()>
|
||||||
|
<cfset structInsert(sortItem,"fld",fldNum)>
|
||||||
|
<cfset structInsert(sortItem,"asc",(fldAsc EQ "a"))>
|
||||||
|
|
||||||
|
<!--- drop all occurences of this field --->
|
||||||
|
<cfset i=1>
|
||||||
|
<cfloop condition="i LE arrayLen(sortArray)">
|
||||||
|
<cftry>
|
||||||
|
<cfif sortArray[i].fld EQ sortItem.fld>
|
||||||
|
<cfset arrayDeleteAt(sortArray,i)>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch type="Any">
|
||||||
|
<cfset arrayDeleteAt(sortArray,i)>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
<cfset i=i+1>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- insert item --->
|
||||||
|
<cfset arrayPrepend(sortArray, sortItem)>
|
||||||
|
|
||||||
|
<!--- truncate array--->
|
||||||
|
<cfloop condition="arrayLen(sortArray) GT ATTRIBUTES.sortlength">
|
||||||
|
<cfset arrayDeleteAt(sortArray,arrayLen(sortArray))>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<!--- save --->
|
||||||
|
<cfset ATTRIBUTES.sort.sortArray=sortArray>
|
||||||
|
<!---/save --->
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfsilent>
|
||||||
+103
@@ -0,0 +1,103 @@
|
|||||||
|
<cfsilent><!--- утилита для передачи стека вызовов через URL --->
|
||||||
|
<!---v1.01 14:00 03.04.2019 added output param thisURL--->
|
||||||
|
<!---v1.02 06:16 26.12.2019 symbols -._ are not encoded --->
|
||||||
|
|
||||||
|
<cfparam name="ATTRIBUTES.thisUrl" type="string" default=""><!--- текущий URL без трэка --->
|
||||||
|
<cfparam name="ATTRIBUTES.output" type="string" default="tr">
|
||||||
|
<cfparam name="ATTRIBUTES.input" type="string" default="track"><!--- имя параметра в URL или FORM, в которой лежит текущий трэк --->
|
||||||
|
<cfparam name="ATTRIBUTES.urlParamName" type="string" default="#ATTRIBUTES.input#">
|
||||||
|
<cfparam name="ATTRIBUTES.defaultBackUrl" type="string">
|
||||||
|
|
||||||
|
<!---<cffunction name="urlSafeWrap">
|
||||||
|
<cfargument name="s" type="string"/>
|
||||||
|
<cfreturn replace(URLEncodedFormat(s), "%", "!", "ALL")/>
|
||||||
|
</cffunction>--->
|
||||||
|
<!---<cffunction name="urlSafeWrap">
|
||||||
|
<cfargument name="s" type="string"/>
|
||||||
|
<cfreturn
|
||||||
|
replace(
|
||||||
|
replaceList(
|
||||||
|
URLEncodedFormat(s),
|
||||||
|
"#URLEncodedFormat('_')#,#URLEncodedFormat('.')#,#URLEncodedFormat('-')#,#URLEncodedFormat('~')#,#URLEncodedFormat('=')#",
|
||||||
|
"_,.,-,~,="
|
||||||
|
),
|
||||||
|
"%", "!", "ALL"
|
||||||
|
)/>
|
||||||
|
</cffunction>--->
|
||||||
|
|
||||||
|
<cffunction name="urlSafeWrap">
|
||||||
|
<cfargument name="s" type="string"/>
|
||||||
|
<cfreturn
|
||||||
|
replace(
|
||||||
|
replaceList(
|
||||||
|
URLEncodedFormat(s),
|
||||||
|
"#URLEncodedFormat('_')#,#URLEncodedFormat('.')#,#URLEncodedFormat('-')#",
|
||||||
|
"_,.,-"
|
||||||
|
),
|
||||||
|
"%", "!", "ALL"
|
||||||
|
)/><!---unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" https://www.ietf.org/rfc/rfc3986.txt--->
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="urlSafeUnWrap">
|
||||||
|
<cfargument name="s" type="string"/>
|
||||||
|
<cfreturn URLDecode(replace(s, "!", "%","ALL"))/>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cfif StructKeyExists(form, ATTRIBUTES.input)>
|
||||||
|
<cfset track=structFind(form, ATTRIBUTES.input)/>
|
||||||
|
<cfelseif StructKeyExists(url, ATTRIBUTES.input)>
|
||||||
|
<cfset track=structFind(url, ATTRIBUTES.input)/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset track=""/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- для предохранения спецсимволов (особенно запятых) в урле используется заворачивание в URLEncodedFormat. ColdFusion (по крайней мере старые версии) не умеет возвращать множественные значения иначе, как списком через запятую. Чтобы избежать непрошенного разворачивания URLEncodedFormat при отправке GETом, мы заменяем % на !.
|
||||||
|
Реализовал вариант с заворачиванием в GZIP, работает неплохо, но вариант с URLEncodedFormat сохраняет хоть какую-то читабельность.
|
||||||
|
Можно было бы кодировать в base64. Или вообще хранить на сервере в сессии, а передавать хэш - но непонятно, когда можно забыть этот урл, а накапливать их зря в сессии не хочется.
|
||||||
|
Попробовал Base64 - очень плохо, все размножается при повторном кодировании и начинает виснуть.
|
||||||
|
--->
|
||||||
|
|
||||||
|
<cfset trackBack="">
|
||||||
|
|
||||||
|
<cfif listLen(track) GT 0>
|
||||||
|
<cfset trackBack=ListDeleteAt(track, listLen(track))>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset trackForward=ListAppend(track, urlSafeWrap(ATTRIBUTES.thisUrl))/>
|
||||||
|
<cfset backUrlBase=urlSafeUnWrap(listLast(track))/>
|
||||||
|
|
||||||
|
<cfif len(backUrlBase) GT 0>
|
||||||
|
<cfif len(trackBack) GT 0>
|
||||||
|
<cfif find("?", backUrlBase) GT 0>
|
||||||
|
<!--- has query string --->
|
||||||
|
<cfset backUrl="#backUrlBase#&#ATTRIBUTES.urlParamName#=#trackBack#"/>
|
||||||
|
<cfelse>
|
||||||
|
<!--- no query string --->
|
||||||
|
<cfset backUrl="#backUrlBase#?#ATTRIBUTES.urlParamName#=#trackBack#"/>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<cfset backUrl="#backUrlBase#"/>
|
||||||
|
</cfif>
|
||||||
|
<cfelse>
|
||||||
|
<cfset backUrl="#ATTRIBUTES.defaultBackUrl#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif NOT (find("?", ATTRIBUTES.thisUrl) GT 0)>
|
||||||
|
<cfset selfUrl="#ATTRIBUTES.thisUrl#?#ATTRIBUTES.urlParamName#=#track#"/>
|
||||||
|
<cfelse>
|
||||||
|
<cfset selfUrl="#ATTRIBUTES.thisUrl#&#ATTRIBUTES.urlParamName#=#track#"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#", structNew())/>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.fw", trackForward)/><!--- значение стэка: &track=#tr.fw# --->
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.fwx", "#ATTRIBUTES.urlParamName#=#trackForward#")/><!--- готовый суффикс для URL: &#tr.fwx#--->
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.bk", trackBack)/>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.backUrl", backUrl)/>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.self", track)/>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.selfUrl", selfUrl)/>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.thisUrl", ATTRIBUTES.thisUrl)/>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.len", listLen(track))/>
|
||||||
|
|
||||||
|
<cfexit method="exitTag"/>
|
||||||
|
</cfsilent>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<cfparam name="ATTRIBUTES.field"/>
|
||||||
|
<cfparam name="ATTRIBUTES.output"/>
|
||||||
|
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.ok", "No")/>
|
||||||
|
|
||||||
|
<cfif !structKeyExists(FORM, ATTRIBUTES.field)>
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cffile
|
||||||
|
action="upload"
|
||||||
|
filefield="#ATTRIBUTES.field#"
|
||||||
|
destination="#GetTempDirectory()#/"
|
||||||
|
nameconflict="MAKEUNIQUE"
|
||||||
|
/>
|
||||||
|
<cfcatch>
|
||||||
|
<!---<cfdump var=#cfcatch#/><cfrethrow/>--->
|
||||||
|
<cfexit method="exittag"/>
|
||||||
|
</cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
||||||
|
<cfset pathOnServer="#cffile.ServerDirectory#/#cffile.ServerFile#"/>
|
||||||
|
<cfset contentType="#cffile.ContentType#/#cffile.ContentSubType#"/>
|
||||||
|
<cfset fileName="#cffile.ClientFile#"/>
|
||||||
|
|
||||||
|
<cffile
|
||||||
|
action="ReadBinary"
|
||||||
|
file="#pathOnServer#"
|
||||||
|
variable="binaryData"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<cffile action="DELETE" file="#pathOnServer#"/>
|
||||||
|
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.fileName", "#fileName#")/>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.contentType", "#contentType#")/>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.binaryData", #binaryData#)/>
|
||||||
|
<cfset setVariable("CALLER.#ATTRIBUTES.output#.ok", "Yes")/>
|
||||||
|
|
||||||
|
<cfexit method="exittag"/>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<cfapplication name="deck.utils" applicationTimeout = #CreateTimeSpan(3, 0, 0, 0)# />
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<cfparam name="d" default=""/>
|
||||||
|
|
||||||
|
<cftry>
|
||||||
|
<cfif structKeyExists(form,"enc")>
|
||||||
|
<cfset d=toBase64(d)/>
|
||||||
|
<cfelseif structKeyExists(form,"dec")>
|
||||||
|
<cfset d=ToString(ToBinary(d))/>
|
||||||
|
</cfif>
|
||||||
|
<cfcatch type="ANY"><cfoutput>#cfcatch.message# #cfcatch.detail#</cfoutput></cfcatch>
|
||||||
|
</cftry>
|
||||||
|
|
||||||
|
<form action="" method="POST">
|
||||||
|
<textarea name="d" rows="10" cols="90"><cfoutput>#d#</cfoutput></textarea>
|
||||||
|
<button type="submit" name="enc">Encode to Base64</button>
|
||||||
|
<button type="submit" name="dec">Decode from Base64</button>
|
||||||
|
</form>
|
||||||
@@ -0,0 +1,259 @@
|
|||||||
|
<!--- примитивный конструктор селектов, чтобы не набирать руками колонки --->
|
||||||
|
<!--- v0.1 2018.02.27 --->
|
||||||
|
<!--- v0.3 13.11.2020 --->
|
||||||
|
<!--- v0.4 16.11.2020 for rest api--->
|
||||||
|
<cfsetting enablecfoutputonly="false"/>
|
||||||
|
<!---<cfimport prefix="m" taglib="../../../lib"/>--->
|
||||||
|
<!---<cfimport prefix="d" taglib="lib/data"/>--->
|
||||||
|
|
||||||
|
<cfparam name="obj" default=""/>
|
||||||
|
<!---<cfparam name="ds" default="metadata"/>--->
|
||||||
|
<cfparam name="out" default="field_set,bean"/>
|
||||||
|
<cfparam name="alias" default=""/>
|
||||||
|
<cfparam name="dt_format_120" default="0"/>
|
||||||
|
|
||||||
|
<!---<cfswitch expression=#ds#>
|
||||||
|
<cfcase value="metadata"><cfset request.ds="mms"/></cfcase>
|
||||||
|
<cfcase value="data"><cfset request.ds="ditcloud"/></cfcase>
|
||||||
|
<cfdefaultcase><cfset request.ds=""/></cfdefaultcase>
|
||||||
|
</cfswitch>--->
|
||||||
|
|
||||||
|
<cfquery name="qObj" datasource="#request.DS#">
|
||||||
|
select o.type, o.name from sys.objects o where o.type in ('U','V') order by o.type, o.name
|
||||||
|
</cfquery>
|
||||||
|
<!---<cfdump var=#qObj#/>--->
|
||||||
|
|
||||||
|
<cfquery name="qCol" datasource="#request.DS#">
|
||||||
|
select c.name, c.system_type_id, c.max_length, c.is_nullable, c.is_identity from sys.columns c join sys.objects o on (c.object_id=o.object_id) where o.name=<cfqueryparam cfsqltype="cf_sql_varchar" value="#obj#"/>
|
||||||
|
</cfquery>
|
||||||
|
<!---<cfdump var=#qCol#/>--->
|
||||||
|
|
||||||
|
<form action="" method="post">
|
||||||
|
<!--- <select name="ds" onchange="submit();">
|
||||||
|
<option <cfif #ds# EQ "data"> selected</cfif>>data</option>
|
||||||
|
<option <cfif #ds# EQ "metadata"> selected</cfif>>metadata</option>
|
||||||
|
</select>--->
|
||||||
|
<select name="obj">
|
||||||
|
<cfoutput query="qObj">
|
||||||
|
<option <cfif #obj# EQ #name#> selected</cfif> value="#name#">
|
||||||
|
<cfif #trim(qObj.type)# EQ 'U'>(table)<cfelseif #trim(qObj.type)# EQ 'V'>(view)<cfelse></cfif> #name#
|
||||||
|
</option>
|
||||||
|
</cfoutput>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<input type="checkbox" name="out_all" id="out_all" value="1" onchange="; for (var i=0, checkboxes=document.getElementsByName('out'), n=checkboxes.length; i < n ; i++) {checkboxes[i].checked = this.checked;}"/>
|
||||||
|
<label for="out_all">(все)</label>
|
||||||
|
|
||||||
|
<cfloop list="fields_spec,field_set,select,insert,update,bean,grid,detail" item="outformat">
|
||||||
|
<cfoutput>
|
||||||
|
<input type="checkbox" name="out" id="#outformat#" value="#outformat#"<cfif structKeyExists(form, "out")><cfif listFind(form.out,outformat)> checked</cfif></cfif>/>
|
||||||
|
<label for="#outformat#">#outformat#</label>
|
||||||
|
</cfoutput>
|
||||||
|
</cfloop>
|
||||||
|
<cfoutput>
|
||||||
|
<label for="alias">Alias:</label> <input type="text" name="alias" id="alias" value="#alias#" size="2"/>
|
||||||
|
</cfoutput>
|
||||||
|
<input type="checkbox" name="dt_format_120" id="dt_format_120" value="1"<cfif dt_format_120> checked</cfif>/><label for="dt_format_120">Datetime format 120</label>
|
||||||
|
|
||||||
|
<input type="submit" name="do" value="Сформировать"/>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
<cfif structKeyExists(form, "do")>
|
||||||
|
<cfif structKeyExists(form, "out")>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"fields_spec")>
|
||||||
|
<pre>
|
||||||
|
{
|
||||||
|
<cfoutput query="qCol">#name#={prefix="#alias#", type="#cfType(system_type_id)#"}<cfif qCol.currentRow NEQ qCol.recordCount>,</cfif>
|
||||||
|
</cfoutput>
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"field_set")>
|
||||||
|
<pre>
|
||||||
|
select
|
||||||
|
<m:field_set titleMapOut="titleMap" lengthOut="fieldCount"><cfoutput query="qCol">
|
||||||
|
<cfif isDateType(system_type_id)><m:field title="#name#"><!---
|
||||||
|
---><cfif dt_format_120><!---
|
||||||
|
--->convert(varchar(25), <!---
|
||||||
|
---><cfif len(alias)>#alias#.<!---
|
||||||
|
---></cfif>#name#, 120) as #name#<!---
|
||||||
|
---><cfelse><!---
|
||||||
|
---><cfif len(alias)>#alias#.<!---
|
||||||
|
---></cfif>#name#<!---
|
||||||
|
---></cfif></m:field><!---
|
||||||
|
---><cfelse><m:field title="#name#"><!---
|
||||||
|
---><cfif len(alias)>#alias#.<!---
|
||||||
|
---></cfif>#name#</m:field><!---
|
||||||
|
---></cfif></cfoutput>
|
||||||
|
</m:field_set>
|
||||||
|
from <cfoutput>#obj#<cfif len(alias)> #alias#</cfif></cfoutput>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"select")>
|
||||||
|
<cfset comma="No"/>
|
||||||
|
<pre>
|
||||||
|
select <cfoutput query="qCol">
|
||||||
|
<cfif comma>,<cfelse><cfset comma="Yes"/></cfif><cfif len(alias)>#alias#.</cfif>#name#</cfoutput>
|
||||||
|
from <cfoutput>#obj#<cfif len(alias)> #alias#</cfif></cfoutput>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"insert")>
|
||||||
|
<pre>
|
||||||
|
insert into <cfoutput>#obj#</cfoutput> (<cfset comma="No"/>
|
||||||
|
<cfoutput query="qCol"><cfif !is_identity><cfif comma>,<cfelse><cfset comma="Yes"/></cfif>#name#</cfif></cfoutput>
|
||||||
|
) values (<cfset comma="No"/><cfoutput query="qCol"><cfif !is_identity>
|
||||||
|
<cfif comma>,<cfelse><cfset comma="Yes"/></cfif><cfqueryparam cfsqltype="cf_sql_#fieldType(system_type_id)#" value="###name###"/></cfif></cfoutput>
|
||||||
|
)
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"update")>
|
||||||
|
<cfset comma="No"/>
|
||||||
|
<pre>
|
||||||
|
update <cfoutput>#obj#</cfoutput> set <cfoutput query="qCol"><cfif !is_identity>
|
||||||
|
<cfif comma>,<cfelse><cfset comma="Yes"/></cfif>#name#=<cfqueryparam cfsqltype="cf_sql_#fieldType(system_type_id)#" value="###name###"/></cfif></cfoutput>
|
||||||
|
<cfoutput>where #obj#_id=<cfqueryparam cfsqltype="cf_sql_integer" value="###obj#_id##" null=##!isNumeric(#obj#_id)##/></cfoutput>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"bean")>
|
||||||
|
<pre>
|
||||||
|
<d:bean readonly=#!pageInfo.writePermitted()# table="<cfoutput>#obj#</cfoutput>" datasource="#request.DS#" output="d" status="status"><cfoutput query="qCol">
|
||||||
|
<cfswitch expression=#name#>
|
||||||
|
<cfcase value="creator_id"><d:param field="creator_id" type="integer" value="##request.usr_id##" skipUpdate/></cfcase>
|
||||||
|
<cfcase value="modifier_id"><d:param field="modifier_id" type="integer" value="##request.usr_id##"/></cfcase>
|
||||||
|
<cfcase value="dt_created"><d:param field="dt_created" type="timestamp" value="##Now()##" skipUpdate/></cfcase>
|
||||||
|
<cfcase value="dt_modified"><d:param field="dt_modified" type="timestamp" value="##Now()##"/></cfcase>
|
||||||
|
<cfdefaultcase><d:param field="#name#" type="#fieldType(system_type_id)#"<cfif (system_type_id EQ 167) AND (max_length GT 0)> size="#max_length#" preprocessor=##cleanInput##</cfif><cfif (system_type_id EQ 167) AND (max_length LE 0)> preprocessor=##plain2HtmClean##</cfif><cfif "#name#" EQ "#obj#_id"> key autoincrement<cfelseif is_nullable AND listFind("127,104,106,62,59,36,56,108,52,41,58,61,189,48",system_type_id)> forNull=""</cfif><cfif system_type_id EQ 104> default="0" init="0"</cfif>/></cfdefaultcase>
|
||||||
|
</cfswitch></cfoutput>
|
||||||
|
</d:bean>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"grid")>
|
||||||
|
<pre>
|
||||||
|
<table class="worktable">
|
||||||
|
<thead>
|
||||||
|
<layout:grid_head titleMap=#titleMap# sortArray=#pageInfo.settings.sort.sortArray#>
|
||||||
|
<cfoutput query="qCol"><th><layout:column_head name="#name#"/></th>
|
||||||
|
</cfoutput>
|
||||||
|
</layout:grid_head>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<cfoutput query="qRead" maxrows="#pageInfo.recordsPerPage#" startrow="#pageInfo.nStart#">
|
||||||
|
<tr>
|
||||||
|
<cfoutput query="qCol"><td class="c">##<cfif listFind("58,61,189",system_type_id)>dateFormat(#name#,'YYYY-MM-DD')<cfelse>#name#</cfif>##</td>
|
||||||
|
</cfoutput>
|
||||||
|
</tr>
|
||||||
|
</cfoutput>
|
||||||
|
</table>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"detail")>
|
||||||
|
<pre>
|
||||||
|
<div class="detail">
|
||||||
|
<cfoutput query="qCol"><cfif "#name#" NEQ "#obj#_id">
|
||||||
|
<div class="tr">
|
||||||
|
<div class="th">#name#</div>
|
||||||
|
<div class="td"><cfif ((system_type_id EQ 167) AND (max_length GT 0)) OR listFind("127,106,62,59,36,56,108,52,41,58,61,189,48",system_type_id)><input type="text" name="#name#" id="#name#" value="<cfelseif ((system_type_id EQ 167) AND (max_length LE 0))><textarea rows="5" cols="70" name="#name#" id="#name#"><cfelseif system_type_id EQ 104><input type="checkbox" name="#name#" id="name" value="1"<cfif (</cfif><!---
|
||||||
|
--->##<cfif listFind("58,61,189",system_type_id)>dateFormat(d.#name#,'YYYY-MM-DD')<cfelse>d.#name#</cfif>##<cfif ((system_type_id EQ 167) AND (max_length GT 0)) OR listFind("127,106,62,59,36,56,108,52,41,58,61,189,48",system_type_id)>"/><cfelseif ((system_type_id EQ 167) AND (max_length LE 0))></textarea><cfelseif system_type_id EQ 104> GT 0)> checked</cfif>/></cfif><!---
|
||||||
|
---></div>
|
||||||
|
</div>
|
||||||
|
</cfif></cfoutput>
|
||||||
|
</div>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="isDateType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="40,41,58,61,189"><cfreturn true/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn false/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<cffunction name="fieldType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="127"><cfreturn "bigint"/></cfcase>
|
||||||
|
<cfcase value="104"><cfreturn "integer"/></cfcase><!---bit--->
|
||||||
|
<cfcase value="175"><cfreturn "char"/></cfcase>
|
||||||
|
<cfcase value="40"><cfreturn "date"/></cfcase>
|
||||||
|
<cfcase value="106"><cfreturn "decimal"/></cfcase>
|
||||||
|
<cfcase value="62"><cfreturn "double"/></cfcase><!---float--cf_sql_double--->
|
||||||
|
<cfcase value="59"><cfreturn "float"/></cfcase><!---real--cf_sql_float--->
|
||||||
|
<cfcase value="36"><cfreturn "idstamp"/></cfcase><!---uniqueidentifier--->
|
||||||
|
<cfcase value="56"><cfreturn "integer"/></cfcase>
|
||||||
|
<cfcase value="108"><cfreturn "numeric"/></cfcase>
|
||||||
|
<!---<cfcase value="59"><cfreturn "real"/></cfcase>--->
|
||||||
|
<cfcase value="52"><cfreturn "smallint"/></cfcase>
|
||||||
|
<cfcase value="41"><cfreturn "time"/></cfcase>
|
||||||
|
<cfcase value="58,61,189"><cfreturn "timestamp"/></cfcase><!---smalldatetime,datetime,timestamp--->
|
||||||
|
<cfcase value="48"><cfreturn "tinyint"/></cfcase>
|
||||||
|
<cfcase value="165"><cfreturn "varbinary"/></cfcase>
|
||||||
|
<cfcase value="167,231"><cfreturn "varchar"/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn "varchar"/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="cfType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="127"><cfreturn "integer"/></cfcase>
|
||||||
|
<cfcase value="104"><cfreturn "boolean"/></cfcase><!---bit--->
|
||||||
|
<cfcase value="175"><cfreturn "string"/></cfcase><!---char--->
|
||||||
|
<cfcase value="40"><cfreturn "date"/></cfcase>
|
||||||
|
<cfcase value="106"><cfreturn "numeric"/></cfcase><!---decimal--->
|
||||||
|
<cfcase value="62"><cfreturn "numeric"/></cfcase><!---float--cf_sql_double--->
|
||||||
|
<cfcase value="59"><cfreturn "numeric"/></cfcase><!---real--cf_sql_float--->
|
||||||
|
<cfcase value="36"><cfreturn "guid"/></cfcase><!---uniqueidentifier--->
|
||||||
|
<cfcase value="56"><cfreturn "integer"/></cfcase>
|
||||||
|
<cfcase value="108"><cfreturn "numeric"/></cfcase>
|
||||||
|
<cfcase value="52"><cfreturn "integer"/></cfcase><!---smallint--->
|
||||||
|
<cfcase value="41"><cfreturn "date"/></cfcase><!---time--->
|
||||||
|
<cfcase value="58,61,189"><cfreturn "date"/></cfcase><!---smalldatetime,datetime,timestamp--->
|
||||||
|
<cfcase value="48"><cfreturn "integer"/></cfcase>
|
||||||
|
<cfcase value="165"><cfreturn "binary"/></cfcase>
|
||||||
|
<cfcase value="167,231"><cfreturn "string"/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn "string"/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<!---
|
||||||
|
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
|
||||||
|
--->
|
||||||
@@ -0,0 +1,477 @@
|
|||||||
|
<!--- примитивный конструктор селектов, чтобы не набирать руками колонки --->
|
||||||
|
<!--- v0.1 2018.02.27 --->
|
||||||
|
<!--- v0.3 13.11.2020 --->
|
||||||
|
<!--- v0.4 16.11.2020 for rest api--->
|
||||||
|
<!--- v0.5 27.07.2025 fields, export--->
|
||||||
|
<!--- v0.6 01.09.2025 fixes--->
|
||||||
|
<cfsetting enablecfoutputonly="false"/>
|
||||||
|
<!---<cfimport prefix="m" taglib="../../../lib"/>--->
|
||||||
|
<!---<cfimport prefix="d" taglib="lib/data"/>--->
|
||||||
|
|
||||||
|
<cfparam name="obj" default=""/>
|
||||||
|
<cfparam name="schema" default=""/>
|
||||||
|
<!---<cfparam name="ds" default="metadata"/>--->
|
||||||
|
<cfparam name="out" default="field_set,bean"/>
|
||||||
|
<cfparam name="alias" default=""/>
|
||||||
|
<cfparam name="dt_format_iso" default="0"/>
|
||||||
|
<cfparam name="form.column_list" default=""/>
|
||||||
|
<cfset tag_prefix="m:"/>
|
||||||
|
<!---<cfswitch expression=#ds#>
|
||||||
|
<cfcase value="metadata"><cfset request.ds="mms"/></cfcase>
|
||||||
|
<cfcase value="data"><cfset request.ds="ditcloud"/></cfcase>
|
||||||
|
<cfdefaultcase><cfset request.ds=""/></cfdefaultcase>
|
||||||
|
</cfswitch>--->
|
||||||
|
<cfquery name="qSchema" datasource="#request.DS#">
|
||||||
|
select schema_name
|
||||||
|
from information_schema.schemata
|
||||||
|
order by schema_name
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfquery name="qObj" datasource="#request.DS#">
|
||||||
|
select o.table_type as type, o.table_name as name
|
||||||
|
from information_schema.tables o
|
||||||
|
where o.table_type in ('BASE TABLE','VIEW')
|
||||||
|
<cfif len(schema)>AND table_schema=<cfqueryparam cfsqltype="cf_sql_varchar" value="#schema#"/></cfif>
|
||||||
|
order by o.table_type, o.table_name
|
||||||
|
</cfquery>
|
||||||
|
<!---<cfdump var=#qObj#/>--->
|
||||||
|
<!--- *** надо ключевые поля выводить первыми --->
|
||||||
|
<cfquery name="qCol" datasource="#request.DS#">
|
||||||
|
select c.column_name as name, c.data_type, c.character_maximum_length as max_length, c.is_nullable, c.is_identity
|
||||||
|
from information_schema.columns c
|
||||||
|
join information_schema.tables o on (c.table_name=o.table_name AND c.table_schema=o.table_schema)
|
||||||
|
where o.table_name=<cfqueryparam cfsqltype="cf_sql_varchar" value="#obj#"/>
|
||||||
|
</cfquery>
|
||||||
|
<!---
|
||||||
|
|
||||||
|
<cfdump var=#qCol#/><cfabort/>--->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<html><head></head><body>
|
||||||
|
<h1>Генератор всякой всячины из таблицы PostgreSQL</h1>
|
||||||
|
<form action="" method="post">
|
||||||
|
<!--- <select name="ds" onchange="submit();">
|
||||||
|
<option <cfif #ds# EQ "data"> selected</cfif>>data</option>
|
||||||
|
<option <cfif #ds# EQ "metadata"> selected</cfif>>metadata</option>
|
||||||
|
</select>--->
|
||||||
|
<label for="schema">Схема</label>
|
||||||
|
<select name="schema" id="schema" onchange="submit();">
|
||||||
|
<cfoutput query="qSchema">
|
||||||
|
<option <cfif #qSchema.schema_name# EQ #schema#> selected</cfif>>#qSchema.schema_name#</option>
|
||||||
|
</cfoutput>
|
||||||
|
</select>
|
||||||
|
<label for="obj">Таблица</label>
|
||||||
|
<select name="obj" id="obj">
|
||||||
|
<cfoutput query="qObj">
|
||||||
|
<option <cfif #obj# EQ #name#> selected</cfif> value="#name#">
|
||||||
|
<cfif #trim(qObj.type)# EQ 'U'>(table)<cfelseif #trim(qObj.type)# EQ 'V'>(view)<cfelse></cfif> #name#
|
||||||
|
</option>
|
||||||
|
</cfoutput>
|
||||||
|
</select>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<b>Поля:</b>
|
||||||
|
<input type="checkbox" name="fields_all" id="fields_all" value="1" onchange="; for (var i=0, fld_checkboxes=document.getElementsByName('column_list'), n=fld_checkboxes.length; i < n ; i++) {fld_checkboxes[i].checked = this.checked;}" style="margin-right:0;"/>
|
||||||
|
<label for="fields_all">(все)</label>
|
||||||
|
<cfoutput query="qCol">
|
||||||
|
<input type="checkbox" name="column_list" id="column_list_#qCol.currentRow#" value="#name#"<cfif structKeyExists(form, "column_list")><cfif listFind(form.column_list,name)> checked</cfif></cfif> style="margin-right:0;"/><label for="column_list_#qCol.currentRow#">#name#</label>
|
||||||
|
</cfoutput> <i>для некоторых вариантов предполагается, что таблица имеет целочисленный ключ "tablename_id"</i>
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
<b>Генерировать:</b>
|
||||||
|
<input type="checkbox" name="out_all" id="out_all" value="1" onchange="; for (var i=0, checkboxes=document.getElementsByName('out'), n=checkboxes.length; i < n ; i++) {checkboxes[i].checked = this.checked;}" style="margin-right:0;"/>
|
||||||
|
<label for="out_all">(все)</label>
|
||||||
|
|
||||||
|
<cfloop list="select,update,insert,export,bean,grid,simple_grid,detail,fields_spec,field_set" item="outformat">
|
||||||
|
<cfoutput>
|
||||||
|
<input type="checkbox" name="out" id="#outformat#" value
|
||||||
|
="#outformat#"<cfif structKeyExists(form, "out")><cfif listFind(form.out,outformat)> checked</cfif></cfif> style="margin-right:0;"/>
|
||||||
|
<label for="#outformat#">#outformat#</label>
|
||||||
|
</cfoutput>
|
||||||
|
</cfloop>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<cfoutput>
|
||||||
|
<label for="alias">Alias:</label> <input type="text" name="alias" id="alias" value="#alias#" size="2"/>
|
||||||
|
</cfoutput>
|
||||||
|
<input type="checkbox" name="dt_format_iso" id="dt_format_iso" value="1"<cfif dt_format_iso> checked</cfif>/><label for="dt_format_iso">Datetime format ISO</label>
|
||||||
|
|
||||||
|
<input type="submit" name="do" value="Сформировать"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<cfif structKeyExists(form, "do")>
|
||||||
|
<cfif structKeyExists(form, "out")>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"select")>
|
||||||
|
<cfset comma="No"/>
|
||||||
|
<pre>
|
||||||
|
select <cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)>
|
||||||
|
<cfif comma>,<cfelse> <cfset comma="Yes"/></cfif><cfif len(alias)>#alias#.</cfif>#name#</cfif></cfoutput>
|
||||||
|
from <cfoutput>#schema#.#obj#<cfif len(alias)> #alias#</cfif></cfoutput>;
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"insert")>
|
||||||
|
<pre>
|
||||||
|
insert into <cfoutput>#schema#.#obj#</cfoutput> (<cfset comma="No"/> <cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)><cfif !is_identity OR listLen(column_list)><cfif comma>
|
||||||
|
,<cfelse>
|
||||||
|
<cfset comma="Yes"/></cfif>#name#</cfif></cfif></cfoutput>
|
||||||
|
) values (<cfset comma="No"/><cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)><cfif !is_identity OR listLen(column_list)><cfif comma>
|
||||||
|
,<cfelse>
|
||||||
|
<cfset comma="Yes"/></cfif><cfqueryparam cfsqltype="#cfSqlType(data_type)#" value="###name###"/></cfif></cfif></cfoutput>
|
||||||
|
);
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"export")>
|
||||||
|
<cfquery name="qExport">
|
||||||
|
select <cfif listLen(column_list)>#column_list#<cfelse>*</cfif>
|
||||||
|
from #schema#.#obj#
|
||||||
|
</cfquery>
|
||||||
|
<pre>
|
||||||
|
insert into <cfoutput>#schema#.#obj#</cfoutput> (<cfset comma="No"/> <cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)><cfif comma>
|
||||||
|
,<cfelse>
|
||||||
|
<cfset comma="Yes"/></cfif>#name#</cfif></cfoutput>
|
||||||
|
)
|
||||||
|
values
|
||||||
|
<cfoutput query="qExport">(<cfset comma="No"/><cfloop query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)><cfif comma>, <cfelse><cfset comma="Yes"/></cfif><!---
|
||||||
|
--->'#qExport[qCol.name]#'</cfif></cfloop>)<!---
|
||||||
|
---><cfif qExport.currentRow LT qExport.recordCount>,</cfif>
|
||||||
|
</cfoutput>;
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"update")>
|
||||||
|
<cfset comma="No"/>
|
||||||
|
<pre>
|
||||||
|
update <cfoutput>#obj#</cfoutput> set <cfoutput query="qCol"><cfif !is_identity OR listLen(column_list)><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)>
|
||||||
|
<cfif comma>,<cfelse> <cfset comma="Yes"/></cfif>#name#=<cfqueryparam cfsqltype="#cfSqlType(data_type)#" value="###name###"/></cfif></cfif></cfoutput>
|
||||||
|
<cfoutput>where #obj#_id=<cfqueryparam cfsqltype="cf_sql_integer" value="###obj#_id##" null=##!isNumeric(#obj#_id)##/></cfoutput>;
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"bean")>
|
||||||
|
<pre>
|
||||||
|
<d:bean readonly=#!pageInfo.writePermitted()# table="<cfoutput>#obj#</cfoutput>" datasource="#request.DS#" output="d" status="status"><cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)>
|
||||||
|
<cfswitch expression=#name#>
|
||||||
|
<cfcase value="creator_id"><d:param field="creator_id" type="integer" value="##request.usr_id##" skipUpdate/></cfcase>
|
||||||
|
<cfcase value="updater_id"><d:param field="updater_id" type="integer" value="##request.usr_id##"/></cfcase>
|
||||||
|
<cfcase value="dt_created"><d:param field="dt_created" type="timestamp" value="##Now()##" skipUpdate/></cfcase>
|
||||||
|
<cfcase value="dt_updated"><d:param field="dt_updated" type="timestamp" value="##Now()##"/></cfcase>
|
||||||
|
<cfdefaultcase><d:param field="#name#" type="#translateDataType(data_type)#"<cfif isStringType(data_type) AND (max_length GT 0)> size="#max_length#" preprocessor=##cleanInput##</cfif><cfif isStringType(data_type) AND (max_length LE 0)> preprocessor=##plain2HtmClean##</cfif><cfif "#name#" EQ "#obj#_id"> key autoincrement<cfelseif is_nullable AND true> forNull=""</cfif><cfif isIntegerType(data_type)> default="0" init="0"</cfif>/></cfdefaultcase>
|
||||||
|
</cfswitch></cfif></cfoutput>
|
||||||
|
</d:bean>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"grid")>
|
||||||
|
<pre>
|
||||||
|
<table class="worktable">
|
||||||
|
<thead>
|
||||||
|
<layout:grid_head titleMap=#titleMap# sortArray=#pageInfo.settings.sort.sortArray#><cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)>
|
||||||
|
<th><layout:column_head name="#name#"/></th></cfif></cfoutput>
|
||||||
|
</layout:grid_head>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<cfoutput query="qRead" maxrows="#pageInfo.recordsPerPage#" startrow="#pageInfo.nStart#">
|
||||||
|
<tr><cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)>
|
||||||
|
<td class="c">##<cfif isDateType(data_type)>dateFormat(#name#,'YYYY-MM-DD')<cfelse>#name#</cfif>##</td></cfif></cfoutput>
|
||||||
|
</tr>
|
||||||
|
</cfoutput>
|
||||||
|
</table>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"simple_grid")>
|
||||||
|
<pre>
|
||||||
|
<table class="worktable">
|
||||||
|
<thead>
|
||||||
|
<tr><cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)>
|
||||||
|
<th>#name#</th></cfif></cfoutput>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
|
||||||
|
<cfoutput query="qRead">
|
||||||
|
<tr><cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)>
|
||||||
|
<td class="c">##<cfif isDateType(data_type)>dateFormat(#name#,'YYYY-MM-DD')<cfelse>#name#</cfif>##</td></cfif></cfoutput>
|
||||||
|
</tr>
|
||||||
|
</cfoutput>
|
||||||
|
</table>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"detail")>
|
||||||
|
<pre>
|
||||||
|
|
||||||
|
<div class="detail">
|
||||||
|
<cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)>
|
||||||
|
<div class="tr">
|
||||||
|
<div class="th">#name#</div>
|
||||||
|
<div class="td"><cfif NOT max_length GT 0><input type="text" name="#name#" id="#name#" value="<cfelseif (isStringType(data_type) AND (max_length LE 0))><textarea rows="5" cols="70" name="#name#" id="#name#"><cfelseif isIntegerType(data_type)><input type="checkbox" name="#name#" id="name" value="1"<cfif (</cfif><!---
|
||||||
|
--->##<cfif isDateType(data_type)>dateFormat(d.#name#,'YYYY-MM-DD')<cfelse>d.#name#</cfif>##<cfif (isStringType(data_type) AND max_length GT 0) OR /*listFind("127,106,62,59,36,56,108,52,41,58,61,189,48",system_type_id)*/true>"/><cfelseif (isStringType(data_type) AND (max_length LE 0))></textarea><cfelseif isIntegerType(data_type)> GT 0)> checked</cfif>/></cfif><!---
|
||||||
|
---></div>
|
||||||
|
</div>
|
||||||
|
</cfif></cfoutput>
|
||||||
|
</div>
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"fields_spec")><!--- webkit bug? it changes order of pre with left curved bracket inside,
|
||||||
|
minimum sample: 1<pre>{</pre>3 Пытается парсить Error: Parse error on line 1:
|
||||||
|
{ contragent_id={prefi
|
||||||
|
--^
|
||||||
|
Expecting 'STRING', '}', got 'undefined'--->
|
||||||
|
<div style="white-space:pre; font-family:monospace;">
|
||||||
|
{
|
||||||
|
<cfset comma="No"/><cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)><cfif comma>
|
||||||
|
,<cfelse> <cfset comma="Yes"/></cfif>#name#={prefix="#alias#", type="#cfSqlType(data_type)#"}</cfif></cfoutput>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</cfif>
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
<cfif listFind(form.out,"field_set")>
|
||||||
|
<pre>
|
||||||
|
select
|
||||||
|
<cfoutput><#tag_prefix#field_set titleMapOut="local.titleMap" lengthOut="fieldCount"></cfoutput><cfoutput query="qCol"><cfif listLen(column_list) EQ 0 OR listFind(#column_list#,#name#)>
|
||||||
|
<cfif isDateType(data_type)><#tag_prefix#field title="#name#"><!---
|
||||||
|
---><cfif dt_format_iso><!---
|
||||||
|
--->to_char(<!---
|
||||||
|
---><cfif len(alias)>#alias#.<!---
|
||||||
|
---></cfif>#name#, 'YYYY-MM-DD"T"HH24:MI:SS.FF3TZHTZM') as #name#<!---
|
||||||
|
---><cfelse><!---
|
||||||
|
---><cfif len(alias)>#alias#.<!---
|
||||||
|
---></cfif>#name#<!---
|
||||||
|
---></cfif></#tag_prefix#field><!---
|
||||||
|
---><cfelseif isUuidType(data_type)><#tag_prefix#field title="#name#"><!---
|
||||||
|
---><cfif len(alias)>#alias#.<!---
|
||||||
|
---></cfif>#name#::text as #name#</#tag_prefix#field><!---
|
||||||
|
---><cfelse><#tag_prefix#field title="#name#"><!---
|
||||||
|
---><cfif len(alias)>#alias#.<!---
|
||||||
|
---></cfif>#name#</#tag_prefix#field><!---
|
||||||
|
---></cfif></cfif></cfoutput>
|
||||||
|
<cfoutput></#tag_prefix#field_set></cfoutput>
|
||||||
|
from <cfoutput>#obj#<cfif len(alias)> #alias#</cfif></cfoutput>;
|
||||||
|
</pre>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<hr/>
|
||||||
|
|
||||||
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
</body></html>
|
||||||
|
|
||||||
|
<!--- <cffunction name="isDateType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="40,41,58,61,189"><cfreturn true/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn false/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction> --->
|
||||||
|
|
||||||
|
<cffunction name="isDateType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="date,time,datetime,timestamp,timestamptz,timestamp with time zone"><cfreturn true/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn false/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="isNumericType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="bigint,integer,decimal,double,float,numeric,smallint,tinyint"><cfreturn true/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn false/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="isIntegerType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="bigint,integer,smallint,tinyint"><cfreturn true/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn false/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="isStringType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="varchar,character varying,text"><cfreturn true/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn false/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="isUuidType" output="No"><!--- UUID и GUID имеют разный формат --->
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="uuid"><cfreturn true/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn false/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="cfSqlType" output="No">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="uuid"><cfreturn "cf_sql_other"/></cfcase><!--- idstamp for postgresql does not work correctly --->
|
||||||
|
<cfcase value="bit,bool,boolean"><cfreturn "cf_sql_bit"/></cfcase>
|
||||||
|
<cfcase value="bigint"><cfreturn "cf_sql_bigint"/></cfcase>
|
||||||
|
<cfcase value="integer"><cfreturn "cf_sql_integer"/></cfcase>
|
||||||
|
<cfcase value="smallint"><cfreturn "cf_sql_smallint"/></cfcase>
|
||||||
|
<cfcase value="tinyint"><cfreturn "cf_sql_tinyint"/></cfcase>
|
||||||
|
<cfcase value="decimal"><cfreturn "cf_sql_decimal"/></cfcase>
|
||||||
|
<cfcase value="numeric"><cfreturn "cf_sql_numeric"/></cfcase>
|
||||||
|
<cfcase value="real"><cfreturn "cf_sql_real"/></cfcase>
|
||||||
|
<cfcase value="float"><cfreturn "cf_sql_float"/></cfcase>
|
||||||
|
<cfcase value="double"><cfreturn "cf_sql_double"/></cfcase>
|
||||||
|
<cfcase value="char"><cfreturn "cf_sql_char"/></cfcase>
|
||||||
|
<cfcase value="varchar,character varying,text"><cfreturn "cf_sql_varchar"/></cfcase>
|
||||||
|
<cfcase value="date"><cfreturn "cf_sql_date"/></cfcase>
|
||||||
|
<cfcase value="time"><cfreturn "cf_sql_time"/></cfcase>
|
||||||
|
<cfcase value="timestamp,timestamptz,timestamp with time zone"><cfreturn "cf_sql_timestamp"/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn "cf_sql_varchar"/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="translateDataType" output="No">
|
||||||
|
<cfargument name="type"/>
|
||||||
|
<cfswitch expression=#type#>
|
||||||
|
<cfcase value="bit,bool,boolean"><cfreturn "bit"/></cfcase>
|
||||||
|
<cfcase value="varchar,character varying,text"><cfreturn "varchar"/></cfcase>
|
||||||
|
<cfcase value="timestamp,timestamptz,timestamp with time zone"><cfreturn "timestamp"/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn #type#/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<!--- https://cfdocs.org/cfsqltype-cheatsheet
|
||||||
|
CF_SQL_* JDBC DB2 Oracle SQL Server
|
||||||
|
CF_SQL_ARRAY ARRAY N/A N/A N/A
|
||||||
|
CF_SQL_BIGINT BIGINT BigInt N/A bigint
|
||||||
|
CF_SQL_BINARY BINARY Char for Bit Data N/A binary, timestamp
|
||||||
|
CF_SQL_BIT BIT N/A N/A bit
|
||||||
|
CF_SQL_BLOB BLOB Blob blob, bfile longvarbinary
|
||||||
|
CF_SQL_CHAR CHAR Char char, nchar char, nchar, uniqueidentifier
|
||||||
|
CF_SQL_CLOB CLOB Clob clob, nclob N/A
|
||||||
|
CF_SQL_DATE DATE Date N/A/ date
|
||||||
|
CF_SQL_DECIMAL* DECIMAL Decimal number decimal, money, smallmoney
|
||||||
|
CF_SQL_DISTINCT DISTINCT N/A N/A N/A
|
||||||
|
CF_SQL_DOUBLE DOUBLE Double N/A float
|
||||||
|
CF_SQL_FLOAT FLOAT Float number real
|
||||||
|
CF_SQL_IDSTAMP CHAR Char char, nchar char, nchar, uniqueidentifier
|
||||||
|
CF_SQL_INTEGER INTEGER Integer N/A int
|
||||||
|
CF_SQL_LONGVARBINARY LONGVARBINARY Long Varchar for Bit Data long raw image
|
||||||
|
CF_SQL_LONGNVARCHAR LONGNVARCHAR LONGVARGRAPHIC NVARCHAR2 ntext, xml
|
||||||
|
CF_SQL_LONGVARCHAR LONGVARCHAR Long Varchar long text, ntext, xml
|
||||||
|
CF_SQL_MONEY DOUBLE Double N/A float, money
|
||||||
|
CF_SQL_MONEY4 DOUBLE Double N/A float, money
|
||||||
|
CF_SQL_NCHAR NCHAR NCHAR NCHAR nchar
|
||||||
|
CF_SQL_NCLOB CLOB NCLOB NCLOB nvarchar
|
||||||
|
CF_SQL_NULL NULL N/A N/A N/A
|
||||||
|
CF_SQL_NUMERIC* NUMERIC Numeric N/A numeric
|
||||||
|
CF_SQL_NVARCHAR NVARCHAR NVARCHAR NVARCHAR2 nvarchar
|
||||||
|
CF_SQL_OTHER OTHER N/A N/A N/A
|
||||||
|
CF_SQL_REAL REAL Real N/A real
|
||||||
|
CF_SQL_REFCURSOR REF N/A N/A N/A
|
||||||
|
CF_SQL_SMALLINT SMALLINT Smallint N/A smallint
|
||||||
|
CF_SQL_STRUCT STRUCT N/A N/A N/A
|
||||||
|
CF_SQL_SQLXML N/A N/A N/A xml
|
||||||
|
CF_SQL_TIME TIME Time N/A time
|
||||||
|
CF_SQL_TIMESTAMP TIMESTAMP Timestamp date datetime, datetime2, smalldatetime
|
||||||
|
CF_SQL_TINYINT TINYINT N/A N/A tinyint
|
||||||
|
CF_SQL_VARBINARY VARBINARY Rowid raw varbinary, geometry, geography, udt
|
||||||
|
CF_SQL_VARCHAR VARCHAR Varchar varchar2, nvarchar2 varchar, nvarchar --->
|
||||||
|
|
||||||
|
<cffunction name="fieldType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="127"><cfreturn "bigint"/></cfcase>
|
||||||
|
<cfcase value="104"><cfreturn "integer"/></cfcase><!---bit--->
|
||||||
|
<cfcase value="175"><cfreturn "char"/></cfcase>
|
||||||
|
<cfcase value="40"><cfreturn "date"/></cfcase>
|
||||||
|
<cfcase value="106"><cfreturn "decimal"/></cfcase>
|
||||||
|
<cfcase value="62"><cfreturn "double"/></cfcase><!---float--cf_sql_double--->
|
||||||
|
<cfcase value="59"><cfreturn "float"/></cfcase><!---real--cf_sql_float--->
|
||||||
|
<cfcase value="36"><cfreturn "idstamp"/></cfcase><!---uniqueidentifier--->
|
||||||
|
<cfcase value="56"><cfreturn "integer"/></cfcase>
|
||||||
|
<cfcase value="108"><cfreturn "numeric"/></cfcase>
|
||||||
|
<!---<cfcase value="59"><cfreturn "real"/></cfcase>--->
|
||||||
|
<cfcase value="52"><cfreturn "smallint"/></cfcase>
|
||||||
|
<cfcase value="41"><cfreturn "time"/></cfcase>
|
||||||
|
<cfcase value="58,61,189"><cfreturn "timestamp"/></cfcase><!---smalldatetime,datetime,timestamp--->
|
||||||
|
<cfcase value="48"><cfreturn "tinyint"/></cfcase>
|
||||||
|
<cfcase value="165"><cfreturn "varbinary"/></cfcase>
|
||||||
|
<cfcase value="167,231"><cfreturn "varchar"/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn "varchar"/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
<cffunction name="cfType" output="No">
|
||||||
|
<cfargument name="xtype"/>
|
||||||
|
<cfswitch expression=#xtype#>
|
||||||
|
<cfcase value="127"><cfreturn "integer"/></cfcase>
|
||||||
|
<cfcase value="104"><cfreturn "boolean"/></cfcase><!---bit--->
|
||||||
|
<cfcase value="175"><cfreturn "string"/></cfcase><!---char--->
|
||||||
|
<cfcase value="40"><cfreturn "date"/></cfcase>
|
||||||
|
<cfcase value="106"><cfreturn "numeric"/></cfcase><!---decimal--->
|
||||||
|
<cfcase value="62"><cfreturn "numeric"/></cfcase><!---float--cf_sql_double--->
|
||||||
|
<cfcase value="59"><cfreturn "numeric"/></cfcase><!---real--cf_sql_float--->
|
||||||
|
<cfcase value="36"><cfreturn "guid"/></cfcase><!---uniqueidentifier--->
|
||||||
|
<cfcase value="56"><cfreturn "integer"/></cfcase>
|
||||||
|
<cfcase value="108"><cfreturn "numeric"/></cfcase>
|
||||||
|
<cfcase value="52"><cfreturn "integer"/></cfcase><!---smallint--->
|
||||||
|
<cfcase value="41"><cfreturn "date"/></cfcase><!---time--->
|
||||||
|
<cfcase value="58,61,189"><cfreturn "date"/></cfcase><!---smalldatetime,datetime,timestamp--->
|
||||||
|
<cfcase value="48"><cfreturn "integer"/></cfcase>
|
||||||
|
<cfcase value="165"><cfreturn "binary"/></cfcase>
|
||||||
|
<cfcase value="167,231"><cfreturn "string"/></cfcase>
|
||||||
|
<cfdefaultcase><cfreturn "string"/></cfdefaultcase>
|
||||||
|
</cfswitch>
|
||||||
|
</cffunction>
|
||||||
|
|
||||||
|
|
||||||
|
<!---
|
||||||
|
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
|
||||||
|
--->
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<cfquery name="qUsr" datasource="#request.DS#">
|
||||||
|
select current_user;
|
||||||
|
</cfquery>
|
||||||
|
<cfdump var=#qUsr#/>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<!--- <cfdump var=#createObject("java", "java.lang.System").getenv()#/> --->
|
||||||
|
<cfdump var=#server#/>
|
||||||
|
<cfdump var=#CGI#/>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<cfparam name="passwd" default=""/>
|
||||||
|
<cfset tStart=GetTickCount()/>
|
||||||
|
<cfoutput>
|
||||||
|
<form action="" method="post">
|
||||||
|
<input type="password" name="passwd"/>
|
||||||
|
<input type="submit"/>
|
||||||
|
<br/>
|
||||||
|
#GenerateArgon2Hash(#passwd#, 'argon2id', 1, 16000, 2)#
|
||||||
|
<!---input variant parallelizmFactor memoryCost iterations--->
|
||||||
|
<!---https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id--->
|
||||||
|
<br/>
|
||||||
|
</form>
|
||||||
|
#(GetTickCount()-tStart)# ms
|
||||||
|
</cfoutput>
|
||||||
|
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<cfparam name="passwd" default=""/>
|
||||||
|
<cfset tStart=GetTickCount()/>
|
||||||
|
<cfoutput>
|
||||||
|
<form action="" method="post">
|
||||||
|
<input type="password" name="passwd"/>
|
||||||
|
<input type="submit"/>
|
||||||
|
<br/>
|
||||||
|
#GenerateArgon2Hash(#passwd#, 'argon2id', 1, 16000, 2)#
|
||||||
|
<!---input variant parallelizmFactor memoryCost iterations--->
|
||||||
|
<!---https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id--->
|
||||||
|
<br/>
|
||||||
|
</form>
|
||||||
|
#(GetTickCount()-tStart)# ms
|
||||||
|
</cfoutput>
|
||||||
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<cfoutput>
|
||||||
|
Now():#Now()#<br/>
|
||||||
|
LSDateTimeFormat(Now()):#LSDateTimeFormat(Now())#<br/>
|
||||||
|
System TimeZone:#getSystemTZ()#<br/>
|
||||||
|
TimeStamp:#LSDateTimeFormat(Now(),"dd-MM-yyyy'T'HH:nn:ss.SSSZ")#<br/>
|
||||||
|
<!--- <cfdump var=#getSystemTZ()#/> --->
|
||||||
|
<hr/>
|
||||||
|
Server Date/Time: #lsdateFormat(date:now(),timezone:"jvm")# #lstimeFormat(time:now(),timezone:"jvm")#<br/>
|
||||||
|
Lucee Date/Time: #lsdateFormat(date:now())# #lstimeFormat(time:now())#<br/>
|
||||||
|
JVM(System) Timezone: #createobject("java","java.util.TimeZone").getDefault().getId()#<br/>
|
||||||
|
PageContext(Lucee) Timezone: #getPageContext().getTimeZone()#
|
||||||
|
</cfoutput>
|
||||||
|
|
||||||
|
<cfscript>
|
||||||
|
function getSystemTZ(){
|
||||||
|
return createObject("java", "java.util.TimeZone").getDefault().getId();
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
Reference in New Issue
Block a user