<commit_before>/**
 * core/site data store: site info.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { addQueryArgs } from '@wordpress/url';
import queryString from 'query-string';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { STORE_NAME, AMP_MODE_PRIMARY, AMP_MODE_SECONDARY } from './constants';

const { createRegistrySelector } = Data;

function getSiteInfoProperty( propName ) {
	return createRegistrySelector( ( select ) => () => {
		const siteInfo = select( STORE_NAME ).getSiteInfo() || {};
		return siteInfo[ propName ];
	} );
}

// Actions
const RECEIVE_SITE_INFO = 'RECEIVE_SITE_INFO';

export const INITIAL_STATE = {
	siteInfo: undefined,
};

export const actions = {
	/**
	 * Stores site info in the datastore.
	 *
	 * Because this is frequently-accessed data, this is usually sourced
	 * from a global variable (`_googlesitekitSiteData`), set by PHP
	 * in the `before_print` callback for `googlesitekit-datastore-site`.
	 *
	 * @since 1.7.0
	 * @private
	 *
	 * @param {Object} siteInfo Site info, usually supplied via a global variable from PHP.
	 * @return {Object} Redux-style action.
	 */
	receiveSiteInfo( siteInfo ) {
		invariant( siteInfo, 'siteInfo is required.' );

		return {
			payload: { siteInfo },
			type: RECEIVE_SITE_INFO,
		};
	},
};

export const controls = {};

export const reducer = ( state, { payload, type } ) => {
	switch ( type ) {
		case RECEIVE_SITE_INFO: {
			const {
				adminURL,
				ampMode,
				currentEntityURL,
				currentEntityID,
				currentEntityTitle,
				currentEntityType,
				homeURL,
				referenceSiteURL,
				timezone,
				usingProxy,
				siteName,
			} = payload.siteInfo;

			return {
				...state,
				siteInfo: {
					adminURL,
					ampMode,
					currentEntityURL,
					currentEntityID: parseInt( currentEntityID, 10 ),
					currentEntityTitle,
					currentEntityType,
					homeURL,
					referenceSiteURL,
					timezone,
					usingProxy,
					siteName,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getSiteInfo() {
		const registry = yield Data.commonActions.getRegistry();

		if ( registry.select( STORE_NAME ).getSiteInfo() ) {
			return;
		}

		if ( ! global._googlesitekitBaseData || ! global._googlesitekitEntityData ) {
			global.console.error( 'Could not load core/site info.' );
			return;
		}

		const {
			adminURL,
			ampMode,
			homeURL,
			referenceSiteURL,
			timezone,
			usingProxy,
			siteName,
		} = global._googlesitekitBaseData;
		const {
			currentEntityURL,
			currentEntityID,
			currentEntityTitle,
			currentEntityType,
		} = global._googlesitekitEntityData;

		yield actions.receiveSiteInfo( {
			adminURL,
			ampMode,
			currentEntityURL,
			currentEntityID,
			currentEntityTitle,
			currentEntityType,
			homeURL,
			referenceSiteURL,
			timezone,
			usingProxy: !! usingProxy,
			siteName,
		} );
	},
};

export const selectors = {
	/**
	 * Gets all site info from this data store.
	 *
	 * Not intended to be used publicly; this is largely here so other selectors can
	 * request data using the selector/resolver pattern.
	 *
	 * @since 1.7.0
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} Site connection info.
	 */
	getSiteInfo( state ) {
		return state.siteInfo;
	},

	/**
	 * Gets a site's admin URL.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @param {(string|undefined)} page Optional page query argument ( Simple format: 'test-page' or Full format: 'custom.php?page=test-page' ) to add to admin URL. If not provided, the base admin URL is returned.
	 * @param {(Object|undefined)} args Optional additional query arguments to add to admin URL.
	 * @return {(string|undefined)} This site's admin URL.
	 */
	getAdminURL: createRegistrySelector( ( select ) => ( state, page, args = {} ) => {
		const { adminURL } = select( STORE_NAME ).getSiteInfo() || {};

		// Return adminURL if undefined, or if no page supplied.
		if ( adminURL === undefined || page === undefined ) {
			return adminURL;
		}

		const baseURL = ( adminURL[ adminURL.length - 1 ] === '/' ) ? adminURL : `${ adminURL }/`;
		let pageArg = page;
		let phpFile = 'admin.php';

		// If page argument is full format (i.e. 'admin.php?page=google-site-kit'), extract php file and pageArg, returning early with adminURL if no 'page' param found.
		if ( page.indexOf( '.php?' ) !== -1 ) {
			const splitPage = page.split( '?' );
			pageArg = queryString.parse( splitPage.pop() ).page;

			if ( ! pageArg ) {
				return adminURL;
			}

			phpFile = splitPage.shift();
		}

		// Since page should be first query arg, create queryArgs without 'page' to prevent a 'page' in args from overriding it.
		const { page: extraPage, ...queryArgs } = args; // eslint-disable-line no-unused-vars

		return addQueryArgs(
			`${ baseURL }${ phpFile }`,
			{
				page: pageArg,
				...queryArgs,
			}
		);
	} ),

	/**
	 * Gets a site's AMP mode.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} AMP Mode.
	 */
	getAMPMode: getSiteInfoProperty( 'ampMode' ),

	/**
	 * Gets the current entity's ID.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(number|null|undefined)} Current entity's ID, null if there is
	 *                                   none, undefined if not loaded yet.
	 */
	getCurrentEntityID: getSiteInfoProperty( 'currentEntityID' ),

	/**
	 * Gets the current entity's title.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|null|undefined)} Current entity's title, null if there
	 *                                   is none, undefined if not loaded yet.
	 */
	getCurrentEntityTitle: getSiteInfoProperty( 'currentEntityTitle' ),

	/**
	 * Gets the current entity's title.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|null|undefined)} Current entity's type, null if there
	 *                                   is none, undefined if not loaded yet.
	 */
	getCurrentEntityType: getSiteInfoProperty( 'currentEntityType' ),

	/**
	 * Gets the current entity's reference URL.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|null|undefined)} Current entity's URL, null if there is
	 *                                   none, undefined if not loaded yet.
	 */
	getCurrentEntityURL: getSiteInfoProperty( 'currentEntityURL' ),

	/**
	 * Gets a site's homepage URL.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} This site's home URL.
	 */
	getHomeURL: getSiteInfoProperty( 'homeURL' ),

	/**
	 * Gets a site's reference site URL.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} The reference site URL.
	 */
	getReferenceSiteURL: getSiteInfoProperty( 'referenceSiteURL' ),

	/**
	 * Gets proxy setup URL.
	 *
	 * @since n.e.x.t
	 *
	 * @return {string} Proxy setup URL if available, otherwise an empty string.
	 */
	getProxySetupURL: getSiteInfoProperty( 'proxySetupURL' ),

	/**
	 * Gets proxy permissions URL.
	 *
	 * @since n.e.x.t
	 *
	 * @return {string} Proxy permissions URL if available, otherwise an empty string.
	 */
	getProxyPermissionsURL: getSiteInfoProperty( 'proxyPermissionsURL' ),

	/**
	 * Gets the current reference URL to use.
	 *
	 * This selector should be used to get the contextual URL for requesting
	 * URL-specific data from Google APIs.
	 *
	 * If a current entity exists, this will return the same value as the
	 * `getCurrentEntityURL` selector. Otherwise it will fall back to returning
	 * the same value as the `getReferenceSiteURL` selector.
	 *
	 * @since 1.10.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} The current reference URL, or undefined if
	 *                              not loaded yet.
	 */
	getCurrentReferenceURL: createRegistrySelector( ( select ) => () => {
		// Use current entity URL if present or still loading.
		const currentEntityURL = select( STORE_NAME ).getCurrentEntityURL();
		if ( currentEntityURL !== null ) {
			return currentEntityURL;
		}

		// Otherwise fall back to reference site URL.
		return select( STORE_NAME ).getReferenceSiteURL();
	} ),

	/**
	 * Returns true if this site supports AMP.
	 *
	 * @since 1.7.0
	 * @since 1.11.0 Renamed from isAmp to isAMP.
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} `true` if AMP support is enabled, `false` if not. Returns `undefined` if not loaded.
	 */
	isAMP: createRegistrySelector( ( select ) => () => {
		const ampMode = select( STORE_NAME ).getAMPMode();

		if ( ampMode === undefined ) {
			return undefined;
		}

		return !! ampMode;
	} ),

	/**
	 * Checks if the site is in the primary AMP mode.
	 *
	 * @since 1.12.0
	 *
	 * @return {(boolean|undefined)} `true` or `false` if the site is in the primary AMP mode. Returns `undefined` if not loaded.
	 */
	isPrimaryAMP: createRegistrySelector( ( select ) => () => {
		const ampMode = select( STORE_NAME ).getAMPMode();

		if ( ampMode === undefined ) {
			return undefined;
		}

		return ampMode === AMP_MODE_PRIMARY;
	} ),

	/**
	 * Checks if the site is in a secondary AMP mode.
	 *
	 * @since 1.12.0
	 *
	 * @return {(boolean|undefined)} `true` or `false` if the site is in a secondary AMP mode. Returns `undefined` if not loaded.
	 */
	isSecondaryAMP: createRegistrySelector( ( select ) => () => {
		const ampMode = select( STORE_NAME ).getAMPMode();

		if ( ampMode === undefined ) {
			return undefined;
		}

		return ampMode === AMP_MODE_SECONDARY;
	} ),

	/**
	 * Gets a site's timezone.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} The timezone.
	 */
	getTimezone: getSiteInfoProperty( 'timezone' ),

	/**
	 * Returns true if this site is using the proxy service.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(boolean|undefined)} `true` if the proxy service is in use, `false` if not. Returns `undefined` if not loaded.
	 */
	isUsingProxy: getSiteInfoProperty( 'usingProxy' ),

	/**
	 * Gets a site's name.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} The site name.
	 */
	getSiteName: getSiteInfoProperty( 'siteName' ),

};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<commit_msg>Add proxySetupURL and proxyPermissionsURL to site info.<commit_after>/**
 * core/site data store: site info.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * External dependencies
 */
import invariant from 'invariant';
import { addQueryArgs } from '@wordpress/url';
import queryString from 'query-string';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { STORE_NAME, AMP_MODE_PRIMARY, AMP_MODE_SECONDARY } from './constants';

const { createRegistrySelector } = Data;

function getSiteInfoProperty( propName ) {
	return createRegistrySelector( ( select ) => () => {
		const siteInfo = select( STORE_NAME ).getSiteInfo() || {};
		return siteInfo[ propName ];
	} );
}

// Actions
const RECEIVE_SITE_INFO = 'RECEIVE_SITE_INFO';

export const INITIAL_STATE = {
	siteInfo: undefined,
};

export const actions = {
	/**
	 * Stores site info in the datastore.
	 *
	 * Because this is frequently-accessed data, this is usually sourced
	 * from a global variable (`_googlesitekitSiteData`), set by PHP
	 * in the `before_print` callback for `googlesitekit-datastore-site`.
	 *
	 * @since 1.7.0
	 * @private
	 *
	 * @param {Object} siteInfo Site info, usually supplied via a global variable from PHP.
	 * @return {Object} Redux-style action.
	 */
	receiveSiteInfo( siteInfo ) {
		invariant( siteInfo, 'siteInfo is required.' );

		return {
			payload: { siteInfo },
			type: RECEIVE_SITE_INFO,
		};
	},
};

export const controls = {};

export const reducer = ( state, { payload, type } ) => {
	switch ( type ) {
		case RECEIVE_SITE_INFO: {
			const {
				adminURL,
				ampMode,
				currentEntityURL,
				currentEntityID,
				currentEntityTitle,
				currentEntityType,
				proxyPermissionsURL,
				proxySetupURL,
				homeURL,
				referenceSiteURL,
				timezone,
				usingProxy,
				siteName,
			} = payload.siteInfo;

			return {
				...state,
				siteInfo: {
					adminURL,
					ampMode,
					currentEntityURL,
					currentEntityID: parseInt( currentEntityID, 10 ),
					currentEntityTitle,
					currentEntityType,
					proxyPermissionsURL,
					proxySetupURL,
					homeURL,
					referenceSiteURL,
					timezone,
					usingProxy,
					siteName,
				},
			};
		}

		default: {
			return { ...state };
		}
	}
};

export const resolvers = {
	*getSiteInfo() {
		const registry = yield Data.commonActions.getRegistry();

		if ( registry.select( STORE_NAME ).getSiteInfo() ) {
			return;
		}

		if ( ! global._googlesitekitBaseData || ! global._googlesitekitEntityData ) {
			global.console.error( 'Could not load core/site info.' );
			return;
		}

		const {
			adminURL,
			ampMode,
			homeURL,
			proxyPermissionsURL,
			proxySetupURL,
			referenceSiteURL,
			timezone,
			usingProxy,
			siteName,
		} = global._googlesitekitBaseData;
		const {
			currentEntityURL,
			currentEntityID,
			currentEntityTitle,
			currentEntityType,
		} = global._googlesitekitEntityData;

		yield actions.receiveSiteInfo( {
			adminURL,
			ampMode,
			currentEntityURL,
			currentEntityID,
			currentEntityTitle,
			currentEntityType,
			homeURL,
			proxyPermissionsURL,
			proxySetupURL,
			referenceSiteURL,
			timezone,
			usingProxy: !! usingProxy,
			siteName,
		} );
	},
};

export const selectors = {
	/**
	 * Gets all site info from this data store.
	 *
	 * Not intended to be used publicly; this is largely here so other selectors can
	 * request data using the selector/resolver pattern.
	 *
	 * @since 1.7.0
	 * @private
	 *
	 * @param {Object} state Data store's state.
	 * @return {(Object|undefined)} Site connection info.
	 */
	getSiteInfo( state ) {
		return state.siteInfo;
	},

	/**
	 * Gets a site's admin URL.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @param {(string|undefined)} page Optional page query argument ( Simple format: 'test-page' or Full format: 'custom.php?page=test-page' ) to add to admin URL. If not provided, the base admin URL is returned.
	 * @param {(Object|undefined)} args Optional additional query arguments to add to admin URL.
	 * @return {(string|undefined)} This site's admin URL.
	 */
	getAdminURL: createRegistrySelector( ( select ) => ( state, page, args = {} ) => {
		const { adminURL } = select( STORE_NAME ).getSiteInfo() || {};

		// Return adminURL if undefined, or if no page supplied.
		if ( adminURL === undefined || page === undefined ) {
			return adminURL;
		}

		const baseURL = ( adminURL[ adminURL.length - 1 ] === '/' ) ? adminURL : `${ adminURL }/`;
		let pageArg = page;
		let phpFile = 'admin.php';

		// If page argument is full format (i.e. 'admin.php?page=google-site-kit'), extract php file and pageArg, returning early with adminURL if no 'page' param found.
		if ( page.indexOf( '.php?' ) !== -1 ) {
			const splitPage = page.split( '?' );
			pageArg = queryString.parse( splitPage.pop() ).page;

			if ( ! pageArg ) {
				return adminURL;
			}

			phpFile = splitPage.shift();
		}

		// Since page should be first query arg, create queryArgs without 'page' to prevent a 'page' in args from overriding it.
		const { page: extraPage, ...queryArgs } = args; // eslint-disable-line no-unused-vars

		return addQueryArgs(
			`${ baseURL }${ phpFile }`,
			{
				page: pageArg,
				...queryArgs,
			}
		);
	} ),

	/**
	 * Gets a site's AMP mode.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} AMP Mode.
	 */
	getAMPMode: getSiteInfoProperty( 'ampMode' ),

	/**
	 * Gets the current entity's ID.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(number|null|undefined)} Current entity's ID, null if there is
	 *                                   none, undefined if not loaded yet.
	 */
	getCurrentEntityID: getSiteInfoProperty( 'currentEntityID' ),

	/**
	 * Gets the current entity's title.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|null|undefined)} Current entity's title, null if there
	 *                                   is none, undefined if not loaded yet.
	 */
	getCurrentEntityTitle: getSiteInfoProperty( 'currentEntityTitle' ),

	/**
	 * Gets the current entity's title.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|null|undefined)} Current entity's type, null if there
	 *                                   is none, undefined if not loaded yet.
	 */
	getCurrentEntityType: getSiteInfoProperty( 'currentEntityType' ),

	/**
	 * Gets the current entity's reference URL.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|null|undefined)} Current entity's URL, null if there is
	 *                                   none, undefined if not loaded yet.
	 */
	getCurrentEntityURL: getSiteInfoProperty( 'currentEntityURL' ),

	/**
	 * Gets a site's homepage URL.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} This site's home URL.
	 */
	getHomeURL: getSiteInfoProperty( 'homeURL' ),

	/**
	 * Gets a site's reference site URL.
	 *
	 * @since 1.7.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} The reference site URL.
	 */
	getReferenceSiteURL: getSiteInfoProperty( 'referenceSiteURL' ),

	/**
	 * Gets proxy setup URL.
	 *
	 * @since n.e.x.t
	 *
	 * @return {string} Proxy setup URL if available, otherwise an empty string.
	 */
	getProxySetupURL: getSiteInfoProperty( 'proxySetupURL' ),

	/**
	 * Gets proxy permissions URL.
	 *
	 * @since n.e.x.t
	 *
	 * @return {string} Proxy permissions URL if available, otherwise an empty string.
	 */
	getProxyPermissionsURL: getSiteInfoProperty( 'proxyPermissionsURL' ),

	/**
	 * Gets the current reference URL to use.
	 *
	 * This selector should be used to get the contextual URL for requesting
	 * URL-specific data from Google APIs.
	 *
	 * If a current entity exists, this will return the same value as the
	 * `getCurrentEntityURL` selector. Otherwise it will fall back to returning
	 * the same value as the `getReferenceSiteURL` selector.
	 *
	 * @since 1.10.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} The current reference URL, or undefined if
	 *                              not loaded yet.
	 */
	getCurrentReferenceURL: createRegistrySelector( ( select ) => () => {
		// Use current entity URL if present or still loading.
		const currentEntityURL = select( STORE_NAME ).getCurrentEntityURL();
		if ( currentEntityURL !== null ) {
			return currentEntityURL;
		}

		// Otherwise fall back to reference site URL.
		return select( STORE_NAME ).getReferenceSiteURL();
	} ),

	/**
	 * Returns true if this site supports AMP.
	 *
	 * @since 1.7.0
	 * @since 1.11.0 Renamed from isAmp to isAMP.
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} `true` if AMP support is enabled, `false` if not. Returns `undefined` if not loaded.
	 */
	isAMP: createRegistrySelector( ( select ) => () => {
		const ampMode = select( STORE_NAME ).getAMPMode();

		if ( ampMode === undefined ) {
			return undefined;
		}

		return !! ampMode;
	} ),

	/**
	 * Checks if the site is in the primary AMP mode.
	 *
	 * @since 1.12.0
	 *
	 * @return {(boolean|undefined)} `true` or `false` if the site is in the primary AMP mode. Returns `undefined` if not loaded.
	 */
	isPrimaryAMP: createRegistrySelector( ( select ) => () => {
		const ampMode = select( STORE_NAME ).getAMPMode();

		if ( ampMode === undefined ) {
			return undefined;
		}

		return ampMode === AMP_MODE_PRIMARY;
	} ),

	/**
	 * Checks if the site is in a secondary AMP mode.
	 *
	 * @since 1.12.0
	 *
	 * @return {(boolean|undefined)} `true` or `false` if the site is in a secondary AMP mode. Returns `undefined` if not loaded.
	 */
	isSecondaryAMP: createRegistrySelector( ( select ) => () => {
		const ampMode = select( STORE_NAME ).getAMPMode();

		if ( ampMode === undefined ) {
			return undefined;
		}

		return ampMode === AMP_MODE_SECONDARY;
	} ),

	/**
	 * Gets a site's timezone.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} The timezone.
	 */
	getTimezone: getSiteInfoProperty( 'timezone' ),

	/**
	 * Returns true if this site is using the proxy service.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(boolean|undefined)} `true` if the proxy service is in use, `false` if not. Returns `undefined` if not loaded.
	 */
	isUsingProxy: getSiteInfoProperty( 'usingProxy' ),

	/**
	 * Gets a site's name.
	 *
	 * @since 1.9.0
	 *
	 * @param {Object} state Data store's state.
	 * @return {(string|undefined)} The site name.
	 */
	getSiteName: getSiteInfoProperty( 'siteName' ),

};

export default {
	INITIAL_STATE,
	actions,
	controls,
	reducer,
	resolvers,
	selectors,
};
<|endoftext|>


<commit_before><commit_msg>Rename enquiry_type to opportunity_type in MultiSelect dialog (#12517)<commit_after><|endoftext|>


<commit_before>'use strict';

const defaultByteLength = 1024 * 8;
const charArray = [];

/**
 * IOBuffer
 * @constructor
 * @param {undefined|number|ArrayBuffer|DataView|IOBuffer} data - The data to construct the IOBuffer with.
 * If it's a number it will initialize the buffer with the number as the buffer's length
 * If it's undefined it will initialize the buffer with a default length of 8 Kb
 * If its an ArrayBuffer, a typed array or an IOBuffer instance, it will create a copy of the buffer
 * @param {object} options - An option object
 * @param {number} [option.offset=0] - copy the input data ignoring the first n bytes
 * @property {ArrayBuffer} buffer - Reference to the internal ArrayBuffer object
 * @property {number} length - Byte length of the internal ArrayBuffer
 * @property {number} offset - The current offset of the buffer's pointer
 */
class IOBuffer {
    constructor(data, options) {
        options = options || {};
        var dataIsGiven = false;
        if (data === undefined) {
            data = defaultByteLength;
        }
        if (typeof data === 'number') {
            data = new ArrayBuffer(data);
        } else {
            dataIsGiven = true;
            this._lastWrittenByte = data.byteLength;
        }

        let length = data.byteLength;
        const offset = options.offset ? options.offset>>>0 : 0;
        if (data.buffer) {
            length = data.byteLength - offset;
            if (data.byteLength !== data.buffer.byteLength) { // Node.js buffer from pool
                data = data.buffer.slice(data.byteOffset + offset, data.byteOffset + data.byteLength);
            } else if (offset) {
                data = data.buffer.slice(offset);
            } else {
                data = data.buffer;
            }
        }
        if(dataIsGiven) {
            this._lastWrittenByte = length;
        } else {
            this._lastWrittenByte = 0;
        }
        this.buffer = data;
        this.length = length;
        this.byteLength = length;
        this.byteOffset = 0;
        this.offset = 0;
        this.littleEndian = true;
        this._data = new DataView(this.buffer);
        this._increment = length || defaultByteLength;
        this._mark = 0;
        this._marks = [];
    }

    /**
     * Checks if the memory allocated to the buffer is sufficient to store more bytes after the offset
     * @param {number} [byteLength=1] The needed memory in bytes
     * @returns {boolean} Returns true if there is sufficient space and false otherwise
     */
    available(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        return (this.offset + byteLength) <= this.length;
    }

    /**
     * Check if little-endian mode is used for reading and writing multi-byte values
     * @returns {boolean} Returns true if little-endian mode is used, false otherwise
     */
    isLittleEndian() {
        return this.littleEndian;
    }

    /**
     * Set little-endian mode for reading and writing multi-byte values
     * @returns {IOBuffer}
     */
    setLittleEndian() {
        this.littleEndian = true;
        return this;
    }

    /**
     * Check if big-endian mode is used for reading and writing multi-byte values
     * @returns {boolean} Returns true if big-endian mode is used, false otherwise
     */
    isBigEndian() {
        return !this.littleEndian;
    }

    /**
     * Switches to big-endian mode for reading and writing multi-byte values
     * @returns {IOBuffer}
     */
    setBigEndian() {
        this.littleEndian = false;
        return this;
    }

    /**
     * Move the pointer n bytes forward
     * @param {number} n
     * @returns {IOBuffer}
     */
    skip(n) {
        if (n === undefined) n = 1;
        this.offset += n;
        return this;
    }

    /**
     * Move the pointer to the given offset
     * @param {number} offset
     * @returns {IOBuffer}
     */
    seek(offset) {
        this.offset = offset;
        return this;
    }

    /**
     * Store the current pointer offset.
     * @see {@link IOBuffer#reset}
     * @returns {IOBuffer}
     */
    mark() {
        this._mark = this.offset;
        return this;
    }

    /**
     * Move the pointer back to the last pointer offset set by mark
     * @see {@link IOBuffer#mark}
     * @returns {IOBuffer}
     */
    reset() {
        this.offset = this._mark;
        return this;
    }

    /**
     * Push the current pointer offset to the mark stack
     * @see {@link IOBuffer#popMark}
     * @returns {IOBuffer}
     */
    pushMark() {
        this._marks.push(this.offset);
        return this;
    }

    /**
     * Pop the last pointer offset from the mark stack, and set the current pointer offset to the popped value
     * @see {@link IOBuffer#pushMark}
     * @returns {IOBuffer}
     */
    popMark() {
        const offset = this._marks.pop();
        if(offset === undefined) throw new Error('Mark stack empty');
        this.seek(offset);
        return this;
    }

    /**
     * Move the pointer offset back to 0
     * @returns {IOBuffer}
     */
    rewind() {
        this.offset = 0;
        return this;
    }

    /**
     * Make sure the buffer has sufficient memory to write a given byteLength at the current pointer offset
     * If the buffer's memory is insufficient, this method will double the internal buffer's length
     * @param {number} byteLength
     * @returns {IOBuffer}
     */
    ensureAvailable(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        if (!this.available(byteLength)) {
            const newIncrement = this._increment + this._increment;
            this._increment = newIncrement;
            const newLength = this.length + newIncrement;
            const newArray = new Uint8Array(newLength);
            newArray.set(new Uint8Array(this.buffer));
            this.buffer = newArray.buffer;
            this.length = newLength;
            this._data = new DataView(this.buffer);
        }
        return this;
    }

    /**
     * Read a byte and return false if the byte's value is 0, or true otherwise
     * Moves pointer forward
     * @returns {boolean}
     */
    readBoolean() {
        return this.readUint8() !== 0;
    }

    /**
     * Read a signed 8-bit integer and move pointer forward
     * @returns {number}
     */
    readInt8() {
        return this._data.getInt8(this.offset++);
    }

    /**
     * Read an unsigned 8-bit integer and move pointer forward
     * @returns {number}
     */
    readUint8() {
        return this._data.getUint8(this.offset++);
    }

    /**
     * Alias for {@link IOBuffer#readUint8}
     * @returns {number}
     */
    readByte() {
        return this.readUint8();
    }

    /**
     * Read n bytes and move pointer forward.
     * @param n
     * @returns {Uint8Array}
     */
    readBytes(n) {
        if (n === undefined) n = 1;
        var bytes = new Uint8Array(n);
        for (var i = 0; i < n; i++) {
            bytes[i] = this.readByte();
        }
        return bytes;
    }

    /**
     * Read a 16-bit signed integer and move pointer forward
     * @returns {number}
     */
    readInt16() {
        var value = this._data.getInt16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 16-bit unsigned integer and move pointer forward
     * @returns {number}
     */
    readUint16() {
        var value = this._data.getUint16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 32-bit signed integer and move pointer forward
     * @returns {number}
     */
    readInt32() {
        var value = this._data.getInt32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit unsigned integer and move pointer forward
     * @returns {number}
     */
    readUint32() {
        var value = this._data.getUint32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit floating number and move pointer forward
     * @returns {number}
     */
    readFloat32() {
        var value = this._data.getFloat32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 64-bit floating number and move pointer forward
     * @returns {number}
     */
    readFloat64() {
        var value = this._data.getFloat64(this.offset, this.littleEndian);
        this.offset += 8;
        return value;
    }

    /**
     * Read 1-byte ascii character and move pointer forward
     * @returns {string}
     */
    readChar() {
        return String.fromCharCode(this.readInt8());
    }

    /**
     * Read n 1-byte ascii characters and move pointer forward
     * @param n
     * @returns {string}
     */
    readChars(n) {
        if (n === undefined) n = 1;
        charArray.length = n;
        for (var i = 0; i < n; i++) {
            charArray[i] = this.readChar();
        }
        return charArray.join('');
    }

    /**
     * Write 0xff if the passed value is truthy, 0x00 otherwise
     * @param {any} value
     * @returns {IOBuffer}
     */
    writeBoolean(value) {
        this.writeUint8(value ? 0xff : 0x00);
        return this;
    }

    /**
     * Write value as an 8-bit signed integer
     * @param value
     * @returns {IOBuffer}
     */
    writeInt8(value) {
        this.ensureAvailable(1);
        this._data.setInt8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 8-bit unsigned integer
     * @param value
     * @returns {IOBuffer}
     */
    writeUint8(value) {
        this.ensureAvailable(1);
        this._data.setUint8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * An alias for {@link IOBuffer#writeUint8}
     * @param value
     * @returns {IOBuffer}
     */
    writeByte(value) {
        return this.writeUint8(value);
    }

    /**
     * Write bytes
     * @param {Array|Uint8Array} bytes
     * @returns {IOBuffer}
     */
    writeBytes(bytes) {
        this.ensureAvailable(bytes.length);
        for (var i = 0; i < bytes.length; i++) {
            this._data.setUint8(this.offset++, bytes[i]);
        }
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as an 16-bit signed integer
     * @param value
     * @returns {IOBuffer}
     */
    writeInt16(value) {
        this.ensureAvailable(2);
        this._data.setInt16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 16-bit unsigned integer
     * @param value
     * @returns {IOBuffer}
     */
    writeUint16(value) {
        this.ensureAvailable(2);
        this._data.setUint16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit signed integer at the current pointer offset
     * @param value
     * @returns {IOBuffer}
     */
    writeInt32(value) {
        this.ensureAvailable(4);
        this._data.setInt32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit unsigned integer at the current pointer offset
     * @param value - The value to set
     * @returns {IOBuffer}
     */
    writeUint32(value) {
        this.ensureAvailable(4);
        this._data.setUint32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit floating number at the current pointer offset
     * @param value - The value to set
     * @returns {IOBuffer}
     */
    writeFloat32(value) {
        this.ensureAvailable(4);
        this._data.setFloat32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 64-bit floating number at the current pointer offset
     * @param value
     * @returns {IOBuffer}
     */
    writeFloat64(value) {
        this.ensureAvailable(8);
        this._data.setFloat64(this.offset, value, this.littleEndian);
        this.offset += 8;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write the charCode of the passed string's first character to the current pointer offset
     * @param {string} str - The character to set
     * @returns {IOBuffer}
     */
    writeChar(str) {
        return this.writeUint8(str.charCodeAt(0));
    }

    /**
     * Write the charCodes of the passed string's characters to the current pointer offset
     * @param str
     * @returns {IOBuffer}
     */
    writeChars(str) {
        for (var i = 0; i < str.length; i++) {
            this.writeUint8(str.charCodeAt(i));
        }
        return this;
    }

    /**
     * Export a view of the internal buffer. The view spans from offset 0 to the largest written offset
     * @returns {Uint8Array}
     */
    toArray() {
        return new Uint8Array(this.buffer, 0, this._lastWrittenByte);
    }

    /**
     * Update the last written byte offset
     * @private
     */
    _updateLastWrittenByte() {
        if(this.offset > this._lastWrittenByte) {
            this._lastWrittenByte = this.offset;
        }
    }
}

module.exports = IOBuffer;
<commit_msg>refactor: never copy input buffer<commit_after>'use strict';

const defaultByteLength = 1024 * 8;
const charArray = [];

/**
 * IOBuffer
 * @constructor
 * @param {undefined|number|ArrayBuffer|DataView|IOBuffer} data - The data to construct the IOBuffer with.
 * If it's a number it will initialize the buffer with the number as the buffer's length
 * If it's undefined it will initialize the buffer with a default length of 8 Kb
 * If its an ArrayBuffer, a typed array or an IOBuffer instance, it will create a copy of the buffer
 * @param {object} options - An option object
 * @param {number} [option.offset=0] - copy the input data ignoring the first n bytes
 * @property {ArrayBuffer} buffer - Reference to the internal ArrayBuffer object
 * @property {number} length - Byte length of the internal ArrayBuffer
 * @property {number} offset - The current offset of the buffer's pointer
 */
class IOBuffer {
    constructor(data, options) {
        options = options || {};
        var dataIsGiven = false;
        if (data === undefined) {
            data = defaultByteLength;
        }
        if (typeof data === 'number') {
            data = new ArrayBuffer(data);
        } else {
            dataIsGiven = true;
            this._lastWrittenByte = data.byteLength;
        }

        let byteLength = data.byteLength;
        const offset = options.offset ? options.offset>>>0 : 0;
        let dvOffset = offset;
        if (data.buffer) {
            byteLength = data.byteLength - offset;
            if (data.byteLength !== data.buffer.byteLength) {
                dvOffset = data.byteOffset + offset;
            }
            data = data.buffer;
        }
        if (dataIsGiven) {
            this._lastWrittenByte = byteLength;
        } else {
            this._lastWrittenByte = 0;
        }
        this.buffer = data;
        this.length = byteLength;
        this.byteLength = byteLength;
        this.byteOffset = dvOffset;
        this.offset = 0;
        this.littleEndian = true;
        this._data = new DataView(this.buffer, dvOffset, byteLength);
        this._increment = byteLength || defaultByteLength;
        this._mark = 0;
        this._marks = [];
    }

    /**
     * Checks if the memory allocated to the buffer is sufficient to store more bytes after the offset
     * @param {number} [byteLength=1] The needed memory in bytes
     * @returns {boolean} Returns true if there is sufficient space and false otherwise
     */
    available(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        return (this.offset + byteLength) <= this.length;
    }

    /**
     * Check if little-endian mode is used for reading and writing multi-byte values
     * @returns {boolean} Returns true if little-endian mode is used, false otherwise
     */
    isLittleEndian() {
        return this.littleEndian;
    }

    /**
     * Set little-endian mode for reading and writing multi-byte values
     * @returns {IOBuffer}
     */
    setLittleEndian() {
        this.littleEndian = true;
        return this;
    }

    /**
     * Check if big-endian mode is used for reading and writing multi-byte values
     * @returns {boolean} Returns true if big-endian mode is used, false otherwise
     */
    isBigEndian() {
        return !this.littleEndian;
    }

    /**
     * Switches to big-endian mode for reading and writing multi-byte values
     * @returns {IOBuffer}
     */
    setBigEndian() {
        this.littleEndian = false;
        return this;
    }

    /**
     * Move the pointer n bytes forward
     * @param {number} n
     * @returns {IOBuffer}
     */
    skip(n) {
        if (n === undefined) n = 1;
        this.offset += n;
        return this;
    }

    /**
     * Move the pointer to the given offset
     * @param {number} offset
     * @returns {IOBuffer}
     */
    seek(offset) {
        this.offset = offset;
        return this;
    }

    /**
     * Store the current pointer offset.
     * @see {@link IOBuffer#reset}
     * @returns {IOBuffer}
     */
    mark() {
        this._mark = this.offset;
        return this;
    }

    /**
     * Move the pointer back to the last pointer offset set by mark
     * @see {@link IOBuffer#mark}
     * @returns {IOBuffer}
     */
    reset() {
        this.offset = this._mark;
        return this;
    }

    /**
     * Push the current pointer offset to the mark stack
     * @see {@link IOBuffer#popMark}
     * @returns {IOBuffer}
     */
    pushMark() {
        this._marks.push(this.offset);
        return this;
    }

    /**
     * Pop the last pointer offset from the mark stack, and set the current pointer offset to the popped value
     * @see {@link IOBuffer#pushMark}
     * @returns {IOBuffer}
     */
    popMark() {
        const offset = this._marks.pop();
        if(offset === undefined) throw new Error('Mark stack empty');
        this.seek(offset);
        return this;
    }

    /**
     * Move the pointer offset back to 0
     * @returns {IOBuffer}
     */
    rewind() {
        this.offset = 0;
        return this;
    }

    /**
     * Make sure the buffer has sufficient memory to write a given byteLength at the current pointer offset
     * If the buffer's memory is insufficient, this method will double the internal buffer's length
     * @param {number} byteLength
     * @returns {IOBuffer}
     */
    ensureAvailable(byteLength) {
        if (byteLength === undefined) byteLength = 1;
        if (!this.available(byteLength)) {
            const newIncrement = this._increment + this._increment;
            this._increment = newIncrement;
            const newLength = this.length + newIncrement;
            const newArray = new Uint8Array(newLength);
            newArray.set(new Uint8Array(this.buffer));
            this.buffer = newArray.buffer;
            this.length = newLength;
            this._data = new DataView(this.buffer);
        }
        return this;
    }

    /**
     * Read a byte and return false if the byte's value is 0, or true otherwise
     * Moves pointer forward
     * @returns {boolean}
     */
    readBoolean() {
        return this.readUint8() !== 0;
    }

    /**
     * Read a signed 8-bit integer and move pointer forward
     * @returns {number}
     */
    readInt8() {
        return this._data.getInt8(this.offset++);
    }

    /**
     * Read an unsigned 8-bit integer and move pointer forward
     * @returns {number}
     */
    readUint8() {
        return this._data.getUint8(this.offset++);
    }

    /**
     * Alias for {@link IOBuffer#readUint8}
     * @returns {number}
     */
    readByte() {
        return this.readUint8();
    }

    /**
     * Read n bytes and move pointer forward.
     * @param n
     * @returns {Uint8Array}
     */
    readBytes(n) {
        if (n === undefined) n = 1;
        var bytes = new Uint8Array(n);
        for (var i = 0; i < n; i++) {
            bytes[i] = this.readByte();
        }
        return bytes;
    }

    /**
     * Read a 16-bit signed integer and move pointer forward
     * @returns {number}
     */
    readInt16() {
        var value = this._data.getInt16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 16-bit unsigned integer and move pointer forward
     * @returns {number}
     */
    readUint16() {
        var value = this._data.getUint16(this.offset, this.littleEndian);
        this.offset += 2;
        return value;
    }

    /**
     * Read a 32-bit signed integer and move pointer forward
     * @returns {number}
     */
    readInt32() {
        var value = this._data.getInt32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit unsigned integer and move pointer forward
     * @returns {number}
     */
    readUint32() {
        var value = this._data.getUint32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 32-bit floating number and move pointer forward
     * @returns {number}
     */
    readFloat32() {
        var value = this._data.getFloat32(this.offset, this.littleEndian);
        this.offset += 4;
        return value;
    }

    /**
     * Read a 64-bit floating number and move pointer forward
     * @returns {number}
     */
    readFloat64() {
        var value = this._data.getFloat64(this.offset, this.littleEndian);
        this.offset += 8;
        return value;
    }

    /**
     * Read 1-byte ascii character and move pointer forward
     * @returns {string}
     */
    readChar() {
        return String.fromCharCode(this.readInt8());
    }

    /**
     * Read n 1-byte ascii characters and move pointer forward
     * @param n
     * @returns {string}
     */
    readChars(n) {
        if (n === undefined) n = 1;
        charArray.length = n;
        for (var i = 0; i < n; i++) {
            charArray[i] = this.readChar();
        }
        return charArray.join('');
    }

    /**
     * Write 0xff if the passed value is truthy, 0x00 otherwise
     * @param {any} value
     * @returns {IOBuffer}
     */
    writeBoolean(value) {
        this.writeUint8(value ? 0xff : 0x00);
        return this;
    }

    /**
     * Write value as an 8-bit signed integer
     * @param value
     * @returns {IOBuffer}
     */
    writeInt8(value) {
        this.ensureAvailable(1);
        this._data.setInt8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 8-bit unsigned integer
     * @param value
     * @returns {IOBuffer}
     */
    writeUint8(value) {
        this.ensureAvailable(1);
        this._data.setUint8(this.offset++, value);
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * An alias for {@link IOBuffer#writeUint8}
     * @param value
     * @returns {IOBuffer}
     */
    writeByte(value) {
        return this.writeUint8(value);
    }

    /**
     * Write bytes
     * @param {Array|Uint8Array} bytes
     * @returns {IOBuffer}
     */
    writeBytes(bytes) {
        this.ensureAvailable(bytes.length);
        for (var i = 0; i < bytes.length; i++) {
            this._data.setUint8(this.offset++, bytes[i]);
        }
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as an 16-bit signed integer
     * @param value
     * @returns {IOBuffer}
     */
    writeInt16(value) {
        this.ensureAvailable(2);
        this._data.setInt16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write value as a 16-bit unsigned integer
     * @param value
     * @returns {IOBuffer}
     */
    writeUint16(value) {
        this.ensureAvailable(2);
        this._data.setUint16(this.offset, value, this.littleEndian);
        this.offset += 2;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit signed integer at the current pointer offset
     * @param value
     * @returns {IOBuffer}
     */
    writeInt32(value) {
        this.ensureAvailable(4);
        this._data.setInt32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit unsigned integer at the current pointer offset
     * @param value - The value to set
     * @returns {IOBuffer}
     */
    writeUint32(value) {
        this.ensureAvailable(4);
        this._data.setUint32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 32-bit floating number at the current pointer offset
     * @param value - The value to set
     * @returns {IOBuffer}
     */
    writeFloat32(value) {
        this.ensureAvailable(4);
        this._data.setFloat32(this.offset, value, this.littleEndian);
        this.offset += 4;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write a 64-bit floating number at the current pointer offset
     * @param value
     * @returns {IOBuffer}
     */
    writeFloat64(value) {
        this.ensureAvailable(8);
        this._data.setFloat64(this.offset, value, this.littleEndian);
        this.offset += 8;
        this._updateLastWrittenByte();
        return this;
    }

    /**
     * Write the charCode of the passed string's first character to the current pointer offset
     * @param {string} str - The character to set
     * @returns {IOBuffer}
     */
    writeChar(str) {
        return this.writeUint8(str.charCodeAt(0));
    }

    /**
     * Write the charCodes of the passed string's characters to the current pointer offset
     * @param str
     * @returns {IOBuffer}
     */
    writeChars(str) {
        for (var i = 0; i < str.length; i++) {
            this.writeUint8(str.charCodeAt(i));
        }
        return this;
    }

    /**
     * Export a view of the internal buffer. The view spans from offset 0 to the largest written offset
     * @returns {Uint8Array}
     */
    toArray() {
        return new Uint8Array(this.buffer, 0, this._lastWrittenByte);
    }

    /**
     * Update the last written byte offset
     * @private
     */
    _updateLastWrittenByte() {
        if(this.offset > this._lastWrittenByte) {
            this._lastWrittenByte = this.offset;
        }
    }
}

module.exports = IOBuffer;
<|endoftext|>


<commit_before>// External Packages
import React, { Fragment } from 'react'
import { Container } from 'reactstrap'
import { Link } from 'react-router-dom'

// Components
import DCSLink from '/imports/client/ui/components/DCSLink/index.js'
import NavMenu from './NavMenu'
import { TableOfContents } from './TableOfContents'

// Styles and Other
import './styles.scss'
import i18n from '/imports/both/i18n/en/'

const Remarkable = require('remarkable')
const md = new Remarkable()
md.set({
  breaks: true,
  linkify: true
})

const index = (props) => {
  const wpForword = md.render(i18n.Whitepaper.Forword)
  const fullText = i18n.Whitepaper.FullText
  const sectionCount = fullText.length
  const fullTextReversed = [...fullText].reverse()

  return (
    <Container className="mt-5">

      <NavMenu />

Welcome to the 1st Draft!

      <DCSLink badge="true" format="speech-bubble" title="General Discussion" triggerId="forword" display="inline" />

      <TableOfContents />
      <DCSLink badge="true" format="speech-bubble" title="click balloons to comment on section above" triggerId="bal" display="inline" />

      {fullText.map((elem, index) => {
        const text = md.render(elem.fullText)
        // const chapterCode = findChaperCode(fullTextReversed, sectionCount, index)

        return (
          <Fragment key={elem.title}>

              {elem.chapterHeading && elem.title}

            {/* {elem.fullText.match(/[a-z]/i) && <DCSLink badge="true" format="speech-bubble" triggerId={chapterCode} display="inline" />} */}
            {elem.fullText.match(/[a-z]/i) && <DCSLink badge="true" format="speech-bubble" triggerId={elem.code.toLowerCase()} display="inline" />}
            {elem.image && (

                  <Link to={elem.image} target="_blank"></Link>

            )}
          </Fragment>
        )
      })}

    </Container>

  )
}

export default index

function findChaperCode (reversedSectionArray, sectionCount, currentIndex) {
  const subArray = reversedSectionArray.slice(sectionCount - currentIndex - 1)
  return subArray.find(e => e.chapterHeading === true).code
}
<commit_msg>changed intro. removed 1st draft reference<commit_after>// External Packages
import React, { Fragment } from 'react'
import { Container } from 'reactstrap'
import { Link } from 'react-router-dom'

// Components
import DCSLink from '/imports/client/ui/components/DCSLink/index.js'
import NavMenu from './NavMenu'
import { TableOfContents } from './TableOfContents'

// Styles and Other
import './styles.scss'
import i18n from '/imports/both/i18n/en/'

const Remarkable = require('remarkable')
const md = new Remarkable()
md.set({
  breaks: true,
  linkify: true
})

const index = (props) => {
  const wpForword = md.render(i18n.Whitepaper.Forword)
  const fullText = i18n.Whitepaper.FullText
  const sectionCount = fullText.length
  const fullTextReversed = [...fullText].reverse()

  return (
    <Container className="mt-5">

      <NavMenu />

Welcome!

      <DCSLink badge="true" format="speech-bubble" title="General Discussion" triggerId="forword" display="inline" />

      <TableOfContents />
      <DCSLink badge="true" format="speech-bubble" title="click balloons to comment on section above" triggerId="bal" display="inline" />

      {fullText.map((elem, index) => {
        const text = md.render(elem.fullText)
        // const chapterCode = findChaperCode(fullTextReversed, sectionCount, index)

        return (
          <Fragment key={elem.title}>

              {elem.chapterHeading && elem.title}

            {/* {elem.fullText.match(/[a-z]/i) && <DCSLink badge="true" format="speech-bubble" triggerId={chapterCode} display="inline" />} */}
            {elem.fullText.match(/[a-z]/i) && <DCSLink badge="true" format="speech-bubble" triggerId={elem.code.toLowerCase()} display="inline" />}
            {elem.image && (

                  <Link to={elem.image} target="_blank"></Link>

            )}
          </Fragment>
        )
      })}

    </Container>

  )
}

export default index

function findChaperCode (reversedSectionArray, sectionCount, currentIndex) {
  const subArray = reversedSectionArray.slice(sectionCount - currentIndex - 1)
  return subArray.find(e => e.chapterHeading === true).code
}
<|endoftext|>


<commit_before>/* Module: Users -- Manage "known" user accounts and permission flags. */

var Module = require('./Module.js');
var fs = require('fs');
var jsonfile = require('jsonfile');

var PERM_ADMIN = 'administrator';
var PERM_MOD = 'moderator';

class ModUsers extends Module {

    get optionalParams() { return [
        'datafile'
    ]; }

    constructor(name) {
        super('Users', name);

        this._params['datafile'] = 'users.data.json';

        this._userdata = [];
        this._userhandles = {};
        this._permissionProviders = [];
    }

    initialize(envs, mods, moduleRequest) {
        if (!super.initialize(envs, mods, moduleRequest)) return false;

        //Load data

        if (!this.loadUsers()) return false;

        //Register callbacks

        moduleRequest('Commands', (commands) => {

            commands.registerCommand('useradd', {
                args: ["handle"],
                description: "Create a new empty user account with the given handle.",
                types: ["private"],
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                if (this.addUser(args.handle)) {
                    reply("The account " + args.handle + " was successfuly created.");
                } else {
                    reply("There already exists an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('userdel', {
                args: ["handle"],
                description: "Delete an existing user account identified by the given handle.",
                types: ["private"],
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                if (this.delUser(args.handle)) {
                    reply("The account " + args.handle + " was successfully deleted.");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('idadd', {
                args: ["handle", "environment", "idpattern"],
                description: "Add an ID pattern (regex) to authenticate the user account identified by the handle in the specified environment.",
                types: ["private"],
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                args.idpattern = "^" + args.idpattern + "$";
                if (!this._environments[args.environment]) {
                    reply("There is no environment named " + args.environment + " at this time.");
                    return true;
                }

                if (this.addId(args.handle, args.environment, args.idpattern)) {
                    reply("Successfully added the requested pattern to the account identified by " + args.handle + ".");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('iddel', {
                args: ["handle", "environment", "idpattern"],
                description: "Remove an existing ID pattern from a user such that it will no longer authenticate the user account identified by the handle.",
                types: ["private"],
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                args.idpattern = "^" + args.idpattern + "$";

                if (this.delId(args.handle, args.environment, args.idpattern)) {
                    reply("Successfully removed the requested patterm from the account identified by " + args.handle + ".");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('permadd', {
                args: ["handle", "permissions", true],
                minArgs: 2,
                description: "Add one or more permissions to the user account identified by the handle.",
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                if (this.addPerms(args.handle, args.permissions)) {
                    reply("The permissions listed were added to the account identified by " + args.handle + ".");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('permdel', {
                args: ["handle", "permissions", true],
                minArgs: 2,
                description: "Remove one or more permissions from the user account identified by the handle.",
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                if (this.delPerms(args.handle, args.permissions)) {
                    reply("The permissions listed were removed from the account identified by " + args.handle + ".");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('userfind', {
                args: ["environment", "id"],
                description: "List the handles of the user accounts that match the given id and environment.",
                types: ["private"],
                permissions: [PERM_ADMIN, PERM_MOD]
            }, (env, type, userid, command, args, handle, reply) => {

                var handles = this.getHandlesById(args.environment, args.id);
                if (!handles.length) {
                    reply("No handles were found matching the given environment and id.");
                    return true;
                }

                while(handles.length) {
                    var outbound = handles.slice(0, 10);
                    outbound = '"' + outbound.join('","') + '"';
                    reply(outbound);
                    handles = handles.slice(10);
                }

                return true;
            });

            commands.registerCommand('userlist', {
                args: ["perms", true],
                description: "Lists the handles of the user accounts that have the given permissions.",
                types: ["private"],
                permissions: [PERM_ADMIN, PERM_MOD]
            }, (env, type, userid, command, args, handle, reply) => {

                var handles = this.getHandlesByPerms(args.perms);
                if (!handles.length) {
                    reply("No handles were found with the given permission" + (args.perms.length != 1 ? "s" : "") + ".");
                    return true;
                }

                while(handles.length) {
                    var outbound = handles.slice(0, 10);
                    outbound = '"' + outbound.join('","') + '"';
                    reply(outbound);
                    handles = handles.slice(10);
                }

                return true;
            });

            commands.registerCommand('whois', {
                args: ["handle"],
                description: "Describe the user account identified by the handle.",
                types: ["private"],
                permissions: [PERM_ADMIN, PERM_MOD]
            }, (env, type, userid, command, args, handle, reply) => {

                var account = this.getUser(args.handle);
                if (!account) {
                    reply("I could not find an account identified by " + args.handle + "!");
                    return true;
                }

                reply('========== ' + account.handle + ' ==========');

                reply('* ID patterns:');
                if (account.ids) {
                    for (var i = 0; i < account.ids.length; i++) {
                        reply('    {' + account.ids[i].env + '} `' + account.ids[i].idpattern + '`');
                    }
                }

                reply('* Permissions:');
                var perms = account.perms;
                if (perms) {
                    while (perms.length) {
                        var outbound = perms.slice(0, 10);
                        outbound = outbound.join(', ');
                        reply(outbound);
                        perms = perms.slice(10);
                    }
                }

                reply('.');

                return true;
            });

        });

        return true;
    }

    // # Module code below this line #

    //User account manipulation (new accounts only have a handle)

    loadUsers() {
        var datafile = this.param('datafile');

        try {
            fs.accessSync(datafile, fs.F_OK);
        } catch (e) {
            jsonfile.writeFileSync(datafile, []);
        }

        try {
            this._userdata = jsonfile.readFileSync(datafile);
        } catch (e) {
            return false;
        }
        if (!this._userdata) this._userdata = [];

        this._userhandles = {};
        for (let eachuser of this._userdata) {
            this._userhandles[eachuser.handle] = eachuser;
        }

        return true;
    }

    saveUsers() {
        var datafile = this.param('datafile');

        jsonfile.writeFileSync(datafile, this._userdata);
    }

    addUser(handle) {
        if (this._userhandles[handle]) return false;

        var newuser = {
            handle: handle,
            ids: [],
            perms: []
        }
        this._userdata.push(newuser);
        this._userhandles[handle] = newuser;

        this.saveUsers();
        return true;
    }

    delUser(handle) {
        if (!this._userhandles[handle]) return false;

        var i = this._userdata.findIndex(
            (checkuser) => (checkuser.handle == handle)
        );

        if (i > -1) this._userdata.splice(i, 1);
        delete this._userhandles[handle];

        this.saveUsers();
        return true;
    }

    getUser(handle) {
        return this._userhandles[handle];
    }

    //User ID manipulation - Identify a user in a specific environment as being the owner of the account. idpattern matches the environment's authorid/targetid.

    addId(handle, env, idpattern) {
        if (!env || !idpattern) return false;

        var changed = false;
        var chuser = this.getUser(handle);
        if (!chuser) return false;

        if (!chuser.ids.find(
            (id) => (id.env == env && id.idpattern == idpattern)
        )) {
            chuser.ids.push({env: env, idpattern: idpattern});
            changed = true;
        }

        if (changed) this.saveUsers();
        return true;
    }

    delId(handle, env, idpattern) {
        if (!env) return false;

        var changed = false;
        var chuser = this.getUser(handle);
        if (!chuser) return false;

        for (var i = 0; i < chuser.ids.length; i++) {
            if (chuser.ids[i].env != env) continue;
            if (idpattern && chuser.ids[i].idpattern != idpattern) continue;
            chuser.ids.splice(i, 1);
            changed = true;
            i -= 1;
        }

        if (changed) this.saveUsers();
        return true;
    }

    getIds(handle, env) {
        var chuser = this.getUser(handle);
        if (!chuser) return [];

        if (!env) return chuser.ids;

        return chuser.ids.map(
            (item) => (item.env == env ? item.idpattern : null)
        ).filter(
            (idpattern) => (idpattern != null)
        );
    }

    isIdHandle(handle, env, id, strict) {
        if (strict) {
            if (!this._environments[env].idIsSecured(id) || !this._environments[env].idIsAuthenticated(id)) {
                return false;
            }
        }

        var ids = this.getIds(handle, env);

        for (let eachid of ids) {
            if (RegExp(eachid).exec(id)) {
                return true;
            }
        }

        return false;
    }

    getHandlesById(env, id, strict) {
        var result = [];

        for (let eachuser of this._userdata) {
            if (this.isIdHandle(eachuser.handle, env, id, strict)) {
                result.push(eachuser.handle);
            }
        }

        return result;
    }

    //Permission manipulation - A permission is a literal which the user may have or not. Permissions are meaningless unless used by other modules.

    addPerms(handle, perms) {
        if (!perms) return false;

        var changed = false;
        var chuser = this.getUser(handle);
        if (!chuser) return false;

        for (let perm of perms) {
            perm = perm.toLowerCase();
            if (!chuser.perms.find((checkperm) => (checkperm == perm))) {
                chuser.perms.push(perm);
                changed = true;
            }
        }

        if (changed) this.saveUsers();
        return true;
    }

    delPerms(handle, perms) {
        if (!perms) return false;

        var changed = false;
        var chuser = this.getUser(handle);
        if (!chuser) return false;

        for (let perm of perms) {
            perm = perm.toLowerCase();
            var ind = chuser.perms.findIndex((checkperm) => (checkperm == perm));
            if (ind > -1) {
                chuser.perms.splice(ind, 1);
                changed = true;
            }
        }

        if (changed) this.saveUsers();
        return true;
    }

    getPerms(handle) {
        var checkuser = this.getUser(handle);
        if (!checkuser) return [];
        return checkuser.perms;
    }

    hasAllPerms(handle, perms) {
        var checkuser = this.getUser(handle);
        if (!checkuser) return false;
        for (let perm of perms) {
            perm = perm.toLowerCase();
            if (!checkuser.perms.find((checkperm) => (checkperm == perm))) {
                return false;
            }
        }
        return true;
    }

    hasAnyPerm(handle, perms) {
        var checkuser = this.getUser(handle);
        if (!checkuser) return false;
        for (let perm of perms) {
            perm = perm.toLowerCase();
            if (checkuser.perms.find((checkperm) => (checkperm == perm))) {
                return true;
            }
        }
        return false;
    }

    subsetPerms(handle, perms) {
        var checkuser = this.getUser(handle);
        if (!checkuser) return false;
        var subset = [];
        for (let perm of perms) {
            if (checkuser.perms.find((checkperm) => (checkperm == perm.toLowerCase()))) {
                subset.push(perm);
            }
        }
        return subset;
    }

    getHandlesByPerms(perms) {
        var result = [];

        for (let eachuser of this._userdata) {
            if (this.hasAllPerms(eachuser.handle, perms)) {
                result.push(eachuser.handle);
            }
        }

        return result;
    }

    //Programmatic permission providers (not persisted)
    //callback(env, userid, permissions) -- Return a subset of permissions that the user has (empty for none).

    registerPermissionProvider(func, self) {
        console.log('Registering permissions provider. Context: ' + self.constructor.name);
        if (!self) {
            this._permissionProviders.push(func);
        } else {
            this._permissionProviders.push([func, self]);
        }
    }

    testPermissions(env, userid, permissions, requireall, handle) {

        var removeduplicates = {};
        for (let perm of permissions) {
            removeduplicates[perm] = true;
        }

        var ascertained = {};

        //From providers

        for (let provider of this._permissionProviders) {
            let subset = [];
            if (typeof provider == "function") {
                subset = provider.apply(this, [env, userid, permissions]);
            } else {
                subset = provider[0].apply(provider[1], [env, userid, permissions]);
            }
            for (let perm of subset) {
                ascertained[perm] = true;
            }
        }

        //From account

        var handles = this.getHandlesById(env, userid, true);
        if (!handle) {
            handle = (handles.length ? handles[0] : null);
        } else {
            let confirmed = false;
            for (let checkhandle of handles) {
                if (checkhandle == handle) {
                    confirmed = true;
                    break;
                }
            }
            if (!confirmed) handle = null;
        }

        if (handle) {
            for (let perm of this.subsetPerms(handle, permissions)) {
                ascertained[perm] = true;
            }
        }

        //Result

        return (!requireall && Object.keys(ascertained).length
                || requireall && Object.keys(ascertained).length == Object.keys(removeduplicates).length);

    }

}

module.exports = ModUsers;
<commit_msg>Rename user accounts<commit_after>/* Module: Users -- Manage "known" user accounts and permission flags. */

var Module = require('./Module.js');
var fs = require('fs');
var jsonfile = require('jsonfile');

var PERM_ADMIN = 'administrator';
var PERM_MOD = 'moderator';

class ModUsers extends Module {

    get optionalParams() { return [
        'datafile'
    ]; }

    constructor(name) {
        super('Users', name);

        this._params['datafile'] = 'users.data.json';

        this._userdata = [];
        this._userhandles = {};
        this._permissionProviders = [];
    }

    initialize(envs, mods, moduleRequest) {
        if (!super.initialize(envs, mods, moduleRequest)) return false;

        //Load data

        if (!this.loadUsers()) return false;

        //Register callbacks

        moduleRequest('Commands', (commands) => {

            commands.registerCommand('useradd', {
                args: ["handle"],
                description: "Create a new empty user account with the given handle.",
                types: ["private"],
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                if (this.addUser(args.handle)) {
                    reply("The account " + args.handle + " was successfuly created.");
                } else {
                    reply("There already exists an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('userdel', {
                args: ["handle"],
                description: "Delete an existing user account identified by the given handle.",
                types: ["private"],
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                if (this.delUser(args.handle)) {
                    reply("The account " + args.handle + " was successfully deleted.");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            comands.registerCommand('userrename', {
                args: ["fromhandle", "tohandle"],
                description: "Rename an existing account.",
                types: ["private"],
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                if (this.renameUser(args.fromhandle, args.tohandle)) {
                    reply("The account " + args.fromhandle + " was successfuly renamed to " + args.tohandle + ".");
                } else{
                    reply("The account " + args.fromhandle + " could not be renamed.");
                }

                return true;
            });

            commands.registerCommand('idadd', {
                args: ["handle", "environment", "idpattern"],
                description: "Add an ID pattern (regex) to authenticate the user account identified by the handle in the specified environment.",
                types: ["private"],
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                args.idpattern = "^" + args.idpattern + "$";
                if (!this._environments[args.environment]) {
                    reply("There is no environment named " + args.environment + " at this time.");
                    return true;
                }

                if (this.addId(args.handle, args.environment, args.idpattern)) {
                    reply("Successfully added the requested pattern to the account identified by " + args.handle + ".");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('iddel', {
                args: ["handle", "environment", "idpattern"],
                description: "Remove an existing ID pattern from a user such that it will no longer authenticate the user account identified by the handle.",
                types: ["private"],
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                args.idpattern = "^" + args.idpattern + "$";

                if (this.delId(args.handle, args.environment, args.idpattern)) {
                    reply("Successfully removed the requested patterm from the account identified by " + args.handle + ".");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('permadd', {
                args: ["handle", "permissions", true],
                minArgs: 2,
                description: "Add one or more permissions to the user account identified by the handle.",
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                if (this.addPerms(args.handle, args.permissions)) {
                    reply("The permissions listed were added to the account identified by " + args.handle + ".");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('permdel', {
                args: ["handle", "permissions", true],
                minArgs: 2,
                description: "Remove one or more permissions from the user account identified by the handle.",
                permissions: [PERM_ADMIN]
            }, (env, type, userid, command, args, handle, reply) => {

                if (this.delPerms(args.handle, args.permissions)) {
                    reply("The permissions listed were removed from the account identified by " + args.handle + ".");
                } else {
                    reply("I could not find an account identified by " + args.handle + "!");
                }

                return true;
            });

            commands.registerCommand('userfind', {
                args: ["environment", "id"],
                description: "List the handles of the user accounts that match the given id and environment.",
                types: ["private"],
                permissions: [PERM_ADMIN, PERM_MOD]
            }, (env, type, userid, command, args, handle, reply) => {

                var handles = this.getHandlesById(args.environment, args.id);
                if (!handles.length) {
                    reply("No handles were found matching the given environment and id.");
                    return true;
                }

                while(handles.length) {
                    var outbound = handles.slice(0, 10);
                    outbound = '"' + outbound.join('","') + '"';
                    reply(outbound);
                    handles = handles.slice(10);
                }

                return true;
            });

            commands.registerCommand('userlist', {
                args: ["perms", true],
                minArgs: 0,
                description: "Lists the handles of the user accounts that have the given permissions.",
                types: ["private"],
                permissions: [PERM_ADMIN, PERM_MOD]
            }, (env, type, userid, command, args, handle, reply) => {

                var handles = this.getHandlesByPerms(args.perms);
                if (!handles.length) {
                    reply("No handles were found with the given permission" + (args.perms.length != 1 ? "s" : "") + ".");
                    return true;
                }

                while(handles.length) {
                    var outbound = handles.slice(0, 10);
                    outbound = '"' + outbound.join('","') + '"';
                    reply(outbound);
                    handles = handles.slice(10);
                }

                return true;
            });

            commands.registerCommand('whois', {
                args: ["handle"],
                description: "Describe the user account identified by the handle.",
                types: ["private"],
                permissions: [PERM_ADMIN, PERM_MOD]
            }, (env, type, userid, command, args, handle, reply) => {

                var account = this.getUser(args.handle);
                if (!account) {
                    reply("I could not find an account identified by " + args.handle + "!");
                    return true;
                }

                reply('========== ' + account.handle + ' ==========');

                reply('* ID patterns:');
                if (account.ids) {
                    for (var i = 0; i < account.ids.length; i++) {
                        reply('    {' + account.ids[i].env + '} `' + account.ids[i].idpattern + '`');
                    }
                }

                reply('* Permissions:');
                var perms = account.perms;
                if (perms) {
                    while (perms.length) {
                        var outbound = perms.slice(0, 10);
                        outbound = outbound.join(', ');
                        reply(outbound);
                        perms = perms.slice(10);
                    }
                }

                reply('.');

                return true;
            });

        });

        return true;
    }

    // # Module code below this line #

    //User account manipulation (new accounts only have a handle)

    loadUsers() {
        var datafile = this.param('datafile');

        try {
            fs.accessSync(datafile, fs.F_OK);
        } catch (e) {
            jsonfile.writeFileSync(datafile, []);
        }

        try {
            this._userdata = jsonfile.readFileSync(datafile);
        } catch (e) {
            return false;
        }
        if (!this._userdata) this._userdata = [];

        this._userhandles = {};
        for (let eachuser of this._userdata) {
            this._userhandles[eachuser.handle] = eachuser;
        }

        return true;
    }

    saveUsers() {
        var datafile = this.param('datafile');

        jsonfile.writeFileSync(datafile, this._userdata);
    }

    addUser(handle) {
        if (this._userhandles[handle]) return false;

        var newuser = {
            handle: handle,
            ids: [],
            perms: []
        }
        this._userdata.push(newuser);
        this._userhandles[handle] = newuser;

        this.saveUsers();
        return true;
    }

    delUser(handle) {
        if (!this._userhandles[handle]) return false;

        var i = this._userdata.findIndex(
            (checkuser) => (checkuser.handle == handle)
        );

        if (i > -1) this._userdata.splice(i, 1);
        delete this._userhandles[handle];

        this.saveUsers();
        return true;
    }

    getUser(handle) {
        return this._userhandles[handle];
    }

    renameUser(fromhandle, tohandle) {
        var desc = this._userhandles[fromhandle];
        if (!desc) return false;
        if (this._userhandles[tohandle]) return false;

        desc.handle = tohandle;
        delete this._userhandles[fromhandle];
        this._userhandles[tohandle] = desc;

        this.saveUsers();
        return true;
    }

    //User ID manipulation - Identify a user in a specific environment as being the owner of the account. idpattern matches the environment's authorid/targetid.

    addId(handle, env, idpattern) {
        if (!env || !idpattern) return false;

        var changed = false;
        var chuser = this.getUser(handle);
        if (!chuser) return false;

        if (!chuser.ids.find(
            (id) => (id.env == env && id.idpattern == idpattern)
        )) {
            chuser.ids.push({env: env, idpattern: idpattern});
            changed = true;
        }

        if (changed) this.saveUsers();
        return true;
    }

    delId(handle, env, idpattern) {
        if (!env) return false;

        var changed = false;
        var chuser = this.getUser(handle);
        if (!chuser) return false;

        for (var i = 0; i < chuser.ids.length; i++) {
            if (chuser.ids[i].env != env) continue;
            if (idpattern && chuser.ids[i].idpattern != idpattern) continue;
            chuser.ids.splice(i, 1);
            changed = true;
            i -= 1;
        }

        if (changed) this.saveUsers();
        return true;
    }

    getIds(handle, env) {
        var chuser = this.getUser(handle);
        if (!chuser) return [];

        if (!env) return chuser.ids;

        return chuser.ids.map(
            (item) => (item.env == env ? item.idpattern : null)
        ).filter(
            (idpattern) => (idpattern != null)
        );
    }

    isIdHandle(handle, env, id, strict) {
        if (strict) {
            if (!this._environments[env].idIsSecured(id) || !this._environments[env].idIsAuthenticated(id)) {
                return false;
            }
        }

        var ids = this.getIds(handle, env);

        for (let eachid of ids) {
            if (RegExp(eachid).exec(id)) {
                return true;
            }
        }

        return false;
    }

    getHandlesById(env, id, strict) {
        var result = [];

        for (let eachuser of this._userdata) {
            if (this.isIdHandle(eachuser.handle, env, id, strict)) {
                result.push(eachuser.handle);
            }
        }

        return result;
    }

    //Permission manipulation - A permission is a literal which the user may have or not. Permissions are meaningless unless used by other modules.

    addPerms(handle, perms) {
        if (!perms) return false;

        var changed = false;
        var chuser = this.getUser(handle);
        if (!chuser) return false;

        for (let perm of perms) {
            perm = perm.toLowerCase();
            if (!chuser.perms.find((checkperm) => (checkperm == perm))) {
                chuser.perms.push(perm);
                changed = true;
            }
        }

        if (changed) this.saveUsers();
        return true;
    }

    delPerms(handle, perms) {
        if (!perms) return false;

        var changed = false;
        var chuser = this.getUser(handle);
        if (!chuser) return false;

        for (let perm of perms) {
            perm = perm.toLowerCase();
            var ind = chuser.perms.findIndex((checkperm) => (checkperm == perm));
            if (ind > -1) {
                chuser.perms.splice(ind, 1);
                changed = true;
            }
        }

        if (changed) this.saveUsers();
        return true;
    }

    getPerms(handle) {
        var checkuser = this.getUser(handle);
        if (!checkuser) return [];
        return checkuser.perms;
    }

    hasAllPerms(handle, perms) {
        var checkuser = this.getUser(handle);
        if (!checkuser) return false;
        for (let perm of perms) {
            perm = perm.toLowerCase();
            if (!checkuser.perms.find((checkperm) => (checkperm == perm))) {
                return false;
            }
        }
        return true;
    }

    hasAnyPerm(handle, perms) {
        var checkuser = this.getUser(handle);
        if (!checkuser) return false;
        for (let perm of perms) {
            perm = perm.toLowerCase();
            if (checkuser.perms.find((checkperm) => (checkperm == perm))) {
                return true;
            }
        }
        return false;
    }

    subsetPerms(handle, perms) {
        var checkuser = this.getUser(handle);
        if (!checkuser) return false;
        var subset = [];
        for (let perm of perms) {
            if (checkuser.perms.find((checkperm) => (checkperm == perm.toLowerCase()))) {
                subset.push(perm);
            }
        }
        return subset;
    }

    getHandlesByPerms(perms) {
        var result = [];

        for (let eachuser of this._userdata) {
            if (this.hasAllPerms(eachuser.handle, perms)) {
                result.push(eachuser.handle);
            }
        }

        return result;
    }

    //Programmatic permission providers (not persisted)
    //callback(env, userid, permissions) -- Return a subset of permissions that the user has (empty for none).

    registerPermissionProvider(func, self) {
        console.log('Registering permissions provider. Context: ' + self.constructor.name);
        if (!self) {
            this._permissionProviders.push(func);
        } else {
            this._permissionProviders.push([func, self]);
        }
    }

    testPermissions(env, userid, permissions, requireall, handle) {

        var removeduplicates = {};
        for (let perm of permissions) {
            removeduplicates[perm] = true;
        }

        var ascertained = {};

        //From providers

        for (let provider of this._permissionProviders) {
            let subset = [];
            if (typeof provider == "function") {
                subset = provider.apply(this, [env, userid, permissions]);
            } else {
                subset = provider[0].apply(provider[1], [env, userid, permissions]);
            }
            for (let perm of subset) {
                ascertained[perm] = true;
            }
        }

        //From account

        var handles = this.getHandlesById(env, userid, true);
        if (!handle) {
            handle = (handles.length ? handles[0] : null);
        } else {
            let confirmed = false;
            for (let checkhandle of handles) {
                if (checkhandle == handle) {
                    confirmed = true;
                    break;
                }
            }
            if (!confirmed) handle = null;
        }

        if (handle) {
            for (let perm of this.subsetPerms(handle, permissions)) {
                ascertained[perm] = true;
            }
        }

        //Result

        return (!requireall && Object.keys(ascertained).length
                || requireall && Object.keys(ascertained).length == Object.keys(removeduplicates).length);

    }

}

module.exports = ModUsers;
<|endoftext|>