From bdfdb057c648a9a64634ad6bf3afa6648753cef3 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 09:56:20 +0200 Subject: [PATCH 01/51] Remove protected _id variable from Form classes The visibility of this variable is public in the parent class. The overwitten visibility caused the following errors: PHP Fatal error: Access level to CRM_Admin_Form_Resource:: must be public (as in class CRM_Admin_Form) --- CRM/Admin/Form/AdhocChargesItem.php | 1 - CRM/Admin/Form/Resource.php | 1 - CRM/Admin/Form/ResourceConfigOption.php | 1 - CRM/Admin/Form/ResourceConfigSet.php | 1 - CRM/Booking/Form/Booking/Base.php | 2 -- CRM/Booking/Form/SelectResource.php | 7 ------- 6 files changed, 13 deletions(-) diff --git a/CRM/Admin/Form/AdhocChargesItem.php b/CRM/Admin/Form/AdhocChargesItem.php index 6bf7122e..34dcfe3e 100644 --- a/CRM/Admin/Form/AdhocChargesItem.php +++ b/CRM/Admin/Form/AdhocChargesItem.php @@ -38,7 +38,6 @@ * */ class CRM_Admin_Form_AdhocChargesItem extends CRM_Admin_Form { - protected $_id = NULL; function preProcess() { parent::preProcess(); diff --git a/CRM/Admin/Form/Resource.php b/CRM/Admin/Form/Resource.php index 5e4d5277..1d558b2c 100755 --- a/CRM/Admin/Form/Resource.php +++ b/CRM/Admin/Form/Resource.php @@ -38,7 +38,6 @@ * */ class CRM_Admin_Form_Resource extends CRM_Admin_Form { - protected $_id = NULL; function preProcess() { parent::preProcess(); diff --git a/CRM/Admin/Form/ResourceConfigOption.php b/CRM/Admin/Form/ResourceConfigOption.php index 6c1ce72c..fa6e4f28 100644 --- a/CRM/Admin/Form/ResourceConfigOption.php +++ b/CRM/Admin/Form/ResourceConfigOption.php @@ -38,7 +38,6 @@ * */ class CRM_Admin_Form_ResourceConfigOption extends CRM_Admin_Form { - protected $_id = NULL; protected $_sid = NULL; function preProcess() { diff --git a/CRM/Admin/Form/ResourceConfigSet.php b/CRM/Admin/Form/ResourceConfigSet.php index faa6d5c0..311ca8d4 100644 --- a/CRM/Admin/Form/ResourceConfigSet.php +++ b/CRM/Admin/Form/ResourceConfigSet.php @@ -38,7 +38,6 @@ * */ class CRM_Admin_Form_ResourceConfigSet extends CRM_Admin_Form { - protected $_id = NULL; function preProcess() { parent::preProcess(); diff --git a/CRM/Booking/Form/Booking/Base.php b/CRM/Booking/Form/Booking/Base.php index 8b23a28e..5a8d2603 100644 --- a/CRM/Booking/Form/Booking/Base.php +++ b/CRM/Booking/Form/Booking/Base.php @@ -40,8 +40,6 @@ */ abstract class CRM_Booking_Form_Booking_Base extends CRM_Core_Form { - protected $_id; - protected $_cid; protected $_values; diff --git a/CRM/Booking/Form/SelectResource.php b/CRM/Booking/Form/SelectResource.php index dd177186..a0f670fb 100644 --- a/CRM/Booking/Form/SelectResource.php +++ b/CRM/Booking/Form/SelectResource.php @@ -9,13 +9,6 @@ * @see http://wiki.civicrm.org/confluence/display/CRMDOC43/QuickForm+Reference */ class CRM_Booking_Form_SelectResource extends CRM_Core_Form { - /** - * the booking ID of the booking if we are editing a booking - * - * @var integer - */ - //protected $_id; - private $configOptions; From 95d6ff73501a8bea8f790a23f6fd2593f7d305dd Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 10:00:10 +0200 Subject: [PATCH 02/51] Add fresh underscore-umd package to the codebase It bumps the underscore version from 1.5.0 to 1.13.1 --- packages/underscore-umd.js | 2042 ++++++++++++++++++++++++++++++++++++ 1 file changed, 2042 insertions(+) create mode 100644 packages/underscore-umd.js diff --git a/packages/underscore-umd.js b/packages/underscore-umd.js new file mode 100644 index 00000000..ffd77af9 --- /dev/null +++ b/packages/underscore-umd.js @@ -0,0 +1,2042 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define('underscore', factory) : + (global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () { + var current = global._; + var exports = global._ = factory(); + exports.noConflict = function () { global._ = current; return exports; }; + }())); +}(this, (function () { + // Underscore.js 1.13.1 + // https://underscorejs.org + // (c) 2009-2021 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors + // Underscore may be freely distributed under the MIT license. + + // Current version. + var VERSION = '1.13.1'; + + // Establish the root object, `window` (`self`) in the browser, `global` + // on the server, or `this` in some virtual machines. We use `self` + // instead of `window` for `WebWorker` support. + var root = typeof self == 'object' && self.self === self && self || + typeof global == 'object' && global.global === global && global || + Function('return this')() || + {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype; + var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // Modern feature detection. + var supportsArrayBuffer = typeof ArrayBuffer !== 'undefined', + supportsDataView = typeof DataView !== 'undefined'; + + // All **ECMAScript 5+** native function implementations that we hope to use + // are declared here. + var nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeCreate = Object.create, + nativeIsView = supportsArrayBuffer && ArrayBuffer.isView; + + // Create references to these builtin functions because we override them. + var _isNaN = isNaN, + _isFinite = isFinite; + + // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed. + var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString'); + var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString', + 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString']; + + // The largest integer that can be represented exactly. + var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; + + // Some functions take a variable number of arguments, or a few expected + // arguments at the beginning and then a variable number of values to operate + // on. This helper accumulates all remaining arguments past the function’s + // argument length (or an explicit `startIndex`), into an array that becomes + // the last argument. Similar to ES6’s "rest parameter". + function restArguments(func, startIndex) { + startIndex = startIndex == null ? func.length - 1 : +startIndex; + return function() { + var length = Math.max(arguments.length - startIndex, 0), + rest = Array(length), + index = 0; + for (; index < length; index++) { + rest[index] = arguments[index + startIndex]; + } + switch (startIndex) { + case 0: return func.call(this, rest); + case 1: return func.call(this, arguments[0], rest); + case 2: return func.call(this, arguments[0], arguments[1], rest); + } + var args = Array(startIndex + 1); + for (index = 0; index < startIndex; index++) { + args[index] = arguments[index]; + } + args[startIndex] = rest; + return func.apply(this, args); + }; + } + + // Is a given variable an object? + function isObject(obj) { + var type = typeof obj; + return type === 'function' || type === 'object' && !!obj; + } + + // Is a given value equal to null? + function isNull(obj) { + return obj === null; + } + + // Is a given variable undefined? + function isUndefined(obj) { + return obj === void 0; + } + + // Is a given value a boolean? + function isBoolean(obj) { + return obj === true || obj === false || toString.call(obj) === '[object Boolean]'; + } + + // Is a given value a DOM element? + function isElement(obj) { + return !!(obj && obj.nodeType === 1); + } + + // Internal function for creating a `toString`-based type tester. + function tagTester(name) { + var tag = '[object ' + name + ']'; + return function(obj) { + return toString.call(obj) === tag; + }; + } + + var isString = tagTester('String'); + + var isNumber = tagTester('Number'); + + var isDate = tagTester('Date'); + + var isRegExp = tagTester('RegExp'); + + var isError = tagTester('Error'); + + var isSymbol = tagTester('Symbol'); + + var isArrayBuffer = tagTester('ArrayBuffer'); + + var isFunction = tagTester('Function'); + + // Optimize `isFunction` if appropriate. Work around some `typeof` bugs in old + // v8, IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236). + var nodelist = root.document && root.document.childNodes; + if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') { + isFunction = function(obj) { + return typeof obj == 'function' || false; + }; + } + + var isFunction$1 = isFunction; + + var hasObjectTag = tagTester('Object'); + + // In IE 10 - Edge 13, `DataView` has string tag `'[object Object]'`. + // In IE 11, the most common among them, this problem also applies to + // `Map`, `WeakMap` and `Set`. + var hasStringTagBug = ( + supportsDataView && hasObjectTag(new DataView(new ArrayBuffer(8))) + ), + isIE11 = (typeof Map !== 'undefined' && hasObjectTag(new Map)); + + var isDataView = tagTester('DataView'); + + // In IE 10 - Edge 13, we need a different heuristic + // to determine whether an object is a `DataView`. + function ie10IsDataView(obj) { + return obj != null && isFunction$1(obj.getInt8) && isArrayBuffer(obj.buffer); + } + + var isDataView$1 = (hasStringTagBug ? ie10IsDataView : isDataView); + + // Is a given value an array? + // Delegates to ECMA5's native `Array.isArray`. + var isArray = nativeIsArray || tagTester('Array'); + + // Internal function to check whether `key` is an own property name of `obj`. + function has$1(obj, key) { + return obj != null && hasOwnProperty.call(obj, key); + } + + var isArguments = tagTester('Arguments'); + + // Define a fallback version of the method in browsers (ahem, IE < 9), where + // there isn't any inspectable "Arguments" type. + (function() { + if (!isArguments(arguments)) { + isArguments = function(obj) { + return has$1(obj, 'callee'); + }; + } + }()); + + var isArguments$1 = isArguments; + + // Is a given object a finite number? + function isFinite$1(obj) { + return !isSymbol(obj) && _isFinite(obj) && !isNaN(parseFloat(obj)); + } + + // Is the given value `NaN`? + function isNaN$1(obj) { + return isNumber(obj) && _isNaN(obj); + } + + // Predicate-generating function. Often useful outside of Underscore. + function constant(value) { + return function() { + return value; + }; + } + + // Common internal logic for `isArrayLike` and `isBufferLike`. + function createSizePropertyCheck(getSizeProperty) { + return function(collection) { + var sizeProperty = getSizeProperty(collection); + return typeof sizeProperty == 'number' && sizeProperty >= 0 && sizeProperty <= MAX_ARRAY_INDEX; + } + } + + // Internal helper to generate a function to obtain property `key` from `obj`. + function shallowProperty(key) { + return function(obj) { + return obj == null ? void 0 : obj[key]; + }; + } + + // Internal helper to obtain the `byteLength` property of an object. + var getByteLength = shallowProperty('byteLength'); + + // Internal helper to determine whether we should spend extensive checks against + // `ArrayBuffer` et al. + var isBufferLike = createSizePropertyCheck(getByteLength); + + // Is a given value a typed array? + var typedArrayPattern = /\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/; + function isTypedArray(obj) { + // `ArrayBuffer.isView` is the most future-proof, so use it when available. + // Otherwise, fall back on the above regular expression. + return nativeIsView ? (nativeIsView(obj) && !isDataView$1(obj)) : + isBufferLike(obj) && typedArrayPattern.test(toString.call(obj)); + } + + var isTypedArray$1 = supportsArrayBuffer ? isTypedArray : constant(false); + + // Internal helper to obtain the `length` property of an object. + var getLength = shallowProperty('length'); + + // Internal helper to create a simple lookup structure. + // `collectNonEnumProps` used to depend on `_.contains`, but this led to + // circular imports. `emulatedSet` is a one-off solution that only works for + // arrays of strings. + function emulatedSet(keys) { + var hash = {}; + for (var l = keys.length, i = 0; i < l; ++i) hash[keys[i]] = true; + return { + contains: function(key) { return hash[key]; }, + push: function(key) { + hash[key] = true; + return keys.push(key); + } + }; + } + + // Internal helper. Checks `keys` for the presence of keys in IE < 9 that won't + // be iterated by `for key in ...` and thus missed. Extends `keys` in place if + // needed. + function collectNonEnumProps(obj, keys) { + keys = emulatedSet(keys); + var nonEnumIdx = nonEnumerableProps.length; + var constructor = obj.constructor; + var proto = isFunction$1(constructor) && constructor.prototype || ObjProto; + + // Constructor is a special case. + var prop = 'constructor'; + if (has$1(obj, prop) && !keys.contains(prop)) keys.push(prop); + + while (nonEnumIdx--) { + prop = nonEnumerableProps[nonEnumIdx]; + if (prop in obj && obj[prop] !== proto[prop] && !keys.contains(prop)) { + keys.push(prop); + } + } + } + + // Retrieve the names of an object's own properties. + // Delegates to **ECMAScript 5**'s native `Object.keys`. + function keys(obj) { + if (!isObject(obj)) return []; + if (nativeKeys) return nativeKeys(obj); + var keys = []; + for (var key in obj) if (has$1(obj, key)) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + } + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + function isEmpty(obj) { + if (obj == null) return true; + // Skip the more expensive `toString`-based type checks if `obj` has no + // `.length`. + var length = getLength(obj); + if (typeof length == 'number' && ( + isArray(obj) || isString(obj) || isArguments$1(obj) + )) return length === 0; + return getLength(keys(obj)) === 0; + } + + // Returns whether an object has a given set of `key:value` pairs. + function isMatch(object, attrs) { + var _keys = keys(attrs), length = _keys.length; + if (object == null) return !length; + var obj = Object(object); + for (var i = 0; i < length; i++) { + var key = _keys[i]; + if (attrs[key] !== obj[key] || !(key in obj)) return false; + } + return true; + } + + // If Underscore is called as a function, it returns a wrapped object that can + // be used OO-style. This wrapper holds altered versions of all functions added + // through `_.mixin`. Wrapped objects may be chained. + function _$1(obj) { + if (obj instanceof _$1) return obj; + if (!(this instanceof _$1)) return new _$1(obj); + this._wrapped = obj; + } + + _$1.VERSION = VERSION; + + // Extracts the result from a wrapped and chained object. + _$1.prototype.value = function() { + return this._wrapped; + }; + + // Provide unwrapping proxies for some methods used in engine operations + // such as arithmetic and JSON stringification. + _$1.prototype.valueOf = _$1.prototype.toJSON = _$1.prototype.value; + + _$1.prototype.toString = function() { + return String(this._wrapped); + }; + + // Internal function to wrap or shallow-copy an ArrayBuffer, + // typed array or DataView to a new view, reusing the buffer. + function toBufferView(bufferSource) { + return new Uint8Array( + bufferSource.buffer || bufferSource, + bufferSource.byteOffset || 0, + getByteLength(bufferSource) + ); + } + + // We use this string twice, so give it a name for minification. + var tagDataView = '[object DataView]'; + + // Internal recursive comparison function for `_.isEqual`. + function eq(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the [Harmony `egal` proposal](https://wiki.ecmascript.org/doku.php?id=harmony:egal). + if (a === b) return a !== 0 || 1 / a === 1 / b; + // `null` or `undefined` only equal to itself (strict comparison). + if (a == null || b == null) return false; + // `NaN`s are equivalent, but non-reflexive. + if (a !== a) return b !== b; + // Exhaust primitive checks + var type = typeof a; + if (type !== 'function' && type !== 'object' && typeof b != 'object') return false; + return deepEq(a, b, aStack, bStack); + } + + // Internal recursive comparison function for `_.isEqual`. + function deepEq(a, b, aStack, bStack) { + // Unwrap any wrapped objects. + if (a instanceof _$1) a = a._wrapped; + if (b instanceof _$1) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className !== toString.call(b)) return false; + // Work around a bug in IE 10 - Edge 13. + if (hasStringTagBug && className == '[object Object]' && isDataView$1(a)) { + if (!isDataView$1(b)) return false; + className = tagDataView; + } + switch (className) { + // These types are compared by value. + case '[object RegExp]': + // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i') + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return '' + a === '' + b; + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. + // Object(NaN) is equivalent to NaN. + if (+a !== +a) return +b !== +b; + // An `egal` comparison is performed for other numeric values. + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a === +b; + case '[object Symbol]': + return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b); + case '[object ArrayBuffer]': + case tagDataView: + // Coerce to typed array so we can fall through. + return deepEq(toBufferView(a), toBufferView(b), aStack, bStack); + } + + var areArrays = className === '[object Array]'; + if (!areArrays && isTypedArray$1(a)) { + var byteLength = getByteLength(a); + if (byteLength !== getByteLength(b)) return false; + if (a.buffer === b.buffer && a.byteOffset === b.byteOffset) return true; + areArrays = true; + } + if (!areArrays) { + if (typeof a != 'object' || typeof b != 'object') return false; + + // Objects with different constructors are not equivalent, but `Object`s or `Array`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(isFunction$1(aCtor) && aCtor instanceof aCtor && + isFunction$1(bCtor) && bCtor instanceof bCtor) + && ('constructor' in a && 'constructor' in b)) { + return false; + } + } + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + + // Initializing stack of traversed objects. + // It's done here since we only need them for objects and arrays comparison. + aStack = aStack || []; + bStack = bStack || []; + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] === a) return bStack[length] === b; + } + + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + + // Recursively compare objects and arrays. + if (areArrays) { + // Compare array lengths to determine if a deep comparison is necessary. + length = a.length; + if (length !== b.length) return false; + // Deep compare the contents, ignoring non-numeric properties. + while (length--) { + if (!eq(a[length], b[length], aStack, bStack)) return false; + } + } else { + // Deep compare objects. + var _keys = keys(a), key; + length = _keys.length; + // Ensure that both objects contain the same number of properties before comparing deep equality. + if (keys(b).length !== length) return false; + while (length--) { + // Deep compare each member + key = _keys[length]; + if (!(has$1(b, key) && eq(a[key], b[key], aStack, bStack))) return false; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return true; + } + + // Perform a deep comparison to check if two objects are equal. + function isEqual(a, b) { + return eq(a, b); + } + + // Retrieve all the enumerable property names of an object. + function allKeys(obj) { + if (!isObject(obj)) return []; + var keys = []; + for (var key in obj) keys.push(key); + // Ahem, IE < 9. + if (hasEnumBug) collectNonEnumProps(obj, keys); + return keys; + } + + // Since the regular `Object.prototype.toString` type tests don't work for + // some types in IE 11, we use a fingerprinting heuristic instead, based + // on the methods. It's not great, but it's the best we got. + // The fingerprint method lists are defined below. + function ie11fingerprint(methods) { + var length = getLength(methods); + return function(obj) { + if (obj == null) return false; + // `Map`, `WeakMap` and `Set` have no enumerable keys. + var keys = allKeys(obj); + if (getLength(keys)) return false; + for (var i = 0; i < length; i++) { + if (!isFunction$1(obj[methods[i]])) return false; + } + // If we are testing against `WeakMap`, we need to ensure that + // `obj` doesn't have a `forEach` method in order to distinguish + // it from a regular `Map`. + return methods !== weakMapMethods || !isFunction$1(obj[forEachName]); + }; + } + + // In the interest of compact minification, we write + // each string in the fingerprints only once. + var forEachName = 'forEach', + hasName = 'has', + commonInit = ['clear', 'delete'], + mapTail = ['get', hasName, 'set']; + + // `Map`, `WeakMap` and `Set` each have slightly different + // combinations of the above sublists. + var mapMethods = commonInit.concat(forEachName, mapTail), + weakMapMethods = commonInit.concat(mapTail), + setMethods = ['add'].concat(commonInit, forEachName, hasName); + + var isMap = isIE11 ? ie11fingerprint(mapMethods) : tagTester('Map'); + + var isWeakMap = isIE11 ? ie11fingerprint(weakMapMethods) : tagTester('WeakMap'); + + var isSet = isIE11 ? ie11fingerprint(setMethods) : tagTester('Set'); + + var isWeakSet = tagTester('WeakSet'); + + // Retrieve the values of an object's properties. + function values(obj) { + var _keys = keys(obj); + var length = _keys.length; + var values = Array(length); + for (var i = 0; i < length; i++) { + values[i] = obj[_keys[i]]; + } + return values; + } + + // Convert an object into a list of `[key, value]` pairs. + // The opposite of `_.object` with one argument. + function pairs(obj) { + var _keys = keys(obj); + var length = _keys.length; + var pairs = Array(length); + for (var i = 0; i < length; i++) { + pairs[i] = [_keys[i], obj[_keys[i]]]; + } + return pairs; + } + + // Invert the keys and values of an object. The values must be serializable. + function invert(obj) { + var result = {}; + var _keys = keys(obj); + for (var i = 0, length = _keys.length; i < length; i++) { + result[obj[_keys[i]]] = _keys[i]; + } + return result; + } + + // Return a sorted list of the function names available on the object. + function functions(obj) { + var names = []; + for (var key in obj) { + if (isFunction$1(obj[key])) names.push(key); + } + return names.sort(); + } + + // An internal function for creating assigner functions. + function createAssigner(keysFunc, defaults) { + return function(obj) { + var length = arguments.length; + if (defaults) obj = Object(obj); + if (length < 2 || obj == null) return obj; + for (var index = 1; index < length; index++) { + var source = arguments[index], + keys = keysFunc(source), + l = keys.length; + for (var i = 0; i < l; i++) { + var key = keys[i]; + if (!defaults || obj[key] === void 0) obj[key] = source[key]; + } + } + return obj; + }; + } + + // Extend a given object with all the properties in passed-in object(s). + var extend = createAssigner(allKeys); + + // Assigns a given object with all the own properties in the passed-in + // object(s). + // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) + var extendOwn = createAssigner(keys); + + // Fill in a given object with default properties. + var defaults = createAssigner(allKeys, true); + + // Create a naked function reference for surrogate-prototype-swapping. + function ctor() { + return function(){}; + } + + // An internal function for creating a new object that inherits from another. + function baseCreate(prototype) { + if (!isObject(prototype)) return {}; + if (nativeCreate) return nativeCreate(prototype); + var Ctor = ctor(); + Ctor.prototype = prototype; + var result = new Ctor; + Ctor.prototype = null; + return result; + } + + // Creates an object that inherits from the given prototype object. + // If additional properties are provided then they will be added to the + // created object. + function create(prototype, props) { + var result = baseCreate(prototype); + if (props) extendOwn(result, props); + return result; + } + + // Create a (shallow-cloned) duplicate of an object. + function clone(obj) { + if (!isObject(obj)) return obj; + return isArray(obj) ? obj.slice() : extend({}, obj); + } + + // Invokes `interceptor` with the `obj` and then returns `obj`. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + function tap(obj, interceptor) { + interceptor(obj); + return obj; + } + + // Normalize a (deep) property `path` to array. + // Like `_.iteratee`, this function can be customized. + function toPath$1(path) { + return isArray(path) ? path : [path]; + } + _$1.toPath = toPath$1; + + // Internal wrapper for `_.toPath` to enable minification. + // Similar to `cb` for `_.iteratee`. + function toPath(path) { + return _$1.toPath(path); + } + + // Internal function to obtain a nested property in `obj` along `path`. + function deepGet(obj, path) { + var length = path.length; + for (var i = 0; i < length; i++) { + if (obj == null) return void 0; + obj = obj[path[i]]; + } + return length ? obj : void 0; + } + + // Get the value of the (deep) property on `path` from `object`. + // If any property in `path` does not exist or if the value is + // `undefined`, return `defaultValue` instead. + // The `path` is normalized through `_.toPath`. + function get(object, path, defaultValue) { + var value = deepGet(object, toPath(path)); + return isUndefined(value) ? defaultValue : value; + } + + // Shortcut function for checking if an object has a given property directly on + // itself (in other words, not on a prototype). Unlike the internal `has` + // function, this public version can also traverse nested properties. + function has(obj, path) { + path = toPath(path); + var length = path.length; + for (var i = 0; i < length; i++) { + var key = path[i]; + if (!has$1(obj, key)) return false; + obj = obj[key]; + } + return !!length; + } + + // Keep the identity function around for default iteratees. + function identity(value) { + return value; + } + + // Returns a predicate for checking whether an object has a given set of + // `key:value` pairs. + function matcher(attrs) { + attrs = extendOwn({}, attrs); + return function(obj) { + return isMatch(obj, attrs); + }; + } + + // Creates a function that, when passed an object, will traverse that object’s + // properties down the given `path`, specified as an array of keys or indices. + function property(path) { + path = toPath(path); + return function(obj) { + return deepGet(obj, path); + }; + } + + // Internal function that returns an efficient (for current engines) version + // of the passed-in callback, to be repeatedly applied in other Underscore + // functions. + function optimizeCb(func, context, argCount) { + if (context === void 0) return func; + switch (argCount == null ? 3 : argCount) { + case 1: return function(value) { + return func.call(context, value); + }; + // The 2-argument case is omitted because we’re not using it. + case 3: return function(value, index, collection) { + return func.call(context, value, index, collection); + }; + case 4: return function(accumulator, value, index, collection) { + return func.call(context, accumulator, value, index, collection); + }; + } + return function() { + return func.apply(context, arguments); + }; + } + + // An internal function to generate callbacks that can be applied to each + // element in a collection, returning the desired result — either `_.identity`, + // an arbitrary callback, a property matcher, or a property accessor. + function baseIteratee(value, context, argCount) { + if (value == null) return identity; + if (isFunction$1(value)) return optimizeCb(value, context, argCount); + if (isObject(value) && !isArray(value)) return matcher(value); + return property(value); + } + + // External wrapper for our callback generator. Users may customize + // `_.iteratee` if they want additional predicate/iteratee shorthand styles. + // This abstraction hides the internal-only `argCount` argument. + function iteratee(value, context) { + return baseIteratee(value, context, Infinity); + } + _$1.iteratee = iteratee; + + // The function we call internally to generate a callback. It invokes + // `_.iteratee` if overridden, otherwise `baseIteratee`. + function cb(value, context, argCount) { + if (_$1.iteratee !== iteratee) return _$1.iteratee(value, context); + return baseIteratee(value, context, argCount); + } + + // Returns the results of applying the `iteratee` to each element of `obj`. + // In contrast to `_.map` it returns an object. + function mapObject(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var _keys = keys(obj), + length = _keys.length, + results = {}; + for (var index = 0; index < length; index++) { + var currentKey = _keys[index]; + results[currentKey] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + } + + // Predicate-generating function. Often useful outside of Underscore. + function noop(){} + + // Generates a function for a given object that returns a given property. + function propertyOf(obj) { + if (obj == null) return noop; + return function(path) { + return get(obj, path); + }; + } + + // Run a function **n** times. + function times(n, iteratee, context) { + var accum = Array(Math.max(0, n)); + iteratee = optimizeCb(iteratee, context, 1); + for (var i = 0; i < n; i++) accum[i] = iteratee(i); + return accum; + } + + // Return a random integer between `min` and `max` (inclusive). + function random(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + } + + // A (possibly faster) way to get the current timestamp as an integer. + var now = Date.now || function() { + return new Date().getTime(); + }; + + // Internal helper to generate functions for escaping and unescaping strings + // to/from HTML interpolation. + function createEscaper(map) { + var escaper = function(match) { + return map[match]; + }; + // Regexes for identifying a key that needs to be escaped. + var source = '(?:' + keys(map).join('|') + ')'; + var testRegexp = RegExp(source); + var replaceRegexp = RegExp(source, 'g'); + return function(string) { + string = string == null ? '' : '' + string; + return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string; + }; + } + + // Internal list of HTML entities for escaping. + var escapeMap = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '`': '`' + }; + + // Function for escaping strings to HTML interpolation. + var _escape = createEscaper(escapeMap); + + // Internal list of HTML entities for unescaping. + var unescapeMap = invert(escapeMap); + + // Function for unescaping strings from HTML interpolation. + var _unescape = createEscaper(unescapeMap); + + // By default, Underscore uses ERB-style template delimiters. Change the + // following template settings to use alternative delimiters. + var templateSettings = _$1.templateSettings = { + evaluate: /<%([\s\S]+?)%>/g, + interpolate: /<%=([\s\S]+?)%>/g, + escape: /<%-([\s\S]+?)%>/g + }; + + // When customizing `_.templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g; + + function escapeChar(match) { + return '\\' + escapes[match]; + } + + // In order to prevent third-party code injection through + // `_.templateSettings.variable`, we test it against the following regular + // expression. It is intentionally a bit more liberal than just matching valid + // identifiers, but still prevents possible loopholes through defaults or + // destructuring assignment. + var bareIdentifier = /^\s*(\w|\$)+\s*$/; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + // NB: `oldSettings` only exists for backwards compatibility. + function template(text, settings, oldSettings) { + if (!settings && oldSettings) settings = oldSettings; + settings = defaults({}, settings, _$1.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset).replace(escapeRegExp, escapeChar); + index = offset + match.length; + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } else if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } else if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + + // Adobe VMs need the match returned to produce the correct offset. + return match; + }); + source += "';\n"; + + var argument = settings.variable; + if (argument) { + // Insure against third-party code injection. (CVE-2021-23358) + if (!bareIdentifier.test(argument)) throw new Error( + 'variable is not a bare identifier: ' + argument + ); + } else { + // If a variable is not specified, place data values in local scope. + source = 'with(obj||{}){\n' + source + '}\n'; + argument = 'obj'; + } + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + 'return __p;\n'; + + var render; + try { + render = new Function(argument, '_', source); + } catch (e) { + e.source = source; + throw e; + } + + var template = function(data) { + return render.call(this, data, _$1); + }; + + // Provide the compiled source as a convenience for precompilation. + template.source = 'function(' + argument + '){\n' + source + '}'; + + return template; + } + + // Traverses the children of `obj` along `path`. If a child is a function, it + // is invoked with its parent as context. Returns the value of the final + // child, or `fallback` if any child is undefined. + function result(obj, path, fallback) { + path = toPath(path); + var length = path.length; + if (!length) { + return isFunction$1(fallback) ? fallback.call(obj) : fallback; + } + for (var i = 0; i < length; i++) { + var prop = obj == null ? void 0 : obj[path[i]]; + if (prop === void 0) { + prop = fallback; + i = length; // Ensure we don't continue iterating. + } + obj = isFunction$1(prop) ? prop.call(obj) : prop; + } + return obj; + } + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + function uniqueId(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + } + + // Start chaining a wrapped Underscore object. + function chain(obj) { + var instance = _$1(obj); + instance._chain = true; + return instance; + } + + // Internal function to execute `sourceFunc` bound to `context` with optional + // `args`. Determines whether to execute a function as a constructor or as a + // normal function. + function executeBound(sourceFunc, boundFunc, context, callingContext, args) { + if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args); + var self = baseCreate(sourceFunc.prototype); + var result = sourceFunc.apply(self, args); + if (isObject(result)) return result; + return self; + } + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. `_` acts + // as a placeholder by default, allowing any combination of arguments to be + // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument. + var partial = restArguments(function(func, boundArgs) { + var placeholder = partial.placeholder; + var bound = function() { + var position = 0, length = boundArgs.length; + var args = Array(length); + for (var i = 0; i < length; i++) { + args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i]; + } + while (position < arguments.length) args.push(arguments[position++]); + return executeBound(func, bound, this, this, args); + }; + return bound; + }); + + partial.placeholder = _$1; + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). + var bind = restArguments(function(func, context, args) { + if (!isFunction$1(func)) throw new TypeError('Bind must be called on a function'); + var bound = restArguments(function(callArgs) { + return executeBound(func, bound, context, this, args.concat(callArgs)); + }); + return bound; + }); + + // Internal helper for collection methods to determine whether a collection + // should be iterated as an array or as an object. + // Related: https://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength + // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094 + var isArrayLike = createSizePropertyCheck(getLength); + + // Internal implementation of a recursive `flatten` function. + function flatten$1(input, depth, strict, output) { + output = output || []; + if (!depth && depth !== 0) { + depth = Infinity; + } else if (depth <= 0) { + return output.concat(input); + } + var idx = output.length; + for (var i = 0, length = getLength(input); i < length; i++) { + var value = input[i]; + if (isArrayLike(value) && (isArray(value) || isArguments$1(value))) { + // Flatten current level of array or arguments object. + if (depth > 1) { + flatten$1(value, depth - 1, strict, output); + idx = output.length; + } else { + var j = 0, len = value.length; + while (j < len) output[idx++] = value[j++]; + } + } else if (!strict) { + output[idx++] = value; + } + } + return output; + } + + // Bind a number of an object's methods to that object. Remaining arguments + // are the method names to be bound. Useful for ensuring that all callbacks + // defined on an object belong to it. + var bindAll = restArguments(function(obj, keys) { + keys = flatten$1(keys, false, false); + var index = keys.length; + if (index < 1) throw new Error('bindAll must be passed function names'); + while (index--) { + var key = keys[index]; + obj[key] = bind(obj[key], obj); + } + return obj; + }); + + // Memoize an expensive function by storing its results. + function memoize(func, hasher) { + var memoize = function(key) { + var cache = memoize.cache; + var address = '' + (hasher ? hasher.apply(this, arguments) : key); + if (!has$1(cache, address)) cache[address] = func.apply(this, arguments); + return cache[address]; + }; + memoize.cache = {}; + return memoize; + } + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + var delay = restArguments(function(func, wait, args) { + return setTimeout(function() { + return func.apply(null, args); + }, wait); + }); + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + var defer = partial(delay, _$1, 1); + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. Normally, the throttled function will run + // as much as it can, without ever going more than once per `wait` duration; + // but if you'd like to disable the execution on the leading edge, pass + // `{leading: false}`. To disable execution on the trailing edge, ditto. + function throttle(func, wait, options) { + var timeout, context, args, result; + var previous = 0; + if (!options) options = {}; + + var later = function() { + previous = options.leading === false ? 0 : now(); + timeout = null; + result = func.apply(context, args); + if (!timeout) context = args = null; + }; + + var throttled = function() { + var _now = now(); + if (!previous && options.leading === false) previous = _now; + var remaining = wait - (_now - previous); + context = this; + args = arguments; + if (remaining <= 0 || remaining > wait) { + if (timeout) { + clearTimeout(timeout); + timeout = null; + } + previous = _now; + result = func.apply(context, args); + if (!timeout) context = args = null; + } else if (!timeout && options.trailing !== false) { + timeout = setTimeout(later, remaining); + } + return result; + }; + + throttled.cancel = function() { + clearTimeout(timeout); + previous = 0; + timeout = context = args = null; + }; + + return throttled; + } + + // When a sequence of calls of the returned function ends, the argument + // function is triggered. The end of a sequence is defined by the `wait` + // parameter. If `immediate` is passed, the argument function will be + // triggered at the beginning of the sequence instead of at the end. + function debounce(func, wait, immediate) { + var timeout, previous, args, result, context; + + var later = function() { + var passed = now() - previous; + if (wait > passed) { + timeout = setTimeout(later, wait - passed); + } else { + timeout = null; + if (!immediate) result = func.apply(context, args); + // This check is needed because `func` can recursively invoke `debounced`. + if (!timeout) args = context = null; + } + }; + + var debounced = restArguments(function(_args) { + context = this; + args = _args; + previous = now(); + if (!timeout) { + timeout = setTimeout(later, wait); + if (immediate) result = func.apply(context, args); + } + return result; + }); + + debounced.cancel = function() { + clearTimeout(timeout); + timeout = args = context = null; + }; + + return debounced; + } + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + function wrap(func, wrapper) { + return partial(wrapper, func); + } + + // Returns a negated version of the passed-in predicate. + function negate(predicate) { + return function() { + return !predicate.apply(this, arguments); + }; + } + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + function compose() { + var args = arguments; + var start = args.length - 1; + return function() { + var i = start; + var result = args[start].apply(this, arguments); + while (i--) result = args[i].call(this, result); + return result; + }; + } + + // Returns a function that will only be executed on and after the Nth call. + function after(times, func) { + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + } + + // Returns a function that will only be executed up to (but not including) the + // Nth call. + function before(times, func) { + var memo; + return function() { + if (--times > 0) { + memo = func.apply(this, arguments); + } + if (times <= 1) func = null; + return memo; + }; + } + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + var once = partial(before, 2); + + // Returns the first key on an object that passes a truth test. + function findKey(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = keys(obj), key; + for (var i = 0, length = _keys.length; i < length; i++) { + key = _keys[i]; + if (predicate(obj[key], key, obj)) return key; + } + } + + // Internal function to generate `_.findIndex` and `_.findLastIndex`. + function createPredicateIndexFinder(dir) { + return function(array, predicate, context) { + predicate = cb(predicate, context); + var length = getLength(array); + var index = dir > 0 ? 0 : length - 1; + for (; index >= 0 && index < length; index += dir) { + if (predicate(array[index], index, array)) return index; + } + return -1; + }; + } + + // Returns the first index on an array-like that passes a truth test. + var findIndex = createPredicateIndexFinder(1); + + // Returns the last index on an array-like that passes a truth test. + var findLastIndex = createPredicateIndexFinder(-1); + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + function sortedIndex(array, obj, iteratee, context) { + iteratee = cb(iteratee, context, 1); + var value = iteratee(obj); + var low = 0, high = getLength(array); + while (low < high) { + var mid = Math.floor((low + high) / 2); + if (iteratee(array[mid]) < value) low = mid + 1; else high = mid; + } + return low; + } + + // Internal function to generate the `_.indexOf` and `_.lastIndexOf` functions. + function createIndexFinder(dir, predicateFind, sortedIndex) { + return function(array, item, idx) { + var i = 0, length = getLength(array); + if (typeof idx == 'number') { + if (dir > 0) { + i = idx >= 0 ? idx : Math.max(idx + length, i); + } else { + length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1; + } + } else if (sortedIndex && idx && length) { + idx = sortedIndex(array, item); + return array[idx] === item ? idx : -1; + } + if (item !== item) { + idx = predicateFind(slice.call(array, i, length), isNaN$1); + return idx >= 0 ? idx + i : -1; + } + for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) { + if (array[idx] === item) return idx; + } + return -1; + }; + } + + // Return the position of the first occurrence of an item in an array, + // or -1 if the item is not included in the array. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + var indexOf = createIndexFinder(1, findIndex, sortedIndex); + + // Return the position of the last occurrence of an item in an array, + // or -1 if the item is not included in the array. + var lastIndexOf = createIndexFinder(-1, findLastIndex); + + // Return the first value which passes a truth test. + function find(obj, predicate, context) { + var keyFinder = isArrayLike(obj) ? findIndex : findKey; + var key = keyFinder(obj, predicate, context); + if (key !== void 0 && key !== -1) return obj[key]; + } + + // Convenience version of a common use case of `_.find`: getting the first + // object containing specific `key:value` pairs. + function findWhere(obj, attrs) { + return find(obj, matcher(attrs)); + } + + // The cornerstone for collection functions, an `each` + // implementation, aka `forEach`. + // Handles raw objects in addition to array-likes. Treats all + // sparse array-likes as if they were dense. + function each(obj, iteratee, context) { + iteratee = optimizeCb(iteratee, context); + var i, length; + if (isArrayLike(obj)) { + for (i = 0, length = obj.length; i < length; i++) { + iteratee(obj[i], i, obj); + } + } else { + var _keys = keys(obj); + for (i = 0, length = _keys.length; i < length; i++) { + iteratee(obj[_keys[i]], _keys[i], obj); + } + } + return obj; + } + + // Return the results of applying the iteratee to each element. + function map(obj, iteratee, context) { + iteratee = cb(iteratee, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length, + results = Array(length); + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + results[index] = iteratee(obj[currentKey], currentKey, obj); + } + return results; + } + + // Internal helper to create a reducing function, iterating left or right. + function createReduce(dir) { + // Wrap code that reassigns argument variables in a separate function than + // the one that accesses `arguments.length` to avoid a perf hit. (#1991) + var reducer = function(obj, iteratee, memo, initial) { + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length, + index = dir > 0 ? 0 : length - 1; + if (!initial) { + memo = obj[_keys ? _keys[index] : index]; + index += dir; + } + for (; index >= 0 && index < length; index += dir) { + var currentKey = _keys ? _keys[index] : index; + memo = iteratee(memo, obj[currentKey], currentKey, obj); + } + return memo; + }; + + return function(obj, iteratee, memo, context) { + var initial = arguments.length >= 3; + return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial); + }; + } + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. + var reduce = createReduce(1); + + // The right-associative version of reduce, also known as `foldr`. + var reduceRight = createReduce(-1); + + // Return all the elements that pass a truth test. + function filter(obj, predicate, context) { + var results = []; + predicate = cb(predicate, context); + each(obj, function(value, index, list) { + if (predicate(value, index, list)) results.push(value); + }); + return results; + } + + // Return all the elements for which a truth test fails. + function reject(obj, predicate, context) { + return filter(obj, negate(cb(predicate)), context); + } + + // Determine whether all of the elements pass a truth test. + function every(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + if (!predicate(obj[currentKey], currentKey, obj)) return false; + } + return true; + } + + // Determine if at least one element in the object passes a truth test. + function some(obj, predicate, context) { + predicate = cb(predicate, context); + var _keys = !isArrayLike(obj) && keys(obj), + length = (_keys || obj).length; + for (var index = 0; index < length; index++) { + var currentKey = _keys ? _keys[index] : index; + if (predicate(obj[currentKey], currentKey, obj)) return true; + } + return false; + } + + // Determine if the array or object contains a given item (using `===`). + function contains(obj, item, fromIndex, guard) { + if (!isArrayLike(obj)) obj = values(obj); + if (typeof fromIndex != 'number' || guard) fromIndex = 0; + return indexOf(obj, item, fromIndex) >= 0; + } + + // Invoke a method (with arguments) on every item in a collection. + var invoke = restArguments(function(obj, path, args) { + var contextPath, func; + if (isFunction$1(path)) { + func = path; + } else { + path = toPath(path); + contextPath = path.slice(0, -1); + path = path[path.length - 1]; + } + return map(obj, function(context) { + var method = func; + if (!method) { + if (contextPath && contextPath.length) { + context = deepGet(context, contextPath); + } + if (context == null) return void 0; + method = context[path]; + } + return method == null ? method : method.apply(context, args); + }); + }); + + // Convenience version of a common use case of `_.map`: fetching a property. + function pluck(obj, key) { + return map(obj, property(key)); + } + + // Convenience version of a common use case of `_.filter`: selecting only + // objects containing specific `key:value` pairs. + function where(obj, attrs) { + return filter(obj, matcher(attrs)); + } + + // Return the maximum element (or element-based computation). + function max(obj, iteratee, context) { + var result = -Infinity, lastComputed = -Infinity, + value, computed; + if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) { + obj = isArrayLike(obj) ? obj : values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value > result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed > lastComputed || computed === -Infinity && result === -Infinity) { + result = v; + lastComputed = computed; + } + }); + } + return result; + } + + // Return the minimum element (or element-based computation). + function min(obj, iteratee, context) { + var result = Infinity, lastComputed = Infinity, + value, computed; + if (iteratee == null || typeof iteratee == 'number' && typeof obj[0] != 'object' && obj != null) { + obj = isArrayLike(obj) ? obj : values(obj); + for (var i = 0, length = obj.length; i < length; i++) { + value = obj[i]; + if (value != null && value < result) { + result = value; + } + } + } else { + iteratee = cb(iteratee, context); + each(obj, function(v, index, list) { + computed = iteratee(v, index, list); + if (computed < lastComputed || computed === Infinity && result === Infinity) { + result = v; + lastComputed = computed; + } + }); + } + return result; + } + + // Sample **n** random values from a collection using the modern version of the + // [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher–Yates_shuffle). + // If **n** is not specified, returns a single random element. + // The internal `guard` argument allows it to work with `_.map`. + function sample(obj, n, guard) { + if (n == null || guard) { + if (!isArrayLike(obj)) obj = values(obj); + return obj[random(obj.length - 1)]; + } + var sample = isArrayLike(obj) ? clone(obj) : values(obj); + var length = getLength(sample); + n = Math.max(Math.min(n, length), 0); + var last = length - 1; + for (var index = 0; index < n; index++) { + var rand = random(index, last); + var temp = sample[index]; + sample[index] = sample[rand]; + sample[rand] = temp; + } + return sample.slice(0, n); + } + + // Shuffle a collection. + function shuffle(obj) { + return sample(obj, Infinity); + } + + // Sort the object's values by a criterion produced by an iteratee. + function sortBy(obj, iteratee, context) { + var index = 0; + iteratee = cb(iteratee, context); + return pluck(map(obj, function(value, key, list) { + return { + value: value, + index: index++, + criteria: iteratee(value, key, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index - right.index; + }), 'value'); + } + + // An internal function used for aggregate "group by" operations. + function group(behavior, partition) { + return function(obj, iteratee, context) { + var result = partition ? [[], []] : {}; + iteratee = cb(iteratee, context); + each(obj, function(value, index) { + var key = iteratee(value, index, obj); + behavior(result, value, key); + }); + return result; + }; + } + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + var groupBy = group(function(result, value, key) { + if (has$1(result, key)) result[key].push(value); else result[key] = [value]; + }); + + // Indexes the object's values by a criterion, similar to `_.groupBy`, but for + // when you know that your index values will be unique. + var indexBy = group(function(result, value, key) { + result[key] = value; + }); + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + var countBy = group(function(result, value, key) { + if (has$1(result, key)) result[key]++; else result[key] = 1; + }); + + // Split a collection into two arrays: one whose elements all pass the given + // truth test, and one whose elements all do not pass the truth test. + var partition = group(function(result, value, pass) { + result[pass ? 0 : 1].push(value); + }, true); + + // Safely create a real, live array from anything iterable. + var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g; + function toArray(obj) { + if (!obj) return []; + if (isArray(obj)) return slice.call(obj); + if (isString(obj)) { + // Keep surrogate pair characters together. + return obj.match(reStrSymbol); + } + if (isArrayLike(obj)) return map(obj, identity); + return values(obj); + } + + // Return the number of elements in a collection. + function size(obj) { + if (obj == null) return 0; + return isArrayLike(obj) ? obj.length : keys(obj).length; + } + + // Internal `_.pick` helper function to determine whether `key` is an enumerable + // property name of `obj`. + function keyInObj(value, key, obj) { + return key in obj; + } + + // Return a copy of the object only containing the allowed properties. + var pick = restArguments(function(obj, keys) { + var result = {}, iteratee = keys[0]; + if (obj == null) return result; + if (isFunction$1(iteratee)) { + if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]); + keys = allKeys(obj); + } else { + iteratee = keyInObj; + keys = flatten$1(keys, false, false); + obj = Object(obj); + } + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i]; + var value = obj[key]; + if (iteratee(value, key, obj)) result[key] = value; + } + return result; + }); + + // Return a copy of the object without the disallowed properties. + var omit = restArguments(function(obj, keys) { + var iteratee = keys[0], context; + if (isFunction$1(iteratee)) { + iteratee = negate(iteratee); + if (keys.length > 1) context = keys[1]; + } else { + keys = map(flatten$1(keys, false, false), String); + iteratee = function(value, key) { + return !contains(keys, key); + }; + } + return pick(obj, iteratee, context); + }); + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. + function initial(array, n, guard) { + return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n))); + } + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. The **guard** check allows it to work with `_.map`. + function first(array, n, guard) { + if (array == null || array.length < 1) return n == null || guard ? void 0 : []; + if (n == null || guard) return array[0]; + return initial(array, array.length - n); + } + + // Returns everything but the first entry of the `array`. Especially useful on + // the `arguments` object. Passing an **n** will return the rest N values in the + // `array`. + function rest(array, n, guard) { + return slice.call(array, n == null || guard ? 1 : n); + } + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. + function last(array, n, guard) { + if (array == null || array.length < 1) return n == null || guard ? void 0 : []; + if (n == null || guard) return array[array.length - 1]; + return rest(array, Math.max(0, array.length - n)); + } + + // Trim out all falsy values from an array. + function compact(array) { + return filter(array, Boolean); + } + + // Flatten out an array, either recursively (by default), or up to `depth`. + // Passing `true` or `false` as `depth` means `1` or `Infinity`, respectively. + function flatten(array, depth) { + return flatten$1(array, depth, false); + } + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + var difference = restArguments(function(array, rest) { + rest = flatten$1(rest, true, true); + return filter(array, function(value){ + return !contains(rest, value); + }); + }); + + // Return a version of the array that does not contain the specified value(s). + var without = restArguments(function(array, otherArrays) { + return difference(array, otherArrays); + }); + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // The faster algorithm will not work with an iteratee if the iteratee + // is not a one-to-one function, so providing an iteratee will disable + // the faster algorithm. + function uniq(array, isSorted, iteratee, context) { + if (!isBoolean(isSorted)) { + context = iteratee; + iteratee = isSorted; + isSorted = false; + } + if (iteratee != null) iteratee = cb(iteratee, context); + var result = []; + var seen = []; + for (var i = 0, length = getLength(array); i < length; i++) { + var value = array[i], + computed = iteratee ? iteratee(value, i, array) : value; + if (isSorted && !iteratee) { + if (!i || seen !== computed) result.push(value); + seen = computed; + } else if (iteratee) { + if (!contains(seen, computed)) { + seen.push(computed); + result.push(value); + } + } else if (!contains(result, value)) { + result.push(value); + } + } + return result; + } + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + var union = restArguments(function(arrays) { + return uniq(flatten$1(arrays, true, true)); + }); + + // Produce an array that contains every item shared between all the + // passed-in arrays. + function intersection(array) { + var result = []; + var argsLength = arguments.length; + for (var i = 0, length = getLength(array); i < length; i++) { + var item = array[i]; + if (contains(result, item)) continue; + var j; + for (j = 1; j < argsLength; j++) { + if (!contains(arguments[j], item)) break; + } + if (j === argsLength) result.push(item); + } + return result; + } + + // Complement of zip. Unzip accepts an array of arrays and groups + // each array's elements on shared indices. + function unzip(array) { + var length = array && max(array, getLength).length || 0; + var result = Array(length); + + for (var index = 0; index < length; index++) { + result[index] = pluck(array, index); + } + return result; + } + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + var zip = restArguments(unzip); + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. Passing by pairs is the reverse of `_.pairs`. + function object(list, values) { + var result = {}; + for (var i = 0, length = getLength(list); i < length; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + } + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](https://docs.python.org/library/functions.html#range). + function range(start, stop, step) { + if (stop == null) { + stop = start || 0; + start = 0; + } + if (!step) { + step = stop < start ? -1 : 1; + } + + var length = Math.max(Math.ceil((stop - start) / step), 0); + var range = Array(length); + + for (var idx = 0; idx < length; idx++, start += step) { + range[idx] = start; + } + + return range; + } + + // Chunk a single array into multiple arrays, each containing `count` or fewer + // items. + function chunk(array, count) { + if (count == null || count < 1) return []; + var result = []; + var i = 0, length = array.length; + while (i < length) { + result.push(slice.call(array, i, i += count)); + } + return result; + } + + // Helper function to continue chaining intermediate results. + function chainResult(instance, obj) { + return instance._chain ? _$1(obj).chain() : obj; + } + + // Add your own custom functions to the Underscore object. + function mixin(obj) { + each(functions(obj), function(name) { + var func = _$1[name] = obj[name]; + _$1.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return chainResult(this, func.apply(_$1, args)); + }; + }); + return _$1; + } + + // Add all mutator `Array` functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _$1.prototype[name] = function() { + var obj = this._wrapped; + if (obj != null) { + method.apply(obj, arguments); + if ((name === 'shift' || name === 'splice') && obj.length === 0) { + delete obj[0]; + } + } + return chainResult(this, obj); + }; + }); + + // Add all accessor `Array` functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _$1.prototype[name] = function() { + var obj = this._wrapped; + if (obj != null) obj = method.apply(obj, arguments); + return chainResult(this, obj); + }; + }); + + // Named Exports + + var allExports = { + __proto__: null, + VERSION: VERSION, + restArguments: restArguments, + isObject: isObject, + isNull: isNull, + isUndefined: isUndefined, + isBoolean: isBoolean, + isElement: isElement, + isString: isString, + isNumber: isNumber, + isDate: isDate, + isRegExp: isRegExp, + isError: isError, + isSymbol: isSymbol, + isArrayBuffer: isArrayBuffer, + isDataView: isDataView$1, + isArray: isArray, + isFunction: isFunction$1, + isArguments: isArguments$1, + isFinite: isFinite$1, + isNaN: isNaN$1, + isTypedArray: isTypedArray$1, + isEmpty: isEmpty, + isMatch: isMatch, + isEqual: isEqual, + isMap: isMap, + isWeakMap: isWeakMap, + isSet: isSet, + isWeakSet: isWeakSet, + keys: keys, + allKeys: allKeys, + values: values, + pairs: pairs, + invert: invert, + functions: functions, + methods: functions, + extend: extend, + extendOwn: extendOwn, + assign: extendOwn, + defaults: defaults, + create: create, + clone: clone, + tap: tap, + get: get, + has: has, + mapObject: mapObject, + identity: identity, + constant: constant, + noop: noop, + toPath: toPath$1, + property: property, + propertyOf: propertyOf, + matcher: matcher, + matches: matcher, + times: times, + random: random, + now: now, + escape: _escape, + unescape: _unescape, + templateSettings: templateSettings, + template: template, + result: result, + uniqueId: uniqueId, + chain: chain, + iteratee: iteratee, + partial: partial, + bind: bind, + bindAll: bindAll, + memoize: memoize, + delay: delay, + defer: defer, + throttle: throttle, + debounce: debounce, + wrap: wrap, + negate: negate, + compose: compose, + after: after, + before: before, + once: once, + findKey: findKey, + findIndex: findIndex, + findLastIndex: findLastIndex, + sortedIndex: sortedIndex, + indexOf: indexOf, + lastIndexOf: lastIndexOf, + find: find, + detect: find, + findWhere: findWhere, + each: each, + forEach: each, + map: map, + collect: map, + reduce: reduce, + foldl: reduce, + inject: reduce, + reduceRight: reduceRight, + foldr: reduceRight, + filter: filter, + select: filter, + reject: reject, + every: every, + all: every, + some: some, + any: some, + contains: contains, + includes: contains, + include: contains, + invoke: invoke, + pluck: pluck, + where: where, + max: max, + min: min, + shuffle: shuffle, + sample: sample, + sortBy: sortBy, + groupBy: groupBy, + indexBy: indexBy, + countBy: countBy, + partition: partition, + toArray: toArray, + size: size, + pick: pick, + omit: omit, + first: first, + head: first, + take: first, + initial: initial, + last: last, + rest: rest, + tail: rest, + drop: rest, + compact: compact, + flatten: flatten, + without: without, + uniq: uniq, + unique: uniq, + union: union, + intersection: intersection, + difference: difference, + unzip: unzip, + transpose: unzip, + zip: zip, + object: object, + range: range, + chunk: chunk, + mixin: mixin, + 'default': _$1 + }; + + // Default Export + + // Add all of the Underscore functions to the wrapper object. + var _ = mixin(allExports); + // Legacy Node.js API. + _._ = _; + + return _; + +}))); +//# sourceMappingURL=underscore-umd.js.map From 49f8eb4f41586f3fc2ea224cb7fb5732891adb2d Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 10:12:51 +0200 Subject: [PATCH 03/51] Add backbone, marionette, mobelbinder js libs to the codebase The backbone stack is deprecated in the civicrm. In the 5.37.1 version the libs seems to be missing (404 in browser console), so that the Booking frontend application is broken. Versions: - Backbone: 1.4.0 has been added (developer version). I don't know which version was used in the civi. - Marionette: 4.1.2 has been added (latest version). I think an older (<3) version was used in the civi. - Modelbinder: 1.1.1 has been added (latest version). I don't know which version was used in the civi. --- packages/backbone.js | 2096 ++++++++++++++++++++++++++++++ packages/backbone.marionette.js | 203 +++ packages/backbone.modelbinder.js | 577 ++++++++ 3 files changed, 2876 insertions(+) create mode 100644 packages/backbone.js create mode 100644 packages/backbone.marionette.js create mode 100644 packages/backbone.modelbinder.js diff --git a/packages/backbone.js b/packages/backbone.js new file mode 100644 index 00000000..3e09d0dc --- /dev/null +++ b/packages/backbone.js @@ -0,0 +1,2096 @@ +// Backbone.js 1.4.0 + +// (c) 2010-2019 Jeremy Ashkenas and DocumentCloud +// Backbone may be freely distributed under the MIT license. +// For all details and documentation: +// http://backbonejs.org + +(function(factory) { + + // Establish the root object, `window` (`self`) in the browser, or `global` on the server. + // We use `self` instead of `window` for `WebWorker` support. + var root = typeof self == 'object' && self.self === self && self || + typeof global == 'object' && global.global === global && global; + + // Set up Backbone appropriately for the environment. Start with AMD. + if (typeof define === 'function' && define.amd) { + define(['underscore', 'jquery', 'exports'], function(_, $, exports) { + // Export global even in AMD case in case this script is loaded with + // others that may still expect a global Backbone. + root.Backbone = factory(root, exports, _, $); + }); + + // Next for Node.js or CommonJS. jQuery may not be needed as a module. + } else if (typeof exports !== 'undefined') { + var _ = require('underscore'), $; + try { $ = require('jquery'); } catch (e) {} + factory(root, exports, _, $); + + // Finally, as a browser global. + } else { + root.Backbone = factory(root, {}, root._, root.jQuery || root.Zepto || root.ender || root.$); + } + +})(function(root, Backbone, _, $) { + + // Initial Setup + // ------------- + + // Save the previous value of the `Backbone` variable, so that it can be + // restored later on, if `noConflict` is used. + var previousBackbone = root.Backbone; + + // Create a local reference to a common array method we'll want to use later. + var slice = Array.prototype.slice; + + // Current version of the library. Keep in sync with `package.json`. + Backbone.VERSION = '1.4.0'; + + // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns + // the `$` variable. + Backbone.$ = $; + + // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable + // to its previous owner. Returns a reference to this Backbone object. + Backbone.noConflict = function() { + root.Backbone = previousBackbone; + return this; + }; + + // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option + // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and + // set a `X-Http-Method-Override` header. + Backbone.emulateHTTP = false; + + // Turn on `emulateJSON` to support legacy servers that can't deal with direct + // `application/json` requests ... this will encode the body as + // `application/x-www-form-urlencoded` instead and will send the model in a + // form param named `model`. + Backbone.emulateJSON = false; + + // Backbone.Events + // --------------- + + // A module that can be mixed in to *any object* in order to provide it with + // a custom event channel. You may bind a callback to an event with `on` or + // remove with `off`; `trigger`-ing an event fires all callbacks in + // succession. + // + // var object = {}; + // _.extend(object, Backbone.Events); + // object.on('expand', function(){ alert('expanded'); }); + // object.trigger('expand'); + // + var Events = Backbone.Events = {}; + + // Regular expression used to split event strings. + var eventSplitter = /\s+/; + + // A private global variable to share between listeners and listenees. + var _listening; + + // Iterates over the standard `event, callback` (as well as the fancy multiple + // space-separated events `"change blur", callback` and jQuery-style event + // maps `{event: callback}`). + var eventsApi = function(iteratee, events, name, callback, opts) { + var i = 0, names; + if (name && typeof name === 'object') { + // Handle event maps. + if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback; + for (names = _.keys(name); i < names.length ; i++) { + events = eventsApi(iteratee, events, names[i], name[names[i]], opts); + } + } else if (name && eventSplitter.test(name)) { + // Handle space-separated event names by delegating them individually. + for (names = name.split(eventSplitter); i < names.length; i++) { + events = iteratee(events, names[i], callback, opts); + } + } else { + // Finally, standard events. + events = iteratee(events, name, callback, opts); + } + return events; + }; + + // Bind an event to a `callback` function. Passing `"all"` will bind + // the callback to all events fired. + Events.on = function(name, callback, context) { + this._events = eventsApi(onApi, this._events || {}, name, callback, { + context: context, + ctx: this, + listening: _listening + }); + + if (_listening) { + var listeners = this._listeners || (this._listeners = {}); + listeners[_listening.id] = _listening; + // Allow the listening to use a counter, instead of tracking + // callbacks for library interop + _listening.interop = false; + } + + return this; + }; + + // Inversion-of-control versions of `on`. Tell *this* object to listen to + // an event in another object... keeping track of what it's listening to + // for easier unbinding later. + Events.listenTo = function(obj, name, callback) { + if (!obj) return this; + var id = obj._listenId || (obj._listenId = _.uniqueId('l')); + var listeningTo = this._listeningTo || (this._listeningTo = {}); + var listening = _listening = listeningTo[id]; + + // This object is not listening to any other events on `obj` yet. + // Setup the necessary references to track the listening callbacks. + if (!listening) { + this._listenId || (this._listenId = _.uniqueId('l')); + listening = _listening = listeningTo[id] = new Listening(this, obj); + } + + // Bind callbacks on obj. + var error = tryCatchOn(obj, name, callback, this); + _listening = void 0; + + if (error) throw error; + // If the target obj is not Backbone.Events, track events manually. + if (listening.interop) listening.on(name, callback); + + return this; + }; + + // The reducing API that adds a callback to the `events` object. + var onApi = function(events, name, callback, options) { + if (callback) { + var handlers = events[name] || (events[name] = []); + var context = options.context, ctx = options.ctx, listening = options.listening; + if (listening) listening.count++; + + handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening}); + } + return events; + }; + + // An try-catch guarded #on function, to prevent poisoning the global + // `_listening` variable. + var tryCatchOn = function(obj, name, callback, context) { + try { + obj.on(name, callback, context); + } catch (e) { + return e; + } + }; + + // Remove one or many callbacks. If `context` is null, removes all + // callbacks with that function. If `callback` is null, removes all + // callbacks for the event. If `name` is null, removes all bound + // callbacks for all events. + Events.off = function(name, callback, context) { + if (!this._events) return this; + this._events = eventsApi(offApi, this._events, name, callback, { + context: context, + listeners: this._listeners + }); + + return this; + }; + + // Tell this object to stop listening to either specific events ... or + // to every object it's currently listening to. + Events.stopListening = function(obj, name, callback) { + var listeningTo = this._listeningTo; + if (!listeningTo) return this; + + var ids = obj ? [obj._listenId] : _.keys(listeningTo); + for (var i = 0; i < ids.length; i++) { + var listening = listeningTo[ids[i]]; + + // If listening doesn't exist, this object is not currently + // listening to obj. Break out early. + if (!listening) break; + + listening.obj.off(name, callback, this); + if (listening.interop) listening.off(name, callback); + } + if (_.isEmpty(listeningTo)) this._listeningTo = void 0; + + return this; + }; + + // The reducing API that removes a callback from the `events` object. + var offApi = function(events, name, callback, options) { + if (!events) return; + + var context = options.context, listeners = options.listeners; + var i = 0, names; + + // Delete all event listeners and "drop" events. + if (!name && !context && !callback) { + for (names = _.keys(listeners); i < names.length; i++) { + listeners[names[i]].cleanup(); + } + return; + } + + names = name ? [name] : _.keys(events); + for (; i < names.length; i++) { + name = names[i]; + var handlers = events[name]; + + // Bail out if there are no events stored. + if (!handlers) break; + + // Find any remaining events. + var remaining = []; + for (var j = 0; j < handlers.length; j++) { + var handler = handlers[j]; + if ( + callback && callback !== handler.callback && + callback !== handler.callback._callback || + context && context !== handler.context + ) { + remaining.push(handler); + } else { + var listening = handler.listening; + if (listening) listening.off(name, callback); + } + } + + // Replace events if there are any remaining. Otherwise, clean up. + if (remaining.length) { + events[name] = remaining; + } else { + delete events[name]; + } + } + + return events; + }; + + // Bind an event to only be triggered a single time. After the first time + // the callback is invoked, its listener will be removed. If multiple events + // are passed in using the space-separated syntax, the handler will fire + // once for each event, not once for a combination of all events. + Events.once = function(name, callback, context) { + // Map the event into a `{event: once}` object. + var events = eventsApi(onceMap, {}, name, callback, this.off.bind(this)); + if (typeof name === 'string' && context == null) callback = void 0; + return this.on(events, callback, context); + }; + + // Inversion-of-control versions of `once`. + Events.listenToOnce = function(obj, name, callback) { + // Map the event into a `{event: once}` object. + var events = eventsApi(onceMap, {}, name, callback, this.stopListening.bind(this, obj)); + return this.listenTo(obj, events); + }; + + // Reduces the event callbacks into a map of `{event: onceWrapper}`. + // `offer` unbinds the `onceWrapper` after it has been called. + var onceMap = function(map, name, callback, offer) { + if (callback) { + var once = map[name] = _.once(function() { + offer(name, once); + callback.apply(this, arguments); + }); + once._callback = callback; + } + return map; + }; + + // Trigger one or many events, firing all bound callbacks. Callbacks are + // passed the same arguments as `trigger` is, apart from the event name + // (unless you're listening on `"all"`, which will cause your callback to + // receive the true name of the event as the first argument). + Events.trigger = function(name) { + if (!this._events) return this; + + var length = Math.max(0, arguments.length - 1); + var args = Array(length); + for (var i = 0; i < length; i++) args[i] = arguments[i + 1]; + + eventsApi(triggerApi, this._events, name, void 0, args); + return this; + }; + + // Handles triggering the appropriate event callbacks. + var triggerApi = function(objEvents, name, callback, args) { + if (objEvents) { + var events = objEvents[name]; + var allEvents = objEvents.all; + if (events && allEvents) allEvents = allEvents.slice(); + if (events) triggerEvents(events, args); + if (allEvents) triggerEvents(allEvents, [name].concat(args)); + } + return objEvents; + }; + + // A difficult-to-believe, but optimized internal dispatch function for + // triggering events. Tries to keep the usual cases speedy (most internal + // Backbone events have 3 arguments). + var triggerEvents = function(events, args) { + var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2]; + switch (args.length) { + case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return; + case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return; + case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return; + case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return; + default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return; + } + }; + + // A listening class that tracks and cleans up memory bindings + // when all callbacks have been offed. + var Listening = function(listener, obj) { + this.id = listener._listenId; + this.listener = listener; + this.obj = obj; + this.interop = true; + this.count = 0; + this._events = void 0; + }; + + Listening.prototype.on = Events.on; + + // Offs a callback (or several). + // Uses an optimized counter if the listenee uses Backbone.Events. + // Otherwise, falls back to manual tracking to support events + // library interop. + Listening.prototype.off = function(name, callback) { + var cleanup; + if (this.interop) { + this._events = eventsApi(offApi, this._events, name, callback, { + context: void 0, + listeners: void 0 + }); + cleanup = !this._events; + } else { + this.count--; + cleanup = this.count === 0; + } + if (cleanup) this.cleanup(); + }; + + // Cleans up memory bindings between the listener and the listenee. + Listening.prototype.cleanup = function() { + delete this.listener._listeningTo[this.obj._listenId]; + if (!this.interop) delete this.obj._listeners[this.id]; + }; + + // Aliases for backwards compatibility. + Events.bind = Events.on; + Events.unbind = Events.off; + + // Allow the `Backbone` object to serve as a global event bus, for folks who + // want global "pubsub" in a convenient place. + _.extend(Backbone, Events); + + // Backbone.Model + // -------------- + + // Backbone **Models** are the basic data object in the framework -- + // frequently representing a row in a table in a database on your server. + // A discrete chunk of data and a bunch of useful, related methods for + // performing computations and transformations on that data. + + // Create a new model with the specified attributes. A client id (`cid`) + // is automatically generated and assigned for you. + var Model = Backbone.Model = function(attributes, options) { + var attrs = attributes || {}; + options || (options = {}); + this.preinitialize.apply(this, arguments); + this.cid = _.uniqueId(this.cidPrefix); + this.attributes = {}; + if (options.collection) this.collection = options.collection; + if (options.parse) attrs = this.parse(attrs, options) || {}; + var defaults = _.result(this, 'defaults'); + attrs = _.defaults(_.extend({}, defaults, attrs), defaults); + this.set(attrs, options); + this.changed = {}; + this.initialize.apply(this, arguments); + }; + + // Attach all inheritable methods to the Model prototype. + _.extend(Model.prototype, Events, { + + // A hash of attributes whose current and previous value differ. + changed: null, + + // The value returned during the last failed validation. + validationError: null, + + // The default name for the JSON `id` attribute is `"id"`. MongoDB and + // CouchDB users may want to set this to `"_id"`. + idAttribute: 'id', + + // The prefix is used to create the client id which is used to identify models locally. + // You may want to override this if you're experiencing name clashes with model ids. + cidPrefix: 'c', + + // preinitialize is an empty function by default. You can override it with a function + // or object. preinitialize will run before any instantiation logic is run in the Model. + preinitialize: function(){}, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Return a copy of the model's `attributes` object. + toJSON: function(options) { + return _.clone(this.attributes); + }, + + // Proxy `Backbone.sync` by default -- but override this if you need + // custom syncing semantics for *this* particular model. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Get the value of an attribute. + get: function(attr) { + return this.attributes[attr]; + }, + + // Get the HTML-escaped value of an attribute. + escape: function(attr) { + return _.escape(this.get(attr)); + }, + + // Returns `true` if the attribute contains a value that is not null + // or undefined. + has: function(attr) { + return this.get(attr) != null; + }, + + // Special-cased proxy to underscore's `_.matches` method. + matches: function(attrs) { + return !!_.iteratee(attrs, this)(this.attributes); + }, + + // Set a hash of model attributes on the object, firing `"change"`. This is + // the core primitive operation of a model, updating the data and notifying + // anyone who needs to know about the change in state. The heart of the beast. + set: function(key, val, options) { + if (key == null) return this; + + // Handle both `"key", value` and `{key: value}` -style arguments. + var attrs; + if (typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options || (options = {}); + + // Run validation. + if (!this._validate(attrs, options)) return false; + + // Extract attributes and options. + var unset = options.unset; + var silent = options.silent; + var changes = []; + var changing = this._changing; + this._changing = true; + + if (!changing) { + this._previousAttributes = _.clone(this.attributes); + this.changed = {}; + } + + var current = this.attributes; + var changed = this.changed; + var prev = this._previousAttributes; + + // For each `set` attribute, update or delete the current value. + for (var attr in attrs) { + val = attrs[attr]; + if (!_.isEqual(current[attr], val)) changes.push(attr); + if (!_.isEqual(prev[attr], val)) { + changed[attr] = val; + } else { + delete changed[attr]; + } + unset ? delete current[attr] : current[attr] = val; + } + + // Update the `id`. + if (this.idAttribute in attrs) this.id = this.get(this.idAttribute); + + // Trigger all relevant attribute changes. + if (!silent) { + if (changes.length) this._pending = options; + for (var i = 0; i < changes.length; i++) { + this.trigger('change:' + changes[i], this, current[changes[i]], options); + } + } + + // You might be wondering why there's a `while` loop here. Changes can + // be recursively nested within `"change"` events. + if (changing) return this; + if (!silent) { + while (this._pending) { + options = this._pending; + this._pending = false; + this.trigger('change', this, options); + } + } + this._pending = false; + this._changing = false; + return this; + }, + + // Remove an attribute from the model, firing `"change"`. `unset` is a noop + // if the attribute doesn't exist. + unset: function(attr, options) { + return this.set(attr, void 0, _.extend({}, options, {unset: true})); + }, + + // Clear all attributes on the model, firing `"change"`. + clear: function(options) { + var attrs = {}; + for (var key in this.attributes) attrs[key] = void 0; + return this.set(attrs, _.extend({}, options, {unset: true})); + }, + + // Determine if the model has changed since the last `"change"` event. + // If you specify an attribute name, determine if that attribute has changed. + hasChanged: function(attr) { + if (attr == null) return !_.isEmpty(this.changed); + return _.has(this.changed, attr); + }, + + // Return an object containing all the attributes that have changed, or + // false if there are no changed attributes. Useful for determining what + // parts of a view need to be updated and/or what attributes need to be + // persisted to the server. Unset attributes will be set to undefined. + // You can also pass an attributes object to diff against the model, + // determining if there *would be* a change. + changedAttributes: function(diff) { + if (!diff) return this.hasChanged() ? _.clone(this.changed) : false; + var old = this._changing ? this._previousAttributes : this.attributes; + var changed = {}; + var hasChanged; + for (var attr in diff) { + var val = diff[attr]; + if (_.isEqual(old[attr], val)) continue; + changed[attr] = val; + hasChanged = true; + } + return hasChanged ? changed : false; + }, + + // Get the previous value of an attribute, recorded at the time the last + // `"change"` event was fired. + previous: function(attr) { + if (attr == null || !this._previousAttributes) return null; + return this._previousAttributes[attr]; + }, + + // Get all of the attributes of the model at the time of the previous + // `"change"` event. + previousAttributes: function() { + return _.clone(this._previousAttributes); + }, + + // Fetch the model from the server, merging the response with the model's + // local attributes. Any changed attributes will trigger a "change" event. + fetch: function(options) { + options = _.extend({parse: true}, options); + var model = this; + var success = options.success; + options.success = function(resp) { + var serverAttrs = options.parse ? model.parse(resp, options) : resp; + if (!model.set(serverAttrs, options)) return false; + if (success) success.call(options.context, model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Set a hash of model attributes, and sync the model to the server. + // If the server returns an attributes hash that differs, the model's + // state will be `set` again. + save: function(key, val, options) { + // Handle both `"key", value` and `{key: value}` -style arguments. + var attrs; + if (key == null || typeof key === 'object') { + attrs = key; + options = val; + } else { + (attrs = {})[key] = val; + } + + options = _.extend({validate: true, parse: true}, options); + var wait = options.wait; + + // If we're not waiting and attributes exist, save acts as + // `set(attr).save(null, opts)` with validation. Otherwise, check if + // the model will be valid when the attributes, if any, are set. + if (attrs && !wait) { + if (!this.set(attrs, options)) return false; + } else if (!this._validate(attrs, options)) { + return false; + } + + // After a successful server-side save, the client is (optionally) + // updated with the server-side state. + var model = this; + var success = options.success; + var attributes = this.attributes; + options.success = function(resp) { + // Ensure attributes are restored during synchronous saves. + model.attributes = attributes; + var serverAttrs = options.parse ? model.parse(resp, options) : resp; + if (wait) serverAttrs = _.extend({}, attrs, serverAttrs); + if (serverAttrs && !model.set(serverAttrs, options)) return false; + if (success) success.call(options.context, model, resp, options); + model.trigger('sync', model, resp, options); + }; + wrapError(this, options); + + // Set temporary attributes if `{wait: true}` to properly find new ids. + if (attrs && wait) this.attributes = _.extend({}, attributes, attrs); + + var method = this.isNew() ? 'create' : options.patch ? 'patch' : 'update'; + if (method === 'patch' && !options.attrs) options.attrs = attrs; + var xhr = this.sync(method, this, options); + + // Restore attributes. + this.attributes = attributes; + + return xhr; + }, + + // Destroy this model on the server if it was already persisted. + // Optimistically removes the model from its collection, if it has one. + // If `wait: true` is passed, waits for the server to respond before removal. + destroy: function(options) { + options = options ? _.clone(options) : {}; + var model = this; + var success = options.success; + var wait = options.wait; + + var destroy = function() { + model.stopListening(); + model.trigger('destroy', model, model.collection, options); + }; + + options.success = function(resp) { + if (wait) destroy(); + if (success) success.call(options.context, model, resp, options); + if (!model.isNew()) model.trigger('sync', model, resp, options); + }; + + var xhr = false; + if (this.isNew()) { + _.defer(options.success); + } else { + wrapError(this, options); + xhr = this.sync('delete', this, options); + } + if (!wait) destroy(); + return xhr; + }, + + // Default URL for the model's representation on the server -- if you're + // using Backbone's restful methods, override this to change the endpoint + // that will be called. + url: function() { + var base = + _.result(this, 'urlRoot') || + _.result(this.collection, 'url') || + urlError(); + if (this.isNew()) return base; + var id = this.get(this.idAttribute); + return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id); + }, + + // **parse** converts a response into the hash of attributes to be `set` on + // the model. The default implementation is just to pass the response along. + parse: function(resp, options) { + return resp; + }, + + // Create a new model with identical attributes to this one. + clone: function() { + return new this.constructor(this.attributes); + }, + + // A model is new if it has never been saved to the server, and lacks an id. + isNew: function() { + return !this.has(this.idAttribute); + }, + + // Check if the model is currently in a valid state. + isValid: function(options) { + return this._validate({}, _.extend({}, options, {validate: true})); + }, + + // Run validation against the next complete set of model attributes, + // returning `true` if all is well. Otherwise, fire an `"invalid"` event. + _validate: function(attrs, options) { + if (!options.validate || !this.validate) return true; + attrs = _.extend({}, this.attributes, attrs); + var error = this.validationError = this.validate(attrs, options) || null; + if (!error) return true; + this.trigger('invalid', this, error, _.extend(options, {validationError: error})); + return false; + } + + }); + + // Backbone.Collection + // ------------------- + + // If models tend to represent a single row of data, a Backbone Collection is + // more analogous to a table full of data ... or a small slice or page of that + // table, or a collection of rows that belong together for a particular reason + // -- all of the messages in this particular folder, all of the documents + // belonging to this particular author, and so on. Collections maintain + // indexes of their models, both in order, and for lookup by `id`. + + // Create a new **Collection**, perhaps to contain a specific type of `model`. + // If a `comparator` is specified, the Collection will maintain + // its models in sort order, as they're added and removed. + var Collection = Backbone.Collection = function(models, options) { + options || (options = {}); + this.preinitialize.apply(this, arguments); + if (options.model) this.model = options.model; + if (options.comparator !== void 0) this.comparator = options.comparator; + this._reset(); + this.initialize.apply(this, arguments); + if (models) this.reset(models, _.extend({silent: true}, options)); + }; + + // Default options for `Collection#set`. + var setOptions = {add: true, remove: true, merge: true}; + var addOptions = {add: true, remove: false}; + + // Splices `insert` into `array` at index `at`. + var splice = function(array, insert, at) { + at = Math.min(Math.max(at, 0), array.length); + var tail = Array(array.length - at); + var length = insert.length; + var i; + for (i = 0; i < tail.length; i++) tail[i] = array[i + at]; + for (i = 0; i < length; i++) array[i + at] = insert[i]; + for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i]; + }; + + // Define the Collection's inheritable methods. + _.extend(Collection.prototype, Events, { + + // The default model for a collection is just a **Backbone.Model**. + // This should be overridden in most cases. + model: Model, + + + // preinitialize is an empty function by default. You can override it with a function + // or object. preinitialize will run before any instantiation logic is run in the Collection. + preinitialize: function(){}, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // The JSON representation of a Collection is an array of the + // models' attributes. + toJSON: function(options) { + return this.map(function(model) { return model.toJSON(options); }); + }, + + // Proxy `Backbone.sync` by default. + sync: function() { + return Backbone.sync.apply(this, arguments); + }, + + // Add a model, or list of models to the set. `models` may be Backbone + // Models or raw JavaScript objects to be converted to Models, or any + // combination of the two. + add: function(models, options) { + return this.set(models, _.extend({merge: false}, options, addOptions)); + }, + + // Remove a model, or a list of models from the set. + remove: function(models, options) { + options = _.extend({}, options); + var singular = !_.isArray(models); + models = singular ? [models] : models.slice(); + var removed = this._removeModels(models, options); + if (!options.silent && removed.length) { + options.changes = {added: [], merged: [], removed: removed}; + this.trigger('update', this, options); + } + return singular ? removed[0] : removed; + }, + + // Update a collection by `set`-ing a new list of models, adding new ones, + // removing models that are no longer present, and merging models that + // already exist in the collection, as necessary. Similar to **Model#set**, + // the core operation for updating the data contained by the collection. + set: function(models, options) { + if (models == null) return; + + options = _.extend({}, setOptions, options); + if (options.parse && !this._isModel(models)) { + models = this.parse(models, options) || []; + } + + var singular = !_.isArray(models); + models = singular ? [models] : models.slice(); + + var at = options.at; + if (at != null) at = +at; + if (at > this.length) at = this.length; + if (at < 0) at += this.length + 1; + + var set = []; + var toAdd = []; + var toMerge = []; + var toRemove = []; + var modelMap = {}; + + var add = options.add; + var merge = options.merge; + var remove = options.remove; + + var sort = false; + var sortable = this.comparator && at == null && options.sort !== false; + var sortAttr = _.isString(this.comparator) ? this.comparator : null; + + // Turn bare objects into model references, and prevent invalid models + // from being added. + var model, i; + for (i = 0; i < models.length; i++) { + model = models[i]; + + // If a duplicate is found, prevent it from being added and + // optionally merge it into the existing model. + var existing = this.get(model); + if (existing) { + if (merge && model !== existing) { + var attrs = this._isModel(model) ? model.attributes : model; + if (options.parse) attrs = existing.parse(attrs, options); + existing.set(attrs, options); + toMerge.push(existing); + if (sortable && !sort) sort = existing.hasChanged(sortAttr); + } + if (!modelMap[existing.cid]) { + modelMap[existing.cid] = true; + set.push(existing); + } + models[i] = existing; + + // If this is a new, valid model, push it to the `toAdd` list. + } else if (add) { + model = models[i] = this._prepareModel(model, options); + if (model) { + toAdd.push(model); + this._addReference(model, options); + modelMap[model.cid] = true; + set.push(model); + } + } + } + + // Remove stale models. + if (remove) { + for (i = 0; i < this.length; i++) { + model = this.models[i]; + if (!modelMap[model.cid]) toRemove.push(model); + } + if (toRemove.length) this._removeModels(toRemove, options); + } + + // See if sorting is needed, update `length` and splice in new models. + var orderChanged = false; + var replace = !sortable && add && remove; + if (set.length && replace) { + orderChanged = this.length !== set.length || _.some(this.models, function(m, index) { + return m !== set[index]; + }); + this.models.length = 0; + splice(this.models, set, 0); + this.length = this.models.length; + } else if (toAdd.length) { + if (sortable) sort = true; + splice(this.models, toAdd, at == null ? this.length : at); + this.length = this.models.length; + } + + // Silently sort the collection if appropriate. + if (sort) this.sort({silent: true}); + + // Unless silenced, it's time to fire all appropriate add/sort/update events. + if (!options.silent) { + for (i = 0; i < toAdd.length; i++) { + if (at != null) options.index = at + i; + model = toAdd[i]; + model.trigger('add', model, this, options); + } + if (sort || orderChanged) this.trigger('sort', this, options); + if (toAdd.length || toRemove.length || toMerge.length) { + options.changes = { + added: toAdd, + removed: toRemove, + merged: toMerge + }; + this.trigger('update', this, options); + } + } + + // Return the added (or merged) model (or models). + return singular ? models[0] : models; + }, + + // When you have more items than you want to add or remove individually, + // you can reset the entire set with a new list of models, without firing + // any granular `add` or `remove` events. Fires `reset` when finished. + // Useful for bulk operations and optimizations. + reset: function(models, options) { + options = options ? _.clone(options) : {}; + for (var i = 0; i < this.models.length; i++) { + this._removeReference(this.models[i], options); + } + options.previousModels = this.models; + this._reset(); + models = this.add(models, _.extend({silent: true}, options)); + if (!options.silent) this.trigger('reset', this, options); + return models; + }, + + // Add a model to the end of the collection. + push: function(model, options) { + return this.add(model, _.extend({at: this.length}, options)); + }, + + // Remove a model from the end of the collection. + pop: function(options) { + var model = this.at(this.length - 1); + return this.remove(model, options); + }, + + // Add a model to the beginning of the collection. + unshift: function(model, options) { + return this.add(model, _.extend({at: 0}, options)); + }, + + // Remove a model from the beginning of the collection. + shift: function(options) { + var model = this.at(0); + return this.remove(model, options); + }, + + // Slice out a sub-array of models from the collection. + slice: function() { + return slice.apply(this.models, arguments); + }, + + // Get a model from the set by id, cid, model object with id or cid + // properties, or an attributes object that is transformed through modelId. + get: function(obj) { + if (obj == null) return void 0; + return this._byId[obj] || + this._byId[this.modelId(this._isModel(obj) ? obj.attributes : obj)] || + obj.cid && this._byId[obj.cid]; + }, + + // Returns `true` if the model is in the collection. + has: function(obj) { + return this.get(obj) != null; + }, + + // Get the model at the given index. + at: function(index) { + if (index < 0) index += this.length; + return this.models[index]; + }, + + // Return models with matching attributes. Useful for simple cases of + // `filter`. + where: function(attrs, first) { + return this[first ? 'find' : 'filter'](attrs); + }, + + // Return the first model with matching attributes. Useful for simple cases + // of `find`. + findWhere: function(attrs) { + return this.where(attrs, true); + }, + + // Force the collection to re-sort itself. You don't need to call this under + // normal circumstances, as the set will maintain sort order as each item + // is added. + sort: function(options) { + var comparator = this.comparator; + if (!comparator) throw new Error('Cannot sort a set without a comparator'); + options || (options = {}); + + var length = comparator.length; + if (_.isFunction(comparator)) comparator = comparator.bind(this); + + // Run sort based on type of `comparator`. + if (length === 1 || _.isString(comparator)) { + this.models = this.sortBy(comparator); + } else { + this.models.sort(comparator); + } + if (!options.silent) this.trigger('sort', this, options); + return this; + }, + + // Pluck an attribute from each model in the collection. + pluck: function(attr) { + return this.map(attr + ''); + }, + + // Fetch the default set of models for this collection, resetting the + // collection when they arrive. If `reset: true` is passed, the response + // data will be passed through the `reset` method instead of `set`. + fetch: function(options) { + options = _.extend({parse: true}, options); + var success = options.success; + var collection = this; + options.success = function(resp) { + var method = options.reset ? 'reset' : 'set'; + collection[method](resp, options); + if (success) success.call(options.context, collection, resp, options); + collection.trigger('sync', collection, resp, options); + }; + wrapError(this, options); + return this.sync('read', this, options); + }, + + // Create a new instance of a model in this collection. Add the model to the + // collection immediately, unless `wait: true` is passed, in which case we + // wait for the server to agree. + create: function(model, options) { + options = options ? _.clone(options) : {}; + var wait = options.wait; + model = this._prepareModel(model, options); + if (!model) return false; + if (!wait) this.add(model, options); + var collection = this; + var success = options.success; + options.success = function(m, resp, callbackOpts) { + if (wait) collection.add(m, callbackOpts); + if (success) success.call(callbackOpts.context, m, resp, callbackOpts); + }; + model.save(null, options); + return model; + }, + + // **parse** converts a response into a list of models to be added to the + // collection. The default implementation is just to pass it through. + parse: function(resp, options) { + return resp; + }, + + // Create a new collection with an identical list of models as this one. + clone: function() { + return new this.constructor(this.models, { + model: this.model, + comparator: this.comparator + }); + }, + + // Define how to uniquely identify models in the collection. + modelId: function(attrs) { + return attrs[this.model.prototype.idAttribute || 'id']; + }, + + // Get an iterator of all models in this collection. + values: function() { + return new CollectionIterator(this, ITERATOR_VALUES); + }, + + // Get an iterator of all model IDs in this collection. + keys: function() { + return new CollectionIterator(this, ITERATOR_KEYS); + }, + + // Get an iterator of all [ID, model] tuples in this collection. + entries: function() { + return new CollectionIterator(this, ITERATOR_KEYSVALUES); + }, + + // Private method to reset all internal state. Called when the collection + // is first initialized or reset. + _reset: function() { + this.length = 0; + this.models = []; + this._byId = {}; + }, + + // Prepare a hash of attributes (or other model) to be added to this + // collection. + _prepareModel: function(attrs, options) { + if (this._isModel(attrs)) { + if (!attrs.collection) attrs.collection = this; + return attrs; + } + options = options ? _.clone(options) : {}; + options.collection = this; + var model = new this.model(attrs, options); + if (!model.validationError) return model; + this.trigger('invalid', this, model.validationError, options); + return false; + }, + + // Internal method called by both remove and set. + _removeModels: function(models, options) { + var removed = []; + for (var i = 0; i < models.length; i++) { + var model = this.get(models[i]); + if (!model) continue; + + var index = this.indexOf(model); + this.models.splice(index, 1); + this.length--; + + // Remove references before triggering 'remove' event to prevent an + // infinite loop. #3693 + delete this._byId[model.cid]; + var id = this.modelId(model.attributes); + if (id != null) delete this._byId[id]; + + if (!options.silent) { + options.index = index; + model.trigger('remove', model, this, options); + } + + removed.push(model); + this._removeReference(model, options); + } + return removed; + }, + + // Method for checking whether an object should be considered a model for + // the purposes of adding to the collection. + _isModel: function(model) { + return model instanceof Model; + }, + + // Internal method to create a model's ties to a collection. + _addReference: function(model, options) { + this._byId[model.cid] = model; + var id = this.modelId(model.attributes); + if (id != null) this._byId[id] = model; + model.on('all', this._onModelEvent, this); + }, + + // Internal method to sever a model's ties to a collection. + _removeReference: function(model, options) { + delete this._byId[model.cid]; + var id = this.modelId(model.attributes); + if (id != null) delete this._byId[id]; + if (this === model.collection) delete model.collection; + model.off('all', this._onModelEvent, this); + }, + + // Internal method called every time a model in the set fires an event. + // Sets need to update their indexes when models change ids. All other + // events simply proxy through. "add" and "remove" events that originate + // in other collections are ignored. + _onModelEvent: function(event, model, collection, options) { + if (model) { + if ((event === 'add' || event === 'remove') && collection !== this) return; + if (event === 'destroy') this.remove(model, options); + if (event === 'change') { + var prevId = this.modelId(model.previousAttributes()); + var id = this.modelId(model.attributes); + if (prevId !== id) { + if (prevId != null) delete this._byId[prevId]; + if (id != null) this._byId[id] = model; + } + } + } + this.trigger.apply(this, arguments); + } + + }); + + // Defining an @@iterator method implements JavaScript's Iterable protocol. + // In modern ES2015 browsers, this value is found at Symbol.iterator. + /* global Symbol */ + var $$iterator = typeof Symbol === 'function' && Symbol.iterator; + if ($$iterator) { + Collection.prototype[$$iterator] = Collection.prototype.values; + } + + // CollectionIterator + // ------------------ + + // A CollectionIterator implements JavaScript's Iterator protocol, allowing the + // use of `for of` loops in modern browsers and interoperation between + // Backbone.Collection and other JavaScript functions and third-party libraries + // which can operate on Iterables. + var CollectionIterator = function(collection, kind) { + this._collection = collection; + this._kind = kind; + this._index = 0; + }; + + // This "enum" defines the three possible kinds of values which can be emitted + // by a CollectionIterator that correspond to the values(), keys() and entries() + // methods on Collection, respectively. + var ITERATOR_VALUES = 1; + var ITERATOR_KEYS = 2; + var ITERATOR_KEYSVALUES = 3; + + // All Iterators should themselves be Iterable. + if ($$iterator) { + CollectionIterator.prototype[$$iterator] = function() { + return this; + }; + } + + CollectionIterator.prototype.next = function() { + if (this._collection) { + + // Only continue iterating if the iterated collection is long enough. + if (this._index < this._collection.length) { + var model = this._collection.at(this._index); + this._index++; + + // Construct a value depending on what kind of values should be iterated. + var value; + if (this._kind === ITERATOR_VALUES) { + value = model; + } else { + var id = this._collection.modelId(model.attributes); + if (this._kind === ITERATOR_KEYS) { + value = id; + } else { // ITERATOR_KEYSVALUES + value = [id, model]; + } + } + return {value: value, done: false}; + } + + // Once exhausted, remove the reference to the collection so future + // calls to the next method always return done. + this._collection = void 0; + } + + return {value: void 0, done: true}; + }; + + // Backbone.View + // ------------- + + // Backbone Views are almost more convention than they are actual code. A View + // is simply a JavaScript object that represents a logical chunk of UI in the + // DOM. This might be a single item, an entire list, a sidebar or panel, or + // even the surrounding frame which wraps your whole app. Defining a chunk of + // UI as a **View** allows you to define your DOM events declaratively, without + // having to worry about render order ... and makes it easy for the view to + // react to specific changes in the state of your models. + + // Creating a Backbone.View creates its initial element outside of the DOM, + // if an existing element is not provided... + var View = Backbone.View = function(options) { + this.cid = _.uniqueId('view'); + this.preinitialize.apply(this, arguments); + _.extend(this, _.pick(options, viewOptions)); + this._ensureElement(); + this.initialize.apply(this, arguments); + }; + + // Cached regex to split keys for `delegate`. + var delegateEventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be set as properties. + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events']; + + // Set up all inheritable **Backbone.View** properties and methods. + _.extend(View.prototype, Events, { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + // jQuery delegate for element lookup, scoped to DOM elements within the + // current view. This should be preferred to global lookups where possible. + $: function(selector) { + return this.$el.find(selector); + }, + + // preinitialize is an empty function by default. You can override it with a function + // or object. preinitialize will run before any instantiation logic is run in the View + preinitialize: function(){}, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // **render** is the core function that your view should override, in order + // to populate its element (`this.el`), with the appropriate HTML. The + // convention is for **render** to always return `this`. + render: function() { + return this; + }, + + // Remove this view by taking the element out of the DOM, and removing any + // applicable Backbone.Events listeners. + remove: function() { + this._removeElement(); + this.stopListening(); + return this; + }, + + // Remove this view's element from the document and all event listeners + // attached to it. Exposed for subclasses using an alternative DOM + // manipulation API. + _removeElement: function() { + this.$el.remove(); + }, + + // Change the view's element (`this.el` property) and re-delegate the + // view's events on the new element. + setElement: function(element) { + this.undelegateEvents(); + this._setElement(element); + this.delegateEvents(); + return this; + }, + + // Creates the `this.el` and `this.$el` references for this view using the + // given `el`. `el` can be a CSS selector or an HTML string, a jQuery + // context or an element. Subclasses can override this to utilize an + // alternative DOM manipulation API and are only required to set the + // `this.el` property. + _setElement: function(el) { + this.$el = el instanceof Backbone.$ ? el : Backbone.$(el); + this.el = this.$el[0]; + }, + + // Set callbacks, where `this.events` is a hash of + // + // *{"event selector": "callback"}* + // + // { + // 'mousedown .title': 'edit', + // 'click .button': 'save', + // 'click .open': function(e) { ... } + // } + // + // pairs. Callbacks will be bound to the view, with `this` set properly. + // Uses event delegation for efficiency. + // Omitting the selector binds the event to `this.el`. + delegateEvents: function(events) { + events || (events = _.result(this, 'events')); + if (!events) return this; + this.undelegateEvents(); + for (var key in events) { + var method = events[key]; + if (!_.isFunction(method)) method = this[method]; + if (!method) continue; + var match = key.match(delegateEventSplitter); + this.delegate(match[1], match[2], method.bind(this)); + } + return this; + }, + + // Add a single event listener to the view's element (or a child element + // using `selector`). This only works for delegate-able events: not `focus`, + // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer. + delegate: function(eventName, selector, listener) { + this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener); + return this; + }, + + // Clears all callbacks previously bound to the view by `delegateEvents`. + // You usually don't need to use this, but may wish to if you have multiple + // Backbone views attached to the same DOM element. + undelegateEvents: function() { + if (this.$el) this.$el.off('.delegateEvents' + this.cid); + return this; + }, + + // A finer-grained `undelegateEvents` for removing a single delegated event. + // `selector` and `listener` are both optional. + undelegate: function(eventName, selector, listener) { + this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener); + return this; + }, + + // Produces a DOM element to be assigned to your view. Exposed for + // subclasses using an alternative DOM manipulation API. + _createElement: function(tagName) { + return document.createElement(tagName); + }, + + // Ensure that the View has a DOM element to render into. + // If `this.el` is a string, pass it through `$()`, take the first + // matching element, and re-assign it to `el`. Otherwise, create + // an element from the `id`, `className` and `tagName` properties. + _ensureElement: function() { + if (!this.el) { + var attrs = _.extend({}, _.result(this, 'attributes')); + if (this.id) attrs.id = _.result(this, 'id'); + if (this.className) attrs['class'] = _.result(this, 'className'); + this.setElement(this._createElement(_.result(this, 'tagName'))); + this._setAttributes(attrs); + } else { + this.setElement(_.result(this, 'el')); + } + }, + + // Set attributes from a hash on this view's element. Exposed for + // subclasses using an alternative DOM manipulation API. + _setAttributes: function(attributes) { + this.$el.attr(attributes); + } + + }); + + // Proxy Backbone class methods to Underscore functions, wrapping the model's + // `attributes` object or collection's `models` array behind the scenes. + // + // collection.filter(function(model) { return model.get('age') > 10 }); + // collection.each(this.addView); + // + // `Function#apply` can be slow so we use the method's arg count, if we know it. + var addMethod = function(base, length, method, attribute) { + switch (length) { + case 1: return function() { + return base[method](this[attribute]); + }; + case 2: return function(value) { + return base[method](this[attribute], value); + }; + case 3: return function(iteratee, context) { + return base[method](this[attribute], cb(iteratee, this), context); + }; + case 4: return function(iteratee, defaultVal, context) { + return base[method](this[attribute], cb(iteratee, this), defaultVal, context); + }; + default: return function() { + var args = slice.call(arguments); + args.unshift(this[attribute]); + return base[method].apply(base, args); + }; + } + }; + + var addUnderscoreMethods = function(Class, base, methods, attribute) { + _.each(methods, function(length, method) { + if (base[method]) Class.prototype[method] = addMethod(base, length, method, attribute); + }); + }; + + // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`. + var cb = function(iteratee, instance) { + if (_.isFunction(iteratee)) return iteratee; + if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee); + if (_.isString(iteratee)) return function(model) { return model.get(iteratee); }; + return iteratee; + }; + var modelMatcher = function(attrs) { + var matcher = _.matches(attrs); + return function(model) { + return matcher(model.attributes); + }; + }; + + // Underscore methods that we want to implement on the Collection. + // 90% of the core usefulness of Backbone Collections is actually implemented + // right here: + var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0, + foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3, + select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3, + contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3, + head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3, + without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3, + isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3, + sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3}; + + + // Underscore methods that we want to implement on the Model, mapped to the + // number of arguments they take. + var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0, + omit: 0, chain: 1, isEmpty: 1}; + + // Mix in each Underscore method as a proxy to `Collection#models`. + + _.each([ + [Collection, collectionMethods, 'models'], + [Model, modelMethods, 'attributes'] + ], function(config) { + var Base = config[0], + methods = config[1], + attribute = config[2]; + + Base.mixin = function(obj) { + var mappings = _.reduce(_.functions(obj), function(memo, name) { + memo[name] = 0; + return memo; + }, {}); + addUnderscoreMethods(Base, obj, mappings, attribute); + }; + + addUnderscoreMethods(Base, _, methods, attribute); + }); + + // Backbone.sync + // ------------- + + // Override this function to change the manner in which Backbone persists + // models to the server. You will be passed the type of request, and the + // model in question. By default, makes a RESTful Ajax request + // to the model's `url()`. Some possible customizations could be: + // + // * Use `setTimeout` to batch rapid-fire updates into a single request. + // * Send up the models as XML instead of JSON. + // * Persist models via WebSockets instead of Ajax. + // + // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests + // as `POST`, with a `_method` parameter containing the true HTTP method, + // as well as all requests with the body as `application/x-www-form-urlencoded` + // instead of `application/json` with the model in a param named `model`. + // Useful when interfacing with server-side languages like **PHP** that make + // it difficult to read the body of `PUT` requests. + Backbone.sync = function(method, model, options) { + var type = methodMap[method]; + + // Default options, unless specified. + _.defaults(options || (options = {}), { + emulateHTTP: Backbone.emulateHTTP, + emulateJSON: Backbone.emulateJSON + }); + + // Default JSON-request options. + var params = {type: type, dataType: 'json'}; + + // Ensure that we have a URL. + if (!options.url) { + params.url = _.result(model, 'url') || urlError(); + } + + // Ensure that we have the appropriate request data. + if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) { + params.contentType = 'application/json'; + params.data = JSON.stringify(options.attrs || model.toJSON(options)); + } + + // For older servers, emulate JSON by encoding the request into an HTML-form. + if (options.emulateJSON) { + params.contentType = 'application/x-www-form-urlencoded'; + params.data = params.data ? {model: params.data} : {}; + } + + // For older servers, emulate HTTP by mimicking the HTTP method with `_method` + // And an `X-HTTP-Method-Override` header. + if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) { + params.type = 'POST'; + if (options.emulateJSON) params.data._method = type; + var beforeSend = options.beforeSend; + options.beforeSend = function(xhr) { + xhr.setRequestHeader('X-HTTP-Method-Override', type); + if (beforeSend) return beforeSend.apply(this, arguments); + }; + } + + // Don't process data on a non-GET request. + if (params.type !== 'GET' && !options.emulateJSON) { + params.processData = false; + } + + // Pass along `textStatus` and `errorThrown` from jQuery. + var error = options.error; + options.error = function(xhr, textStatus, errorThrown) { + options.textStatus = textStatus; + options.errorThrown = errorThrown; + if (error) error.call(options.context, xhr, textStatus, errorThrown); + }; + + // Make the request, allowing the user to override any Ajax options. + var xhr = options.xhr = Backbone.ajax(_.extend(params, options)); + model.trigger('request', model, xhr, options); + return xhr; + }; + + // Map from CRUD to HTTP for our default `Backbone.sync` implementation. + var methodMap = { + create: 'POST', + update: 'PUT', + patch: 'PATCH', + delete: 'DELETE', + read: 'GET' + }; + + // Set the default implementation of `Backbone.ajax` to proxy through to `$`. + // Override this if you'd like to use a different library. + Backbone.ajax = function() { + return Backbone.$.ajax.apply(Backbone.$, arguments); + }; + + // Backbone.Router + // --------------- + + // Routers map faux-URLs to actions, and fire events when routes are + // matched. Creating a new one sets its `routes` hash, if not set statically. + var Router = Backbone.Router = function(options) { + options || (options = {}); + this.preinitialize.apply(this, arguments); + if (options.routes) this.routes = options.routes; + this._bindRoutes(); + this.initialize.apply(this, arguments); + }; + + // Cached regular expressions for matching named param parts and splatted + // parts of route strings. + var optionalParam = /\((.*?)\)/g; + var namedParam = /(\(\?)?:\w+/g; + var splatParam = /\*\w+/g; + var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; + + // Set up all inheritable **Backbone.Router** properties and methods. + _.extend(Router.prototype, Events, { + + // preinitialize is an empty function by default. You can override it with a function + // or object. preinitialize will run before any instantiation logic is run in the Router. + preinitialize: function(){}, + + // Initialize is an empty function by default. Override it with your own + // initialization logic. + initialize: function(){}, + + // Manually bind a single named route to a callback. For example: + // + // this.route('search/:query/p:num', 'search', function(query, num) { + // ... + // }); + // + route: function(route, name, callback) { + if (!_.isRegExp(route)) route = this._routeToRegExp(route); + if (_.isFunction(name)) { + callback = name; + name = ''; + } + if (!callback) callback = this[name]; + var router = this; + Backbone.history.route(route, function(fragment) { + var args = router._extractParameters(route, fragment); + if (router.execute(callback, args, name) !== false) { + router.trigger.apply(router, ['route:' + name].concat(args)); + router.trigger('route', name, args); + Backbone.history.trigger('route', router, name, args); + } + }); + return this; + }, + + // Execute a route handler with the provided parameters. This is an + // excellent place to do pre-route setup or post-route cleanup. + execute: function(callback, args, name) { + if (callback) callback.apply(this, args); + }, + + // Simple proxy to `Backbone.history` to save a fragment into the history. + navigate: function(fragment, options) { + Backbone.history.navigate(fragment, options); + return this; + }, + + // Bind all defined routes to `Backbone.history`. We have to reverse the + // order of the routes here to support behavior where the most general + // routes can be defined at the bottom of the route map. + _bindRoutes: function() { + if (!this.routes) return; + this.routes = _.result(this, 'routes'); + var route, routes = _.keys(this.routes); + while ((route = routes.pop()) != null) { + this.route(route, this.routes[route]); + } + }, + + // Convert a route string into a regular expression, suitable for matching + // against the current location hash. + _routeToRegExp: function(route) { + route = route.replace(escapeRegExp, '\\$&') + .replace(optionalParam, '(?:$1)?') + .replace(namedParam, function(match, optional) { + return optional ? match : '([^/?]+)'; + }) + .replace(splatParam, '([^?]*?)'); + return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$'); + }, + + // Given a route, and a URL fragment that it matches, return the array of + // extracted decoded parameters. Empty or unmatched parameters will be + // treated as `null` to normalize cross-browser behavior. + _extractParameters: function(route, fragment) { + var params = route.exec(fragment).slice(1); + return _.map(params, function(param, i) { + // Don't decode the search params. + if (i === params.length - 1) return param || null; + return param ? decodeURIComponent(param) : null; + }); + } + + }); + + // Backbone.History + // ---------------- + + // Handles cross-browser history management, based on either + // [pushState](http://diveintohtml5.info/history.html) and real URLs, or + // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange) + // and URL fragments. If the browser supports neither (old IE, natch), + // falls back to polling. + var History = Backbone.History = function() { + this.handlers = []; + this.checkUrl = this.checkUrl.bind(this); + + // Ensure that `History` can be used outside of the browser. + if (typeof window !== 'undefined') { + this.location = window.location; + this.history = window.history; + } + }; + + // Cached regex for stripping a leading hash/slash and trailing space. + var routeStripper = /^[#\/]|\s+$/g; + + // Cached regex for stripping leading and trailing slashes. + var rootStripper = /^\/+|\/+$/g; + + // Cached regex for stripping urls of hash. + var pathStripper = /#.*$/; + + // Has the history handling already been started? + History.started = false; + + // Set up all inheritable **Backbone.History** properties and methods. + _.extend(History.prototype, Events, { + + // The default interval to poll for hash changes, if necessary, is + // twenty times a second. + interval: 50, + + // Are we at the app root? + atRoot: function() { + var path = this.location.pathname.replace(/[^\/]$/, '$&/'); + return path === this.root && !this.getSearch(); + }, + + // Does the pathname match the root? + matchRoot: function() { + var path = this.decodeFragment(this.location.pathname); + var rootPath = path.slice(0, this.root.length - 1) + '/'; + return rootPath === this.root; + }, + + // Unicode characters in `location.pathname` are percent encoded so they're + // decoded for comparison. `%25` should not be decoded since it may be part + // of an encoded parameter. + decodeFragment: function(fragment) { + return decodeURI(fragment.replace(/%25/g, '%2525')); + }, + + // In IE6, the hash fragment and search params are incorrect if the + // fragment contains `?`. + getSearch: function() { + var match = this.location.href.replace(/#.*/, '').match(/\?.+/); + return match ? match[0] : ''; + }, + + // Gets the true hash value. Cannot use location.hash directly due to bug + // in Firefox where location.hash will always be decoded. + getHash: function(window) { + var match = (window || this).location.href.match(/#(.*)$/); + return match ? match[1] : ''; + }, + + // Get the pathname and search params, without the root. + getPath: function() { + var path = this.decodeFragment( + this.location.pathname + this.getSearch() + ).slice(this.root.length - 1); + return path.charAt(0) === '/' ? path.slice(1) : path; + }, + + // Get the cross-browser normalized URL fragment from the path or hash. + getFragment: function(fragment) { + if (fragment == null) { + if (this._usePushState || !this._wantsHashChange) { + fragment = this.getPath(); + } else { + fragment = this.getHash(); + } + } + return fragment.replace(routeStripper, ''); + }, + + // Start the hash change handling, returning `true` if the current URL matches + // an existing route, and `false` otherwise. + start: function(options) { + if (History.started) throw new Error('Backbone.history has already been started'); + History.started = true; + + // Figure out the initial configuration. Do we need an iframe? + // Is pushState desired ... is it available? + this.options = _.extend({root: '/'}, this.options, options); + this.root = this.options.root; + this._wantsHashChange = this.options.hashChange !== false; + this._hasHashChange = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7); + this._useHashChange = this._wantsHashChange && this._hasHashChange; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.history && this.history.pushState); + this._usePushState = this._wantsPushState && this._hasPushState; + this.fragment = this.getFragment(); + + // Normalize root to always include a leading and trailing slash. + this.root = ('/' + this.root + '/').replace(rootStripper, '/'); + + // Transition from hashChange to pushState or vice versa if both are + // requested. + if (this._wantsHashChange && this._wantsPushState) { + + // If we've started off with a route from a `pushState`-enabled + // browser, but we're currently in a browser that doesn't support it... + if (!this._hasPushState && !this.atRoot()) { + var rootPath = this.root.slice(0, -1) || '/'; + this.location.replace(rootPath + '#' + this.getPath()); + // Return immediately as browser will do redirect to new url + return true; + + // Or if we've started out with a hash-based route, but we're currently + // in a browser where it could be `pushState`-based instead... + } else if (this._hasPushState && this.atRoot()) { + this.navigate(this.getHash(), {replace: true}); + } + + } + + // Proxy an iframe to handle location events if the browser doesn't + // support the `hashchange` event, HTML5 history, or the user wants + // `hashChange` but not `pushState`. + if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) { + this.iframe = document.createElement('iframe'); + this.iframe.src = 'javascript:0'; + this.iframe.style.display = 'none'; + this.iframe.tabIndex = -1; + var body = document.body; + // Using `appendChild` will throw on IE < 9 if the document is not ready. + var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow; + iWindow.document.open(); + iWindow.document.close(); + iWindow.location.hash = '#' + this.fragment; + } + + // Add a cross-platform `addEventListener` shim for older browsers. + var addEventListener = window.addEventListener || function(eventName, listener) { + return attachEvent('on' + eventName, listener); + }; + + // Depending on whether we're using pushState or hashes, and whether + // 'onhashchange' is supported, determine how we check the URL state. + if (this._usePushState) { + addEventListener('popstate', this.checkUrl, false); + } else if (this._useHashChange && !this.iframe) { + addEventListener('hashchange', this.checkUrl, false); + } else if (this._wantsHashChange) { + this._checkUrlInterval = setInterval(this.checkUrl, this.interval); + } + + if (!this.options.silent) return this.loadUrl(); + }, + + // Disable Backbone.history, perhaps temporarily. Not useful in a real app, + // but possibly useful for unit testing Routers. + stop: function() { + // Add a cross-platform `removeEventListener` shim for older browsers. + var removeEventListener = window.removeEventListener || function(eventName, listener) { + return detachEvent('on' + eventName, listener); + }; + + // Remove window listeners. + if (this._usePushState) { + removeEventListener('popstate', this.checkUrl, false); + } else if (this._useHashChange && !this.iframe) { + removeEventListener('hashchange', this.checkUrl, false); + } + + // Clean up the iframe if necessary. + if (this.iframe) { + document.body.removeChild(this.iframe); + this.iframe = null; + } + + // Some environments will throw when clearing an undefined interval. + if (this._checkUrlInterval) clearInterval(this._checkUrlInterval); + History.started = false; + }, + + // Add a route to be tested when the fragment changes. Routes added later + // may override previous routes. + route: function(route, callback) { + this.handlers.unshift({route: route, callback: callback}); + }, + + // Checks the current URL to see if it has changed, and if it has, + // calls `loadUrl`, normalizing across the hidden iframe. + checkUrl: function(e) { + var current = this.getFragment(); + + // If the user pressed the back button, the iframe's hash will have + // changed and we should use that for comparison. + if (current === this.fragment && this.iframe) { + current = this.getHash(this.iframe.contentWindow); + } + + if (current === this.fragment) return false; + if (this.iframe) this.navigate(current); + this.loadUrl(); + }, + + // Attempt to load the current URL fragment. If a route succeeds with a + // match, returns `true`. If no defined routes matches the fragment, + // returns `false`. + loadUrl: function(fragment) { + // If the root doesn't match, no routes can match either. + if (!this.matchRoot()) return false; + fragment = this.fragment = this.getFragment(fragment); + return _.some(this.handlers, function(handler) { + if (handler.route.test(fragment)) { + handler.callback(fragment); + return true; + } + }); + }, + + // Save a fragment into the hash history, or replace the URL state if the + // 'replace' option is passed. You are responsible for properly URL-encoding + // the fragment in advance. + // + // The options object can contain `trigger: true` if you wish to have the + // route callback be fired (not usually desirable), or `replace: true`, if + // you wish to modify the current URL without adding an entry to the history. + navigate: function(fragment, options) { + if (!History.started) return false; + if (!options || options === true) options = {trigger: !!options}; + + // Normalize the fragment. + fragment = this.getFragment(fragment || ''); + + // Don't include a trailing slash on the root. + var rootPath = this.root; + if (fragment === '' || fragment.charAt(0) === '?') { + rootPath = rootPath.slice(0, -1) || '/'; + } + var url = rootPath + fragment; + + // Strip the fragment of the query and hash for matching. + fragment = fragment.replace(pathStripper, ''); + + // Decode for matching. + var decodedFragment = this.decodeFragment(fragment); + + if (this.fragment === decodedFragment) return; + this.fragment = decodedFragment; + + // If pushState is available, we use it to set the fragment as a real URL. + if (this._usePushState) { + this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url); + + // If hash changes haven't been explicitly disabled, update the hash + // fragment to store history. + } else if (this._wantsHashChange) { + this._updateHash(this.location, fragment, options.replace); + if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) { + var iWindow = this.iframe.contentWindow; + + // Opening and closing the iframe tricks IE7 and earlier to push a + // history entry on hash-tag change. When replace is true, we don't + // want this. + if (!options.replace) { + iWindow.document.open(); + iWindow.document.close(); + } + + this._updateHash(iWindow.location, fragment, options.replace); + } + + // If you've told us that you explicitly don't want fallback hashchange- + // based history, then `navigate` becomes a page refresh. + } else { + return this.location.assign(url); + } + if (options.trigger) return this.loadUrl(fragment); + }, + + // Update the hash location, either replacing the current entry, or adding + // a new one to the browser history. + _updateHash: function(location, fragment, replace) { + if (replace) { + var href = location.href.replace(/(javascript:|#).*$/, ''); + location.replace(href + '#' + fragment); + } else { + // Some browsers require that `hash` contains a leading #. + location.hash = '#' + fragment; + } + } + + }); + + // Create the default Backbone.history. + Backbone.history = new History; + + // Helpers + // ------- + + // Helper function to correctly set up the prototype chain for subclasses. + // Similar to `goog.inherits`, but uses a hash of prototype properties and + // class properties to be extended. + var extend = function(protoProps, staticProps) { + var parent = this; + var child; + + // The constructor function for the new subclass is either defined by you + // (the "constructor" property in your `extend` definition), or defaulted + // by us to simply call the parent constructor. + if (protoProps && _.has(protoProps, 'constructor')) { + child = protoProps.constructor; + } else { + child = function(){ return parent.apply(this, arguments); }; + } + + // Add static properties to the constructor function, if supplied. + _.extend(child, parent, staticProps); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function and add the prototype properties. + child.prototype = _.create(parent.prototype, protoProps); + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is needed + // later. + child.__super__ = parent.prototype; + + return child; + }; + + // Set up inheritance for the model, collection, router, view and history. + Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend; + + // Throw an error when a URL is needed, and none is supplied. + var urlError = function() { + throw new Error('A "url" property or function must be specified'); + }; + + // Wrap an optional error callback with a fallback error event. + var wrapError = function(model, options) { + var error = options.error; + options.error = function(resp) { + if (error) error.call(options.context, model, resp, options); + model.trigger('error', model, resp, options); + }; + }; + + return Backbone; +}); diff --git a/packages/backbone.marionette.js b/packages/backbone.marionette.js new file mode 100644 index 00000000..d95ecfee --- /dev/null +++ b/packages/backbone.marionette.js @@ -0,0 +1,203 @@ +(function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?factory(exports,require('backbone'),require('underscore'),require('backbone.radio')):typeof define==='function'&&define.amd?define(['exports','backbone','underscore','backbone.radio'],factory):(global=global||self,(function(){var current=global.Marionette;var exports=global.Marionette={};factory(exports,global.Backbone,global._,global.Backbone.Radio);exports.noConflict=function(){global.Marionette=current;return exports;};}()));}(this,function(exports,Backbone,_,Radio){'use strict';Backbone=Backbone&&Backbone.hasOwnProperty('default')?Backbone['default']:Backbone;_=_&&_.hasOwnProperty('default')?_['default']:_;Radio=Radio&&Radio.hasOwnProperty('default')?Radio['default']:Radio;var version="4.1.2";var proxy=function proxy(method){return function(context){for(var _len=arguments.length,args=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++){args[_key-1]=arguments[_key];} +return method.apply(context,args);};};var extend=Backbone.Model.extend;var normalizeMethods=function normalizeMethods(hash){var _this=this;if(!hash){return;} +return _.reduce(hash,function(normalizedHash,method,name){if(!_.isFunction(method)){method=_this[method];} +if(method){normalizedHash[name]=method;} +return normalizedHash;},{});};var errorProps=['description','fileName','lineNumber','name','message','number','url'];var MarionetteError=extend.call(Error,{urlRoot:"http://marionettejs.com/docs/v".concat(version,"/"),url:'',constructor:function constructor(options){var error=Error.call(this,options.message);_.extend(this,_.pick(error,errorProps),_.pick(options,errorProps));if(Error.captureStackTrace){this.captureStackTrace();} +this.url=this.urlRoot+this.url;},captureStackTrace:function captureStackTrace(){Error.captureStackTrace(this,MarionetteError);},toString:function toString(){return "".concat(this.name,": ").concat(this.message," See: ").concat(this.url);}});function normalizeBindings(context,bindings){if(!_.isObject(bindings)){throw new MarionetteError({message:'Bindings must be an object.',url:'common.html#bindevents'});} +return normalizeMethods.call(context,bindings);} +function bindEvents(entity,bindings){if(!entity||!bindings){return this;} +this.listenTo(entity,normalizeBindings(this,bindings));return this;} +function unbindEvents(entity,bindings){if(!entity){return this;} +if(!bindings){this.stopListening(entity);return this;} +this.stopListening(entity,normalizeBindings(this,bindings));return this;} +function normalizeBindings$1(context,bindings){if(!_.isObject(bindings)){throw new MarionetteError({message:'Bindings must be an object.',url:'common.html#bindrequests'});} +return normalizeMethods.call(context,bindings);} +function bindRequests(channel,bindings){if(!channel||!bindings){return this;} +channel.reply(normalizeBindings$1(this,bindings),this);return this;} +function unbindRequests(channel,bindings){if(!channel){return this;} +if(!bindings){channel.stopReplying(null,null,this);return this;} +channel.stopReplying(normalizeBindings$1(this,bindings));return this;} +var getOption=function getOption(optionName){if(!optionName){return;} +if(this.options&&this.options[optionName]!==undefined){return this.options[optionName];}else{return this[optionName];}};var mergeOptions=function mergeOptions(options,keys){var _this=this;if(!options){return;} +_.each(keys,function(key){var option=options[key];if(option!==undefined){_this[key]=option;}});};function triggerMethodChildren(view,event,shouldTrigger){if(!view._getImmediateChildren){return;} +_.each(view._getImmediateChildren(),function(child){if(!shouldTrigger(child)){return;} +child.triggerMethod(event,child);});} +function shouldTriggerAttach(view){return!view._isAttached;} +function shouldAttach(view){if(!shouldTriggerAttach(view)){return false;} +view._isAttached=true;return true;} +function shouldTriggerDetach(view){return view._isAttached;} +function shouldDetach(view){view._isAttached=false;return true;} +function triggerDOMRefresh(view){if(view._isAttached&&view._isRendered){view.triggerMethod('dom:refresh',view);}} +function triggerDOMRemove(view){if(view._isAttached&&view._isRendered){view.triggerMethod('dom:remove',view);}} +function handleBeforeAttach(){triggerMethodChildren(this,'before:attach',shouldTriggerAttach);} +function handleAttach(){triggerMethodChildren(this,'attach',shouldAttach);triggerDOMRefresh(this);} +function handleBeforeDetach(){triggerMethodChildren(this,'before:detach',shouldTriggerDetach);triggerDOMRemove(this);} +function handleDetach(){triggerMethodChildren(this,'detach',shouldDetach);} +function handleBeforeRender(){triggerDOMRemove(this);} +function handleRender(){triggerDOMRefresh(this);} +function monitorViewEvents(view){if(view._areViewEventsMonitored||view.monitorViewEvents===false){return;} +view._areViewEventsMonitored=true;view.on({'before:attach':handleBeforeAttach,'attach':handleAttach,'before:detach':handleBeforeDetach,'detach':handleDetach,'before:render':handleBeforeRender,'render':handleRender});} +var splitter=/(^|:)(\w)/gi;var methodCache={};function getEventName(match,prefix,eventName){return eventName.toUpperCase();} +var getOnMethodName=function getOnMethodName(event){if(!methodCache[event]){methodCache[event]='on'+event.replace(splitter,getEventName);} +return methodCache[event];};function triggerMethod(event){var methodName=getOnMethodName(event);var method=getOption.call(this,methodName);var result;if(_.isFunction(method)){result=method.apply(this,_.drop(arguments));} +this.trigger.apply(this,arguments);return result;} +var Events={triggerMethod:triggerMethod};var CommonMixin={normalizeMethods:normalizeMethods,_setOptions:function _setOptions(options,classOptions){this.options=_.extend({},_.result(this,'options'),options);this.mergeOptions(options,classOptions);},mergeOptions:mergeOptions,getOption:getOption,bindEvents:bindEvents,unbindEvents:unbindEvents,bindRequests:bindRequests,unbindRequests:unbindRequests,triggerMethod:triggerMethod};_.extend(CommonMixin,Backbone.Events);var DestroyMixin={_isDestroyed:false,isDestroyed:function isDestroyed(){return this._isDestroyed;},destroy:function destroy(options){if(this._isDestroyed){return this;} +this.triggerMethod('before:destroy',this,options);this._isDestroyed=true;this.triggerMethod('destroy',this,options);this.stopListening();return this;}};var RadioMixin={_initRadio:function _initRadio(){var channelName=_.result(this,'channelName');if(!channelName){return;} +if(!Radio){throw new MarionetteError({message:'The dependency "backbone.radio" is missing.',url:'backbone.radio.html#marionette-integration'});} +var channel=this._channel=Radio.channel(channelName);var radioEvents=_.result(this,'radioEvents');this.bindEvents(channel,radioEvents);var radioRequests=_.result(this,'radioRequests');this.bindRequests(channel,radioRequests);this.on('destroy',this._destroyRadio);},_destroyRadio:function _destroyRadio(){this._channel.stopReplying(null,null,this);},getChannel:function getChannel(){return this._channel;}};var ClassOptions=['channelName','radioEvents','radioRequests'];var MarionetteObject=function MarionetteObject(options){this._setOptions(options,ClassOptions);this.cid=_.uniqueId(this.cidPrefix);this._initRadio();this.initialize.apply(this,arguments);};MarionetteObject.extend=extend;_.extend(MarionetteObject.prototype,CommonMixin,DestroyMixin,RadioMixin,{cidPrefix:'mno',initialize:function initialize(){}});var _invoke=_.invokeMap||_.invoke;function getBehaviorClass(options){if(options.behaviorClass){return{BehaviorClass:options.behaviorClass,options:options};} +if(_.isFunction(options)){return{BehaviorClass:options,options:{}};} +throw new MarionetteError({message:'Unable to get behavior class. A Behavior constructor should be passed directly or as behaviorClass property of options',url:'marionette.behavior.html#defining-and-attaching-behaviors'});} +function parseBehaviors(view,behaviors,allBehaviors){return _.reduce(behaviors,function(reducedBehaviors,behaviorDefiniton){var _getBehaviorClass=getBehaviorClass(behaviorDefiniton),BehaviorClass=_getBehaviorClass.BehaviorClass,options=_getBehaviorClass.options;var behavior=new BehaviorClass(options,view);reducedBehaviors.push(behavior);return parseBehaviors(view,_.result(behavior,'behaviors'),reducedBehaviors);},allBehaviors);} +var BehaviorsMixin={_initBehaviors:function _initBehaviors(){this._behaviors=parseBehaviors(this,_.result(this,'behaviors'),[]);},_getBehaviorTriggers:function _getBehaviorTriggers(){var triggers=_invoke(this._behaviors,'_getTriggers');return _.reduce(triggers,function(memo,_triggers){return _.extend(memo,_triggers);},{});},_getBehaviorEvents:function _getBehaviorEvents(){var events=_invoke(this._behaviors,'_getEvents');return _.reduce(events,function(memo,_events){return _.extend(memo,_events);},{});},_proxyBehaviorViewProperties:function _proxyBehaviorViewProperties(){_invoke(this._behaviors,'proxyViewProperties');},_delegateBehaviorEntityEvents:function _delegateBehaviorEntityEvents(){_invoke(this._behaviors,'delegateEntityEvents');},_undelegateBehaviorEntityEvents:function _undelegateBehaviorEntityEvents(){_invoke(this._behaviors,'undelegateEntityEvents');},_destroyBehaviors:function _destroyBehaviors(options){_invoke(this._behaviors,'destroy',options);},_removeBehavior:function _removeBehavior(behavior){if(this._isDestroyed){return;} +this.undelegate(".trig".concat(behavior.cid," .").concat(behavior.cid));this._behaviors=_.without(this._behaviors,behavior);},_bindBehaviorUIElements:function _bindBehaviorUIElements(){_invoke(this._behaviors,'bindUIElements');},_unbindBehaviorUIElements:function _unbindBehaviorUIElements(){_invoke(this._behaviors,'unbindUIElements');},_triggerEventOnBehaviors:function _triggerEventOnBehaviors(eventName,view,options){_invoke(this._behaviors,'triggerMethod',eventName,view,options);}};var DelegateEntityEventsMixin={_delegateEntityEvents:function _delegateEntityEvents(model,collection){if(model){this._modelEvents=_.result(this,'modelEvents');this.bindEvents(model,this._modelEvents);} +if(collection){this._collectionEvents=_.result(this,'collectionEvents');this.bindEvents(collection,this._collectionEvents);}},_undelegateEntityEvents:function _undelegateEntityEvents(model,collection){if(this._modelEvents){this.unbindEvents(model,this._modelEvents);delete this._modelEvents;} +if(this._collectionEvents){this.unbindEvents(collection,this._collectionEvents);delete this._collectionEvents;}},_deleteEntityEventHandlers:function _deleteEntityEventHandlers(){delete this._modelEvents;delete this._collectionEvents;}};var TemplateRenderMixin={_renderTemplate:function _renderTemplate(template){var data=this.mixinTemplateContext(this.serializeData())||{};var html=this._renderHtml(template,data);if(typeof html!=='undefined'){this.attachElContent(html);}},getTemplate:function getTemplate(){return this.template;},mixinTemplateContext:function mixinTemplateContext(serializedData){var templateContext=_.result(this,'templateContext');if(!templateContext){return serializedData;} +if(!serializedData){return templateContext;} +return _.extend({},serializedData,templateContext);},serializeData:function serializeData(){if(this.model){return this.serializeModel();} +if(this.collection){return{items:this.serializeCollection()};}},serializeModel:function serializeModel(){return this.model.attributes;},serializeCollection:function serializeCollection(){return _.map(this.collection.models,function(model){return model.attributes;});},_renderHtml:function _renderHtml(template,data){return template(data);},attachElContent:function attachElContent(html){this.Dom.setContents(this.el,html,this.$el);}};var delegateEventSplitter=/^(\S+)\s*(.*)$/;var getNamespacedEventName=function getNamespacedEventName(eventName,namespace){var match=eventName.match(delegateEventSplitter);return "".concat(match[1],".").concat(namespace," ").concat(match[2]);};var FEATURES={childViewEventPrefix:false,triggersStopPropagation:true,triggersPreventDefault:true,DEV_MODE:false};function isEnabled(name){return!!FEATURES[name];} +function setEnabled(name,state){return FEATURES[name]=state;} +function buildViewTrigger(view,triggerDef){if(_.isString(triggerDef)){triggerDef={event:triggerDef};} +var eventName=triggerDef.event;var shouldPreventDefault=!!triggerDef.preventDefault;if(isEnabled('triggersPreventDefault')){shouldPreventDefault=triggerDef.preventDefault!==false;} +var shouldStopPropagation=!!triggerDef.stopPropagation;if(isEnabled('triggersStopPropagation')){shouldStopPropagation=triggerDef.stopPropagation!==false;} +return function(event){if(shouldPreventDefault){event.preventDefault();} +if(shouldStopPropagation){event.stopPropagation();} +for(var _len=arguments.length,args=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++){args[_key-1]=arguments[_key];} +view.triggerMethod.apply(view,[eventName,view,event].concat(args));};} +var TriggersMixin={_getViewTriggers:function _getViewTriggers(view,triggers){var _this=this;return _.reduce(triggers,function(events,value,key){key=getNamespacedEventName(key,"trig".concat(_this.cid));events[key]=buildViewTrigger(view,value);return events;},{});}};var _normalizeUIKeys=function normalizeUIKeys(hash,ui){return _.reduce(hash,function(memo,val,key){var normalizedKey=_normalizeUIString(key,ui);memo[normalizedKey]=val;return memo;},{});};var uiRegEx=/@ui\.[a-zA-Z-_$0-9]*/g;var _normalizeUIString=function normalizeUIString(uiString,ui){return uiString.replace(uiRegEx,function(r){return ui[r.slice(4)];});};var _normalizeUIValues=function normalizeUIValues(hash,ui,property){_.each(hash,function(val,key){if(_.isString(val)){hash[key]=_normalizeUIString(val,ui);}else if(val){var propertyVal=val[property];if(_.isString(propertyVal)){val[property]=_normalizeUIString(propertyVal,ui);}}});return hash;};var UIMixin={normalizeUIKeys:function normalizeUIKeys(hash){var uiBindings=this._getUIBindings();return _normalizeUIKeys(hash,uiBindings);},normalizeUIString:function normalizeUIString(uiString){var uiBindings=this._getUIBindings();return _normalizeUIString(uiString,uiBindings);},normalizeUIValues:function normalizeUIValues(hash,property){var uiBindings=this._getUIBindings();return _normalizeUIValues(hash,uiBindings,property);},_getUIBindings:function _getUIBindings(){var uiBindings=_.result(this,'_uiBindings');return uiBindings||_.result(this,'ui');},_bindUIElements:function _bindUIElements(){var _this=this;if(!this.ui){return;} +if(!this._uiBindings){this._uiBindings=this.ui;} +var bindings=_.result(this,'_uiBindings');this._ui={};_.each(bindings,function(selector,key){_this._ui[key]=_this.$(selector);});this.ui=this._ui;},_unbindUIElements:function _unbindUIElements(){var _this2=this;if(!this.ui||!this._uiBindings){return;} +_.each(this.ui,function($el,name){delete _this2.ui[name];});this.ui=this._uiBindings;delete this._uiBindings;delete this._ui;},_getUI:function _getUI(name){return this._ui[name];}};function _getEl(el){return el instanceof Backbone.$?el:Backbone.$(el);} +function setDomApi(mixin){this.prototype.Dom=_.extend({},this.prototype.Dom,mixin);return this;} +var DomApi={createBuffer:function createBuffer(){return document.createDocumentFragment();},getDocumentEl:function getDocumentEl(el){return el.ownerDocument.documentElement;},getEl:function getEl(selector){return _getEl(selector);},findEl:function findEl(el,selector){return _getEl(el).find(selector);},hasEl:function hasEl(el,childEl){return el.contains(childEl&&childEl.parentNode);},detachEl:function detachEl(el){var _$el=arguments.length>1&&arguments[1]!==undefined?arguments[1]:_getEl(el);_$el.detach();},replaceEl:function replaceEl(newEl,oldEl){if(newEl===oldEl){return;} +var parent=oldEl.parentNode;if(!parent){return;} +parent.replaceChild(newEl,oldEl);},swapEl:function swapEl(el1,el2){if(el1===el2){return;} +var parent1=el1.parentNode;var parent2=el2.parentNode;if(!parent1||!parent2){return;} +var next1=el1.nextSibling;var next2=el2.nextSibling;parent1.insertBefore(el2,next1);parent2.insertBefore(el1,next2);},setContents:function setContents(el,html){var _$el=arguments.length>2&&arguments[2]!==undefined?arguments[2]:_getEl(el);_$el.html(html);},appendContents:function appendContents(el,contents){var _ref=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{},_ref$_$el=_ref._$el,_$el=_ref$_$el===void 0?_getEl(el):_ref$_$el,_ref$_$contents=_ref._$contents,_$contents=_ref$_$contents===void 0?_getEl(contents):_ref$_$contents;_$el.append(_$contents);},hasContents:function hasContents(el){return!!el&&el.hasChildNodes();},detachContents:function detachContents(el){var _$el=arguments.length>1&&arguments[1]!==undefined?arguments[1]:_getEl(el);_$el.contents().detach();}};var ViewMixin={Dom:DomApi,_isElAttached:function _isElAttached(){return!!this.el&&this.Dom.hasEl(this.Dom.getDocumentEl(this.el),this.el);},supportsRenderLifecycle:true,supportsDestroyLifecycle:true,_isDestroyed:false,isDestroyed:function isDestroyed(){return!!this._isDestroyed;},_isRendered:false,isRendered:function isRendered(){return!!this._isRendered;},_isAttached:false,isAttached:function isAttached(){return!!this._isAttached;},delegateEvents:function delegateEvents(events){this._proxyBehaviorViewProperties();this._buildEventProxies();var combinedEvents=_.extend({},this._getBehaviorEvents(),this._getEvents(events),this._getBehaviorTriggers(),this._getTriggers());Backbone.View.prototype.delegateEvents.call(this,combinedEvents);return this;},_getEvents:function _getEvents(events){if(events){return this.normalizeUIKeys(events);} +if(!this.events){return;} +return this.normalizeUIKeys(_.result(this,'events'));},_getTriggers:function _getTriggers(){if(!this.triggers){return;} +var triggers=this.normalizeUIKeys(_.result(this,'triggers'));return this._getViewTriggers(this,triggers);},delegateEntityEvents:function delegateEntityEvents(){this._delegateEntityEvents(this.model,this.collection);this._delegateBehaviorEntityEvents();return this;},undelegateEntityEvents:function undelegateEntityEvents(){this._undelegateEntityEvents(this.model,this.collection);this._undelegateBehaviorEntityEvents();return this;},destroy:function destroy(options){if(this._isDestroyed||this._isDestroying){return this;} +this._isDestroying=true;var shouldTriggerDetach=this._isAttached&&!this._disableDetachEvents;this.triggerMethod('before:destroy',this,options);if(shouldTriggerDetach){this.triggerMethod('before:detach',this);} +this.unbindUIElements();this._removeElement();if(shouldTriggerDetach){this._isAttached=false;this.triggerMethod('detach',this);} +this._removeChildren();this._isDestroyed=true;this._isRendered=false;this._destroyBehaviors(options);this._deleteEntityEventHandlers();this.triggerMethod('destroy',this,options);this._triggerEventOnBehaviors('destroy',this,options);this.stopListening();return this;},_removeElement:function _removeElement(){this.$el.off().removeData();this.Dom.detachEl(this.el,this.$el);},bindUIElements:function bindUIElements(){this._bindUIElements();this._bindBehaviorUIElements();return this;},unbindUIElements:function unbindUIElements(){this._unbindUIElements();this._unbindBehaviorUIElements();return this;},getUI:function getUI(name){return this._getUI(name);},_buildEventProxies:function _buildEventProxies(){this._childViewEvents=this.normalizeMethods(_.result(this,'childViewEvents'));this._childViewTriggers=_.result(this,'childViewTriggers');this._eventPrefix=this._getEventPrefix();},_getEventPrefix:function _getEventPrefix(){var defaultPrefix=isEnabled('childViewEventPrefix')?'childview':false;var prefix=_.result(this,'childViewEventPrefix',defaultPrefix);return prefix===false?prefix:prefix+':';},_proxyChildViewEvents:function _proxyChildViewEvents(view){if(this._childViewEvents||this._childViewTriggers||this._eventPrefix){this.listenTo(view,'all',this._childViewEventHandler);}},_childViewEventHandler:function _childViewEventHandler(eventName){var childViewEvents=this._childViewEvents;for(var _len=arguments.length,args=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++){args[_key-1]=arguments[_key];} +if(childViewEvents&&childViewEvents[eventName]){childViewEvents[eventName].apply(this,args);} +var childViewTriggers=this._childViewTriggers;if(childViewTriggers&&childViewTriggers[eventName]){this.triggerMethod.apply(this,[childViewTriggers[eventName]].concat(args));} +if(this._eventPrefix){this.triggerMethod.apply(this,[this._eventPrefix+eventName].concat(args));}}};_.extend(ViewMixin,BehaviorsMixin,CommonMixin,DelegateEntityEventsMixin,TemplateRenderMixin,TriggersMixin,UIMixin);function renderView(view){if(view._isRendered){return;} +if(!view.supportsRenderLifecycle){view.triggerMethod('before:render',view);} +view.render();view._isRendered=true;if(!view.supportsRenderLifecycle){view.triggerMethod('render',view);}} +function destroyView(view,disableDetachEvents){if(view.destroy){view._disableDetachEvents=disableDetachEvents;view.destroy();return;} +if(!view.supportsDestroyLifecycle){view.triggerMethod('before:destroy',view);} +var shouldTriggerDetach=view._isAttached&&!disableDetachEvents;if(shouldTriggerDetach){view.triggerMethod('before:detach',view);} +view.remove();if(shouldTriggerDetach){view._isAttached=false;view.triggerMethod('detach',view);} +view._isDestroyed=true;if(!view.supportsDestroyLifecycle){view.triggerMethod('destroy',view);}} +var classErrorName='RegionError';var ClassOptions$1=['allowMissingEl','parentEl','replaceElement'];var Region=function Region(options){this._setOptions(options,ClassOptions$1);this.cid=_.uniqueId(this.cidPrefix);this._initEl=this.el=this.getOption('el');this.el=this.el instanceof Backbone.$?this.el[0]:this.el;this.$el=this._getEl(this.el);this.initialize.apply(this,arguments);};Region.extend=extend;Region.setDomApi=setDomApi;_.extend(Region.prototype,CommonMixin,{Dom:DomApi,cidPrefix:'mnr',replaceElement:false,_isReplaced:false,_isSwappingView:false,initialize:function initialize(){},show:function show(view,options){if(!this._ensureElement(options)){return;} +view=this._getView(view,options);if(view===this.currentView){return this;} +if(view._isShown){throw new MarionetteError({name:classErrorName,message:'View is already shown in a Region or CollectionView',url:'marionette.region.html#showing-a-view'});} +this._isSwappingView=!!this.currentView;this.triggerMethod('before:show',this,view,options);if(this.currentView||!view._isAttached){this.empty(options);} +this._setupChildView(view);this.currentView=view;renderView(view);this._attachView(view,options);this.triggerMethod('show',this,view,options);this._isSwappingView=false;return this;},_getEl:function _getEl(el){if(!el){throw new MarionetteError({name:classErrorName,message:'An "el" must be specified for a region.',url:'marionette.region.html#additional-options'});} +return this.getEl(el);},_setEl:function _setEl(){this.$el=this._getEl(this.el);if(this.$el.length){this.el=this.$el[0];} +if(this.$el.length>1){this.$el=this.Dom.getEl(this.el);}},_setElement:function _setElement(el){if(el===this.el){return this;} +var shouldReplace=this._isReplaced;this._restoreEl();this.el=el;this._setEl();if(this.currentView){var view=this.currentView;if(shouldReplace){this._replaceEl(view);}else{this.attachHtml(view);}} +return this;},_setupChildView:function _setupChildView(view){monitorViewEvents(view);this._proxyChildViewEvents(view);view.on('destroy',this._empty,this);},_proxyChildViewEvents:function _proxyChildViewEvents(view){var parentView=this._parentView;if(!parentView){return;} +parentView._proxyChildViewEvents(view);},_shouldDisableMonitoring:function _shouldDisableMonitoring(){return this._parentView&&this._parentView.monitorViewEvents===false;},_isElAttached:function _isElAttached(){return this.Dom.hasEl(this.Dom.getDocumentEl(this.el),this.el);},_attachView:function _attachView(view){var _ref=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{},replaceElement=_ref.replaceElement;var shouldTriggerAttach=!view._isAttached&&this._isElAttached()&&!this._shouldDisableMonitoring();var shouldReplaceEl=typeof replaceElement==='undefined'?!!_.result(this,'replaceElement'):!!replaceElement;if(shouldTriggerAttach){view.triggerMethod('before:attach',view);} +if(shouldReplaceEl){this._replaceEl(view);}else{this.attachHtml(view);} +if(shouldTriggerAttach){view._isAttached=true;view.triggerMethod('attach',view);} +view._isShown=true;},_ensureElement:function _ensureElement(){var options=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{};if(!_.isObject(this.el)){this._setEl();} +if(!this.$el||this.$el.length===0){var allowMissingEl=typeof options.allowMissingEl==='undefined'?!!_.result(this,'allowMissingEl'):!!options.allowMissingEl;if(allowMissingEl){return false;}else{throw new MarionetteError({name:classErrorName,message:"An \"el\" must exist in DOM for this region ".concat(this.cid),url:'marionette.region.html#additional-options'});}} +return true;},_getView:function _getView(view){if(!view){throw new MarionetteError({name:classErrorName,message:'The view passed is undefined and therefore invalid. You must pass a view instance to show.',url:'marionette.region.html#showing-a-view'});} +if(view._isDestroyed){throw new MarionetteError({name:classErrorName,message:"View (cid: \"".concat(view.cid,"\") has already been destroyed and cannot be used."),url:'marionette.region.html#showing-a-view'});} +if(view instanceof Backbone.View){return view;} +var viewOptions=this._getViewOptions(view);return new View(viewOptions);},_getViewOptions:function _getViewOptions(viewOptions){if(_.isFunction(viewOptions)){return{template:viewOptions};} +if(_.isObject(viewOptions)){return viewOptions;} +var template=function template(){return viewOptions;};return{template:template};},getEl:function getEl(el){var context=_.result(this,'parentEl');if(context&&_.isString(el)){return this.Dom.findEl(context,el);} +return this.Dom.getEl(el);},_replaceEl:function _replaceEl(view){this._restoreEl();view.on('before:destroy',this._restoreEl,this);this.Dom.replaceEl(view.el,this.el);this._isReplaced=true;},_restoreEl:function _restoreEl(){if(!this._isReplaced){return;} +var view=this.currentView;if(!view){return;} +this._detachView(view);this._isReplaced=false;},isReplaced:function isReplaced(){return!!this._isReplaced;},isSwappingView:function isSwappingView(){return!!this._isSwappingView;},attachHtml:function attachHtml(view){this.Dom.appendContents(this.el,view.el,{_$el:this.$el,_$contents:view.$el});},empty:function empty(){var options=arguments.length>0&&arguments[0]!==undefined?arguments[0]:{allowMissingEl:true};var view=this.currentView;if(!view){if(this._ensureElement(options)){this.detachHtml();} +return this;} +this._empty(view,true);return this;},_empty:function _empty(view,shouldDestroy){view.off('destroy',this._empty,this);this.triggerMethod('before:empty',this,view);this._restoreEl();delete this.currentView;if(!view._isDestroyed){if(shouldDestroy){this.removeView(view);}else{this._detachView(view);} +view._isShown=false;this._stopChildViewEvents(view);} +this.triggerMethod('empty',this,view);},_stopChildViewEvents:function _stopChildViewEvents(view){var parentView=this._parentView;if(!parentView){return;} +this._parentView.stopListening(view);},destroyView:function destroyView$1(view){if(view._isDestroyed){return view;} +destroyView(view,this._shouldDisableMonitoring());return view;},removeView:function removeView(view){this.destroyView(view);},detachView:function detachView(){var view=this.currentView;if(!view){return;} +this._empty(view);return view;},_detachView:function _detachView(view){var shouldTriggerDetach=view._isAttached&&!this._shouldDisableMonitoring();var shouldRestoreEl=this._isReplaced;if(shouldTriggerDetach){view.triggerMethod('before:detach',view);} +if(shouldRestoreEl){this.Dom.replaceEl(this.el,view.el);}else{this.detachHtml();} +if(shouldTriggerDetach){view._isAttached=false;view.triggerMethod('detach',view);}},detachHtml:function detachHtml(){this.Dom.detachContents(this.el,this.$el);},hasView:function hasView(){return!!this.currentView;},reset:function reset(options){this.empty(options);this.el=this._initEl;delete this.$el;return this;},_isDestroyed:false,isDestroyed:function isDestroyed(){return this._isDestroyed;},destroy:function destroy(options){if(this._isDestroyed){return this;} +this.triggerMethod('before:destroy',this,options);this._isDestroyed=true;this.reset(options);if(this._name){this._parentView._removeReferences(this._name);} +delete this._parentView;delete this._name;this.triggerMethod('destroy',this,options);this.stopListening();return this;}});function buildRegion(definition,defaults){if(definition instanceof Region){return definition;} +if(_.isString(definition)){return buildRegionFromObject(defaults,{el:definition});} +if(_.isFunction(definition)){return buildRegionFromObject(defaults,{regionClass:definition});} +if(_.isObject(definition)){return buildRegionFromObject(defaults,definition);} +throw new MarionetteError({message:'Improper region configuration type.',url:'marionette.region.html#defining-regions'});} +function buildRegionFromObject(defaults,definition){var options=_.extend({},defaults,definition);var RegionClass=options.regionClass;delete options.regionClass;return new RegionClass(options);} +var RegionsMixin={regionClass:Region,_initRegions:function _initRegions(){this.regions=this.regions||{};this._regions={};this.addRegions(_.result(this,'regions'));},_reInitRegions:function _reInitRegions(){_invoke(this._regions,'reset');},addRegion:function addRegion(name,definition){var regions={};regions[name]=definition;return this.addRegions(regions)[name];},addRegions:function addRegions(regions){if(_.isEmpty(regions)){return;} +regions=this.normalizeUIValues(regions,'el');this.regions=_.extend({},this.regions,regions);return this._addRegions(regions);},_addRegions:function _addRegions(regionDefinitions){var _this=this;var defaults={regionClass:this.regionClass,parentEl:_.partial(_.result,this,'el')};return _.reduce(regionDefinitions,function(regions,definition,name){regions[name]=buildRegion(definition,defaults);_this._addRegion(regions[name],name);return regions;},{});},_addRegion:function _addRegion(region,name){this.triggerMethod('before:add:region',this,name,region);region._parentView=this;region._name=name;this._regions[name]=region;this.triggerMethod('add:region',this,name,region);},removeRegion:function removeRegion(name){var region=this._regions[name];this._removeRegion(region,name);return region;},removeRegions:function removeRegions(){var regions=this._getRegions();_.each(this._regions,this._removeRegion.bind(this));return regions;},_removeRegion:function _removeRegion(region,name){this.triggerMethod('before:remove:region',this,name,region);region.destroy();this.triggerMethod('remove:region',this,name,region);},_removeReferences:function _removeReferences(name){delete this.regions[name];delete this._regions[name];},emptyRegions:function emptyRegions(){var regions=this.getRegions();_invoke(regions,'empty');return regions;},hasRegion:function hasRegion(name){return!!this.getRegion(name);},getRegion:function getRegion(name){if(!this._isRendered){this.render();} +return this._regions[name];},_getRegions:function _getRegions(){return _.clone(this._regions);},getRegions:function getRegions(){if(!this._isRendered){this.render();} +return this._getRegions();},showChildView:function showChildView(name,view,options){var region=this.getRegion(name);region.show(view,options);return view;},detachChildView:function detachChildView(name){return this.getRegion(name).detachView();},getChildView:function getChildView(name){return this.getRegion(name).currentView;}};function setRenderer(renderer){this.prototype._renderHtml=renderer;return this;} +var ClassOptions$2=['behaviors','childViewEventPrefix','childViewEvents','childViewTriggers','collectionEvents','events','modelEvents','regionClass','regions','template','templateContext','triggers','ui'];function childReducer(children,region){if(region.currentView){children.push(region.currentView);} +return children;} +var View=Backbone.View.extend({constructor:function constructor(options){this._setOptions(options,ClassOptions$2);monitorViewEvents(this);this._initBehaviors();this._initRegions();Backbone.View.prototype.constructor.apply(this,arguments);this.delegateEntityEvents();this._triggerEventOnBehaviors('initialize',this,options);},setElement:function setElement(){Backbone.View.prototype.setElement.apply(this,arguments);this._isRendered=this.Dom.hasContents(this.el);this._isAttached=this._isElAttached();if(this._isRendered){this.bindUIElements();} +return this;},render:function render(){var template=this.getTemplate();if(template===false||this._isDestroyed){return this;} +this.triggerMethod('before:render',this);if(this._isRendered){this._reInitRegions();} +this._renderTemplate(template);this.bindUIElements();this._isRendered=true;this.triggerMethod('render',this);return this;},_removeChildren:function _removeChildren(){this.removeRegions();},_getImmediateChildren:function _getImmediateChildren(){return _.reduce(this._regions,childReducer,[]);}},{setRenderer:setRenderer,setDomApi:setDomApi});_.extend(View.prototype,ViewMixin,RegionsMixin);var Container=function Container(){this._init();};var methods=['forEach','each','map','find','detect','filter','select','reject','every','all','some','any','include','contains','invoke','toArray','first','initial','rest','last','without','isEmpty','pluck','reduce','partition'];_.each(methods,function(method){Container.prototype[method]=function(){for(var _len=arguments.length,args=new Array(_len),_key=0;_key<_len;_key++){args[_key]=arguments[_key];} +return _[method].apply(_,[this._views].concat(args));};});function stringComparator(comparator,view){return view.model&&view.model.get(comparator);} +_.extend(Container.prototype,{_init:function _init(){this._views=[];this._viewsByCid={};this._indexByModel={};this._updateLength();},_add:function _add(view){var index=arguments.length>1&&arguments[1]!==undefined?arguments[1]:this._views.length;this._addViewIndexes(view);this._views.splice(index,0,view);this._updateLength();},_addViewIndexes:function _addViewIndexes(view){this._viewsByCid[view.cid]=view;if(view.model){this._indexByModel[view.model.cid]=view;}},_sort:function _sort(comparator,context){if(typeof comparator==='string'){comparator=_.partial(stringComparator,comparator);return this._sortBy(comparator);} +if(comparator.length===1){return this._sortBy(comparator.bind(context));} +return this._views.sort(comparator.bind(context));},_sortBy:function _sortBy(comparator){var sortedViews=_.sortBy(this._views,comparator);this._set(sortedViews);return sortedViews;},_set:function _set(views,shouldReset){this._views.length=0;this._views.push.apply(this._views,views.slice(0));if(shouldReset){this._viewsByCid={};this._indexByModel={};_.each(views,this._addViewIndexes.bind(this));this._updateLength();}},_swap:function _swap(view1,view2){var view1Index=this.findIndexByView(view1);var view2Index=this.findIndexByView(view2);if(view1Index===-1||view2Index===-1){return;} +var swapView=this._views[view1Index];this._views[view1Index]=this._views[view2Index];this._views[view2Index]=swapView;},findByModel:function findByModel(model){return this.findByModelCid(model.cid);},findByModelCid:function findByModelCid(modelCid){return this._indexByModel[modelCid];},findByIndex:function findByIndex(index){return this._views[index];},findIndexByView:function findIndexByView(view){return this._views.indexOf(view);},findByCid:function findByCid(cid){return this._viewsByCid[cid];},hasView:function hasView(view){return!!this.findByCid(view.cid);},_remove:function _remove(view){if(!this._viewsByCid[view.cid]){return;} +if(view.model){delete this._indexByModel[view.model.cid];} +delete this._viewsByCid[view.cid];var index=this.findIndexByView(view);this._views.splice(index,1);this._updateLength();},_updateLength:function _updateLength(){this.length=this._views.length;}});var classErrorName$1='CollectionViewError';var ClassOptions$3=['behaviors','childView','childViewContainer','childViewEventPrefix','childViewEvents','childViewOptions','childViewTriggers','collectionEvents','emptyView','emptyViewOptions','events','modelEvents','sortWithCollection','template','templateContext','triggers','ui','viewComparator','viewFilter'];var CollectionView=Backbone.View.extend({sortWithCollection:true,constructor:function constructor(options){this._setOptions(options,ClassOptions$3);monitorViewEvents(this);this._initChildViewStorage();this._initBehaviors();Backbone.View.prototype.constructor.apply(this,arguments);this.getEmptyRegion();this.delegateEntityEvents();this._triggerEventOnBehaviors('initialize',this,options);},_initChildViewStorage:function _initChildViewStorage(){this._children=new Container();this.children=new Container();},getEmptyRegion:function getEmptyRegion(){var $emptyEl=this.$container||this.$el;if(this._emptyRegion&&!this._emptyRegion.isDestroyed()){this._emptyRegion._setElement($emptyEl[0]);return this._emptyRegion;} +this._emptyRegion=new Region({el:$emptyEl[0],replaceElement:false});this._emptyRegion._parentView=this;return this._emptyRegion;},_initialEvents:function _initialEvents(){if(this._isRendered){return;} +this.listenTo(this.collection,{'sort':this._onCollectionSort,'reset':this._onCollectionReset,'update':this._onCollectionUpdate});},_onCollectionSort:function _onCollectionSort(collection,_ref){var add=_ref.add,merge=_ref.merge,remove=_ref.remove;if(!this.sortWithCollection||this.viewComparator===false){return;} +if(add||remove||merge){return;} +this.sort();},_onCollectionReset:function _onCollectionReset(){this._destroyChildren();this._addChildModels(this.collection.models);this.sort();},_onCollectionUpdate:function _onCollectionUpdate(collection,options){var changes=options.changes;var removedViews=changes.removed.length&&this._removeChildModels(changes.removed);this._addedViews=changes.added.length&&this._addChildModels(changes.added);this._detachChildren(removedViews);this.sort();this._removeChildViews(removedViews);},_removeChildModels:function _removeChildModels(models){var _this=this;return _.reduce(models,function(views,model){var removeView=_this._removeChildModel(model);if(removeView){views.push(removeView);} +return views;},[]);},_removeChildModel:function _removeChildModel(model){var view=this._children.findByModel(model);if(view){this._removeChild(view);} +return view;},_removeChild:function _removeChild(view){this.triggerMethod('before:remove:child',this,view);this.children._remove(view);this._children._remove(view);this.triggerMethod('remove:child',this,view);},_addChildModels:function _addChildModels(models){return _.map(models,this._addChildModel.bind(this));},_addChildModel:function _addChildModel(model){var view=this._createChildView(model);this._addChild(view);return view;},_createChildView:function _createChildView(model){var ChildView=this._getChildView(model);var childViewOptions=this._getChildViewOptions(model);var view=this.buildChildView(model,ChildView,childViewOptions);return view;},_addChild:function _addChild(view,index){this.triggerMethod('before:add:child',this,view);this._setupChildView(view);this._children._add(view,index);this.children._add(view,index);this.triggerMethod('add:child',this,view);},_getChildView:function _getChildView(child){var childView=this.childView;if(!childView){throw new MarionetteError({name:classErrorName$1,message:'A "childView" must be specified',url:'marionette.collectionview.html#collectionviews-childview'});} +childView=this._getView(childView,child);if(!childView){throw new MarionetteError({name:classErrorName$1,message:'"childView" must be a view class or a function that returns a view class',url:'marionette.collectionview.html#collectionviews-childview'});} +return childView;},_getView:function _getView(view,child){if(view.prototype instanceof Backbone.View||view===Backbone.View){return view;}else if(_.isFunction(view)){return view.call(this,child);}},_getChildViewOptions:function _getChildViewOptions(child){if(_.isFunction(this.childViewOptions)){return this.childViewOptions(child);} +return this.childViewOptions;},buildChildView:function buildChildView(child,ChildViewClass,childViewOptions){var options=_.extend({model:child},childViewOptions);return new ChildViewClass(options);},_setupChildView:function _setupChildView(view){monitorViewEvents(view);view.on('destroy',this.removeChildView,this);this._proxyChildViewEvents(view);},_getImmediateChildren:function _getImmediateChildren(){return this.children._views;},setElement:function setElement(){Backbone.View.prototype.setElement.apply(this,arguments);this._isAttached=this._isElAttached();return this;},render:function render(){if(this._isDestroyed){return this;} +this.triggerMethod('before:render',this);this._destroyChildren();if(this.collection){this._addChildModels(this.collection.models);this._initialEvents();} +var template=this.getTemplate();if(template){this._renderTemplate(template);this.bindUIElements();} +this._getChildViewContainer();this.sort();this._isRendered=true;this.triggerMethod('render',this);return this;},_getChildViewContainer:function _getChildViewContainer(){var childViewContainer=_.result(this,'childViewContainer');this.$container=childViewContainer?this.$(childViewContainer):this.$el;if(!this.$container.length){throw new MarionetteError({name:classErrorName$1,message:"The specified \"childViewContainer\" was not found: ".concat(childViewContainer),url:'marionette.collectionview.html#defining-the-childviewcontainer'});}},sort:function sort(){this._sortChildren();this.filter();return this;},_sortChildren:function _sortChildren(){if(!this._children.length){return;} +var viewComparator=this.getComparator();if(!viewComparator){return;} +delete this._addedViews;this.triggerMethod('before:sort',this);this._children._sort(viewComparator,this);this.triggerMethod('sort',this);},setComparator:function setComparator(comparator){var _ref2=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{},preventRender=_ref2.preventRender;var comparatorChanged=this.viewComparator!==comparator;var shouldSort=comparatorChanged&&!preventRender;this.viewComparator=comparator;if(shouldSort){this.sort();} +return this;},removeComparator:function removeComparator(options){return this.setComparator(null,options);},getComparator:function getComparator(){if(this.viewComparator){return this.viewComparator;} +if(!this.sortWithCollection||this.viewComparator===false||!this.collection){return false;} +return this._viewComparator;},_viewComparator:function _viewComparator(view){return this.collection.indexOf(view.model);},filter:function filter(){if(this._isDestroyed){return this;} +this._filterChildren();this._renderChildren();return this;},_filterChildren:function _filterChildren(){var _this2=this;if(!this._children.length){return;} +var viewFilter=this._getFilter();if(!viewFilter){var shouldReset=this.children.length!==this._children.length;this.children._set(this._children._views,shouldReset);return;} +delete this._addedViews;this.triggerMethod('before:filter',this);var attachViews=[];var detachViews=[];_.each(this._children._views,function(view,key,children){(viewFilter.call(_this2,view,key,children)?attachViews:detachViews).push(view);});this._detachChildren(detachViews);this.children._set(attachViews,true);this.triggerMethod('filter',this,attachViews,detachViews);},_getFilter:function _getFilter(){var viewFilter=this.getFilter();if(!viewFilter){return false;} +if(_.isFunction(viewFilter)){return viewFilter;} +if(_.isObject(viewFilter)){var matcher=_.matches(viewFilter);return function(view){return matcher(view.model&&view.model.attributes);};} +if(_.isString(viewFilter)){return function(view){return view.model&&view.model.get(viewFilter);};} +throw new MarionetteError({name:classErrorName$1,message:'"viewFilter" must be a function, predicate object literal, a string indicating a model attribute, or falsy',url:'marionette.collectionview.html#defining-the-viewfilter'});},getFilter:function getFilter(){return this.viewFilter;},setFilter:function setFilter(filter){var _ref3=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{},preventRender=_ref3.preventRender;var filterChanged=this.viewFilter!==filter;var shouldRender=filterChanged&&!preventRender;this.viewFilter=filter;if(shouldRender){this.filter();} +return this;},removeFilter:function removeFilter(options){return this.setFilter(null,options);},_detachChildren:function _detachChildren(detachingViews){_.each(detachingViews,this._detachChildView.bind(this));},_detachChildView:function _detachChildView(view){var shouldTriggerDetach=view._isAttached&&this.monitorViewEvents!==false;if(shouldTriggerDetach){view.triggerMethod('before:detach',view);} +this.detachHtml(view);if(shouldTriggerDetach){view._isAttached=false;view.triggerMethod('detach',view);} +view._isShown=false;},detachHtml:function detachHtml(view){this.Dom.detachEl(view.el,view.$el);},_renderChildren:function _renderChildren(){if(this._hasUnrenderedViews){delete this._addedViews;delete this._hasUnrenderedViews;} +var views=this._addedViews||this.children._views;this.triggerMethod('before:render:children',this,views);if(this.isEmpty()){this._showEmptyView();}else{this._destroyEmptyView();var els=this._getBuffer(views);this._attachChildren(els,views);} +delete this._addedViews;this.triggerMethod('render:children',this,views);},_getBuffer:function _getBuffer(views){var _this3=this;var elBuffer=this.Dom.createBuffer();_.each(views,function(view){renderView(view);view._isShown=true;_this3.Dom.appendContents(elBuffer,view.el,{_$contents:view.$el});});return elBuffer;},_attachChildren:function _attachChildren(els,views){var shouldTriggerAttach=this._isAttached&&this.monitorViewEvents!==false;views=shouldTriggerAttach?views:[];_.each(views,function(view){if(view._isAttached){return;} +view.triggerMethod('before:attach',view);});this.attachHtml(els,this.$container);_.each(views,function(view){if(view._isAttached){return;} +view._isAttached=true;view.triggerMethod('attach',view);});},attachHtml:function attachHtml(els,$container){this.Dom.appendContents($container[0],els,{_$el:$container});},isEmpty:function isEmpty(){return!this.children.length;},_showEmptyView:function _showEmptyView(){var EmptyView=this._getEmptyView();if(!EmptyView){return;} +var options=this._getEmptyViewOptions();var emptyRegion=this.getEmptyRegion();emptyRegion.show(new EmptyView(options));},_getEmptyView:function _getEmptyView(){var emptyView=this.emptyView;if(!emptyView){return;} +return this._getView(emptyView);},_destroyEmptyView:function _destroyEmptyView(){var emptyRegion=this.getEmptyRegion();if(emptyRegion.hasView()){emptyRegion.empty();}},_getEmptyViewOptions:function _getEmptyViewOptions(){var emptyViewOptions=this.emptyViewOptions||this.childViewOptions;if(_.isFunction(emptyViewOptions)){return emptyViewOptions.call(this);} +return emptyViewOptions;},swapChildViews:function swapChildViews(view1,view2){if(!this._children.hasView(view1)||!this._children.hasView(view2)){throw new MarionetteError({name:classErrorName$1,message:'Both views must be children of the collection view to swap.',url:'marionette.collectionview.html#swapping-child-views'});} +this._children._swap(view1,view2);this.Dom.swapEl(view1.el,view2.el);if(this.children.hasView(view1)!==this.children.hasView(view2)){this.filter();}else{this.children._swap(view1,view2);} +return this;},addChildView:function addChildView(view,index){var options=arguments.length>2&&arguments[2]!==undefined?arguments[2]:{};if(!view||view._isDestroyed){return view;} +if(view._isShown){throw new MarionetteError({name:classErrorName$1,message:'View is already shown in a Region or CollectionView',url:'marionette.region.html#showing-a-view'});} +if(_.isObject(index)){options=index;} +if(options.index!=null){index=options.index;} +if(!this._isRendered){this.render();} +this._addChild(view,index);if(options.preventRender){this._hasUnrenderedViews=true;return view;} +var hasIndex=typeof index!=='undefined';var isAddedToEnd=!hasIndex||index>=this._children.length;if(isAddedToEnd&&!this._hasUnrenderedViews){this._addedViews=[view];} +if(hasIndex){this._renderChildren();}else{this.sort();} +return view;},detachChildView:function detachChildView(view){this.removeChildView(view,{shouldDetach:true});return view;},removeChildView:function removeChildView(view,options){if(!view){return view;} +this._removeChildView(view,options);this._removeChild(view);if(this.isEmpty()){this._showEmptyView();} +return view;},_removeChildViews:function _removeChildViews(views){_.each(views,this._removeChildView.bind(this));},_removeChildView:function _removeChildView(view){var _ref4=arguments.length>1&&arguments[1]!==undefined?arguments[1]:{},shouldDetach=_ref4.shouldDetach;view.off('destroy',this.removeChildView,this);if(shouldDetach){this._detachChildView(view);}else{this._destroyChildView(view);} +this.stopListening(view);},_destroyChildView:function _destroyChildView(view){if(view._isDestroyed){return;} +var shouldDisableEvents=this.monitorViewEvents===false;destroyView(view,shouldDisableEvents);},_removeChildren:function _removeChildren(){this._destroyChildren();var emptyRegion=this.getEmptyRegion();emptyRegion.destroy();delete this._addedViews;},_destroyChildren:function _destroyChildren(){if(!this._children.length){return;} +this.triggerMethod('before:destroy:children',this);if(this.monitorViewEvents===false){this.Dom.detachContents(this.el,this.$el);} +this._removeChildViews(this._children._views);this._children._init();this.children._init();this.triggerMethod('destroy:children',this);}},{setDomApi:setDomApi,setRenderer:setRenderer});_.extend(CollectionView.prototype,ViewMixin);var ClassOptions$4=['collectionEvents','events','modelEvents','triggers','ui'];var Behavior=function Behavior(options,view){this.view=view;this._setOptions(options,ClassOptions$4);this.cid=_.uniqueId(this.cidPrefix);this.ui=_.extend({},_.result(this,'ui'),_.result(view,'ui'));this.listenTo(view,'all',this.triggerMethod);this.initialize.apply(this,arguments);};Behavior.extend=extend;_.extend(Behavior.prototype,CommonMixin,DelegateEntityEventsMixin,TriggersMixin,UIMixin,{cidPrefix:'mnb',initialize:function initialize(){},$:function $(){return this.view.$.apply(this.view,arguments);},destroy:function destroy(){this.stopListening();this.view._removeBehavior(this);this._deleteEntityEventHandlers();return this;},proxyViewProperties:function proxyViewProperties(){this.$el=this.view.$el;this.el=this.view.el;return this;},bindUIElements:function bindUIElements(){this._bindUIElements();return this;},unbindUIElements:function unbindUIElements(){this._unbindUIElements();return this;},getUI:function getUI(name){return this._getUI(name);},delegateEntityEvents:function delegateEntityEvents(){this._delegateEntityEvents(this.view.model,this.view.collection);return this;},undelegateEntityEvents:function undelegateEntityEvents(){this._undelegateEntityEvents(this.view.model,this.view.collection);return this;},_getEvents:function _getEvents(){var _this=this;if(!this.events){return;} +var behaviorEvents=this.normalizeUIKeys(_.result(this,'events'));return _.reduce(behaviorEvents,function(events,behaviorHandler,key){if(!_.isFunction(behaviorHandler)){behaviorHandler=_this[behaviorHandler];} +if(!behaviorHandler){return events;} +key=getNamespacedEventName(key,_this.cid);events[key]=behaviorHandler.bind(_this);return events;},{});},_getTriggers:function _getTriggers(){if(!this.triggers){return;} +var behaviorTriggers=this.normalizeUIKeys(_.result(this,'triggers'));return this._getViewTriggers(this.view,behaviorTriggers);}});var ClassOptions$5=['channelName','radioEvents','radioRequests','region','regionClass'];var Application=function Application(options){this._setOptions(options,ClassOptions$5);this.cid=_.uniqueId(this.cidPrefix);this._initRegion();this._initRadio();this.initialize.apply(this,arguments);};Application.extend=extend;_.extend(Application.prototype,CommonMixin,DestroyMixin,RadioMixin,{cidPrefix:'mna',initialize:function initialize(){},start:function start(options){this.triggerMethod('before:start',this,options);this.triggerMethod('start',this,options);return this;},regionClass:Region,_initRegion:function _initRegion(){var region=this.region;if(!region){return;} +var defaults={regionClass:this.regionClass};this._region=buildRegion(region,defaults);},getRegion:function getRegion(){return this._region;},showView:function showView(view){var region=this.getRegion();for(var _len=arguments.length,args=new Array(_len>1?_len-1:0),_key=1;_key<_len;_key++){args[_key-1]=arguments[_key];} +region.show.apply(region,[view].concat(args));return view;},getView:function getView(){return this.getRegion().currentView;}});var bindEvents$1=proxy(bindEvents);var unbindEvents$1=proxy(unbindEvents);var bindRequests$1=proxy(bindRequests);var unbindRequests$1=proxy(unbindRequests);var mergeOptions$1=proxy(mergeOptions);var getOption$1=proxy(getOption);var normalizeMethods$1=proxy(normalizeMethods);var triggerMethod$1=proxy(triggerMethod);var setDomApi$1=function setDomApi(mixin){CollectionView.setDomApi(mixin);Region.setDomApi(mixin);View.setDomApi(mixin);};var setRenderer$1=function setRenderer(renderer){CollectionView.setRenderer(renderer);View.setRenderer(renderer);};var backbone_marionette={View:View,CollectionView:CollectionView,MnObject:MarionetteObject,Object:MarionetteObject,Region:Region,Behavior:Behavior,Application:Application,isEnabled:isEnabled,setEnabled:setEnabled,monitorViewEvents:monitorViewEvents,Events:Events,extend:extend,DomApi:DomApi,VERSION:version};exports.Application=Application;exports.Behavior=Behavior;exports.CollectionView=CollectionView;exports.DomApi=DomApi;exports.Events=Events;exports.MnObject=MarionetteObject;exports.Region=Region;exports.VERSION=version;exports.View=View;exports.bindEvents=bindEvents$1;exports.bindRequests=bindRequests$1;exports.default=backbone_marionette;exports.extend=extend;exports.getOption=getOption$1;exports.isEnabled=isEnabled;exports.mergeOptions=mergeOptions$1;exports.monitorViewEvents=monitorViewEvents;exports.normalizeMethods=normalizeMethods$1;exports.setDomApi=setDomApi$1;exports.setEnabled=setEnabled;exports.setRenderer=setRenderer$1;exports.triggerMethod=triggerMethod$1;exports.unbindEvents=unbindEvents$1;exports.unbindRequests=unbindRequests$1;Object.defineProperty(exports,'__esModule',{value:true});}));this&&this.Marionette&&(this.Mn=this.Marionette); \ No newline at end of file diff --git a/packages/backbone.modelbinder.js b/packages/backbone.modelbinder.js new file mode 100644 index 00000000..4c0886b8 --- /dev/null +++ b/packages/backbone.modelbinder.js @@ -0,0 +1,577 @@ +// Backbone.ModelBinder v1.1.1 +// (c) 2020 Bart Wood +// Distributed Under MIT License + +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['underscore', 'jquery', 'backbone'], factory); + } else if(typeof module !== 'undefined' && module.exports) { + // CommonJS + module.exports = factory( + require('underscore'), + require('jquery'), + require('backbone') + ); + } else { + // Browser globals + factory(_, jQuery, Backbone); + } +}(function(_, $, Backbone){ + + if(!Backbone){ + throw new Error('Please include Backbone.js before Backbone.ModelBinder.js'); + } + + Backbone.ModelBinder = function(){ + _.bindAll.apply(_, [this].concat(_.functions(this))); + }; + + // Static setter for class level options + Backbone.ModelBinder.SetOptions = function(options){ + Backbone.ModelBinder.options = options; + }; + + // Current version of the library. + Backbone.ModelBinder.VERSION = '1.1.1'; + Backbone.ModelBinder.Constants = {}; + Backbone.ModelBinder.Constants.ModelToView = 'ModelToView'; + Backbone.ModelBinder.Constants.ViewToModel = 'ViewToModel'; + + _.extend(Backbone.ModelBinder.prototype, { + + bind:function (model, rootEl, attributeBindings, options) { + this.unbind(); + + this._model = model; + this._rootEl = rootEl; + this._setOptions(options); + + if (!this._model) this._throwException('model must be specified'); + if (!this._rootEl) this._throwException('rootEl must be specified'); + + if(attributeBindings){ + // Create a deep clone of the attribute bindings + this._attributeBindings = $.extend(true, {}, attributeBindings); + + this._initializeAttributeBindings(); + this._initializeElBindings(); + } + else { + this._initializeDefaultBindings(); + } + + this._bindModelToView(); + this._bindViewToModel(); + }, + + bindCustomTriggers: function (model, rootEl, triggers, attributeBindings, modelSetOptions) { + this._triggers = triggers; + this.bind(model, rootEl, attributeBindings, modelSetOptions); + }, + + unbind:function () { + this._unbindModelToView(); + this._unbindViewToModel(); + + if(this._attributeBindings){ + delete this._attributeBindings; + this._attributeBindings = undefined; + } + }, + + _setOptions: function(options){ + this._options = _.extend({ + boundAttribute: 'name' + }, Backbone.ModelBinder.options, options); + + // initialize default options + if(!this._options['modelSetOptions']){ + this._options['modelSetOptions'] = {}; + } + this._options['modelSetOptions'].changeSource = 'ModelBinder'; + + if(!this._options['changeTriggers']){ + this._options['changeTriggers'] = {'': 'change', '[contenteditable]': 'blur'}; + } + + if(!this._options['initialCopyDirection']){ + this._options['initialCopyDirection'] = Backbone.ModelBinder.Constants.ModelToView; + } + }, + + // Converts the input bindings, which might just be empty or strings, to binding objects + _initializeAttributeBindings:function () { + var attributeBindingKey, inputBinding, attributeBinding, elementBindingCount, elementBinding; + + for (attributeBindingKey in this._attributeBindings) { + inputBinding = this._attributeBindings[attributeBindingKey]; + + if (_.isString(inputBinding)) { + attributeBinding = {elementBindings: [{selector: inputBinding}]}; + } + else if (_.isArray(inputBinding)) { + attributeBinding = {elementBindings: inputBinding}; + } + else if(_.isObject(inputBinding)){ + attributeBinding = {elementBindings: [inputBinding]}; + } + else { + this._throwException('Unsupported type passed to Model Binder ' + attributeBinding); + } + + // Add a linkage from the element binding back to the attribute binding + for(elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++){ + elementBinding = attributeBinding.elementBindings[elementBindingCount]; + elementBinding.attributeBinding = attributeBinding; + } + + attributeBinding.attributeName = attributeBindingKey; + this._attributeBindings[attributeBindingKey] = attributeBinding; + } + }, + + // If the bindings are not specified, the default binding is performed on the specified attribute, name by default + _initializeDefaultBindings: function(){ + var elCount, elsWithAttribute, matchedEl, name, attributeBinding; + + this._attributeBindings = {}; + elsWithAttribute = $('[' + this._options['boundAttribute'] + ']', this._rootEl); + + for(elCount = 0; elCount < elsWithAttribute.length; elCount++){ + matchedEl = elsWithAttribute[elCount]; + name = $(matchedEl).attr(this._options['boundAttribute']); + + // For elements like radio buttons we only want a single attribute binding with possibly multiple element bindings + if(!this._attributeBindings[name]){ + attributeBinding = {attributeName: name}; + attributeBinding.elementBindings = [{attributeBinding: attributeBinding, boundEls: [matchedEl]}]; + this._attributeBindings[name] = attributeBinding; + } + else{ + this._attributeBindings[name].elementBindings.push({attributeBinding: this._attributeBindings[name], boundEls: [matchedEl]}); + } + } + }, + + _initializeElBindings:function () { + var bindingKey, attributeBinding, bindingCount, elementBinding, foundEls, elCount, el; + for (bindingKey in this._attributeBindings) { + attributeBinding = this._attributeBindings[bindingKey]; + + for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) { + elementBinding = attributeBinding.elementBindings[bindingCount]; + if (elementBinding.selector === '') { + foundEls = $(this._rootEl); + } + else { + foundEls = $(elementBinding.selector, this._rootEl); + } + + if (foundEls.length === 0) { + this._throwException('Bad binding found. No elements returned for binding selector ' + elementBinding.selector); + } + else { + elementBinding.boundEls = []; + for (elCount = 0; elCount < foundEls.length; elCount++) { + el = foundEls[elCount]; + elementBinding.boundEls.push(el); + } + } + } + } + }, + + _bindModelToView: function () { + this._model.on('change', this._onModelChange, this); + + if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ModelToView){ + this.copyModelAttributesToView(); + } + }, + + // attributesToCopy is an optional parameter - if empty, all attributes + // that are bound will be copied. Otherwise, only attributeBindings specified + // in the attributesToCopy are copied. + copyModelAttributesToView: function(attributesToCopy){ + var attributeName, attributeBinding; + + for (attributeName in this._attributeBindings) { + if(attributesToCopy === undefined || _.indexOf(attributesToCopy, attributeName) !== -1){ + attributeBinding = this._attributeBindings[attributeName]; + this._copyModelToView(attributeBinding); + } + } + }, + + copyViewValuesToModel: function(){ + var bindingKey, attributeBinding, bindingCount, elementBinding, elCount, el; + for (bindingKey in this._attributeBindings) { + attributeBinding = this._attributeBindings[bindingKey]; + + for (bindingCount = 0; bindingCount < attributeBinding.elementBindings.length; bindingCount++) { + elementBinding = attributeBinding.elementBindings[bindingCount]; + + if(this._isBindingUserEditable(elementBinding)){ + if(this._isBindingRadioGroup(elementBinding)){ + el = this._getRadioButtonGroupCheckedEl(elementBinding); + if(el){ + this._copyViewToModel(elementBinding, el); + } + } + else { + for(elCount = 0; elCount < elementBinding.boundEls.length; elCount++){ + el = $(elementBinding.boundEls[elCount]); + if(this._isElUserEditable(el)){ + this._copyViewToModel(elementBinding, el); + } + } + } + } + } + } + }, + + _unbindModelToView: function(){ + if(this._model){ + this._model.off('change', this._onModelChange); + this._model = undefined; + } + }, + + _bindViewToModel: function () { + _.each(this._options['changeTriggers'], function (event, selector) { + $(this._rootEl).on(event, selector, this._onElChanged); + }, this); + + if(this._options['initialCopyDirection'] === Backbone.ModelBinder.Constants.ViewToModel){ + this.copyViewValuesToModel(); + } + }, + + _unbindViewToModel: function () { + if(this._options && this._options['changeTriggers']){ + _.each(this._options['changeTriggers'], function (event, selector) { + $(this._rootEl).off(event, selector, this._onElChanged); + }, this); + } + }, + + _onElChanged:function (event) { + var el, elBindings, elBindingCount, elBinding; + + el = $(event.target)[0]; + elBindings = this._getElBindings(el); + + for(elBindingCount = 0; elBindingCount < elBindings.length; elBindingCount++){ + elBinding = elBindings[elBindingCount]; + if (this._isBindingUserEditable(elBinding)) { + this._copyViewToModel(elBinding, el); + } + } + }, + + _isBindingUserEditable: function(elBinding){ + return elBinding.elAttribute === undefined || + elBinding.elAttribute === 'text' || + elBinding.elAttribute === 'html'; + }, + + _isElUserEditable: function(el){ + var isContentEditable = el.attr('contenteditable'); + return isContentEditable || el.is('input') || el.is('select') || el.is('textarea'); + }, + + _isBindingRadioGroup: function(elBinding){ + var elCount, el; + var isAllRadioButtons = elBinding.boundEls.length > 0; + for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){ + el = $(elBinding.boundEls[elCount]); + if(el.attr('type') !== 'radio'){ + isAllRadioButtons = false; + break; + } + } + + return isAllRadioButtons; + }, + + _getRadioButtonGroupCheckedEl: function(elBinding){ + var elCount, el; + for(elCount = 0; elCount < elBinding.boundEls.length; elCount++){ + el = $(elBinding.boundEls[elCount]); + if(el.attr('type') === 'radio' && el.prop('checked')){ + return el; + } + } + + return undefined; + }, + + _getElBindings:function (findEl) { + var attributeName, attributeBinding, elementBindingCount, elementBinding, boundElCount, boundEl; + var elBindings = []; + + for (attributeName in this._attributeBindings) { + attributeBinding = this._attributeBindings[attributeName]; + + for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) { + elementBinding = attributeBinding.elementBindings[elementBindingCount]; + + for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) { + boundEl = elementBinding.boundEls[boundElCount]; + + if (boundEl === findEl) { + elBindings.push(elementBinding); + } + } + } + } + + return elBindings; + }, + + _onModelChange:function () { + var changedAttribute, attributeBinding; + + for (changedAttribute in this._model.changedAttributes()) { + attributeBinding = this._attributeBindings[changedAttribute]; + + if (attributeBinding) { + this._copyModelToView(attributeBinding); + } + } + }, + + _copyModelToView:function (attributeBinding) { + var elementBindingCount, elementBinding, boundElCount, boundEl, value, convertedValue; + + value = this._model.get(attributeBinding.attributeName); + + for (elementBindingCount = 0; elementBindingCount < attributeBinding.elementBindings.length; elementBindingCount++) { + elementBinding = attributeBinding.elementBindings[elementBindingCount]; + + for (boundElCount = 0; boundElCount < elementBinding.boundEls.length; boundElCount++) { + boundEl = elementBinding.boundEls[boundElCount]; + + if(!boundEl._isSetting){ + convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value); + this._setEl($(boundEl), elementBinding, convertedValue); + } + } + } + }, + + _setEl: function (el, elementBinding, convertedValue) { + if (elementBinding.elAttribute) { + this._setElAttribute(el, elementBinding, convertedValue); + } + else { + this._setElValue(el, convertedValue); + } + }, + + _setElAttribute:function (el, elementBinding, convertedValue) { + switch (elementBinding.elAttribute) { + case 'html': + el.html(convertedValue); + break; + case 'text': + el.text(convertedValue); + break; + case 'enabled': + el.prop('disabled', !convertedValue); + break; + case 'displayed': + el[convertedValue ? 'show' : 'hide'](); + break; + case 'hidden': + el[convertedValue ? 'hide' : 'show'](); + break; + case 'css': + el.css(elementBinding.cssAttribute, convertedValue); + break; + case 'class': + var previousValue = this._model.previous(elementBinding.attributeBinding.attributeName); + var currentValue = this._model.get(elementBinding.attributeBinding.attributeName); + // is current value is now defined then remove the class the may have been set for the undefined value + if(!_.isUndefined(previousValue) || !_.isUndefined(currentValue)){ + previousValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, previousValue); + el.removeClass(previousValue); + } + + if(convertedValue){ + el.addClass(convertedValue); + } + break; + default: + el.attr(elementBinding.elAttribute, convertedValue); + } + }, + + _setElValue:function (el, convertedValue) { + if(el.attr('type')){ + switch (el.attr('type')) { + case 'radio': + el.prop('checked', el.val() === convertedValue); + break; + case 'checkbox': + el.prop('checked', !!convertedValue); + break; + case 'file': + break; + default: + el.val(convertedValue); + } + } + else if(el.is('input') || el.is('select') || el.is('textarea')){ + el.val(convertedValue || (convertedValue === 0 ? '0' : '')); + } + else { + el.text(convertedValue || (convertedValue === 0 ? '0' : '')); + } + }, + + _copyViewToModel: function (elementBinding, el) { + var result, value, convertedValue; + + if (!el._isSetting) { + + el._isSetting = true; + result = this._setModel(elementBinding, $(el)); + el._isSetting = false; + + if(result && elementBinding.converter){ + value = this._model.get(elementBinding.attributeBinding.attributeName); + convertedValue = this._getConvertedValue(Backbone.ModelBinder.Constants.ModelToView, elementBinding, value); + this._setEl($(el), elementBinding, convertedValue); + } + } + }, + + _getElValue: function(elementBinding, el){ + switch (el.attr('type')) { + case 'checkbox': + return el.prop('checked') ? true : false; + default: + if(el.attr('contenteditable') !== undefined){ + return el.html(); + } + else { + return el.val(); + } + } + }, + + _setModel: function (elementBinding, el) { + var data = {}; + var elVal = this._getElValue(elementBinding, el); + elVal = this._getConvertedValue(Backbone.ModelBinder.Constants.ViewToModel, elementBinding, elVal); + data[elementBinding.attributeBinding.attributeName] = elVal; + return this._model.set(data, this._options['modelSetOptions']); + }, + + _getConvertedValue: function (direction, elementBinding, value) { + + if (elementBinding.converter) { + value = elementBinding.converter(direction, value, elementBinding.attributeBinding.attributeName, this._model, elementBinding.boundEls); + } + else if(this._options['converter']){ + value = this._options['converter'](direction, value, elementBinding.attributeBinding.attributeName, this._model, elementBinding.boundEls); + } + + return value; + }, + + _throwException: function(message){ + if(this._options.suppressThrows){ + if(typeof(console) !== 'undefined' && console.error){ + console.error(message); + } + } + else { + throw new Error(message); + } + } + }); + + Backbone.ModelBinder.CollectionConverter = function(collection){ + this._collection = collection; + + if(!this._collection){ + throw new Error('Collection must be defined'); + } + _.bindAll(this, 'convert'); + }; + + _.extend(Backbone.ModelBinder.CollectionConverter.prototype, { + convert: function(direction, value){ + if (direction === Backbone.ModelBinder.Constants.ModelToView) { + return value ? value.id : undefined; + } + else { + return this._collection.get(value); + } + } + }); + + // A static helper function to create a default set of bindings that you can customize before calling the bind() function + // rootEl - where to find all of the bound elements + // attributeType - probably 'name' or 'id' in most cases + // converter(optional) - the default converter you want applied to all your bindings + // elAttribute(optional) - the default elAttribute you want applied to all your bindings + Backbone.ModelBinder.createDefaultBindings = function(rootEl, attributeType, converter, elAttribute){ + var foundEls, elCount, foundEl, attributeName; + var bindings = {}; + + foundEls = $('[' + attributeType + ']', rootEl); + + for(elCount = 0; elCount < foundEls.length; elCount++){ + foundEl = foundEls[elCount]; + attributeName = $(foundEl).attr(attributeType); + + if(!bindings[attributeName]){ + var attributeBinding = {selector: '[' + attributeType + '="' + attributeName + '"]'}; + bindings[attributeName] = attributeBinding; + + if(converter){ + bindings[attributeName].converter = converter; + } + + if(elAttribute){ + bindings[attributeName].elAttribute = elAttribute; + } + } + } + + return bindings; + }; + + // Helps you to combine 2 sets of bindings + Backbone.ModelBinder.combineBindings = function(destination, source){ + _.each(source, function(value, key){ + var elementBinding = {selector: value.selector}; + + if(value.converter){ + elementBinding.converter = value.converter; + } + + if(value.elAttribute){ + elementBinding.elAttribute = value.elAttribute; + } + + if(!destination[key]){ + destination[key] = elementBinding; + } + else { + destination[key] = [destination[key], elementBinding]; + } + }); + + return destination; + }; + + + return Backbone.ModelBinder; + +})); From 73030ffdbd7432b8b227fcdd56dc2c822e5e39bb Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 10:20:58 +0200 Subject: [PATCH 04/51] Add json2.js to packages It is included from the civi backbone packages directory, so that is could be missing also. --- packages/json2.js | 530 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 530 insertions(+) create mode 100644 packages/json2.js diff --git a/packages/json2.js b/packages/json2.js new file mode 100644 index 00000000..f6fada68 --- /dev/null +++ b/packages/json2.js @@ -0,0 +1,530 @@ +// json2.js +// 2017-06-12 +// Public Domain. +// NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK. + +// USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO +// NOT CONTROL. + +// This file creates a global JSON object containing two methods: stringify +// and parse. This file provides the ES5 JSON capability to ES3 systems. +// If a project might run on IE8 or earlier, then this file should be included. +// This file does nothing on ES5 systems. + +// JSON.stringify(value, replacer, space) +// value any JavaScript value, usually an object or array. +// replacer an optional parameter that determines how object +// values are stringified for objects. It can be a +// function or an array of strings. +// space an optional parameter that specifies the indentation +// of nested structures. If it is omitted, the text will +// be packed without extra whitespace. If it is a number, +// it will specify the number of spaces to indent at each +// level. If it is a string (such as "\t" or " "), +// it contains the characters used to indent at each level. +// This method produces a JSON text from a JavaScript value. +// When an object value is found, if the object contains a toJSON +// method, its toJSON method will be called and the result will be +// stringified. A toJSON method does not serialize: it returns the +// value represented by the name/value pair that should be serialized, +// or undefined if nothing should be serialized. The toJSON method +// will be passed the key associated with the value, and this will be +// bound to the value. + +// For example, this would serialize Dates as ISO strings. + +// Date.prototype.toJSON = function (key) { +// function f(n) { +// // Format integers to have at least two digits. +// return (n < 10) +// ? "0" + n +// : n; +// } +// return this.getUTCFullYear() + "-" + +// f(this.getUTCMonth() + 1) + "-" + +// f(this.getUTCDate()) + "T" + +// f(this.getUTCHours()) + ":" + +// f(this.getUTCMinutes()) + ":" + +// f(this.getUTCSeconds()) + "Z"; +// }; + +// You can provide an optional replacer method. It will be passed the +// key and value of each member, with this bound to the containing +// object. The value that is returned from your method will be +// serialized. If your method returns undefined, then the member will +// be excluded from the serialization. + +// If the replacer parameter is an array of strings, then it will be +// used to select the members to be serialized. It filters the results +// such that only members with keys listed in the replacer array are +// stringified. + +// Values that do not have JSON representations, such as undefined or +// functions, will not be serialized. Such values in objects will be +// dropped; in arrays they will be replaced with null. You can use +// a replacer function to replace those with JSON values. + +// JSON.stringify(undefined) returns undefined. + +// The optional space parameter produces a stringification of the +// value that is filled with line breaks and indentation to make it +// easier to read. + +// If the space parameter is a non-empty string, then that string will +// be used for indentation. If the space parameter is a number, then +// the indentation will be that many spaces. + +// Example: + +// text = JSON.stringify(["e", {pluribus: "unum"}]); +// // text is '["e",{"pluribus":"unum"}]' + +// text = JSON.stringify(["e", {pluribus: "unum"}], null, "\t"); +// // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]' + +// text = JSON.stringify([new Date()], function (key, value) { +// return this[key] instanceof Date +// ? "Date(" + this[key] + ")" +// : value; +// }); +// // text is '["Date(---current time---)"]' + +// JSON.parse(text, reviver) +// This method parses a JSON text to produce an object or array. +// It can throw a SyntaxError exception. + +// The optional reviver parameter is a function that can filter and +// transform the results. It receives each of the keys and values, +// and its return value is used instead of the original value. +// If it returns what it received, then the structure is not modified. +// If it returns undefined then the member is deleted. + +// Example: + +// // Parse the text. Values that look like ISO date strings will +// // be converted to Date objects. + +// myData = JSON.parse(text, function (key, value) { +// var a; +// if (typeof value === "string") { +// a = +// /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value); +// if (a) { +// return new Date(Date.UTC( +// +a[1], +a[2] - 1, +a[3], +a[4], +a[5], +a[6] +// )); +// } +// return value; +// } +// }); + +// myData = JSON.parse( +// "[\"Date(09/09/2001)\"]", +// function (key, value) { +// var d; +// if ( +// typeof value === "string" +// && value.slice(0, 5) === "Date(" +// && value.slice(-1) === ")" +// ) { +// d = new Date(value.slice(5, -1)); +// if (d) { +// return d; +// } +// } +// return value; +// } +// ); + +// This is a reference implementation. You are free to copy, modify, or +// redistribute. + +/*jslint + eval, for, this +*/ + +/*property + JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours, + getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join, + lastIndex, length, parse, prototype, push, replace, slice, stringify, + test, toJSON, toString, valueOf +*/ + + +// Create a JSON object only if one does not already exist. We create the +// methods in a closure to avoid creating global variables. + +if (typeof JSON !== "object") { + JSON = {}; +} + +(function () { + "use strict"; + + var rx_one = /^[\],:{}\s]*$/; + var rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g; + var rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g; + var rx_four = /(?:^|:|,)(?:\s*\[)+/g; + var rx_escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + var rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + + function f(n) { + // Format integers to have at least two digits. + return (n < 10) + ? "0" + n + : n; + } + + function this_value() { + return this.valueOf(); + } + + if (typeof Date.prototype.toJSON !== "function") { + + Date.prototype.toJSON = function () { + + return isFinite(this.valueOf()) + ? ( + this.getUTCFullYear() + + "-" + + f(this.getUTCMonth() + 1) + + "-" + + f(this.getUTCDate()) + + "T" + + f(this.getUTCHours()) + + ":" + + f(this.getUTCMinutes()) + + ":" + + f(this.getUTCSeconds()) + + "Z" + ) + : null; + }; + + Boolean.prototype.toJSON = this_value; + Number.prototype.toJSON = this_value; + String.prototype.toJSON = this_value; + } + + var gap; + var indent; + var meta; + var rep; + + + function quote(string) { + +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can safely slap some quotes around it. +// Otherwise we must also replace the offending characters with safe escape +// sequences. + + rx_escapable.lastIndex = 0; + return rx_escapable.test(string) + ? "\"" + string.replace(rx_escapable, function (a) { + var c = meta[a]; + return typeof c === "string" + ? c + : "\\u" + ("0000" + a.charCodeAt(0).toString(16)).slice(-4); + }) + "\"" + : "\"" + string + "\""; + } + + + function str(key, holder) { + +// Produce a string from holder[key]. + + var i; // The loop counter. + var k; // The member key. + var v; // The member value. + var length; + var mind = gap; + var partial; + var value = holder[key]; + +// If the value has a toJSON method, call it to obtain a replacement value. + + if ( + value + && typeof value === "object" + && typeof value.toJSON === "function" + ) { + value = value.toJSON(key); + } + +// If we were called with a replacer function, then call the replacer to +// obtain a replacement value. + + if (typeof rep === "function") { + value = rep.call(holder, key, value); + } + +// What happens next depends on the value's type. + + switch (typeof value) { + case "string": + return quote(value); + + case "number": + +// JSON numbers must be finite. Encode non-finite numbers as null. + + return (isFinite(value)) + ? String(value) + : "null"; + + case "boolean": + case "null": + +// If the value is a boolean or null, convert it to a string. Note: +// typeof null does not produce "null". The case is included here in +// the remote chance that this gets fixed someday. + + return String(value); + +// If the type is "object", we might be dealing with an object or an array or +// null. + + case "object": + +// Due to a specification blunder in ECMAScript, typeof null is "object", +// so watch out for that case. + + if (!value) { + return "null"; + } + +// Make an array to hold the partial results of stringifying this object value. + + gap += indent; + partial = []; + +// Is the value an array? + + if (Object.prototype.toString.apply(value) === "[object Array]") { + +// The value is an array. Stringify every element. Use null as a placeholder +// for non-JSON values. + + length = value.length; + for (i = 0; i < length; i += 1) { + partial[i] = str(i, value) || "null"; + } + +// Join all of the elements together, separated with commas, and wrap them in +// brackets. + + v = partial.length === 0 + ? "[]" + : gap + ? ( + "[\n" + + gap + + partial.join(",\n" + gap) + + "\n" + + mind + + "]" + ) + : "[" + partial.join(",") + "]"; + gap = mind; + return v; + } + +// If the replacer is an array, use it to select the members to be stringified. + + if (rep && typeof rep === "object") { + length = rep.length; + for (i = 0; i < length; i += 1) { + if (typeof rep[i] === "string") { + k = rep[i]; + v = str(k, value); + if (v) { + partial.push(quote(k) + ( + (gap) + ? ": " + : ":" + ) + v); + } + } + } + } else { + +// Otherwise, iterate through all of the keys in the object. + + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = str(k, value); + if (v) { + partial.push(quote(k) + ( + (gap) + ? ": " + : ":" + ) + v); + } + } + } + } + +// Join all of the member texts together, separated with commas, +// and wrap them in braces. + + v = partial.length === 0 + ? "{}" + : gap + ? "{\n" + gap + partial.join(",\n" + gap) + "\n" + mind + "}" + : "{" + partial.join(",") + "}"; + gap = mind; + return v; + } + } + +// If the JSON object does not yet have a stringify method, give it one. + + if (typeof JSON.stringify !== "function") { + meta = { // table of character substitutions + "\b": "\\b", + "\t": "\\t", + "\n": "\\n", + "\f": "\\f", + "\r": "\\r", + "\"": "\\\"", + "\\": "\\\\" + }; + JSON.stringify = function (value, replacer, space) { + +// The stringify method takes a value and an optional replacer, and an optional +// space parameter, and returns a JSON text. The replacer can be a function +// that can replace values, or an array of strings that will select the keys. +// A default replacer method can be provided. Use of the space parameter can +// produce text that is more easily readable. + + var i; + gap = ""; + indent = ""; + +// If the space parameter is a number, make an indent string containing that +// many spaces. + + if (typeof space === "number") { + for (i = 0; i < space; i += 1) { + indent += " "; + } + +// If the space parameter is a string, it will be used as the indent string. + + } else if (typeof space === "string") { + indent = space; + } + +// If there is a replacer, it must be a function or an array. +// Otherwise, throw an error. + + rep = replacer; + if (replacer && typeof replacer !== "function" && ( + typeof replacer !== "object" + || typeof replacer.length !== "number" + )) { + throw new Error("JSON.stringify"); + } + +// Make a fake root object containing our value under the key of "". +// Return the result of stringifying the value. + + return str("", {"": value}); + }; + } + + +// If the JSON object does not yet have a parse method, give it one. + + if (typeof JSON.parse !== "function") { + JSON.parse = function (text, reviver) { + +// The parse method takes a text and an optional reviver function, and returns +// a JavaScript value if the text is a valid JSON text. + + var j; + + function walk(holder, key) { + +// The walk method is used to recursively walk the resulting structure so +// that modifications can be made. + + var k; + var v; + var value = holder[key]; + if (value && typeof value === "object") { + for (k in value) { + if (Object.prototype.hasOwnProperty.call(value, k)) { + v = walk(value, k); + if (v !== undefined) { + value[k] = v; + } else { + delete value[k]; + } + } + } + } + return reviver.call(holder, key, value); + } + + +// Parsing happens in four stages. In the first stage, we replace certain +// Unicode characters with escape sequences. JavaScript handles many characters +// incorrectly, either silently deleting them, or treating them as line endings. + + text = String(text); + rx_dangerous.lastIndex = 0; + if (rx_dangerous.test(text)) { + text = text.replace(rx_dangerous, function (a) { + return ( + "\\u" + + ("0000" + a.charCodeAt(0).toString(16)).slice(-4) + ); + }); + } + +// In the second stage, we run the text against regular expressions that look +// for non-JSON patterns. We are especially concerned with "()" and "new" +// because they can cause invocation, and "=" because it can cause mutation. +// But just to be safe, we want to reject all unexpected forms. + +// We split the second stage into 4 regexp operations in order to work around +// crippling inefficiencies in IE's and Safari's regexp engines. First we +// replace the JSON backslash pairs with "@" (a non-JSON character). Second, we +// replace all simple value tokens with "]" characters. Third, we delete all +// open brackets that follow a colon or comma or that begin the text. Finally, +// we look to see that the remaining characters are only whitespace or "]" or +// "," or ":" or "{" or "}". If that is so, then the text is safe for eval. + + if ( + rx_one.test( + text + .replace(rx_two, "@") + .replace(rx_three, "]") + .replace(rx_four, "") + ) + ) { + +// In the third stage we use the eval function to compile the text into a +// JavaScript structure. The "{" operator is subject to a syntactic ambiguity +// in JavaScript: it can begin a block or an object literal. We wrap the text +// in parens to eliminate the ambiguity. + + j = eval("(" + text + ")"); + +// In the optional fourth stage, we recursively walk the new structure, passing +// each name/value pair to a reviver function for possible transformation. + + return (typeof reviver === "function") + ? walk({"": j}, "") + : j; + } + +// If the text is not JSON parseable, then a SyntaxError is thrown. + + throw new SyntaxError("JSON.parse"); + }; + } +}()); From 3f5ae634f7f07fda8697257fc0920dc7b232b828 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 10:25:38 +0200 Subject: [PATCH 05/51] Replace the old underscore version to the new one. Also use the LONG_NAME variable instead of the hardcoded module name --- CRM/Booking/Form/AddSubResource.php | 2 +- CRM/Booking/Form/Booking/Info.php | 2 +- CRM/Booking/Form/SelectResource.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 8043072b..6713fb32 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -282,7 +282,7 @@ static function registerScripts() { ->addStyleFile('uk.co.compucorp.civicrm.booking', 'css/booking.css', 92, 'page-header') ->addScriptFile('civicrm', 'packages/backbone/json2.js', 100, 'html-header', FALSE) - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'packages/underscore.js', 110, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.js', 120, 'html-header') ->addScriptFile('civicrm', 'packages/backbone/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) diff --git a/CRM/Booking/Form/Booking/Info.php b/CRM/Booking/Form/Booking/Info.php index 61a7f7ed..5b90428e 100644 --- a/CRM/Booking/Form/Booking/Info.php +++ b/CRM/Booking/Form/Booking/Info.php @@ -400,7 +400,7 @@ static function registerScripts() { ->addStyleFile('uk.co.compucorp.civicrm.booking', 'css/booking.css', 92, 'page-header') ->addScriptFile('civicrm', 'packages/backbone/json2.js', 100, 'html-header', FALSE) - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'packages/underscore.js', 110, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.js', 120, 'html-header') ->addScriptFile('civicrm', 'packages/backbone/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) diff --git a/CRM/Booking/Form/SelectResource.php b/CRM/Booking/Form/SelectResource.php index a0f670fb..132e4d18 100644 --- a/CRM/Booking/Form/SelectResource.php +++ b/CRM/Booking/Form/SelectResource.php @@ -205,7 +205,7 @@ static function registerScripts() { ->addStyleFile('uk.co.compucorp.civicrm.booking', 'js/vendor/dhtmlxScheduler/sources/dhtmlxscheduler.css', 92, 'page-header') ->addStyleFile('uk.co.compucorp.civicrm.booking', 'css/booking.css', 92, 'page-header') - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'packages/underscore.js', 110, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.js', 120, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.marionette.js', 125, 'html-header', FALSE) From e4d4c51700145fa94bdb73fb1632c82bdb6c6d00 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 10:28:54 +0200 Subject: [PATCH 06/51] Replace backbone to the one provided by this package --- CRM/Booking/Form/AddSubResource.php | 2 +- CRM/Booking/Form/Booking/Info.php | 2 +- CRM/Booking/Form/SelectResource.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 6713fb32..82b25c3b 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -283,7 +283,7 @@ static function registerScripts() { ->addScriptFile('civicrm', 'packages/backbone/json2.js', 100, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) - ->addScriptFile('civicrm', 'packages/backbone/backbone.js', 120, 'html-header') + ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile('civicrm', 'packages/backbone/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) diff --git a/CRM/Booking/Form/Booking/Info.php b/CRM/Booking/Form/Booking/Info.php index 5b90428e..a9963ae2 100644 --- a/CRM/Booking/Form/Booking/Info.php +++ b/CRM/Booking/Form/Booking/Info.php @@ -401,7 +401,7 @@ static function registerScripts() { ->addStyleFile('uk.co.compucorp.civicrm.booking', 'css/booking.css', 92, 'page-header') ->addScriptFile('civicrm', 'packages/backbone/json2.js', 100, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) - ->addScriptFile('civicrm', 'packages/backbone/backbone.js', 120, 'html-header') + ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile('civicrm', 'packages/backbone/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) diff --git a/CRM/Booking/Form/SelectResource.php b/CRM/Booking/Form/SelectResource.php index 132e4d18..07922547 100644 --- a/CRM/Booking/Form/SelectResource.php +++ b/CRM/Booking/Form/SelectResource.php @@ -206,7 +206,7 @@ static function registerScripts() { ->addStyleFile('uk.co.compucorp.civicrm.booking', 'css/booking.css', 92, 'page-header') ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) - ->addScriptFile('civicrm', 'packages/backbone/backbone.js', 120, 'html-header') + ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) From 19ff9c135142456943a95ebdf640a88924facb4c Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 10:30:19 +0200 Subject: [PATCH 07/51] Replace marionette to the one provided by this package --- CRM/Booking/Form/AddSubResource.php | 2 +- CRM/Booking/Form/Booking/Info.php | 2 +- CRM/Booking/Form/SelectResource.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 82b25c3b..ecd966cb 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -284,7 +284,7 @@ static function registerScripts() { ->addScriptFile('civicrm', 'packages/backbone/json2.js', 100, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') - ->addScriptFile('civicrm', 'packages/backbone/backbone.marionette.js', 125, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) diff --git a/CRM/Booking/Form/Booking/Info.php b/CRM/Booking/Form/Booking/Info.php index a9963ae2..5cb2a800 100644 --- a/CRM/Booking/Form/Booking/Info.php +++ b/CRM/Booking/Form/Booking/Info.php @@ -402,7 +402,7 @@ static function registerScripts() { ->addScriptFile('civicrm', 'packages/backbone/json2.js', 100, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') - ->addScriptFile('civicrm', 'packages/backbone/backbone.marionette.js', 125, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) diff --git a/CRM/Booking/Form/SelectResource.php b/CRM/Booking/Form/SelectResource.php index 07922547..42532848 100644 --- a/CRM/Booking/Form/SelectResource.php +++ b/CRM/Booking/Form/SelectResource.php @@ -208,7 +208,7 @@ static function registerScripts() { ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) - ->addScriptFile('civicrm', 'packages/backbone/backbone.marionette.js', 125, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) From 15e5b542e24dfa053905ea6048cfeafa73e369a0 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 10:35:17 +0200 Subject: [PATCH 08/51] Replace modelbinder to the one provided by this package --- CRM/Booking/Form/AddSubResource.php | 2 +- CRM/Booking/Form/Booking/Info.php | 2 +- CRM/Booking/Form/SelectResource.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index ecd966cb..2ad8473c 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -285,7 +285,7 @@ static function registerScripts() { ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) - ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) diff --git a/CRM/Booking/Form/Booking/Info.php b/CRM/Booking/Form/Booking/Info.php index 5cb2a800..1c434abd 100644 --- a/CRM/Booking/Form/Booking/Info.php +++ b/CRM/Booking/Form/Booking/Info.php @@ -403,7 +403,7 @@ static function registerScripts() { ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) - ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/booking-info/app.js', 150, 'html-header') diff --git a/CRM/Booking/Form/SelectResource.php b/CRM/Booking/Form/SelectResource.php index 42532848..d50e8247 100644 --- a/CRM/Booking/Form/SelectResource.php +++ b/CRM/Booking/Form/SelectResource.php @@ -209,7 +209,7 @@ static function registerScripts() { ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) - ->addScriptFile('civicrm', 'packages/backbone/backbone.modelbinder.js', 125, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 140, 'html-header') From 7893f65be1d9c57aa9edf0c8e0b64d8343674939 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 10:37:26 +0200 Subject: [PATCH 09/51] Replace json2 to the one provided by this package --- CRM/Booking/Form/AddSubResource.php | 2 +- CRM/Booking/Form/Booking/Info.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 2ad8473c..2ac298ee 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -281,7 +281,7 @@ static function registerScripts() { ->addStyleFile('uk.co.compucorp.civicrm.booking', 'css/booking.css', 92, 'page-header') - ->addScriptFile('civicrm', 'packages/backbone/json2.js', 100, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/json2.js', 100, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) diff --git a/CRM/Booking/Form/Booking/Info.php b/CRM/Booking/Form/Booking/Info.php index 1c434abd..774fca02 100644 --- a/CRM/Booking/Form/Booking/Info.php +++ b/CRM/Booking/Form/Booking/Info.php @@ -399,7 +399,7 @@ static function registerScripts() { CRM_Core_Resources::singleton() ->addStyleFile('uk.co.compucorp.civicrm.booking', 'css/booking.css', 92, 'page-header') - ->addScriptFile('civicrm', 'packages/backbone/json2.js', 100, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'packages/json2.js', 100, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/underscore-umd.js', 110, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) From 2a444ad9f03045d1491c9b6b07f3d9d2d15d13b5 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 11:12:31 +0200 Subject: [PATCH 10/51] Add crm.backbone.js to codebase and fix undefined issue In the browser console the following error appeared: Uncaught TypeError: Cannot read property 'Model' of undefined Solution: If the Backbone is undefined, fallback to the window.Backbone. --- js/vendor/crm.backbone.js | 584 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 584 insertions(+) create mode 100644 js/vendor/crm.backbone.js diff --git a/js/vendor/crm.backbone.js b/js/vendor/crm.backbone.js new file mode 100644 index 00000000..f18d7b06 --- /dev/null +++ b/js/vendor/crm.backbone.js @@ -0,0 +1,584 @@ +(function($, _, Backbone) { + /* + * In the browser console the following error appeared: + * Uncaught TypeError: Cannot read property 'Model' of undefined + * The issue pointed to the line where the Backbone variable were first used. + * The issue was verified with the debug tool. This function input Backbone was + * undefined, but the window.Backbone were defined. + * This if branch is for fixing the issue. If the Backbone is undefined, fallback to + * the window.Backbone. + * */ + if (typeof Backbone === 'undefined') { + Backbone = window.Backbone; + } + if (!CRM.Backbone) CRM.Backbone = {}; + + /** + * Backbone.sync provider which uses CRM.api() for I/O. + * To support CRUD operations, model classes must be defined with a "crmEntityName" property. + * To load collections using API queries, set the "crmCriteria" property or override the + * method "toCrmCriteria". + * + * @param method Accepts normal Backbone.sync methods; also accepts "crm-replace" + * @param model + * @param options + * @see tests/qunit/crm-backbone + */ + CRM.Backbone.sync = function(method, model, options) { + var isCollection = _.isArray(model.models); + + var apiOptions, params; + if (isCollection) { + apiOptions = { + success: function(data) { + // unwrap data + options.success(_.toArray(data.values)); + }, + error: function(data) { + // CRM.api displays errors by default, but Backbone.sync + // protocol requires us to override "error". This restores + // the default behavior. + $().crmError(data.error_message, ts('Error')); + options.error(data); + } + }; + switch (method) { + case 'read': + CRM.api(model.crmEntityName, model.toCrmAction('get'), model.toCrmCriteria(), apiOptions); + break; + // replace all entities matching "x.crmCriteria" with new entities in "x.models" + case 'crm-replace': + params = this.toCrmCriteria(); + params.version = 3; + params.values = this.toJSON(); + CRM.api(model.crmEntityName, model.toCrmAction('replace'), params, apiOptions); + break; + default: + apiOptions.error({is_error: 1, error_message: "CRM.Backbone.sync(" + method + ") not implemented for collections"}); + break; + } + } else { + // callback options to pass to CRM.api + apiOptions = { + success: function(data) { + // unwrap data + var values = _.toArray(data.values); + if (data.count == 1) { + options.success(values[0]); + } else { + data.is_error = 1; + data.error_message = ts("Expected exactly one response"); + apiOptions.error(data); + } + }, + error: function(data) { + // CRM.api displays errors by default, but Backbone.sync + // protocol requires us to override "error". This restores + // the default behavior. + $().crmError(data.error_message, ts('Error')); + options.error(data); + } + }; + switch (method) { + case 'create': // pass-through + case 'update': + params = model.toJSON(); + if (!params.options) params.options = {}; + params.options.reload = 1; + if (!model._isDuplicate) { + CRM.api(model.crmEntityName, model.toCrmAction('create'), params, apiOptions); + } else { + CRM.api(model.crmEntityName, model.toCrmAction('duplicate'), params, apiOptions); + } + break; + case 'read': + case 'delete': + var apiAction = (method == 'delete') ? 'delete' : 'get'; + params = model.toCrmCriteria(); + if (!params.id) { + apiOptions.error({is_error: 1, error_message: 'Missing ID for ' + model.crmEntityName}); + return; + } + CRM.api(model.crmEntityName, model.toCrmAction(apiAction), params, apiOptions); + break; + default: + apiOptions.error({is_error: 1, error_message: "CRM.Backbone.sync(" + method + ") not implemented for models"}); + } + } + }; + + /** + * Connect a "model" class to CiviCRM's APIv3 + * + * @code + * // Setup class + * var ContactModel = Backbone.Model.extend({}); + * CRM.Backbone.extendModel(ContactModel, "Contact"); + * + * // Use class + * c = new ContactModel({id: 3}); + * c.fetch(); + * @endcode + * + * @param Class ModelClass + * @param string crmEntityName APIv3 entity name, such as "Contact" or "CustomField" + * @see tests/qunit/crm-backbone + */ + CRM.Backbone.extendModel = function(ModelClass, crmEntityName) { + // Defaults - if specified in ModelClass, preserve + _.defaults(ModelClass.prototype, { + crmEntityName: crmEntityName, + crmActions: {}, // map: string backboneActionName => string serverSideActionName + crmReturn: null, // array: list of fields to return + toCrmAction: function(action) { + return this.crmActions[action] ? this.crmActions[action] : action; + }, + toCrmCriteria: function() { + var result = (this.get('id')) ? {id: this.get('id')} : {}; + if (!_.isEmpty(this.crmReturn)) { + result.return = this.crmReturn; + } + return result; + }, + duplicate: function() { + var newModel = new ModelClass(this.toJSON()); + newModel._isDuplicate = true; + if (newModel.setModified) newModel.setModified(); + newModel.listenTo(newModel, 'sync', function(){ + // may get called on subsequent resaves -- don't care! + delete newModel._isDuplicate; + }); + return newModel; + } + }); + // Overrides - if specified in ModelClass, replace + _.extend(ModelClass.prototype, { + sync: CRM.Backbone.sync + }); + }; + + /** + * Configure a model class to track whether a model has unsaved changes. + * + * Methods: + * - setModified() - flag the model as modified/dirty + * - isSaved() - return true if there have been no changes to the data since the last fetch or save + * Events: + * - saved(object model, bool is_saved) - triggered whenever isSaved() value would change + * + * Note: You should not directly call isSaved() within the context of the success/error/sync callback; + * I haven't found a way to make isSaved() behave correctly within these callbacks without patching + * Backbone. Instead, attach an event listener to the 'saved' event. + * + * @param ModelClass + */ + CRM.Backbone.trackSaved = function(ModelClass) { + // Retain references to some of the original class's functions + var Parent = _.pick(ModelClass.prototype, 'initialize', 'save', 'fetch'); + + // Private callback + var onSyncSuccess = function() { + this._modified = false; + if (this._oldModified.length > 0) { + this._oldModified.pop(); + } + this.trigger('saved', this, this.isSaved()); + }; + var onSaveError = function() { + if (this._oldModified.length > 0) { + this._modified = this._oldModified.pop(); + this.trigger('saved', this, this.isSaved()); + } + }; + + // Defaults - if specified in ModelClass, preserve + _.defaults(ModelClass.prototype, { + isSaved: function() { + var result = !this.isNew() && !this.isModified(); + return result; + }, + isModified: function() { + return this._modified; + }, + _saved_onchange: function(model, options) { + if (options.parse) return; + // console.log('change', model.changedAttributes(), model.previousAttributes()); + this.setModified(); + }, + setModified: function() { + var oldModified = this._modified; + this._modified = true; + if (!oldModified) { + this.trigger('saved', this, this.isSaved()); + } + } + }); + + // Overrides - if specified in ModelClass, replace + _.extend(ModelClass.prototype, { + initialize: function(options) { + this._modified = false; + this._oldModified = []; + this.listenTo(this, 'change', this._saved_onchange); + this.listenTo(this, 'error', onSaveError); + this.listenTo(this, 'sync', onSyncSuccess); + if (Parent.initialize) { + return Parent.initialize.apply(this, arguments); + } + }, + save: function() { + // we'll assume success + this._oldModified.push(this._modified); + return Parent.save.apply(this, arguments); + }, + fetch: function() { + this._oldModified.push(this._modified); + return Parent.fetch.apply(this, arguments); + } + }); + }; + + /** + * Configure a model class to support client-side soft deletion. + * One can call "model.setDeleted(BOOLEAN)" to flag an entity for + * deletion (or not) -- however, deletion will be deferred until save() + * is called. + * + * Methods: + * setSoftDeleted(boolean) - flag the model as deleted (or not-deleted) + * isSoftDeleted() - determine whether model has been soft-deleted + * Events: + * softDelete(model, is_deleted) -- change value of is_deleted + * + * @param ModelClass + */ + CRM.Backbone.trackSoftDelete = function(ModelClass) { + // Retain references to some of the original class's functions + var Parent = _.pick(ModelClass.prototype, 'save'); + + // Defaults - if specified in ModelClass, preserve + _.defaults(ModelClass.prototype, { + is_soft_deleted: false, + setSoftDeleted: function(is_deleted) { + if (this.is_soft_deleted != is_deleted) { + this.is_soft_deleted = is_deleted; + this.trigger('softDelete', this, is_deleted); + if (this.setModified) this.setModified(); // FIXME: ugly interaction, trackSoftDelete-trackSaved + } + }, + isSoftDeleted: function() { + return this.is_soft_deleted; + } + }); + + // Overrides - if specified in ModelClass, replace + _.extend(ModelClass.prototype, { + save: function(attributes, options) { + if (this.isSoftDeleted()) { + return this.destroy(options); + } else { + return Parent.save.apply(this, arguments); + } + } + }); + }; + + /** + * Connect a "collection" class to CiviCRM's APIv3 + * + * Note: the collection supports a special property, crmCriteria, which is an array of + * query options to send to the API. + * + * @code + * // Setup class + * var ContactModel = Backbone.Model.extend({}); + * CRM.Backbone.extendModel(ContactModel, "Contact"); + * var ContactCollection = Backbone.Collection.extend({ + * model: ContactModel + * }); + * CRM.Backbone.extendCollection(ContactCollection); + * + * // Use class (with passive criteria) + * var c = new ContactCollection([], { + * crmCriteria: {contact_type: 'Organization'} + * }); + * c.fetch(); + * c.get(123).set('property', 'value'); + * c.get(456).setDeleted(true); + * c.save(); + * + * // Use class (with active criteria) + * var criteriaModel = new SomeModel({ + * contact_type: 'Organization' + * }); + * var c = new ContactCollection([], { + * crmCriteriaModel: criteriaModel + * }); + * c.fetch(); + * c.get(123).set('property', 'value'); + * c.get(456).setDeleted(true); + * c.save(); + * @endcode + * + * + * @param Class CollectionClass + * @see tests/qunit/crm-backbone + */ + CRM.Backbone.extendCollection = function(CollectionClass) { + var origInit = CollectionClass.prototype.initialize; + // Defaults - if specified in CollectionClass, preserve + _.defaults(CollectionClass.prototype, { + crmEntityName: CollectionClass.prototype.model.prototype.crmEntityName, + crmActions: {}, // map: string backboneActionName => string serverSideActionName + toCrmAction: function(action) { + return this.crmActions[action] ? this.crmActions[action] : action; + }, + toCrmCriteria: function() { + var result = (this.crmCriteria) ? _.extend({}, this.crmCriteria) : {}; + if (!_.isEmpty(this.crmReturn)) { + result.return = this.crmReturn; + } else if (this.model && !_.isEmpty(this.model.prototype.crmReturn)) { + result.return = this.model.prototype.crmReturn; + } + return result; + }, + + /** + * Get an object which represents this collection's criteria + * as a live model. Any changes to the model will be applied + * to the collection, and the collection will be refreshed. + * + * @param criteriaModelClass + */ + setCriteriaModel: function(criteriaModel) { + var collection = this; + this.crmCriteria = criteriaModel.toJSON(); + this.listenTo(criteriaModel, 'change', function() { + collection.crmCriteria = criteriaModel.toJSON(); + collection.debouncedFetch(); + }); + }, + + debouncedFetch: _.debounce(function() { + this.fetch({reset: true}); + }, 100), + + /** + * Reconcile the server's collection with the client's collection. + * New/modified items from the client will be saved/updated on the + * server. Deleted items from the client will be deleted on the + * server. + * + * @param Object options - accepts "success" and "error" callbacks + */ + save: function(options) { + if (!options) options = {}; + var collection = this; + var success = options.success; + options.success = function(resp) { + // Ensure attributes are restored during synchronous saves. + collection.reset(resp, options); + if (success) success(collection, resp, options); + // collection.trigger('sync', collection, resp, options); + }; + wrapError(collection, options); + + return this.sync('crm-replace', this, options); + } + }); + // Overrides - if specified in CollectionClass, replace + _.extend(CollectionClass.prototype, { + sync: CRM.Backbone.sync, + initialize: function(models, options) { + if (!options) options = {}; + if (options.crmCriteriaModel) { + this.setCriteriaModel(options.crmCriteriaModel); + } else if (options.crmCriteria) { + this.crmCriteria = options.crmCriteria; + } + if (options.crmActions) { + this.crmActions = _.extend(this.crmActions, options.crmActions); + } + if (origInit) { + return origInit.apply(this, arguments); + } + }, + toJSON: function() { + var result = []; + // filter models list, excluding any soft-deleted items + this.each(function(model) { + // if model doesn't track soft-deletes + // or if model tracks soft-deletes and wasn't soft-deleted + if (!model.isSoftDeleted || !model.isSoftDeleted()) { + result.push(model.toJSON()); + } + }); + return result; + } + }); + }; + + /** + * Find a single record, or create a new record. + * + * @param Object options: + * - CollectionClass: class + * - crmCriteria: Object values to search/default on + * - defaults: Object values to put on newly created model (if needed) + * - success: function(model) + * - error: function(collection, error) + */ + CRM.Backbone.findCreate = function(options) { + if (!options) options = {}; + var collection = new options.CollectionClass([], { + crmCriteria: options.crmCriteria + }); + collection.fetch({ + success: function(collection) { + if (collection.length === 0) { + var attrs = _.extend({}, collection.crmCriteria, options.defaults || {}); + var model = collection._prepareModel(attrs, options); + options.success(model); + } else if (collection.length == 1) { + options.success(collection.first()); + } else { + options.error(collection, { + is_error: 1, + error_message: 'Too many matches' + }); + } + }, + error: function(collection, errorData) { + if (options.error) { + options.error(collection, errorData); + } + } + }); + }; + + + CRM.Backbone.Model = Backbone.Model.extend({ + /** + * Return JSON version of model -- but only include fields that are + * listed in the 'schema'. + * + * @return {*} + */ + toStrictJSON: function() { + var schema = this.schema; + var result = this.toJSON(); + _.each(result, function(value, key) { + if (!schema[key]) { + delete result[key]; + } + }); + return result; + }, + setRel: function(key, value, options) { + this.rels = this.rels || {}; + if (this.rels[key] != value) { + this.rels[key] = value; + this.trigger("rel:" + key, value); + } + }, + getRel: function(key) { + return this.rels ? this.rels[key] : null; + } + }); + + CRM.Backbone.Collection = Backbone.Collection.extend({ + /** + * Store 'key' on this.rel and automatically copy it to + * any children. + * + * @param key + * @param value + * @param initialModels + */ + initializeCopyToChildrenRelation: function(key, value, initialModels) { + this.setRel(key, value, {silent: true}); + this.on('reset', this._copyToChildren, this); + this.on('add', this._copyToChild, this); + }, + _copyToChildren: function() { + var collection = this; + collection.each(function(model) { + collection._copyToChild(model); + }); + }, + _copyToChild: function(model) { + _.each(this.rels, function(relValue, relKey) { + model.setRel(relKey, relValue, {silent: true}); + }); + }, + setRel: function(key, value, options) { + this.rels = this.rels || {}; + if (this.rels[key] != value) { + this.rels[key] = value; + this.trigger("rel:" + key, value); + } + }, + getRel: function(key) { + return this.rels ? this.rels[key] : null; + } + }); + + /* + CRM.Backbone.Form = Backbone.Form.extend({ + validate: function() { + // Add support for form-level validators + var errors = Backbone.Form.prototype.validate.apply(this, []) || {}; + var self = this; + if (this.validators) { + _.each(this.validators, function(validator) { + var modelErrors = validator(this.getValue()); + + // The following if() has been copied-pasted from the parent's + // handling of model-validators. They are similar in that the errors are + // probably keyed by field names... but not necessarily, so we use _others + // as a fallback. + if (modelErrors) { + var isDictionary = _.isObject(modelErrors) && !_.isArray(modelErrors); + + //If errors are not in object form then just store on the error object + if (!isDictionary) { + errors._others = errors._others || []; + errors._others.push(modelErrors); + } + + //Merge programmatic errors (requires model.validate() to return an object e.g. { fieldKey: 'error' }) + if (isDictionary) { + _.each(modelErrors, function(val, key) { + //Set error on field if there isn't one already + if (self.fields[key] && !errors[key]) { + self.fields[key].setError(val); + errors[key] = val; + } + + else { + //Otherwise add to '_others' key + errors._others = errors._others || []; + var tmpErr = {}; + tmpErr[key] = val; + errors._others.push(tmpErr); + } + }); + } + } + + }); + } + return _.isEmpty(errors) ? null : errors; + } + }); + */ + + // Wrap an optional error callback with a fallback error event. + var wrapError = function (model, options) { + var error = options.error; + options.error = function(resp) { + if (error) error(model, resp, optio); + model.trigger('error', model, resp, options); + }; + }; +})(CRM.$, CRM._, CRM.BB); From d9a49d834ddc47a2a4e37ddba668862ac3890516 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 11:12:57 +0200 Subject: [PATCH 11/51] Replace crm.backbone.js to the one provided by this package --- CRM/Booking/Form/AddSubResource.php | 2 +- CRM/Booking/Form/Booking/Info.php | 2 +- CRM/Booking/Form/SelectResource.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 2ac298ee..39050293 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -286,7 +286,7 @@ static function registerScripts() { ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.modelbinder.js', 125, 'html-header', FALSE) - ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'js/vendor/crm.backbone.js', 130, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/civicrm-moment-strftime.js', 140, 'html-header', FALSE) diff --git a/CRM/Booking/Form/Booking/Info.php b/CRM/Booking/Form/Booking/Info.php index 774fca02..5372c42c 100644 --- a/CRM/Booking/Form/Booking/Info.php +++ b/CRM/Booking/Form/Booking/Info.php @@ -404,7 +404,7 @@ static function registerScripts() { ->addScriptFile(E::LONG_NAME, 'packages/backbone.js', 120, 'html-header') ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.modelbinder.js', 125, 'html-header', FALSE) - ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'js/vendor/crm.backbone.js', 130, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/booking-info/app.js', 150, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/booking-info/view.js', 170, 'html-header'); diff --git a/CRM/Booking/Form/SelectResource.php b/CRM/Booking/Form/SelectResource.php index d50e8247..432a159e 100644 --- a/CRM/Booking/Form/SelectResource.php +++ b/CRM/Booking/Form/SelectResource.php @@ -210,7 +210,7 @@ static function registerScripts() { ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.marionette.js', 125, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'packages/backbone.modelbinder.js', 125, 'html-header', FALSE) - ->addScriptFile('civicrm', 'js/crm.backbone.js', 130, 'html-header', FALSE) + ->addScriptFile(E::LONG_NAME, 'js/vendor/crm.backbone.js', 130, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 140, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/utils.js', 141, 'html-header', FALSE) From 40475acd23b6967d5f2594c2c68d2a0f7fc5f0fe Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 11:22:27 +0200 Subject: [PATCH 12/51] Eliminate some undefined issue in add-sub-resource app The Backbone.Marionette is undefined in the current lib. But Marionette is defined, so it could be used instead. --- js/booking/add-sub-resource/app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index e73dd956..bbae6430 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -1,7 +1,7 @@ -CRM.BookingApp = new Backbone.Marionette.Application(); +CRM.BookingApp = new Marionette.Application(); // see http://lostechies.com/derickbailey/2012/04/17/managing-a-modal-dialog-with-backbone-and-marionette/ -var ModalRegion = Backbone.Marionette.Region.extend({ +var ModalRegion = Marionette.Region.extend({ el: "#crm-booking-dialog", constructor: function(){ From d04a73070cf6fef984c34cb40d64b49c71d95ee8 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 11:54:46 +0200 Subject: [PATCH 13/51] Replace deprecated addRegions method in add-sub-resource app --- js/booking/add-sub-resource/app.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index bbae6430..383534ca 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -1,5 +1,4 @@ -CRM.BookingApp = new Marionette.Application(); // see http://lostechies.com/derickbailey/2012/04/17/managing-a-modal-dialog-with-backbone-and-marionette/ var ModalRegion = Marionette.Region.extend({ el: "#crm-booking-dialog", @@ -27,12 +26,14 @@ var ModalRegion = Marionette.Region.extend({ cj('#crm-booking-dialog').dialog().dialog("close"); } }); - -CRM.BookingApp.addRegions({ +// The addRegions method has been removed and not present in the +// current Marionette version. The Application.extend could be the +// solution for passing the necessary parameters to the application. +var MyApp = Marionette.Application.extend({ main: "#resource-main", modal: ModalRegion - }); +CRM.BookingApp = new MyApp(); CRM.BookingApp.on("initialize:after", function(){ if( ! Backbone.History.started) Backbone.history.start(); From 9c7dbabb729c8c183ed57c3e310791e523622fca Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 12:30:14 +0200 Subject: [PATCH 14/51] Move utils module code to add-sub-resource app As the Marionette.Module is deprecated and has been removed from the codebase, the utils.js triggered js error. The utils.js function has been removed and the add-sub-resource app has been extended with the removed logic. --- CRM/Booking/Form/AddSubResource.php | 1 - CRM/Booking/Form/SelectResource.php | 1 - js/booking/add-sub-resource/app.js | 15 ++++++++++++++- js/booking/utils.js | 20 -------------------- 4 files changed, 14 insertions(+), 23 deletions(-) delete mode 100644 js/booking/utils.js diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 39050293..98e3c818 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -292,7 +292,6 @@ static function registerScripts() { ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/civicrm-moment-strftime.js', 140, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 150, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/common/views.js', 151, 'html-header', FALSE) - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/utils.js', 151, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/entities.js', 160, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/view.js', 170, 'html-header'); diff --git a/CRM/Booking/Form/SelectResource.php b/CRM/Booking/Form/SelectResource.php index 432a159e..65ba5fd5 100644 --- a/CRM/Booking/Form/SelectResource.php +++ b/CRM/Booking/Form/SelectResource.php @@ -213,7 +213,6 @@ static function registerScripts() { ->addScriptFile(E::LONG_NAME, 'js/vendor/crm.backbone.js', 130, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 140, 'html-header') - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/utils.js', 141, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/civicrm-moment-strftime.js', 142, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/dhtmlxScheduler/sources/dhtmlxscheduler.js', 132, 'html-header') diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 383534ca..ac7227fe 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -31,7 +31,20 @@ var ModalRegion = Marionette.Region.extend({ // solution for passing the necessary parameters to the application. var MyApp = Marionette.Application.extend({ main: "#resource-main", - modal: ModalRegion + modal: ModalRegion, + Utils: { + //http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript + getCurrentUnixTimstamp: function () { + return unix = Math.round(+new Date()/1000); + }, + //http://stackoverflow.com/questions/10834796/validate-that-a-string-is-a-positive-integer + isPositiveInteger: function (n){ + return 0 === n % (!isNaN(parseFloat(n)) && 0 <= ~~n); + }, + isPositiveNumber: function (n){ + return !_.isNumber(n) && 0 <= n; + }, + }, }); CRM.BookingApp = new MyApp(); diff --git a/js/booking/utils.js b/js/booking/utils.js deleted file mode 100644 index fcd1711f..00000000 --- a/js/booking/utils.js +++ /dev/null @@ -1,20 +0,0 @@ -CRM.BookingApp.module("Utils", function(Utils, BookingApp, Backbone, Marionette, $, _){ - - //http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript - Utils.getCurrentUnixTimstamp = function () { - return unix = Math.round(+new Date()/1000); - }; - - //http://stackoverflow.com/questions/10834796/validate-that-a-string-is-a-positive-integer - Utils.isPositiveInteger = function (n){ - return 0 === n % (!isNaN(parseFloat(n)) && 0 <= ~~n); - }; - - Utils.isPositiveNumber = function (n){ - return !_.isNumber(n) && 0 <= n; - }; - -}); - - - From e3b8beb3f00bc8d70e84b86799edc853d1a9b79b Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 13:40:36 +0200 Subject: [PATCH 15/51] Fix the first creen of the new booking flow Console errors are not visible on the screen. Changes: - entites.js module elimination. The content has been added to the app.js. - addInitializer has been removed, the onStart function has to be used instead - move ResourceTableView from views to app as the modules are not supported --- js/booking/add-sub-resource/app.js | 220 ++++++++++++++++++++++-- js/booking/add-sub-resource/entities.js | 37 ---- js/booking/add-sub-resource/view.js | 182 -------------------- 3 files changed, 210 insertions(+), 229 deletions(-) delete mode 100644 js/booking/add-sub-resource/entities.js diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index ac7227fe..576ff07c 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -1,4 +1,35 @@ +var Entities = { + SubResource: Backbone.Model.extend({ + defaults: { + sub_resources: {}, + resources: {}, + sub_total: 0, + adhoc_charges: {total:0}, + discount_amount:0, + total_price:0 + }, + }), + AddSubResource: Backbone.Model.extend({ + defaults: { + parent_ref_id: null, + ref_id: null, + resource: {id : null, label :null}, + configuration: {id : null, label :null, price :0}, + quantity: 0, + time_required: null, + note: null, + price_estimate: 0, + }, + }), + AdhocCharges: Backbone.Model.extend({ + defaults: { + items: {}, + note: null, + total: 0, + }, + }), +}; // see http://lostechies.com/derickbailey/2012/04/17/managing-a-modal-dialog-with-backbone-and-marionette/ var ModalRegion = Marionette.Region.extend({ el: "#crm-booking-dialog", @@ -26,11 +57,185 @@ var ModalRegion = Marionette.Region.extend({ cj('#crm-booking-dialog').dialog().dialog("close"); } }); +//Resource table view +var ResourceTableView = Marionette.View.extend({ + template: '#resource-table-template', + + initialize: function(){ + if ($.trim($("#sub_resources").val())) { + this.model.attributes = JSON.parse($.trim($("#sub_resources").val())); + } + this.model.attributes.total_price = $("#total_price").val(); + this.model.attributes.sub_total = $("#sub_total").val(); + //this.model.attributes.adhoc_charges = $("#adhoc_charge").val(); + this.model.attributes.discount_amount = $("#discount_amount").val(); + }, + + onRender: function(){ + var subtotal = 0; + var self = this; + //init the current price for each resource + this.$el.find("span[id^='resource-price-']").each(function(){ + var el = $(this); + self.model.attributes.resources[el.data('ref')] = el.text(); + }); + var items = []; + var template = _.template($('#sub-resource-row-template').html()); + _.each(this.model.get('sub_resources'), function (item, key){ + self.$el.find("#crm-booking-sub-resource-table-" + item.parent_ref_id).append(template(item)); + priceCache[item.ref_id] = item.price_estimate; + items.push(item); + }); + this.$el.find("span[id^='resource-total-price-']").each(function(){ + var el = $(this);/////////////////////////// + var resourceTotalPrice = parseFloat(el.data('price')); + _.find(items, function (item) { + if(parseInt(item.parent_ref_id) === parseInt(el.data('ref'))){ + resourceTotalPrice += parseFloat(item.price_estimate); + } + }); + if(resourceTotalPrice != null){ + subtotal += resourceTotalPrice; + el.text(resourceTotalPrice.toFixed(2)); + resourceTotal[el.data('ref')] = resourceTotalPrice.toFixed(2); + self.$el.find('#crm-booking-sub-resource-row-' + el.data('ref')).show(); + } + }); + this.model.attributes.sub_total = subtotal; + this.model.attributes.total_price = (subtotal + + parseFloat(this.model.get("adhoc_charges").total)) + - parseFloat(this.model.get("discount_amount")); + this.model.attributes.discount_amount = this.model.get("discount_amount"); + + unlimitedTimeConfig = timeConfig; + + var subTotalText = this.model.get('sub_total'); + var adhocText = this.model.get('adhoc_charges').total; + var discountText = this.model.get('discount_amount'); + var totalText = this.model.get('total_price'); + this.$el.find("#sub-total-summary").text(subTotalText.toFixed(2)); + this.$el.find("#ad-hoc-charge-summary").text(adhocText); + try{ + this.$el.find("#ad-hoc-charge-summary").text(adhocText,toFixed(2)); + }catch(err){} + this.$el.find("#discount_amount_dummy").val(discountText); + this.$el.find("#total-price-summary").text(totalText.toFixed(2)); + }, + events: { + 'click .add-sub-resource': 'addSubResource', + 'click .edit-sub-resource': 'editSubResource', + 'click .edit-adhoc-charge': 'editAdhocCharge', + 'click .collapsed' : 'toggleHiddenElement', + 'click .remove-sub-resource': 'removeSubResource', + //'keypress #discount_amount_dummy': 'addDiscountAmount', + 'keyup #discount_amount_dummy': 'addDiscountAmount', + //'keydown #discount_amount_dummy': 'addDiscountAmount' + }, + + addSubResource: function(e){ + var ref = $(e.currentTarget).data('ref');///////////////// + //resourceTotal[ref] = 0; + endDate = $(e.currentTarget).data('edate'); + startDate = $(e.currentTarget).data('sdate'); + var model = new CRM.BookingApp.Entities.AddSubResource({parent_ref_id:ref, time_required:startDate}); + var view = new AddSubResource.AddSubResourceModal({model: model, is_new: true}); + view.title = ts('Add Unlimited Resource'); + CRM.BookingApp.modal.show(view); + }, + + addDiscountAmount: function(e){ + var currentSubTotal = parseFloat(this.model.get('sub_total')); + var currentAdhocCharges = parseFloat(this.model.get('adhoc_charges').total); + // Get the discount amount stripping out non-numeric characters + var sDiscountAmount = $(e.currentTarget).val().replace(/[^\d.-]/g, ''); + var fDiscountAmount = parseFloat(sDiscountAmount); + if (!_.isNumber(fDiscountAmount) || _.isNaN(fDiscountAmount)) { + fDiscountAmount = 0; + sDiscountAmount = ''; + } + var newTotal = (currentSubTotal + currentAdhocCharges) - fDiscountAmount; + try{newTotal = newTotal.toFixed(2); }catch(err){} + this.model.set("total_price", newTotal); + this.model.set("discount_amount", sDiscountAmount); + CRM.BookingApp.vent.trigger('render:price', this.model ); + }, + + editAdhocCharge: function(e) { + var model = new CRM.BookingApp.Entities.AdhocCharges({ + items : this.model.get('adhoc_charges').items, + note : this.model.get('adhoc_charges').note, + total : this.model.get('adhoc_charges').total + }); + var view = new AddSubResource.EditAdhocChargesModal({ + model : model + }); + view.title = ts('Edit Additional Charges'); + CRM.BookingApp.modal.show(view); + }, + + toggleHiddenElement: function(e){ + var row = $(e.currentTarget).data('ref'); + $('#crm-booking-sub-resource-row-' + row).toggle(); + }, + removeSubResource: function(e){ + var ref = $(e.currentTarget).data('ref'); + var parentRef = $(e.currentTarget).data('parent-ref'); + var price = $(e.currentTarget).data('price'); + $('#crm-booking-sub-resource-individual-row-' + ref).remove(); + delete this.model.attributes.sub_resources[ref]; + + var newResourcePrice = parseFloat(this.model.get("resources")[parentRef]) - parseFloat(price); + + this.model.attributes.resources[parentRef] = newResourcePrice; + resourceTotal[parentRef] -= parseFloat(price); + try{resourceTotal[parentRef] = resourceTotal[parentRef].toFixed(2);}catch(err){} + $("#resource-total-price-" + parentRef).text(resourceTotal[parentRef]); + var currentSubTotal = this.model.get('sub_total'); + var newSubTotal = parseFloat(this.model.get('sub_total') - parseFloat(price)); + var currentTotal = this.model.get('total_price'); + var newTotal = parseFloat(currentTotal) - parseFloat(price); + + this.model.set("sub_total", newSubTotal); + this.model.set("total_price", newTotal); + + CRM.BookingApp.vent.trigger('render:price', this.model , parentRef ); + CRM.BookingApp.vent.trigger('update:resources', this.model); + CRM.alert(ts(''), ts('Unlimited resource removed'), 'success'); + }, + + //when edit sub resource + editSubResource: function(e) { + var refId = $(e.currentTarget).data('ref'); //retrieve id from attribute data-ref + var parentRef = $(e.currentTarget).data('parent-ref'); //retrieve id from attribute data-parent-ref + var timeRequired = $(e.currentTarget).data('time-required'); //retrieve datetime from attribute data-time-required + selectedItem = this.model.attributes.sub_resources[refId]; + + //create backbone model form json object + var model = new CRM.BookingApp.Entities.AddSubResource({ + parent_ref_id : parentRef, + ref_id : refId, + resource: {id : selectedItem.resource.id, label :selectedItem.resource.label}, + configuration: selectedItem.configuration, + quantity: selectedItem.quantity, + time_required: timeRequired, + note: selectedItem.note, + price_estimate: selectedItem.price_estimate, + }); + //create backbone view + var view = new AddSubResource.AddSubResourceModal({ + model : model, + is_new: false + }); + view.title = ts('Edit unlimited resource'); + CRM.BookingApp.modal.show(view); + } + +}); // The addRegions method has been removed and not present in the // current Marionette version. The Application.extend could be the // solution for passing the necessary parameters to the application. var MyApp = Marionette.Application.extend({ - main: "#resource-main", + region: "#resource-main", modal: ModalRegion, Utils: { //http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript @@ -45,18 +250,13 @@ var MyApp = Marionette.Application.extend({ return !_.isNumber(n) && 0 <= n; }, }, + Entities: Entities, + onStart: function(app, options) { + this.getRegion().show(new ResourceTableView({model: new this.Entities.SubResource()})); + }, }); CRM.BookingApp = new MyApp(); CRM.BookingApp.on("initialize:after", function(){ if( ! Backbone.History.started) Backbone.history.start(); }); - -CRM.BookingApp.addInitializer(function(){ - var model = new CRM.BookingApp.Entities.SubResource(); - var view = new CRM.BookingApp.AddSubResource.ResourceTableView({model: model}); - - CRM.BookingApp.main.show(view); -}); - - diff --git a/js/booking/add-sub-resource/entities.js b/js/booking/add-sub-resource/entities.js deleted file mode 100644 index 1a46d193..00000000 --- a/js/booking/add-sub-resource/entities.js +++ /dev/null @@ -1,37 +0,0 @@ - -CRM.BookingApp.module('Entities', function(Entities, BookingApp, Backbone, Marionette, $, _){ - - Entities.SubResource = Backbone.Model.extend({ - defaults: { - sub_resources: {}, - resources: {}, - sub_total: 0, - adhoc_charges: {total:0}, - discount_amount:0, - total_price:0 - - }, - }); - - Entities.AddSubResource = Backbone.Model.extend({ - defaults: { - parent_ref_id: null, - ref_id: null, - resource: {id : null, label :null}, - configuration: {id : null, label :null, price :0}, - quantity: 0, - time_required: null, - note: null, - price_estimate: 0, - }, - }); - - Entities.AdhocCharges = Backbone.Model.extend({ - defaults: { - items: {}, - note: null, - total: 0, - }, - }); - -}); diff --git a/js/booking/add-sub-resource/view.js b/js/booking/add-sub-resource/view.js index a8ced7a1..d9445d9a 100755 --- a/js/booking/add-sub-resource/view.js +++ b/js/booking/add-sub-resource/view.js @@ -50,188 +50,6 @@ CRM.BookingApp.module('AddSubResource', function(AddSubResource, BookingApp, Bac })); }); - //Resource table view - AddSubResource.ResourceTableView = Backbone.Marionette.ItemView.extend({ - template: '#resource-table-template', - - initialize: function(){ - if ($.trim($("#sub_resources").val())) { - this.model.attributes = JSON.parse($.trim($("#sub_resources").val())); - } - this.model.attributes.total_price = $("#total_price").val(); - this.model.attributes.sub_total = $("#sub_total").val(); - //this.model.attributes.adhoc_charges = $("#adhoc_charge").val(); - this.model.attributes.discount_amount = $("#discount_amount").val(); - }, - - onRender: function(){ - var subtotal = 0; - var self = this; - //init the current price for each resource - this.$el.find("span[id^='resource-price-']").each(function(){ - var el = $(this); - self.model.attributes.resources[el.data('ref')] = el.text(); - }); - var items = []; - var template = _.template($('#sub-resource-row-template').html()); - _.each(this.model.get('sub_resources'), function (item, key){ - self.$el.find("#crm-booking-sub-resource-table-" + item.parent_ref_id).append(template(item)); - priceCache[item.ref_id] = item.price_estimate; - items.push(item); - }); - //if($.trim($("#sub_resources").val())) { - this.$el.find("span[id^='resource-total-price-']").each(function(){ - var el = $(this);/////////////////////////// - var resourceTotalPrice = parseFloat(el.data('price')); - _.find(items, function (item) { - - if(parseInt(item.parent_ref_id) === parseInt(el.data('ref'))){ - resourceTotalPrice += parseFloat(item.price_estimate); - } - - }); - if(resourceTotalPrice != null){ - subtotal += resourceTotalPrice; - el.text(resourceTotalPrice.toFixed(2)); - resourceTotal[el.data('ref')] = resourceTotalPrice.toFixed(2); - self.$el.find('#crm-booking-sub-resource-row-' + el.data('ref')).show(); - } - }); - //} - this.model.attributes.sub_total = subtotal; - this.model.attributes.total_price = (subtotal - + parseFloat(this.model.get("adhoc_charges").total)) - - parseFloat(this.model.get("discount_amount")); - this.model.attributes.discount_amount = this.model.get("discount_amount"); - - unlimitedTimeConfig = timeConfig; - - var subTotalText = this.model.get('sub_total'); - var adhocText = this.model.get('adhoc_charges').total; - var discountText = this.model.get('discount_amount'); - var totalText = this.model.get('total_price'); - this.$el.find("#sub-total-summary").text(subTotalText.toFixed(2)); - this.$el.find("#ad-hoc-charge-summary").text(adhocText); - try{ - this.$el.find("#ad-hoc-charge-summary").text(adhocText,toFixed(2)); - }catch(err){} - this.$el.find("#discount_amount_dummy").val(discountText); - this.$el.find("#total-price-summary").text(totalText.toFixed(2)); - }, - events: { - 'click .add-sub-resource': 'addSubResource', - 'click .edit-sub-resource': 'editSubResource', - 'click .edit-adhoc-charge': 'editAdhocCharge', - 'click .collapsed' : 'toggleHiddenElement', - 'click .remove-sub-resource': 'removeSubResource', - //'keypress #discount_amount_dummy': 'addDiscountAmount', - 'keyup #discount_amount_dummy': 'addDiscountAmount', - //'keydown #discount_amount_dummy': 'addDiscountAmount' - }, - - addSubResource: function(e){ - var ref = $(e.currentTarget).data('ref');///////////////// - //resourceTotal[ref] = 0; - endDate = $(e.currentTarget).data('edate'); - startDate = $(e.currentTarget).data('sdate'); - var model = new CRM.BookingApp.Entities.AddSubResource({parent_ref_id:ref, time_required:startDate}); - var view = new AddSubResource.AddSubResourceModal({model: model, is_new: true}); - view.title = ts('Add Unlimited Resource'); - CRM.BookingApp.modal.show(view); - }, - - addDiscountAmount: function(e){ - var currentSubTotal = parseFloat(this.model.get('sub_total')); - var currentAdhocCharges = parseFloat(this.model.get('adhoc_charges').total); - - // Get the discount amount stripping out non-numeric characters - var sDiscountAmount = $(e.currentTarget).val().replace(/[^\d.-]/g, ''); - var fDiscountAmount = parseFloat(sDiscountAmount); - if (!_.isNumber(fDiscountAmount) || _.isNaN(fDiscountAmount)) { - fDiscountAmount = 0; - sDiscountAmount = ''; - } - - var newTotal = (currentSubTotal + currentAdhocCharges) - fDiscountAmount; - try{newTotal = newTotal.toFixed(2); }catch(err){} - this.model.set("total_price", newTotal); - this.model.set("discount_amount", sDiscountAmount); - CRM.BookingApp.vent.trigger('render:price', this.model ); - - }, - - editAdhocCharge: function(e) { - var model = new CRM.BookingApp.Entities.AdhocCharges({ - items : this.model.get('adhoc_charges').items, - note : this.model.get('adhoc_charges').note, - total : this.model.get('adhoc_charges').total - }); - var view = new AddSubResource.EditAdhocChargesModal({ - model : model - }); - view.title = ts('Edit Additional Charges'); - CRM.BookingApp.modal.show(view); - }, - - toggleHiddenElement: function(e){ - var row = $(e.currentTarget).data('ref'); - $('#crm-booking-sub-resource-row-' + row).toggle(); - }, - removeSubResource: function(e){ - var ref = $(e.currentTarget).data('ref'); - var parentRef = $(e.currentTarget).data('parent-ref'); - var price = $(e.currentTarget).data('price'); - $('#crm-booking-sub-resource-individual-row-' + ref).remove(); - delete this.model.attributes.sub_resources[ref]; - - var newResourcePrice = parseFloat(this.model.get("resources")[parentRef]) - parseFloat(price); - - this.model.attributes.resources[parentRef] = newResourcePrice; - resourceTotal[parentRef] -= parseFloat(price); - try{resourceTotal[parentRef] = resourceTotal[parentRef].toFixed(2);}catch(err){} - $("#resource-total-price-" + parentRef).text(resourceTotal[parentRef]); - var currentSubTotal = this.model.get('sub_total'); - var newSubTotal = parseFloat(this.model.get('sub_total') - parseFloat(price)); - var currentTotal = this.model.get('total_price'); - var newTotal = parseFloat(currentTotal) - parseFloat(price); - - this.model.set("sub_total", newSubTotal); - this.model.set("total_price", newTotal); - - CRM.BookingApp.vent.trigger('render:price', this.model , parentRef ); - CRM.BookingApp.vent.trigger('update:resources', this.model); - CRM.alert(ts(''), ts('Unlimited resource removed'), 'success'); - }, - - //when edit sub resource - editSubResource: function(e) { - var refId = $(e.currentTarget).data('ref'); //retrieve id from attribute data-ref - var parentRef = $(e.currentTarget).data('parent-ref'); //retrieve id from attribute data-parent-ref - var timeRequired = $(e.currentTarget).data('time-required'); //retrieve datetime from attribute data-time-required - selectedItem = this.model.attributes.sub_resources[refId]; - - //create backbone model form json object - var model = new CRM.BookingApp.Entities.AddSubResource({ - parent_ref_id : parentRef, - ref_id : refId, - resource: {id : selectedItem.resource.id, label :selectedItem.resource.label}, - configuration: selectedItem.configuration, - quantity: selectedItem.quantity, - time_required: timeRequired, - note: selectedItem.note, - price_estimate: selectedItem.price_estimate, - }); - //create backbone view - var view = new AddSubResource.AddSubResourceModal({ - model : model, - is_new: false - }); - view.title = ts('Edit unlimited resource'); - CRM.BookingApp.modal.show(view); - } - - }); - //Sub(Unlimited) resource dialog view AddSubResource.AddSubResourceModal = BookingApp.Common.Views.BookingProcessModal.extend({ template: "#add-sub-resource-template", From bb8d366a4e54f5bf869520c71b255250beb99eb9 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 13:47:23 +0200 Subject: [PATCH 16/51] Don't load entities.js --- CRM/Booking/Form/AddSubResource.php | 1 - 1 file changed, 1 deletion(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 98e3c818..9b7e1723 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -292,7 +292,6 @@ static function registerScripts() { ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/civicrm-moment-strftime.js', 140, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 150, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/common/views.js', 151, 'html-header', FALSE) - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/entities.js', 160, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/view.js', 170, 'html-header'); From 28fb44e172defbe14fd8fae1ba2cca7435c3c6f3 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 13:54:30 +0200 Subject: [PATCH 17/51] Remove the common.views module modules are deprecated and has been removed from the codebase --- CRM/Booking/Form/AddSubResource.php | 1 - js/booking/add-sub-resource/app.js | 29 ++++++++++++++++++++++++ js/booking/common/views.js | 35 ----------------------------- 3 files changed, 29 insertions(+), 36 deletions(-) delete mode 100644 js/booking/common/views.js diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 9b7e1723..46321bc4 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -291,7 +291,6 @@ static function registerScripts() { ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/civicrm-moment-strftime.js', 140, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 150, 'html-header') - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/common/views.js', 151, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/view.js', 170, 'html-header'); diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 576ff07c..eaac6ddb 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -30,6 +30,35 @@ var Entities = { }, }), }; +var Views = { + /** + * A form that use in a Modal that required the validate in the form + * + */ + BookingProcessModal: Marionette.View.extend({ + onRender: function() { + var rules = this.createValidationRules(); + this.$('form').validate(rules); + }, + /** + * + * @return {*} jQuery.validate rules + */ + createValidationRules: function() { + var rules = _.extend({}, CRM.validate.params); + rules.rules || (rules.rules = {}); + this.triggerMethod("validateRules:create", this, rules); + return rules; + }, + onRenderError: function(errors){ + var view = this; + _.each(errors, function(error) { + console.log($(error).attr('for')); + view.$('[name=' + $(error).attr('for') + ']').crmError($(error).text()); + }); + }, + }), +}; // see http://lostechies.com/derickbailey/2012/04/17/managing-a-modal-dialog-with-backbone-and-marionette/ var ModalRegion = Marionette.Region.extend({ el: "#crm-booking-dialog", diff --git a/js/booking/common/views.js b/js/booking/common/views.js deleted file mode 100644 index fa263b51..00000000 --- a/js/booking/common/views.js +++ /dev/null @@ -1,35 +0,0 @@ -CRM.BookingApp.module('Common.Views', function(Views, BookingApp, Backbone, Marionette, $, _) { - - - /** - * A form that use in a Modal that required the validate in the form - * - */ - Views.BookingProcessModal = Marionette.ItemView.extend({ - onRender: function() { - var rules = this.createValidationRules(); - this.$('form').validate(rules); - }, - /** - * - * @return {*} jQuery.validate rules - */ - createValidationRules: function() { - var rules = _.extend({}, CRM.validate.params); - rules.rules || (rules.rules = {}); - this.triggerMethod("validateRules:create", this, rules); - return rules; - }, - - onRenderError: function(errors){ - var view = this; - _.each(errors, function(error) { - console.log($(error).attr('for')); - view.$('[name=' + $(error).attr('for') + ']').crmError($(error).text()); - }); - } - - }); - - -}); From 7871c35939ce48e3932ccebee0ff75ceb288c064 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 14:30:03 +0200 Subject: [PATCH 18/51] Eliminate some undefined issue. Use CRM.$ instead of $, and CRM._ instead of _ --- js/booking/add-sub-resource/app.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index eaac6ddb..6913ebf0 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -88,16 +88,16 @@ var ModalRegion = Marionette.Region.extend({ }); //Resource table view var ResourceTableView = Marionette.View.extend({ - template: '#resource-table-template', + template: CRM._.template(CRM.$('#resource-table-template').html()), initialize: function(){ - if ($.trim($("#sub_resources").val())) { - this.model.attributes = JSON.parse($.trim($("#sub_resources").val())); + if (CRM.$.trim(CRM.$("#sub_resources").val())) { + this.model.attributes = JSON.parse(CRM.$.trim($("#sub_resources").val())); } - this.model.attributes.total_price = $("#total_price").val(); - this.model.attributes.sub_total = $("#sub_total").val(); + this.model.attributes.total_price = CRM.$("#total_price").val(); + this.model.attributes.sub_total = CRM.$("#sub_total").val(); //this.model.attributes.adhoc_charges = $("#adhoc_charge").val(); - this.model.attributes.discount_amount = $("#discount_amount").val(); + this.model.attributes.discount_amount = CRM.$("#discount_amount").val(); }, onRender: function(){ @@ -105,18 +105,18 @@ var ResourceTableView = Marionette.View.extend({ var self = this; //init the current price for each resource this.$el.find("span[id^='resource-price-']").each(function(){ - var el = $(this); + var el = CRM.$(this); self.model.attributes.resources[el.data('ref')] = el.text(); }); var items = []; - var template = _.template($('#sub-resource-row-template').html()); - _.each(this.model.get('sub_resources'), function (item, key){ + var template = CRM._.template(CRM.$('#sub-resource-row-template').html()); + CRM._.each(this.model.get('sub_resources'), function (item, key){ self.$el.find("#crm-booking-sub-resource-table-" + item.parent_ref_id).append(template(item)); priceCache[item.ref_id] = item.price_estimate; items.push(item); }); this.$el.find("span[id^='resource-total-price-']").each(function(){ - var el = $(this);/////////////////////////// + var el = CRM.$(this);/////////////////////////// var resourceTotalPrice = parseFloat(el.data('price')); _.find(items, function (item) { if(parseInt(item.parent_ref_id) === parseInt(el.data('ref'))){ @@ -162,10 +162,10 @@ var ResourceTableView = Marionette.View.extend({ }, addSubResource: function(e){ - var ref = $(e.currentTarget).data('ref');///////////////// + var ref = CRM.$(e.currentTarget).data('ref');///////////////// //resourceTotal[ref] = 0; - endDate = $(e.currentTarget).data('edate'); - startDate = $(e.currentTarget).data('sdate'); + endDate = CRM.$(e.currentTarget).data('edate'); + startDate = CRM.$(e.currentTarget).data('sdate'); var model = new CRM.BookingApp.Entities.AddSubResource({parent_ref_id:ref, time_required:startDate}); var view = new AddSubResource.AddSubResourceModal({model: model, is_new: true}); view.title = ts('Add Unlimited Resource'); From e8fc4e9c3adaf89d0a5ab3153282df652a55c0d9 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 14:42:36 +0200 Subject: [PATCH 19/51] Don't include add-sub-resource view.js --- CRM/Booking/Form/AddSubResource.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 46321bc4..732726a0 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -290,8 +290,7 @@ static function registerScripts() { ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/civicrm-moment-strftime.js', 140, 'html-header', FALSE) - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 150, 'html-header') - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/view.js', 170, 'html-header'); + ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 150, 'html-header'); $templateDir = CRM_Extension_System::singleton()->getMapper()->keyToBasePath('uk.co.compucorp.civicrm.booking') . '/templates/'; From 2336911153180ba31d3f3d88204091261c2b6629 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 20:18:25 +0200 Subject: [PATCH 20/51] Move the templates to html-header region On the page header regions they were loaded later than the template finder function excution. The missing template caused empty content. --- CRM/Booking/Form/AddSubResource.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 732726a0..3250793a 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -294,7 +294,7 @@ static function registerScripts() { $templateDir = CRM_Extension_System::singleton()->getMapper()->keyToBasePath('uk.co.compucorp.civicrm.booking') . '/templates/'; - $region = CRM_Core_Region::instance('page-header'); + $region = CRM_Core_Region::instance('html-header'); foreach (glob($templateDir . 'CRM/Booking/tpl/add-sub-resource/*.tpl') as $file) { $fileName = substr($file, strlen($templateDir)); $region->add(array( From 382f9c91b22310d167a9c5d87298bcc6dd376a75 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 20:26:57 +0200 Subject: [PATCH 21/51] Partially rendered unlimited resources screen --- js/booking/add-sub-resource/app.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 6913ebf0..a671ef44 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -88,11 +88,12 @@ var ModalRegion = Marionette.Region.extend({ }); //Resource table view var ResourceTableView = Marionette.View.extend({ + resourceTotal: new Array(), template: CRM._.template(CRM.$('#resource-table-template').html()), initialize: function(){ if (CRM.$.trim(CRM.$("#sub_resources").val())) { - this.model.attributes = JSON.parse(CRM.$.trim($("#sub_resources").val())); + this.model.attributes = JSON.parse(CRM.$.trim(CRM.$("#sub_resources").val())); } this.model.attributes.total_price = CRM.$("#total_price").val(); this.model.attributes.sub_total = CRM.$("#sub_total").val(); @@ -126,7 +127,7 @@ var ResourceTableView = Marionette.View.extend({ if(resourceTotalPrice != null){ subtotal += resourceTotalPrice; el.text(resourceTotalPrice.toFixed(2)); - resourceTotal[el.data('ref')] = resourceTotalPrice.toFixed(2); + self.resourceTotal[el.data('ref')] = resourceTotalPrice.toFixed(2); self.$el.find('#crm-booking-sub-resource-row-' + el.data('ref')).show(); } }); @@ -207,6 +208,7 @@ var ResourceTableView = Marionette.View.extend({ $('#crm-booking-sub-resource-row-' + row).toggle(); }, removeSubResource: function(e){ + var self = this; var ref = $(e.currentTarget).data('ref'); var parentRef = $(e.currentTarget).data('parent-ref'); var price = $(e.currentTarget).data('price'); @@ -216,9 +218,9 @@ var ResourceTableView = Marionette.View.extend({ var newResourcePrice = parseFloat(this.model.get("resources")[parentRef]) - parseFloat(price); this.model.attributes.resources[parentRef] = newResourcePrice; - resourceTotal[parentRef] -= parseFloat(price); - try{resourceTotal[parentRef] = resourceTotal[parentRef].toFixed(2);}catch(err){} - $("#resource-total-price-" + parentRef).text(resourceTotal[parentRef]); + self.resourceTotal[parentRef] -= parseFloat(price); + try{self.resourceTotal[parentRef] = self.resourceTotal[parentRef].toFixed(2);}catch(err){} + $("#resource-total-price-" + parentRef).text(self.resourceTotal[parentRef]); var currentSubTotal = this.model.get('sub_total'); var newSubTotal = parseFloat(this.model.get('sub_total') - parseFloat(price)); var currentTotal = this.model.get('total_price'); @@ -283,6 +285,7 @@ var MyApp = Marionette.Application.extend({ onStart: function(app, options) { this.getRegion().show(new ResourceTableView({model: new this.Entities.SubResource()})); }, + Views: Views, }); CRM.BookingApp = new MyApp(); From 79bf915739de468caab10b3759fbb51dba0ae41a Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 21:04:52 +0200 Subject: [PATCH 22/51] Modal region fixes, copy further content to the app --- js/booking/add-sub-resource/app.js | 312 ++++++++++++++++++++++++++++- 1 file changed, 309 insertions(+), 3 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index a671ef44..d5c37b18 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -64,7 +64,7 @@ var ModalRegion = Marionette.Region.extend({ el: "#crm-booking-dialog", constructor: function(){ - Backbone.Marionette.Region.prototype.constructor.apply(this, arguments); + Marionette.Region.prototype.constructor.apply(this, arguments); this.on("show", this.showModal, this); this.on("close", this.hideModal, this); @@ -168,7 +168,7 @@ var ResourceTableView = Marionette.View.extend({ endDate = CRM.$(e.currentTarget).data('edate'); startDate = CRM.$(e.currentTarget).data('sdate'); var model = new CRM.BookingApp.Entities.AddSubResource({parent_ref_id:ref, time_required:startDate}); - var view = new AddSubResource.AddSubResourceModal({model: model, is_new: true}); + var view = new CRM.BookingApp.AddSubResource.AddSubResourceModal({model: model, is_new: true}); view.title = ts('Add Unlimited Resource'); CRM.BookingApp.modal.show(view); }, @@ -262,12 +262,317 @@ var ResourceTableView = Marionette.View.extend({ } }); +var AddSubResource = { + //Sub(Unlimited) resource dialog view + AddSubResourceModal: Views.BookingProcessModal.extend({ + template: "#add-sub-resource-template", + initialize: function(options){ + this.isNew = options.is_new; + }, + events: { + 'click #add-to-basket': 'addSubResource', + 'change #resource_select': 'getConfigurations', + 'change #configuration_select': 'updatePriceEstmate', + 'keypress #quantity': 'updatePriceEstmate', + 'keyup #quantity': 'updatePriceEstmate', + 'keydown #quantity': 'updatePriceEstmate', + }, + onRender: function(){ + BookingApp.Common.Views.BookingProcessModal.prototype.onRender.apply(this, arguments); + + var thisView = this; //set 'this' object for calling inside callback function + this.$el.find('#loading').show(); + + var initsdate = moment(this.model.get('time_required'), "YYYY-MM-DD HH:mm:ss"); + var timeTxt = [initsdate.hours() < 10 ? '0' + initsdate.hours() : initsdate.hours(), ":", initsdate.minute() < 10 ? '0' + initsdate.minute() : initsdate.minute()].join(""); + + //set the formatted months + var month=new Array(); + month[0]="01"; + month[1]="02"; + month[2]="03"; + month[3]="04"; + month[4]="05"; + month[5]="06"; + month[6]="07"; + month[7]="08"; + month[8]="09"; + month[9]="10"; + month[10]="11"; + month[11]="12"; + var dateTxt = [ initsdate.format("DD"),"/", month[initsdate.months()],"/", initsdate.years()].join(""); + this.$el.find("#required_date").val(dateTxt); + this.$el.find("#required_time").val(timeTxt); + + CRM.api('Resource', 'get', {'sequential': 1, 'is_unlimited': 1, 'is_deleted': 0, 'is_active': 1}, + {success: function(data) { + thisView.template = _.template($('#add-sub-resource-template').html()); + //var configValue = CRM_Booking_BAO_BookingConfig::getConfig(); + + thisView.$el.find("#required_date").datepicker({changeMonth: true, changeYear: true, dateFormat: 'dd/mm/yy'}); + thisView.$el.find('#required_time').timeEntry({show24Hours: true}).change(function() { + var log = $('#log'); + log.val(log.val() + ($('#defaultEntry').val() || 'blank') + '\n'); + }); + + //thisView.resources = data.values; + var tpl = _.template($('#select-option-template').html()); + var params = { + context: thisView, + template: tpl, + list: data.values, + element: "#resource_select", + first_option: ['- ', ts('select resource'), ' -'].join("") + } + CRM.BookingApp.vent.trigger("render:options", params); + + if(thisView.isNew == false){ + //set values + thisView.$el.find("#resource_select").val(thisView.model.get('resource').id); + //set configuration option value + thisView.$el.find("#configuration_select").attr('data-selected-id',thisView.model.get('configuration').id); + thisView.getConfigurations(); //call inside callback function + thisView.$el.find("#quantity").val(thisView.model.get('quantity')); + thisView.$el.find("#sub-resource-note").val(thisView.model.get('note')); + thisView.$el.find("#price-estimate").html(thisView.model.get('price_estimate')); + + thisView.$el.find("#quantity").prop('disabled', false); //enable quantity input text + } + thisView.$el.find('#loading').hide(); + thisView.$el.find('#content').show(); + } + } + ); + }, + + beforeClose: function() {this.$('form').find("#required_date").datepicker("destroy");}, + + /** + * Define form validation rules + * + * @param View view the view for which validation rules are created + * @param Object r the validation rules for the view + */ + onValidateRulesCreate: function(view, r) { + $.validator.addMethod("withinValidTime", function(value, element) { + var dateVals = $("#required_date").val().split("/"); + var timeVals = $("#required_time").val().split(":"); + var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); + var minDate = moment(startDate, "YYYY-MM-DD HH:mm:ss"); + var maxDate = moment(endDate, "YYYY-MM-DD HH:mm:ss"); + if (unlimitedTimeConfig==0){ + return true; + }else{ + var val = requiredDate>=minDate && requiredDate<=maxDate; + return val; + } + }, ts("Please select the date and time during the valid booking time.")); + _.extend(r.rules, { + resource_select: { + required: true + }, + configuration_select: { + required: true + }, + "required_date": { + required: true, + "withinValidTime": true + }, + quantity: { + required: true, + digits: true + }, + }); + }, + //calcualte price + updatePriceEstmate: function(e){ + var quantitySelector = this.$el.find('#quantity'); + if(e.type == 'change'){ + var configSelect = this.$el.find('#configuration_select'); + if(configSelect.val() !== ''){ + configSelect.find(':selected').data('price'); + var price = configSelect.find(':selected').data('price'); + this.model.set('configuration', {id: configSelect.val(), label: configSelect.find(':selected').text(), price: price}); + quantitySelector.prop('disabled', false); + }else{ + qualitySelector.prop('disabled', true); + quantitySelector.val(''); + } + } + var configPrice = this.model.get('configuration').price + var quantity = quantitySelector.val(); + if(CRM.BookingApp.Utils.isPositiveInteger(quantity)){ + var priceEstimate = quantity * configPrice; + this.model.set('quantity', quantity); + this.model.set('price_estimate', priceEstimate.toFixed(2)); + this.$el.find('#price-estimate').html(priceEstimate.toFixed(2)); + } + }, + + //render configuration options + getConfigurations: function(e){ + selectedVal = $('#resource_select').val(); + if(selectedVal !== ""){ + var params = { + id: selectedVal, + sequential: 1, + 'api.resource_config_set.get': { + id: '$value.set_id', + is_active: 1, + is_deleted: 0, + 'api.resource_config_option.get': { + set_id: '$value.id', + is_active: 1, + is_deleted: 0, + 'api.option_group.get':{ + name: 'booking_size_unit', + }, + 'api.option_value.get':{ + value: '$value.unit_id', + sequential: 1, + option_group_id: '$value.api.option_group.get.id' + } + } + } + }; + this.$el.find('#config-loading').show(); + this.$el.find('#configuration_select').hide(); + var self = this; + CRM.api('Resource', 'get', params, + { context: self, + success: function(data) { + var resource = data['values']['0']; + var configSet = data['values']['0']['api.resource_config_set.get']; + if(configSet.count !== 1){ + var url = CRM.url('civicrm/admin/resource/config_set', { + reset: 1, + action: 'update', + id: resource.id + }); + CRM.alert(ts(''), ts('Your resource configuration set is disabled.') + + ' ' + ' ' + ts('Click here to edit configuration set.') + ' ', 'error'); + } + else if(configSet['values']['0']['api.resource_config_option.get'].count < 1){ + var url = CRM.url('civicrm/admin/resource/config_set/config_option', { + reset: 1, + sid: configSet['values']['0'].id + }); + CRM.alert(ts(''), ts('Your resource configuration options are all disabled or none have been created.') + + ' ' + ' ' + ts('Click here to edit or create options.') + ' ', 'error'); + } + else{ + var options = data['values']['0']['api.resource_config_set.get']['values']['0']['api.resource_config_option.get']['values']; + self.model.set('resource', {id: resource.id, label: resource.label}); + var params = { + context: self, + template: _.template($('#select-config-option-template').html()), + list: options, + element: "#configuration_select", + first_option: '- ' + ts('select configuration') + ' -' + } + CRM.BookingApp.vent.trigger("render:options", params); + //set configuration options for edit mode of subresource view + var configSelectedId = this.$el.find('#configuration_select').data('selected-id'); //retrieve data from data-selected-id attribute + if(configSelectedId != 'undefined'){ + this.$el.find('#configuration_select').val(configSelectedId); + } + this.$el.find('#config-loading').hide(); + this.$el.find('#configuration_select').show(); + } + } + }); + }else{ + var params = { + context:this, + template: _.template($('#select-config-option-template').html()), + list: new Array(), + element: "#configuration_select", + first_option: '- ' + ts('select configuration') + ' -'} + CRM.BookingApp.vent.trigger("render:options", params); + this.$el.find('#configuration_select').prop('disabled', true); + } + }, + + //save sub-resource + addSubResource: function(e){ + e.preventDefault(); + if (!this.$('form').valid()) { + var errors = this.$('form').validate().errors(); + this.onRenderError(errors); + return false; + } + this.$('form').find("#required_date").datepicker("destroy"); + this.model.set('note', this.$el.find('#sub-resource-note').val()); + var dateVals = this.$el.find("#required_date").val().split("/"); + var timeVals = this.$el.find("#required_time").val().split(":"); + var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); + var timeRequired = moment(requiredDate).format("YYYY-M-D HH:mm"); + this.model.set('time_required', timeRequired); + + var parentRefId = this.model.get('parent_ref_id'); + + var refId = null; + if(this.isNew){ + refId = CRM.BookingApp.Utils.getCurrentUnixTimstamp(); + this.model.set('ref_id', refId); + }else{ + refId = this.model.get('ref_id'); + } + + var template = _.template($('#sub-resource-row-template').html()); + + //ui update + if(this.isNew){ + $('#crm-booking-sub-resource-table-' + parentRefId).find('tbody').append(template(this.model.toJSON())); + }else{ + $('#crm-booking-sub-resource-individual-row-'+refId).replaceWith(template(this.model.toJSON())); + } + $('#crm-booking-sub-resource-row-' + parentRefId).show(); + var resourceRefId = this.model.get("parent_ref_id"); + var priceEstimate = this.model.get("price_estimate"); + var subResourceRefId = this.model.get("ref_id"); + + var subResourceModel = CRM.BookingApp.main.currentView.model; + subResourceModel.attributes.sub_resources[refId] = this.model.toJSON(); + + var currentSubTotal = subResourceModel.get('sub_total'); + var newSubTotal = parseFloat(priceEstimate) + parseFloat(subResourceModel.get('sub_total')); + subResourceModel.set("sub_total", newSubTotal); + + var currentTotal = subResourceModel.get('total_price'); + var newTotal = (parseFloat(currentTotal) - parseFloat(currentSubTotal)) + parseFloat(newSubTotal); + subResourceModel.set("total_price", newTotal); + + var currentResourceTotal = resourceTotal[resourceRefId]; + + if(this.isNew){ + var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate); + priceCache[subResourceRefId] = parseFloat(priceEstimate); + }else{ + var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate) - priceCache[subResourceRefId]; + subResourceModel.attributes.sub_total = subResourceModel.attributes.sub_total - priceCache[subResourceRefId]; + subResourceModel.attributes.total_price = subResourceModel.attributes.total_price - priceCache[subResourceRefId]; + priceCache[subResourceRefId] = parseFloat(priceEstimate); + } + resourceTotal[resourceRefId] = resourceTotalPrice; + subResourceModel.attributes.resources[resourceRefId] = resourceTotalPrice.toFixed(2); + //set total price for resource row + $("#resource-total-price-" + resourceRefId).text(subResourceModel.attributes.resources[resourceRefId]); + CRM.BookingApp.vent.trigger('render:price', subResourceModel, resourceRefId ); + CRM.BookingApp.vent.trigger('update:resources', subResourceModel); + CRM.BookingApp.modal.close(this); + }, + + }), +} // The addRegions method has been removed and not present in the // current Marionette version. The Application.extend could be the // solution for passing the necessary parameters to the application. var MyApp = Marionette.Application.extend({ region: "#resource-main", - modal: ModalRegion, + modal: new ModalRegion(), Utils: { //http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript getCurrentUnixTimstamp: function () { @@ -286,6 +591,7 @@ var MyApp = Marionette.Application.extend({ this.getRegion().show(new ResourceTableView({model: new this.Entities.SubResource()})); }, Views: Views, + AddSubResource: AddSubResource, }); CRM.BookingApp = new MyApp(); From bd0d01dd1a581145475b11298dfa3992658bd94a Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 10 Jun 2021 21:51:40 +0200 Subject: [PATCH 23/51] Fix various js issues, delete some code from the views.js --- js/booking/add-sub-resource/app.js | 74 ++++--- js/booking/add-sub-resource/view.js | 307 ---------------------------- 2 files changed, 42 insertions(+), 339 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index d5c37b18..38fdff67 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -169,6 +169,16 @@ var ResourceTableView = Marionette.View.extend({ startDate = CRM.$(e.currentTarget).data('sdate'); var model = new CRM.BookingApp.Entities.AddSubResource({parent_ref_id:ref, time_required:startDate}); var view = new CRM.BookingApp.AddSubResource.AddSubResourceModal({model: model, is_new: true}); + view.on("render:options", function(options) { + var select = options.context.$el.find(options.element); + if (select.is('[disabled]')) { + select.prop('disabled', false); + } + select.html(options.template({ + options : options.list, + first_option : options.first_option + })); + }); view.title = ts('Add Unlimited Resource'); CRM.BookingApp.modal.show(view); }, @@ -265,7 +275,7 @@ var ResourceTableView = Marionette.View.extend({ var AddSubResource = { //Sub(Unlimited) resource dialog view AddSubResourceModal: Views.BookingProcessModal.extend({ - template: "#add-sub-resource-template", + template: CRM._.template(CRM.$('#add-sub-resource-template').html()), initialize: function(options){ this.isNew = options.is_new; }, @@ -278,7 +288,7 @@ var AddSubResource = { 'keydown #quantity': 'updatePriceEstmate', }, onRender: function(){ - BookingApp.Common.Views.BookingProcessModal.prototype.onRender.apply(this, arguments); + Views.BookingProcessModal.prototype.onRender.apply(this, arguments); var thisView = this; //set 'this' object for calling inside callback function this.$el.find('#loading').show(); @@ -306,7 +316,7 @@ var AddSubResource = { CRM.api('Resource', 'get', {'sequential': 1, 'is_unlimited': 1, 'is_deleted': 0, 'is_active': 1}, {success: function(data) { - thisView.template = _.template($('#add-sub-resource-template').html()); + thisView.template = CRM._.template(CRM.$('#add-sub-resource-template').html()); //var configValue = CRM_Booking_BAO_BookingConfig::getConfig(); thisView.$el.find("#required_date").datepicker({changeMonth: true, changeYear: true, dateFormat: 'dd/mm/yy'}); @@ -316,7 +326,7 @@ var AddSubResource = { }); //thisView.resources = data.values; - var tpl = _.template($('#select-option-template').html()); + var tpl = CRM._.template(CRM.$('#select-option-template').html()); var params = { context: thisView, template: tpl, @@ -324,7 +334,7 @@ var AddSubResource = { element: "#resource_select", first_option: ['- ', ts('select resource'), ' -'].join("") } - CRM.BookingApp.vent.trigger("render:options", params); + thisView.triggerMethod("render:options", params); if(thisView.isNew == false){ //set values @@ -354,7 +364,7 @@ var AddSubResource = { * @param Object r the validation rules for the view */ onValidateRulesCreate: function(view, r) { - $.validator.addMethod("withinValidTime", function(value, element) { + CRM.$.validator.addMethod("withinValidTime", function(value, element) { var dateVals = $("#required_date").val().split("/"); var timeVals = $("#required_time").val().split(":"); var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); @@ -367,7 +377,7 @@ var AddSubResource = { return val; } }, ts("Please select the date and time during the valid booking time.")); - _.extend(r.rules, { + CRM._.extend(r.rules, { resource_select: { required: true }, @@ -402,7 +412,7 @@ var AddSubResource = { var configPrice = this.model.get('configuration').price var quantity = quantitySelector.val(); if(CRM.BookingApp.Utils.isPositiveInteger(quantity)){ - var priceEstimate = quantity * configPrice; + var priceEstimate = quantity * configPrice; this.model.set('quantity', quantity); this.model.set('price_estimate', priceEstimate.toFixed(2)); this.$el.find('#price-estimate').html(priceEstimate.toFixed(2)); @@ -411,30 +421,30 @@ var AddSubResource = { //render configuration options getConfigurations: function(e){ - selectedVal = $('#resource_select').val(); + selectedVal = CRM.$('#resource_select').val(); if(selectedVal !== ""){ var params = { - id: selectedVal, - sequential: 1, - 'api.resource_config_set.get': { - id: '$value.set_id', - is_active: 1, - is_deleted: 0, - 'api.resource_config_option.get': { - set_id: '$value.id', - is_active: 1, - is_deleted: 0, - 'api.option_group.get':{ - name: 'booking_size_unit', - }, - 'api.option_value.get':{ - value: '$value.unit_id', - sequential: 1, - option_group_id: '$value.api.option_group.get.id' - } - } + id: selectedVal, + sequential: 1, + 'api.resource_config_set.get': { + id: '$value.set_id', + is_active: 1, + is_deleted: 0, + 'api.resource_config_option.get': { + set_id: '$value.id', + is_active: 1, + is_deleted: 0, + 'api.option_group.get':{ + name: 'booking_size_unit', + }, + 'api.option_value.get':{ + value: '$value.unit_id', + sequential: 1, + option_group_id: '$value.api.option_group.get.id' } - }; + } + } + }; this.$el.find('#config-loading').show(); this.$el.find('#configuration_select').hide(); var self = this; @@ -467,12 +477,12 @@ var AddSubResource = { self.model.set('resource', {id: resource.id, label: resource.label}); var params = { context: self, - template: _.template($('#select-config-option-template').html()), + template: _.template(CRM.$('#select-config-option-template').html()), list: options, element: "#configuration_select", first_option: '- ' + ts('select configuration') + ' -' } - CRM.BookingApp.vent.trigger("render:options", params); + self.triggerMethod("render:options", params); //set configuration options for edit mode of subresource view var configSelectedId = this.$el.find('#configuration_select').data('selected-id'); //retrieve data from data-selected-id attribute if(configSelectedId != 'undefined'){ @@ -490,7 +500,7 @@ var AddSubResource = { list: new Array(), element: "#configuration_select", first_option: '- ' + ts('select configuration') + ' -'} - CRM.BookingApp.vent.trigger("render:options", params); + self.triggerMethod("render:options", params); this.$el.find('#configuration_select').prop('disabled', true); } }, diff --git a/js/booking/add-sub-resource/view.js b/js/booking/add-sub-resource/view.js index d9445d9a..2f9ad969 100755 --- a/js/booking/add-sub-resource/view.js +++ b/js/booking/add-sub-resource/view.js @@ -50,313 +50,6 @@ CRM.BookingApp.module('AddSubResource', function(AddSubResource, BookingApp, Bac })); }); - //Sub(Unlimited) resource dialog view - AddSubResource.AddSubResourceModal = BookingApp.Common.Views.BookingProcessModal.extend({ - template: "#add-sub-resource-template", - initialize: function(options){ - this.isNew = options.is_new; - }, - events: { - 'click #add-to-basket': 'addSubResource', - 'change #resource_select': 'getConfigurations', - 'change #configuration_select': 'updatePriceEstmate', - 'keypress #quantity': 'updatePriceEstmate', - 'keyup #quantity': 'updatePriceEstmate', - 'keydown #quantity': 'updatePriceEstmate', - }, - onRender: function(){ - BookingApp.Common.Views.BookingProcessModal.prototype.onRender.apply(this, arguments); - - var thisView = this; //set 'this' object for calling inside callback function - this.$el.find('#loading').show(); - - - - var initsdate = moment(this.model.get('time_required'), "YYYY-MM-DD HH:mm:ss"); - var timeTxt = [initsdate.hours() < 10 ? '0' + initsdate.hours() : initsdate.hours(), ":", initsdate.minute() < 10 ? '0' + initsdate.minute() : initsdate.minute()].join(""); - - //set the formatted months - var month=new Array(); - month[0]="01"; - month[1]="02"; - month[2]="03"; - month[3]="04"; - month[4]="05"; - month[5]="06"; - month[6]="07"; - month[7]="08"; - month[8]="09"; - month[9]="10"; - month[10]="11"; - month[11]="12"; - var dateTxt = [ initsdate.format("DD"),"/", month[initsdate.months()],"/", initsdate.years()].join(""); - this.$el.find("#required_date").val(dateTxt); - this.$el.find("#required_time").val(timeTxt); - - CRM.api('Resource', 'get', {'sequential': 1, 'is_unlimited': 1, 'is_deleted': 0, 'is_active': 1}, - {success: function(data) { - thisView.template = _.template($('#add-sub-resource-template').html()); - //var configValue = CRM_Booking_BAO_BookingConfig::getConfig(); - - thisView.$el.find("#required_date").datepicker({changeMonth: true, changeYear: true, dateFormat: 'dd/mm/yy'}); - thisView.$el.find('#required_time').timeEntry({show24Hours: true}).change(function() { - var log = $('#log'); - log.val(log.val() + ($('#defaultEntry').val() || 'blank') + '\n'); - }); - - //thisView.resources = data.values; - var tpl = _.template($('#select-option-template').html()); - var params = { - context: thisView, - template: tpl, - list: data.values, - element: "#resource_select", - first_option: ['- ', ts('select resource'), ' -'].join("") - } - CRM.BookingApp.vent.trigger("render:options", params); - - if(thisView.isNew == false){ - //set values - thisView.$el.find("#resource_select").val(thisView.model.get('resource').id); - //set configuration option value - thisView.$el.find("#configuration_select").attr('data-selected-id',thisView.model.get('configuration').id); - thisView.getConfigurations(); //call inside callback function - thisView.$el.find("#quantity").val(thisView.model.get('quantity')); - thisView.$el.find("#sub-resource-note").val(thisView.model.get('note')); - thisView.$el.find("#price-estimate").html(thisView.model.get('price_estimate')); - - thisView.$el.find("#quantity").prop('disabled', false); //enable quantity input text - } - thisView.$el.find('#loading').hide(); - thisView.$el.find('#content').show(); - } - } - ); - }, - - beforeClose: function() {this.$('form').find("#required_date").datepicker("destroy");}, - - /** - * Define form validation rules - * - * @param View view the view for which validation rules are created - * @param Object r the validation rules for the view - */ - onValidateRulesCreate: function(view, r) { - $.validator.addMethod("withinValidTime", function(value, element) { - var dateVals = $("#required_date").val().split("/"); - var timeVals = $("#required_time").val().split(":"); - var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); - var minDate = moment(startDate, "YYYY-MM-DD HH:mm:ss"); - var maxDate = moment(endDate, "YYYY-MM-DD HH:mm:ss"); - if (unlimitedTimeConfig==0){ - return true; - }else{ - var val = requiredDate>=minDate && requiredDate<=maxDate; - return val; - } - }, ts("Please select the date and time during the valid booking time.")); - _.extend(r.rules, { - resource_select: { - required: true - }, - configuration_select: { - required: true - }, - "required_date": { - required: true, - "withinValidTime": true - }, - quantity: { - required: true, - digits: true - }, - }); - }, - //calcualte price - updatePriceEstmate: function(e){ - var quantitySelector = this.$el.find('#quantity'); - if(e.type == 'change'){ - var configSelect = this.$el.find('#configuration_select'); - if(configSelect.val() !== ''){ - configSelect.find(':selected').data('price'); - var price = configSelect.find(':selected').data('price'); - this.model.set('configuration', {id: configSelect.val(), label: configSelect.find(':selected').text(), price: price}); - quantitySelector.prop('disabled', false); - }else{ - qualitySelector.prop('disabled', true); - quantitySelector.val(''); - } - } - var configPrice = this.model.get('configuration').price - var quantity = quantitySelector.val(); - if(CRM.BookingApp.Utils.isPositiveInteger(quantity)){ - var priceEstimate = quantity * configPrice; - this.model.set('quantity', quantity); - this.model.set('price_estimate', priceEstimate.toFixed(2)); - this.$el.find('#price-estimate').html(priceEstimate.toFixed(2)); - } - }, - - //render configuration options - getConfigurations: function(e){ - selectedVal = $('#resource_select').val(); - if(selectedVal !== ""){ - var params = { - id: selectedVal, - sequential: 1, - 'api.resource_config_set.get': { - id: '$value.set_id', - is_active: 1, - is_deleted: 0, - 'api.resource_config_option.get': { - set_id: '$value.id', - is_active: 1, - is_deleted: 0, - 'api.option_group.get':{ - name: 'booking_size_unit', - }, - 'api.option_value.get':{ - value: '$value.unit_id', - sequential: 1, - option_group_id: '$value.api.option_group.get.id' - } - } - } - }; - this.$el.find('#config-loading').show(); - this.$el.find('#configuration_select').hide(); - var self = this; - CRM.api('Resource', 'get', params, - { context: self, - success: function(data) { - var resource = data['values']['0']; - var configSet = data['values']['0']['api.resource_config_set.get']; - if(configSet.count !== 1){ - var url = CRM.url('civicrm/admin/resource/config_set', { - reset: 1, - action: 'update', - id: resource.id - }); - CRM.alert(ts(''), ts('Your resource configuration set is disabled.') - + ' ' + ' ' + ts('Click here to edit configuration set.') + ' ', 'error'); - } - else if(configSet['values']['0']['api.resource_config_option.get'].count < 1){ - var url = CRM.url('civicrm/admin/resource/config_set/config_option', { - reset: 1, - sid: configSet['values']['0'].id - }); - CRM.alert(ts(''), ts('Your resource configuration options are all disabled or none have been created.') - + ' ' + ' ' + ts('Click here to edit or create options.') + ' ', 'error'); - } - else{ - var options = data['values']['0']['api.resource_config_set.get']['values']['0']['api.resource_config_option.get']['values']; - self.model.set('resource', {id: resource.id, label: resource.label}); - var params = { - context: self, - template: _.template($('#select-config-option-template').html()), - list: options, - element: "#configuration_select", - first_option: '- ' + ts('select configuration') + ' -' - } - CRM.BookingApp.vent.trigger("render:options", params); - //set configuration options for edit mode of subresource view - var configSelectedId = this.$el.find('#configuration_select').data('selected-id'); //retrieve data from data-selected-id attribute - if(configSelectedId != 'undefined'){ - this.$el.find('#configuration_select').val(configSelectedId); - } - this.$el.find('#config-loading').hide(); - this.$el.find('#configuration_select').show(); - } - } - }); - }else{ - var params = { - context:this, - template: _.template($('#select-config-option-template').html()), - list: new Array(), - element: "#configuration_select", - first_option: '- ' + ts('select configuration') + ' -'} - CRM.BookingApp.vent.trigger("render:options", params); - this.$el.find('#configuration_select').prop('disabled', true); - } - }, - - //save sub-resource - addSubResource: function(e){ - e.preventDefault(); - if (!this.$('form').valid()) { - var errors = this.$('form').validate().errors(); - this.onRenderError(errors); - return false; - } - this.$('form').find("#required_date").datepicker("destroy"); - this.model.set('note', this.$el.find('#sub-resource-note').val()); - var dateVals = this.$el.find("#required_date").val().split("/"); - var timeVals = this.$el.find("#required_time").val().split(":"); - var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); - var timeRequired = moment(requiredDate).format("YYYY-M-D HH:mm"); - this.model.set('time_required', timeRequired); - - var parentRefId = this.model.get('parent_ref_id'); - - var refId = null; - if(this.isNew){ - refId = CRM.BookingApp.Utils.getCurrentUnixTimstamp(); - this.model.set('ref_id', refId); - }else{ - refId = this.model.get('ref_id'); - } - - var template = _.template($('#sub-resource-row-template').html()); - - //ui update - if(this.isNew){ - $('#crm-booking-sub-resource-table-' + parentRefId).find('tbody').append(template(this.model.toJSON())); - }else{ - $('#crm-booking-sub-resource-individual-row-'+refId).replaceWith(template(this.model.toJSON())); - } - $('#crm-booking-sub-resource-row-' + parentRefId).show(); - var resourceRefId = this.model.get("parent_ref_id"); - var priceEstimate = this.model.get("price_estimate"); - var subResourceRefId = this.model.get("ref_id"); - - var subResourceModel = CRM.BookingApp.main.currentView.model; - subResourceModel.attributes.sub_resources[refId] = this.model.toJSON(); - - var currentSubTotal = subResourceModel.get('sub_total'); - var newSubTotal = parseFloat(priceEstimate) + parseFloat(subResourceModel.get('sub_total')); - subResourceModel.set("sub_total", newSubTotal); - - var currentTotal = subResourceModel.get('total_price'); - var newTotal = (parseFloat(currentTotal) - parseFloat(currentSubTotal)) + parseFloat(newSubTotal); - subResourceModel.set("total_price", newTotal); - - var currentResourceTotal = resourceTotal[resourceRefId]; - - - if(this.isNew){ - var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate); - priceCache[subResourceRefId] = parseFloat(priceEstimate); - }else{ - var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate) - priceCache[subResourceRefId]; - subResourceModel.attributes.sub_total = subResourceModel.attributes.sub_total - priceCache[subResourceRefId]; - subResourceModel.attributes.total_price = subResourceModel.attributes.total_price - priceCache[subResourceRefId]; - priceCache[subResourceRefId] = parseFloat(priceEstimate); - } - resourceTotal[resourceRefId] = resourceTotalPrice; - subResourceModel.attributes.resources[resourceRefId] = resourceTotalPrice.toFixed(2); - //set total price for resource row - $("#resource-total-price-" + resourceRefId).text(subResourceModel.attributes.resources[resourceRefId]); - CRM.BookingApp.vent.trigger('render:price', subResourceModel, resourceRefId ); - CRM.BookingApp.vent.trigger('update:resources', subResourceModel); - CRM.BookingApp.modal.close(this); - }, - - }); - //Additaional charges dialog view AddSubResource.EditAdhocChargesModal = BookingApp.Common.Views.BookingProcessModal.extend({ template: "#edit-adhoc-charges-template", From 98483c3bdbc90475f01e1484a737b26ee8d69313 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 11:21:58 +0200 Subject: [PATCH 24/51] Further fixes - replace a bunch of $ to CRM.$ Also fix the form selector --- js/booking/add-sub-resource/app.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 38fdff67..83018e84 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -365,8 +365,8 @@ var AddSubResource = { */ onValidateRulesCreate: function(view, r) { CRM.$.validator.addMethod("withinValidTime", function(value, element) { - var dateVals = $("#required_date").val().split("/"); - var timeVals = $("#required_time").val().split(":"); + var dateVals = CRM.$("#required_date").val().split("/"); + var timeVals = CRM.$("#required_time").val().split(":"); var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); var minDate = moment(startDate, "YYYY-MM-DD HH:mm:ss"); var maxDate = moment(endDate, "YYYY-MM-DD HH:mm:ss"); @@ -508,12 +508,16 @@ var AddSubResource = { //save sub-resource addSubResource: function(e){ e.preventDefault(); - if (!this.$('form').valid()) { - var errors = this.$('form').validate().errors(); + var targetForm = e.currentTarget; + while(targetForm.tagName != 'FORM') { + targetForm = targetForm.parentNode; + } + if (!CRM.$(targetForm).valid()) { + var errors = CRM.$(targetForm).validate().errors(); this.onRenderError(errors); return false; } - this.$('form').find("#required_date").datepicker("destroy"); + CRM.$(targetForm).find("#required_date").datepicker("destroy"); this.model.set('note', this.$el.find('#sub-resource-note').val()); var dateVals = this.$el.find("#required_date").val().split("/"); var timeVals = this.$el.find("#required_time").val().split(":"); @@ -531,15 +535,15 @@ var AddSubResource = { refId = this.model.get('ref_id'); } - var template = _.template($('#sub-resource-row-template').html()); + var template = _.template(CRM.$('#sub-resource-row-template').html()); //ui update if(this.isNew){ - $('#crm-booking-sub-resource-table-' + parentRefId).find('tbody').append(template(this.model.toJSON())); + CRM.$('#crm-booking-sub-resource-table-' + parentRefId).find('tbody').append(template(this.model.toJSON())); }else{ - $('#crm-booking-sub-resource-individual-row-'+refId).replaceWith(template(this.model.toJSON())); + CRM.$('#crm-booking-sub-resource-individual-row-'+refId).replaceWith(template(this.model.toJSON())); } - $('#crm-booking-sub-resource-row-' + parentRefId).show(); + CRM.$('#crm-booking-sub-resource-row-' + parentRefId).show(); var resourceRefId = this.model.get("parent_ref_id"); var priceEstimate = this.model.get("price_estimate"); var subResourceRefId = this.model.get("ref_id"); @@ -569,7 +573,7 @@ var AddSubResource = { resourceTotal[resourceRefId] = resourceTotalPrice; subResourceModel.attributes.resources[resourceRefId] = resourceTotalPrice.toFixed(2); //set total price for resource row - $("#resource-total-price-" + resourceRefId).text(subResourceModel.attributes.resources[resourceRefId]); + CRM.$("#resource-total-price-" + resourceRefId).text(subResourceModel.attributes.resources[resourceRefId]); CRM.BookingApp.vent.trigger('render:price', subResourceModel, resourceRefId ); CRM.BookingApp.vent.trigger('update:resources', subResourceModel); CRM.BookingApp.modal.close(this); From 20bb3912140d1bdc0cd74a50ca002cd1bfed6a42 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 11:49:23 +0200 Subject: [PATCH 25/51] Fix variable instead of main the getRegion function needs to be used --- js/booking/add-sub-resource/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 83018e84..75387aa4 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -548,7 +548,7 @@ var AddSubResource = { var priceEstimate = this.model.get("price_estimate"); var subResourceRefId = this.model.get("ref_id"); - var subResourceModel = CRM.BookingApp.main.currentView.model; + var subResourceModel = CRM.BookingApp.getRegion().currentView.model; subResourceModel.attributes.sub_resources[refId] = this.model.toJSON(); var currentSubTotal = subResourceModel.get('sub_total'); From ebdf0f45e2580fc22f0365ffdb75d8b9498e0273 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 11:54:08 +0200 Subject: [PATCH 26/51] Make PriceCache, ResourceTotal variables global --- js/booking/add-sub-resource/app.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 75387aa4..83b158f7 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -86,9 +86,10 @@ var ModalRegion = Marionette.Region.extend({ cj('#crm-booking-dialog').dialog().dialog("close"); } }); +var ResourceTotal = new Array(); +var PriceCache = new Array(); //Resource table view var ResourceTableView = Marionette.View.extend({ - resourceTotal: new Array(), template: CRM._.template(CRM.$('#resource-table-template').html()), initialize: function(){ @@ -113,7 +114,7 @@ var ResourceTableView = Marionette.View.extend({ var template = CRM._.template(CRM.$('#sub-resource-row-template').html()); CRM._.each(this.model.get('sub_resources'), function (item, key){ self.$el.find("#crm-booking-sub-resource-table-" + item.parent_ref_id).append(template(item)); - priceCache[item.ref_id] = item.price_estimate; + PriceCache[item.ref_id] = item.price_estimate; items.push(item); }); this.$el.find("span[id^='resource-total-price-']").each(function(){ @@ -127,7 +128,7 @@ var ResourceTableView = Marionette.View.extend({ if(resourceTotalPrice != null){ subtotal += resourceTotalPrice; el.text(resourceTotalPrice.toFixed(2)); - self.resourceTotal[el.data('ref')] = resourceTotalPrice.toFixed(2); + ResourceTotal[el.data('ref')] = resourceTotalPrice.toFixed(2); self.$el.find('#crm-booking-sub-resource-row-' + el.data('ref')).show(); } }); @@ -164,7 +165,6 @@ var ResourceTableView = Marionette.View.extend({ addSubResource: function(e){ var ref = CRM.$(e.currentTarget).data('ref');///////////////// - //resourceTotal[ref] = 0; endDate = CRM.$(e.currentTarget).data('edate'); startDate = CRM.$(e.currentTarget).data('sdate'); var model = new CRM.BookingApp.Entities.AddSubResource({parent_ref_id:ref, time_required:startDate}); @@ -228,9 +228,9 @@ var ResourceTableView = Marionette.View.extend({ var newResourcePrice = parseFloat(this.model.get("resources")[parentRef]) - parseFloat(price); this.model.attributes.resources[parentRef] = newResourcePrice; - self.resourceTotal[parentRef] -= parseFloat(price); - try{self.resourceTotal[parentRef] = self.resourceTotal[parentRef].toFixed(2);}catch(err){} - $("#resource-total-price-" + parentRef).text(self.resourceTotal[parentRef]); + ResourceTotal[parentRef] -= parseFloat(price); + try{ResourceTotal[parentRef] = ResourceTotal[parentRef].toFixed(2);}catch(err){} + $("#resource-total-price-" + parentRef).text(ResourceTotal[parentRef]); var currentSubTotal = this.model.get('sub_total'); var newSubTotal = parseFloat(this.model.get('sub_total') - parseFloat(price)); var currentTotal = this.model.get('total_price'); @@ -559,18 +559,18 @@ var AddSubResource = { var newTotal = (parseFloat(currentTotal) - parseFloat(currentSubTotal)) + parseFloat(newSubTotal); subResourceModel.set("total_price", newTotal); - var currentResourceTotal = resourceTotal[resourceRefId]; + var currentResourceTotal = ResourceTotal[resourceRefId]; if(this.isNew){ var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate); - priceCache[subResourceRefId] = parseFloat(priceEstimate); + PriceCache[subResourceRefId] = parseFloat(priceEstimate); }else{ - var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate) - priceCache[subResourceRefId]; - subResourceModel.attributes.sub_total = subResourceModel.attributes.sub_total - priceCache[subResourceRefId]; - subResourceModel.attributes.total_price = subResourceModel.attributes.total_price - priceCache[subResourceRefId]; - priceCache[subResourceRefId] = parseFloat(priceEstimate); + var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate) - PriceCache[subResourceRefId]; + subResourceModel.attributes.sub_total = subResourceModel.attributes.sub_total - PriceCache[subResourceRefId]; + subResourceModel.attributes.total_price = subResourceModel.attributes.total_price - PriceCache[subResourceRefId]; + PriceCache[subResourceRefId] = parseFloat(priceEstimate); } - resourceTotal[resourceRefId] = resourceTotalPrice; + ResourceTotal[resourceRefId] = resourceTotalPrice; subResourceModel.attributes.resources[resourceRefId] = resourceTotalPrice.toFixed(2); //set total price for resource row CRM.$("#resource-total-price-" + resourceRefId).text(subResourceModel.attributes.resources[resourceRefId]); From 0b35085d336d66e06f2af46e7a8d02578d40ce59 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 13:46:07 +0200 Subject: [PATCH 27/51] Move event handlers to app --- js/booking/add-sub-resource/app.js | 28 +++++++++++++++++++-- js/booking/add-sub-resource/view.js | 39 ----------------------------- 2 files changed, 26 insertions(+), 41 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 83b158f7..1ea12b98 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -179,6 +179,29 @@ var ResourceTableView = Marionette.View.extend({ first_option : options.first_option })); }); + view.on("update:resources", function(model) { + CRM.$('#sub_resources').val(JSON.stringify(model.toJSON())); + }); + view.on("render:price", function(model) { + CRM.$("#total_price").val(model.attributes.total_price); + var totalText = model.attributes.total_price; + try{ + if(model.attributes.total_price>=0){ + var totalText = model.attributes.total_price.toFixed(2); + } + }catch(err){} + CRM.$("#total-price-summary").text(totalText); + CRM.$("#discount_amount").val(model.attributes.discount_amount); + CRM.$('#discount_amount_dummy').val(model.attributes.discount_amount); + CRM.$("#sub_total").val(model.attributes.sub_total); + var subtotalText = model.attributes.sub_total; + try{ + var subtotalText = model.attributes.sub_total.toFixed(2); + }catch(err){} + CRM.$("#sub-total-summary").text(subtotalText); + CRM.$('#adhoc_charge').val(model.attributes.adhoc_charges.total); + CRM.$('#ad-hoc-charge-summary').html(model.attributes.adhoc_charges.total); + }); view.title = ts('Add Unlimited Resource'); CRM.BookingApp.modal.show(view); }, @@ -508,6 +531,7 @@ var AddSubResource = { //save sub-resource addSubResource: function(e){ e.preventDefault(); + var self = this; var targetForm = e.currentTarget; while(targetForm.tagName != 'FORM') { targetForm = targetForm.parentNode; @@ -574,8 +598,8 @@ var AddSubResource = { subResourceModel.attributes.resources[resourceRefId] = resourceTotalPrice.toFixed(2); //set total price for resource row CRM.$("#resource-total-price-" + resourceRefId).text(subResourceModel.attributes.resources[resourceRefId]); - CRM.BookingApp.vent.trigger('render:price', subResourceModel, resourceRefId ); - CRM.BookingApp.vent.trigger('update:resources', subResourceModel); + self.triggerMethod('render:price', subResourceModel, resourceRefId ); + self.triggerMethod('update:resources', subResourceModel); CRM.BookingApp.modal.close(this); }, diff --git a/js/booking/add-sub-resource/view.js b/js/booking/add-sub-resource/view.js index 2f9ad969..ee004133 100755 --- a/js/booking/add-sub-resource/view.js +++ b/js/booking/add-sub-resource/view.js @@ -11,45 +11,6 @@ CRM.BookingApp.module('AddSubResource', function(AddSubResource, BookingApp, Bac var resourceTotal = new Array(); var priceCache = new Array(); - CRM.BookingApp.vent.on("update:resources", function(model) { - $('#sub_resources').val(JSON.stringify(model.toJSON())); - }); - - CRM.BookingApp.vent.on("render:price", function(model) { - $("#total_price").val(model.attributes.total_price); - var totalText = model.attributes.total_price; - try{ - if(model.attributes.total_price>=0){ - var totalText = model.attributes.total_price.toFixed(2); - } - }catch(err){} - $("#total-price-summary").text(totalText); - - $("#discount_amount").val(model.attributes.discount_amount); - $('#discount_amount_dummy').val(model.attributes.discount_amount); - - $("#sub_total").val(model.attributes.sub_total); - var subtotalText = model.attributes.sub_total; - try{ - var subtotalText = model.attributes.sub_total.toFixed(2); - }catch(err){} - $("#sub-total-summary").text(subtotalText); - - $('#adhoc_charge').val(model.attributes.adhoc_charges.total); - $('#ad-hoc-charge-summary').html(model.attributes.adhoc_charges.total); - }); - - CRM.BookingApp.vent.on("render:options", function(options) { - var select = options.context.$el.find(options.element); - if (select.is('[disabled]')) { - select.prop('disabled', false); - } - select.html(options.template({ - options : options.list, - first_option : options.first_option - })); - }); - //Additaional charges dialog view AddSubResource.EditAdhocChargesModal = BookingApp.Common.Views.BookingProcessModal.extend({ template: "#edit-adhoc-charges-template", From 968d773db9ca53143694555e559f0f769ee0db73 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 14:01:45 +0200 Subject: [PATCH 28/51] Trigger modal close event --- js/booking/add-sub-resource/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 1ea12b98..71c3da3e 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -600,7 +600,7 @@ var AddSubResource = { CRM.$("#resource-total-price-" + resourceRefId).text(subResourceModel.attributes.resources[resourceRefId]); self.triggerMethod('render:price', subResourceModel, resourceRefId ); self.triggerMethod('update:resources', subResourceModel); - CRM.BookingApp.modal.close(this); + CRM.BookingApp.modal.triggerMethod('close'); }, }), From 591554766e16ebbf6c51a39e01fa1ba803361656 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 15:31:51 +0200 Subject: [PATCH 29/51] Duplicate event listeners for displaying the selects in the edit modal --- js/booking/add-sub-resource/app.js | 39 +++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 71c3da3e..73fb21d7 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -269,9 +269,9 @@ var ResourceTableView = Marionette.View.extend({ //when edit sub resource editSubResource: function(e) { - var refId = $(e.currentTarget).data('ref'); //retrieve id from attribute data-ref - var parentRef = $(e.currentTarget).data('parent-ref'); //retrieve id from attribute data-parent-ref - var timeRequired = $(e.currentTarget).data('time-required'); //retrieve datetime from attribute data-time-required + var refId = CRM.$(e.currentTarget).data('ref'); //retrieve id from attribute data-ref + var parentRef = CRM.$(e.currentTarget).data('parent-ref'); //retrieve id from attribute data-parent-ref + var timeRequired = CRM.$(e.currentTarget).data('time-required'); //retrieve datetime from attribute data-time-required selectedItem = this.model.attributes.sub_resources[refId]; //create backbone model form json object @@ -291,6 +291,39 @@ var ResourceTableView = Marionette.View.extend({ is_new: false }); view.title = ts('Edit unlimited resource'); + view.on("render:options", function(options) { + var select = options.context.$el.find(options.element); + if (select.is('[disabled]')) { + select.prop('disabled', false); + } + select.html(options.template({ + options : options.list, + first_option : options.first_option + })); + }); + view.on("update:resources", function(model) { + CRM.$('#sub_resources').val(JSON.stringify(model.toJSON())); + }); + view.on("render:price", function(model) { + CRM.$("#total_price").val(model.attributes.total_price); + var totalText = model.attributes.total_price; + try{ + if(model.attributes.total_price>=0){ + var totalText = model.attributes.total_price.toFixed(2); + } + }catch(err){} + CRM.$("#total-price-summary").text(totalText); + CRM.$("#discount_amount").val(model.attributes.discount_amount); + CRM.$('#discount_amount_dummy').val(model.attributes.discount_amount); + CRM.$("#sub_total").val(model.attributes.sub_total); + var subtotalText = model.attributes.sub_total; + try{ + var subtotalText = model.attributes.sub_total.toFixed(2); + }catch(err){} + CRM.$("#sub-total-summary").text(subtotalText); + CRM.$('#adhoc_charge').val(model.attributes.adhoc_charges.total); + CRM.$('#ad-hoc-charge-summary').html(model.attributes.adhoc_charges.total); + }); CRM.BookingApp.modal.show(view); } From 0ea6083ed637e4703a2bc6649afa8f36c4ce6cc4 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 15:38:59 +0200 Subject: [PATCH 30/51] Remove sub resource functionality --- js/booking/add-sub-resource/app.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 73fb21d7..ece5f9ec 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -242,10 +242,10 @@ var ResourceTableView = Marionette.View.extend({ }, removeSubResource: function(e){ var self = this; - var ref = $(e.currentTarget).data('ref'); - var parentRef = $(e.currentTarget).data('parent-ref'); - var price = $(e.currentTarget).data('price'); - $('#crm-booking-sub-resource-individual-row-' + ref).remove(); + var ref = CRM.$(e.currentTarget).data('ref'); + var parentRef = CRM.$(e.currentTarget).data('parent-ref'); + var price = CRM.$(e.currentTarget).data('price'); + CRM.$('#crm-booking-sub-resource-individual-row-' + ref).remove(); delete this.model.attributes.sub_resources[ref]; var newResourcePrice = parseFloat(this.model.get("resources")[parentRef]) - parseFloat(price); @@ -253,7 +253,7 @@ var ResourceTableView = Marionette.View.extend({ this.model.attributes.resources[parentRef] = newResourcePrice; ResourceTotal[parentRef] -= parseFloat(price); try{ResourceTotal[parentRef] = ResourceTotal[parentRef].toFixed(2);}catch(err){} - $("#resource-total-price-" + parentRef).text(ResourceTotal[parentRef]); + CRM.$("#resource-total-price-" + parentRef).text(ResourceTotal[parentRef]); var currentSubTotal = this.model.get('sub_total'); var newSubTotal = parseFloat(this.model.get('sub_total') - parseFloat(price)); var currentTotal = this.model.get('total_price'); @@ -262,8 +262,8 @@ var ResourceTableView = Marionette.View.extend({ this.model.set("sub_total", newSubTotal); this.model.set("total_price", newTotal); - CRM.BookingApp.vent.trigger('render:price', this.model , parentRef ); - CRM.BookingApp.vent.trigger('update:resources', this.model); + self.triggerMethod('render:price', this.model); + self.triggerMethod('update:resources', this.model); CRM.alert(ts(''), ts('Unlimited resource removed'), 'success'); }, From 4fe513c73d0c7dc8afcaff185c968cad5d5f04b5 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 17:10:12 +0200 Subject: [PATCH 31/51] Move further functionality to app from the view --- js/booking/add-sub-resource/app.js | 88 +++++++++++++++++++++++++++++ js/booking/add-sub-resource/view.js | 66 ---------------------- 2 files changed, 88 insertions(+), 66 deletions(-) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index ece5f9ec..2463148b 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -264,6 +264,29 @@ var ResourceTableView = Marionette.View.extend({ self.triggerMethod('render:price', this.model); self.triggerMethod('update:resources', this.model); + self.on("update:resources", function(model) { + CRM.$('#sub_resources').val(JSON.stringify(model.toJSON())); + }); + self.on("render:price", function(model) { + CRM.$("#total_price").val(model.attributes.total_price); + var totalText = model.attributes.total_price; + try{ + if(model.attributes.total_price>=0){ + var totalText = model.attributes.total_price.toFixed(2); + } + }catch(err){} + CRM.$("#total-price-summary").text(totalText); + CRM.$("#discount_amount").val(model.attributes.discount_amount); + CRM.$('#discount_amount_dummy').val(model.attributes.discount_amount); + CRM.$("#sub_total").val(model.attributes.sub_total); + var subtotalText = model.attributes.sub_total; + try{ + var subtotalText = model.attributes.sub_total.toFixed(2); + }catch(err){} + CRM.$("#sub-total-summary").text(subtotalText); + CRM.$('#adhoc_charge').val(model.attributes.adhoc_charges.total); + CRM.$('#ad-hoc-charge-summary').html(model.attributes.adhoc_charges.total); + }); CRM.alert(ts(''), ts('Unlimited resource removed'), 'success'); }, @@ -329,6 +352,71 @@ var ResourceTableView = Marionette.View.extend({ }); var AddSubResource = { + //Additaional charges dialog view + EditAdhocChargesModal: Views.BookingProcessModal.extend({ + template: CRM._.template(CRM.$('#edit-adhoc-charges-template').html()), + className: "modal-dialog", + onRender: function(){ + var thisView = this; + CRM._.each(this.model.get('items'), function(item){ + thisView.$el.find('#' + item.name).html(item.item_price); + thisView.$el.find('input[name="' + item.name + '"]').val(item.quantity); + }); + this.$el.find('#adhoc-charges-note').val(this.model.get('note')); + this.$el.find('#total-adhoc-charges').html(this.model.get('total')); + Views.BookingProcessModal.prototype.onRender.apply(this, arguments); + }, + events: { + 'keypress .item': 'updatePrice', + 'keyup .item': 'updatePrice', + 'keydown .item': 'updatePrice', + 'click #update-adhoc-charges': 'updateAdhocCharges', + }, + updatePrice: function(e){ + var el = CRM.$(e.currentTarget); + var itemId = el.data('id'); + var price = el.data('price'); + var quantity = el.val(); + var name = el.attr('name'); + if(CRM.BookingApp.Utils.isPositiveInteger(quantity)){ + var itemPrice = parseFloat(price) * parseFloat(quantity); + this.$el.find('#'+ name).html(parseFloat(itemPrice).toFixed(2)); + var item = {item_id: itemId, name: name, price: price, quantity: quantity, item_price: itemPrice} + this.model.attributes.items[itemId] = item; + }else{ + this.$el.find('#'+ name).html(0); + this.$el.find('input[name="'+ name + '"]').val(''); + delete this.model.attributes.items[itemId]; + } + var items = this.model.get('items'); + var total = 0.0; + CRM._.each(items,function(item){ + total = parseFloat(total) + parseFloat(item.item_price); + }); + this.$el.find('#total-adhoc-charges').html(total.toFixed(2)); + this.model.set('total', total.toFixed(2)); + }, + updateAdhocCharges: function(e){ + e.preventDefault(); + var self = this; + var subResourceModel = CRM.BookingApp.getRegion().currentView.model; + this.model.set('note',this.$el.find('#adhoc-charges-note').val() ); + var adhocChargesTotal = this.model.get('total'); + subResourceModel.set('adhoc_charges', this.model.attributes); + var currentTotal = subResourceModel.get('sub_total'); + var discountAmount = subResourceModel.get('discount_amount'); + if(CRM.BookingApp.Utils.isPositiveNumber(discountAmount)){ + var newTotal = (parseFloat(adhocChargesTotal) + parseFloat(currentTotal)) - parseFloat(discountAmount); + }else{ + var newTotal = (parseFloat(adhocChargesTotal) + parseFloat(currentTotal)) - 0; + } + subResourceModel.set("total_price", parseFloat(newTotal).toFixed(2)); + console.log(subResourceModel); + self.triggerMethod('render:price', subResourceModel); + self.triggerMethod('update:resources', subResourceModel); + CRM.BookingApp.modal.triggerMethod('close'); + } + }), //Sub(Unlimited) resource dialog view AddSubResourceModal: Views.BookingProcessModal.extend({ template: CRM._.template(CRM.$('#add-sub-resource-template').html()), diff --git a/js/booking/add-sub-resource/view.js b/js/booking/add-sub-resource/view.js index ee004133..2025c84b 100755 --- a/js/booking/add-sub-resource/view.js +++ b/js/booking/add-sub-resource/view.js @@ -11,72 +11,6 @@ CRM.BookingApp.module('AddSubResource', function(AddSubResource, BookingApp, Bac var resourceTotal = new Array(); var priceCache = new Array(); - //Additaional charges dialog view - AddSubResource.EditAdhocChargesModal = BookingApp.Common.Views.BookingProcessModal.extend({ - template: "#edit-adhoc-charges-template", - className: "modal-dialog", - onRender: function(){ - var thisView = this; - _.each(this.model.get('items'), function(item){ - thisView.$el.find('#' + item.name).html(item.item_price); - thisView.$el.find('input[name="' + item.name + '"]').val(item.quantity); - }); - this.$el.find('#adhoc-charges-note').val(this.model.get('note')); - this.$el.find('#total-adhoc-charges').html(this.model.get('total')); - BookingApp.Common.Views.BookingProcessModal.prototype.onRender.apply(this, arguments); - }, - events: { - 'keypress .item': 'updatePrice', - 'keyup .item': 'updatePrice', - 'keydown .item': 'updatePrice', - 'click #update-adhoc-charges': 'updateAdhocCharges', - }, - updatePrice: function(e){ - var el = $(e.currentTarget); - var itemId = el.data('id'); - var price = el.data('price'); - var quantity = el.val(); - var name = el.attr('name'); - if(CRM.BookingApp.Utils.isPositiveInteger(quantity)){ - var itemPrice = parseFloat(price) * parseFloat(quantity); - this.$el.find('#'+ name).html(parseFloat(itemPrice).toFixed(2)); - var item = {item_id: itemId, name: name, price: price, quantity: quantity, item_price: itemPrice} - this.model.attributes.items[itemId] = item; - }else{ - this.$el.find('#'+ name).html(0); - this.$el.find('input[name="'+ name + '"]').val(''); - delete this.model.attributes.items[itemId]; - } - var items = this.model.get('items'); - var total = 0.0; - _.each(items,function(item){ - total = parseFloat(total) + parseFloat(item.item_price); - }); - this.$el.find('#total-adhoc-charges').html(total.toFixed(2)); - this.model.set('total', total.toFixed(2)); - }, - updateAdhocCharges: function(e){ - e.preventDefault(); - var subResourceModel = CRM.BookingApp.main.currentView.model; - this.model.set('note',this.$el.find('#adhoc-charges-note').val() ); - var adhocChargesTotal = this.model.get('total'); - //console.log(adhocChargesTotal); - subResourceModel.set('adhoc_charges', this.model.attributes); - var currentTotal = subResourceModel.get('sub_total'); - var discountAmount = subResourceModel.get('discount_amount'); - if(CRM.BookingApp.Utils.isPositiveNumber(discountAmount)){ - var newTotal = (parseFloat(adhocChargesTotal) + parseFloat(currentTotal)) - parseFloat(discountAmount); - }else{ - var newTotal = (parseFloat(adhocChargesTotal) + parseFloat(currentTotal)) - 0; - } - subResourceModel.set("total_price", parseFloat(newTotal).toFixed(2)); - CRM.BookingApp.vent.trigger('render:price', subResourceModel); - CRM.BookingApp.vent.trigger('update:resources', subResourceModel); - //console.log(subResourceModel.attributes); - CRM.BookingApp.modal.close(this); - } - - }); }); }(CRM.$, CRM.ts('uk.co.compucorp.civicrm.booking'))); From 8284f37283802d7abf7acfff42712e530a143021 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 17:37:49 +0200 Subject: [PATCH 32/51] Move entities js object to a new js file As the app.js is getting too long, some of the objects could be moved to separate files. It helps me understanding the current issues. --- CRM/Booking/Form/AddSubResource.php | 1 + CRM/Booking/Form/SelectResource.php | 1 + js/booking/add-sub-resource/app.js | 32 ------------------------- js/booking/add-sub-resource/entities.js | 32 +++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 32 deletions(-) create mode 100644 js/booking/add-sub-resource/entities.js diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 3250793a..43c7ff5f 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -290,6 +290,7 @@ static function registerScripts() { ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/vendor/moment.min.js', 120, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/civicrm-moment-strftime.js', 140, 'html-header', FALSE) + ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/entities.js', 145, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 150, 'html-header'); diff --git a/CRM/Booking/Form/SelectResource.php b/CRM/Booking/Form/SelectResource.php index 65ba5fd5..4bc0d752 100644 --- a/CRM/Booking/Form/SelectResource.php +++ b/CRM/Booking/Form/SelectResource.php @@ -212,6 +212,7 @@ static function registerScripts() { ->addScriptFile(E::LONG_NAME, 'packages/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'js/vendor/crm.backbone.js', 130, 'html-header', FALSE) + ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/entities.js', 135, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 140, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/civicrm-moment-strftime.js', 142, 'html-header', FALSE) diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index 2463148b..ab233579 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -1,35 +1,3 @@ -var Entities = { - SubResource: Backbone.Model.extend({ - defaults: { - sub_resources: {}, - resources: {}, - sub_total: 0, - adhoc_charges: {total:0}, - discount_amount:0, - total_price:0 - - }, - }), - AddSubResource: Backbone.Model.extend({ - defaults: { - parent_ref_id: null, - ref_id: null, - resource: {id : null, label :null}, - configuration: {id : null, label :null, price :0}, - quantity: 0, - time_required: null, - note: null, - price_estimate: 0, - }, - }), - AdhocCharges: Backbone.Model.extend({ - defaults: { - items: {}, - note: null, - total: 0, - }, - }), -}; var Views = { /** * A form that use in a Modal that required the validate in the form diff --git a/js/booking/add-sub-resource/entities.js b/js/booking/add-sub-resource/entities.js new file mode 100644 index 00000000..543d015d --- /dev/null +++ b/js/booking/add-sub-resource/entities.js @@ -0,0 +1,32 @@ +var Entities = { + SubResource: Backbone.Model.extend({ + defaults: { + sub_resources: {}, + resources: {}, + sub_total: 0, + adhoc_charges: {total:0}, + discount_amount:0, + total_price:0 + + }, + }), + AddSubResource: Backbone.Model.extend({ + defaults: { + parent_ref_id: null, + ref_id: null, + resource: {id : null, label :null}, + configuration: {id : null, label :null, price :0}, + quantity: 0, + time_required: null, + note: null, + price_estimate: 0, + }, + }), + AdhocCharges: Backbone.Model.extend({ + defaults: { + items: {}, + note: null, + total: 0, + }, + }), +}; From 2540a174db6f5056c964cccd877ec868a38410cb Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 22:46:53 +0200 Subject: [PATCH 33/51] Move view classes to view.js and fix event handlers As both modal trigger events the handler supposed to be the parent class --- CRM/Booking/Form/AddSubResource.php | 1 + js/booking/add-sub-resource/app.js | 671 +--------------------------- js/booking/add-sub-resource/view.js | 612 ++++++++++++++++++++++++- 3 files changed, 603 insertions(+), 681 deletions(-) diff --git a/CRM/Booking/Form/AddSubResource.php b/CRM/Booking/Form/AddSubResource.php index 43c7ff5f..484be280 100644 --- a/CRM/Booking/Form/AddSubResource.php +++ b/CRM/Booking/Form/AddSubResource.php @@ -291,6 +291,7 @@ static function registerScripts() { ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/civicrm-moment-strftime.js', 140, 'html-header', FALSE) ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/entities.js', 145, 'html-header') + ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/view.js', 145, 'html-header') ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/add-sub-resource/app.js', 150, 'html-header'); diff --git a/js/booking/add-sub-resource/app.js b/js/booking/add-sub-resource/app.js index ab233579..412f469a 100644 --- a/js/booking/add-sub-resource/app.js +++ b/js/booking/add-sub-resource/app.js @@ -1,32 +1,3 @@ -var Views = { - /** - * A form that use in a Modal that required the validate in the form - * - */ - BookingProcessModal: Marionette.View.extend({ - onRender: function() { - var rules = this.createValidationRules(); - this.$('form').validate(rules); - }, - /** - * - * @return {*} jQuery.validate rules - */ - createValidationRules: function() { - var rules = _.extend({}, CRM.validate.params); - rules.rules || (rules.rules = {}); - this.triggerMethod("validateRules:create", this, rules); - return rules; - }, - onRenderError: function(errors){ - var view = this; - _.each(errors, function(error) { - console.log($(error).attr('for')); - view.$('[name=' + $(error).attr('for') + ']').crmError($(error).text()); - }); - }, - }), -}; // see http://lostechies.com/derickbailey/2012/04/17/managing-a-modal-dialog-with-backbone-and-marionette/ var ModalRegion = Marionette.Region.extend({ el: "#crm-booking-dialog", @@ -56,644 +27,6 @@ var ModalRegion = Marionette.Region.extend({ }); var ResourceTotal = new Array(); var PriceCache = new Array(); -//Resource table view -var ResourceTableView = Marionette.View.extend({ - template: CRM._.template(CRM.$('#resource-table-template').html()), - - initialize: function(){ - if (CRM.$.trim(CRM.$("#sub_resources").val())) { - this.model.attributes = JSON.parse(CRM.$.trim(CRM.$("#sub_resources").val())); - } - this.model.attributes.total_price = CRM.$("#total_price").val(); - this.model.attributes.sub_total = CRM.$("#sub_total").val(); - //this.model.attributes.adhoc_charges = $("#adhoc_charge").val(); - this.model.attributes.discount_amount = CRM.$("#discount_amount").val(); - }, - - onRender: function(){ - var subtotal = 0; - var self = this; - //init the current price for each resource - this.$el.find("span[id^='resource-price-']").each(function(){ - var el = CRM.$(this); - self.model.attributes.resources[el.data('ref')] = el.text(); - }); - var items = []; - var template = CRM._.template(CRM.$('#sub-resource-row-template').html()); - CRM._.each(this.model.get('sub_resources'), function (item, key){ - self.$el.find("#crm-booking-sub-resource-table-" + item.parent_ref_id).append(template(item)); - PriceCache[item.ref_id] = item.price_estimate; - items.push(item); - }); - this.$el.find("span[id^='resource-total-price-']").each(function(){ - var el = CRM.$(this);/////////////////////////// - var resourceTotalPrice = parseFloat(el.data('price')); - _.find(items, function (item) { - if(parseInt(item.parent_ref_id) === parseInt(el.data('ref'))){ - resourceTotalPrice += parseFloat(item.price_estimate); - } - }); - if(resourceTotalPrice != null){ - subtotal += resourceTotalPrice; - el.text(resourceTotalPrice.toFixed(2)); - ResourceTotal[el.data('ref')] = resourceTotalPrice.toFixed(2); - self.$el.find('#crm-booking-sub-resource-row-' + el.data('ref')).show(); - } - }); - this.model.attributes.sub_total = subtotal; - this.model.attributes.total_price = (subtotal - + parseFloat(this.model.get("adhoc_charges").total)) - - parseFloat(this.model.get("discount_amount")); - this.model.attributes.discount_amount = this.model.get("discount_amount"); - - unlimitedTimeConfig = timeConfig; - - var subTotalText = this.model.get('sub_total'); - var adhocText = this.model.get('adhoc_charges').total; - var discountText = this.model.get('discount_amount'); - var totalText = this.model.get('total_price'); - this.$el.find("#sub-total-summary").text(subTotalText.toFixed(2)); - this.$el.find("#ad-hoc-charge-summary").text(adhocText); - try{ - this.$el.find("#ad-hoc-charge-summary").text(adhocText,toFixed(2)); - }catch(err){} - this.$el.find("#discount_amount_dummy").val(discountText); - this.$el.find("#total-price-summary").text(totalText.toFixed(2)); - }, - events: { - 'click .add-sub-resource': 'addSubResource', - 'click .edit-sub-resource': 'editSubResource', - 'click .edit-adhoc-charge': 'editAdhocCharge', - 'click .collapsed' : 'toggleHiddenElement', - 'click .remove-sub-resource': 'removeSubResource', - //'keypress #discount_amount_dummy': 'addDiscountAmount', - 'keyup #discount_amount_dummy': 'addDiscountAmount', - //'keydown #discount_amount_dummy': 'addDiscountAmount' - }, - - addSubResource: function(e){ - var ref = CRM.$(e.currentTarget).data('ref');///////////////// - endDate = CRM.$(e.currentTarget).data('edate'); - startDate = CRM.$(e.currentTarget).data('sdate'); - var model = new CRM.BookingApp.Entities.AddSubResource({parent_ref_id:ref, time_required:startDate}); - var view = new CRM.BookingApp.AddSubResource.AddSubResourceModal({model: model, is_new: true}); - view.on("render:options", function(options) { - var select = options.context.$el.find(options.element); - if (select.is('[disabled]')) { - select.prop('disabled', false); - } - select.html(options.template({ - options : options.list, - first_option : options.first_option - })); - }); - view.on("update:resources", function(model) { - CRM.$('#sub_resources').val(JSON.stringify(model.toJSON())); - }); - view.on("render:price", function(model) { - CRM.$("#total_price").val(model.attributes.total_price); - var totalText = model.attributes.total_price; - try{ - if(model.attributes.total_price>=0){ - var totalText = model.attributes.total_price.toFixed(2); - } - }catch(err){} - CRM.$("#total-price-summary").text(totalText); - CRM.$("#discount_amount").val(model.attributes.discount_amount); - CRM.$('#discount_amount_dummy').val(model.attributes.discount_amount); - CRM.$("#sub_total").val(model.attributes.sub_total); - var subtotalText = model.attributes.sub_total; - try{ - var subtotalText = model.attributes.sub_total.toFixed(2); - }catch(err){} - CRM.$("#sub-total-summary").text(subtotalText); - CRM.$('#adhoc_charge').val(model.attributes.adhoc_charges.total); - CRM.$('#ad-hoc-charge-summary').html(model.attributes.adhoc_charges.total); - }); - view.title = ts('Add Unlimited Resource'); - CRM.BookingApp.modal.show(view); - }, - - addDiscountAmount: function(e){ - var currentSubTotal = parseFloat(this.model.get('sub_total')); - var currentAdhocCharges = parseFloat(this.model.get('adhoc_charges').total); - // Get the discount amount stripping out non-numeric characters - var sDiscountAmount = $(e.currentTarget).val().replace(/[^\d.-]/g, ''); - var fDiscountAmount = parseFloat(sDiscountAmount); - if (!_.isNumber(fDiscountAmount) || _.isNaN(fDiscountAmount)) { - fDiscountAmount = 0; - sDiscountAmount = ''; - } - var newTotal = (currentSubTotal + currentAdhocCharges) - fDiscountAmount; - try{newTotal = newTotal.toFixed(2); }catch(err){} - this.model.set("total_price", newTotal); - this.model.set("discount_amount", sDiscountAmount); - CRM.BookingApp.vent.trigger('render:price', this.model ); - }, - - editAdhocCharge: function(e) { - var model = new CRM.BookingApp.Entities.AdhocCharges({ - items : this.model.get('adhoc_charges').items, - note : this.model.get('adhoc_charges').note, - total : this.model.get('adhoc_charges').total - }); - var view = new AddSubResource.EditAdhocChargesModal({ - model : model - }); - view.title = ts('Edit Additional Charges'); - CRM.BookingApp.modal.show(view); - }, - - toggleHiddenElement: function(e){ - var row = $(e.currentTarget).data('ref'); - $('#crm-booking-sub-resource-row-' + row).toggle(); - }, - removeSubResource: function(e){ - var self = this; - var ref = CRM.$(e.currentTarget).data('ref'); - var parentRef = CRM.$(e.currentTarget).data('parent-ref'); - var price = CRM.$(e.currentTarget).data('price'); - CRM.$('#crm-booking-sub-resource-individual-row-' + ref).remove(); - delete this.model.attributes.sub_resources[ref]; - - var newResourcePrice = parseFloat(this.model.get("resources")[parentRef]) - parseFloat(price); - - this.model.attributes.resources[parentRef] = newResourcePrice; - ResourceTotal[parentRef] -= parseFloat(price); - try{ResourceTotal[parentRef] = ResourceTotal[parentRef].toFixed(2);}catch(err){} - CRM.$("#resource-total-price-" + parentRef).text(ResourceTotal[parentRef]); - var currentSubTotal = this.model.get('sub_total'); - var newSubTotal = parseFloat(this.model.get('sub_total') - parseFloat(price)); - var currentTotal = this.model.get('total_price'); - var newTotal = parseFloat(currentTotal) - parseFloat(price); - - this.model.set("sub_total", newSubTotal); - this.model.set("total_price", newTotal); - - self.triggerMethod('render:price', this.model); - self.triggerMethod('update:resources', this.model); - self.on("update:resources", function(model) { - CRM.$('#sub_resources').val(JSON.stringify(model.toJSON())); - }); - self.on("render:price", function(model) { - CRM.$("#total_price").val(model.attributes.total_price); - var totalText = model.attributes.total_price; - try{ - if(model.attributes.total_price>=0){ - var totalText = model.attributes.total_price.toFixed(2); - } - }catch(err){} - CRM.$("#total-price-summary").text(totalText); - CRM.$("#discount_amount").val(model.attributes.discount_amount); - CRM.$('#discount_amount_dummy').val(model.attributes.discount_amount); - CRM.$("#sub_total").val(model.attributes.sub_total); - var subtotalText = model.attributes.sub_total; - try{ - var subtotalText = model.attributes.sub_total.toFixed(2); - }catch(err){} - CRM.$("#sub-total-summary").text(subtotalText); - CRM.$('#adhoc_charge').val(model.attributes.adhoc_charges.total); - CRM.$('#ad-hoc-charge-summary').html(model.attributes.adhoc_charges.total); - }); - CRM.alert(ts(''), ts('Unlimited resource removed'), 'success'); - }, - - //when edit sub resource - editSubResource: function(e) { - var refId = CRM.$(e.currentTarget).data('ref'); //retrieve id from attribute data-ref - var parentRef = CRM.$(e.currentTarget).data('parent-ref'); //retrieve id from attribute data-parent-ref - var timeRequired = CRM.$(e.currentTarget).data('time-required'); //retrieve datetime from attribute data-time-required - selectedItem = this.model.attributes.sub_resources[refId]; - - //create backbone model form json object - var model = new CRM.BookingApp.Entities.AddSubResource({ - parent_ref_id : parentRef, - ref_id : refId, - resource: {id : selectedItem.resource.id, label :selectedItem.resource.label}, - configuration: selectedItem.configuration, - quantity: selectedItem.quantity, - time_required: timeRequired, - note: selectedItem.note, - price_estimate: selectedItem.price_estimate, - }); - //create backbone view - var view = new AddSubResource.AddSubResourceModal({ - model : model, - is_new: false - }); - view.title = ts('Edit unlimited resource'); - view.on("render:options", function(options) { - var select = options.context.$el.find(options.element); - if (select.is('[disabled]')) { - select.prop('disabled', false); - } - select.html(options.template({ - options : options.list, - first_option : options.first_option - })); - }); - view.on("update:resources", function(model) { - CRM.$('#sub_resources').val(JSON.stringify(model.toJSON())); - }); - view.on("render:price", function(model) { - CRM.$("#total_price").val(model.attributes.total_price); - var totalText = model.attributes.total_price; - try{ - if(model.attributes.total_price>=0){ - var totalText = model.attributes.total_price.toFixed(2); - } - }catch(err){} - CRM.$("#total-price-summary").text(totalText); - CRM.$("#discount_amount").val(model.attributes.discount_amount); - CRM.$('#discount_amount_dummy').val(model.attributes.discount_amount); - CRM.$("#sub_total").val(model.attributes.sub_total); - var subtotalText = model.attributes.sub_total; - try{ - var subtotalText = model.attributes.sub_total.toFixed(2); - }catch(err){} - CRM.$("#sub-total-summary").text(subtotalText); - CRM.$('#adhoc_charge').val(model.attributes.adhoc_charges.total); - CRM.$('#ad-hoc-charge-summary').html(model.attributes.adhoc_charges.total); - }); - CRM.BookingApp.modal.show(view); - } - -}); -var AddSubResource = { - //Additaional charges dialog view - EditAdhocChargesModal: Views.BookingProcessModal.extend({ - template: CRM._.template(CRM.$('#edit-adhoc-charges-template').html()), - className: "modal-dialog", - onRender: function(){ - var thisView = this; - CRM._.each(this.model.get('items'), function(item){ - thisView.$el.find('#' + item.name).html(item.item_price); - thisView.$el.find('input[name="' + item.name + '"]').val(item.quantity); - }); - this.$el.find('#adhoc-charges-note').val(this.model.get('note')); - this.$el.find('#total-adhoc-charges').html(this.model.get('total')); - Views.BookingProcessModal.prototype.onRender.apply(this, arguments); - }, - events: { - 'keypress .item': 'updatePrice', - 'keyup .item': 'updatePrice', - 'keydown .item': 'updatePrice', - 'click #update-adhoc-charges': 'updateAdhocCharges', - }, - updatePrice: function(e){ - var el = CRM.$(e.currentTarget); - var itemId = el.data('id'); - var price = el.data('price'); - var quantity = el.val(); - var name = el.attr('name'); - if(CRM.BookingApp.Utils.isPositiveInteger(quantity)){ - var itemPrice = parseFloat(price) * parseFloat(quantity); - this.$el.find('#'+ name).html(parseFloat(itemPrice).toFixed(2)); - var item = {item_id: itemId, name: name, price: price, quantity: quantity, item_price: itemPrice} - this.model.attributes.items[itemId] = item; - }else{ - this.$el.find('#'+ name).html(0); - this.$el.find('input[name="'+ name + '"]').val(''); - delete this.model.attributes.items[itemId]; - } - var items = this.model.get('items'); - var total = 0.0; - CRM._.each(items,function(item){ - total = parseFloat(total) + parseFloat(item.item_price); - }); - this.$el.find('#total-adhoc-charges').html(total.toFixed(2)); - this.model.set('total', total.toFixed(2)); - }, - updateAdhocCharges: function(e){ - e.preventDefault(); - var self = this; - var subResourceModel = CRM.BookingApp.getRegion().currentView.model; - this.model.set('note',this.$el.find('#adhoc-charges-note').val() ); - var adhocChargesTotal = this.model.get('total'); - subResourceModel.set('adhoc_charges', this.model.attributes); - var currentTotal = subResourceModel.get('sub_total'); - var discountAmount = subResourceModel.get('discount_amount'); - if(CRM.BookingApp.Utils.isPositiveNumber(discountAmount)){ - var newTotal = (parseFloat(adhocChargesTotal) + parseFloat(currentTotal)) - parseFloat(discountAmount); - }else{ - var newTotal = (parseFloat(adhocChargesTotal) + parseFloat(currentTotal)) - 0; - } - subResourceModel.set("total_price", parseFloat(newTotal).toFixed(2)); - console.log(subResourceModel); - self.triggerMethod('render:price', subResourceModel); - self.triggerMethod('update:resources', subResourceModel); - CRM.BookingApp.modal.triggerMethod('close'); - } - }), - //Sub(Unlimited) resource dialog view - AddSubResourceModal: Views.BookingProcessModal.extend({ - template: CRM._.template(CRM.$('#add-sub-resource-template').html()), - initialize: function(options){ - this.isNew = options.is_new; - }, - events: { - 'click #add-to-basket': 'addSubResource', - 'change #resource_select': 'getConfigurations', - 'change #configuration_select': 'updatePriceEstmate', - 'keypress #quantity': 'updatePriceEstmate', - 'keyup #quantity': 'updatePriceEstmate', - 'keydown #quantity': 'updatePriceEstmate', - }, - onRender: function(){ - Views.BookingProcessModal.prototype.onRender.apply(this, arguments); - - var thisView = this; //set 'this' object for calling inside callback function - this.$el.find('#loading').show(); - - var initsdate = moment(this.model.get('time_required'), "YYYY-MM-DD HH:mm:ss"); - var timeTxt = [initsdate.hours() < 10 ? '0' + initsdate.hours() : initsdate.hours(), ":", initsdate.minute() < 10 ? '0' + initsdate.minute() : initsdate.minute()].join(""); - - //set the formatted months - var month=new Array(); - month[0]="01"; - month[1]="02"; - month[2]="03"; - month[3]="04"; - month[4]="05"; - month[5]="06"; - month[6]="07"; - month[7]="08"; - month[8]="09"; - month[9]="10"; - month[10]="11"; - month[11]="12"; - var dateTxt = [ initsdate.format("DD"),"/", month[initsdate.months()],"/", initsdate.years()].join(""); - this.$el.find("#required_date").val(dateTxt); - this.$el.find("#required_time").val(timeTxt); - - CRM.api('Resource', 'get', {'sequential': 1, 'is_unlimited': 1, 'is_deleted': 0, 'is_active': 1}, - {success: function(data) { - thisView.template = CRM._.template(CRM.$('#add-sub-resource-template').html()); - //var configValue = CRM_Booking_BAO_BookingConfig::getConfig(); - - thisView.$el.find("#required_date").datepicker({changeMonth: true, changeYear: true, dateFormat: 'dd/mm/yy'}); - thisView.$el.find('#required_time').timeEntry({show24Hours: true}).change(function() { - var log = $('#log'); - log.val(log.val() + ($('#defaultEntry').val() || 'blank') + '\n'); - }); - - //thisView.resources = data.values; - var tpl = CRM._.template(CRM.$('#select-option-template').html()); - var params = { - context: thisView, - template: tpl, - list: data.values, - element: "#resource_select", - first_option: ['- ', ts('select resource'), ' -'].join("") - } - thisView.triggerMethod("render:options", params); - - if(thisView.isNew == false){ - //set values - thisView.$el.find("#resource_select").val(thisView.model.get('resource').id); - //set configuration option value - thisView.$el.find("#configuration_select").attr('data-selected-id',thisView.model.get('configuration').id); - thisView.getConfigurations(); //call inside callback function - thisView.$el.find("#quantity").val(thisView.model.get('quantity')); - thisView.$el.find("#sub-resource-note").val(thisView.model.get('note')); - thisView.$el.find("#price-estimate").html(thisView.model.get('price_estimate')); - - thisView.$el.find("#quantity").prop('disabled', false); //enable quantity input text - } - thisView.$el.find('#loading').hide(); - thisView.$el.find('#content').show(); - } - } - ); - }, - - beforeClose: function() {this.$('form').find("#required_date").datepicker("destroy");}, - - /** - * Define form validation rules - * - * @param View view the view for which validation rules are created - * @param Object r the validation rules for the view - */ - onValidateRulesCreate: function(view, r) { - CRM.$.validator.addMethod("withinValidTime", function(value, element) { - var dateVals = CRM.$("#required_date").val().split("/"); - var timeVals = CRM.$("#required_time").val().split(":"); - var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); - var minDate = moment(startDate, "YYYY-MM-DD HH:mm:ss"); - var maxDate = moment(endDate, "YYYY-MM-DD HH:mm:ss"); - if (unlimitedTimeConfig==0){ - return true; - }else{ - var val = requiredDate>=minDate && requiredDate<=maxDate; - return val; - } - }, ts("Please select the date and time during the valid booking time.")); - CRM._.extend(r.rules, { - resource_select: { - required: true - }, - configuration_select: { - required: true - }, - "required_date": { - required: true, - "withinValidTime": true - }, - quantity: { - required: true, - digits: true - }, - }); - }, - //calcualte price - updatePriceEstmate: function(e){ - var quantitySelector = this.$el.find('#quantity'); - if(e.type == 'change'){ - var configSelect = this.$el.find('#configuration_select'); - if(configSelect.val() !== ''){ - configSelect.find(':selected').data('price'); - var price = configSelect.find(':selected').data('price'); - this.model.set('configuration', {id: configSelect.val(), label: configSelect.find(':selected').text(), price: price}); - quantitySelector.prop('disabled', false); - }else{ - qualitySelector.prop('disabled', true); - quantitySelector.val(''); - } - } - var configPrice = this.model.get('configuration').price - var quantity = quantitySelector.val(); - if(CRM.BookingApp.Utils.isPositiveInteger(quantity)){ - var priceEstimate = quantity * configPrice; - this.model.set('quantity', quantity); - this.model.set('price_estimate', priceEstimate.toFixed(2)); - this.$el.find('#price-estimate').html(priceEstimate.toFixed(2)); - } - }, - - //render configuration options - getConfigurations: function(e){ - selectedVal = CRM.$('#resource_select').val(); - if(selectedVal !== ""){ - var params = { - id: selectedVal, - sequential: 1, - 'api.resource_config_set.get': { - id: '$value.set_id', - is_active: 1, - is_deleted: 0, - 'api.resource_config_option.get': { - set_id: '$value.id', - is_active: 1, - is_deleted: 0, - 'api.option_group.get':{ - name: 'booking_size_unit', - }, - 'api.option_value.get':{ - value: '$value.unit_id', - sequential: 1, - option_group_id: '$value.api.option_group.get.id' - } - } - } - }; - this.$el.find('#config-loading').show(); - this.$el.find('#configuration_select').hide(); - var self = this; - CRM.api('Resource', 'get', params, - { context: self, - success: function(data) { - var resource = data['values']['0']; - var configSet = data['values']['0']['api.resource_config_set.get']; - if(configSet.count !== 1){ - var url = CRM.url('civicrm/admin/resource/config_set', { - reset: 1, - action: 'update', - id: resource.id - }); - CRM.alert(ts(''), ts('Your resource configuration set is disabled.') - + ' ' + ' ' + ts('Click here to edit configuration set.') + ' ', 'error'); - } - else if(configSet['values']['0']['api.resource_config_option.get'].count < 1){ - var url = CRM.url('civicrm/admin/resource/config_set/config_option', { - reset: 1, - sid: configSet['values']['0'].id - }); - CRM.alert(ts(''), ts('Your resource configuration options are all disabled or none have been created.') - + ' ' + ' ' + ts('Click here to edit or create options.') + ' ', 'error'); - } - else{ - var options = data['values']['0']['api.resource_config_set.get']['values']['0']['api.resource_config_option.get']['values']; - self.model.set('resource', {id: resource.id, label: resource.label}); - var params = { - context: self, - template: _.template(CRM.$('#select-config-option-template').html()), - list: options, - element: "#configuration_select", - first_option: '- ' + ts('select configuration') + ' -' - } - self.triggerMethod("render:options", params); - //set configuration options for edit mode of subresource view - var configSelectedId = this.$el.find('#configuration_select').data('selected-id'); //retrieve data from data-selected-id attribute - if(configSelectedId != 'undefined'){ - this.$el.find('#configuration_select').val(configSelectedId); - } - this.$el.find('#config-loading').hide(); - this.$el.find('#configuration_select').show(); - } - } - }); - }else{ - var params = { - context:this, - template: _.template($('#select-config-option-template').html()), - list: new Array(), - element: "#configuration_select", - first_option: '- ' + ts('select configuration') + ' -'} - self.triggerMethod("render:options", params); - this.$el.find('#configuration_select').prop('disabled', true); - } - }, - - //save sub-resource - addSubResource: function(e){ - e.preventDefault(); - var self = this; - var targetForm = e.currentTarget; - while(targetForm.tagName != 'FORM') { - targetForm = targetForm.parentNode; - } - if (!CRM.$(targetForm).valid()) { - var errors = CRM.$(targetForm).validate().errors(); - this.onRenderError(errors); - return false; - } - CRM.$(targetForm).find("#required_date").datepicker("destroy"); - this.model.set('note', this.$el.find('#sub-resource-note').val()); - var dateVals = this.$el.find("#required_date").val().split("/"); - var timeVals = this.$el.find("#required_time").val().split(":"); - var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); - var timeRequired = moment(requiredDate).format("YYYY-M-D HH:mm"); - this.model.set('time_required', timeRequired); - - var parentRefId = this.model.get('parent_ref_id'); - - var refId = null; - if(this.isNew){ - refId = CRM.BookingApp.Utils.getCurrentUnixTimstamp(); - this.model.set('ref_id', refId); - }else{ - refId = this.model.get('ref_id'); - } - - var template = _.template(CRM.$('#sub-resource-row-template').html()); - - //ui update - if(this.isNew){ - CRM.$('#crm-booking-sub-resource-table-' + parentRefId).find('tbody').append(template(this.model.toJSON())); - }else{ - CRM.$('#crm-booking-sub-resource-individual-row-'+refId).replaceWith(template(this.model.toJSON())); - } - CRM.$('#crm-booking-sub-resource-row-' + parentRefId).show(); - var resourceRefId = this.model.get("parent_ref_id"); - var priceEstimate = this.model.get("price_estimate"); - var subResourceRefId = this.model.get("ref_id"); - - var subResourceModel = CRM.BookingApp.getRegion().currentView.model; - subResourceModel.attributes.sub_resources[refId] = this.model.toJSON(); - - var currentSubTotal = subResourceModel.get('sub_total'); - var newSubTotal = parseFloat(priceEstimate) + parseFloat(subResourceModel.get('sub_total')); - subResourceModel.set("sub_total", newSubTotal); - - var currentTotal = subResourceModel.get('total_price'); - var newTotal = (parseFloat(currentTotal) - parseFloat(currentSubTotal)) + parseFloat(newSubTotal); - subResourceModel.set("total_price", newTotal); - - var currentResourceTotal = ResourceTotal[resourceRefId]; - - if(this.isNew){ - var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate); - PriceCache[subResourceRefId] = parseFloat(priceEstimate); - }else{ - var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate) - PriceCache[subResourceRefId]; - subResourceModel.attributes.sub_total = subResourceModel.attributes.sub_total - PriceCache[subResourceRefId]; - subResourceModel.attributes.total_price = subResourceModel.attributes.total_price - PriceCache[subResourceRefId]; - PriceCache[subResourceRefId] = parseFloat(priceEstimate); - } - ResourceTotal[resourceRefId] = resourceTotalPrice; - subResourceModel.attributes.resources[resourceRefId] = resourceTotalPrice.toFixed(2); - //set total price for resource row - CRM.$("#resource-total-price-" + resourceRefId).text(subResourceModel.attributes.resources[resourceRefId]); - self.triggerMethod('render:price', subResourceModel, resourceRefId ); - self.triggerMethod('update:resources', subResourceModel); - CRM.BookingApp.modal.triggerMethod('close'); - }, - - }), -} // The addRegions method has been removed and not present in the // current Marionette version. The Application.extend could be the // solution for passing the necessary parameters to the application. @@ -715,10 +48,8 @@ var MyApp = Marionette.Application.extend({ }, Entities: Entities, onStart: function(app, options) { - this.getRegion().show(new ResourceTableView({model: new this.Entities.SubResource()})); + this.getRegion().show(new Views.ResourceTableView({model: new this.Entities.SubResource()})); }, - Views: Views, - AddSubResource: AddSubResource, }); CRM.BookingApp = new MyApp(); diff --git a/js/booking/add-sub-resource/view.js b/js/booking/add-sub-resource/view.js index 2025c84b..10e225cb 100755 --- a/js/booking/add-sub-resource/view.js +++ b/js/booking/add-sub-resource/view.js @@ -1,16 +1,606 @@ -(function ($, ts){ +var Views = { + /** + * A form that use in a Modal that required the validate in the form + * + */ + BookingProcessModal: Marionette.View.extend({ + onRender: function() { + var rules = this.createValidationRules(); + this.$('form').validate(rules); + }, + /** + * + * @return {*} jQuery.validate rules + */ + createValidationRules: function() { + var rules = _.extend({}, CRM.validate.params); + rules.rules || (rules.rules = {}); + this.triggerMethod("validateRules:create", this, rules); + return rules; + }, + onRenderError: function(errors){ + var view = this; + _.each(errors, function(error) { + console.log($(error).attr('for')); + view.$('[name=' + $(error).attr('for') + ']').crmError($(error).text()); + }); + }, + onRenderOptions: function(options) { + var select = options.context.$el.find(options.element); + if (select.is('[disabled]')) { + select.prop('disabled', false); + } + select.html(options.template({ + options : options.list, + first_option : options.first_option + })); + }, + onUpdateResource: function(model) { + CRM.$('#sub_resources').val(JSON.stringify(model.toJSON())); + }, + onRenderPrice: function(model) { + CRM.$("#total_price").val(model.attributes.total_price); + var totalText = model.attributes.total_price; + try{ + if(model.attributes.total_price>=0){ + var totalText = model.attributes.total_price.toFixed(2); + } + }catch(err){} + CRM.$("#total-price-summary").text(totalText); + CRM.$("#discount_amount").val(model.attributes.discount_amount); + CRM.$('#discount_amount_dummy').val(model.attributes.discount_amount); + CRM.$("#sub_total").val(model.attributes.sub_total); + var subtotalText = model.attributes.sub_total; + try{ + var subtotalText = model.attributes.sub_total.toFixed(2); + }catch(err){} + CRM.$("#sub-total-summary").text(subtotalText); + CRM.$('#adhoc_charge').val(model.attributes.adhoc_charges.total); + CRM.$('#ad-hoc-charge-summary').html(model.attributes.adhoc_charges.total); + }, + }), + //Resource table view + ResourceTableView: Marionette.View.extend({ + template: CRM._.template(CRM.$('#resource-table-template').html()), -/* - * View classes belong to the second wizard screen of create/edit booking - */ -CRM.BookingApp.module('AddSubResource', function(AddSubResource, BookingApp, Backbone, Marionette, $, _) { + initialize: function(){ + if (CRM.$.trim(CRM.$("#sub_resources").val())) { + this.model.attributes = JSON.parse(CRM.$.trim(CRM.$("#sub_resources").val())); + } + this.model.attributes.total_price = CRM.$("#total_price").val(); + this.model.attributes.sub_total = CRM.$("#sub_total").val(); + this.model.attributes.adhoc_charges = CRM.$("#adhoc_charge").val(); + this.model.attributes.discount_amount = CRM.$("#discount_amount").val(); + }, + onRender: function(){ + var subtotal = 0; + var self = this; + //init the current price for each resource + this.$el.find("span[id^='resource-price-']").each(function(){ + var el = CRM.$(this); + self.model.attributes.resources[el.data('ref')] = el.text(); + }); + var items = []; + var template = CRM._.template(CRM.$('#sub-resource-row-template').html()); + CRM._.each(this.model.get('sub_resources'), function (item, key){ + self.$el.find("#crm-booking-sub-resource-table-" + item.parent_ref_id).append(template(item)); + PriceCache[item.ref_id] = item.price_estimate; + items.push(item); + }); + this.$el.find("span[id^='resource-total-price-']").each(function(){ + var el = CRM.$(this);/////////////////////////// + var resourceTotalPrice = parseFloat(el.data('price')); + _.find(items, function (item) { + if(parseInt(item.parent_ref_id) === parseInt(el.data('ref'))){ + resourceTotalPrice += parseFloat(item.price_estimate); + } + }); + if(resourceTotalPrice != null){ + subtotal += resourceTotalPrice; + el.text(resourceTotalPrice.toFixed(2)); + ResourceTotal[el.data('ref')] = resourceTotalPrice.toFixed(2); + self.$el.find('#crm-booking-sub-resource-row-' + el.data('ref')).show(); + } + }); + this.model.attributes.sub_total = subtotal; + this.model.attributes.total_price = (subtotal + + parseFloat(this.model.get("adhoc_charges").total)) + - parseFloat(this.model.get("discount_amount")); + this.model.attributes.discount_amount = this.model.get("discount_amount"); - var startDate; - var endDate; - var unlimitedTimeConfig; - var resourceTotal = new Array(); - var priceCache = new Array(); + unlimitedTimeConfig = timeConfig; + var subTotalText = this.model.get('sub_total'); + var adhocText = this.model.get('adhoc_charges').total; + var discountText = this.model.get('discount_amount'); + var totalText = this.model.get('total_price'); + this.$el.find("#sub-total-summary").text(subTotalText.toFixed(2)); + this.$el.find("#ad-hoc-charge-summary").text(adhocText); + try{ + this.$el.find("#ad-hoc-charge-summary").text(adhocText,toFixed(2)); + }catch(err){} + this.$el.find("#discount_amount_dummy").val(discountText); + this.$el.find("#total-price-summary").text(totalText.toFixed(2)); + }, + events: { + 'click .add-sub-resource': 'addSubResource', + 'click .edit-sub-resource': 'editSubResource', + 'click .edit-adhoc-charge': 'editAdhocCharge', + 'click .collapsed' : 'toggleHiddenElement', + 'click .remove-sub-resource': 'removeSubResource', + //'keypress #discount_amount_dummy': 'addDiscountAmount', + 'keyup #discount_amount_dummy': 'addDiscountAmount', + //'keydown #discount_amount_dummy': 'addDiscountAmount' + }, + addSubResource: function(e){ + var ref = CRM.$(e.currentTarget).data('ref');///////////////// + endDate = CRM.$(e.currentTarget).data('edate'); + startDate = CRM.$(e.currentTarget).data('sdate'); + var model = new Entities.AddSubResource({parent_ref_id:ref, time_required:startDate}); + var view = new Views.AddSubResourceModal({model: model, is_new: true}); + view.title = ts('Add Unlimited Resource'); + CRM.BookingApp.modal.show(view); + }, + + addDiscountAmount: function(e){ + var currentSubTotal = parseFloat(this.model.get('sub_total')); + var currentAdhocCharges = parseFloat(this.model.get('adhoc_charges').total); + // Get the discount amount stripping out non-numeric characters + var sDiscountAmount = $(e.currentTarget).val().replace(/[^\d.-]/g, ''); + var fDiscountAmount = parseFloat(sDiscountAmount); + if (!_.isNumber(fDiscountAmount) || _.isNaN(fDiscountAmount)) { + fDiscountAmount = 0; + sDiscountAmount = ''; + } + var newTotal = (currentSubTotal + currentAdhocCharges) - fDiscountAmount; + try{newTotal = newTotal.toFixed(2); }catch(err){} + this.model.set("total_price", newTotal); + this.model.set("discount_amount", sDiscountAmount); + CRM.BookingApp.vent.trigger('render:price', this.model ); + }, + + editAdhocCharge: function(e) { + var model = new Entities.AdhocCharges({ + items : this.model.get('adhoc_charges').items, + note : this.model.get('adhoc_charges').note, + total : this.model.get('adhoc_charges').total + }); + var view = new Views.EditAdhocChargesModal({ + model : model + }); + view.title = ts('Edit Additional Charges'); + CRM.BookingApp.modal.show(view); + }, + + toggleHiddenElement: function(e){ + var row = $(e.currentTarget).data('ref'); + $('#crm-booking-sub-resource-row-' + row).toggle(); + }, + removeSubResource: function(e){ + var self = this; + var ref = CRM.$(e.currentTarget).data('ref'); + var parentRef = CRM.$(e.currentTarget).data('parent-ref'); + var price = CRM.$(e.currentTarget).data('price'); + CRM.$('#crm-booking-sub-resource-individual-row-' + ref).remove(); + delete this.model.attributes.sub_resources[ref]; + + var newResourcePrice = parseFloat(this.model.get("resources")[parentRef]) - parseFloat(price); + + this.model.attributes.resources[parentRef] = newResourcePrice; + ResourceTotal[parentRef] -= parseFloat(price); + try{ResourceTotal[parentRef] = ResourceTotal[parentRef].toFixed(2);}catch(err){} + CRM.$("#resource-total-price-" + parentRef).text(ResourceTotal[parentRef]); + var currentSubTotal = this.model.get('sub_total'); + var newSubTotal = parseFloat(this.model.get('sub_total') - parseFloat(price)); + var currentTotal = this.model.get('total_price'); + var newTotal = parseFloat(currentTotal) - parseFloat(price); + + this.model.set("sub_total", newSubTotal); + this.model.set("total_price", newTotal); + + self.triggerMethod('render:price', this.model); + self.triggerMethod('update:resources', this.model); + CRM.alert(ts(''), ts('Unlimited resource removed'), 'success'); + }, + + //when edit sub resource + editSubResource: function(e) { + var refId = CRM.$(e.currentTarget).data('ref'); //retrieve id from attribute data-ref + var parentRef = CRM.$(e.currentTarget).data('parent-ref'); //retrieve id from attribute data-parent-ref + var timeRequired = CRM.$(e.currentTarget).data('time-required'); //retrieve datetime from attribute data-time-required + selectedItem = this.model.attributes.sub_resources[refId]; + + //create backbone model form json object + var model = new CRM.BookingApp.Entities.AddSubResource({ + parent_ref_id : parentRef, + ref_id : refId, + resource: {id : selectedItem.resource.id, label :selectedItem.resource.label}, + configuration: selectedItem.configuration, + quantity: selectedItem.quantity, + time_required: timeRequired, + note: selectedItem.note, + price_estimate: selectedItem.price_estimate, + }); + //create backbone view + var view = new Views.AddSubResourceModal({ + model : model, + is_new: false + }); + view.title = ts('Edit unlimited resource'); + CRM.BookingApp.modal.show(view); + }, + }), +}; + //Additaional charges dialog view +Views['EditAdhocChargesModal'] = Views.BookingProcessModal.extend({ + template: CRM._.template(CRM.$('#edit-adhoc-charges-template').html()), + className: "modal-dialog", + onRender: function(){ + var thisView = this; + CRM._.each(this.model.get('items'), function(item){ + thisView.$el.find('#' + item.name).html(item.item_price); + thisView.$el.find('input[name="' + item.name + '"]').val(item.quantity); + }); + this.$el.find('#adhoc-charges-note').val(this.model.get('note')); + this.$el.find('#total-adhoc-charges').html(this.model.get('total')); + Views.BookingProcessModal.prototype.onRender.apply(this, arguments); + }, + events: { + 'keypress .item': 'updatePrice', + 'keyup .item': 'updatePrice', + 'keydown .item': 'updatePrice', + 'click #update-adhoc-charges': 'updateAdhocCharges', + }, + updatePrice: function(e){ + var el = CRM.$(e.currentTarget); + var itemId = el.data('id'); + var price = el.data('price'); + var quantity = el.val(); + var name = el.attr('name'); + if(CRM.BookingApp.Utils.isPositiveInteger(quantity)){ + var itemPrice = parseFloat(price) * parseFloat(quantity); + this.$el.find('#'+ name).html(parseFloat(itemPrice).toFixed(2)); + var item = {item_id: itemId, name: name, price: price, quantity: quantity, item_price: itemPrice} + this.model.attributes.items[itemId] = item; + }else{ + this.$el.find('#'+ name).html(0); + this.$el.find('input[name="'+ name + '"]').val(''); + delete this.model.attributes.items[itemId]; + } + var items = this.model.get('items'); + var total = 0.0; + CRM._.each(items,function(item){ + total = parseFloat(total) + parseFloat(item.item_price); + }); + this.$el.find('#total-adhoc-charges').html(total.toFixed(2)); + this.model.set('total', total.toFixed(2)); + }, + updateAdhocCharges: function(e){ + e.preventDefault(); + var self = this; + var subResourceModel = CRM.BookingApp.getRegion().currentView.model; + this.model.set('note',this.$el.find('#adhoc-charges-note').val() ); + var adhocChargesTotal = this.model.get('total'); + subResourceModel.set('adhoc_charges', this.model.attributes); + var currentTotal = subResourceModel.get('sub_total'); + var discountAmount = subResourceModel.get('discount_amount'); + if(CRM.BookingApp.Utils.isPositiveNumber(discountAmount)){ + var newTotal = (parseFloat(adhocChargesTotal) + parseFloat(currentTotal)) - parseFloat(discountAmount); + }else{ + var newTotal = (parseFloat(adhocChargesTotal) + parseFloat(currentTotal)) - 0; + } + subResourceModel.set("total_price", parseFloat(newTotal).toFixed(2)); + console.log(subResourceModel); + self.triggerMethod('render:price', subResourceModel); + self.triggerMethod('update:resources', subResourceModel); + CRM.BookingApp.modal.triggerMethod('close'); + } +}); +//Sub(Unlimited) resource dialog view +Views['AddSubResourceModal'] = Views.BookingProcessModal.extend({ + template: CRM._.template(CRM.$('#add-sub-resource-template').html()), + initialize: function(options){ + this.isNew = options.is_new; + }, + events: { + 'click #add-to-basket': 'addSubResource', + 'change #resource_select': 'getConfigurations', + 'change #configuration_select': 'updatePriceEstmate', + 'keypress #quantity': 'updatePriceEstmate', + 'keyup #quantity': 'updatePriceEstmate', + 'keydown #quantity': 'updatePriceEstmate', + }, + onRender: function(){ + Views.BookingProcessModal.prototype.onRender.apply(this, arguments); + + var thisView = this; //set 'this' object for calling inside callback function + this.$el.find('#loading').show(); + + var initsdate = moment(this.model.get('time_required'), "YYYY-MM-DD HH:mm:ss"); + var timeTxt = [initsdate.hours() < 10 ? '0' + initsdate.hours() : initsdate.hours(), ":", initsdate.minute() < 10 ? '0' + initsdate.minute() : initsdate.minute()].join(""); + + //set the formatted months + var month=new Array(); + month[0]="01"; + month[1]="02"; + month[2]="03"; + month[3]="04"; + month[4]="05"; + month[5]="06"; + month[6]="07"; + month[7]="08"; + month[8]="09"; + month[9]="10"; + month[10]="11"; + month[11]="12"; + var dateTxt = [ initsdate.format("DD"),"/", month[initsdate.months()],"/", initsdate.years()].join(""); + this.$el.find("#required_date").val(dateTxt); + this.$el.find("#required_time").val(timeTxt); + + CRM.api('Resource', 'get', {'sequential': 1, 'is_unlimited': 1, 'is_deleted': 0, 'is_active': 1}, + {success: function(data) { + thisView.template = CRM._.template(CRM.$('#add-sub-resource-template').html()); + //var configValue = CRM_Booking_BAO_BookingConfig::getConfig(); + + thisView.$el.find("#required_date").datepicker({changeMonth: true, changeYear: true, dateFormat: 'dd/mm/yy'}); + thisView.$el.find('#required_time').timeEntry({show24Hours: true}).change(function() { + var log = $('#log'); + log.val(log.val() + ($('#defaultEntry').val() || 'blank') + '\n'); + }); + + //thisView.resources = data.values; + var tpl = CRM._.template(CRM.$('#select-option-template').html()); + var params = { + context: thisView, + template: tpl, + list: data.values, + element: "#resource_select", + first_option: ['- ', ts('select resource'), ' -'].join("") + } + thisView.triggerMethod("render:options", params); + + if(thisView.isNew == false){ + //set values + thisView.$el.find("#resource_select").val(thisView.model.get('resource').id); + //set configuration option value + thisView.$el.find("#configuration_select").attr('data-selected-id',thisView.model.get('configuration').id); + thisView.getConfigurations(); //call inside callback function + thisView.$el.find("#quantity").val(thisView.model.get('quantity')); + thisView.$el.find("#sub-resource-note").val(thisView.model.get('note')); + thisView.$el.find("#price-estimate").html(thisView.model.get('price_estimate')); + + thisView.$el.find("#quantity").prop('disabled', false); //enable quantity input text + } + thisView.$el.find('#loading').hide(); + thisView.$el.find('#content').show(); + } + } + ); + }, + + beforeClose: function() {this.$('form').find("#required_date").datepicker("destroy");}, + + /** + * Define form validation rules + * + * @param View view the view for which validation rules are created + * @param Object r the validation rules for the view + */ + onValidateRulesCreate: function(view, r) { + CRM.$.validator.addMethod("withinValidTime", function(value, element) { + var dateVals = CRM.$("#required_date").val().split("/"); + var timeVals = CRM.$("#required_time").val().split(":"); + var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); + var minDate = moment(startDate, "YYYY-MM-DD HH:mm:ss"); + var maxDate = moment(endDate, "YYYY-MM-DD HH:mm:ss"); + if (unlimitedTimeConfig==0){ + return true; + }else{ + var val = requiredDate>=minDate && requiredDate<=maxDate; + return val; + } + }, ts("Please select the date and time during the valid booking time.")); + CRM._.extend(r.rules, { + resource_select: { + required: true + }, + configuration_select: { + required: true + }, + "required_date": { + required: true, + "withinValidTime": true + }, + quantity: { + required: true, + digits: true + }, + }); + }, + //calcualte price + updatePriceEstmate: function(e){ + var quantitySelector = this.$el.find('#quantity'); + if(e.type == 'change'){ + var configSelect = this.$el.find('#configuration_select'); + if(configSelect.val() !== ''){ + configSelect.find(':selected').data('price'); + var price = configSelect.find(':selected').data('price'); + this.model.set('configuration', {id: configSelect.val(), label: configSelect.find(':selected').text(), price: price}); + quantitySelector.prop('disabled', false); + }else{ + qualitySelector.prop('disabled', true); + quantitySelector.val(''); + } + } + var configPrice = this.model.get('configuration').price + var quantity = quantitySelector.val(); + if(CRM.BookingApp.Utils.isPositiveInteger(quantity)){ + var priceEstimate = quantity * configPrice; + this.model.set('quantity', quantity); + this.model.set('price_estimate', priceEstimate.toFixed(2)); + this.$el.find('#price-estimate').html(priceEstimate.toFixed(2)); + } + }, + + //render configuration options + getConfigurations: function(e){ + selectedVal = CRM.$('#resource_select').val(); + if(selectedVal !== ""){ + var params = { + id: selectedVal, + sequential: 1, + 'api.resource_config_set.get': { + id: '$value.set_id', + is_active: 1, + is_deleted: 0, + 'api.resource_config_option.get': { + set_id: '$value.id', + is_active: 1, + is_deleted: 0, + 'api.option_group.get':{ + name: 'booking_size_unit', + }, + 'api.option_value.get':{ + value: '$value.unit_id', + sequential: 1, + option_group_id: '$value.api.option_group.get.id' + } + } + } + }; + this.$el.find('#config-loading').show(); + this.$el.find('#configuration_select').hide(); + var self = this; + CRM.api('Resource', 'get', params, + { context: self, + success: function(data) { + var resource = data['values']['0']; + var configSet = data['values']['0']['api.resource_config_set.get']; + if(configSet.count !== 1){ + var url = CRM.url('civicrm/admin/resource/config_set', { + reset: 1, + action: 'update', + id: resource.id + }); + CRM.alert(ts(''), ts('Your resource configuration set is disabled.') + + ' ' + ' ' + ts('Click here to edit configuration set.') + ' ', 'error'); + } + else if(configSet['values']['0']['api.resource_config_option.get'].count < 1){ + var url = CRM.url('civicrm/admin/resource/config_set/config_option', { + reset: 1, + sid: configSet['values']['0'].id + }); + CRM.alert(ts(''), ts('Your resource configuration options are all disabled or none have been created.') + + ' ' + ' ' + ts('Click here to edit or create options.') + ' ', 'error'); + } + else{ + var options = data['values']['0']['api.resource_config_set.get']['values']['0']['api.resource_config_option.get']['values']; + self.model.set('resource', {id: resource.id, label: resource.label}); + var params = { + context: self, + template: _.template(CRM.$('#select-config-option-template').html()), + list: options, + element: "#configuration_select", + first_option: '- ' + ts('select configuration') + ' -' + } + self.triggerMethod("render:options", params); + //set configuration options for edit mode of subresource view + var configSelectedId = this.$el.find('#configuration_select').data('selected-id'); //retrieve data from data-selected-id attribute + if(configSelectedId != 'undefined'){ + this.$el.find('#configuration_select').val(configSelectedId); + } + this.$el.find('#config-loading').hide(); + this.$el.find('#configuration_select').show(); + } + } + }); + }else{ + var params = { + context:this, + template: _.template($('#select-config-option-template').html()), + list: new Array(), + element: "#configuration_select", + first_option: '- ' + ts('select configuration') + ' -'} + self.triggerMethod("render:options", params); + this.$el.find('#configuration_select').prop('disabled', true); + } + }, + + //save sub-resource + addSubResource: function(e){ + e.preventDefault(); + var self = this; + var targetForm = e.currentTarget; + while(targetForm.tagName != 'FORM') { + targetForm = targetForm.parentNode; + } + if (!CRM.$(targetForm).valid()) { + var errors = CRM.$(targetForm).validate().errors(); + this.onRenderError(errors); + return false; + } + CRM.$(targetForm).find("#required_date").datepicker("destroy"); + this.model.set('note', this.$el.find('#sub-resource-note').val()); + var dateVals = this.$el.find("#required_date").val().split("/"); + var timeVals = this.$el.find("#required_time").val().split(":"); + var requiredDate = new Date(dateVals[2],dateVals[1]-1,dateVals[0],timeVals[0],timeVals[1]); + var timeRequired = moment(requiredDate).format("YYYY-M-D HH:mm"); + this.model.set('time_required', timeRequired); + + var parentRefId = this.model.get('parent_ref_id'); + + var refId = null; + if(this.isNew){ + refId = CRM.BookingApp.Utils.getCurrentUnixTimstamp(); + this.model.set('ref_id', refId); + }else{ + refId = this.model.get('ref_id'); + } + + var template = _.template(CRM.$('#sub-resource-row-template').html()); + + //ui update + if(this.isNew){ + CRM.$('#crm-booking-sub-resource-table-' + parentRefId).find('tbody').append(template(this.model.toJSON())); + }else{ + CRM.$('#crm-booking-sub-resource-individual-row-'+refId).replaceWith(template(this.model.toJSON())); + } + CRM.$('#crm-booking-sub-resource-row-' + parentRefId).show(); + var resourceRefId = this.model.get("parent_ref_id"); + var priceEstimate = this.model.get("price_estimate"); + var subResourceRefId = this.model.get("ref_id"); + + var subResourceModel = CRM.BookingApp.getRegion().currentView.model; + subResourceModel.attributes.sub_resources[refId] = this.model.toJSON(); + + var currentSubTotal = subResourceModel.get('sub_total'); + var newSubTotal = parseFloat(priceEstimate) + parseFloat(subResourceModel.get('sub_total')); + subResourceModel.set("sub_total", newSubTotal); + + var currentTotal = subResourceModel.get('total_price'); + var newTotal = (parseFloat(currentTotal) - parseFloat(currentSubTotal)) + parseFloat(newSubTotal); + subResourceModel.set("total_price", newTotal); + + var currentResourceTotal = ResourceTotal[resourceRefId]; + + if(this.isNew){ + var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate); + PriceCache[subResourceRefId] = parseFloat(priceEstimate); + }else{ + var resourceTotalPrice = parseFloat(currentResourceTotal) + parseFloat(priceEstimate) - PriceCache[subResourceRefId]; + subResourceModel.attributes.sub_total = subResourceModel.attributes.sub_total - PriceCache[subResourceRefId]; + subResourceModel.attributes.total_price = subResourceModel.attributes.total_price - PriceCache[subResourceRefId]; + PriceCache[subResourceRefId] = parseFloat(priceEstimate); + } + ResourceTotal[resourceRefId] = resourceTotalPrice; + subResourceModel.attributes.resources[resourceRefId] = resourceTotalPrice.toFixed(2); + //set total price for resource row + CRM.$("#resource-total-price-" + resourceRefId).text(subResourceModel.attributes.resources[resourceRefId]); + self.triggerMethod('render:price', subResourceModel, resourceRefId ); + self.triggerMethod('update:resources', subResourceModel); + CRM.BookingApp.modal.triggerMethod('close'); + }, }); -}(CRM.$, CRM.ts('uk.co.compucorp.civicrm.booking'))); From 5d220786e08f47a97160214f0957d8bee8749b71 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Fri, 11 Jun 2021 23:47:22 +0200 Subject: [PATCH 34/51] Discount amount field change handler --- js/booking/add-sub-resource/view.js | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/js/booking/add-sub-resource/view.js b/js/booking/add-sub-resource/view.js index 10e225cb..75ea1795 100755 --- a/js/booking/add-sub-resource/view.js +++ b/js/booking/add-sub-resource/view.js @@ -69,7 +69,7 @@ var Views = { } this.model.attributes.total_price = CRM.$("#total_price").val(); this.model.attributes.sub_total = CRM.$("#sub_total").val(); - this.model.attributes.adhoc_charges = CRM.$("#adhoc_charge").val(); + //this.model.attributes.adhoc_charges = CRM.$("#adhoc_charge").val(); this.model.attributes.discount_amount = CRM.$("#discount_amount").val(); }, onRender: function(){ @@ -147,7 +147,7 @@ var Views = { var currentSubTotal = parseFloat(this.model.get('sub_total')); var currentAdhocCharges = parseFloat(this.model.get('adhoc_charges').total); // Get the discount amount stripping out non-numeric characters - var sDiscountAmount = $(e.currentTarget).val().replace(/[^\d.-]/g, ''); + var sDiscountAmount = CRM.$(e.currentTarget).val().replace(/[^\d.-]/g, ''); var fDiscountAmount = parseFloat(sDiscountAmount); if (!_.isNumber(fDiscountAmount) || _.isNaN(fDiscountAmount)) { fDiscountAmount = 0; @@ -157,7 +157,7 @@ var Views = { try{newTotal = newTotal.toFixed(2); }catch(err){} this.model.set("total_price", newTotal); this.model.set("discount_amount", sDiscountAmount); - CRM.BookingApp.vent.trigger('render:price', this.model ); + this.triggerMethod('render:price', this.model); }, editAdhocCharge: function(e) { @@ -212,7 +212,7 @@ var Views = { selectedItem = this.model.attributes.sub_resources[refId]; //create backbone model form json object - var model = new CRM.BookingApp.Entities.AddSubResource({ + var model = new Entities.AddSubResource({ parent_ref_id : parentRef, ref_id : refId, resource: {id : selectedItem.resource.id, label :selectedItem.resource.label}, @@ -230,6 +230,26 @@ var Views = { view.title = ts('Edit unlimited resource'); CRM.BookingApp.modal.show(view); }, + onRenderPrice: function(model) { + CRM.$("#total_price").val(model.attributes.total_price); + var totalText = model.attributes.total_price; + try{ + if(model.attributes.total_price>=0){ + var totalText = model.attributes.total_price.toFixed(2); + } + }catch(err){} + CRM.$("#total-price-summary").text(totalText); + CRM.$("#discount_amount").val(model.attributes.discount_amount); + CRM.$('#discount_amount_dummy').val(model.attributes.discount_amount); + CRM.$("#sub_total").val(model.attributes.sub_total); + var subtotalText = model.attributes.sub_total; + try{ + var subtotalText = model.attributes.sub_total.toFixed(2); + }catch(err){} + CRM.$("#sub-total-summary").text(subtotalText); + CRM.$('#adhoc_charge').val(model.attributes.adhoc_charges.total); + CRM.$('#ad-hoc-charge-summary').html(model.attributes.adhoc_charges.total); + }, }), }; //Additaional charges dialog view From 27fd8c0c9a8333390ecad88a7fe1b24e07c12783 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Sat, 12 Jun 2021 13:24:05 +0200 Subject: [PATCH 35/51] Make info page work without js issues To be honest i don't know that it works well or not. Couldn't catch the render dialog event --- CRM/Booking/Form/Booking/Info.php | 4 +-- js/booking/booking-info/app.js | 32 +++++++++---------- js/booking/booking-info/view.js | 51 +++++++++++-------------------- 3 files changed, 34 insertions(+), 53 deletions(-) diff --git a/CRM/Booking/Form/Booking/Info.php b/CRM/Booking/Form/Booking/Info.php index 5372c42c..097d8874 100644 --- a/CRM/Booking/Form/Booking/Info.php +++ b/CRM/Booking/Form/Booking/Info.php @@ -406,8 +406,8 @@ static function registerScripts() { ->addScriptFile(E::LONG_NAME, 'packages/backbone.modelbinder.js', 125, 'html-header', FALSE) ->addScriptFile(E::LONG_NAME, 'js/vendor/crm.backbone.js', 130, 'html-header', FALSE) - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/booking-info/app.js', 150, 'html-header') - ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/booking-info/view.js', 170, 'html-header'); + ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/booking-info/view.js', 150, 'html-header') + ->addScriptFile('uk.co.compucorp.civicrm.booking', 'js/booking/booking-info/app.js', 170, 'html-header'); $templateDir = CRM_Extension_System::singleton()->getMapper()->keyToBasePath('uk.co.compucorp.civicrm.booking') . '/templates/'; $region = CRM_Core_Region::instance('page-header'); diff --git a/js/booking/booking-info/app.js b/js/booking/booking-info/app.js index 49d0d075..d98c5cb9 100644 --- a/js/booking/booking-info/app.js +++ b/js/booking/booking-info/app.js @@ -1,39 +1,37 @@ -CRM.BookingApp = new Backbone.Marionette.Application(); - -var ModalRegion = Backbone.Marionette.Region.extend({ +var ModalRegion = Marionette.Region.extend({ el: "#crm-booking-profile-form", constructor: function(){ - Backbone.Marionette.Region.prototype.constructor.apply(this, arguments); + Marionette.Region.prototype.constructor.apply(this, arguments); this.on("show", this.showModal, this); this.on("close", this.hideModal, this); }, showModal: function(view){ - // view.on("close", this.hideModal, this); - - }, hideModal: function(){ cj('#crm-booking-dialog').dialog().dialog( "destroy" ); } }); - -CRM.BookingApp.addRegions({ +var MyApp = Marionette.Application.extend({ contactRegion: "#contact-container", orgRegion: "#organisation-container", - modal: ModalRegion - + modal: new ModalRegion(), + onRenderDialog: function(profile, title, elementId, targetElementId){ + var view = new BookingInfoViews.Dialog({ + profile: profile, + title: title, + elementId: elementId, + targetElementId: targetElementId, + }); + CRM.BookingApp.modal.show(view); + }, }); +CRM.BookingApp = new MyApp(); + CRM.BookingApp.on("initialize:after", function(){ if( ! Backbone.History.started) Backbone.history.start(); }); - -CRM.BookingApp.addInitializer(function(){ - -}); - - diff --git a/js/booking/booking-info/view.js b/js/booking/booking-info/view.js index 3d1f7d65..842d4204 100644 --- a/js/booking/booking-info/view.js +++ b/js/booking/booking-info/view.js @@ -1,20 +1,6 @@ -(function ($, ts){ -CRM.BookingApp.module('BookingInfo', function(BookingInfo, BookingApp, Backbone, Marionette, $, _) { - - - - CRM.BookingApp.vent.on("render:dialog", function (profile, title, elementId, targetElementId){ - var view = new BookingInfo.Dialog({ - profile: profile, - title: title, - elementId: elementId, - targetElementId: targetElementId}); - CRM.BookingApp.modal.show(view); - }); - - - BookingInfo.Dialog = Backbone.Marionette.ItemView.extend({ - template: '#booking-info-profile-template', +var BookingInfoViews = { + Dialog: Marionette.View.extend({ + template: CRM._.template(CRM.$('#booking-info-profile-template').html()), initialize: function(options){ this.title = options.title; this.profile = options.profile; @@ -33,25 +19,24 @@ CRM.BookingApp.module('BookingInfo', function(BookingInfo, BookingApp, Backbone, modal: true, minWidth: 600, open: function() { - $.getJSON(self.profileUrl, function(data) { + CRM.$.getJSON(self.profileUrl, function(data) { self.displayNewContactProfile(data); }); }, close: function() { - $(this).dialog('destroy'); + CRM.$(this).dialog('destroy'); } }); }, - displayNewContactProfile: function(data) { var self = this; - $('#crm-booking-profile-form').html(data.content); - $("#crm-booking-profile-form .cancel.form-submit").click(function() { - $("#crm-booking-profile-form").dialog('close'); - return false; - }); - $('#email-Primary').addClass('email'); - $("#crm-booking-profile-form form").ajaxForm({ + CRM.$('#crm-booking-profile-form').html(data.content); + CRM.$("#crm-booking-profile-form .cancel.form-submit").click(function() { + CRM.$("#crm-booking-profile-form").dialog('close'); + return false; + }); + CRM.$('#email-Primary').addClass('email'); + CRM.$("#crm-booking-profile-form form").ajaxForm({ // context=dialog triggers civi's profile to respond with json instead of an html redirect // but it also results in lots of unwanted scripts being added to the form snippet, so we // add it here during submission and not during form retrieval. @@ -59,11 +44,11 @@ CRM.BookingApp.module('BookingInfo', function(BookingInfo, BookingApp, Backbone, dataType: 'json', success: function(response) { if (response.newContactSuccess) { - $('#crm-booking-profile-form').dialog('close'); + CRM.$('#crm-booking-profile-form').dialog('close'); CRM.alert(ts('%1 has been created.', {1: response.displayName}), ts('Contact Saved'), 'success'); console.log('test',self.targetElementId); - $('input[name="'+self.targetElementId+'"]').val(response.contactID); - $(self.elementId).val(response.displayName); + CRM.$('input[name="'+self.targetElementId+'"]').val(response.contactID); + CRM.$(self.elementId).val(response.displayName); } else { self.displayNewContactProfile(response); @@ -72,7 +57,5 @@ CRM.BookingApp.module('BookingInfo', function(BookingInfo, BookingApp, Backbone, }).validate(CRM.validate.params); } - }); - -}); -}(CRM.$, CRM.ts('uk.co.compucorp.civicrm.booking'))); + }), +}; From 473bf1fe5c00068f54237cb70beb87d96c6b8924 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Wed, 23 Jun 2021 17:33:14 +0200 Subject: [PATCH 36/51] Fix display condition on day-view form --- CRM/Booking/Form/DayView.php | 1 + templates/CRM/Booking/Form/DayView.tpl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CRM/Booking/Form/DayView.php b/CRM/Booking/Form/DayView.php index b6ee6220..143c5206 100644 --- a/CRM/Booking/Form/DayView.php +++ b/CRM/Booking/Form/DayView.php @@ -47,6 +47,7 @@ function postProcess() { $resources = CRM_Booking_BAO_Slot::getSlotDetailsOrderByResourceBetweenDate($from, $to); //put resources result to values, being ready to display. $values['resources'] = $resources; + $values['resultDisplay'] = true; if(empty($resources)){ //check empty result //Convert date to show on no match found view diff --git a/templates/CRM/Booking/Form/DayView.tpl b/templates/CRM/Booking/Form/DayView.tpl index 8a4198ff..0f8e5883 100644 --- a/templates/CRM/Booking/Form/DayView.tpl +++ b/templates/CRM/Booking/Form/DayView.tpl @@ -23,7 +23,7 @@ {* Search DayView results *} -{if $resources} +{if $resultDisplay} {if empty($resources)} {* No matches for submitted search request or viewing an empty result. *}
From 1fe1fb8c71d17c0c00fb3de871fb758556c96264 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Wed, 23 Jun 2021 17:34:37 +0200 Subject: [PATCH 37/51] Remove crmAccordion call It is handled with a function in common.js that is already loaded on the form --- templates/CRM/Booking/Form/DayView.tpl | 7 ------- 1 file changed, 7 deletions(-) diff --git a/templates/CRM/Booking/Form/DayView.tpl b/templates/CRM/Booking/Form/DayView.tpl index 0f8e5883..301a1be6 100644 --- a/templates/CRM/Booking/Form/DayView.tpl +++ b/templates/CRM/Booking/Form/DayView.tpl @@ -43,11 +43,4 @@ {/if}
-{literal} - -{/literal} {/crmScope} From fac427b32dbf0c5e807143c0c83a2a00047dcb8d Mon Sep 17 00:00:00 2001 From: akosgarai Date: Wed, 23 Jun 2021 17:44:05 +0200 Subject: [PATCH 38/51] Remove other crmAccordion call --- templates/CRM/Booking/Form/Search.tpl | 7 ------- 1 file changed, 7 deletions(-) diff --git a/templates/CRM/Booking/Form/Search.tpl b/templates/CRM/Booking/Form/Search.tpl index 45d070c0..dec1585f 100644 --- a/templates/CRM/Booking/Form/Search.tpl +++ b/templates/CRM/Booking/Form/Search.tpl @@ -74,12 +74,5 @@
{/if} -{literal} - -{/literal} {/crmScope} From f7d6d65f83e74f375400bf73ed2120a5b4d8b664 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Wed, 23 Jun 2021 17:49:12 +0200 Subject: [PATCH 39/51] Fix jquery toggle function --- js/booking/add-sub-resource/view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/booking/add-sub-resource/view.js b/js/booking/add-sub-resource/view.js index 75ea1795..feb39fde 100755 --- a/js/booking/add-sub-resource/view.js +++ b/js/booking/add-sub-resource/view.js @@ -174,8 +174,8 @@ var Views = { }, toggleHiddenElement: function(e){ - var row = $(e.currentTarget).data('ref'); - $('#crm-booking-sub-resource-row-' + row).toggle(); + var row = CRM.$(e.currentTarget).data('ref'); + CRM.$('#crm-booking-sub-resource-row-' + row).toggle(); }, removeSubResource: function(e){ var self = this; From 40b6f482ff97b0f1c21e0c02521371976ad453c6 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Wed, 23 Jun 2021 18:56:33 +0200 Subject: [PATCH 40/51] Fix jquery function --- js/booking/add-sub-resource/view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/booking/add-sub-resource/view.js b/js/booking/add-sub-resource/view.js index feb39fde..fea6e229 100755 --- a/js/booking/add-sub-resource/view.js +++ b/js/booking/add-sub-resource/view.js @@ -365,8 +365,8 @@ Views['AddSubResourceModal'] = Views.BookingProcessModal.extend({ thisView.$el.find("#required_date").datepicker({changeMonth: true, changeYear: true, dateFormat: 'dd/mm/yy'}); thisView.$el.find('#required_time').timeEntry({show24Hours: true}).change(function() { - var log = $('#log'); - log.val(log.val() + ($('#defaultEntry').val() || 'blank') + '\n'); + var log = CRM.$('#log'); + log.val(log.val() + (CRM.$('#defaultEntry').val() || 'blank') + '\n'); }); //thisView.resources = data.values; From b06f4fae498305e378b919d778aedca40cc6d3a6 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Wed, 23 Jun 2021 20:50:07 +0200 Subject: [PATCH 41/51] Regenerate civix.php file with civix command command --- booking.civix.php | 319 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 253 insertions(+), 66 deletions(-) diff --git a/booking.civix.php b/booking.civix.php index e6bd993c..71a0ca75 100644 --- a/booking.civix.php +++ b/booking.civix.php @@ -6,11 +6,10 @@ * The ExtensionUtil class provides small stubs for accessing resources of this * extension. */ - class CRM_Booking_ExtensionUtil { - const SHORT_NAME = "booking"; - const LONG_NAME = "uk.co.compucorp.civicrm.booking"; - const CLASS_PREFIX = "CRM_Booking"; + const SHORT_NAME = 'booking'; + const LONG_NAME = 'uk.co.compucorp.civicrm.booking'; + const CLASS_PREFIX = 'CRM_Booking'; /** * Translate a string using the extension's domain. @@ -25,9 +24,9 @@ class CRM_Booking_ExtensionUtil { * Translated text. * @see ts */ - public static function ts($text, $params = array()) { + public static function ts($text, $params = []) { if (!array_key_exists('domain', $params)) { - $params['domain'] = array(self::LONG_NAME, NULL); + $params['domain'] = [self::LONG_NAME, NULL]; } return ts($text, $params); } @@ -77,33 +76,43 @@ public static function findClass($suffix) { } } + +use CRM_Booking_ExtensionUtil as E; + /** - * (Delegated) Implementation of hook_civicrm_config + * (Delegated) Implements hook_civicrm_config(). + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_config */ function _booking_civix_civicrm_config(&$config = NULL) { static $configured = FALSE; - if ($configured) return; + if ($configured) { + return; + } $configured = TRUE; $template =& CRM_Core_Smarty::singleton(); - $extRoot = dirname( __FILE__ ) . DIRECTORY_SEPARATOR; + $extRoot = dirname(__FILE__) . DIRECTORY_SEPARATOR; $extDir = $extRoot . 'templates'; - if ( is_array( $template->template_dir ) ) { - array_unshift( $template->template_dir, $extDir ); - } else { - $template->template_dir = array( $extDir, $template->template_dir ); + if (is_array($template->template_dir)) { + array_unshift($template->template_dir, $extDir); + } + else { + $template->template_dir = [$extDir, $template->template_dir]; } - $include_path = $extRoot . PATH_SEPARATOR . get_include_path( ); - set_include_path( $include_path ); + $include_path = $extRoot . PATH_SEPARATOR . get_include_path(); + set_include_path($include_path); } /** - * (Delegated) Implementation of hook_civicrm_xmlMenu + * (Delegated) Implements hook_civicrm_xmlMenu(). * * @param $files array(string) + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_xmlMenu */ function _booking_civix_civicrm_xmlMenu(&$files) { foreach (_booking_civix_glob(__DIR__ . '/xml/Menu/*.xml') as $file) { @@ -112,57 +121,83 @@ function _booking_civix_civicrm_xmlMenu(&$files) { } /** - * Implementation of hook_civicrm_install + * Implements hook_civicrm_install(). + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install */ function _booking_civix_civicrm_install() { _booking_civix_civicrm_config(); if ($upgrader = _booking_civix_upgrader()) { - return $upgrader->onInstall(); + $upgrader->onInstall(); } } /** - * Implementation of hook_civicrm_uninstall + * Implements hook_civicrm_postInstall(). + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall + */ +function _booking_civix_civicrm_postInstall() { + _booking_civix_civicrm_config(); + if ($upgrader = _booking_civix_upgrader()) { + if (is_callable([$upgrader, 'onPostInstall'])) { + $upgrader->onPostInstall(); + } + } +} + +/** + * Implements hook_civicrm_uninstall(). + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall */ function _booking_civix_civicrm_uninstall() { _booking_civix_civicrm_config(); if ($upgrader = _booking_civix_upgrader()) { - return $upgrader->onUninstall(); + $upgrader->onUninstall(); } } /** - * (Delegated) Implementation of hook_civicrm_enable + * (Delegated) Implements hook_civicrm_enable(). + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable */ function _booking_civix_civicrm_enable() { _booking_civix_civicrm_config(); if ($upgrader = _booking_civix_upgrader()) { - if (is_callable(array($upgrader, 'onEnable'))) { - return $upgrader->onEnable(); + if (is_callable([$upgrader, 'onEnable'])) { + $upgrader->onEnable(); } } } /** - * (Delegated) Implementation of hook_civicrm_disable + * (Delegated) Implements hook_civicrm_disable(). + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable + * @return mixed */ function _booking_civix_civicrm_disable() { _booking_civix_civicrm_config(); if ($upgrader = _booking_civix_upgrader()) { - if (is_callable(array($upgrader, 'onDisable'))) { - return $upgrader->onDisable(); + if (is_callable([$upgrader, 'onDisable'])) { + $upgrader->onDisable(); } } } /** - * (Delegated) Implementation of hook_civicrm_upgrade + * (Delegated) Implements hook_civicrm_upgrade(). * * @param $op string, the type of operation being performed; 'check' or 'enqueue' * @param $queue CRM_Queue_Queue, (for 'enqueue') the modifiable list of pending up upgrade tasks * - * @return mixed based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending) - * for 'enqueue', returns void + * @return mixed + * based on op. for 'check', returns array(boolean) (TRUE if upgrades are pending) + * for 'enqueue', returns void + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_upgrade */ function _booking_civix_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) { if ($upgrader = _booking_civix_upgrader()) { @@ -170,31 +205,36 @@ function _booking_civix_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) { } } +/** + * @return CRM_Booking_Upgrader + */ function _booking_civix_upgrader() { - if (!file_exists(__DIR__.'/CRM/Booking/Upgrader.php')) { + if (!file_exists(__DIR__ . '/CRM/Booking/Upgrader.php')) { return NULL; - } else { + } + else { return CRM_Booking_Upgrader_Base::instance(); } } /** - * Search directory tree for files which match a glob pattern + * Search directory tree for files which match a glob pattern. * * Note: Dot-directories (like "..", ".git", or ".svn") will be ignored. * Note: In Civi 4.3+, delegate to CRM_Utils_File::findFiles() * - * @param $dir string, base dir - * @param $pattern string, glob pattern, eg "*.txt" - * @return array(string) + * @param string $dir base dir + * @param string $pattern , glob pattern, eg "*.txt" + * + * @return array */ function _booking_civix_find_files($dir, $pattern) { - if (is_callable(array('CRM_Utils_File', 'findFiles'))) { + if (is_callable(['CRM_Utils_File', 'findFiles'])) { return CRM_Utils_File::findFiles($dir, $pattern); } - $todos = array($dir); - $result = array(); + $todos = [$dir]; + $result = []; while (!empty($todos)) { $subdir = array_shift($todos); foreach (_booking_civix_glob("$subdir/$pattern") as $match) { @@ -205,8 +245,9 @@ function _booking_civix_find_files($dir, $pattern) { if ($dh = opendir($subdir)) { while (FALSE !== ($entry = readdir($dh))) { $path = $subdir . DIRECTORY_SEPARATOR . $entry; - if ($entry{0} == '.') { - } elseif (is_dir($path)) { + if ($entry[0] == '.') { + } + elseif (is_dir($path)) { $todos[] = $path; } } @@ -215,24 +256,103 @@ function _booking_civix_find_files($dir, $pattern) { } return $result; } + /** - * (Delegated) Implementation of hook_civicrm_managed + * (Delegated) Implements hook_civicrm_managed(). * * Find any *.mgd.php files, merge their content, and return. + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_managed */ function _booking_civix_civicrm_managed(&$entities) { $mgdFiles = _booking_civix_find_files(__DIR__, '*.mgd.php'); + sort($mgdFiles); foreach ($mgdFiles as $file) { $es = include $file; foreach ($es as $e) { if (empty($e['module'])) { - $e['module'] = 'uk.co.compucorp.civicrm.booking'; + $e['module'] = E::LONG_NAME; + } + if (empty($e['params']['version'])) { + $e['params']['version'] = '3'; } $entities[] = $e; } } } +/** + * (Delegated) Implements hook_civicrm_caseTypes(). + * + * Find any and return any files matching "xml/case/*.xml" + * + * Note: This hook only runs in CiviCRM 4.4+. + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_caseTypes + */ +function _booking_civix_civicrm_caseTypes(&$caseTypes) { + if (!is_dir(__DIR__ . '/xml/case')) { + return; + } + + foreach (_booking_civix_glob(__DIR__ . '/xml/case/*.xml') as $file) { + $name = preg_replace('/\.xml$/', '', basename($file)); + if ($name != CRM_Case_XMLProcessor::mungeCaseType($name)) { + $errorMessage = sprintf("Case-type file name is malformed (%s vs %s)", $name, CRM_Case_XMLProcessor::mungeCaseType($name)); + throw new CRM_Core_Exception($errorMessage); + } + $caseTypes[$name] = [ + 'module' => E::LONG_NAME, + 'name' => $name, + 'file' => $file, + ]; + } +} + +/** + * (Delegated) Implements hook_civicrm_angularModules(). + * + * Find any and return any files matching "ang/*.ang.php" + * + * Note: This hook only runs in CiviCRM 4.5+. + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_angularModules + */ +function _booking_civix_civicrm_angularModules(&$angularModules) { + if (!is_dir(__DIR__ . '/ang')) { + return; + } + + $files = _booking_civix_glob(__DIR__ . '/ang/*.ang.php'); + foreach ($files as $file) { + $name = preg_replace(':\.ang\.php$:', '', basename($file)); + $module = include $file; + if (empty($module['ext'])) { + $module['ext'] = E::LONG_NAME; + } + $angularModules[$name] = $module; + } +} + +/** + * (Delegated) Implements hook_civicrm_themes(). + * + * Find any and return any files matching "*.theme.php" + */ +function _booking_civix_civicrm_themes(&$themes) { + $files = _booking_civix_glob(__DIR__ . '/*.theme.php'); + foreach ($files as $file) { + $themeMeta = include $file; + if (empty($themeMeta['name'])) { + $themeMeta['name'] = preg_replace(':\.theme\.php$:', '', basename($file)); + } + if (empty($themeMeta['ext'])) { + $themeMeta['ext'] = E::LONG_NAME; + } + $themes[$themeMeta['name']] = $themeMeta; + } +} + /** * Glob wrapper which is guaranteed to return an array. * @@ -241,50 +361,117 @@ function _booking_civix_civicrm_managed(&$entities) { * result for an empty match is sometimes array() and sometimes FALSE. * This wrapper provides consistency. * - * @see http://php.net/glob + * @link http://php.net/glob * @param string $pattern - * @return array, possibly empty + * + * @return array */ function _booking_civix_glob($pattern) { $result = glob($pattern); - return is_array($result) ? $result : array(); + return is_array($result) ? $result : []; } /** - * Inserts a navigation menu item at a given place in the hierarchy + * Inserts a navigation menu item at a given place in the hierarchy. + * + * @param array $menu - menu hierarchy + * @param string $path - path to parent of this item, e.g. 'my_extension/submenu' + * 'Mailing', or 'Administer/System Settings' + * @param array $item - the item to insert (parent/child attributes will be + * filled for you) * - * $menu - menu hierarchy - * $path - path where insertion should happen (ie. Administer/System Settings) - * $item - menu you need to insert (parent/child attributes will be filled for you) - * $parentId - used internally to recurse in the menu structure + * @return bool */ -function _booking_civix_insert_navigation_menu(&$menu, $path, $item, $parentId = NULL) { - static $navId; - +function _booking_civix_insert_navigation_menu(&$menu, $path, $item) { // If we are done going down the path, insert menu if (empty($path)) { - if (!$navId) $navId = CRM_Core_DAO::singleValueQuery("SELECT max(id) FROM civicrm_navigation"); - $navId ++; - $menu[$navId] = array ( - 'attributes' => array_merge($item, array( + $menu[] = [ + 'attributes' => array_merge([ 'label' => CRM_Utils_Array::value('name', $item), 'active' => 1, - 'parentID' => $parentId, - 'navID' => $navId, - )) - ); - return true; - } else { + ], $item), + ]; + return TRUE; + } + else { // Find an recurse into the next level down - $found = false; + $found = FALSE; $path = explode('/', $path); $first = array_shift($path); foreach ($menu as $key => &$entry) { if ($entry['attributes']['name'] == $first) { - if (!$entry['child']) $entry['child'] = array(); - $found = _booking_civix_insert_navigation_menu($entry['child'], implode('/', $path), $item, $key); + if (!isset($entry['child'])) { + $entry['child'] = []; + } + $found = _booking_civix_insert_navigation_menu($entry['child'], implode('/', $path), $item); } } return $found; } } + +/** + * (Delegated) Implements hook_civicrm_navigationMenu(). + */ +function _booking_civix_navigationMenu(&$nodes) { + if (!is_callable(['CRM_Core_BAO_Navigation', 'fixNavigationMenu'])) { + _booking_civix_fixNavigationMenu($nodes); + } +} + +/** + * Given a navigation menu, generate navIDs for any items which are + * missing them. + */ +function _booking_civix_fixNavigationMenu(&$nodes) { + $maxNavID = 1; + array_walk_recursive($nodes, function($item, $key) use (&$maxNavID) { + if ($key === 'navID') { + $maxNavID = max($maxNavID, $item); + } + }); + _booking_civix_fixNavigationMenuItems($nodes, $maxNavID, NULL); +} + +function _booking_civix_fixNavigationMenuItems(&$nodes, &$maxNavID, $parentID) { + $origKeys = array_keys($nodes); + foreach ($origKeys as $origKey) { + if (!isset($nodes[$origKey]['attributes']['parentID']) && $parentID !== NULL) { + $nodes[$origKey]['attributes']['parentID'] = $parentID; + } + // If no navID, then assign navID and fix key. + if (!isset($nodes[$origKey]['attributes']['navID'])) { + $newKey = ++$maxNavID; + $nodes[$origKey]['attributes']['navID'] = $newKey; + $nodes[$newKey] = $nodes[$origKey]; + unset($nodes[$origKey]); + $origKey = $newKey; + } + if (isset($nodes[$origKey]['child']) && is_array($nodes[$origKey]['child'])) { + _booking_civix_fixNavigationMenuItems($nodes[$origKey]['child'], $maxNavID, $nodes[$origKey]['attributes']['navID']); + } + } +} + +/** + * (Delegated) Implements hook_civicrm_alterSettingsFolders(). + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_alterSettingsFolders + */ +function _booking_civix_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) { + $settingsDir = __DIR__ . DIRECTORY_SEPARATOR . 'settings'; + if (!in_array($settingsDir, $metaDataFolders) && is_dir($settingsDir)) { + $metaDataFolders[] = $settingsDir; + } +} + +/** + * (Delegated) Implements hook_civicrm_entityTypes(). + * + * Find any *.entityType.php files, merge their content, and return. + * + * @link https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes + */ +function _booking_civix_civicrm_entityTypes(&$entityTypes) { + $entityTypes = array_merge($entityTypes, []); +} From 9793b9a03cc385a95de5d94f3e6201a4eb54da9f Mon Sep 17 00:00:00 2001 From: akosgarai Date: Wed, 23 Jun 2021 21:00:27 +0200 Subject: [PATCH 42/51] Regenerate upgrader base with civix command --- CRM/Booking/Upgrader/Base.php | 241 +++++++++++++++++++++++++--------- 1 file changed, 180 insertions(+), 61 deletions(-) diff --git a/CRM/Booking/Upgrader/Base.php b/CRM/Booking/Upgrader/Base.php index ae54f2ff..54ce5301 100644 --- a/CRM/Booking/Upgrader/Base.php +++ b/CRM/Booking/Upgrader/Base.php @@ -1,7 +1,7 @@ ctx = array_shift($args); $instance->queue = $instance->ctx->queue; $method = array_shift($args); - return call_user_func_array(array($instance, $method), $args); + return call_user_func_array([$instance, $method], $args); } + /** + * CRM_Booking_Upgrader_Base constructor. + * + * @param $extensionName + * @param $extensionDir + */ public function __construct($extensionName, $extensionDir) { $this->extensionName = $extensionName; $this->extensionDir = $extensionDir; @@ -74,49 +88,89 @@ public function __construct($extensionName, $extensionDir) { // ******** Task helpers ******** /** - * Run a CustomData file + * Run a CustomData file. * - * @param string $relativePath the CustomData XML file path (relative to this extension's dir) + * @param string $relativePath + * the CustomData XML file path (relative to this extension's dir) * @return bool */ public function executeCustomDataFile($relativePath) { $xml_file = $this->extensionDir . '/' . $relativePath; - require_once 'CRM/Utils/Migrate/Import.php'; + return $this->executeCustomDataFileByAbsPath($xml_file); + } + + /** + * Run a CustomData file + * + * @param string $xml_file + * the CustomData XML file path (absolute path) + * + * @return bool + */ + protected function executeCustomDataFileByAbsPath($xml_file) { $import = new CRM_Utils_Migrate_Import(); $import->run($xml_file); return TRUE; } /** - * Run a SQL file + * Run a SQL file. + * + * @param string $relativePath + * the SQL file path (relative to this extension's dir) * - * @param string $relativePath the SQL file path (relative to this extension's dir) * @return bool */ public function executeSqlFile($relativePath) { CRM_Utils_File::sourceSQLFile( CIVICRM_DSN, - $this->extensionDir . '/' . $relativePath + $this->extensionDir . DIRECTORY_SEPARATOR . $relativePath ); return TRUE; } /** - * Run one SQL query + * Run the sql commands in the specified file. + * + * @param string $tplFile + * The SQL file path (relative to this extension's dir). + * Ex: "sql/mydata.mysql.tpl". + * + * @return bool + * @throws \CRM_Core_Exception + */ + public function executeSqlTemplate($tplFile) { + // Assign multilingual variable to Smarty. + $upgrade = new CRM_Upgrade_Form(); + + $tplFile = CRM_Utils_File::isAbsolute($tplFile) ? $tplFile : $this->extensionDir . DIRECTORY_SEPARATOR . $tplFile; + $smarty = CRM_Core_Smarty::singleton(); + $smarty->assign('domainID', CRM_Core_Config::domainID()); + CRM_Utils_File::sourceSQLFile( + CIVICRM_DSN, $smarty->fetch($tplFile), NULL, TRUE + ); + return TRUE; + } + + /** + * Run one SQL query. * * This is just a wrapper for CRM_Core_DAO::executeSql, but it - * provides syntatic sugar for queueing several tasks that + * provides syntactic sugar for queueing several tasks that * run different queries + * + * @return bool */ - public function executeSql($query, $params = array()) { + public function executeSql($query, $params = []) { // FIXME verify that we raise an exception on error - CRM_Core_DAO::executeSql($query, $params); + CRM_Core_DAO::executeQuery($query, $params); return TRUE; } /** - * Syntatic sugar for enqueuing a task which calls a function - * in this class. The task is weighted so that it is processed + * Syntactic sugar for enqueuing a task which calls a function in this class. + * + * The task is weighted so that it is processed * as part of the currently-pending revision. * * After passing the $funcName, you can also pass parameters that will go to @@ -126,17 +180,17 @@ public function addTask($title) { $args = func_get_args(); $title = array_shift($args); $task = new CRM_Queue_Task( - array(get_class($this), '_queueAdapter'), + [get_class($this), '_queueAdapter'], $args, $title ); - return $this->queue->createItem($task, array('weight' => -1)); + return $this->queue->createItem($task, ['weight' => -1]); } // ******** Revision-tracking helpers ******** /** - * Determine if there are any pending revisions + * Determine if there are any pending revisions. * * @return bool */ @@ -155,7 +209,9 @@ public function hasPendingRevisions() { } /** - * Add any pending revisions to the queue + * Add any pending revisions to the queue. + * + * @param CRM_Queue_Queue $queue */ public function enqueuePendingRevisions(CRM_Queue_Queue $queue) { $this->queue = $queue; @@ -163,23 +219,23 @@ public function enqueuePendingRevisions(CRM_Queue_Queue $queue) { $currentRevision = $this->getCurrentRevision(); foreach ($this->getRevisions() as $revision) { if ($revision > $currentRevision) { - $title = E::ts('Upgrade %1 to revision %2', array( + $title = E::ts('Upgrade %1 to revision %2', [ 1 => $this->extensionName, 2 => $revision, - )); + ]); // note: don't use addTask() because it sets weight=-1 $task = new CRM_Queue_Task( - array(get_class($this), '_queueAdapter'), - array('upgrade_' . $revision), + [get_class($this), '_queueAdapter'], + ['upgrade_' . $revision], $title ); $this->queue->createItem($task); $task = new CRM_Queue_Task( - array(get_class($this), '_queueAdapter'), - array('setCurrentRevision', $revision), + [get_class($this), '_queueAdapter'], + ['setCurrentRevision', $revision], $title ); $this->queue->createItem($task); @@ -188,13 +244,14 @@ public function enqueuePendingRevisions(CRM_Queue_Queue $queue) { } /** - * Get a list of revisions + * Get a list of revisions. * - * @return array(revisionNumbers) sorted numerically + * @return array + * revisionNumbers sorted numerically */ public function getRevisions() { - if (! is_array($this->revisions)) { - $this->revisions = array(); + if (!is_array($this->revisions)) { + $this->revisions = []; $clazz = new ReflectionClass(get_class($this)); $methods = $clazz->getMethods(); @@ -210,68 +267,130 @@ public function getRevisions() { } public function getCurrentRevision() { - // return CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName); + $revision = CRM_Core_BAO_Extension::getSchemaVersion($this->extensionName); + if (!$revision) { + $revision = $this->getCurrentRevisionDeprecated(); + } + return $revision; + } + + private function getCurrentRevisionDeprecated() { $key = $this->extensionName . ':version'; - return CRM_Core_BAO_Setting::getItem('Extension', $key); + if ($revision = \Civi::settings()->get($key)) { + $this->revisionStorageIsDeprecated = TRUE; + } + return $revision; } public function setCurrentRevision($revision) { - // We call this during hook_civicrm_install, but the underlying SQL - // UPDATE fails because the extension record hasn't been INSERTed yet. - // Instead, track revisions in our own namespace. - // CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision); - - $key = $this->extensionName . ':version'; - CRM_Core_BAO_Setting::setItem($revision, 'Extension', $key); + CRM_Core_BAO_Extension::setSchemaVersion($this->extensionName, $revision); + // clean up legacy schema version store (CRM-19252) + $this->deleteDeprecatedRevision(); return TRUE; } + private function deleteDeprecatedRevision() { + if ($this->revisionStorageIsDeprecated) { + $setting = new CRM_Core_BAO_Setting(); + $setting->name = $this->extensionName . ':version'; + $setting->delete(); + CRM_Core_Error::debug_log_message("Migrated extension schema revision ID for {$this->extensionName} from civicrm_setting (deprecated) to civicrm_extension.\n"); + } + } + // ******** Hook delegates ******** + /** + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_install + */ public function onInstall() { - foreach (glob($this->extensionDir . '/sql/*_install.sql') as $file) { - CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file); + $files = glob($this->extensionDir . '/sql/*_install.sql'); + if (is_array($files)) { + foreach ($files as $file) { + CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file); + } + } + $files = glob($this->extensionDir . '/sql/*_install.mysql.tpl'); + if (is_array($files)) { + foreach ($files as $file) { + $this->executeSqlTemplate($file); + } + } + $files = glob($this->extensionDir . '/xml/*_install.xml'); + if (is_array($files)) { + foreach ($files as $file) { + $this->executeCustomDataFileByAbsPath($file); + } } - if (is_callable(array($this, 'install'))) { + if (is_callable([$this, 'install'])) { $this->install(); } + } + + /** + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_postInstall + */ + public function onPostInstall() { $revisions = $this->getRevisions(); if (!empty($revisions)) { $this->setCurrentRevision(max($revisions)); } + if (is_callable([$this, 'postInstall'])) { + $this->postInstall(); + } } + /** + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_uninstall + */ public function onUninstall() { - if (is_callable(array($this, 'uninstall'))) { + $files = glob($this->extensionDir . '/sql/*_uninstall.mysql.tpl'); + if (is_array($files)) { + foreach ($files as $file) { + $this->executeSqlTemplate($file); + } + } + if (is_callable([$this, 'uninstall'])) { $this->uninstall(); } - foreach (glob($this->extensionDir . '/sql/*_uninstall.sql') as $file) { - CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file); + $files = glob($this->extensionDir . '/sql/*_uninstall.sql'); + if (is_array($files)) { + foreach ($files as $file) { + CRM_Utils_File::sourceSQLFile(CIVICRM_DSN, $file); + } } - $this->setCurrentRevision(NULL); } + /** + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_enable + */ public function onEnable() { // stub for possible future use - if (is_callable(array($this, 'enable'))) { + if (is_callable([$this, 'enable'])) { $this->enable(); } } + /** + * @see https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_disable + */ public function onDisable() { // stub for possible future use - if (is_callable(array($this, 'disable'))) { + if (is_callable([$this, 'disable'])) { $this->disable(); } } public function onUpgrade($op, CRM_Queue_Queue $queue = NULL) { - switch($op) { + switch ($op) { case 'check': - return array($this->hasPendingRevisions()); + return [$this->hasPendingRevisions()]; + case 'enqueue': return $this->enqueuePendingRevisions($queue); + default: } } + } From ae7131c5df80ebf27f23cdd9120dda0b19dce70e Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 24 Jun 2021 11:50:42 +0200 Subject: [PATCH 43/51] Rename OptionGroups.xml, suffix it with _install The current upgrader processes the xml files that has this suffix --- xml/{data/OptionGroups.xml => OptionGroups_install.xml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename xml/{data/OptionGroups.xml => OptionGroups_install.xml} (100%) diff --git a/xml/data/OptionGroups.xml b/xml/OptionGroups_install.xml similarity index 100% rename from xml/data/OptionGroups.xml rename to xml/OptionGroups_install.xml From 123bbecb8a5c9291d9cb3ecb4ad0add14bda5665 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 24 Jun 2021 11:52:42 +0200 Subject: [PATCH 44/51] Replace array() to [], further code updates Based on the solution that this fork applied: https://github.com/mjwconsult/civibooking --- CRM/Booking/Upgrader.php | 428 +++++++++++---------------------------- booking.php | 149 +++++++------- 2 files changed, 188 insertions(+), 389 deletions(-) diff --git a/CRM/Booking/Upgrader.php b/CRM/Booking/Upgrader.php index 1bb5a1b9..dc30408b 100755 --- a/CRM/Booking/Upgrader.php +++ b/CRM/Booking/Upgrader.php @@ -1,5 +1,5 @@ 3, + $params = [ 'sequential' => 1, 'label' => 'Booking', 'name' => CRM_Booking_Utils_Constants::ACTIVITY_TYPE, - ); - //chcck if it exist in case of re-installation - $optionValue = civicrm_api3('OptionValue', 'get',$params); - if($optionValue['count'] == 0){ + ]; + //check if it exist in case of re-installation + $optionValue = civicrm_api3('OptionValue', 'get', $params); + if ($optionValue['count'] == 0) { $params['weight'] = 1; $params['is_reserved'] = 1; $params['is_active'] = 1; - $result = civicrm_api('ActivityType', 'create', $params); + civicrm_api3('ActivityType', 'create', $params); } //create new activity type for sending email confirmation :CVB-95 - $params = array( - 'version' => 3, + $params = [ 'sequential' => 1, 'label' => 'Send booking confirmation', 'name' => CRM_Booking_Utils_Constants::ACTIVITY_TYPE_SEND_EMAIL, - ); - //chcck if it exist in case of re-installation - $optionValue = civicrm_api3('OptionValue', 'get',$params); - if($optionValue['count'] == 0){ + ]; + //check if it exist in case of re-installation + $optionValue = civicrm_api3('OptionValue', 'get', $params); + if ($optionValue['count'] == 0) { $params['weight'] = 1; $params['is_reserved'] = 1; $params['is_active'] = 1; - $result = civicrm_api('ActivityType', 'create', $params); + civicrm_api3('ActivityType', 'create', $params); } - $result = civicrm_api('OptionGroup', 'getsingle', array( - 'version' => 3, - 'sequential' => 1, - 'name' => 'msg_tpl_workflow_booking') + $result = civicrm_api3('OptionGroup', 'getsingle', [ + 'sequential' => 1, + 'name' => 'msg_tpl_workflow_booking', + ] ); - if(isset($result['id'])){ - $params = array( - 'version' => 3, + if (isset($result['id'])) { + $params = [ 'sequential' => 1, 'option_group_id' => $result['id'], - ); - $opvResult = civicrm_api('OptionValue', 'get', $params); - if(isset($opvResult['values']) && !empty($opvResult['values'])){ - foreach ($opvResult['values'] as $value) { + ]; + $opvResult = civicrm_api3('OptionValue', 'get', $params); + if (isset($opvResult['values']) && !empty($opvResult['values'])) { + foreach ($opvResult['values'] as $value) { switch ($value['name']) { case 'booking_offline_receipt': $html = file_get_contents($this->extensionDir . '/msg_tpl/booking_offline_receipt.html', FILE_USE_INCLUDE_PATH); @@ -63,139 +60,99 @@ public function install() { $title = E::ts("Booking - Confirmation and Receipt (off-line)"); break; } - if(isset($title)){ - $params = array( - 'version' => 3, - 'sequential' => 1, + if (isset($title)) { + $params = [ 'msg_title' => $title, - 'msg_subject' => E::ts("Booking - Confirmation Receipt").' - '.ts("Booking Status:").'{$booking_status}', + 'msg_subject' => E::ts("Booking - Confirmation Receipt") . ' - ' . E::ts("Booking Status:") . '{$booking_status}', 'msg_text' => $text, 'msg_html' => $html, 'is_active' => 1, 'workflow_id' => $value['id'], 'is_default' => 1, 'is_reserved' => 0, - ); - $result = civicrm_api('MessageTemplate', 'create', $params); + ]; + civicrm_api3('MessageTemplate', 'create', $params); $params['is_default'] = 0; $params['is_reserved'] = 1; //re-created another template - $result = civicrm_api('MessageTemplate', 'create', $params); + civicrm_api3('MessageTemplate', 'create', $params); } } } } $this->executeSqlFile('sql/civibooking_default.sql'); - $this->upgrade_1101(); + // Enable booking custom data + $this->upgrade_1102(); } /** * Example: Run an external SQL script when the module is uninstalled */ public function uninstall() { - $this->removeNavigationMenus(); - } - - /** - * Removes menu items added by this extension. - */ - private function removeNavigationMenus() { - $menuItems = $this->buildBookingSubMenusParameters(); - - foreach ($menuItems as $item) { - $this->removeNav($item['name']); - } - - CRM_Core_BAO_Navigation::resetNavigation(); } /** * Example: Run a simple query when a module is enabled * -*/ + */ public function enable() { - $this->executeSqlFile('sql/civibooking_enable.sql'); - $this->toggleIsActiveMenuItems(true); + $this->executeSqlFile('sql/civibooking_enable.sql'); } /** * Example: Run a simple query when a module is disabled * - */ + */ public function disable() { //TODO:: Disable the message template $this->executeSqlFile('sql/civibooking_disable.sql'); - $this->toggleIsActiveMenuItems(false); - } - - /** - * Sets is_active parameter for menu items created by this extension. - * - * @param $isActive - */ - private function toggleIsActiveMenuItems($isActive) { - $sortedItems = array(); - $menuItems = $this->buildBookingSubMenusParameters(); - $isActive = (int) $isActive; - foreach ($menuItems as $item) { - $parent = CRM_Utils_Array::value('parent_name', $item, '_noparent_'); - $sortedItems[$parent][] = $item['name']; - } - - foreach ($sortedItems as $parent => $items) { - $params = array( - 'sequential' => 1, - 'name' => array('IN' => $items), - 'parent_id' => $parent, - 'api.Navigation.create' => array('id' => '$value.id', 'is_active' => $isActive), - ); - - if ($parent === '_noparent_') { - $params['parent_id'] = array('IS NULL' => 1); - } - - $versionNum = $this->versionSwitcher(); - if ($versionNum >= 470){ - civicrm_api3('Navigation', 'get', $params); - } - else { - $items = implode("','", $items); - $query = "UPDATE civicrm_navigation SET is_active = {$isActive} WHERE name IN ('{$items}')"; - CRM_Core_DAO::executeQuery($query); - } - } - - CRM_Core_BAO_Navigation::resetNavigation(); } // By convention, functions that look like "function upgrade_NNNN()" are // upgrade tasks. They are executed in order (like Drupal's hook_update_N). public function upgrade_1100() { - $this->ctx->log->info('Applying update 1100'); - $this->executeSqlFile('sql/update_1100.sql'); + Civi::log()->info('Applying update 1100'); + if (!CRM_Core_BAO_SchemaHandler::checkIfFieldExists('civicrm_booking_config', 'unlimited_resource_time_config')) { + $this->executeSqlFile('sql/update_1100.sql'); + } return TRUE; } /** + * Deprecated. The navigation menu building process is now handled with a hook. * This upgrade builds navigation menus for the extension. */ public function upgrade_1101() { - $administerMenuId = $this->getAdministerMenuID(); + return TRUE; + } - // skip adding menu if there is no administer menu - if ($administerMenuId) { - $bookingSubMenus = $this->buildBookingSubMenusParameters(); + public function upgrade_1102() { + Civi::log()->info('Enabling Booking custom data'); + self::enableBookingCustomData(); + return TRUE; + } - foreach ($bookingSubMenus as $menuItem) { - $this->addNav($menuItem); - } + public static function enableBookingCustomData() { + // Enable Booking custom data + $optionValue = [ + 'name' => 'civicrm_booking', + 'label' => 'Booking', + 'value' => 'Booking', + ]; + $optionValues = civicrm_api3('OptionValue', 'get', [ + 'option_group_id' => 'cg_extend_objects', + 'name' => $optionValue['name'], + ]); + if (!$optionValues['count']) { + civicrm_api3('OptionValue', 'create', [ + 'option_group_id' => 'cg_extend_objects', + 'name' => $optionValue['name'], + 'label' => $optionValue['label'], + 'value' => $optionValue['value'], + ]); } - - CRM_Core_BAO_Navigation::resetNavigation(); - - return TRUE; } /** @@ -203,15 +160,15 @@ public function upgrade_1101() { * * @return array */ - private function buildBookingSubMenusParameters() { - $bookingStatusGid = $this->getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_BOOKING_STATUS); - $resourceTypeGid = $this->getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_RESOURCE_TYPE); - $resourceLocationGId = $this->getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_RESOURCE_LOCATION); - $sizeUnitGid = $this->getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_SIZE_UNIT); - $cancellationChargesGid = $this->getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_CANCELLATION_CHARGES); - - $menus = array( - array( + public static function getMenuItems() { + $bookingStatusGid = self::getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_BOOKING_STATUS); + $resourceTypeGid = self::getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_RESOURCE_TYPE); + $resourceLocationGId = self::getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_RESOURCE_LOCATION); + $sizeUnitGid = self::getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_SIZE_UNIT); + $cancellationChargesGid = self::getOptionGroupID(CRM_Booking_Utils_Constants::OPTION_CANCELLATION_CHARGES); + + $menus = [ + [ 'label' => ts('CiviBooking'), 'name' => 'admin_booking', 'url' => '#', @@ -219,105 +176,105 @@ private function buildBookingSubMenusParameters() { 'operator' => null, 'separator' => 1, 'parent_name' => 'Administer', - ), - array( + ], + [ 'label' => ts('Resource Configuration Set'), 'name' => 'resource_config_set', 'url' => CRM_Utils_System::url('civicrm/admin/resource/config_set', "reset=1", TRUE), 'permission' => null, 'operator' => null, 'separator' => 0, - 'parent_name' => 'admin_booking', - ), - array( + 'parent_name' => 'Administer/admin_booking', + ], + [ 'label' => ts('Manage Resources'), 'name' => 'manage_resources', 'url' => CRM_Utils_system::url('civicrm/admin/resource', "reset=1", TRUE), 'permission' => null, 'operator' => null, 'separator' => 0, - 'parent_name' => 'admin_booking', - ), - array( + 'parent_name' => 'Administer/admin_booking', + ], + [ 'label' => ts('Additional Charges Item'), 'name' => 'adhoc_charges_item', 'url' => CRM_Utils_system::url('civicrm/admin/adhoc_charges_item', "reset=1", TRUE), 'permission' => null, 'operator' => null, 'separator' => 0, - 'parent_name' => 'admin_booking', - ), - array( + 'parent_name' => 'Administer/admin_booking', + ], + [ 'label' => ts('Booking Status'), 'name' => 'booking_status', - 'url' => CRM_Utils_system::url('civicrm/admin/options', array('gid' => $bookingStatusGid, 'reset' => 1), TRUE), + 'url' => CRM_Utils_system::url('civicrm/admin/options', ['gid' => $bookingStatusGid, 'reset' => 1], TRUE), 'permission' => null, 'operator' => null, 'separator' => 0, - 'parent_name' => 'admin_booking', - ), - array( + 'parent_name' => 'Administer/admin_booking', + ], + [ 'label' => ts('Resource Type'), 'name' => 'resource_type', - 'url' => CRM_Utils_system::url('civicrm/admin/options', array('gid' => $resourceTypeGid, 'reset' => 1), TRUE), + 'url' => CRM_Utils_system::url('civicrm/admin/options', ['gid' => $resourceTypeGid, 'reset' => 1], TRUE), 'permission' => null, 'operator' => null, 'separator' => 0, - 'parent_name' => 'admin_booking', - ), - array( + 'parent_name' => 'Administer/admin_booking', + ], + [ 'label' => ts('Size Unit'), 'name' => 'size_unit', - 'url' => CRM_Utils_system::url('civicrm/admin/options', array('gid' => $sizeUnitGid, 'reset' => 1), TRUE), + 'url' => CRM_Utils_system::url('civicrm/admin/options', ['gid' => $sizeUnitGid, 'reset' => 1], TRUE), 'permission' => null, 'operator' => null, 'separator' => 0, - 'parent_name' => 'admin_booking', - ), - array( + 'parent_name' => 'Administer/admin_booking', + ], + [ 'label' => ts('Cancellation Charges'), 'name' => 'cancellation_charges', - 'url' => CRM_Utils_system::url('civicrm/admin/options', array('gid' => $cancellationChargesGid,'reset' => 1), TRUE), + 'url' => CRM_Utils_system::url('civicrm/admin/options', ['gid' => $cancellationChargesGid,'reset' => 1], TRUE), 'permission' => null, 'operator' => null, 'separator' => 0, - 'parent_name' => 'admin_booking', - ), - array( + 'parent_name' => 'Administer/admin_booking', + ], + [ 'label' => ts('Booking Component Settings'), 'name' => 'booking_component_settings', 'url' => CRM_Utils_system::url('civicrm/admin/setting/preferences/booking', "reset=1", TRUE), 'permission' => null, 'operator' => null, 'separator' => 0, - 'parent_name' => 'admin_booking', - ), - array( + 'parent_name' => 'Administer/admin_booking', + ], + [ 'label' => ts('Resource Location'), 'name' => 'resource_location', - 'url' => CRM_Utils_system::url('civicrm/admin/options', array('gid' => $resourceLocationGId,'reset' => 1), TRUE), + 'url' => CRM_Utils_system::url('civicrm/admin/options', ['gid' => $resourceLocationGId,'reset' => 1], TRUE), 'permission' => null, 'operator' => null, 'separator' => 0, - 'parent_name' => 'admin_booking', - ), - array( + 'parent_name' => 'Administer/admin_booking', + ], + [ 'label' => ts('Find Bookings'), 'name' => 'find_booking', 'url' => CRM_Utils_system::url('civicrm/booking/search', "reset=1", TRUE), 'permission' => 'administer CiviBooking,create and update bookings,view all bookings', 'operator' => null, 'separator' => 0, - ), - array( + ], + [ 'label' => ts('Booking'), 'name' => 'booking', 'url' => null, 'permission' => 'administer CiviBooking,create and update bookings,view all bookings', 'operator' => null, 'separator' => null, - ), - array( + ], + [ 'label' => ts('New Booking'), 'name' => 'new_booking', 'url' => CRM_Utils_system::url('civicrm/booking/add', "reset=1", TRUE), @@ -325,8 +282,8 @@ private function buildBookingSubMenusParameters() { 'operator' => null, 'separator' => 0, 'parent_name' => 'booking', - ), - array( + ], + [ 'label' => ts('Day View'), 'name' => 'day_view', 'url' => CRM_Utils_system::url('civicrm/booking/day-view', "reset=1", TRUE), @@ -334,30 +291,12 @@ private function buildBookingSubMenusParameters() { 'operator' => null, 'separator' => 0, 'parent_name' => 'booking', - ), - ); + ], + ]; return $menus; } - /** - * Obtains ID of Administer menu. - * - * @return null|string - */ - private function getAdministerMenuID() { - $domain_id = CRM_Core_Config::domainID(); - - $administerMenuId = CRM_Core_DAO::singleValueQuery(" - SELECT id - FROM civicrm_navigation - WHERE name = 'Administer' - AND domain_id = $domain_id - "); - - return $administerMenuId; - } - /** * Obtains an option group's ID given its name. * @@ -365,139 +304,10 @@ private function getAdministerMenuID() { * * @return int */ - private function getOptionGroupID($name) { - $result = civicrm_api3('OptionGroup', 'getsingle', array('name' => $name)); - - if($result['id']){ - return $result['id']; - } - + private static function getOptionGroupID($name) { + try { + return civicrm_api3('OptionGroup', 'getsingle', ['name' => $name])['id']; + } catch (Exception $e) {} return 0; } - - /** - * Adds given menu item to CiviCRM navigation. - * - * @param array $menuItem - */ - private function addNav($menuItem) { - if (isset($menuItem['parent_name'])) { - $menuItem['parent_id'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Navigation', $menuItem['parent_name'], 'id', 'name'); - unset($menuItem['parent_name']); - } - - $menuItem['is_active'] = 1; - - CRM_Core_BAO_Navigation::add($menuItem); - } - - /** - * Removes navigation item identified by $name from CiviCRM navigation. - * - * @param string $name - */ - private function removeNav($name) { - $versionNum = $this->versionSwitcher(); - if ($versionNum >= 470){ - civicrm_api3('Navigation', 'get', array( - 'sequential' => 1, - 'name' => $name, - 'api.Navigation.delete' => array('id' => '$value.id'), - )); - } - else { - $query = "DELETE FROM civicrm_navigation WHERE name = '{$name}'"; - CRM_Core_DAO::executeQuery($query); - } - } - - /** - * Get civicrm version. - * - * @return $versionNum - */ - private function versionSwitcher() { - $version = CRM_Utils_System::version(); - preg_match('/[0-9]\.[0-9]\.[0-9]/', $version, $matches); - $versionNum = str_replace(".","",array_pop($matches)); - - return $versionNum; - } - - /** - * Example: Run a couple simple queries - * - * @return TRUE on success - * @throws Exception - * - public function upgrade_4200() { - $this->ctx->log->info('Applying update 4200'); - CRM_Core_DAO::executeQuery('UPDATE foo SET bar = "whiz"'); - CRM_Core_DAO::executeQuery('DELETE FROM bang WHERE willy = wonka(2)'); - return TRUE; - } // */ - - - /** - * Example: Run an external SQL script - * - * @return TRUE on success - * @throws Exception - public function upgrade_4201() { - $this->ctx->log->info('Applying update 4201'); - // this path is relative to the extension base dir - $this->executeSqlFile('sql/upgrade_4201.sql'); - return TRUE; - } // */ - - - /** - * Example: Run a slow upgrade process by breaking it up into smaller chunk - * - * @return TRUE on success - * @throws Exception - public function upgrade_4202() { - $this->ctx->log->info('Planning update 4202'); // PEAR Log interface - - $this->addTask(E::ts('Process first step'), 'processPart1', $arg1, $arg2); - $this->addTask(E::ts('Process second step'), 'processPart2', $arg3, $arg4); - $this->addTask(E::ts('Process second step'), 'processPart3', $arg5); - return TRUE; - } - public function processPart1($arg1, $arg2) { sleep(10); return TRUE; } - public function processPart2($arg3, $arg4) { sleep(10); return TRUE; } - public function processPart3($arg5) { sleep(10); return TRUE; } - // */ - - - /** - * Example: Run an upgrade with a query that touches many (potentially - * millions) of records by breaking it up into smaller chunks. - * - * @return TRUE on success - * @throws Exception - public function upgrade_4203() { - $this->ctx->log->info('Planning update 4203'); // PEAR Log interface - - $minId = CRM_Core_DAO::singleValueQuery('SELECT coalesce(min(id),0) FROM civicrm_contribution'); - $maxId = CRM_Core_DAO::singleValueQuery('SELECT coalesce(max(id),0) FROM civicrm_contribution'); - for ($startId = $minId; $startId <= $maxId; $startId += self::BATCH_SIZE) { - $endId = $startId + self::BATCH_SIZE - 1; - $title = E::ts('Upgrade Batch (%1 => %2)', array( - 1 => $startId, - 2 => $endId, - )); - $sql = ' - UPDATE civicrm_contribution SET foobar = whiz(wonky()+wanker) - WHERE id BETWEEN %1 and %2 - '; - $params = array( - 1 => array($startId, 'Integer'), - 2 => array($endId, 'Integer'), - ); - $this->addTask($title, 'executeSql', $sql, $params); - } - return TRUE; - } // */ - } diff --git a/booking.php b/booking.php index c6d62fa4..adc7a04e 100755 --- a/booking.php +++ b/booking.php @@ -1,10 +1,8 @@ run( $op ); - - return _booking_civix_civicrm_install(); + _booking_civix_civicrm_install(); } /** * Implementation of hook_civicrm_uninstall */ function booking_civicrm_uninstall() { - return _booking_civix_civicrm_uninstall(); + _booking_civix_civicrm_uninstall(); } /** * Implementation of hook_civicrm_enable */ function booking_civicrm_enable() { - // rebuild the menu so our path is picked up - require_once 'CRM/Core/Invoke.php'; - CRM_Core_Invoke::rebuildMenuAndCaches( ); - return _booking_civix_civicrm_enable(); + _booking_civix_civicrm_enable(); } /** * Implementation of hook_civicrm_disable */ function booking_civicrm_disable() { - return _booking_civix_civicrm_disable(); + _booking_civix_civicrm_disable(); } /** @@ -108,7 +92,7 @@ function booking_civicrm_upgrade($op, CRM_Queue_Queue $queue = NULL) { * is installed, disabled, uninstalled. */ function booking_civicrm_managed(&$entities) { - return _booking_civix_civicrm_managed($entities); + _booking_civix_civicrm_managed($entities); } /** @@ -118,15 +102,14 @@ function booking_civicrm_queryObjects(&$queryObjects, $type) { if ($type == 'Contact') { $queryObjects[] = new CRM_Booking_BAO_Query(); } - elseif ($type == 'Report') {} } /** * Implementation of hook_civicrm_postProcess */ -function booking_civicrm_post( $op, $objectName, $objectId, &$objectRef ) { - if($objectName == 'Contribution'){ - if($op == 'delete'){ +function booking_civicrm_post($op, $objectName, $objectId, &$objectRef) { + if ($objectName == 'Contribution') { + if ($op == 'delete') { CRM_Core_DAO::executeQuery("DELETE FROM civicrm_booking_payment WHERE contribution_id = $objectId"); } } @@ -136,64 +119,70 @@ function booking_civicrm_post( $op, $objectName, $objectId, &$objectRef ) { * Implementation of hook_civicrm_entityTypes */ function booking_civicrm_entityTypes(&$entityTypes) { - $entityTypes[] = array( + $entityTypes[] = [ 'name' => 'AdhocCharges', 'class' => 'CRM_Booking_DAO_AdhocCharges', 'table' => 'civicrm_booking_adhoc_charges', - ); - $entityTypes[] = array( + ]; + $entityTypes[] = [ 'name' => 'AdhocChargesItem', 'class' => 'CRM_Booking_DAO_AdhocChargesItem', 'table' => 'civicrm_booking_adhoc_charges_item', - ); - $entityTypes[] = array( + ]; + $entityTypes[] = [ 'name' => 'Booking', 'class' => 'CRM_Booking_DAO_Booking', 'table' => 'civicrm_booking', - ); - $entityTypes[] = array( + ]; + $entityTypes[] = [ 'name' => 'BookingPayment', 'class' => 'CRM_Booking_DAO_Payment', 'table' => 'civicrm_booking_payment', - ); - $entityTypes[] = array( + ]; + $entityTypes[] = [ 'name' => 'Resource', 'class' => 'CRM_Booking_DAO_Resource', 'table' => 'civicrm_booking_resource', - ); - $entityTypes[] = array( + ]; + $entityTypes[] = [ 'name' => 'ResourceConfigOption', 'class' => 'CRM_Booking_DAO_ResourceConfigOption', 'table' => 'civicrm_booking_resource_config_option', - ); - $entityTypes[] = array( + ]; + $entityTypes[] = [ 'name' => 'ResourceConfigSet', 'class' => 'CRM_Booking_DAO_ResourceConfigSet', 'table' => 'civicrm_booking_resource_config_set', - ); - $entityTypes[] = array( + ]; + $entityTypes[] = [ 'name' => 'Slot', 'class' => 'CRM_Booking_DAO_Slot', 'table' => 'civicrm_booking_slot', - ); - $entityTypes[] = array( + ]; + $entityTypes[] = [ 'name' => 'SubSlot', 'class' => 'CRM_Booking_DAO_SubSlot', 'table' => 'civicrm_booking_sub_slot', - ); - $entityTypes[] = array( + ]; + $entityTypes[] = [ 'name' => 'Cancellation', 'class' => 'CRM_Booking_DAO_Cancellation', 'table' => 'civicrm_booking_cancellation' - ); + ]; } /** - * Implementation of hook_civicrm_merge + * Implements hook_civicrm_alterSettingsFolders(). */ -function booking_civicrm_merge ( $type, &$data, $mainId = NULL, $otherId = NULL, $tables = NULL ){ -if (!empty($mainId) && !empty($otherId) && $type == 'sqls'){ +function booking_civicrm_alterSettingsFolders(&$metaDataFolders = NULL) { + _booking_civix_civicrm_alterSettingsFolders($metaDataFolders); +} +/** + * Implementation of hook_civicrm_merge + */ +function booking_civicrm_merge ($type, &$data, $mainId = NULL, $otherId = NULL, $tables = NULL) { + if (!empty($mainId) && !empty($otherId) && $type == 'sqls') { $query1 = " UPDATE civicrm_booking SET primary_contact_id=$mainId @@ -205,24 +194,13 @@ function booking_civicrm_merge ( $type, &$data, $mainId = NULL, $otherId = NULL, WHERE secondary_contact_id=$otherId; "; - require_once('CRM/Core/DAO.php'); - $dao = CRM_Core_DAO::executeQuery( $query1 ); - $dao = CRM_Core_DAO::executeQuery( $query2 ); - - } -} + $dao = CRM_Core_DAO::executeQuery($query1); + $dao = CRM_Core_DAO::executeQuery($query2); -function civibooking_getMenuKeyMax($menuArray) { - $max = array(max(array_keys($menuArray))); - foreach($menuArray as $v) { - if (!empty($v['child'])) { - $max[] = civibooking_getMenuKeyMax($v['child']); - } } - return max($max); } -function booking_civicrm_permission(&$permissions){ +function booking_civicrm_permission(&$permissions) { $prefix = E::ts('CiviBooking') . ': '; $permissions['administer CiviBooking'] = $prefix . E::ts('administer CiviBooking'); $permissions['create and update bookings'] = $prefix . E::ts('create and update bookings'); @@ -234,40 +212,40 @@ function booking_civicrm_permission(&$permissions){ * @see function _civicrm_api3_permissions for mentioned uppercase issue */ function booking_civicrm_alterAPIPermissions($entity, $action, &$params, &$permissions) { - $commonBookingAPIPermissions = array( - 'create' => array( + $commonBookingAPIPermissions = [ + 'create' => [ 'administer CiviBooking', - ), - 'delete' => array( + ], + 'delete' => [ 'administer CiviBooking', - ), - 'get' => array( - array( + ], + 'get' => [ + [ 'administer CiviBooking', 'create and update bookings', 'view all bookings', - ) - ), - 'update' => array( + ], + ], + 'update' => [ 'administer CiviBooking', - ), - ); + ], + ]; - $bookingEntities = array( + $bookingEntities = [ 'BookingPayment', 'Booking', 'Cancellation', 'Slot', 'SubSlot' - ); + ]; - $configEntities = array( + $configEntities = [ 'AdhocChargesItem', 'AdhocCharges', 'ResourceConfigOption', 'ResourceConfigSet', 'Resource', - ); + ]; // set common permissions foreach (array_merge($bookingEntities, $configEntities) as $entityName) { @@ -277,7 +255,7 @@ function booking_civicrm_alterAPIPermissions($entity, $action, &$params, &$permi //add custom permissions for create/update role foreach ($bookingEntities as $entityName) { - $permissionArray = array(array('administer CiviBooking', 'create and update bookings')); + $permissionArray = [['administer CiviBooking', 'create and update bookings']]; // permissions implementation needs lowercase entities $entityName = _civicrm_api_get_entity_name_from_camel($entityName); $permissions[$entityName]['create'] = $permissionArray; @@ -297,3 +275,14 @@ function booking_civicrm_apiWrappers(&$wrappers, $apiRequest) { $wrappers[] = new CRM_Booking_APIWrapper(); } } + +/** + * Implements hook_civicrm_navigationMenu(). + */ +function booking_civicrm_navigationMenu(&$menu) { + $menuItems = CRM_Booking_Upgrader::getMenuItems(); + foreach ($menuItems as $item) { + _booking_civix_insert_navigation_menu($menu, $item['parent_name'], $item); + } + _booking_civix_navigationMenu($menu); +} From 1045461575f8ad0e5a87999a8468c769cdac38f6 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 24 Jun 2021 12:29:21 +0200 Subject: [PATCH 45/51] Move the schhema xml files under a CRM directory --- xml/schema/{ => CRM}/Booking/AdhocCharges.xml | 0 xml/schema/{ => CRM}/Booking/AdhocChargesItem.xml | 0 xml/schema/{ => CRM}/Booking/Booking.xml | 0 xml/schema/{ => CRM}/Booking/BookingConfig.xml | 0 xml/schema/{ => CRM}/Booking/Cancellation.xml | 0 xml/schema/{ => CRM}/Booking/Payment.xml | 0 xml/schema/{ => CRM}/Booking/Resource.xml | 0 xml/schema/{ => CRM}/Booking/ResourceConfigOption.xml | 0 xml/schema/{ => CRM}/Booking/ResourceConfigSet.xml | 0 xml/schema/{ => CRM}/Booking/ResourceCriteria.xml | 0 xml/schema/{ => CRM}/Booking/Slot.xml | 0 xml/schema/{ => CRM}/Booking/SubSlot.xml | 0 xml/schema/{ => CRM}/Booking/files.xml | 0 xml/schema/Schema.xml | 2 +- 14 files changed, 1 insertion(+), 1 deletion(-) rename xml/schema/{ => CRM}/Booking/AdhocCharges.xml (100%) rename xml/schema/{ => CRM}/Booking/AdhocChargesItem.xml (100%) rename xml/schema/{ => CRM}/Booking/Booking.xml (100%) rename xml/schema/{ => CRM}/Booking/BookingConfig.xml (100%) rename xml/schema/{ => CRM}/Booking/Cancellation.xml (100%) rename xml/schema/{ => CRM}/Booking/Payment.xml (100%) rename xml/schema/{ => CRM}/Booking/Resource.xml (100%) rename xml/schema/{ => CRM}/Booking/ResourceConfigOption.xml (100%) rename xml/schema/{ => CRM}/Booking/ResourceConfigSet.xml (100%) rename xml/schema/{ => CRM}/Booking/ResourceCriteria.xml (100%) rename xml/schema/{ => CRM}/Booking/Slot.xml (100%) rename xml/schema/{ => CRM}/Booking/SubSlot.xml (100%) rename xml/schema/{ => CRM}/Booking/files.xml (100%) diff --git a/xml/schema/Booking/AdhocCharges.xml b/xml/schema/CRM/Booking/AdhocCharges.xml similarity index 100% rename from xml/schema/Booking/AdhocCharges.xml rename to xml/schema/CRM/Booking/AdhocCharges.xml diff --git a/xml/schema/Booking/AdhocChargesItem.xml b/xml/schema/CRM/Booking/AdhocChargesItem.xml similarity index 100% rename from xml/schema/Booking/AdhocChargesItem.xml rename to xml/schema/CRM/Booking/AdhocChargesItem.xml diff --git a/xml/schema/Booking/Booking.xml b/xml/schema/CRM/Booking/Booking.xml similarity index 100% rename from xml/schema/Booking/Booking.xml rename to xml/schema/CRM/Booking/Booking.xml diff --git a/xml/schema/Booking/BookingConfig.xml b/xml/schema/CRM/Booking/BookingConfig.xml similarity index 100% rename from xml/schema/Booking/BookingConfig.xml rename to xml/schema/CRM/Booking/BookingConfig.xml diff --git a/xml/schema/Booking/Cancellation.xml b/xml/schema/CRM/Booking/Cancellation.xml similarity index 100% rename from xml/schema/Booking/Cancellation.xml rename to xml/schema/CRM/Booking/Cancellation.xml diff --git a/xml/schema/Booking/Payment.xml b/xml/schema/CRM/Booking/Payment.xml similarity index 100% rename from xml/schema/Booking/Payment.xml rename to xml/schema/CRM/Booking/Payment.xml diff --git a/xml/schema/Booking/Resource.xml b/xml/schema/CRM/Booking/Resource.xml similarity index 100% rename from xml/schema/Booking/Resource.xml rename to xml/schema/CRM/Booking/Resource.xml diff --git a/xml/schema/Booking/ResourceConfigOption.xml b/xml/schema/CRM/Booking/ResourceConfigOption.xml similarity index 100% rename from xml/schema/Booking/ResourceConfigOption.xml rename to xml/schema/CRM/Booking/ResourceConfigOption.xml diff --git a/xml/schema/Booking/ResourceConfigSet.xml b/xml/schema/CRM/Booking/ResourceConfigSet.xml similarity index 100% rename from xml/schema/Booking/ResourceConfigSet.xml rename to xml/schema/CRM/Booking/ResourceConfigSet.xml diff --git a/xml/schema/Booking/ResourceCriteria.xml b/xml/schema/CRM/Booking/ResourceCriteria.xml similarity index 100% rename from xml/schema/Booking/ResourceCriteria.xml rename to xml/schema/CRM/Booking/ResourceCriteria.xml diff --git a/xml/schema/Booking/Slot.xml b/xml/schema/CRM/Booking/Slot.xml similarity index 100% rename from xml/schema/Booking/Slot.xml rename to xml/schema/CRM/Booking/Slot.xml diff --git a/xml/schema/Booking/SubSlot.xml b/xml/schema/CRM/Booking/SubSlot.xml similarity index 100% rename from xml/schema/Booking/SubSlot.xml rename to xml/schema/CRM/Booking/SubSlot.xml diff --git a/xml/schema/Booking/files.xml b/xml/schema/CRM/Booking/files.xml similarity index 100% rename from xml/schema/Booking/files.xml rename to xml/schema/CRM/Booking/files.xml diff --git a/xml/schema/Schema.xml b/xml/schema/Schema.xml index 79ab8c30..5d1c59b2 100644 --- a/xml/schema/Schema.xml +++ b/xml/schema/Schema.xml @@ -32,6 +32,6 @@ - + From feda5a268b73810fd49d1476537ddf0a49bb78fa Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 24 Jun 2021 13:48:31 +0200 Subject: [PATCH 46/51] Regenerate DAO with civix command civix generate:entity-boilerplate --- CRM/Booking/DAO/AdhocCharges.php | 391 +++++++------ CRM/Booking/DAO/AdhocChargesItem.php | 392 +++++++------ CRM/Booking/DAO/Booking.php | 678 ++++++++++++----------- CRM/Booking/DAO/BookingConfig.php | 467 ++++++++-------- CRM/Booking/DAO/Cancellation.php | 362 ++++++------ CRM/Booking/DAO/Payment.php | 322 +++++------ CRM/Booking/DAO/Resource.php | 469 ++++++++-------- CRM/Booking/DAO/ResourceConfigOption.php | 450 ++++++++------- CRM/Booking/DAO/ResourceConfigSet.php | 354 ++++++------ CRM/Booking/DAO/ResourceCriteria.php | 311 +++++------ CRM/Booking/DAO/Slot.php | 457 +++++++-------- CRM/Booking/DAO/SubSlot.php | 436 ++++++++------- sql/auto_install.sql | 383 +++++++++++++ sql/auto_uninstall.sql | 33 ++ 14 files changed, 2889 insertions(+), 2616 deletions(-) create mode 100644 sql/auto_install.sql create mode 100644 sql/auto_uninstall.sql diff --git a/CRM/Booking/DAO/AdhocCharges.php b/CRM/Booking/DAO/AdhocCharges.php index 3652bdb9..5acf9756 100644 --- a/CRM/Booking/DAO/AdhocCharges.php +++ b/CRM/Booking/DAO/AdhocCharges.php @@ -1,299 +1,278 @@ __table = 'civicrm_booking_adhoc_charges'; parent::__construct(); } + /** - * return foreign keys and entity references + * Returns localized title of this entity. * - * @static - * @access public - * @return array of CRM_Core_EntityReference + * @param bool $plural + * Whether to return the plural version of the title. */ - static function getReferenceColumns() - { - if (!self::$_links) { - self::$_links = array( - new CRM_Core_EntityReference(self::getTableName() , 'booking_id', 'civicrm_booking', 'id') , - new CRM_Core_EntityReference(self::getTableName() , 'item_id', 'civicrm_booking_adhoc_charges_item', 'id') , - ); + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Adhoc Chargeses') : E::ts('Adhoc Charges'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'booking_id', 'civicrm_booking', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'item_id', 'civicrm_booking_adhoc_charges_item', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } - return self::$_links; + return Civi::$statics[__CLASS__]['links']; } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, - ) , - 'booking_id' => array( + 'required' => TRUE, + 'where' => 'civicrm_booking_adhoc_charges.id', + 'table_name' => 'civicrm_booking_adhoc_charges', + 'entity' => 'AdhocCharges', + 'bao' => 'CRM_Booking_DAO_AdhocCharges', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'booking_id' => [ 'name' => 'booking_id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, + 'description' => E::ts('FK to Booking ID'), + 'required' => TRUE, + 'where' => 'civicrm_booking_adhoc_charges.booking_id', + 'table_name' => 'civicrm_booking_adhoc_charges', + 'entity' => 'AdhocCharges', + 'bao' => 'CRM_Booking_DAO_AdhocCharges', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_Booking', - ) , - 'item_id' => array( + 'add' => NULL, + ], + 'item_id' => [ 'name' => 'item_id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, + 'description' => E::ts('FK to Item ID'), + 'required' => TRUE, + 'where' => 'civicrm_booking_adhoc_charges.item_id', + 'table_name' => 'civicrm_booking_adhoc_charges', + 'entity' => 'AdhocCharges', + 'bao' => 'CRM_Booking_DAO_AdhocCharges', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_AdhocChargesItem', - ) , - 'quantity' => array( + 'add' => NULL, + ], + 'quantity' => [ 'name' => 'quantity', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Quantity') , - 'required' => true, - ) , - 'is_cancelled' => array( + 'title' => E::ts('Quantity'), + 'required' => TRUE, + 'where' => 'civicrm_booking_adhoc_charges.quantity', + 'table_name' => 'civicrm_booking_adhoc_charges', + 'entity' => 'AdhocCharges', + 'bao' => 'CRM_Booking_DAO_AdhocCharges', + 'localizable' => 0, + 'add' => NULL, + ], + 'is_cancelled' => [ 'name' => 'is_cancelled', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is cancelled') , - 'import' => true, + 'title' => E::ts('Slot is cancelled'), + 'import' => TRUE, 'where' => 'civicrm_booking_adhoc_charges.is_cancelled', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - 'is_deleted' => array( + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_adhoc_charges', + 'entity' => 'AdhocCharges', + 'bao' => 'CRM_Booking_DAO_AdhocCharges', + 'localizable' => 0, + 'add' => '4.4', + ], + 'is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is in the Trash') , - 'import' => true, + 'title' => E::ts('Slot is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_booking_adhoc_charges.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - ); + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_adhoc_charges', + 'entity' => 'AdhocCharges', + 'bao' => 'CRM_Booking_DAO_AdhocCharges', + 'localizable' => 0, + 'add' => '4.4', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'booking_id' => 'booking_id', - 'item_id' => 'item_id', - 'quantity' => 'quantity', - 'is_cancelled' => 'is_cancelled', - 'is_deleted' => 'is_deleted', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_adhoc_charges', $prefix, []); + return $r; + } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be exported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_adhoc_charges'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_adhoc_charges', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of indices + * + * @param bool $localize * - * @access public - * return array - * @static + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_adhoc_charges'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function indices($localize = TRUE) { + $indices = [ + 'index_is_cancelled' => [ + 'name' => 'index_is_cancelled', + 'field' => [ + 0 => 'is_cancelled', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_adhoc_charges::0::is_cancelled', + ], + 'index_is_deleted' => [ + 'name' => 'index_is_deleted', + 'field' => [ + 0 => 'is_deleted', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_adhoc_charges::0::is_deleted', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/AdhocChargesItem.php b/CRM/Booking/DAO/AdhocChargesItem.php index 7f7873c3..4e751939 100644 --- a/CRM/Booking/DAO/AdhocChargesItem.php +++ b/CRM/Booking/DAO/AdhocChargesItem.php @@ -1,298 +1,282 @@ __table = 'civicrm_booking_adhoc_charges_item'; parent::__construct(); } + /** - * returns all the column names of this table + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Adhoc Charges Items') : E::ts('Adhoc Charges Item'); + } + + /** + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('ID') , - 'required' => true, - ) , - 'name' => array( + 'title' => E::ts('ID'), + 'required' => TRUE, + 'where' => 'civicrm_booking_adhoc_charges_item.id', + 'table_name' => 'civicrm_booking_adhoc_charges_item', + 'entity' => 'AdhocChargesItem', + 'bao' => 'CRM_Booking_DAO_AdhocChargesItem', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'name' => [ 'name' => 'name', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Name') , - 'required' => true, + 'title' => E::ts('Name'), + 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'label' => array( + 'where' => 'civicrm_booking_adhoc_charges_item.name', + 'table_name' => 'civicrm_booking_adhoc_charges_item', + 'entity' => 'AdhocChargesItem', + 'bao' => 'CRM_Booking_DAO_AdhocChargesItem', + 'localizable' => 0, + 'add' => NULL, + ], + 'label' => [ 'name' => 'label', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Label') , - 'required' => true, + 'title' => E::ts('Label'), + 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'price' => array( + 'where' => 'civicrm_booking_adhoc_charges_item.label', + 'table_name' => 'civicrm_booking_adhoc_charges_item', + 'entity' => 'AdhocChargesItem', + 'bao' => 'CRM_Booking_DAO_AdhocChargesItem', + 'localizable' => 0, + 'add' => NULL, + ], + 'price' => [ 'name' => 'price', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => E::ts('Price') , - 'required' => true, - ) , - 'weight' => array( + 'title' => E::ts('Price'), + 'required' => TRUE, + 'precision' => [ + 20, + 2, + ], + 'where' => 'civicrm_booking_adhoc_charges_item.price', + 'table_name' => 'civicrm_booking_adhoc_charges_item', + 'entity' => 'AdhocChargesItem', + 'bao' => 'CRM_Booking_DAO_AdhocChargesItem', + 'localizable' => 0, + 'add' => NULL, + ], + 'weight' => [ 'name' => 'weight', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Weight') , - 'required' => true, - ) , - 'is_active' => array( + 'title' => E::ts('Weight'), + 'required' => TRUE, + 'where' => 'civicrm_booking_adhoc_charges_item.weight', + 'table_name' => 'civicrm_booking_adhoc_charges_item', + 'entity' => 'AdhocChargesItem', + 'bao' => 'CRM_Booking_DAO_AdhocChargesItem', + 'localizable' => 0, + 'add' => NULL, + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is cancelled') , - 'import' => true, + 'title' => E::ts('Slot is cancelled'), + 'import' => TRUE, 'where' => 'civicrm_booking_adhoc_charges_item.is_active', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'default' => '1', - ) , - 'is_deleted' => array( + 'table_name' => 'civicrm_booking_adhoc_charges_item', + 'entity' => 'AdhocChargesItem', + 'bao' => 'CRM_Booking_DAO_AdhocChargesItem', + 'localizable' => 0, + 'add' => '4.4', + ], + 'is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is in the Trash') , - 'import' => true, + 'title' => E::ts('Slot is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_booking_adhoc_charges_item.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - ); + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_adhoc_charges_item', + 'entity' => 'AdhocChargesItem', + 'bao' => 'CRM_Booking_DAO_AdhocChargesItem', + 'localizable' => 0, + 'add' => '4.4', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'name' => 'name', - 'label' => 'label', - 'price' => 'price', - 'weight' => 'weight', - 'is_active' => 'is_active', - 'is_deleted' => 'is_deleted', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be imported * - * @access public - * return array - * @static + * @param bool $prefix + * + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_adhoc_charges_item'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_adhoc_charges_item', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of fields that can be exported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_adhoc_charges_item'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_adhoc_charges_item', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = [ + 'index_is_active' => [ + 'name' => 'index_is_active', + 'field' => [ + 0 => 'is_active', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_adhoc_charges_item::0::is_active', + ], + 'index_is_deleted' => [ + 'name' => 'index_is_deleted', + 'field' => [ + 0 => 'is_deleted', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_adhoc_charges_item::0::is_deleted', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/Booking.php b/CRM/Booking/DAO/Booking.php index 28aa1408..fc94341a 100644 --- a/CRM/Booking/DAO/Booking.php +++ b/CRM/Booking/DAO/Booking.php @@ -1,525 +1,563 @@ __table = 'civicrm_booking'; parent::__construct(); } + /** - * return foreign keys and entity references + * Returns localized title of this entity. * - * @static - * @access public - * @return array of CRM_Core_EntityReference - */ - static function getReferenceColumns() - { - if (!self::$_links) { - self::$_links = array( - new CRM_Core_EntityReference(self::getTableName() , 'primary_contact_id', 'civicrm_contact', 'id') , - new CRM_Core_EntityReference(self::getTableName() , 'secondary_contact_id', 'civicrm_contact', 'id') , - ); + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Bookings') : E::ts('Booking'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'primary_contact_id', 'civicrm_contact', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'secondary_contact_id', 'civicrm_contact', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } - return self::$_links; + return Civi::$statics[__CLASS__]['links']; } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, - ) , - 'primary_contact_id' => array( + 'required' => TRUE, + 'where' => 'civicrm_booking.id', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'primary_contact_id' => [ 'name' => 'primary_contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Primary Contact ID') , - 'required' => true, - 'import' => true, + 'title' => E::ts('Primary Contact ID'), + 'description' => E::ts('FK to Contact ID'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_booking.primary_contact_id', 'headerPattern' => '/contact(.?id)?/i', 'dataPattern' => '/^\d+$/', - 'export' => true, + 'export' => TRUE, + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'secondary_contact_id' => array( + 'add' => '4.4', + ], + 'secondary_contact_id' => [ 'name' => 'secondary_contact_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Secondary Contact ID') , - 'required' => false, - 'import' => true, + 'title' => E::ts('Secondary Contact ID'), + 'description' => E::ts('FK to Contact ID'), + 'required' => FALSE, + 'import' => TRUE, 'where' => 'civicrm_booking.secondary_contact_id', 'headerPattern' => '/contact(.?id)?/i', 'dataPattern' => '/^\d+$/', - 'export' => true, + 'export' => TRUE, + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, 'FKClassName' => 'CRM_Contact_DAO_Contact', - ) , - 'booking_title' => array( + 'add' => '4.4', + ], + 'booking_title' => [ 'name' => 'title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Title') , - 'required' => true, + 'title' => E::ts('Title'), + 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'export' => true, 'where' => 'civicrm_booking.title', - 'headerPattern' => '', - 'dataPattern' => '', - ) , - 'booking_status_id' => array( + 'export' => TRUE, + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'booking_status_id' => [ 'name' => 'status_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Status ID') , - 'required' => true, - 'export' => true, + 'title' => E::ts('Status ID'), + 'description' => E::ts('The status associated with this booking. Implicit FK to option_value row in booking status option_group.'), + 'required' => TRUE, 'where' => 'civicrm_booking.status_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'pseudoconstant' => array( + 'export' => TRUE, + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'pseudoconstant' => [ 'optionGroupName' => 'booking_status', - ) - ) , - 'booking_date' => array( + 'optionEditPath' => 'civicrm/admin/options/booking_status', + ], + 'add' => '4.4', + ], + 'booking_date' => [ 'name' => 'booking_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => E::ts('Booking Date') , - 'required' => true, - 'export' => true, + 'title' => E::ts('Booking Date'), + 'required' => TRUE, 'where' => 'civicrm_booking.booking_date', - 'headerPattern' => '', - 'dataPattern' => '', - ) , - 'booking_start_date' => array( + 'export' => TRUE, + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'booking_start_date' => [ 'name' => 'start_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => E::ts('Start Date') , - 'required' => true, - 'export' => true, + 'title' => E::ts('Start Date'), + 'required' => TRUE, 'where' => 'civicrm_booking.start_date', - 'headerPattern' => '', - 'dataPattern' => '', - ) , - 'booking_end_date' => array( + 'export' => TRUE, + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'booking_end_date' => [ 'name' => 'end_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => E::ts('End Date') , - 'required' => true, - 'export' => true, + 'title' => E::ts('End Date'), + 'required' => TRUE, 'where' => 'civicrm_booking.end_date', - 'headerPattern' => '', - 'dataPattern' => '', - ) , - 'booking_po_number' => array( + 'export' => TRUE, + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'booking_po_number' => [ 'name' => 'po_number', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('PO Number') , - 'required' => true, + 'title' => E::ts('PO Number'), + 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - 'export' => true, 'where' => 'civicrm_booking.po_number', - 'headerPattern' => '', - 'dataPattern' => '', - ) , - 'booking_total_amount' => array( + 'export' => TRUE, + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'total_amount' => [ 'name' => 'total_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => E::ts('Total amount') , - 'required' => true, - 'import' => true, + 'title' => E::ts('Total amount'), + 'description' => E::ts('Total amount of this booking calculated from slots,sub slots, ad-hoc charges and discount amount'), + 'required' => TRUE, + 'precision' => [ + 20, + 2, + ], + 'import' => TRUE, 'where' => 'civicrm_booking.total_amount', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - 'description' => array( + 'export' => TRUE, + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'description' => [ 'name' => 'description', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Description') , + 'title' => E::ts('Description'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'note' => array( + 'where' => 'civicrm_booking.description', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'note' => [ 'name' => 'note', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => E::ts('Note') , - ) , - 'adhoc_charges_note' => array( + 'title' => E::ts('Note'), + 'where' => 'civicrm_booking.note', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'adhoc_charges_note' => [ 'name' => 'adhoc_charges_note', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => E::ts('Adhoc Charges Note') , - ) , - 'participants_estimate' => array( + 'title' => E::ts('Adhoc Charges Note'), + 'where' => 'civicrm_booking.adhoc_charges_note', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'participants_estimate' => [ 'name' => 'participants_estimate', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Participants Estimate') , + 'title' => E::ts('Participants Estimate'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'participants_actual' => array( + 'where' => 'civicrm_booking.participants_estimate', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'participants_actual' => [ 'name' => 'participants_actual', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Participants Actual') , + 'title' => E::ts('Participants Actual'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'discount_amount' => array( + 'where' => 'civicrm_booking.participants_actual', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'discount_amount' => [ 'name' => 'discount_amount', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => E::ts('Discount Amount') , - ) , - 'booking_is_deleted' => array( + 'title' => E::ts('Discount Amount'), + 'precision' => [ + 20, + 2, + ], + 'where' => 'civicrm_booking.discount_amount', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'booking_is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Booking is in the Trash') , - 'import' => true, + 'title' => E::ts('Booking is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_booking.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - 'created_by' => array( + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => '4.4', + ], + 'created_by' => [ 'name' => 'created_by', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Created By') , - 'required' => true, - ) , - 'created_date' => array( + 'title' => E::ts('Created By'), + 'required' => TRUE, + 'where' => 'civicrm_booking.created_by', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'created_date' => [ 'name' => 'created_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => E::ts('Created Date') , - 'required' => true, - ) , - 'updated_by' => array( + 'title' => E::ts('Created Date'), + 'required' => TRUE, + 'where' => 'civicrm_booking.created_date', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'updated_by' => [ 'name' => 'updated_by', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Updated By') , - 'required' => true, - ) , - 'updated_date' => array( + 'title' => E::ts('Updated By'), + 'required' => TRUE, + 'where' => 'civicrm_booking.updated_by', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + 'updated_date' => [ 'name' => 'updated_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => E::ts('Updated Date') , - 'required' => true, - ) , - ); + 'title' => E::ts('Updated Date'), + 'required' => TRUE, + 'where' => 'civicrm_booking.updated_date', + 'table_name' => 'civicrm_booking', + 'entity' => 'Booking', + 'bao' => 'CRM_Booking_DAO_Booking', + 'localizable' => 0, + 'add' => NULL, + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'primary_contact_id' => 'primary_contact_id', - 'secondary_contact_id' => 'secondary_contact_id', - 'title' => 'booking_title', - 'status_id' => 'booking_status_id', - 'booking_date' => 'booking_date', - 'start_date' => 'booking_start_date', - 'end_date' => 'booking_end_date', - 'po_number' => 'booking_po_number', - 'total_amount' => 'total_amount', - 'description' => 'description', - 'note' => 'note', - 'adhoc_charges_note' => 'adhoc_charges_note', - 'participants_estimate' => 'participants_estimate', - 'participants_actual' => 'participants_actual', - 'discount_amount' => 'discount_amount', - 'is_deleted' => 'booking_is_deleted', - 'created_by' => 'created_by', - 'created_date' => 'created_date', - 'updated_by' => 'updated_by', - 'updated_date' => 'updated_date', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be imported * - * @access public - * return array - * @static - */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of fields that can be exported * - * @access public - * return array - * @static - */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + * @param bool $prefix + * + * @return array + */ + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = [ + 'index_is_deleted' => [ + 'name' => 'index_is_deleted', + 'field' => [ + 0 => 'is_deleted', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking::0::is_deleted', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/BookingConfig.php b/CRM/Booking/DAO/BookingConfig.php index bb3e1e94..5a0e095d 100644 --- a/CRM/Booking/DAO/BookingConfig.php +++ b/CRM/Booking/DAO/BookingConfig.php @@ -1,375 +1,384 @@ __table = 'civicrm_booking_config'; parent::__construct(); } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Booking Configs') : E::ts('Booking Config'); + } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, - ) , - 'domain_id' => array( + 'required' => TRUE, + 'where' => 'civicrm_booking_config.id', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'domain_id' => [ 'name' => 'domain_id', 'type' => CRM_Utils_Type::T_INT, - ) , - 'day_start_at' => array( + 'where' => 'civicrm_booking_config.domain_id', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'day_start_at' => [ 'name' => 'day_start_at', 'type' => CRM_Utils_Type::T_TIME, - 'title' => E::ts('Day Start At') , - 'required' => true, - ) , - 'day_end_at' => array( + 'title' => E::ts('Day Start At'), + 'required' => TRUE, + 'where' => 'civicrm_booking_config.day_start_at', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'day_end_at' => [ 'name' => 'day_end_at', 'type' => CRM_Utils_Type::T_TIME, - 'title' => E::ts('Day End At') , - 'required' => true, - ) , - 'time_period' => array( + 'title' => E::ts('Day End At'), + 'required' => TRUE, + 'where' => 'civicrm_booking_config.day_end_at', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'time_period' => [ 'name' => 'time_period', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Time Period') , - 'required' => true, - ) , - 'log_confirmation_email' => array( + 'title' => E::ts('Time Period'), + 'required' => TRUE, + 'where' => 'civicrm_booking_config.time_period', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'log_confirmation_email' => [ 'name' => 'log_confirmation_email', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Log Confirmation Email') , - 'required' => true, - ) , - 'unlimited_resource_time_config' => array( + 'title' => E::ts('Log Confirmation Email'), + 'description' => E::ts('Create an activity record againt contact for conformation emails'), + 'required' => TRUE, + 'where' => 'civicrm_booking_config.log_confirmation_email', + 'default' => '0', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'unlimited_resource_time_config' => [ 'name' => 'unlimited_resource_time_config', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Unlimited Resource Time Config') , - 'required' => true, - ) , - 'cc_email_address' => array( + 'title' => E::ts('Unlimited Resource Time Config'), + 'description' => E::ts('Only allow unlimited resources to be booked within time span of the parent limited resource booking'), + 'required' => TRUE, + 'where' => 'civicrm_booking_config.unlimited_resource_time_config', + 'default' => '1', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'cc_email_address' => [ 'name' => 'cc_email_address', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Cc Email Address') , + 'title' => E::ts('Cc Email Address'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'bcc_email_address' => array( + 'where' => 'civicrm_booking_config.cc_email_address', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'bcc_email_address' => [ 'name' => 'bcc_email_address', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Bcc Email Address') , + 'title' => E::ts('Bcc Email Address'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'slot_new_colour' => array( + 'where' => 'civicrm_booking_config.bcc_email_address', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'slot_new_colour' => [ 'name' => 'slot_new_colour', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Slot New Colour') , + 'title' => E::ts('Slot New Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, - ) , - 'slot_being_edited_colour' => array( + 'where' => 'civicrm_booking_config.slot_new_colour', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'slot_being_edited_colour' => [ 'name' => 'slot_being_edited_colour', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Slot Being Edited Colour') , + 'title' => E::ts('Slot Being Edited Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, - ) , - 'slot_booked_colour' => array( + 'where' => 'civicrm_booking_config.slot_being_edited_colour', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'slot_booked_colour' => [ 'name' => 'slot_booked_colour', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Slot Booked Colour') , + 'title' => E::ts('Slot Booked Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, - ) , - 'slot_provisional_colour' => array( + 'where' => 'civicrm_booking_config.slot_booked_colour', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'slot_provisional_colour' => [ 'name' => 'slot_provisional_colour', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Slot Provisional Colour') , + 'title' => E::ts('Slot Provisional Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, - ) , - 'slot_unavailable_colour' => array( + 'where' => 'civicrm_booking_config.slot_provisional_colour', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + 'slot_unavailable_colour' => [ 'name' => 'slot_unavailable_colour', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Slot Unavailable Colour') , + 'title' => E::ts('Slot Unavailable Colour'), 'maxlength' => 10, 'size' => CRM_Utils_Type::TWELVE, - ) , - ); + 'where' => 'civicrm_booking_config.slot_unavailable_colour', + 'table_name' => 'civicrm_booking_config', + 'entity' => 'BookingConfig', + 'bao' => 'CRM_Booking_DAO_BookingConfig', + 'localizable' => 0, + 'add' => NULL, + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'domain_id' => 'domain_id', - 'day_start_at' => 'day_start_at', - 'day_end_at' => 'day_end_at', - 'time_period' => 'time_period', - 'log_confirmation_email' => 'log_confirmation_email', - 'unlimited_resource_time_config' => 'unlimited_resource_time_config', - 'cc_email_address' => 'cc_email_address', - 'bcc_email_address' => 'bcc_email_address', - 'slot_new_colour' => 'slot_new_colour', - 'slot_being_edited_colour' => 'slot_being_edited_colour', - 'slot_booked_colour' => 'slot_booked_colour', - 'slot_provisional_colour' => 'slot_provisional_colour', - 'slot_unavailable_colour' => 'slot_unavailable_colour', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be imported * - * @access public - * return array - * @static + * @param bool $prefix + * + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_config'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_config', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of fields that can be exported * - * @access public - * return array - * @static + * @param bool $prefix + * + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_config'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_config', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/Cancellation.php b/CRM/Booking/DAO/Cancellation.php index ca6c2e34..c3ca0dce 100644 --- a/CRM/Booking/DAO/Cancellation.php +++ b/CRM/Booking/DAO/Cancellation.php @@ -1,286 +1,258 @@ __table = 'civicrm_booking_cancellation'; parent::__construct(); } + /** - * return foreign keys and entity references + * Returns localized title of this entity. * - * @static - * @access public - * @return array of CRM_Core_EntityReference + * @param bool $plural + * Whether to return the plural version of the title. */ - static function getReferenceColumns() - { - if (!self::$_links) { - self::$_links = array( - new CRM_Core_EntityReference(self::getTableName() , 'booking_id', 'civicrm_booking', 'id') , - ); + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Cancellations') : E::ts('Cancellation'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'booking_id', 'civicrm_booking', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } - return self::$_links; + return Civi::$statics[__CLASS__]['links']; } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, - ) , - 'booking_id' => array( + 'required' => TRUE, + 'where' => 'civicrm_booking_cancellation.id', + 'table_name' => 'civicrm_booking_cancellation', + 'entity' => 'Cancellation', + 'bao' => 'CRM_Booking_DAO_Cancellation', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'booking_id' => [ 'name' => 'booking_id', 'type' => CRM_Utils_Type::T_INT, + 'description' => E::ts('FK to Booking'), + 'where' => 'civicrm_booking_cancellation.booking_id', + 'table_name' => 'civicrm_booking_cancellation', + 'entity' => 'Cancellation', + 'bao' => 'CRM_Booking_DAO_Cancellation', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_Booking', - ) , - 'cancellation_date' => array( + 'add' => NULL, + ], + 'cancellation_date' => [ 'name' => 'cancellation_date', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => E::ts('Cancellation Date') , - 'required' => true, - ) , - 'cancellation_fee' => array( + 'title' => E::ts('Cancellation Date'), + 'required' => TRUE, + 'where' => 'civicrm_booking_cancellation.cancellation_date', + 'table_name' => 'civicrm_booking_cancellation', + 'entity' => 'Cancellation', + 'bao' => 'CRM_Booking_DAO_Cancellation', + 'localizable' => 0, + 'add' => NULL, + ], + 'cancellation_fee' => [ 'name' => 'cancellation_fee', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => E::ts('Cancellation charge') , - 'required' => true, - ) , - 'additional_fee' => array( + 'title' => E::ts('Cancellation charge'), + 'required' => TRUE, + 'precision' => [ + 20, + 2, + ], + 'where' => 'civicrm_booking_cancellation.cancellation_fee', + 'table_name' => 'civicrm_booking_cancellation', + 'entity' => 'Cancellation', + 'bao' => 'CRM_Booking_DAO_Cancellation', + 'localizable' => 0, + 'add' => NULL, + ], + 'additional_fee' => [ 'name' => 'additional_fee', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => E::ts('Additional charge') , - ) , - 'comment' => array( + 'title' => E::ts('Additional charge'), + 'precision' => [ + 20, + 2, + ], + 'where' => 'civicrm_booking_cancellation.additional_fee', + 'table_name' => 'civicrm_booking_cancellation', + 'entity' => 'Cancellation', + 'bao' => 'CRM_Booking_DAO_Cancellation', + 'localizable' => 0, + 'add' => NULL, + ], + 'comment' => [ 'name' => 'comment', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => E::ts('Comment') , - ) , - ); + 'title' => E::ts('Comment'), + 'where' => 'civicrm_booking_cancellation.comment', + 'table_name' => 'civicrm_booking_cancellation', + 'entity' => 'Cancellation', + 'bao' => 'CRM_Booking_DAO_Cancellation', + 'localizable' => 0, + 'add' => NULL, + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'booking_id' => 'booking_id', - 'cancellation_date' => 'cancellation_date', - 'cancellation_fee' => 'cancellation_fee', - 'additional_fee' => 'additional_fee', - 'comment' => 'comment', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be imported * - * @access public - * return array - * @static + * @param bool $prefix + * + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_cancellation'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_cancellation', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of fields that can be exported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_cancellation'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_cancellation', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/Payment.php b/CRM/Booking/DAO/Payment.php index 9faa5388..607da76c 100644 --- a/CRM/Booking/DAO/Payment.php +++ b/CRM/Booking/DAO/Payment.php @@ -1,263 +1,211 @@ __table = 'civicrm_booking_payment'; parent::__construct(); } + /** - * return foreign keys and entity references + * Returns localized title of this entity. * - * @static - * @access public - * @return array of CRM_Core_EntityReference + * @param bool $plural + * Whether to return the plural version of the title. */ - static function getReferenceColumns() - { - if (!self::$_links) { - self::$_links = array( - new CRM_Core_EntityReference(self::getTableName() , 'booking_id', 'civicrm_booking', 'id') , - new CRM_Core_EntityReference(self::getTableName() , 'contribution_id', 'civicrm_contribution', 'id') , - ); + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Payments') : E::ts('Payment'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'booking_id', 'civicrm_booking', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contribution_id', 'civicrm_contribution', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } - return self::$_links; + return Civi::$statics[__CLASS__]['links']; } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('ID') , - 'required' => true, - ) , - 'booking_id' => array( + 'title' => E::ts('ID'), + 'required' => TRUE, + 'where' => 'civicrm_booking_payment.id', + 'table_name' => 'civicrm_booking_payment', + 'entity' => 'Payment', + 'bao' => 'CRM_Booking_DAO_Payment', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'booking_id' => [ 'name' => 'booking_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Booking ID') , - 'required' => true, + 'title' => E::ts('Booking ID'), + 'description' => E::ts('Foreign key to the booking id for this payment.'), + 'required' => TRUE, + 'where' => 'civicrm_booking_payment.booking_id', + 'table_name' => 'civicrm_booking_payment', + 'entity' => 'Payment', + 'bao' => 'CRM_Booking_DAO_Payment', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_Booking', - ) , - 'contribution_id' => array( + 'add' => '2.0', + ], + 'contribution_id' => [ 'name' => 'contribution_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Contribution ID') , - 'required' => true, - 'import' => true, + 'title' => E::ts('Contribution ID'), + 'description' => E::ts('Foreign key to the contribution for this payment.'), + 'required' => TRUE, + 'import' => TRUE, 'where' => 'civicrm_booking_payment.contribution_id', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, + 'table_name' => 'civicrm_booking_payment', + 'entity' => 'Payment', + 'bao' => 'CRM_Booking_DAO_Payment', + 'localizable' => 0, 'FKClassName' => 'CRM_Contribute_DAO_Contribution', - ) , - ); + 'add' => NULL, + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'booking_id' => 'booking_id', - 'contribution_id' => 'contribution_id', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be imported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_payment'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_payment', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of fields that can be exported * - * @access public - * return array - * @static + * @param bool $prefix + * + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_payment'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_payment', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/Resource.php b/CRM/Booking/DAO/Resource.php index d321d79a..7aa14f33 100644 --- a/CRM/Booking/DAO/Resource.php +++ b/CRM/Booking/DAO/Resource.php @@ -1,358 +1,369 @@ __table = 'civicrm_booking_resource'; parent::__construct(); } + /** - * return foreign keys and entity references + * Returns localized title of this entity. * - * @static - * @access public - * @return array of CRM_Core_EntityReference + * @param bool $plural + * Whether to return the plural version of the title. */ - static function getReferenceColumns() - { - if (!self::$_links) { - self::$_links = array( - new CRM_Core_EntityReference(self::getTableName() , 'set_id', 'civicrm_booking_resource_config_set', 'id') , - ); + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Resources') : E::ts('Resource'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'set_id', 'civicrm_booking_resource_config_set', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } - return self::$_links; + return Civi::$statics[__CLASS__]['links']; } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, - ) , - 'set_id' => array( + 'required' => TRUE, + 'where' => 'civicrm_booking_resource.id', + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'set_id' => [ 'name' => 'set_id', 'type' => CRM_Utils_Type::T_INT, + 'description' => E::ts('FK to resource configuration option set'), + 'where' => 'civicrm_booking_resource.set_id', + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_ResourceConfigSet', - ) , - 'booking_resource_label' => array( + 'add' => NULL, + ], + 'booking_resource_label' => [ 'name' => 'label', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Label') , - 'required' => true, + 'title' => E::ts('Label'), + 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'booking_resource_description' => array( + 'where' => 'civicrm_booking_resource.label', + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, + 'add' => NULL, + ], + 'booking_resource_description' => [ 'name' => 'description', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Label') , + 'title' => E::ts('Label'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'type_id' => array( + 'where' => 'civicrm_booking_resource.description', + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, + 'add' => NULL, + ], + 'type_id' => [ 'name' => 'type_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Type ID') , - 'required' => true, + 'title' => E::ts('Type ID'), + 'description' => E::ts('The type associated with this resource. Implicit FK to option_value row in booking_resource_type option_group.'), + 'required' => TRUE, 'maxlength' => 512, 'size' => CRM_Utils_Type::HUGE, - 'pseudoconstant' => array( + 'where' => 'civicrm_booking_resource.type_id', + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, + 'pseudoconstant' => [ 'optionGroupName' => 'booking_resource_type', - ) - ) , - 'location_id' => array( + 'optionEditPath' => 'civicrm/admin/options/booking_resource_type', + ], + 'add' => '4.4', + ], + 'location_id' => [ 'name' => 'location_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Location ID') , + 'title' => E::ts('Location ID'), + 'description' => E::ts('The location associated with this resource. Implicit FK to option_value row in booking_resource_location option_group.'), 'maxlength' => 512, 'size' => CRM_Utils_Type::HUGE, - 'pseudoconstant' => array( + 'where' => 'civicrm_booking_resource.location_id', + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, + 'pseudoconstant' => [ 'optionGroupName' => 'booking_resource_location', - ) - ) , - 'weight' => array( + 'optionEditPath' => 'civicrm/admin/options/booking_resource_location', + ], + 'add' => '4.4', + ], + 'weight' => [ 'name' => 'weight', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Weight') , - 'required' => true, - ) , - 'is_unlimited' => array( + 'title' => E::ts('Weight'), + 'required' => TRUE, + 'where' => 'civicrm_booking_resource.weight', + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, + 'add' => NULL, + ], + 'is_unlimited' => [ 'name' => 'is_unlimited', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'required' => true, - ) , - 'is_active' => array( + 'required' => TRUE, + 'where' => 'civicrm_booking_resource.is_unlimited', + 'default' => '0', + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, + 'add' => NULL, + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is cancelled') , - 'import' => true, + 'title' => E::ts('Slot is cancelled'), + 'import' => TRUE, 'where' => 'civicrm_booking_resource.is_active', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'default' => '1', - ) , - 'is_deleted' => array( + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, + 'add' => '4.4', + ], + 'is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is in the Trash') , - 'import' => true, + 'title' => E::ts('Slot is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_booking_resource.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - ); + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_resource', + 'entity' => 'Resource', + 'bao' => 'CRM_Booking_DAO_Resource', + 'localizable' => 0, + 'add' => '4.4', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'set_id' => 'set_id', - 'label' => 'booking_resource_label', - 'description' => 'booking_resource_description', - 'type_id' => 'type_id', - 'location_id' => 'location_id', - 'weight' => 'weight', - 'is_unlimited' => 'is_unlimited', - 'is_active' => 'is_active', - 'is_deleted' => 'is_deleted', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be imported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_resource'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_resource', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of fields that can be exported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_resource'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_resource', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = [ + 'index_is_unlimited' => [ + 'name' => 'index_is_unlimited', + 'field' => [ + 0 => 'is_unlimited', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_resource::0::is_unlimited', + ], + 'index_is_active' => [ + 'name' => 'index_is_active', + 'field' => [ + 0 => 'is_active', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_resource::0::is_active', + ], + 'index_is_deleted' => [ + 'name' => 'index_is_deleted', + 'field' => [ + 0 => 'is_deleted', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_resource::0::is_deleted', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/ResourceConfigOption.php b/CRM/Booking/DAO/ResourceConfigOption.php index 305b3148..e90291af 100644 --- a/CRM/Booking/DAO/ResourceConfigOption.php +++ b/CRM/Booking/DAO/ResourceConfigOption.php @@ -1,344 +1,342 @@ __table = 'civicrm_booking_resource_config_option'; parent::__construct(); } + /** - * return foreign keys and entity references + * Returns localized title of this entity. * - * @static - * @access public - * @return array of CRM_Core_EntityReference + * @param bool $plural + * Whether to return the plural version of the title. */ - static function getReferenceColumns() - { - if (!self::$_links) { - self::$_links = array( - new CRM_Core_EntityReference(self::getTableName() , 'set_id', 'civicrm_booking_resource_config_set', 'id') , - ); + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Resource Config Options') : E::ts('Resource Config Option'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'set_id', 'civicrm_booking_resource_config_set', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } - return self::$_links; + return Civi::$statics[__CLASS__]['links']; } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('ID') , - 'required' => true, - ) , - 'set_id' => array( + 'title' => E::ts('ID'), + 'required' => TRUE, + 'where' => 'civicrm_booking_resource_config_option.id', + 'table_name' => 'civicrm_booking_resource_config_option', + 'entity' => 'ResourceConfigOption', + 'bao' => 'CRM_Booking_DAO_ResourceConfigOption', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'set_id' => [ 'name' => 'set_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Set ID') , - 'required' => true, + 'title' => E::ts('Set ID'), + 'description' => E::ts('Foreign key to the resource set for this option.'), + 'required' => TRUE, + 'where' => 'civicrm_booking_resource_config_option.set_id', + 'table_name' => 'civicrm_booking_resource_config_option', + 'entity' => 'ResourceConfigOption', + 'bao' => 'CRM_Booking_DAO_ResourceConfigOption', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_ResourceConfigSet', - ) , - 'label' => array( + 'add' => NULL, + ], + 'label' => [ 'name' => 'label', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Label') , - 'required' => true, + 'title' => E::ts('Label'), + 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'price' => array( + 'where' => 'civicrm_booking_resource_config_option.label', + 'table_name' => 'civicrm_booking_resource_config_option', + 'entity' => 'ResourceConfigOption', + 'bao' => 'CRM_Booking_DAO_ResourceConfigOption', + 'localizable' => 0, + 'add' => NULL, + ], + 'price' => [ 'name' => 'price', 'type' => CRM_Utils_Type::T_MONEY, - 'title' => E::ts('Price') , - 'required' => true, - ) , - 'max_size' => array( + 'title' => E::ts('Price'), + 'required' => TRUE, + 'precision' => [ + 20, + 2, + ], + 'where' => 'civicrm_booking_resource_config_option.price', + 'table_name' => 'civicrm_booking_resource_config_option', + 'entity' => 'ResourceConfigOption', + 'bao' => 'CRM_Booking_DAO_ResourceConfigOption', + 'localizable' => 0, + 'add' => NULL, + ], + 'max_size' => [ 'name' => 'max_size', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Max Size') , + 'title' => E::ts('Max Size'), 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'unit_id' => array( + 'where' => 'civicrm_booking_resource_config_option.max_size', + 'table_name' => 'civicrm_booking_resource_config_option', + 'entity' => 'ResourceConfigOption', + 'bao' => 'CRM_Booking_DAO_ResourceConfigOption', + 'localizable' => 0, + 'add' => NULL, + ], + 'unit_id' => [ 'name' => 'unit_id', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Unit ID') , + 'title' => E::ts('Unit ID'), + 'description' => E::ts('The unit associated with this config option. Implicit FK to option_value row in booking_size_unit option_group.'), 'maxlength' => 512, 'size' => CRM_Utils_Type::HUGE, - 'pseudoconstant' => array( + 'where' => 'civicrm_booking_resource_config_option.unit_id', + 'table_name' => 'civicrm_booking_resource_config_option', + 'entity' => 'ResourceConfigOption', + 'bao' => 'CRM_Booking_DAO_ResourceConfigOption', + 'localizable' => 0, + 'pseudoconstant' => [ 'optionGroupName' => 'booking_size_unit', - ) - ) , - 'weight' => array( + 'optionEditPath' => 'civicrm/admin/options/booking_size_unit', + ], + 'add' => '4.4', + ], + 'weight' => [ 'name' => 'weight', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Weight') , - 'required' => true, - ) , - 'is_active' => array( + 'title' => E::ts('Weight'), + 'required' => TRUE, + 'where' => 'civicrm_booking_resource_config_option.weight', + 'table_name' => 'civicrm_booking_resource_config_option', + 'entity' => 'ResourceConfigOption', + 'bao' => 'CRM_Booking_DAO_ResourceConfigOption', + 'localizable' => 0, + 'add' => NULL, + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is cancelled') , - 'import' => true, + 'title' => E::ts('Slot is cancelled'), + 'import' => TRUE, 'where' => 'civicrm_booking_resource_config_option.is_active', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'default' => '1', - ) , - 'is_deleted' => array( + 'table_name' => 'civicrm_booking_resource_config_option', + 'entity' => 'ResourceConfigOption', + 'bao' => 'CRM_Booking_DAO_ResourceConfigOption', + 'localizable' => 0, + 'add' => '4.4', + ], + 'is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is in the Trash') , - 'import' => true, + 'title' => E::ts('Slot is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_booking_resource_config_option.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - ); + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_resource_config_option', + 'entity' => 'ResourceConfigOption', + 'bao' => 'CRM_Booking_DAO_ResourceConfigOption', + 'localizable' => 0, + 'add' => '4.4', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'set_id' => 'set_id', - 'label' => 'label', - 'price' => 'price', - 'max_size' => 'max_size', - 'unit_id' => 'unit_id', - 'weight' => 'weight', - 'is_active' => 'is_active', - 'is_deleted' => 'is_deleted', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_resource_config_option', $prefix, []); + return $r; + } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be exported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_resource_config_option'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_resource_config_option', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of indices + * + * @param bool $localize * - * @access public - * return array - * @static + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_resource_config_option'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function indices($localize = TRUE) { + $indices = [ + 'index_is_active' => [ + 'name' => 'index_is_active', + 'field' => [ + 0 => 'is_active', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_resource_config_option::0::is_active', + ], + 'index_is_deleted' => [ + 'name' => 'index_is_deleted', + 'field' => [ + 0 => 'is_deleted', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_resource_config_option::0::is_deleted', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/ResourceConfigSet.php b/CRM/Booking/DAO/ResourceConfigSet.php index 1816e176..385b7141 100644 --- a/CRM/Booking/DAO/ResourceConfigSet.php +++ b/CRM/Booking/DAO/ResourceConfigSet.php @@ -1,272 +1,242 @@ __table = 'civicrm_booking_resource_config_set'; parent::__construct(); } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Resource Config Sets') : E::ts('Resource Config Set'); + } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('ID') , - 'required' => true, - ) , - 'title' => array( + 'title' => E::ts('ID'), + 'required' => TRUE, + 'where' => 'civicrm_booking_resource_config_set.id', + 'table_name' => 'civicrm_booking_resource_config_set', + 'entity' => 'ResourceConfigSet', + 'bao' => 'CRM_Booking_DAO_ResourceConfigSet', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'title' => [ 'name' => 'title', 'type' => CRM_Utils_Type::T_STRING, - 'title' => E::ts('Title') , - 'required' => true, + 'title' => E::ts('Title'), + 'required' => TRUE, 'maxlength' => 255, 'size' => CRM_Utils_Type::HUGE, - ) , - 'weight' => array( + 'where' => 'civicrm_booking_resource_config_set.title', + 'table_name' => 'civicrm_booking_resource_config_set', + 'entity' => 'ResourceConfigSet', + 'bao' => 'CRM_Booking_DAO_ResourceConfigSet', + 'localizable' => 0, + 'add' => NULL, + ], + 'weight' => [ 'name' => 'weight', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Weight') , - 'required' => true, - ) , - 'is_active' => array( + 'title' => E::ts('Weight'), + 'required' => TRUE, + 'where' => 'civicrm_booking_resource_config_set.weight', + 'table_name' => 'civicrm_booking_resource_config_set', + 'entity' => 'ResourceConfigSet', + 'bao' => 'CRM_Booking_DAO_ResourceConfigSet', + 'localizable' => 0, + 'add' => NULL, + ], + 'is_active' => [ 'name' => 'is_active', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is cancelled') , - 'import' => true, + 'title' => E::ts('Slot is cancelled'), + 'import' => TRUE, 'where' => 'civicrm_booking_resource_config_set.is_active', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, + 'export' => TRUE, 'default' => '1', - ) , - 'is_deleted' => array( + 'table_name' => 'civicrm_booking_resource_config_set', + 'entity' => 'ResourceConfigSet', + 'bao' => 'CRM_Booking_DAO_ResourceConfigSet', + 'localizable' => 0, + 'add' => '4.4', + ], + 'is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is in the Trash') , - 'import' => true, + 'title' => E::ts('Slot is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_booking_resource_config_set.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - ); + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_resource_config_set', + 'entity' => 'ResourceConfigSet', + 'bao' => 'CRM_Booking_DAO_ResourceConfigSet', + 'localizable' => 0, + 'add' => '4.4', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'title' => 'title', - 'weight' => 'weight', - 'is_active' => 'is_active', - 'is_deleted' => 'is_deleted', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + + /** + * Returns the list of fields that can be imported + * + * @param bool $prefix + * + * @return array + */ + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_resource_config_set', $prefix, []); + return $r; + } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be exported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_resource_config_set'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_resource_config_set', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of indices + * + * @param bool $localize * - * @access public - * return array - * @static + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_resource_config_set'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function indices($localize = TRUE) { + $indices = [ + 'index_is_active' => [ + 'name' => 'index_is_active', + 'field' => [ + 0 => 'is_active', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_resource_config_set::0::is_active', + ], + 'index_is_deleted' => [ + 'name' => 'index_is_deleted', + 'field' => [ + 0 => 'is_deleted', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_resource_config_set::0::is_deleted', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/ResourceCriteria.php b/CRM/Booking/DAO/ResourceCriteria.php index 76473a85..ea67da22 100644 --- a/CRM/Booking/DAO/ResourceCriteria.php +++ b/CRM/Booking/DAO/ResourceCriteria.php @@ -1,257 +1,208 @@ __table = 'civicrm_booking_resource_criteria'; parent::__construct(); } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Resource Criterias') : E::ts('Resource Criteria'); + } + /** - * return foreign keys and entity references + * Returns foreign keys and entity references. * - * @static - * @access public - * @return array of CRM_Core_EntityReference + * @return array + * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() - { - if (!self::$_links) { - self::$_links = array( - new CRM_Core_EntityReference(self::getTableName() , 'resource_id', 'civicrm_booking_resource', 'id') , - ); + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'resource_id', 'civicrm_booking_resource', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } - return self::$_links; + return Civi::$statics[__CLASS__]['links']; } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('ID') , - 'required' => true, - ) , - 'resource_id' => array( + 'title' => E::ts('ID'), + 'required' => TRUE, + 'where' => 'civicrm_booking_resource_criteria.id', + 'table_name' => 'civicrm_booking_resource_criteria', + 'entity' => 'ResourceCriteria', + 'bao' => 'CRM_Booking_DAO_ResourceCriteria', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'resource_id' => [ 'name' => 'resource_id', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Resource ID') , - 'required' => true, + 'title' => E::ts('Resource ID'), + 'description' => E::ts('Foreign key to the resoure for this resource criteria.'), + 'required' => TRUE, + 'where' => 'civicrm_booking_resource_criteria.resource_id', + 'table_name' => 'civicrm_booking_resource_criteria', + 'entity' => 'ResourceCriteria', + 'bao' => 'CRM_Booking_DAO_ResourceCriteria', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_Resource', - ) , - 'criteria_id' => array( + 'add' => NULL, + ], + 'criteria_id' => [ 'name' => 'criteria_id', 'type' => CRM_Utils_Type::T_STRING, - 'required' => true, + 'description' => E::ts('Foreign key to the resource criteria option group.'), + 'required' => TRUE, 'maxlength' => 512, 'size' => CRM_Utils_Type::HUGE, - ) , - ); + 'where' => 'civicrm_booking_resource_criteria.criteria_id', + 'table_name' => 'civicrm_booking_resource_criteria', + 'entity' => 'ResourceCriteria', + 'bao' => 'CRM_Booking_DAO_ResourceCriteria', + 'localizable' => 0, + 'add' => NULL, + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'resource_id' => 'resource_id', - 'criteria_id' => 'criteria_id', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be imported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_resource_criteria'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_resource_criteria', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of fields that can be exported * - * @access public - * return array - * @static + * @param bool $prefix + * + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_resource_criteria'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_resource_criteria', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = []; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/Slot.php b/CRM/Booking/DAO/Slot.php index 2e390566..8698334d 100644 --- a/CRM/Booking/DAO/Slot.php +++ b/CRM/Booking/DAO/Slot.php @@ -1,347 +1,348 @@ __table = 'civicrm_booking_slot'; parent::__construct(); } + + /** + * Returns localized title of this entity. + * + * @param bool $plural + * Whether to return the plural version of the title. + */ + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Slots') : E::ts('Slot'); + } + /** - * return foreign keys and entity references + * Returns foreign keys and entity references. * - * @static - * @access public - * @return array of CRM_Core_EntityReference + * @return array + * [CRM_Core_Reference_Interface] */ - static function getReferenceColumns() - { - if (!self::$_links) { - self::$_links = array( - new CRM_Core_EntityReference(self::getTableName() , 'booking_id', 'civicrm_booking', 'id') , - new CRM_Core_EntityReference(self::getTableName() , 'resource_id', 'civicrm_booking_resource', 'id') , - new CRM_Core_EntityReference(self::getTableName() , 'config_id', 'civicrm_booking_resource_config_option', 'id') , - ); + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'booking_id', 'civicrm_booking', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'resource_id', 'civicrm_booking_resource', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'config_id', 'civicrm_booking_resource_config_option', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } - return self::$_links; + return Civi::$statics[__CLASS__]['links']; } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, - ) , - 'booking_id' => array( + 'required' => TRUE, + 'where' => 'civicrm_booking_slot.id', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'booking_id' => [ 'name' => 'booking_id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, + 'description' => E::ts('FK to Booking ID'), + 'required' => TRUE, + 'where' => 'civicrm_booking_slot.booking_id', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_Booking', - ) , - 'resource_id' => array( + 'add' => NULL, + ], + 'resource_id' => [ 'name' => 'resource_id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, + 'description' => E::ts('FK to resource ID'), + 'required' => TRUE, + 'where' => 'civicrm_booking_slot.resource_id', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_Resource', - ) , - 'config_id' => array( + 'add' => NULL, + ], + 'config_id' => [ 'name' => 'config_id', 'type' => CRM_Utils_Type::T_INT, + 'description' => E::ts('FK to resource configuration option ID'), + 'where' => 'civicrm_booking_slot.config_id', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_ResourceConfigOption', - ) , - 'start' => array( + 'add' => NULL, + ], + 'start' => [ 'name' => 'start', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => E::ts('Start') , - 'required' => true, - ) , - 'end' => array( + 'title' => E::ts('Start'), + 'required' => TRUE, + 'where' => 'civicrm_booking_slot.start', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, + 'add' => NULL, + ], + 'end' => [ 'name' => 'end', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => E::ts('End') , - 'required' => true, - ) , - 'quantity' => array( + 'title' => E::ts('End'), + 'required' => TRUE, + 'where' => 'civicrm_booking_slot.end', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, + 'add' => NULL, + ], + 'quantity' => [ 'name' => 'quantity', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Quantity') , - 'required' => true, - ) , - 'note' => array( + 'title' => E::ts('Quantity'), + 'required' => TRUE, + 'where' => 'civicrm_booking_slot.quantity', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, + 'add' => NULL, + ], + 'note' => [ 'name' => 'note', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => E::ts('Note') , - ) , - 'is_cancelled' => array( + 'title' => E::ts('Note'), + 'where' => 'civicrm_booking_slot.note', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, + 'add' => NULL, + ], + 'is_cancelled' => [ 'name' => 'is_cancelled', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is cancelled') , - 'import' => true, + 'title' => E::ts('Slot is cancelled'), + 'import' => TRUE, 'where' => 'civicrm_booking_slot.is_cancelled', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - 'is_deleted' => array( + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, + 'add' => '4.4', + ], + 'is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('Slot is in the Trash') , - 'import' => true, + 'title' => E::ts('Slot is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_booking_slot.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - ); + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_slot', + 'entity' => 'Slot', + 'bao' => 'CRM_Booking_DAO_Slot', + 'localizable' => 0, + 'add' => '4.4', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'booking_id' => 'booking_id', - 'resource_id' => 'resource_id', - 'config_id' => 'config_id', - 'start' => 'start', - 'end' => 'end', - 'quantity' => 'quantity', - 'note' => 'note', - 'is_cancelled' => 'is_cancelled', - 'is_deleted' => 'is_deleted', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be imported * - * @access public - * return array - * @static + * @param bool $prefix + * + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_slot'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_slot', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of fields that can be exported * - * @access public - * return array - * @static + * @param bool $prefix + * + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_slot'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_slot', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = [ + 'index_is_cancelled' => [ + 'name' => 'index_is_cancelled', + 'field' => [ + 0 => 'is_cancelled', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_slot::0::is_cancelled', + ], + 'index_is_deleted' => [ + 'name' => 'index_is_deleted', + 'field' => [ + 0 => 'is_deleted', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_slot::0::is_deleted', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/CRM/Booking/DAO/SubSlot.php b/CRM/Booking/DAO/SubSlot.php index 6612e059..d15f79e9 100644 --- a/CRM/Booking/DAO/SubSlot.php +++ b/CRM/Booking/DAO/SubSlot.php @@ -1,333 +1,329 @@ __table = 'civicrm_booking_sub_slot'; parent::__construct(); } + /** - * return foreign keys and entity references + * Returns localized title of this entity. * - * @static - * @access public - * @return array of CRM_Core_EntityReference + * @param bool $plural + * Whether to return the plural version of the title. */ - static function getReferenceColumns() - { - if (!self::$_links) { - self::$_links = array( - new CRM_Core_EntityReference(self::getTableName() , 'slot_id', 'civicrm_booking_slot', 'id') , - new CRM_Core_EntityReference(self::getTableName() , 'resource_id', 'civicrm_booking_resource', 'id') , - new CRM_Core_EntityReference(self::getTableName() , 'config_id', 'civicrm_booking_resource_config_option', 'id') , - ); + public static function getEntityTitle($plural = FALSE) { + return $plural ? E::ts('Sub Slots') : E::ts('Sub Slot'); + } + + /** + * Returns foreign keys and entity references. + * + * @return array + * [CRM_Core_Reference_Interface] + */ + public static function getReferenceColumns() { + if (!isset(Civi::$statics[__CLASS__]['links'])) { + Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'slot_id', 'civicrm_booking_slot', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'resource_id', 'civicrm_booking_resource', 'id'); + Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'config_id', 'civicrm_booking_resource_config_option', 'id'); + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']); } - return self::$_links; + return Civi::$statics[__CLASS__]['links']; } + /** - * returns all the column names of this table + * Returns all the column names of this table * - * @access public * @return array */ - static function &fields() - { - if (!(self::$_fields)) { - self::$_fields = array( - 'id' => array( + public static function &fields() { + if (!isset(Civi::$statics[__CLASS__]['fields'])) { + Civi::$statics[__CLASS__]['fields'] = [ + 'id' => [ 'name' => 'id', 'type' => CRM_Utils_Type::T_INT, - 'required' => true, - ) , - 'slot_id' => array( + 'required' => TRUE, + 'where' => 'civicrm_booking_sub_slot.id', + 'table_name' => 'civicrm_booking_sub_slot', + 'entity' => 'SubSlot', + 'bao' => 'CRM_Booking_DAO_SubSlot', + 'localizable' => 0, + 'readonly' => TRUE, + 'add' => NULL, + ], + 'slot_id' => [ 'name' => 'slot_id', 'type' => CRM_Utils_Type::T_INT, + 'description' => E::ts('FK to Slot ID'), + 'where' => 'civicrm_booking_sub_slot.slot_id', + 'table_name' => 'civicrm_booking_sub_slot', + 'entity' => 'SubSlot', + 'bao' => 'CRM_Booking_DAO_SubSlot', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_Slot', - ) , - 'resource_id' => array( + 'add' => NULL, + ], + 'resource_id' => [ 'name' => 'resource_id', 'type' => CRM_Utils_Type::T_INT, + 'description' => E::ts('FK to resource ID'), + 'where' => 'civicrm_booking_sub_slot.resource_id', + 'table_name' => 'civicrm_booking_sub_slot', + 'entity' => 'SubSlot', + 'bao' => 'CRM_Booking_DAO_SubSlot', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_Resource', - ) , - 'config_id' => array( + 'add' => NULL, + ], + 'config_id' => [ 'name' => 'config_id', 'type' => CRM_Utils_Type::T_INT, + 'description' => E::ts('FK to resource configuration option ID'), + 'where' => 'civicrm_booking_sub_slot.config_id', + 'table_name' => 'civicrm_booking_sub_slot', + 'entity' => 'SubSlot', + 'bao' => 'CRM_Booking_DAO_SubSlot', + 'localizable' => 0, 'FKClassName' => 'CRM_Booking_DAO_ResourceConfigOption', - ) , - 'time_required' => array( + 'add' => NULL, + ], + 'time_required' => [ 'name' => 'time_required', 'type' => CRM_Utils_Type::T_DATE + CRM_Utils_Type::T_TIME, - 'title' => E::ts('Time Required') , - 'required' => true, - ) , - 'quantity' => array( + 'title' => E::ts('Time Required'), + 'required' => TRUE, + 'where' => 'civicrm_booking_sub_slot.time_required', + 'table_name' => 'civicrm_booking_sub_slot', + 'entity' => 'SubSlot', + 'bao' => 'CRM_Booking_DAO_SubSlot', + 'localizable' => 0, + 'add' => NULL, + ], + 'quantity' => [ 'name' => 'quantity', 'type' => CRM_Utils_Type::T_INT, - 'title' => E::ts('Quantity') , - 'required' => true, - ) , - 'note' => array( + 'title' => E::ts('Quantity'), + 'required' => TRUE, + 'where' => 'civicrm_booking_sub_slot.quantity', + 'table_name' => 'civicrm_booking_sub_slot', + 'entity' => 'SubSlot', + 'bao' => 'CRM_Booking_DAO_SubSlot', + 'localizable' => 0, + 'add' => NULL, + ], + 'note' => [ 'name' => 'note', 'type' => CRM_Utils_Type::T_TEXT, - 'title' => E::ts('Note') , - ) , - 'is_cancelled' => array( + 'title' => E::ts('Note'), + 'where' => 'civicrm_booking_sub_slot.note', + 'table_name' => 'civicrm_booking_sub_slot', + 'entity' => 'SubSlot', + 'bao' => 'CRM_Booking_DAO_SubSlot', + 'localizable' => 0, + 'add' => NULL, + ], + 'is_cancelled' => [ 'name' => 'is_cancelled', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('SubSlot is cancelled') , - 'import' => true, + 'title' => E::ts('SubSlot is cancelled'), + 'import' => TRUE, 'where' => 'civicrm_booking_sub_slot.is_cancelled', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - 'is_deleted' => array( + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_sub_slot', + 'entity' => 'SubSlot', + 'bao' => 'CRM_Booking_DAO_SubSlot', + 'localizable' => 0, + 'add' => '4.4', + ], + 'is_deleted' => [ 'name' => 'is_deleted', 'type' => CRM_Utils_Type::T_BOOLEAN, - 'title' => E::ts('SubSlot is in the Trash') , - 'import' => true, + 'title' => E::ts('SubSlot is in the Trash'), + 'import' => TRUE, 'where' => 'civicrm_booking_sub_slot.is_deleted', - 'headerPattern' => '', - 'dataPattern' => '', - 'export' => true, - ) , - ); + 'export' => TRUE, + 'default' => '0', + 'table_name' => 'civicrm_booking_sub_slot', + 'entity' => 'SubSlot', + 'bao' => 'CRM_Booking_DAO_SubSlot', + 'localizable' => 0, + 'add' => '4.4', + ], + ]; + CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']); } - return self::$_fields; + return Civi::$statics[__CLASS__]['fields']; } + /** - * Returns an array containing, for each field, the arary key used for that - * field in self::$_fields. + * Return a mapping from field-name to the corresponding key (as used in fields()). * - * @access public * @return array + * Array(string $name => string $uniqueName). */ - static function &fieldKeys() - { - if (!(self::$_fieldKeys)) { - self::$_fieldKeys = array( - 'id' => 'id', - 'slot_id' => 'slot_id', - 'resource_id' => 'resource_id', - 'config_id' => 'config_id', - 'time_required' => 'time_required', - 'quantity' => 'quantity', - 'note' => 'note', - 'is_cancelled' => 'is_cancelled', - 'is_deleted' => 'is_deleted', - ); + public static function &fieldKeys() { + if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) { + Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields())); } - return self::$_fieldKeys; + return Civi::$statics[__CLASS__]['fieldKeys']; } + /** - * returns the names of this table + * Returns the names of this table * - * @access public - * @static * @return string */ - static function getTableName() - { + public static function getTableName() { return self::$_tableName; } + /** - * returns if this table needs to be logged + * Returns if this table needs to be logged * - * @access public - * @return boolean + * @return bool */ - function getLog() - { + public function getLog() { return self::$_log; } + /** - * returns the list of fields that can be imported + * Returns the list of fields that can be imported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &import($prefix = false) - { - if (!(self::$_import)) { - self::$_import = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('import', $field)) { - if ($prefix) { - self::$_import['booking_sub_slot'] = & $fields[$name]; - } else { - self::$_import[$name] = & $fields[$name]; - } - } - } - } - return self::$_import; + public static function &import($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'booking_sub_slot', $prefix, []); + return $r; } + /** - * returns the list of fields that can be exported + * Returns the list of fields that can be exported + * + * @param bool $prefix * - * @access public - * return array - * @static + * @return array */ - static function &export($prefix = false) - { - if (!(self::$_export)) { - self::$_export = array(); - $fields = self::fields(); - foreach($fields as $name => $field) { - if (CRM_Utils_Array::value('export', $field)) { - if ($prefix) { - self::$_export['booking_sub_slot'] = & $fields[$name]; - } else { - self::$_export[$name] = & $fields[$name]; - } - } - } - } - return self::$_export; + public static function &export($prefix = FALSE) { + $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'booking_sub_slot', $prefix, []); + return $r; + } + + /** + * Returns the list of indices + * + * @param bool $localize + * + * @return array + */ + public static function indices($localize = TRUE) { + $indices = [ + 'index_is_cancelled' => [ + 'name' => 'index_is_cancelled', + 'field' => [ + 0 => 'is_cancelled', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_sub_slot::0::is_cancelled', + ], + 'index_is_deleted' => [ + 'name' => 'index_is_deleted', + 'field' => [ + 0 => 'is_deleted', + ], + 'localizable' => FALSE, + 'sig' => 'civicrm_booking_sub_slot::0::is_deleted', + ], + ]; + return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices; } + } diff --git a/sql/auto_install.sql b/sql/auto_install.sql new file mode 100644 index 00000000..2d025223 --- /dev/null +++ b/sql/auto_install.sql @@ -0,0 +1,383 @@ +-- +--------------------------------------------------------------------+ +-- | Copyright CiviCRM LLC. All rights reserved. | +-- | | +-- | This work is published under the GNU AGPLv3 license with some | +-- | permitted exceptions and without any warranty. For full license | +-- | and copyright information, see https://civicrm.org/licensing | +-- +--------------------------------------------------------------------+ +-- +-- Generated from schema.tpl +-- DO NOT EDIT. Generated by CRM_Core_CodeGen +-- + +-- +--------------------------------------------------------------------+ +-- | Copyright CiviCRM LLC. All rights reserved. | +-- | | +-- | This work is published under the GNU AGPLv3 license with some | +-- | permitted exceptions and without any warranty. For full license | +-- | and copyright information, see https://civicrm.org/licensing | +-- +--------------------------------------------------------------------+ +-- +-- Generated from drop.tpl +-- DO NOT EDIT. Generated by CRM_Core_CodeGen +-- +-- /******************************************************* +-- * +-- * Clean up the existing tables +-- * +-- *******************************************************/ + +SET FOREIGN_KEY_CHECKS=0; + +DROP TABLE IF EXISTS `civicrm_booking_resource_criteria`; +DROP TABLE IF EXISTS `civicrm_booking_resource_config_option`; +DROP TABLE IF EXISTS `civicrm_booking_resource`; +DROP TABLE IF EXISTS `civicrm_booking_adhoc_charges`; +DROP TABLE IF EXISTS `civicrm_booking_sub_slot`; +DROP TABLE IF EXISTS `civicrm_booking_slot`; +DROP TABLE IF EXISTS `civicrm_booking_resource_config_set`; +DROP TABLE IF EXISTS `civicrm_booking_payment`; +DROP TABLE IF EXISTS `civicrm_booking_cancellation`; +DROP TABLE IF EXISTS `civicrm_booking_config`; +DROP TABLE IF EXISTS `civicrm_booking`; +DROP TABLE IF EXISTS `civicrm_booking_adhoc_charges_item`; + +SET FOREIGN_KEY_CHECKS=1; +-- /******************************************************* +-- * +-- * Create new tables +-- * +-- *******************************************************/ + +-- /******************************************************* +-- * +-- * civicrm_booking_adhoc_charges_item +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_adhoc_charges_item` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `name` varchar(255) NOT NULL , + `label` varchar(255) NOT NULL , + `price` decimal(20,2) NOT NULL , + `weight` int unsigned NOT NULL , + `is_active` tinyint DEFAULT 1 , + `is_deleted` tinyint DEFAULT 0 +, + PRIMARY KEY (`id`) + + , INDEX `index_is_active`( + is_active + ) + , INDEX `index_is_deleted`( + is_deleted + ) + + +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `primary_contact_id` int unsigned NOT NULL COMMENT 'FK to Contact ID', + `secondary_contact_id` int unsigned NULL COMMENT 'FK to Contact ID', + `title` varchar(255) NOT NULL , + `status_id` int unsigned NOT NULL COMMENT 'The status associated with this booking. Implicit FK to option_value row in booking status option_group.', + `booking_date` datetime NOT NULL , + `start_date` datetime NOT NULL , + `end_date` datetime NOT NULL , + `po_number` varchar(255) NOT NULL , + `total_amount` decimal(20,2) NOT NULL COMMENT 'Total amount of this booking calculated from slots,sub slots, ad-hoc charges and discount amount', + `description` varchar(255) , + `note` text , + `adhoc_charges_note` text , + `participants_estimate` varchar(255) , + `participants_actual` varchar(255) , + `discount_amount` decimal(20,2) , + `is_deleted` tinyint DEFAULT 0 , + `created_by` int unsigned NOT NULL , + `created_date` datetime NOT NULL , + `updated_by` int unsigned NOT NULL , + `updated_date` datetime NOT NULL +, + PRIMARY KEY (`id`) + + , INDEX `index_is_deleted`( + is_deleted + ) + +, CONSTRAINT FK_civicrm_booking_primary_contact_id FOREIGN KEY (`primary_contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_secondary_contact_id FOREIGN KEY (`secondary_contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_config +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_config` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `domain_id` int unsigned , + `day_start_at` time NOT NULL , + `day_end_at` time NOT NULL , + `time_period` int NOT NULL , + `log_confirmation_email` tinyint NOT NULL DEFAULT 0 COMMENT 'Create an activity record againt contact for conformation emails', + `unlimited_resource_time_config` tinyint NOT NULL DEFAULT 1 COMMENT 'Only allow unlimited resources to be booked within time span of the parent limited resource booking', + `cc_email_address` varchar(255) , + `bcc_email_address` varchar(255) , + `slot_new_colour` varchar(10) , + `slot_being_edited_colour` varchar(10) , + `slot_booked_colour` varchar(10) , + `slot_provisional_colour` varchar(10) , + `slot_unavailable_colour` varchar(10) +, + PRIMARY KEY (`id`) + + + +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_cancellation +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_cancellation` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `booking_id` int unsigned COMMENT 'FK to Booking', + `cancellation_date` datetime NOT NULL , + `cancellation_fee` decimal(20,2) NOT NULL , + `additional_fee` decimal(20,2) , + `comment` text +, + PRIMARY KEY (`id`) + + +, CONSTRAINT FK_civicrm_booking_cancellation_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_payment +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_payment` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `booking_id` int unsigned NOT NULL COMMENT 'Foreign key to the booking id for this payment.', + `contribution_id` int unsigned NOT NULL COMMENT 'Foreign key to the contribution for this payment.' +, + PRIMARY KEY (`id`) + + +, CONSTRAINT FK_civicrm_booking_payment_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_payment_contribution_id FOREIGN KEY (`contribution_id`) REFERENCES `civicrm_contribution`(`id`) ON DELETE CASCADE +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_resource_config_set +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_resource_config_set` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `title` varchar(255) NOT NULL , + `weight` int unsigned NOT NULL , + `is_active` tinyint DEFAULT 1 , + `is_deleted` tinyint DEFAULT 0 +, + PRIMARY KEY (`id`) + + , INDEX `index_is_active`( + is_active + ) + , INDEX `index_is_deleted`( + is_deleted + ) + + +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_slot +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_slot` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `booking_id` int unsigned NOT NULL COMMENT 'FK to Booking ID', + `resource_id` int unsigned NOT NULL COMMENT 'FK to resource ID', + `config_id` int unsigned COMMENT 'FK to resource configuration option ID', + `start` datetime NOT NULL , + `end` datetime NOT NULL , + `quantity` int NOT NULL , + `note` text , + `is_cancelled` tinyint DEFAULT 0 , + `is_deleted` tinyint DEFAULT 0 +, + PRIMARY KEY (`id`) + + , INDEX `index_is_cancelled`( + is_cancelled + ) + , INDEX `index_is_deleted`( + is_deleted + ) + +, CONSTRAINT FK_civicrm_booking_slot_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_slot_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_slot_config_id FOREIGN KEY (`config_id`) REFERENCES `civicrm_booking_resource_config_option`(`id`) ON DELETE CASCADE +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_sub_slot +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_sub_slot` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `slot_id` int unsigned COMMENT 'FK to Slot ID', + `resource_id` int unsigned COMMENT 'FK to resource ID', + `config_id` int unsigned COMMENT 'FK to resource configuration option ID', + `time_required` datetime NOT NULL , + `quantity` int NOT NULL , + `note` text , + `is_cancelled` tinyint DEFAULT 0 , + `is_deleted` tinyint DEFAULT 0 +, + PRIMARY KEY (`id`) + + , INDEX `index_is_cancelled`( + is_cancelled + ) + , INDEX `index_is_deleted`( + is_deleted + ) + +, CONSTRAINT FK_civicrm_booking_sub_slot_slot_id FOREIGN KEY (`slot_id`) REFERENCES `civicrm_booking_slot`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_sub_slot_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_sub_slot_config_id FOREIGN KEY (`config_id`) REFERENCES `civicrm_booking_resource_config_option`(`id`) ON DELETE CASCADE +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_adhoc_charges +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_adhoc_charges` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `booking_id` int unsigned NOT NULL COMMENT 'FK to Booking ID', + `item_id` int unsigned NOT NULL COMMENT 'FK to Item ID', + `quantity` int NOT NULL , + `is_cancelled` tinyint DEFAULT 0 , + `is_deleted` tinyint DEFAULT 0 +, + PRIMARY KEY (`id`) + + , INDEX `index_is_cancelled`( + is_cancelled + ) + , INDEX `index_is_deleted`( + is_deleted + ) + +, CONSTRAINT FK_civicrm_booking_adhoc_charges_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_adhoc_charges_item_id FOREIGN KEY (`item_id`) REFERENCES `civicrm_booking_adhoc_charges_item`(`id`) ON DELETE CASCADE +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_resource +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_resource` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `set_id` int unsigned COMMENT 'FK to resource configuration option set', + `label` varchar(255) NOT NULL , + `description` varchar(255) , + `type_id` varchar(512) NOT NULL COMMENT 'The type associated with this resource. Implicit FK to option_value row in booking_resource_type option_group.', + `location_id` varchar(512) COMMENT 'The location associated with this resource. Implicit FK to option_value row in booking_resource_location option_group.', + `weight` int NOT NULL , + `is_unlimited` tinyint NOT NULL DEFAULT 0 , + `is_active` tinyint DEFAULT 1 , + `is_deleted` tinyint DEFAULT 0 +, + PRIMARY KEY (`id`) + + , INDEX `index_is_unlimited`( + is_unlimited + ) + , INDEX `index_is_active`( + is_active + ) + , INDEX `index_is_deleted`( + is_deleted + ) + +, CONSTRAINT FK_civicrm_booking_resource_set_id FOREIGN KEY (`set_id`) REFERENCES `civicrm_booking_resource_config_set`(`id`) ON DELETE CASCADE +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_resource_config_option +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_resource_config_option` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `set_id` int unsigned NOT NULL COMMENT 'Foreign key to the resource set for this option.', + `label` varchar(255) NOT NULL , + `price` decimal(20,2) NOT NULL , + `max_size` varchar(255) , + `unit_id` varchar(512) COMMENT 'The unit associated with this config option. Implicit FK to option_value row in booking_size_unit option_group.', + `weight` int unsigned NOT NULL , + `is_active` tinyint DEFAULT 1 , + `is_deleted` tinyint DEFAULT 0 +, + PRIMARY KEY (`id`) + + , INDEX `index_is_active`( + is_active + ) + , INDEX `index_is_deleted`( + is_deleted + ) + +, CONSTRAINT FK_civicrm_booking_resource_config_option_set_id FOREIGN KEY (`set_id`) REFERENCES `civicrm_booking_resource_config_set`(`id`) ON DELETE CASCADE +) ; + +-- /******************************************************* +-- * +-- * civicrm_booking_resource_criteria +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking_resource_criteria` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `resource_id` int unsigned NOT NULL COMMENT 'Foreign key to the resoure for this resource criteria.', + `criteria_id` varchar(512) NOT NULL COMMENT 'Foreign key to the resource criteria option group.' +, + PRIMARY KEY (`id`) + + +, CONSTRAINT FK_civicrm_booking_resource_criteria_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE +) ; + + \ No newline at end of file diff --git a/sql/auto_uninstall.sql b/sql/auto_uninstall.sql new file mode 100644 index 00000000..b6bb62bd --- /dev/null +++ b/sql/auto_uninstall.sql @@ -0,0 +1,33 @@ +-- +--------------------------------------------------------------------+ +-- | Copyright CiviCRM LLC. All rights reserved. | +-- | | +-- | This work is published under the GNU AGPLv3 license with some | +-- | permitted exceptions and without any warranty. For full license | +-- | and copyright information, see https://civicrm.org/licensing | +-- +--------------------------------------------------------------------+ +-- +-- Generated from drop.tpl +-- DO NOT EDIT. Generated by CRM_Core_CodeGen +-- +-- /******************************************************* +-- * +-- * Clean up the existing tables +-- * +-- *******************************************************/ + +SET FOREIGN_KEY_CHECKS=0; + +DROP TABLE IF EXISTS `civicrm_booking_resource_criteria`; +DROP TABLE IF EXISTS `civicrm_booking_resource_config_option`; +DROP TABLE IF EXISTS `civicrm_booking_resource`; +DROP TABLE IF EXISTS `civicrm_booking_adhoc_charges`; +DROP TABLE IF EXISTS `civicrm_booking_sub_slot`; +DROP TABLE IF EXISTS `civicrm_booking_slot`; +DROP TABLE IF EXISTS `civicrm_booking_resource_config_set`; +DROP TABLE IF EXISTS `civicrm_booking_payment`; +DROP TABLE IF EXISTS `civicrm_booking_cancellation`; +DROP TABLE IF EXISTS `civicrm_booking_config`; +DROP TABLE IF EXISTS `civicrm_booking`; +DROP TABLE IF EXISTS `civicrm_booking_adhoc_charges_item`; + +SET FOREIGN_KEY_CHECKS=1; \ No newline at end of file From ff5ce6d0519dd8a956651be91e97350dab65bb35 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 24 Jun 2021 13:51:59 +0200 Subject: [PATCH 47/51] Remove old file that is replaced with the auto_install sql task --- sql/civibooking_install.sql | 352 ------------------------------------ 1 file changed, 352 deletions(-) delete mode 100644 sql/civibooking_install.sql diff --git a/sql/civibooking_install.sql b/sql/civibooking_install.sql deleted file mode 100644 index 45fa3160..00000000 --- a/sql/civibooking_install.sql +++ /dev/null @@ -1,352 +0,0 @@ -SET FOREIGN_KEY_CHECKS=0; - -DROP TABLE IF EXISTS `civicrm_booking_payment`; -DROP TABLE IF EXISTS `civicrm_booking_sub_slot`; -DROP TABLE IF EXISTS `civicrm_booking_slot`; -DROP TABLE IF EXISTS `civicrm_booking_resource_criteria`; -DROP TABLE IF EXISTS `civicrm_booking_resource_config_option`; -DROP TABLE IF EXISTS `civicrm_booking_resource`; -DROP TABLE IF EXISTS `civicrm_booking_adhoc_charges`; -DROP TABLE IF EXISTS `civicrm_booking_resource_config_set`; -DROP TABLE IF EXISTS `civicrm_booking_cancellation`; -DROP TABLE IF EXISTS `civicrm_booking_config`; -DROP TABLE IF EXISTS `civicrm_booking`; -DROP TABLE IF EXISTS `civicrm_booking_adhoc_charges_item`; - - -SET FOREIGN_KEY_CHECKS=1; - - --- /******************************************************* --- * --- * civicrm_booking_adhoc_charges_item --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_adhoc_charges_item` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `name` varchar(255) NOT NULL , - `label` varchar(255) NOT NULL , - `price` decimal(20,2) NOT NULL , - `weight` int unsigned NOT NULL , - `is_active` tinyint DEFAULT 1 , - `is_deleted` tinyint DEFAULT 0 -, - PRIMARY KEY ( `id` ) - - , INDEX `index_is_active`( - is_active - ) - , INDEX `index_is_deleted`( - is_deleted - ) - - -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `primary_contact_id` int unsigned NOT NULL COMMENT 'FK to Contact ID', - `secondary_contact_id` int unsigned NULL COMMENT 'FK to Contact ID', - `title` varchar(255) NOT NULL , - `status_id` int unsigned NOT NULL COMMENT 'The status associated with this booking. Implicit FK to option_value row in booking status option_group.', - `booking_date` datetime NOT NULL , - `start_date` datetime NOT NULL , - `end_date` datetime NOT NULL , - `po_number` varchar(255) NOT NULL , - `total_amount` decimal(20,2) NOT NULL COMMENT 'Total amount of this booking calculated from slots,sub slots, ad-hoc charges and discount amount', - `description` varchar(255) , - `note` text , - `adhoc_charges_note` text , - `participants_estimate` varchar(255) , - `participants_actual` varchar(255) , - `discount_amount` decimal(20,2) , - `is_deleted` tinyint DEFAULT 0 , - `created_by` int unsigned NOT NULL , - `created_date` datetime NOT NULL , - `updated_by` int unsigned NOT NULL , - `updated_date` datetime NOT NULL -, - PRIMARY KEY ( `id` ) - - , INDEX `index_is_deleted`( - is_deleted - ) - -, CONSTRAINT FK_civicrm_booking_primary_contact_id FOREIGN KEY (`primary_contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_secondary_contact_id FOREIGN KEY (`secondary_contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking_config --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_config` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `domain_id` int unsigned , - `day_start_at` time NOT NULL , - `day_end_at` time NOT NULL , - `time_period` int NOT NULL , - `log_confirmation_email` tinyint NOT NULL DEFAULT 0 COMMENT 'Create an activity record againt contact for conformation emails', - `unlimited_resource_time_config` tinyint NOT NULL DEFAULT 1 COMMENT 'Only allow unlimited resources to be booked within time span of the parent limited resource booking', - `cc_email_address` varchar(255) , - `bcc_email_address` varchar(255) , - `slot_new_colour` varchar(10) , - `slot_being_edited_colour` varchar(10) , - `slot_booked_colour` varchar(10) , - `slot_provisional_colour` varchar(10) , - `slot_unavailable_colour` varchar(10) -, - PRIMARY KEY ( `id` ) - - - -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking_cancellation --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_cancellation` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `booking_id` int unsigned COMMENT 'FK to Booking', - `cancellation_date` datetime NOT NULL , - `cancellation_fee` decimal(20,2) NOT NULL , - `additional_fee` decimal(20,2) , - `comment` text -, - PRIMARY KEY ( `id` ) - - -, CONSTRAINT FK_civicrm_booking_cancellation_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking_resource_config_set --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_resource_config_set` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `title` varchar(255) NOT NULL , - `weight` int unsigned NOT NULL , - `is_active` tinyint DEFAULT 1 , - `is_deleted` tinyint DEFAULT 0 -, - PRIMARY KEY ( `id` ) - - , INDEX `index_is_active`( - is_active - ) - , INDEX `index_is_deleted`( - is_deleted - ) - - -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - - --- /******************************************************* --- * --- * civicrm_booking_adhoc_charges --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_adhoc_charges` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `booking_id` int unsigned NOT NULL COMMENT 'FK to Booking ID', - `item_id` int unsigned NOT NULL COMMENT 'FK to Item ID', - `quantity` int NOT NULL , - `is_cancelled` tinyint DEFAULT 0 , - `is_deleted` tinyint DEFAULT 0 -, - PRIMARY KEY ( `id` ) - - , INDEX `index_is_cancelled`( - is_cancelled - ) - , INDEX `index_is_deleted`( - is_deleted - ) - -, CONSTRAINT FK_civicrm_booking_adhoc_charges_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_adhoc_charges_item_id FOREIGN KEY (`item_id`) REFERENCES `civicrm_booking_adhoc_charges_item`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking_resource --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_resource` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `set_id` int unsigned COMMENT 'FK to resource configuration option set', - `label` varchar(255) NOT NULL , - `description` varchar(255) , - `type_id` varchar(512) NOT NULL COMMENT 'The type associated with this resource. Implicit FK to option_value row in booking_resource_type option_group.', - `location_id` varchar(512) COMMENT 'The location associated with this resource. Implicit FK to option_value row in booking_resource_location option_group.', - `weight` int NOT NULL , - `is_unlimited` tinyint NOT NULL DEFAULT 0 , - `is_active` tinyint DEFAULT 1 , - `is_deleted` tinyint DEFAULT 0 -, - PRIMARY KEY ( `id` ) - - , INDEX `index_is_unlimited`( - is_unlimited - ) - , INDEX `index_is_active`( - is_active - ) - , INDEX `index_is_deleted`( - is_deleted - ) - -, CONSTRAINT FK_civicrm_booking_resource_set_id FOREIGN KEY (`set_id`) REFERENCES `civicrm_booking_resource_config_set`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking_resource_config_option --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_resource_config_option` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `set_id` int unsigned NOT NULL COMMENT 'Foreign key to the resource set for this option.', - `label` varchar(255) NOT NULL , - `price` decimal(20,2) NOT NULL , - `max_size` varchar(255) , - `unit_id` varchar(512) COMMENT 'The unit associated with this config option. Implicit FK to option_value row in booking_size_unit option_group.', - `weight` int unsigned NOT NULL , - `is_active` tinyint DEFAULT 1 , - `is_deleted` tinyint DEFAULT 0 -, - PRIMARY KEY ( `id` ) - - , INDEX `index_is_active`( - is_active - ) - , INDEX `index_is_deleted`( - is_deleted - ) - -, CONSTRAINT FK_civicrm_booking_resource_config_option_set_id FOREIGN KEY (`set_id`) REFERENCES `civicrm_booking_resource_config_set`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking_resource_criteria --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_resource_criteria` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `resource_id` int unsigned NOT NULL COMMENT 'Foreign key to the resoure for this resource criteria.', - `criteria_id` varchar(512) NOT NULL COMMENT 'Foreign key to the resource criteria option group.' -, - PRIMARY KEY ( `id` ) - - -, CONSTRAINT FK_civicrm_booking_resource_criteria_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking_slot --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_slot` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `booking_id` int unsigned NOT NULL COMMENT 'FK to Booking ID', - `resource_id` int unsigned NOT NULL COMMENT 'FK to resource ID', - `config_id` int unsigned COMMENT 'FK to resource configuration option ID', - `start` datetime NOT NULL , - `end` datetime NOT NULL , - `quantity` int NOT NULL , - `note` text , - `is_cancelled` tinyint DEFAULT 0 , - `is_deleted` tinyint DEFAULT 0 -, - PRIMARY KEY ( `id` ) - - , INDEX `index_is_cancelled`( - is_cancelled - ) - , INDEX `index_is_deleted`( - is_deleted - ) - -, CONSTRAINT FK_civicrm_booking_slot_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_slot_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_slot_config_id FOREIGN KEY (`config_id`) REFERENCES `civicrm_booking_resource_config_option`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking_sub_slot --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_sub_slot` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `slot_id` int unsigned COMMENT 'FK to Slot ID', - `resource_id` int unsigned COMMENT 'FK to resource ID', - `config_id` int unsigned COMMENT 'FK to resource configuration option ID', - `time_required` datetime NOT NULL , - `quantity` int NOT NULL , - `note` text , - `is_cancelled` tinyint DEFAULT 0 , - `is_deleted` tinyint DEFAULT 0 -, - PRIMARY KEY ( `id` ) - - , INDEX `index_is_cancelled`( - is_cancelled - ) - , INDEX `index_is_deleted`( - is_deleted - ) - -, CONSTRAINT FK_civicrm_booking_sub_slot_slot_id FOREIGN KEY (`slot_id`) REFERENCES `civicrm_booking_slot`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_sub_slot_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_sub_slot_config_id FOREIGN KEY (`config_id`) REFERENCES `civicrm_booking_resource_config_option`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - --- /******************************************************* --- * --- * civicrm_booking_payment --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_payment` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `booking_id` int unsigned NOT NULL COMMENT 'Foreign key to the booking id for this payment.', - `contribution_id` int unsigned NOT NULL COMMENT 'Foreign key to the contribution for this payment.' -, - PRIMARY KEY ( `id` ) - - -, CONSTRAINT FK_civicrm_booking_payment_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_payment_contribution_id FOREIGN KEY (`contribution_id`) REFERENCES `civicrm_contribution`(`id`) ON DELETE CASCADE -) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ; - - From 5a0f3fd6092696710424815432bb3688fb99ed67 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 24 Jun 2021 13:54:07 +0200 Subject: [PATCH 48/51] Remove auto_uninstall steps from civibooking uninstall sql --- sql/civibooking_uninstall.sql | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/sql/civibooking_uninstall.sql b/sql/civibooking_uninstall.sql index 078b2c8a..26aa28fa 100644 --- a/sql/civibooking_uninstall.sql +++ b/sql/civibooking_uninstall.sql @@ -43,19 +43,4 @@ DELETE FROM civicrm_option_group WHERE name = 'booking_cancellation_charges'; DELETE FROM civicrm_option_group WHERE name = 'booking_size_unit'; DELETE FROM civicrm_option_group WHERE name = 'msg_tpl_workflow_booking'; - -DROP TABLE IF EXISTS `civicrm_booking_payment`; -DROP TABLE IF EXISTS `civicrm_booking_sub_slot`; -DROP TABLE IF EXISTS `civicrm_booking_slot`; -DROP TABLE IF EXISTS `civicrm_booking_resource_criteria`; -DROP TABLE IF EXISTS `civicrm_booking_resource_config_option`; -DROP TABLE IF EXISTS `civicrm_booking_resource`; -DROP TABLE IF EXISTS `civicrm_booking_adhoc_charges`; -DROP TABLE IF EXISTS `civicrm_booking_resource_config_set`; -DROP TABLE IF EXISTS `civicrm_booking_cancellation`; -DROP TABLE IF EXISTS `civicrm_booking_config`; -DROP TABLE IF EXISTS `civicrm_booking`; -DROP TABLE IF EXISTS `civicrm_booking_adhoc_charges_item`; - - SET FOREIGN_KEY_CHECKS=1; From f086f829175d65572b8d3b77aa5a3eab24a38cca Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 24 Jun 2021 15:54:54 +0200 Subject: [PATCH 49/51] Remove unnecessary files --- xml/schema/CRM/Booking/files.xml | 17 --------------- xml/schema/Schema.xml | 37 -------------------------------- 2 files changed, 54 deletions(-) delete mode 100644 xml/schema/CRM/Booking/files.xml delete mode 100644 xml/schema/Schema.xml diff --git a/xml/schema/CRM/Booking/files.xml b/xml/schema/CRM/Booking/files.xml deleted file mode 100644 index 9139f566..00000000 --- a/xml/schema/CRM/Booking/files.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - -s - - - - - - - - - diff --git a/xml/schema/Schema.xml b/xml/schema/Schema.xml deleted file mode 100644 index 5d1c59b2..00000000 --- a/xml/schema/Schema.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - -civicrm -utf8 -utf8_unicode_ci -InnoDB -Schema for CiviCRM - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From 06cb6f66a19067e18c8cc33101b584cf5a4f36dd Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 24 Jun 2021 15:55:58 +0200 Subject: [PATCH 50/51] Reorder table creations in install sql to resolve Unknown db error during extension install --- sql/auto_install.sql | 222 +++++++++++++++++++++---------------------- 1 file changed, 110 insertions(+), 112 deletions(-) diff --git a/sql/auto_install.sql b/sql/auto_install.sql index 2d025223..3482e20e 100644 --- a/sql/auto_install.sql +++ b/sql/auto_install.sql @@ -49,6 +49,45 @@ SET FOREIGN_KEY_CHECKS=1; -- * -- *******************************************************/ +-- /******************************************************* +-- * +-- * civicrm_booking +-- * +-- *******************************************************/ +CREATE TABLE `civicrm_booking` ( + + + `id` int unsigned NOT NULL AUTO_INCREMENT , + `primary_contact_id` int unsigned NOT NULL COMMENT 'FK to Contact ID', + `secondary_contact_id` int unsigned NULL COMMENT 'FK to Contact ID', + `title` varchar(255) NOT NULL , + `status_id` int unsigned NOT NULL COMMENT 'The status associated with this booking. Implicit FK to option_value row in booking status option_group.', + `booking_date` datetime NOT NULL , + `start_date` datetime NOT NULL , + `end_date` datetime NOT NULL , + `po_number` varchar(255) NOT NULL , + `total_amount` decimal(20,2) NOT NULL COMMENT 'Total amount of this booking calculated from slots,sub slots, ad-hoc charges and discount amount', + `description` varchar(255) , + `note` text , + `adhoc_charges_note` text , + `participants_estimate` varchar(255) , + `participants_actual` varchar(255) , + `discount_amount` decimal(20,2) , + `is_deleted` tinyint DEFAULT 0 , + `created_by` int unsigned NOT NULL , + `created_date` datetime NOT NULL , + `updated_by` int unsigned NOT NULL , + `updated_date` datetime NOT NULL +, + PRIMARY KEY (`id`) + + , INDEX `index_is_deleted`( + is_deleted + ) + +, CONSTRAINT FK_civicrm_booking_primary_contact_id FOREIGN KEY (`primary_contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_secondary_contact_id FOREIGN KEY (`secondary_contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE +) ; + -- /******************************************************* -- * -- * civicrm_booking_adhoc_charges_item @@ -79,41 +118,29 @@ CREATE TABLE `civicrm_booking_adhoc_charges_item` ( -- /******************************************************* -- * --- * civicrm_booking +-- * civicrm_booking_adhoc_charges -- * -- *******************************************************/ -CREATE TABLE `civicrm_booking` ( +CREATE TABLE `civicrm_booking_adhoc_charges` ( `id` int unsigned NOT NULL AUTO_INCREMENT , - `primary_contact_id` int unsigned NOT NULL COMMENT 'FK to Contact ID', - `secondary_contact_id` int unsigned NULL COMMENT 'FK to Contact ID', - `title` varchar(255) NOT NULL , - `status_id` int unsigned NOT NULL COMMENT 'The status associated with this booking. Implicit FK to option_value row in booking status option_group.', - `booking_date` datetime NOT NULL , - `start_date` datetime NOT NULL , - `end_date` datetime NOT NULL , - `po_number` varchar(255) NOT NULL , - `total_amount` decimal(20,2) NOT NULL COMMENT 'Total amount of this booking calculated from slots,sub slots, ad-hoc charges and discount amount', - `description` varchar(255) , - `note` text , - `adhoc_charges_note` text , - `participants_estimate` varchar(255) , - `participants_actual` varchar(255) , - `discount_amount` decimal(20,2) , - `is_deleted` tinyint DEFAULT 0 , - `created_by` int unsigned NOT NULL , - `created_date` datetime NOT NULL , - `updated_by` int unsigned NOT NULL , - `updated_date` datetime NOT NULL + `booking_id` int unsigned NOT NULL COMMENT 'FK to Booking ID', + `item_id` int unsigned NOT NULL COMMENT 'FK to Item ID', + `quantity` int NOT NULL , + `is_cancelled` tinyint DEFAULT 0 , + `is_deleted` tinyint DEFAULT 0 , PRIMARY KEY (`id`) - , INDEX `index_is_deleted`( + , INDEX `index_is_cancelled`( + is_cancelled + ) + , INDEX `index_is_deleted`( is_deleted ) -, CONSTRAINT FK_civicrm_booking_primary_contact_id FOREIGN KEY (`primary_contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_secondary_contact_id FOREIGN KEY (`secondary_contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE +, CONSTRAINT FK_civicrm_booking_adhoc_charges_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_adhoc_charges_item_id FOREIGN KEY (`item_id`) REFERENCES `civicrm_booking_adhoc_charges_item`(`id`) ON DELETE CASCADE ) ; -- /******************************************************* @@ -212,172 +239,143 @@ CREATE TABLE `civicrm_booking_resource_config_set` ( -- /******************************************************* -- * --- * civicrm_booking_slot +-- * civicrm_booking_resource -- * -- *******************************************************/ -CREATE TABLE `civicrm_booking_slot` ( +CREATE TABLE `civicrm_booking_resource` ( `id` int unsigned NOT NULL AUTO_INCREMENT , - `booking_id` int unsigned NOT NULL COMMENT 'FK to Booking ID', - `resource_id` int unsigned NOT NULL COMMENT 'FK to resource ID', - `config_id` int unsigned COMMENT 'FK to resource configuration option ID', - `start` datetime NOT NULL , - `end` datetime NOT NULL , - `quantity` int NOT NULL , - `note` text , - `is_cancelled` tinyint DEFAULT 0 , + `set_id` int unsigned COMMENT 'FK to resource configuration option set', + `label` varchar(255) NOT NULL , + `description` varchar(255) , + `type_id` varchar(512) NOT NULL COMMENT 'The type associated with this resource. Implicit FK to option_value row in booking_resource_type option_group.', + `location_id` varchar(512) COMMENT 'The location associated with this resource. Implicit FK to option_value row in booking_resource_location option_group.', + `weight` int NOT NULL , + `is_unlimited` tinyint NOT NULL DEFAULT 0 , + `is_active` tinyint DEFAULT 1 , `is_deleted` tinyint DEFAULT 0 , PRIMARY KEY (`id`) - , INDEX `index_is_cancelled`( - is_cancelled + , INDEX `index_is_unlimited`( + is_unlimited + ) + , INDEX `index_is_active`( + is_active ) , INDEX `index_is_deleted`( is_deleted ) -, CONSTRAINT FK_civicrm_booking_slot_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_slot_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_slot_config_id FOREIGN KEY (`config_id`) REFERENCES `civicrm_booking_resource_config_option`(`id`) ON DELETE CASCADE +, CONSTRAINT FK_civicrm_booking_resource_set_id FOREIGN KEY (`set_id`) REFERENCES `civicrm_booking_resource_config_set`(`id`) ON DELETE CASCADE ) ; -- /******************************************************* -- * --- * civicrm_booking_sub_slot +-- * civicrm_booking_resource_config_option -- * -- *******************************************************/ -CREATE TABLE `civicrm_booking_sub_slot` ( +CREATE TABLE `civicrm_booking_resource_config_option` ( `id` int unsigned NOT NULL AUTO_INCREMENT , - `slot_id` int unsigned COMMENT 'FK to Slot ID', - `resource_id` int unsigned COMMENT 'FK to resource ID', - `config_id` int unsigned COMMENT 'FK to resource configuration option ID', - `time_required` datetime NOT NULL , - `quantity` int NOT NULL , - `note` text , - `is_cancelled` tinyint DEFAULT 0 , + `set_id` int unsigned NOT NULL COMMENT 'Foreign key to the resource set for this option.', + `label` varchar(255) NOT NULL , + `price` decimal(20,2) NOT NULL , + `max_size` varchar(255) , + `unit_id` varchar(512) COMMENT 'The unit associated with this config option. Implicit FK to option_value row in booking_size_unit option_group.', + `weight` int unsigned NOT NULL , + `is_active` tinyint DEFAULT 1 , `is_deleted` tinyint DEFAULT 0 , PRIMARY KEY (`id`) - , INDEX `index_is_cancelled`( - is_cancelled + , INDEX `index_is_active`( + is_active ) , INDEX `index_is_deleted`( is_deleted ) -, CONSTRAINT FK_civicrm_booking_sub_slot_slot_id FOREIGN KEY (`slot_id`) REFERENCES `civicrm_booking_slot`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_sub_slot_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_sub_slot_config_id FOREIGN KEY (`config_id`) REFERENCES `civicrm_booking_resource_config_option`(`id`) ON DELETE CASCADE +, CONSTRAINT FK_civicrm_booking_resource_config_option_set_id FOREIGN KEY (`set_id`) REFERENCES `civicrm_booking_resource_config_set`(`id`) ON DELETE CASCADE ) ; -- /******************************************************* -- * --- * civicrm_booking_adhoc_charges +-- * civicrm_booking_resource_criteria -- * -- *******************************************************/ -CREATE TABLE `civicrm_booking_adhoc_charges` ( +CREATE TABLE `civicrm_booking_resource_criteria` ( `id` int unsigned NOT NULL AUTO_INCREMENT , - `booking_id` int unsigned NOT NULL COMMENT 'FK to Booking ID', - `item_id` int unsigned NOT NULL COMMENT 'FK to Item ID', - `quantity` int NOT NULL , - `is_cancelled` tinyint DEFAULT 0 , - `is_deleted` tinyint DEFAULT 0 + `resource_id` int unsigned NOT NULL COMMENT 'Foreign key to the resoure for this resource criteria.', + `criteria_id` varchar(512) NOT NULL COMMENT 'Foreign key to the resource criteria option group.' , PRIMARY KEY (`id`) - , INDEX `index_is_cancelled`( - is_cancelled - ) - , INDEX `index_is_deleted`( - is_deleted - ) - -, CONSTRAINT FK_civicrm_booking_adhoc_charges_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_adhoc_charges_item_id FOREIGN KEY (`item_id`) REFERENCES `civicrm_booking_adhoc_charges_item`(`id`) ON DELETE CASCADE + +, CONSTRAINT FK_civicrm_booking_resource_criteria_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE ) ; -- /******************************************************* -- * --- * civicrm_booking_resource +-- * civicrm_booking_slot -- * -- *******************************************************/ -CREATE TABLE `civicrm_booking_resource` ( +CREATE TABLE `civicrm_booking_slot` ( `id` int unsigned NOT NULL AUTO_INCREMENT , - `set_id` int unsigned COMMENT 'FK to resource configuration option set', - `label` varchar(255) NOT NULL , - `description` varchar(255) , - `type_id` varchar(512) NOT NULL COMMENT 'The type associated with this resource. Implicit FK to option_value row in booking_resource_type option_group.', - `location_id` varchar(512) COMMENT 'The location associated with this resource. Implicit FK to option_value row in booking_resource_location option_group.', - `weight` int NOT NULL , - `is_unlimited` tinyint NOT NULL DEFAULT 0 , - `is_active` tinyint DEFAULT 1 , + `booking_id` int unsigned NOT NULL COMMENT 'FK to Booking ID', + `resource_id` int unsigned NOT NULL COMMENT 'FK to resource ID', + `config_id` int unsigned COMMENT 'FK to resource configuration option ID', + `start` datetime NOT NULL , + `end` datetime NOT NULL , + `quantity` int NOT NULL , + `note` text , + `is_cancelled` tinyint DEFAULT 0 , `is_deleted` tinyint DEFAULT 0 , PRIMARY KEY (`id`) - , INDEX `index_is_unlimited`( - is_unlimited - ) - , INDEX `index_is_active`( - is_active + , INDEX `index_is_cancelled`( + is_cancelled ) , INDEX `index_is_deleted`( is_deleted ) -, CONSTRAINT FK_civicrm_booking_resource_set_id FOREIGN KEY (`set_id`) REFERENCES `civicrm_booking_resource_config_set`(`id`) ON DELETE CASCADE +, CONSTRAINT FK_civicrm_booking_slot_booking_id FOREIGN KEY (`booking_id`) REFERENCES `civicrm_booking`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_slot_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_slot_config_id FOREIGN KEY (`config_id`) REFERENCES `civicrm_booking_resource_config_option`(`id`) ON DELETE CASCADE ) ; -- /******************************************************* -- * --- * civicrm_booking_resource_config_option +-- * civicrm_booking_sub_slot -- * -- *******************************************************/ -CREATE TABLE `civicrm_booking_resource_config_option` ( +CREATE TABLE `civicrm_booking_sub_slot` ( `id` int unsigned NOT NULL AUTO_INCREMENT , - `set_id` int unsigned NOT NULL COMMENT 'Foreign key to the resource set for this option.', - `label` varchar(255) NOT NULL , - `price` decimal(20,2) NOT NULL , - `max_size` varchar(255) , - `unit_id` varchar(512) COMMENT 'The unit associated with this config option. Implicit FK to option_value row in booking_size_unit option_group.', - `weight` int unsigned NOT NULL , - `is_active` tinyint DEFAULT 1 , + `slot_id` int unsigned COMMENT 'FK to Slot ID', + `resource_id` int unsigned COMMENT 'FK to resource ID', + `config_id` int unsigned COMMENT 'FK to resource configuration option ID', + `time_required` datetime NOT NULL , + `quantity` int NOT NULL , + `note` text , + `is_cancelled` tinyint DEFAULT 0 , `is_deleted` tinyint DEFAULT 0 , PRIMARY KEY (`id`) - , INDEX `index_is_active`( - is_active + , INDEX `index_is_cancelled`( + is_cancelled ) , INDEX `index_is_deleted`( is_deleted ) -, CONSTRAINT FK_civicrm_booking_resource_config_option_set_id FOREIGN KEY (`set_id`) REFERENCES `civicrm_booking_resource_config_set`(`id`) ON DELETE CASCADE -) ; - --- /******************************************************* --- * --- * civicrm_booking_resource_criteria --- * --- *******************************************************/ -CREATE TABLE `civicrm_booking_resource_criteria` ( - - - `id` int unsigned NOT NULL AUTO_INCREMENT , - `resource_id` int unsigned NOT NULL COMMENT 'Foreign key to the resoure for this resource criteria.', - `criteria_id` varchar(512) NOT NULL COMMENT 'Foreign key to the resource criteria option group.' -, - PRIMARY KEY (`id`) - - -, CONSTRAINT FK_civicrm_booking_resource_criteria_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE +, CONSTRAINT FK_civicrm_booking_sub_slot_slot_id FOREIGN KEY (`slot_id`) REFERENCES `civicrm_booking_slot`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_sub_slot_resource_id FOREIGN KEY (`resource_id`) REFERENCES `civicrm_booking_resource`(`id`) ON DELETE CASCADE, CONSTRAINT FK_civicrm_booking_sub_slot_config_id FOREIGN KEY (`config_id`) REFERENCES `civicrm_booking_resource_config_option`(`id`) ON DELETE CASCADE ) ; - - \ No newline at end of file From 61e15e18b1666a1fd35a9d71cda79bb7f009d488 Mon Sep 17 00:00:00 2001 From: akosgarai Date: Thu, 24 Jun 2021 16:31:40 +0200 Subject: [PATCH 51/51] Update Booking create api --- api/v3/Booking.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/api/v3/Booking.php b/api/v3/Booking.php index 99255ffb..0639deb6 100644 --- a/api/v3/Booking.php +++ b/api/v3/Booking.php @@ -27,9 +27,7 @@ function _civicrm_api3_booking_create_spec(&$spec) { * @throws API_Exception */ function civicrm_api3_booking_create($params) { - $bookingBAO = CRM_Booking_BAO_Booking::create($params); - _civicrm_api3_object_to_array($bookingBAO, $bookingArray[$bookingBAO->id]); - return civicrm_api3_create_success($bookingArray, $params, 'Booking', 'create'); + return _civicrm_api3_basic_create(_civicrm_api3_get_BAO(__FUNCTION__), $params, 'Booking'); }