├── .gitignore ├── base.cfc ├── collection.cfc ├── content.cfc ├── cookie.cfc ├── execute.cfc ├── flush.cfc ├── header.cfc ├── htmlhead.cfc ├── index.cfc ├── invoke.cfc ├── loginuser.cfc ├── logout.cfc ├── objectcache.cfc ├── query.cfc ├── queryofqueries.cfc ├── readme.md ├── registry.cfc ├── schedule.cfc ├── search.cfc ├── setting.cfc ├── spreadsheet.cfc ├── tests ├── ExecuteTest.cfc ├── LoginFrameworkTest.cfc ├── SpreadsheetTest.cfc └── queryofqueriesTest.cfc ├── wddx.cfc └── zip.cfc /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .project 3 | 4 | .settings/org.eclipse.core.resources.prefs 5 | 6 | settings.xml 7 | -------------------------------------------------------------------------------- /base.cfc: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 20 | 21 | variables.tagAttributes = ''; 22 | variables.parameters = []; 23 | 24 | /** 25 | * Set the name of the service tag to be invoked 26 | * @output false 27 | */ 28 | private void function setTagName(string tagName) 29 | { 30 | variables.tagName = tagName; 31 | } 32 | 33 | 34 | /** 35 | * Get the name of the service tag 36 | * @output false 37 | */ 38 | private string function getTagName() 39 | { 40 | return variables.tagName; 41 | } 42 | 43 | 44 | /** 45 | * Set cfc properties. Wrapper for setAttributes() 46 | * @output false 47 | */ 48 | public void function setProperties() 49 | { 50 | if(structkeyexists(arguments,"1") and isstruct(arguments["1"])) 51 | setAttributes(arguments["1"]); 52 | else 53 | setAttributes(arguments); 54 | } 55 | 56 | 57 | /** 58 | * Set service tag attributes. 59 | * @output false 60 | */ 61 | public void function setAttributes() 62 | { 63 | if(!structisempty(arguments)) 64 | { 65 | var temp = {}; 66 | if(structkeyexists(arguments,"1") and isstruct(arguments["1"])) 67 | { 68 | //include only relevant properties 69 | for(var p in arguments["1"]) 70 | { 71 | if(listcontainsnocase(variables.tagAttributes,p)){ 72 | temp[p] = arguments["1"][p]; 73 | } 74 | } 75 | structappend(variables,temp,"yes"); 76 | } 77 | else 78 | structappend(variables,arguments,"yes"); 79 | } 80 | } 81 | 82 | 83 | /** 84 | * Get cfc properties. Wrapper for getAttributes() 85 | * @output false 86 | */ 87 | public struct function getProperties(string attribs="") 88 | { 89 | return getAttributes(attribs); 90 | } 91 | 92 | 93 | /** 94 | * Returns a struct with all/some of the service tag attribute values. If no attributes are specified, it returns a coldfusion with all service tag attribute values 95 | * @output false 96 | */ 97 | public struct function getAttributes(string attribs="") 98 | { 99 | var attributesstruct = {}; 100 | var i = 0; 101 | arguments.attribs = trim(arguments.attribs) neq "" ? trim(arguments.attribs) : variables.tagAttributes; 102 | 103 | for(i=1; i lte listlen(attribs); i++) 104 | { 105 | var attrib = trim(listgetat(attribs,i)); 106 | if(structkeyexists(variables,attrib)) 107 | { 108 | attributesstruct[attrib] = variables[attrib]; 109 | } 110 | } 111 | return attributesstruct; 112 | } 113 | 114 | 115 | /** 116 | * Clear cfc properties. Wrapper for clearAttributes() 117 | * @output false 118 | */ 119 | public void function clearProperties(string attribs="") 120 | { 121 | return clearAttributes(attribs); 122 | } 123 | 124 | 125 | /** 126 | * Removes a specific service tag attribute from CFC variables scope. Invoked by clearAttributes() 127 | * @output false 128 | */ 129 | private void function clearAttribute(struct tagAttributesStruct,string tagAttribute) 130 | { 131 | if(structkeyexists(tagAttributesStruct,tagAttribute)) 132 | { 133 | structdelete(tagAttributesStruct,tagAttribute); 134 | } 135 | } 136 | 137 | 138 | /** 139 | * Removes all|some of the service tag attributes from CFC variables scope. Accepts a list of service tag attributes to remove. If no attributes are specified, all attributes are 140 | * removed from CFC variables scope 141 | * @output false 142 | */ 143 | public void function clearAttributes(string tagAttributesToClear="") 144 | { 145 | var attributeslist = isdefined("arguments.tagAttributesToClear") and trim(arguments.tagAttributesToClear) neq "" ? arguments.tagAttributesToClear : variables.tagAttributes; 146 | var i = 0; 147 | 148 | for(i=1;i lte listlen(attributeslist); i++) 149 | { 150 | var attrib = trim(listgetat(attributeslist,i)); 151 | clearAttribute(variables,attrib); 152 | } 153 | } 154 | 155 | 156 | /** 157 | * Removes service tag attributes 158 | * @output false 159 | */ 160 | public void function clear() 161 | { 162 | clearAttributes(); 163 | clearParams(); 164 | } 165 | 166 | 167 | /** 168 | * Resets child tags by resetting the parameters array 169 | * @output false 170 | */ 171 | public void function clearParams() 172 | { 173 | if(isdefined("variables.parameters")) 174 | { 175 | variables.parameters = []; 176 | } 177 | } 178 | 179 | 180 | /** 181 | * Returns a struct with attributes set either using implicit setters | init() | setAttributes() 182 | * @output false 183 | */ 184 | private struct function getTagAttributes() 185 | { 186 | var tagAttributes = structnew(); 187 | var i = 0; 188 | 189 | for(i=1; i lte listlen(variables.tagAttributes); i++) 190 | { 191 | var attrib = trim(listgetat(variables.tagAttributes,i)); 192 | if(structkeyexists(variables,attrib)) 193 | { 194 | tagAttributes[attrib] = variables[attrib]; 195 | } 196 | } 197 | return tagAttributes; 198 | } 199 | 200 | /** 201 | * Trim tag attributes 202 | * @output false 203 | */ 204 | private struct function trimAttributes(struct attribs={}) 205 | { 206 | var tagAttributes = {}; 207 | if(!structisempty(attribs)) 208 | { 209 | for(var attrib in attribs) 210 | { 211 | tagAttributes[attrib] = isSimpleValue(attribs[attrib]) ? trim(attribs[attrib]) : attribs[attrib]; 212 | } 213 | } 214 | return tagAttributes; 215 | } 216 | 217 | 218 | /** 219 | * Accepts a service tag name and returns the allowed tag attributes. Uses double-checked locking 220 | * @output false 221 | */ 222 | private string function getSupportedTagAttributes(string tagName) 223 | { 224 | //store all service tag attributes in Server scope for faster access. 225 | if(not isdefined("server.coldfusion.serviceTagAttributes.#tagName#")) 226 | { 227 | lock scope="server" timeout="30" throwontimeout="yes" type="exclusive" 228 | { 229 | if(not isdefined("server.coldfusion.serviceTagAttributes.#tagName#")) 230 | { 231 | var webinf = getPageContext().getServletContext().getRealPath("/WEB-INF"); 232 | var sep = server.os.name contains "windows"? "\" : "/"; 233 | var cftldpath = webinf & sep & "cftags" & sep & "META-INF" & sep & "taglib.cftld"; 234 | var xpath = "/taglib/tag[name='#lcase(Right(tagName,len(tagName)-2))#']/attribute/name"; 235 | var cftagsXml = XmlParse(FileRead(cftldpath)); 236 | var tagAttributes = xmlsearch(cftagsXml,xpath); 237 | var attributeslist = ""; 238 | var i = 0; 239 | for(i=1;i lte arraylen(tagAttributes); i++) 240 | { 241 | attributeslist = listappend(attributeslist,trim(tagAttributes[i].xmltext)); 242 | } 243 | server.coldfusion.serviceTagAttributes[tagName] = listsort(attributeslist,"textnocase","ASC"); 244 | } 245 | } 246 | } 247 | 248 | //use a local variable for faster access 249 | lock scope="server" timeout="30" throwontimeout="yes" type="readonly" 250 | { 251 | var attributeslist = server.coldfusion.serviceTagAttributes[tagName]; 252 | } 253 | 254 | return attributeslist; 255 | } 256 | 257 | 258 | /** 259 | * Add child tags like cfmailparam, cfmailpart etc 260 | * @output false 261 | */ 262 | public void function addParam() 263 | { 264 | if(!structisempty(arguments)) 265 | { 266 | if(isdefined("variables.parameters")) 267 | { 268 | arrayappend(variables.parameters, arguments); 269 | } 270 | } 271 | } 272 | 273 | 274 | /** 275 | * Get array of parameters added using addParam() 276 | * @output false 277 | */ 278 | public array function getParameters() 279 | { 280 | return variables.parameters; 281 | } 282 | 283 | 284 | /** 285 | * Validate against any incorrect attributes passed to the child tags. This is done by passing allowExtraAttributes=false 286 | * @output false 287 | */ 288 | private array function appendAllowExtraAttributes(array params) 289 | { 290 | var temp = []; 291 | var nbrOfParams = arraylen(arguments.params); 292 | var i = 0; 293 | 294 | for(i=1; i lte nbrOfParams; i++) 295 | { 296 | var param = arguments.params[i]; 297 | if(isstruct(param)) 298 | { 299 | structappend(param,{allowExtraAttributes=false}); 300 | arrayappend(temp,param); 301 | } 302 | } 303 | return temp; 304 | } 305 | 306 | 307 | 308 | 309 | 310 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | #mailbody# 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | #trim(mailpartbody)# 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | #PreserveSingleQuotes(sqlQuery)# 459 | 460 | #getPreserveSingleQuotes(sqlArray[1])# 461 | 462 | 463 | 464 | 465 | 466 | #getPreserveSingleQuotes(sqlArray[i])# 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | 540 | 541 | 542 | 543 | 544 | 545 | 546 | 547 | 548 | 549 | 550 | 551 | 552 | 553 | 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | 565 | 566 | 567 | 568 | 569 | 570 | 571 | 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | -------------------------------------------------------------------------------- /collection.cfc: -------------------------------------------------------------------------------- 1 | /*********************************************************************************************************** 2 | * 3 | * Made by Raymond Camden, Jedi Master 4 | * 5 | * MIT License 6 | * 7 | * Copyright (c) 2011 The ColdFusion Community 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 10 | * associated documentation files (the "Software"), to deal in the Software without restriction, including 11 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 13 | * following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial 16 | * portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 19 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | * 24 | * See also: http://www.opensource.org/licenses/mit-license.html 25 | * 26 | *************************************************************************************************************/ 27 | 28 | /** 29 | * Collection Service to perform solr collection operations in cfscript 30 | * @name collection 31 | * @displayname ColdFusion Collection Service 32 | * @output false 33 | * @accessors true 34 | */ 35 | component extends="base" { 36 | 37 | property string action; 38 | property string categories; 39 | property string collection; 40 | property string engine; 41 | property string language; 42 | property string name; 43 | property string path; 44 | 45 | property name="properties" type="any" getter="false" setter="false"; 46 | 47 | //service tag to invoke 48 | variables.tagName = "CFCOLLECTION"; 49 | 50 | //cfcollection tag attributes 51 | variables.tagAttributes = getSupportedTagAttributes(getTagName()); 52 | 53 | /** 54 | * Default constructor invoked when search objects are created. 55 | * @return search object 56 | * @output false 57 | */ 58 | public collection function init() 59 | { 60 | if(!structisempty(arguments)) structappend(variables,arguments); 61 | return this; 62 | } 63 | 64 | 65 | /** 66 | * Invoke the cfcollection service tag to get categories for a collection 67 | * Usage :: new collection().categoryList(collection="fivetag"); 68 | * @output false 69 | */ 70 | public struct function categoryList() 71 | { 72 | //store tag attributes to be passed to the service tag in a local variable 73 | var tagAttributes = duplicate(getTagAttributes()); 74 | 75 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 76 | if (!structisempty(arguments)) 77 | { 78 | structappend(tagAttributes,arguments); 79 | } 80 | 81 | tagAttributes.action="categorylist"; 82 | 83 | //trim attribute values 84 | tagAttributes = trimAttributes(tagAttributes); 85 | 86 | return super.invokeTag(getTagName(),tagAttributes); 87 | } 88 | 89 | /** 90 | * Invoke the cfcollection service tag to create collections 91 | * Usage :: new collection().create(collection="fivetag",path="c..."); 92 | * @output false 93 | */ 94 | public struct function create() 95 | { 96 | //store tag attributes to be passed to the service tag in a local variable 97 | var tagAttributes = duplicate(getTagAttributes()); 98 | 99 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 100 | if (!structisempty(arguments)) 101 | { 102 | structappend(tagAttributes,arguments); 103 | } 104 | 105 | tagAttributes.action="create"; 106 | 107 | //trim attribute values 108 | tagAttributes = trimAttributes(tagAttributes); 109 | 110 | return super.invokeTag(getTagName(),tagAttributes); 111 | } 112 | 113 | /** 114 | * Invoke the cfcollection service tag to delete collections 115 | * Usage :: new collection().delete(collection="boom"); 116 | * @output false 117 | */ 118 | public struct function delete() 119 | { 120 | //store tag attributes to be passed to the service tag in a local variable 121 | var tagAttributes = duplicate(getTagAttributes()); 122 | 123 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 124 | if (!structisempty(arguments)) 125 | { 126 | structappend(tagAttributes,arguments); 127 | } 128 | 129 | tagAttributes.action="delete"; 130 | 131 | //trim attribute values 132 | tagAttributes = trimAttributes(tagAttributes); 133 | 134 | return super.invokeTag(getTagName(),tagAttributes); 135 | } 136 | 137 | /** 138 | * Invoke the cfcollection service tag to list solr collections in cfscript 139 | * Usage :: new collection().list(); 140 | * @output false 141 | */ 142 | public struct function list() 143 | { 144 | //store tag attributes to be passed to the service tag in a local variable 145 | var tagAttributes = duplicate(getTagAttributes()); 146 | 147 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 148 | if (!structisempty(arguments)) 149 | { 150 | structappend(tagAttributes,arguments); 151 | } 152 | 153 | tagAttributes.action="list"; 154 | 155 | //trim attribute values 156 | tagAttributes = trimAttributes(tagAttributes); 157 | 158 | return super.invokeTag(getTagName(),tagAttributes); 159 | } 160 | 161 | /** 162 | * Invoke the cfcollection service tag to optimize collections 163 | * Usage :: new collection().optimize(collection="boom"); 164 | * @output false 165 | */ 166 | public struct function optimize() 167 | { 168 | //store tag attributes to be passed to the service tag in a local variable 169 | var tagAttributes = duplicate(getTagAttributes()); 170 | 171 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 172 | if (!structisempty(arguments)) 173 | { 174 | structappend(tagAttributes,arguments); 175 | } 176 | 177 | tagAttributes.action="optimize"; 178 | 179 | //trim attribute values 180 | tagAttributes = trimAttributes(tagAttributes); 181 | 182 | return super.invokeTag(getTagName(),tagAttributes); 183 | } 184 | 185 | 186 | } -------------------------------------------------------------------------------- /content.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /cookie.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /execute.cfc: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Execute Service : Executes a ColdFusion developer-specified process on a server computer. 4 | * @name execute 5 | * @displayname ColdFusion Execute(Command Line) Service 6 | * @output false 7 | * @accessors true 8 | */ 9 | component extends="base" 10 | { 11 | property string name; 12 | property string arguments; 13 | property string outputFile; 14 | property numeric timeout; 15 | 16 | //service tag to invoke 17 | variables.tagName = "CFEXECUTE"; 18 | 19 | //valid ldap tag attributes 20 | variables.tagAttributes = getSupportedTagAttributes(getTagName()); 21 | 22 | 23 | 24 | /** 25 | * Initialization routine. Returns an instance of this component 26 | * @output false 27 | */ 28 | public execute function init() 29 | { 30 | if(!structisempty(arguments)) 31 | { 32 | structappend(variables,arguments,"yes"); 33 | } 34 | 35 | /* 36 | Special case for cfdump: 37 | If attributes is not set in the contructor, we set it to an empty string. 38 | If this is not done, value for attributes in dump will be a struct containing all properties set in cfc variables scope. 39 | */ 40 | /* 41 | if(!structkeyexists(variables,"attributes")){ 42 | this.setAttributes(""); 43 | } 44 | */ 45 | 46 | return this; 47 | } 48 | 49 | 50 | 51 | /** 52 | * Used to set "attributes" property. 53 | * Note that implicit setter setAttributes() is not used since a method with the same name is defined in base.cfc 54 | * @output false 55 | */ 56 | public void function setLdapAttributes(String attributes) 57 | { 58 | variables.attributes = attributes; 59 | } 60 | 61 | 62 | 63 | /** 64 | * Returns the "attributes" property, if defined, or an empty string 65 | * Note that implicit getter getAttributes() is not used since a method with the same name is defined in base.cfc 66 | * @output false 67 | */ 68 | public function getLdapAttributes() 69 | { 70 | return structkeyexists(variables,"attributes") ? variables.attributes : ""; 71 | } 72 | 73 | 74 | 75 | /** 76 | * Removes tag attributes from variables scope. Accepts a list of tag attributes 77 | * If no attributes are specified, all attributes are removed from CFC variables scope 78 | * @output false 79 | */ 80 | public void function clearAttributes(string tagAttributesToClear="") 81 | { 82 | //delegate call to clearAttributes in base.cfc 83 | super.clearAttributes(tagAttributesToClear); 84 | 85 | //required since we explicitly set attributes to "" (see init method) 86 | if(!structkeyexists(variables,"attributes")) 87 | this.setAttributes(""); 88 | } 89 | 90 | 91 | 92 | /** 93 | * Removes tag attributes 94 | * @output false 95 | */ 96 | public void function clear() 97 | { 98 | //delegate call to clearAttributes 99 | clearAttributes(); 100 | } 101 | 102 | /* ************************ BEGIN :: EXECUTE SERVICES *************************************** */ 103 | 104 | /** 105 | * Invoke the cfexecute tag to provide the command line access in cfscript 106 | * @output true 107 | */ 108 | public result function execute() 109 | { 110 | //store tag attributes to be passed to the service tag in a local variable 111 | var tagAttributes = duplicate(getTagAttributes()); 112 | 113 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 114 | if(!structisempty(arguments)) 115 | { 116 | structappend(tagAttributes,arguments,"yes"); 117 | } 118 | 119 | //if a result attribute is specified, we ignore it so that we can always return the result struct 120 | if(structkeyexists(tagAttributes,"result")) 121 | { 122 | structdelete(tagAttributes, "result"); 123 | } 124 | 125 | //trim attribute values 126 | tagAttributes = trimAttributes(tagAttributes); 127 | 128 | return super.invokeTag(getTagName(),tagAttributes); 129 | } 130 | 131 | 132 | 133 | /* ************************ END :: EXECUTE SERVICES *************************************** */ 134 | 135 | 136 | 137 | 138 | } -------------------------------------------------------------------------------- /flush.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /header.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /htmlhead.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /index.cfc: -------------------------------------------------------------------------------- 1 | /*********************************************************************************************************** 2 | * 3 | * Made by Raymond Camden, Jedi Master 4 | * 5 | * MIT License 6 | * 7 | * Copyright (c) 2011 The ColdFusion Community 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 10 | * associated documentation files (the "Software"), to deal in the Software without restriction, including 11 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 13 | * following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial 16 | * portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 19 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | * 24 | * See also: http://www.opensource.org/licenses/mit-license.html 25 | * 26 | *************************************************************************************************************/ 27 | 28 | /** 29 | * Index Service to perform solr index operations in cfscript 30 | * @name index 31 | * @displayname ColdFusion Index Service 32 | * @output false 33 | * @accessors true 34 | */ 35 | component extends="base" { 36 | 37 | property string action; 38 | property string collection; 39 | property string body; 40 | property string category; 41 | property string categoryTree; 42 | property string custom1; 43 | property string custom2; 44 | property string custom3; 45 | property string custom4; 46 | property string extensions; 47 | property string key; 48 | property string language; 49 | property string prefix; 50 | property string query; 51 | property string status; 52 | property string title; 53 | property string type; 54 | property string urlpath; 55 | 56 | property name="properties" type="any" getter="false" setter="false"; 57 | 58 | //service tag to invoke 59 | variables.tagName = "CFINDEX"; 60 | 61 | //cfcollection tag attributes 62 | variables.tagAttributes = getSupportedTagAttributes(getTagName()); 63 | 64 | /** 65 | * Default constructor invoked when search objects are created. 66 | * @return search object 67 | * @output false 68 | */ 69 | public index function init() 70 | { 71 | if(!structisempty(arguments)) structappend(variables,arguments); 72 | return this; 73 | } 74 | 75 | /** 76 | * Invoke the cfindex service tag to delete keys from solr collections in cfscript 77 | * Usage :: new index().delete(collection="beer",key=9); 78 | * @output false 79 | */ 80 | public struct function delete() 81 | { 82 | //store tag attributes to be passed to the service tag in a local variable 83 | var tagAttributes = duplicate(getTagAttributes()); 84 | 85 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 86 | if (!structisempty(arguments)) 87 | { 88 | structappend(tagAttributes,arguments); 89 | } 90 | 91 | tagAttributes.action="delete"; 92 | 93 | //trim attribute values 94 | tagAttributes = trimAttributes(tagAttributes); 95 | 96 | return super.invokeTag(getTagName(),tagAttributes); 97 | } 98 | 99 | /** 100 | * Invoke the cfindex service tag to purge a solr collection in cfscript 101 | * Usage :: new index().purge(collection="beer"); 102 | * @output false 103 | */ 104 | public struct function purge() 105 | { 106 | //store tag attributes to be passed to the service tag in a local variable 107 | var tagAttributes = duplicate(getTagAttributes()); 108 | 109 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 110 | if (!structisempty(arguments)) 111 | { 112 | structappend(tagAttributes,arguments); 113 | } 114 | 115 | tagAttributes.action="purge"; 116 | 117 | //trim attribute values 118 | tagAttributes = trimAttributes(tagAttributes); 119 | 120 | return super.invokeTag(getTagName(),tagAttributes); 121 | } 122 | 123 | /** 124 | * Invoke the cfindex service tag to refresh solr collections in cfscript 125 | * Usage :: new index().refresh(collection="beer",query=q); 126 | * @output false 127 | */ 128 | public struct function refresh() 129 | { 130 | //store tag attributes to be passed to the service tag in a local variable 131 | var tagAttributes = duplicate(getTagAttributes()); 132 | 133 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 134 | if (!structisempty(arguments)) 135 | { 136 | structappend(tagAttributes,arguments); 137 | } 138 | 139 | tagAttributes.action="refresh"; 140 | 141 | //trim attribute values 142 | tagAttributes = trimAttributes(tagAttributes); 143 | 144 | return super.invokeTag(getTagName(),tagAttributes); 145 | } 146 | 147 | /** 148 | * Invoke the cfindex service tag to refresh solr collections in cfscript 149 | * Usage :: new index().refresh(collection="beer",query=q); 150 | * @output false 151 | */ 152 | public struct function update() 153 | { 154 | //store tag attributes to be passed to the service tag in a local variable 155 | var tagAttributes = duplicate(getTagAttributes()); 156 | 157 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 158 | if (!structisempty(arguments)) 159 | { 160 | structappend(tagAttributes,arguments); 161 | } 162 | 163 | tagAttributes.action="update"; 164 | 165 | //trim attribute values 166 | tagAttributes = trimAttributes(tagAttributes); 167 | 168 | return super.invokeTag(getTagName(),tagAttributes); 169 | } 170 | 171 | } -------------------------------------------------------------------------------- /invoke.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /loginuser.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /logout.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /objectcache.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /query.cfc: -------------------------------------------------------------------------------- 1 | /*********************************************************************************************************** 2 | * 3 | * ADOBE CONFIDENTIAL 4 | * ___________________ 5 | * 6 | * Copyright 2008 Adobe Systems Incorporated 7 | * All Rights Reserved. 8 | * 9 | * NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the 10 | * terms of the Adobe license agreement accompanying it. If you have received this file from a 11 | * source other than Adobe, then your use, modification, or distribution of it requires the prior 12 | * written permission of Adobe. 13 | *************************************************************************************************************/ 14 | 15 | /** 16 | * Query Service to execute SQL queries in cfscript 17 | * @name query 18 | * @displayname ColdFusion Query Service 19 | * @output false 20 | * @accessors true 21 | */ 22 | component extends="base" 23 | { 24 | property string name; 25 | property numeric blockfactor; 26 | property string cachedafter; 27 | property string cachedwithin; 28 | property string dataSource; 29 | property string dbtype; 30 | property boolean debug; 31 | property numeric maxRows; 32 | property string password; 33 | property string result; 34 | property numeric timeout; 35 | property string username; 36 | property string sql; 37 | 38 | //array to store cfqueryparams 39 | variables.parameters = []; 40 | 41 | //service tag to invoke 42 | variables.tagName = "CFQUERY"; 43 | 44 | //list of valid cfquery tag attributes 45 | variables.tagAttributes = getSupportedTagAttributes(getTagName()); 46 | 47 | //default error message and error type 48 | variables.errorMessage = "Error Executing Database Query"; 49 | variables.errorType = "Application"; 50 | 51 | //constants 52 | variables.COMMA = ","; 53 | variables.RIGHTPARENTHESIS = ")"; 54 | variables.NEWLINE = chr(13) & chr(10); 55 | variables.SPACE = " "; 56 | variables.EMPTYSTRING = ""; 57 | variables.SINGLEQUOTE= "'"; 58 | variables.NAMED_DELIMITER = ":"; 59 | variables.POSITIONAL_DELIMITER = "?"; 60 | variables.NAMED_DELIMITER_MARKER = chr(2); 61 | variables.POSITIONAL_DELIMITER_MARKER = chr(3); 62 | 63 | 64 | /** 65 | * Initialization routine. Returns an instance of this component 66 | * @output false 67 | */ 68 | public any function init() 69 | { 70 | if(!structisempty(arguments)) 71 | { 72 | structappend(variables,arguments,"yes"); 73 | } 74 | return this; 75 | } 76 | 77 | 78 | /** 79 | * Inserts a named sql param into sqlParams array. If the named param is not found, an exception is thrown 80 | * 81 | * @param1 namedparam 82 | * @param2 namedparamsarray Array containing all named sql params 83 | * @param3 sqlParams Array containing all sql queryparams supplied 84 | * @return array with the sqlParams inserted 85 | * 86 | * @output false 87 | */ 88 | private array function insertNamedParams(string namedparam,array namedparamsarray,array sqlParams,string sql) 89 | { 90 | var i = 0; 91 | var foundNamedParam = false; 92 | for(i=1; i lte arraylen(namedparamsarray); i++) 93 | { 94 | if(structkeyexists(namedparamsarray[i],"name") and trim(namedparamsarray[i]["name"]) eq namedparam) 95 | { 96 | arrayappend(sqlParams,duplicate(namedparamsarray[i])); 97 | /* 98 | Delete "name" attribute for this entry (named param) from sqlParams array as we no longer need it, and besides, 99 | sending it to cfqueryparam with the name attribute would throw an error since allowExtraAttribute=false 100 | */ 101 | structdelete(sqlParams[arraylen(sqlParams)],"name"); 102 | foundNamedParam = true; 103 | break; 104 | } 105 | } 106 | if(!foundNamedParam) 107 | { 108 | var errorDetail = "Parameter '" & namedparam & "' not found in the list of parameters specified" & "

" & "SQL: " & sql; 109 | throw(errorMessage,errorType,errorDetail); 110 | } 111 | return sqlParams; 112 | } 113 | 114 | 115 | /** 116 | * Processes named sql params. Invokes insertNamedParams() to insert the named param into sqlparams array. 117 | * 118 | * @param1 sqlArray 119 | * @param2 sqlParams 120 | * @param3 namedparamsarray Array containing all named sql params 121 | * @param4 queryparams Array containing all sql queryparams supplied 122 | * @param5 sqlCommand Sql operation to perform 123 | * @return struct with sqlArray and sqlParams 124 | * 125 | * @output false 126 | */ 127 | private struct function processNamedParams(array sqlArray,array sqlParams,array namedparamsarray,array queryparams,string sqlCommand,string sql) 128 | { 129 | var namedparam = ""; 130 | var i = 0; 131 | if(arraylen(sqlArray) gt 0) 132 | { 133 | for(i=2; i <= arraylen(sqlArray); i++) 134 | { 135 | var delimters = SPACE & COMMA & RIGHTPARENTHESIS & NEWLINE; 136 | namedparam = trim(listfirst(sqlArray[i],delimters)); 137 | sqlArray[i] = replace(arguments.sqlArray[i],namedparam,EMPTYSTRING); 138 | sqlParams = insertNamedParams(namedparam,namedparamsarray,sqlParams,sql); 139 | } 140 | } 141 | return {sqlArray=sqlArray,sqlParams=sqlParams}; 142 | } 143 | 144 | 145 | /** 146 | * Removes first positional param from positional sql params array 147 | * 148 | * @param1 posparamsarray An array with positional sql params 149 | * @return array with first positional sql param removed 150 | * 151 | * @output false 152 | */ 153 | private array function removePosParam(array posparamsarray) 154 | { 155 | var newPosParamsArray = []; 156 | var i = 0; 157 | 158 | //create a temp array from posparamsarray without the first element 159 | for(i=2;i lte arraylen(posparamsarray); i++) 160 | { 161 | newPosParamsArray[i-1] = posparamsarray[i]; 162 | } 163 | return newPosParamsArray; 164 | } 165 | 166 | 167 | /** 168 | * Processes positional sql params 169 | * 170 | * @param1 sqlArray 171 | * @param1 sqlParams 172 | * @param1 posparamsarray An array with all positional params 173 | * @param1 queryparams An array with all queryparams the user supplied 174 | * @return struct with keys sqlArray and sqlParams 175 | * 176 | * @output false 177 | */ 178 | private struct function processPosParams(array sqlArray,array sqlParams,array posparamsarray,array queryparams,string sql) 179 | { 180 | if(arraylen(sqlArray) gt 0) 181 | { 182 | if(arraylen(queryparams) lt arraylen(sqlArray)-1) 183 | { 184 | var errorDetail = "The number of parameters specified do not match the number of specified or implied columns" & "

" & "SQL: " & sql; 185 | throw(errorMessage,errorType,errorDetail); 186 | } 187 | 188 | var j = 0; 189 | //loop until all positional sql params are processed 190 | for(j=1;j lte arraylen(sqlArray); j++) 191 | { 192 | if(arraylen(posparamsarray) gt 0) 193 | { 194 | arrayappend(sqlParams,posparamsarray[1]); 195 | } 196 | //remove the postional sql param from the posparamsarray array 197 | posparamsarray = removePosParam(posparamsarray); 198 | } 199 | } 200 | return {sqlArray=sqlArray,sqlParams=sqlParams}; 201 | } 202 | 203 | 204 | /** 205 | * Merge one array into another at a specified position and returns the merged array 206 | * 207 | * @param1 sqlArray1 Array into which to insert 208 | * @param2 sqlArray2 Array to be inserted 209 | * @param2 pos Position at which to insert 210 | * @return array (merged) 211 | * 212 | * @output false 213 | */ 214 | private array function arrayinsert(array sqlArray1, array sqlArray2, numeric pos) 215 | { 216 | var mergedArray = []; 217 | var i = 0; 218 | 219 | //copy array sqlArray1 elements until pos-1 into mergedArray 220 | for(i=1; i lte pos-1; i++) 221 | { 222 | arrayappend(mergedArray,sqlArray1[i]); 223 | } 224 | 225 | //copy sqlArray2 into mergedArray 226 | for(i=1; i lte arraylen(arguments.sqlArray2); i++) 227 | { 228 | arrayappend(mergedArray,sqlArray2[i]); 229 | } 230 | 231 | //copy the remainder of sqlArray1 elements 232 | for(i=pos+1; i lte arraylen(sqlArray1); i++) 233 | { 234 | arrayappend(mergedArray,sqlArray1[i]); 235 | } 236 | 237 | //return the merged array 238 | return mergedArray; 239 | } 240 | 241 | 242 | /** 243 | * Checks if a named sql param exists in namedparams array. 244 | * 245 | * @param1 namedparams named sql params array 246 | * @param2 currentnamedparam named param to search in the array 247 | * @return Position at which the named param is found or false otherwise 248 | * 249 | * @output false 250 | */ 251 | private boolean function checkIfNamedParamExists(array namedSqlparams, string namedparam) 252 | { 253 | var i = 0; 254 | for(i=1; i lte arraylen(namedSqlparams)-1; i++) 255 | { 256 | if(structkeyexists(namedSqlparams[i],"name") and trim(namedSqlparams[i]["name"]) eq namedparam) 257 | { 258 | return i; 259 | } 260 | } 261 | return false; 262 | } 263 | 264 | 265 | /** 266 | * parse queryparams stored in parameters array to filter out all named sql params. 267 | * 268 | * @param1 queryparams An array containing all queryparams 269 | * @return array containing all named sql params 270 | * 271 | * @output false 272 | */ 273 | private array function getNamedSqlParams(array queryparams) 274 | { 275 | var namedSqlParams = []; 276 | var i = 0; 277 | 278 | for(i=1; i lte arraylen(queryparams); i++) 279 | { 280 | if(structkeyexists(queryparams[i],"name")) 281 | { 282 | //If named sql param already exists, update it. Else, add it 283 | var namedparam = trim(queryparams[i]["name"]); 284 | var found = checkIfNamedParamExists(namedSqlParams,namedparam); 285 | if(!found) 286 | { 287 | //insert the queryparam into the namedSqlParams array 288 | arrayappend(namedSqlParams,queryparams[i]); 289 | } 290 | else 291 | { 292 | //since the named param already exists, update its value 293 | structappend(namedSqlParams[found],queryparams[i],"yes"); 294 | } 295 | } 296 | } 297 | return namedSqlParams; 298 | } 299 | 300 | 301 | /** 302 | * parse queryparams stored in parameters array to filter out all postional sql params. 303 | * 304 | * @param1 queryparams An array containing all queryparams 305 | * @return array containing all positional sql params 306 | * 307 | * @output false 308 | */ 309 | private array function getPositionalSqlParams(array queryparams) 310 | { 311 | var positionalSqlParams = []; 312 | var i = 0; 313 | for(i=1; i lte arraylen(queryparams); i++) 314 | { 315 | //if there is no "name" key in the queryparam, it is a positional sql param 316 | if(!structkeyexists(queryparams[i],"name")) 317 | { 318 | //insert the queryparam into the positionalSqlParams array 319 | arrayappend(positionalSqlParams,queryparams[i]); 320 | } 321 | } 322 | return positionalSqlParams; 323 | } 324 | 325 | 326 | /** 327 | * replace named and positional delimiters inside single quotes with corresponding markers to allow parsing SQL on delimiters 328 | * @output false 329 | */ 330 | private string function replaceDelimsWithMarkers(string sql, string sqlDelimtersList, string sqlDelimtersMarkersList) 331 | { 332 | var sqlArray = listtoarray(arguments.sql,SINGLEQUOTE,true); 333 | var newSql = ""; 334 | var i = 0; 335 | for(i=1; i lte arraylen(sqlArray); i++) 336 | { 337 | //even numbered array indices contain value inside the single quotes 338 | //replace occurence of named or positional delimiters in the value with corresponding markers 339 | if( (i%2) eq 0 ) 340 | { 341 | sqlArray[i] = ReplaceList(sqlArray[i],sqlDelimtersList,sqlDelimtersMarkersList); 342 | newSql = newSql & SINGLEQUOTE & sqlArray[i] & SINGLEQUOTE & " "; 343 | } 344 | else 345 | { 346 | newSql = newSql & sqlArray[i]; 347 | } 348 | } 349 | return newSql; 350 | } 351 | 352 | 353 | /** 354 | * Replaces markers for named and positional delimiters (inside single quotes) with actual delimiters 355 | * 356 | * @param1 sqlArray Array with parsed sql 357 | * @return array Array with markers replaced with single quotes 358 | * 359 | * @output false 360 | */ 361 | private array function replaceMarkersWithDelims(array sqlArray, string sqlDelimtersMarkersList, string sqlDelimtersList) 362 | { 363 | var i = 0; 364 | for(i=1; i lte arraylen(sqlArray); i++) 365 | { 366 | sqlArray[i] = ReplaceList(sqlArray[i],sqlDelimtersMarkersList,sqlDelimtersList); 367 | } 368 | return sqlArray; 369 | } 370 | 371 | 372 | /** 373 | * preservesinglequotes() can't handle expressions, so something like #preservesinglequotes(SQlArray[1])# would'nt work, 374 | * hence the need for this wrapper 375 | * @output false 376 | */ 377 | private string function getPreserveSingleQuotes(string sqlstatement) 378 | { 379 | return PreserveSingleQuotes(arguments.sqlstatement); 380 | } 381 | 382 | 383 | /** 384 | * Parse the SQL into named and positional params. 385 | * 386 | * For named params, order of params is not important but for postional params we need to match 387 | * the order in which the params are listed. 388 | * 389 | * Named SQL parameters are specified using ":" (for example, select * from art where artid = :artistid) 390 | * Positional SQL parameters are specified using "?" (for example, select * from art where artid = ? and artistid = ?) 391 | * 392 | * A combination of named and positional sql params is also possible (although not encouraged) 393 | * (for example, select * from art where artid = :artid and artistid = ?) 394 | * 395 | * If named/positional param delimiters (i.e. ":" and "?") appear inside single quotes, we replace them with markers 396 | * to help with the parsing. After we are done with the parsing and before we pass the sql to execute() for execution, we replace the markers back with 397 | * the delimiters 398 | * 399 | * This function parses the sql into sqlArray[] and sqlParams[] 400 | * 401 | * @param1 sql The sql used in the query 402 | * @param2 queryparams The queryparams for the query 403 | * @param3 sqlCommand The sql operation to perform 404 | * @return A struct with keys - sqlType (named/param/both), sqlArray (sql parsed into array) and sqlParams (queryparams parsed into array of structs) 405 | * 406 | * @output false 407 | */ 408 | private any function parseSQL(string sql,array queryparams,string sqlCommand) 409 | { 410 | //type of sql (named | positional | both | "" (for simple SQL)) 411 | var sqlType = ""; 412 | 413 | //sql delimiter (: -> named, ? -> positional) 414 | var delimiter = ""; 415 | 416 | //array to store parsed sql 417 | var sqlArray = []; 418 | 419 | //array to store parsed sql queryparams 420 | var sqlParams = []; 421 | 422 | //markers used to replace named or positional parameters found inside single quotes or '' 423 | var sqlDelimtersList = NAMED_DELIMITER & "," & POSITIONAL_DELIMITER; 424 | var sqlDelimtersMarkersList = NAMED_DELIMITER_MARKER & "," & POSITIONAL_DELIMITER_MARKER; 425 | 426 | //replace occurences of any named or positional delimters inside single quotes with markers since we are dealing with a value rather than a sql param 427 | var mysql = replaceDelimsWithMarkers(sql,sqlDelimtersList,sqlDelimtersMarkersList); 428 | var param = []; 429 | 430 | //parse queryparams (stored in paramters array) into named sql params and positional sql params 431 | var namedparamsarray = getNamedSqlParams(queryparams); 432 | var posparamsarray = getPositionalSqlParams(queryparams); 433 | 434 | //if the sql has both named and positional params, we first parse the sql for positional params, and then for named params 435 | if(mysql contains NAMED_DELIMITER and mysql contains POSITIONAL_DELIMITER) 436 | { 437 | sqlType = "both"; 438 | delimiter = POSITIONAL_DELIMITER; 439 | } 440 | else if(mysql contains NAMED_DELIMITER and not (mysql contains POSITIONAL_DELIMITER)) 441 | { 442 | sqlType = "namedSql"; 443 | delimiter = NAMED_DELIMITER; 444 | } 445 | else if(mysql contains POSITIONAL_DELIMITER and not (mysql contains NAMED_DELIMITER)) 446 | { 447 | sqlType = "posSql"; 448 | delimiter = POSITIONAL_DELIMITER; 449 | } 450 | else 451 | { 452 | sqlType = ""; 453 | delimiter = ""; 454 | } 455 | 456 | //convert sql into an array. If there are no named/positional params, the array would consist of merely the sql but if there are params, we use the delimiter 457 | sqlArray = (delimiter eq "")? [mysql]: listtoarray(mysql,delimiter,true,true); 458 | 459 | switch(sqlType) 460 | { 461 | //we are dealing with postional sql parameters 462 | case "posSql": 463 | { 464 | var s = processPosParams(sqlArray,sqlParams,posparamsarray,queryparams,sql); 465 | sqlArray = s["sqlArray"]; 466 | sqlParams = s["sqlParams"]; 467 | break; 468 | } 469 | 470 | //we are dealing with named sql parameters 471 | case "namedSql": 472 | { 473 | s = processNamedParams(sqlArray,sqlParams,namedparamsarray,queryparams,sqlCommand,sql); 474 | sqlArray = s["sqlArray"]; 475 | sqlParams = s["sqlParams"]; 476 | break; 477 | } 478 | 479 | //we are dealing with a combination of both named and positional sql parameters 480 | case "both": 481 | { 482 | continueloop = true; 483 | var i = 1; 484 | 485 | //loop until we are finished with sqlArray[], which grows dynamically as we continue processing the params 486 | while(continueloop eq true) 487 | { 488 | //this is a positional sql param that we are dealing with here 489 | if(sqlArray[i] does not contain NAMED_DELIMITER) 490 | { 491 | if(i lt arraylen(sqlArray)) 492 | { 493 | if(arraylen(posparamsarray) gt 0) 494 | { 495 | arrayappend(sqlParams,posparamsarray[1]); 496 | } 497 | posparamsarray = removePosParam(posparamsarray); 498 | } 499 | i = i + 1; 500 | } 501 | else 502 | { 503 | //this is a named sql param that we are dealing with here 504 | var temp = processNamedParams(listtoarray(sqlArray[i],NAMED_DELIMITER),sqlParams,namedparamsarray,queryparams,sqlCommand,sql); 505 | param = temp["sqlArray"]; 506 | sqlParams = temp["sqlParams"]; 507 | 508 | //insert array param into sqlArray at postion i and returns the new merged array 509 | sqlArray = arrayinsert(sqlArray,param,i); 510 | 511 | //since we are already done with processing the param array, we skip past it and resume our processing 512 | i = i + arraylen(param); 513 | if(i lte arraylen(sqlArray)) 514 | { 515 | if(arraylen(posparamsarray) gt 0) 516 | { 517 | arrayappend(sqlParams,posparamsarray[1]); 518 | } 519 | posparamsarray = removePosParam(posparamsarray); 520 | } 521 | } 522 | if(i gt arraylen(sqlArray)) 523 | { 524 | continueloop=false; 525 | } 526 | } 527 | break; 528 | } 529 | default: 530 | { 531 | sqlArray = [mysql]; 532 | break; 533 | } 534 | } 535 | 536 | //replace markers for named and positional delimiters with actual delimiters 537 | sqlArray = replaceMarkersWithDelims(sqlArray,sqlDelimtersMarkersList,sqlDelimtersList); 538 | 539 | //return a struct with the parsed array and parsed queryparams back 540 | return {sqlArray=sqlArray,sqlParams=sqlParams,sqlType=sqlType}; 541 | } 542 | 543 | 544 | 545 | /** 546 | * Validate SQL for errors 547 | * @return SQL or an error if SQL has errors 548 | * @output false 549 | */ 550 | private string function validateSQL(struct tagAttributes) 551 | { 552 | var sql = ""; 553 | var errorDetail = ""; 554 | 555 | //throw an error if SQL is not defined 556 | if(not structkeyexists(tagAttributes,"sql")) 557 | { 558 | errorDetail = "SQL is required"; 559 | throw(errorMessage,errorType,errorDetail); 560 | } 561 | else 562 | { 563 | sql = tagAttributes["sql"]; 564 | //throw an error if SQL is empty 565 | if(len(trim(sql)) eq 0) 566 | { 567 | errorDetail = "The value of SQL cannot be empty"; 568 | throw(errorMessage,errorType,errorDetail); 569 | } 570 | } 571 | return sql; 572 | } 573 | 574 | 575 | /** 576 | * Invoke the cfquery (and cfqueryparam) service tag to execute a query in cfscript. Returns the query resultset 577 | * @output false 578 | */ 579 | public result function execute() 580 | { 581 | //store tag attributes to be passed to the service tag in a local variable 582 | var tagAttributes = duplicate(getTagAttributes()); 583 | 584 | //store query params 585 | var queryparams = duplicate(variables.parameters); 586 | 587 | //attributes passed to service tag action like execute() override existing attributes and are discarded after the action 588 | if(!structisempty(arguments)) 589 | { 590 | structappend(tagAttributes,arguments,"yes"); 591 | } 592 | 593 | //extract SQL from attributes and validate it for errors 594 | var sql = validateSQL(tagAttributes); 595 | 596 | //remove leading and trailing parenthesis for stored procs called using {CALL myproc 1,2} 597 | if(left(sql,1) eq "{" and right(sql,1) eq "}") 598 | { 599 | sql = trim(mid(sql,2,len(sql)-2)); 600 | } 601 | 602 | //sql operation to perform (Select | update | insert | delete | exec | call) 603 | var sqlCommand = ucase(listfirst(sql," ")); 604 | 605 | //parse SQL and process queryparams, if defined 606 | var r = parseSQL(sql,queryparams,sqlCommand); 607 | var sqlType = r["sqlType"]; 608 | var sqlArray = r["sqlArray"]; 609 | var sqlParams = r["sqlParams"]; 610 | 611 | //put back leading and trailing parenthesis for stored procs called using {CALL myproc 1,2} 612 | if(sqlCommand eq "CALL") 613 | { 614 | sqlArray[1] = "{" & sqlArray[1]; 615 | sqlArray[arraylen(sqlArray)] = sqlArray[arraylen(sqlArray)] & "}"; 616 | } 617 | 618 | //looks like query name attribute isn't really required, so we give the query a name in case one is not specified 619 | if(!structkeyexists(tagAttributes,"name") or (structkeyexists(tagAttributes,"name") and tagAttributes["name"] eq "")) 620 | { 621 | tagAttributes["name"] = "qryname#randrange(1,100000)#"; 622 | } 623 | 624 | //should we do the same for result attribute also? 625 | if(!structkeyexists(tagAttributes,"result") or (structkeyexists(tagAttributes,"result") and tagAttributes["result"] eq "")) 626 | { 627 | tagAttributes["result"] = "qryresult#randrange(1,100000)#"; 628 | } 629 | 630 | //delete sql attribute since passing it would execute the sql which is not what we want as we are preparsing the sql 631 | if(structkeyexists(tagAttributes,"sql")) 632 | { 633 | structdelete(tagAttributes,"sql"); 634 | } 635 | 636 | //trim attribute values 637 | tagAttributes = trimAttributes(tagAttributes); 638 | 639 | //invoke the cfquery/cfqueryparam tags replacing any named or positional params with cfqueryparam tag 640 | return super.invokeTag(getTagName(),tagAttributes,{params=sqlParams,sqlArray=sqlArray,sql=sql,sqlType=sqlType}); 641 | } 642 | } -------------------------------------------------------------------------------- /queryofqueries.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | #preserveSingleQuotes(arguments.SQL)# 28 | 29 | 30 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # ColdFusion 9+ Script Components 2 | ## Community Edition! 3 | 4 | The components in this project are contributed by the ColdFusion community, for the ColdFusion Community. 5 | 6 | ### Installing... 7 | 8 | You can simply [download the zip](https://github.com/CFCommunity/CFScript-Community-Components/zipball/master) of the available components and toss them in the appropriate directory, if you want. (`{cf-install}\CustomTags\com\adobe\coldfusion\`) 9 | 10 | It would be easier to update if you use git: 11 | 12 | 1. Open a terminal prompt at the above directory 13 | 1. Run this: `git init;git remote add origin git://github.com/CFCommunity/CFScript-Community-Components.git;git pull` 14 | 1. Any time you want to update, simply (go into that directory and) run `git pull` 15 | 16 | # Deficiencies! 17 | 18 | These are the tags that need to be ported over to script. Godspeed! 19 | 20 | ## Significant omissions: 21 | 22 | * cfexchangecalendar 23 | * cfexchangeconnection 24 | * cfexchangecontact 25 | * cfexchangefilter 26 | * cfexchangemail 27 | * cfexchangetask 28 | * cfmodule 29 | * cfoutput (implementation of query looping with grouping) 30 | * cfparam (fix the bug in that enforced requiredness doesn’t work (ie: param name="foo";)) 31 | * cfquery (cachedwithin support) 32 | 33 | ## Reasonable case 34 | 35 | * cfassociate 36 | * cfcache 37 | * cfprint 38 | * cfsharepoint 39 | * cftimer 40 | 41 | ## Ambivalent 42 | 43 | * cfgridupdate 44 | * cfinsert 45 | * cflogin 46 | * cfreport 47 | * cfreportparam 48 | * cfupdate 49 | 50 | # Adobe copyright... 51 | 52 | The following components ship with CF9 and CF9.01. Adobe holds copyright on them, but has granted permission to modify and distribute under an unspecified license, the text of which is below the list. Unless modified to support additional functionality or fix any bugs that may arise, they are not included in this repository. Any time they are changed, you can be sure tremendous effort has been made to maintain compatibility and, unless noted somewhere in this readme, has been achieved. 53 | 54 | * base.cfc 55 | * dbinfo.cfc 56 | * feed.cfc 57 | * ftp.cfc 58 | * http.cfc 59 | * imap.cfc 60 | * ldap.cfc 61 | * mail.cfc 62 | * pdf.cfc 63 | * pop.cfc 64 | * query.cfc 65 | * result.cfc 66 | * storedproc.cfc 67 | * storedprocresult.cfc 68 | 69 | ## Adobe license 70 | 71 | This license text is taken from the components that ship with Adobe ColdFusion 9 and applies only to those components. 72 | 73 | >ADOBE CONFIDENTIAL 74 | >___________________ 75 | > 76 | >Copyright 2008 Adobe Systems Incorporated 77 | >All Rights Reserved. 78 | > 79 | >NOTICE: Adobe permits you to use, modify, and distribute this file in accordance with the terms of the Adobe license agreement accompanying it. If you have received this file from a source other than Adobe, then your use, modification, or distribution of it requires the prior written permission of Adobe. 80 | 81 | # MIT License 82 | 83 | >Copyright (c) 2011 The ColdFusion Community 84 | > 85 | >Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 86 | > 87 | >The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 88 | > 89 | >THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 90 | 91 | See also: [http://www.opensource.org/licenses/mit-license.html](http://www.opensource.org/licenses/mit-license.html) 92 | 93 | # Hey Adobe! 94 | 95 | You are welcome, and encouraged, to ship these in future versions of ColdFusion. Please do, at least where appropriate. In some cases it just doesn't look and feel right to use CFCs to make these features. When that's the case, they are included here only as a stop-gap so that we can _actually write full-script components_. Please, where it makes sense, still write the features in Java. But where this implementation looks and feels natural, by all means, run with it! It'll make life better for everyone! -------------------------------------------------------------------------------- /registry.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /schedule.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /search.cfc: -------------------------------------------------------------------------------- 1 | /*********************************************************************************************************** 2 | * 3 | * Made by Raymond Camden, Jedi Master 4 | * 5 | * MIT License 6 | * 7 | * Copyright (c) 2011 The ColdFusion Community 8 | * 9 | * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 10 | * associated documentation files (the "Software"), to deal in the Software without restriction, including 11 | * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the 13 | * following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in all copies or substantial 16 | * portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 19 | * LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 20 | * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 21 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | * 24 | * See also: http://www.opensource.org/licenses/mit-license.html 25 | * 26 | *************************************************************************************************************/ 27 | 28 | /** 29 | * Search Service to perform search operations in cfscript 30 | * @name search 31 | * @displayname ColdFusion Search Service 32 | * @output false 33 | * @accessors true 34 | */ 35 | component extends="base" { 36 | 37 | property string collection; 38 | //property string name; 39 | property string category; 40 | property string categoryTree; 41 | property numeric contextBytes; 42 | property string contextHighlightBegin; 43 | property string contextHighlightEnd; 44 | property numeric contextPassages; 45 | property string criteria; 46 | //Language is deprecated 47 | property numeric maxRows; 48 | property string previousCriteria; 49 | property numeric startRow; 50 | property string status; 51 | property string suggestions; 52 | property string orderby; 53 | //Type is deprecated 54 | 55 | property name="properties" type="any" getter="false" setter="false"; 56 | 57 | //service tag to invoke 58 | variables.tagName = "CFSEARCH"; 59 | 60 | //cffeed tag attributes 61 | variables.tagAttributes = getSupportedTagAttributes(getTagName()); 62 | 63 | /** 64 | * Default constructor invoked when search objects are created. 65 | * @return search object 66 | * @output false 67 | */ 68 | public search function init() 69 | { 70 | if(!structisempty(arguments)) structappend(variables,arguments); 71 | return this; 72 | } 73 | 74 | /** 75 | * Invoke the cfsearch service tag to search solr collections in cfscript 76 | * Usage :: new search().search(criteria="foo",maxRows=10); 77 | * @output false 78 | */ 79 | public struct function search() 80 | { 81 | //store tag attributes to be passed to the service tag in a local variable 82 | var tagAttributes = duplicate(getTagAttributes()); 83 | 84 | //attributes passed to service tag action like send() override existing attributes and are discarded after the action 85 | if (!structisempty(arguments)) 86 | { 87 | structappend(tagAttributes,arguments); 88 | } 89 | 90 | //trim attribute values 91 | tagAttributes = trimAttributes(tagAttributes); 92 | 93 | return super.invokeTag(getTagName(),tagAttributes); 94 | } 95 | 96 | } -------------------------------------------------------------------------------- /setting.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /spreadsheet.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /tests/ExecuteTest.cfc: -------------------------------------------------------------------------------- 1 | component extends="mxunit.framework.TestCase" { 2 | 3 | variables.NixOSList = "Mac OS X"; 4 | variables.noTestError = "The platform [#server.os.name#] is not in the executeTest server list, or there are no tests written for it yet."; 5 | 6 | public function testDate(){ 7 | var commandline = createObject("component", "CFScript-Community-Components.execute").init(); 8 | 9 | if (ListFind(server.OS.name, variables.NixOSList) > 0){ 10 | commandline.setName("date"); 11 | commandline.setArguments("+%Y-%m-%d-%H-%M-%S"); 12 | commandline.setTimeout(5); 13 | var result = commandline.execute(); 14 | var actual = result.getResult().result; 15 | var now = now(); 16 | var expected = DateFormat(now, "yyyy-mm-dd-") & TimeFormat(now, "HH-mm-ss"); 17 | AssertTrue(Len(result.getResult().result) > 0); 18 | AssertTrue(Len(result.getResult().error) == 0); 19 | AssertEquals(expected, actual); 20 | } 21 | else{ 22 | fail(variables.noTestError); 23 | } 24 | 25 | } 26 | 27 | public function testExecuteError(){ 28 | var commandline = createObject("component", "CFScript-Community-Components.execute").init(); 29 | 30 | if (ListFind(server.OS.name, variables.NixOSList) > 0){ 31 | commandline.setName("ls"); 32 | commandline.setArguments("/null"); 33 | commandline.setTimeout(5); 34 | var result = commandLine.execute(); 35 | AssertTrue(Len(result.getResult().result) == 0); 36 | AssertTrue(Len(result.getResult().error) > 0); 37 | var actual = commandline.execute().getResult().error; 38 | var expected = "ls: /null: No such file or directory"; 39 | AssertEquals(expected, actual); 40 | } 41 | else{ 42 | fail(variables.noTestError); 43 | } 44 | 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /tests/LoginFrameworkTest.cfc: -------------------------------------------------------------------------------- 1 | component extends="mxunit.framework.TestCase" { 2 | 3 | function testLoginLogout() { 4 | assertFalse(isUserLoggedIn()); 5 | assertFalse(isUserInAnyRole("role,otherRole")); 6 | assertFalse(isUserInRole("role")); 7 | 8 | createObject("component", "CFScript-Community-Components.loginuser").init("everybody","secretpassword","role"); 9 | 10 | assertTrue(isUserLoggedIn()); 11 | assertTrue(isUserInAnyRole("role,otherRole")); 12 | assertTrue(isUserInRole("role")); 13 | assertFalse(isUserInRole("otherRole")); 14 | 15 | createObject("component", "CFScript-Community-Components.logout").init(); 16 | 17 | assertFalse(isUserLoggedIn()); 18 | assertFalse(isUserInAnyRole("role,otherRole")); 19 | assertFalse(isUserInRole("role")); 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /tests/SpreadsheetTest.cfc: -------------------------------------------------------------------------------- 1 | component extends="mxunit.framework.TestCase" { 2 | 3 | 4 | function testSpreadsheet() { 5 | var spreadsheet = createObject("component", "CFScript-Community-Components.spreadsheet"); 6 | 7 | var testFile = getTempDirectory() & "spreadsheet-unittest.xls"; 8 | if(fileExists(testFile)) { 9 | fileDelete(testFile); 10 | } 11 | 12 | var dataToWrite = queryNew("ColumnA,ColumnB,ColumnC"); 13 | queryAddRow(dataToWrite, 3); 14 | dataToWrite.ColumnA[1] = "One"; 15 | dataToWrite.ColumnB[1] = 1; 16 | dataToWrite.ColumnC[1] = "Uno"; 17 | dataToWrite.ColumnA[2] = "Two"; 18 | dataToWrite.ColumnB[2] = 2; 19 | dataToWrite.ColumnC[2] = "Dos"; 20 | dataToWrite.ColumnA[3] = "Three"; 21 | dataToWrite.ColumnB[3] = 3; 22 | dataToWrite.ColumnC[3] = "Tres"; 23 | 24 | spreadsheet.write( 25 | filename = testFile, 26 | query = dataToWrite 27 | ); 28 | 29 | var spreadsheetInfo = spreadsheet.readInfo( 30 | src = testFile 31 | ); 32 | 33 | assertTrue(structKeyExists(spreadsheetInfo, "summaryInfo")); 34 | assertEquals(spreadsheetInfo.summaryInfo.sheets, 1); 35 | 36 | spreadsheet.update( 37 | filename = testfile, 38 | query = dataToWrite, 39 | sheetname = "blah" 40 | ); 41 | 42 | var spreadsheetInfo = spreadsheet.readInfo( 43 | src = testFile 44 | ); 45 | 46 | assertTrue(structKeyExists(spreadsheetInfo, "summaryInfo")); 47 | assertEquals(spreadsheetInfo.summaryInfo.sheets, 2); 48 | 49 | 50 | var spreadsheetData = spreadsheet.readQuery( 51 | src = testfile, 52 | excludeHeaderRow = true, 53 | headerRow = 1 54 | ); 55 | 56 | assertTrue(isQuery(spreadsheetData)); 57 | assertEquals(spreadsheetData.ColumnA[3], "Three"); 58 | 59 | if(fileExists(testFile)) { 60 | fileDelete(testFile); 61 | } 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /tests/queryofqueriesTest.cfc: -------------------------------------------------------------------------------- 1 | component extends="mxunit.framework.TestCase" { 2 | 3 | private struct function getTestData() { 4 | var queries = {}; 5 | // Set up two test queries 6 | var q1 = queryNew("id,value"); 7 | queryAddRow(q1); 8 | querySetCell(q1, "id", 1); 9 | querySetCell(q1, "value", "loo"); 10 | queryAddRow(q1); 11 | querySetCell(q1, "id", 2); 12 | querySetCell(q1, "value", "foo"); 13 | queryAddRow(q1); 14 | querySetCell(q1, "id", 3); 15 | querySetCell(q1, "value", "zoo"); 16 | 17 | var q2 = queryNew("id,othervalue"); 18 | queryAddRow(q2); 19 | querySetCell(q2, "id", 1); 20 | querySetCell(q2, "othervalue", "boo"); 21 | queryAddRow(q2); 22 | querySetCell(q2, "id", 3); 23 | querySetCell(q2, "othervalue", "too"); 24 | queryAddRow(q2); 25 | querySetCell(q2, "id", 44); 26 | querySetCell(q2, "othervalue", "moo"); 27 | 28 | queries.q1 = q1; 29 | queries.q2 = q2; 30 | return queries; 31 | } 32 | 33 | function testQueryOfQueries() { 34 | data = getTestData(); 35 | 36 | // This is how you use the QoQ component 37 | var queryResults = createObject("component", "CFScript-Community-Components.queryofqueries").init( 38 | SQL = "select * from q1, q2 where q1.id = q2.id", 39 | q1 = data.q1, 40 | q2 = data.q2 41 | ); 42 | 43 | assertEquals(queryResults.recordcount, 2); 44 | 45 | var queryResults = createObject("component", "CFScript-Community-Components.queryofqueries").init( 46 | SQL = "select * from q1 where id = 2", 47 | q1 = data.q1, 48 | q2 = data.q2 49 | ); 50 | 51 | assertEquals(queryResults.recordcount, 1); 52 | 53 | var queryResults = createObject("component", "CFScript-Community-Components.queryofqueries").init( 54 | SQL = "select * from q1, q2 where q1.id = q2.id and q1.id < 4", 55 | q1 = data.q1, 56 | q2 = data.q2 57 | ); 58 | 59 | assertEquals(queryResults.recordcount, 2); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /wddx.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /zip.cfc: -------------------------------------------------------------------------------- 1 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | --------------------------------------------------------------------------------