1const ConditionsChecking = { 2 conditionsCache: [], 3 fieldNamesCache: {}, 4 triggersCache: {}, 5 boolList: ['false', 'true'], 6 operationsList: ['!(', 'not(', 'not (', '(', ')'], 7 operationsListIncludInSpaceParenthesis: ['and', 'or'], 8 conditionsList: ['==', '!=', ' in', '|length ==', '|length !=', '|length <', '|length <=', '|length >=', '|length >', ' is empty', ' is not empty'], 9 pregQuote(input) { 10 return (`${input}`).replace(new RegExp('[.\\[\\]\\^(){}!=\\\\+*?$<>|:]', 'g'), '\\$&') 11 }, 12 updateOperationData(data, rest, condition, element) { 13 const newIndex = (rest == undefined) ? -1 : condition.length - rest[0].length 14 if (Object.keys(data).length == 0 || data.indexOf == undefined) { 15 data.indexOf = -1 16 } 17 if (newIndex > -1 && (newIndex < data.indexOf || data.indexOf < 0)) { 18 data.indexOf = newIndex 19 data.element = element 20 data.fullRest = rest[1] 21 } 22 }, 23 updateObject(data, condition, names) { 24 const result = {} 25 if (data.indexOf < 0) { 26 result[names.current] = condition 27 result[names.rest] = '' 28 result[names.element] = '' 29 } else { 30 result[names.current] = condition.substr(0, data.indexOf).trim() 31 result[names.element] = data.element 32 result[names.rest] = condition.substr(data.indexOf + data.fullRest.length).trim() 33 } 34 return result 35 }, 36 getFirstOperation(parsingObject) { 37 const condition = parsingObject.restOfCondition.trim() 38 const data = {} 39 for (let index = 0; index < this.operationsList.length; index++) { 40 const element = this.operationsList[index] 41 const rest = condition.match(new RegExp(`(${this.pregQuote(element)})(.*)$`, 'i')) 42 this.updateOperationData(data, rest, condition, element) 43 } 44 for (let index = 0; index < this.operationsListIncludInSpaceParenthesis.length; index++) { 45 const element = this.operationsListIncludInSpaceParenthesis[index] 46 const rest = condition.match(new RegExp(`((?<= |\\)|^)${element}(?= |\\)))(.*)$`, 'i')) 47 this.updateOperationData(data, rest, condition, element) 48 } 49 return this.updateObject(data, condition, { current: 'currentCondition', rest: 'restOfCondition', element: 'operation' }) 50 }, 51 addCondition(condition) { 52 const conditionLocal = condition.trim() 53 const data = {} 54 for (let index = 0; index < this.conditionsList.length; index++) { 55 const element = this.conditionsList[index] 56 const rest = conditionLocal.match(new RegExp(`(${this.pregQuote(element)})(.*)$`, 'i')) 57 this.updateOperationData(data, rest, conditionLocal, element) 58 } 59 for (let index = 0; index < this.boolList.length; index++) { 60 const element = this.boolList[index] 61 const rest = conditionLocal.match(new RegExp(`(${this.pregQuote(element)})(.*)$`, 'i')) 62 this.updateOperationData(data, rest, conditionLocal, element) 63 } 64 return this.updateObject(data, conditionLocal, { current: 'leftPart', rest: 'rightPart', element: 'typeOfCondition' }) 65 }, 66 getCheckboxValues(field) { 67 const result = [] 68 $(field).find('input[type=checkbox]').each(function() { 69 if ($(this).prop('checked') == true) { 70 const name = $(this).attr('name') 71 let value = name.match(/\[[A-Za-z0-9_-]+\]/)[0] 72 value = value.substr(1, value.length - 2) 73 result.push(value) 74 } 75 }) 76 return result 77 }, 78 getCheckboxTagValues(field) { 79 let result = [] 80 const value = $(field).val() 81 if (value.trim() != '') { 82 result = value.split(',') 83 } 84 return result 85 }, 86 getRadioValues(inputs) { 87 const result = [] 88 $(inputs).each(function() { 89 if ($(this).prop('checked') == true) { 90 result.push($(this).attr('value')) 91 } 92 }) 93 return result 94 }, 95 getSelectValues(field) { 96 const result = [] 97 const value = $(field).val() 98 if (value.trim() != '') { 99 result.push(value.trim()) 100 } 101 return result 102 }, 103 getFieldNameValues(fieldName) { 104 if (typeof this.fieldNamesCache[fieldName] === 'undefined') { 105 return [] 106 } 107 const fieldData = this.fieldNamesCache[fieldName] 108 switch (fieldData.type) { 109 case 'checkbox': 110 return this.getCheckboxValues(fieldData.node) 111 case 'checkboxtag': 112 return this.getCheckboxTagValues(fieldData.node) 113 case 'radio': 114 return this.getRadioValues(fieldData.node) 115 case 'select':
116 return this.getSelectValues(fieldData.node) 117 default: 118 break 119 } 120 return [] 121 }, 122 extractValues(values) { 123 if (values.trim() == '') { 124 return [] 125 } 126 let tempValues = values.trim() 127 if (tempValues.substr(0, 1) == '[' && tempValues.substr(-1) == ']') { 128 tempValues = tempValues.substr(1, tempValues.length - 2) 129 } 130 return tempValues.split(',') 131 }, 132 commonForOperations(fieldName, values, extract) { 133 const fieldValues = this.getFieldNameValues(fieldName) 134 const extractedValues = this.extractValues(values) 135 for (let index = 0; index < extractedValues.length; index++) { 136 if (extract.uniqueValues.indexOf(extractedValues[index].trim()) == -1) { 137 extract.uniqueValues.push(extractedValues[index].trim()) 138 } 139 } 140 for (let index = 0; index < fieldValues.length; index++) { 141 if (extract.uniqueFieldValues.indexOf(fieldValues[index].trim()) == -1) { 142 extract.uniqueFieldValues.push(fieldValues[index].trim()) 143 } 144 } 145 }, 146 isLength(fieldName, values, operation) { 147 if (isNaN(values)) { 148 return false 149 } 150 const fieldValues = this.getFieldNameValues(fieldName) 151 const uniqueFieldValues = [] 152 for (let index = 0; index < fieldValues.length; index++) { 153 if (uniqueFieldValues.indexOf(fieldValues[index].trim()) == -1) { 154 uniqueFieldValues.push(fieldValues[index].trim()) 155 } 156 } 157 const { length } = fieldValues 158 const number = Number(values) 159 return eval(`${length} ${operation} ${number}`) 160 }, 161 isEqual(fieldName, values) { 162 const extract = { 163 uniqueValues: [], 164 uniqueFieldValues: [] 165 } 166 this.commonForOperations(fieldName, values, extract) 167 if (extract.uniqueValues.length != extract.uniqueFieldValues.length) { 168 return false 169 } 170 let result = true 171 for (let index = 0; index < extract.uniqueFieldValues.length; index++) { 172 if (extract.uniqueValues.indexOf(extract.uniqueFieldValues[index]) == -1) { 173 result = false 174 } 175 } 176 return result 177 }, 178 isUnEqual(fieldName, values) { 179 const extract = { 180 uniqueValues: [], 181 uniqueFieldValues: [] 182 } 183 this.commonForOperations(fieldName, values, extract) 184 if (extract.uniqueValues.length != extract.uniqueFieldValues.length) { 185 return true 186 } 187 let result = false 188 for (let index = 0; index < extract.uniqueFieldValues.length; index++) { 189 if (extract.uniqueValues.indexOf(extract.uniqueFieldValues[index]) == -1) { 190 result = true 191 } 192 } 193 return result 194 }, 195 isIn(fieldName, values) { 196 const extract = { 197 uniqueValues: [], 198 uniqueFieldValues: [] 199 } 200 this.commonForOperations(fieldName, values, extract) 201 if (extract.uniqueFieldValues.length == 0) { 202 return false 203 } 204 let result = false 205 for (let index = 0; index < extract.uniqueFieldValues.length; index++) { 206 if (extract.uniqueValues.indexOf(extract.uniqueFieldValues[index]) > -1) { 207 result = true 208 } 209 } 210 return result 211 }, 212 isEmpty(fieldName) { 213 return this.isEqual(fieldName, '') 214 }, 215 isNotEmpty(fieldName) { 216 return this.isUnEqual(fieldName, '') 217 }, 218 renderCondition(structuredCondition) { 219 if (typeof structuredCondition.leftPart !== 'undefined' 220 && typeof structuredCondition.rightPart !== 'undefined' 221 && typeof structuredCondition.typeOfCondition !== 'undefined') { 222 return this.renderConditionSecured( 223 structuredCondition.leftPart.trim(), 224 structuredCondition.typeOfCondition.trim(), 225 structuredCondition.rightPart.trim() 226 ) 227 } 228 console.log(structuredCondition) 229 230 return '' 231 }, 232 renderConditionSecured(fieldName, condition, values) { 233 switch (condition) { 234 case '==': 235 return ` this.isEqual("${fieldName}","${values}")` 236 case '!=': 237 return ` this.isUnEqual("${fieldName}","${values}")` 238 case 'in': 239 return ` this.isIn("${fieldName}","${values}")` 240 case 'is empty': 241 return ` this.isEmpty("${fieldName}")` 242 case 'is not empty': 243 return ` this.isNotEmpty("${fieldName}")` 244 case '|length ==': 245 case '|length !=': 246 case '|length <': 247 case '|length <=': 248 case '|length >': 249 case '|length >=': 250 return ` this.isLength("${fieldName}","${values}","${condition.substr('|length '.length)}")` 251 case 'false': 252 return ' false ' 253 case 'true': 254 return ' true ' 255 case '': 256 return '' 257 default: 258 break 259 } 260 return ' false ' 261 }, 262 renderBadFormatingError(structuredCondition, conditionData) { 263 if (typeof structuredCondition.leftPart !== 'undefined' && structuredCondition.leftPart.length != 0) { 264 console.warn(`Left part ('${structuredCondition.leftPart}') should be empty before '${structuredCondition.operation}' in '${conditionData.condition}'`) 265 return true 266 } 267 return false 268 }, 269 emptyCheckbox(element) { 270 $(element).find('input[type=checkbox]').each(function() { 271 $(this).prop('checked', false) 272 $(this).trigger('change') 273 }) 274 }, 275 setDefaultCheckbox(element) { 276 $(element).find('input[type=checkbox]:not([data-default])').each(function() { 277 $(this).prop('checked', false) 278 $(this).trigger('change') 279 }) 280 $(element).find('input[type=checkbox][data-default]').each(function() { 281 const defaultVal = $(this).data('default') 282 if (defaultVal == 'checked') { 283 $(this).prop('checked', true) 284 $(this).trigger('change') 285 } 286 }) 287 }, 288 emptySelect(element) { 289 $(element).find('select').each(function() { 290 $(this).val('') 291 $(this).trigger('change') 292 }) 293 }, 294 setDefaultSelect(element) { 295 $(element).find('select[data-default]').each(function() { 296 const defaultVal = $(this).data('default') 297 const val = $(this).val() 298 if (defaultVal != undefined) { 299 $(this).val(defaultVal) 300 $(this).trigger('change') 301 } 302 }) 303 }, 304 emptyTextarea(element) { 305 $(element).find('textarea').each(function() { 306 $(this).val('') 307 $(this).trigger('change') 308 }) 309 }, 310 emptyRadio(element) { 311 // warning it unselect the radio button but this will not erase previous saved value 312 // it is needed to have a new value to erase it 313 $(element).find('input[type=radio]').each(function() { 314 $(this).prop('checked', false) 315 $(this).trigger('change') 316 }) 317 }, 318 setDefaultRadio(element) { 319 $(element).find('input[type=radio]:not([data-default])').each(function() { 320 $(this).prop('checked', false) 321 $(this).trigger('change') 322 }) 323 $(element).find('input[type=radio][data-default]').each(function() { 324 const defaultVal = $(this).data('default') 325 if (defaultVal == 'checked') { 326 $(this).prop('checked', true) 327 $(this).trigger('change') 328 } 329 }) 330 }, 331 emptyTextarea(element) { 332 $(element).find('textarea').each(function() { 333 $(this).val('') 334 $(this).trigger('change') 335 }) 336 }, 337 emptyGeocode(element) { 338 $(element).find('div[class*="geocode-input"] input[type=hidden]').each(function() { 339 $(this).val('') 340 $(this).trigger('change') 341 }) 342 }, 343 emptyImage(element) { 344 $(element).find('div[class*="bazar-entry-edit-image"]').each(() => { 345 // currently not activated because ImageField is not safe 346 // TODO activate and TEST (prefer usage of ajax) 347 // $(this).find('output').html(""); 348 // $(this).find('input[id^=data-][type=hidden]').val(""); 349 // $(this).find('input[id^=filename-][type=hidden]').val(""); 350 // $(this).find('input[id^=oldimage-][type=hidden]').val(""); 351 // $(this).find('input[type=file]').val(" ");
351 // works only if after this.emptyOthersInputs 352 }) 353 }, 354 hasTagsInput(element) { 355 let result = false 356 Object.values($(element)[0]).forEach((param) => { 357 if (Object.keys(param).includes('tagsinput')) { 358 result = true 359 } 360 }) 361 return result 362 }, 363 emptyByTags(element) { 364 $(element).find('input.yeswiki-input-entries').each(function() { 365 if (ConditionsChecking.hasTagsInput(this)) { 366 $(this).tagsinput('removeAll') 367 } 368 }) 369 }, 370 setDefaultByTags(element) { 371 $(element).find('input.yeswiki-input-entries').each(function() { 372 const propertyName = $(this).prop('name') 373 const val = $(this).val() 374 if (val.length == 0 && propertyName.length > 0 375 && typeof bazarlistTagsInputsData !== 'undefined' 376 && bazarlistTagsInputsData[propertyName] != undefined 377 && ConditionsChecking.hasTagsInput(this) 378 ) { 379 const selectedOptions = bazarlistTagsInputsData[propertyName].selectedOptions || [] 380 const existingTags = bazarlistTagsInputsData[propertyName].existingTags || [] 381 $(this).tagsinput('removeAll') 382 selectedOptions.forEach((tag) => { 383 if (existingTags[tag] != undefined) { 384 $(this).tagsinput('add', existingTags[tag]) 385 } 386 }) 387 } 388 }) 389 }, 390 emptyOthersInputs(element) { 391 $(element).find('input:not([type=checkbox]):not([type=radio]):not([type=hidden]):not(.yeswiki-input-entries)').each(function() { 392 $(this).val('') 393 $(this).trigger('change') 394 }) 395 }, 396 emptyChildren(element) { 397 this.emptyCheckbox(element) 398 this.emptySelect(element) 399 this.emptyTextarea(element) 400 this.emptyRadio(element) 401 this.emptyGeocode(element) 402 this.emptyByTags(element) 403 this.emptyOthersInputs(element) 404 // this.emptyImage(element); 405 // do not work for FileField also 406 }, 407 setDefaultChildren(element) { 408 this.setDefaultSelect(element) 409 this.setDefaultRadio(element) 410 this.setDefaultCheckbox(element) 411 this.setDefaultByTags(element) 412 }, 413 resolveCondition(id, cleanSubelements = true, elemsToClean = {}) { 414 if (typeof this.conditionsCache[id] !== 'undefined') { 415 const conditionData = this.conditionsCache[id] 416 const stack = [] 417 for (const key in conditionData.structuredConditions) { 418 stack.push(conditionData.structuredConditions[key]) 419 } 420 let stringToEval = '' 421 let errorFound = false 422 while (stack.length > 0 && !errorFound) { 423 const structuredCondition = stack[0] 424 stack.splice(0, 1) 425 switch (structuredCondition.operation) { 426 case '(': 427 case '!(': 428 if (this.renderBadFormatingError(structuredCondition, conditionData)) { 429 errorFound = true 430 } else { 431 stringToEval += structuredCondition.operation 432 } 433 break 434 case 'not(': 435 case 'not (': 436 if (this.renderBadFormatingError(structuredCondition, conditionData)) { 437 errorFound = true 438 } else { 439 stringToEval = `${stringToEval}!(` 440 } 441 break 442 case ')': 443 stringToEval = stringToEval + this.renderCondition(structuredCondition) + structuredCondition.operation 444 break 445 case 'and': 446 stringToEval = `${stringToEval + this.renderCondition(structuredCondition)}&&` 447 break 448 case 'or': 449 stringToEval = `${stringToEval + this.renderCondition(structuredCondition)}||` 450 break 451 default: 452 if (stack.length > 0) { 453 errorFound = true 454 console.warn(`Unknown operation '${structuredCondition.operation}' in '${conditionData.condition}'`) 455 } 456 stringToEval += this.renderCondition(structuredCondition) 457 break 458 } 459 } 460 let display = false 461 try { 462 display = errorFound ? false : eval(stringToEval) 463 } catch (error) { 464 console.warn(error) 465 display = false 466 } 467 // for debug console.log(stringToEval+" => "+display) 468 // extract no clean param 469 const clean = $(conditionData.node).data('noclean') != true 470 if (display) { 471 const previousStateVisible = ($(conditionData.node).filter(':visible').length > 0) 472 $(conditionData.node).show() 473 window.dispatchEvent(new Event('resize')) // needed to refresh map for geolocalization 474 if (clean && !previousStateVisible) { 475 if (cleanSubelements) { 476 this.setDefaultChildren(conditionData.node) 477 } else { 478 elemsToClean[id] = false 479 } 480 } 481 } else { 482 $(conditionData.node).hide() 483 if (clean) { 484 if (cleanSubelements) { 485 this.emptyChildren(conditionData.node) 486 } else { 487 elemsToClean[id] = true 488 } 489 } 490 } 491 } 492 }, 493 resolveTrigger(inputId) { 494 if (typeof this.triggersCache[inputId] !== 'undefined') { 495 const fieldsNames = this.triggersCache[inputId] 496 const conditionsIds = [] 497 for (let index = 0; index < fieldsNames.length; index++) { 498 const fieldName = fieldsNames[index] 499 if (typeof this.fieldNamesCache[fieldName] !== 'undefined') { 500 const fieldData = this.fieldNamesCache[fieldName] 501 for (let indexCondition = 0; indexCondition < fieldData.conditionIds.length; indexCondition++) { 502 const id = fieldData.conditionIds[indexCondition] 503 if (conditionsIds.indexOf(id) < 0) { 504 conditionsIds.push(id) 505 } 506 } 507 } 508 } 509 for (let index = 0; index < conditionsIds.length; index++) { 510 const id = conditionsIds[index] 511 this.resolveCondition(id) 512 } 513 } 514 }, 515 registerTrigger(input, fieldName) { 516 const inputId = $(input).attr('id') 517 if (typeof this.triggersCache[inputId] === 'undefined') { 518 this.triggersCache[inputId] = [fieldName] 519 $(input).on('change', () => { 520 ConditionsChecking.resolveTrigger(inputId) 521 }) 522 } else if (this.triggersCache[inputId].indexOf(fieldName) < 0) { 523 this.triggersCache[inputId].push(fieldName) 524 } 525 }, 526 findCheckbox(fieldName, result) { 527 if (result.type != '') { 528 return result 529 } 530 const node = $(`div[class*="group-checkbox-"][class*="${fieldName}"],ul[class*="group-checkbox-"][class*="${fieldName}"]`).filter( 531 function(index) { 532 const classes = $(this).attr('class').split(' ') 533 return classes.filter((className) => className.slice(-fieldName.length) == fieldName).length > 0 534 } 535 ) 536 if (node.length > 0) { 537 const inputs = $(node).find('input[type=checkbox]') 538 if (inputs.length > 0) { 539 result.type = 'checkbox' 540 result.node = node 541 // register triggers 542 $(inputs).each(function() { 543 ConditionsChecking.registerTrigger(this, fieldName) 544 }) 545 } 546 } 547 return result 548 }, 549 findCheckboxTag(fieldName, result) { 550 if (result.type != '') { 551 return result 552 } 553 const node = $(`input[class$=${fieldName}].yeswiki-input-entries`) 554 if (node.length > 0) { 555 result.type = 'checkboxtag' 556 result.node = node 557 // register triggers 558 ConditionsChecking.registerTrigger(node, fieldName) 559 } 560 return result 561 }, 562 findList(fieldName, result) { 563 if (result.type != '') { 564 return result 565 } 566 const node = $(`select[name$=${fieldName}]`) 567 if (node.length > 0) { 568 result.type = 'select' 569 result.node = node 570 // register triggers 571 ConditionsChecking.registerTrigger(node, fieldName) 572 } 573 return result 574 }, 575 findRadio(fieldName, result) { 576 if (result.type != '') { 577 return result 578 } 579 const inputs = $(`input[name$=${fieldName}][type=radio]`) 580 if (inputs.length > 0) { 581 result.type = 'radio' 582 result.node = inputs 583 // register triggers 584 $(inputs).each(function() { 585 ConditionsChecking.registerTrigger(this, fieldName) 586 }) 587 } 588 return result 589 }, 590 extractFieldNode(fieldName) { 591 let result = { 592 type: '', 593 node: {}, 594 conditionIds: [] 595 } 596 result = this.findCheckbox(fieldName, result) 597 result = this.findCheckboxTag(fieldName, result) 598 result = this.findRadio(fieldName, result) 599 result = this.findList(fieldName, result) 600 601 return result 602 }, 603 registerFieldName(fieldName, id) { 604 if (typeof this.fieldNamesCache[fieldName] === 'undefined') { 605 this.fieldNamesCache[fieldName] = this.extractFieldNode(fieldName) 606 this.fieldNamesCache[fieldName].conditionIds.push(id) 607 } else if (this.fieldNamesCache[fieldName].conditionIds.indexOf(id) < 0) { 608 this.fieldNamesCache[fieldName].conditionIds.push(id) 609 } 610 }, 611 parseCondition(element) { 612 const condition = $(element).data('conditionschecking') 613 // index = internal id 614 const id = this.conditionsCache.length 615 // save cache 616 this.conditionsCache.push({ 617 condition, 618 node: element, 619 structuredConditions: {} 620 }) 621 622 let parsingObject = { 623 restOfCondition: condition, 624 currentCondition: '', 625 operation: '' 626 } 627 while (parsingObject.restOfCondition.length > 0) { 628 parsingObject = this.getFirstOperation(parsingObject) 629 // check condition 630 const indexForStructuredCondition = Object.keys(this.conditionsCache[id].structuredConditions).length 631 // save in cache 632 this.conditionsCache[id].structuredConditions[indexForStructuredCondition] = { operation: parsingObject.operation } 633 let structuredCondition = this.conditionsCache[id].structuredConditions[indexForStructuredCondition] 634 if (parsingObject.currentCondition.length > 0) { 635 structuredCondition = this.addCondition( 636 parsingObject.currentCondition 637 ) 638 } else { 639 structuredCondition.leftPart = '' 640 structuredCondition.rightPart = '' 641 structuredCondition.typeOfCondition = '' 642 } 643 // activate trigger 644 if (typeof structuredCondition.leftPart !== 'undefined' && structuredCondition.leftPart.length > 0) { 645 const fieldName = structuredCondition.leftPart.trim() 646 this.registerFieldName(fieldName, id) 647 } 648 for (const key in structuredCondition) { 649 this.conditionsCache[id].structuredConditions[indexForStructuredCondition][key] = structuredCondition[key] 650 } 651 } 652 }, 653 init() { 654 const conditionschecking = this 655 $('div[data-conditionschecking]').each(function() { 656 const element = $(this) 657 conditionschecking.parseCondition(element) 658 }) 659 // init conditions 660 const elemsToClean = {} 661 for (let index = 0; index < this.conditionsCache.length; index++) { 662 this.resolveCondition(index, false, elemsToClean) 663 } 664 for (const id in elemsToClean) { 665 if (elemsToClean[id]) { 666 const conditionData = this.conditionsCache[id] 667 this.emptyChildren(conditionData.node) 668 } 669 } 670 } 671} 672
673ConditionsChecking.init()
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.