<commit_before>/**
 * User Input Questionnaire.
 *
 * Site Kit by Google, Copyright 2021 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.
 */

/**
 * WordPress dependencies
 */
import { useCallback, Fragment, useEffect, useState, useRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import ProgressBar from '../ProgressBar';
import UserInputQuestionWrapper from './UserInputQuestionWrapper';
import UserInputSelectOptions from './UserInputSelectOptions';
import UserInputKeywords from './UserInputKeywords';
import UserInputPreview from './UserInputPreview';
import {
	USER_INPUT_QUESTIONS_LIST,
	USER_INPUT_QUESTION_ROLE,
	USER_INPUT_QUESTION_POST_FREQUENCY,
	USER_INPUT_QUESTION_GOALS,
	USER_INPUT_QUESTION_HELP_NEEDED,
	USER_INPUT_QUESTION_SEARCH_TERMS,
	getUserInputAnwsers,
} from './util/constants';
import useQueryArg from '../../hooks/useQueryArg';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';
import { CORE_SITE } from '../../googlesitekit/datastore/site/constants';
import { CORE_LOCATION } from '../../googlesitekit/datastore/location/constants';
import { Cell, Row } from '../../material-components';
import { trackEvent } from '../../util';
const { useSelect, useDispatch } = Data;

export default function UserInputQuestionnaire() {
	const steps = [ ...USER_INPUT_QUESTIONS_LIST, 'preview' ];

	const [ activeSlug, setActiveSlug ] = useQueryArg( 'question', steps[ 0 ] );
	const [ shouldScrollToActiveQuestion, setShouldScrollToActiveQuestion ] = useState( false );
	const [ redirectURL ] = useQueryArg( 'redirect_url' );
	const [ single, setSingle ] = useQueryArg( 'single', false );

	const containerRef = useRef();

	const activeSlugIndex = steps.indexOf( activeSlug );
	if ( activeSlugIndex === -1 ) {
		setActiveSlug( steps[ 0 ] );
	}

	const { saveUserInputSettings } = useDispatch( CORE_USER );
	const { navigateTo } = useDispatch( CORE_LOCATION );

	const isNavigating = useSelect( ( select ) => select( CORE_LOCATION ).isNavigating() );
	const dashboardURL = useSelect( ( select ) => select( CORE_SITE ).getAdminURL( 'googlesitekit-dashboard' ) );
	const { isSavingSettings, error, answeredUntilIndex } = useSelect( ( select ) => {
		const userInputSettings = select( CORE_USER ).getUserInputSettings();

		return {
			isSavingSettings: select( CORE_USER ).isSavingUserInputSettings( userInputSettings ),
			error: select( CORE_USER ).getErrorForAction( 'saveUserInputSettings', [] ),
			answeredUntilIndex: USER_INPUT_QUESTIONS_LIST.findIndex( ( question ) => userInputSettings?.[ question ]?.values?.length === 0 ),
		};
	} );

	useEffect( () => {
		if ( answeredUntilIndex === -1 ) {
			return;
		}
		if ( activeSlugIndex > answeredUntilIndex ) {
			setActiveSlug( steps[ answeredUntilIndex ] );
		}
	}, [ answeredUntilIndex, activeSlugIndex ] );

	useEffect( () => {
		if ( activeSlug === 'preview' ) {
			trackEvent( 'user_input', 'summary_view' );
		}
	}, [ activeSlug ] );

	const {
		USER_INPUT_ANSWERS_GOALS,
		USER_INPUT_ANSWERS_HELP_NEEDED,
		USER_INPUT_ANSWERS_POST_FREQUENCY,
		USER_INPUT_ANSWERS_ROLE,
	} = getUserInputAnwsers();

	const isSettings = single === 'settings';

	const next = useCallback( () => {
		trackEvent( 'user_input', 'question_advance', steps[ activeSlugIndex ] );
		setActiveSlug( steps[ activeSlugIndex + 1 ] );
	}, [ activeSlugIndex ] );

	const goTo = useCallback( ( num = 1, singleType = false ) => {
		trackEvent(
			'user_input',
			'summary_edit',
			steps[ num - 1 ],
		);

		// If we're going to a single question to edit it, set the query string here.
		// We can't currently set it in the child component because the useQueryArg hook doesn't update in the parent.
		setSingle( singleType );
		if ( steps.length >= num && num > 0 ) {
			setActiveSlug( steps[ num - 1 ] );
		}
	}, [ activeSlugIndex, isSettings ] );

	const back = useCallback( () => {
		trackEvent( 'user_input', 'question_return', steps[ activeSlugIndex ] );
		setActiveSlug( steps[ activeSlugIndex - 1 ] );
	}, [ activeSlugIndex ] );

	const submitChanges = useCallback( async () => {
		trackEvent(
			'user_input',
			isSettings ? 'question_submit' : 'summary_submit',
			isSettings ? steps[ activeSlugIndex ] : undefined,
		);

		const response = await saveUserInputSettings();
		if ( ! response.error ) {
			const url = new URL( redirectURL || dashboardURL );

			// Here we don't use `addQueryArgs` due to a bug with how it handles hashes
			// See https://github.com/WordPress/gutenberg/issues/16655
			url.searchParams.set( 'notification', 'user_input_success' );

			navigateTo( url.toString() );
		}
	}, [ dashboardURL, isSettings ] );

	const goToPreview = useCallback( () => {
		trackEvent( 'user_input', 'question_update', steps[ activeSlugIndex ] );
		setActiveSlug( steps[ steps.length - 1 ] );
	}, [ activeSlugIndex ] );

	useEffect( () => {
		if ( ! shouldScrollToActiveQuestion ) {
			setShouldScrollToActiveQuestion( true );
			return;
		}

		containerRef.current?.scrollIntoView( { behavior: 'smooth' } );
	}, [ activeSlug ] );

	// Update the callbacks and labels for the questions if the user is editing a *single question*.
	let backCallback = back;
	let nextCallback = next;
	let nextLabel;

	if ( single === 'user-input' ) {
		backCallback = undefined;
		// When the user is editing a single question in the user-input screen send them back to the preview when they click Update.
		nextCallback = goToPreview;
		nextLabel = __( 'Update', 'google-site-kit' );
	} else if ( single === 'settings' ) {
		backCallback = undefined;
		// When the user is editing a single question from the settings screen, submit changes and send them back to the settings pages when they click Submit.
		nextCallback = submitChanges;
		nextLabel = __( 'Submit', 'google-site-kit' );
	}

	const settingsProgress = (
		<ProgressBar
			height={ 0 }
			indeterminate={ false }
			progress={ ( activeSlugIndex + 1 ) / USER_INPUT_QUESTIONS_LIST.length }
		/>
	);

	if ( isSavingSettings || isNavigating ) {
		return (
			<Fragment>
				{ settingsProgress }

					<Row>
						<Cell lgSize={ 12 } mdSize={ 8 } smSize={ 4 }>
							<ProgressBar />
						</Cell>
					</Row>

			</Fragment>
		);
	}

	return (

			{ settingsProgress }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_ROLE ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_ROLE }
					isActive={ activeSlug === USER_INPUT_QUESTION_ROLE }
					questionNumber={ 1 }
					title={ __( 'Which best describes your team/role in relation to this site?', 'google-site-kit' ) }
					description={ __( 'This will help Site Kit show tips that help you specifically in your role.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel }
					error={ error }
				>
					<UserInputSelectOptions
						slug={ USER_INPUT_QUESTION_ROLE }
						options={ USER_INPUT_ANSWERS_ROLE }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_POST_FREQUENCY ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_POST_FREQUENCY }
					isActive={ activeSlug === USER_INPUT_QUESTION_POST_FREQUENCY }
					questionNumber={ 2 }
					title={ __( 'How often do you create new posts for this site?', 'google-site-kit' ) }
					description={ __( 'Based on your answer, Site Kit will suggest new features for your dashboard related to content creation.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel }
					back={ backCallback }
					error={ error }
				>
					<UserInputSelectOptions
						slug={ USER_INPUT_QUESTION_POST_FREQUENCY }
						options={ USER_INPUT_ANSWERS_POST_FREQUENCY }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_GOALS ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_GOALS }
					isActive={ activeSlug === USER_INPUT_QUESTION_GOALS }
					questionNumber={ 3 }
					title={ __( 'What are the goals of this site?', 'google-site-kit' ) }
					description={ __( 'Based on your answer, Site Kit will tailor the metrics you see on your dashboard to help you track how close you’re getting to your specific goals.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel }
					back={ backCallback }
					error={ error }
				>
					<UserInputSelectOptions
						slug={ USER_INPUT_QUESTION_GOALS }
						max={ 2 }
						options={ USER_INPUT_ANSWERS_GOALS }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_HELP_NEEDED ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_HELP_NEEDED }
					isActive={ activeSlug === USER_INPUT_QUESTION_HELP_NEEDED }
					questionNumber={ 4 }
					title={ __( 'What do you need help most with for this site?', 'google-site-kit' ) }
					description={ __( 'Based on your answers, Site Kit will tailor the metrics and advice you see on your dashboard to help you make progress in these areas.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel }
					back={ backCallback }
					error={ error }
				>
					<UserInputSelectOptions
						slug={ USER_INPUT_QUESTION_HELP_NEEDED }
						max={ 3 }
						options={ USER_INPUT_ANSWERS_HELP_NEEDED }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_SEARCH_TERMS ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_SEARCH_TERMS }
					isActive={ activeSlug === USER_INPUT_QUESTION_SEARCH_TERMS }
					questionNumber={ 5 }
					title={ __( 'To help us identify opportunities for your site, enter the top three search terms that best describe your site’s content.', 'google-site-kit' ) }
					description={ __( 'Site Kit will keep you informed if people start finding you in Search for these terms.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel === undefined ? __( 'Preview', 'google-site-kit' ) : nextLabel }
					back={ backCallback }
					error={ error }
					allowEmptyValues
				>
					<UserInputKeywords
						slug={ USER_INPUT_QUESTION_SEARCH_TERMS }
						max={ 3 }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlug === 'preview' && (
				<UserInputPreview
					submitChanges={ submitChanges }
					goTo={ goTo }
					error={ error }
				/>
			) }

	);
}
<commit_msg>Always scroll to top of User Input.<commit_after>/**
 * User Input Questionnaire.
 *
 * Site Kit by Google, Copyright 2021 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.
 */

/**
 * WordPress dependencies
 */
import { useCallback, Fragment, useEffect, useState, useRef } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import ProgressBar from '../ProgressBar';
import UserInputQuestionWrapper from './UserInputQuestionWrapper';
import UserInputSelectOptions from './UserInputSelectOptions';
import UserInputKeywords from './UserInputKeywords';
import UserInputPreview from './UserInputPreview';
import {
	USER_INPUT_QUESTIONS_LIST,
	USER_INPUT_QUESTION_ROLE,
	USER_INPUT_QUESTION_POST_FREQUENCY,
	USER_INPUT_QUESTION_GOALS,
	USER_INPUT_QUESTION_HELP_NEEDED,
	USER_INPUT_QUESTION_SEARCH_TERMS,
	getUserInputAnwsers,
} from './util/constants';
import useQueryArg from '../../hooks/useQueryArg';
import { CORE_USER } from '../../googlesitekit/datastore/user/constants';
import { CORE_SITE } from '../../googlesitekit/datastore/site/constants';
import { CORE_LOCATION } from '../../googlesitekit/datastore/location/constants';
import { Cell, Row } from '../../material-components';
import { trackEvent } from '../../util';
const { useSelect, useDispatch } = Data;

export default function UserInputQuestionnaire() {
	const steps = [ ...USER_INPUT_QUESTIONS_LIST, 'preview' ];

	const [ activeSlug, setActiveSlug ] = useQueryArg( 'question', steps[ 0 ] );
	const [ shouldScrollToActiveQuestion, setShouldScrollToActiveQuestion ] = useState( false );
	const [ redirectURL ] = useQueryArg( 'redirect_url' );
	const [ single, setSingle ] = useQueryArg( 'single', false );

	const containerRef = useRef();

	const activeSlugIndex = steps.indexOf( activeSlug );
	if ( activeSlugIndex === -1 ) {
		setActiveSlug( steps[ 0 ] );
	}

	const { saveUserInputSettings } = useDispatch( CORE_USER );
	const { navigateTo } = useDispatch( CORE_LOCATION );

	const isNavigating = useSelect( ( select ) => select( CORE_LOCATION ).isNavigating() );
	const dashboardURL = useSelect( ( select ) => select( CORE_SITE ).getAdminURL( 'googlesitekit-dashboard' ) );
	const { isSavingSettings, error, answeredUntilIndex } = useSelect( ( select ) => {
		const userInputSettings = select( CORE_USER ).getUserInputSettings();

		return {
			isSavingSettings: select( CORE_USER ).isSavingUserInputSettings( userInputSettings ),
			error: select( CORE_USER ).getErrorForAction( 'saveUserInputSettings', [] ),
			answeredUntilIndex: USER_INPUT_QUESTIONS_LIST.findIndex( ( question ) => userInputSettings?.[ question ]?.values?.length === 0 ),
		};
	} );

	useEffect( () => {
		if ( answeredUntilIndex === -1 ) {
			return;
		}
		if ( activeSlugIndex > answeredUntilIndex ) {
			setActiveSlug( steps[ answeredUntilIndex ] );
		}
	}, [ answeredUntilIndex, activeSlugIndex ] );

	useEffect( () => {
		if ( activeSlug === 'preview' ) {
			trackEvent( 'user_input', 'summary_view' );
		}
	}, [ activeSlug ] );

	const {
		USER_INPUT_ANSWERS_GOALS,
		USER_INPUT_ANSWERS_HELP_NEEDED,
		USER_INPUT_ANSWERS_POST_FREQUENCY,
		USER_INPUT_ANSWERS_ROLE,
	} = getUserInputAnwsers();

	const isSettings = single === 'settings';

	const next = useCallback( () => {
		trackEvent( 'user_input', 'question_advance', steps[ activeSlugIndex ] );
		setActiveSlug( steps[ activeSlugIndex + 1 ] );
	}, [ activeSlugIndex ] );

	const goTo = useCallback( ( num = 1, singleType = false ) => {
		trackEvent(
			'user_input',
			'summary_edit',
			steps[ num - 1 ],
		);

		// If we're going to a single question to edit it, set the query string here.
		// We can't currently set it in the child component because the useQueryArg hook doesn't update in the parent.
		setSingle( singleType );
		if ( steps.length >= num && num > 0 ) {
			setActiveSlug( steps[ num - 1 ] );
		}
	}, [ activeSlugIndex, isSettings ] );

	const back = useCallback( () => {
		trackEvent( 'user_input', 'question_return', steps[ activeSlugIndex ] );
		setActiveSlug( steps[ activeSlugIndex - 1 ] );
	}, [ activeSlugIndex ] );

	const submitChanges = useCallback( async () => {
		trackEvent(
			'user_input',
			isSettings ? 'question_submit' : 'summary_submit',
			isSettings ? steps[ activeSlugIndex ] : undefined,
		);

		const response = await saveUserInputSettings();
		if ( ! response.error ) {
			const url = new URL( redirectURL || dashboardURL );

			// Here we don't use `addQueryArgs` due to a bug with how it handles hashes
			// See https://github.com/WordPress/gutenberg/issues/16655
			url.searchParams.set( 'notification', 'user_input_success' );

			navigateTo( url.toString() );
		}
	}, [ dashboardURL, isSettings ] );

	const goToPreview = useCallback( () => {
		trackEvent( 'user_input', 'question_update', steps[ activeSlugIndex ] );
		setActiveSlug( steps[ steps.length - 1 ] );
	}, [ activeSlugIndex ] );

	useEffect( () => {
		if ( ! shouldScrollToActiveQuestion ) {
			setShouldScrollToActiveQuestion( true );
			return;
		}

		global.document?.querySelector( '.googlesitekit-user-input__header' )?.scrollIntoView( { behavior: 'smooth' } );
	}, [ activeSlug ] );

	// Update the callbacks and labels for the questions if the user is editing a *single question*.
	let backCallback = back;
	let nextCallback = next;
	let nextLabel;

	if ( single === 'user-input' ) {
		backCallback = undefined;
		// When the user is editing a single question in the user-input screen send them back to the preview when they click Update.
		nextCallback = goToPreview;
		nextLabel = __( 'Update', 'google-site-kit' );
	} else if ( single === 'settings' ) {
		backCallback = undefined;
		// When the user is editing a single question from the settings screen, submit changes and send them back to the settings pages when they click Submit.
		nextCallback = submitChanges;
		nextLabel = __( 'Submit', 'google-site-kit' );
	}

	const settingsProgress = (
		<ProgressBar
			height={ 0 }
			indeterminate={ false }
			progress={ ( activeSlugIndex + 1 ) / USER_INPUT_QUESTIONS_LIST.length }
		/>
	);

	if ( isSavingSettings || isNavigating ) {
		return (
			<Fragment>
				{ settingsProgress }

					<Row>
						<Cell lgSize={ 12 } mdSize={ 8 } smSize={ 4 }>
							<ProgressBar />
						</Cell>
					</Row>

			</Fragment>
		);
	}

	return (

			{ settingsProgress }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_ROLE ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_ROLE }
					isActive={ activeSlug === USER_INPUT_QUESTION_ROLE }
					questionNumber={ 1 }
					title={ __( 'Which best describes your team/role in relation to this site?', 'google-site-kit' ) }
					description={ __( 'This will help Site Kit show tips that help you specifically in your role.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel }
					error={ error }
				>
					<UserInputSelectOptions
						slug={ USER_INPUT_QUESTION_ROLE }
						options={ USER_INPUT_ANSWERS_ROLE }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_POST_FREQUENCY ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_POST_FREQUENCY }
					isActive={ activeSlug === USER_INPUT_QUESTION_POST_FREQUENCY }
					questionNumber={ 2 }
					title={ __( 'How often do you create new posts for this site?', 'google-site-kit' ) }
					description={ __( 'Based on your answer, Site Kit will suggest new features for your dashboard related to content creation.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel }
					back={ backCallback }
					error={ error }
				>
					<UserInputSelectOptions
						slug={ USER_INPUT_QUESTION_POST_FREQUENCY }
						options={ USER_INPUT_ANSWERS_POST_FREQUENCY }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_GOALS ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_GOALS }
					isActive={ activeSlug === USER_INPUT_QUESTION_GOALS }
					questionNumber={ 3 }
					title={ __( 'What are the goals of this site?', 'google-site-kit' ) }
					description={ __( 'Based on your answer, Site Kit will tailor the metrics you see on your dashboard to help you track how close you’re getting to your specific goals.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel }
					back={ backCallback }
					error={ error }
				>
					<UserInputSelectOptions
						slug={ USER_INPUT_QUESTION_GOALS }
						max={ 2 }
						options={ USER_INPUT_ANSWERS_GOALS }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_HELP_NEEDED ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_HELP_NEEDED }
					isActive={ activeSlug === USER_INPUT_QUESTION_HELP_NEEDED }
					questionNumber={ 4 }
					title={ __( 'What do you need help most with for this site?', 'google-site-kit' ) }
					description={ __( 'Based on your answers, Site Kit will tailor the metrics and advice you see on your dashboard to help you make progress in these areas.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel }
					back={ backCallback }
					error={ error }
				>
					<UserInputSelectOptions
						slug={ USER_INPUT_QUESTION_HELP_NEEDED }
						max={ 3 }
						options={ USER_INPUT_ANSWERS_HELP_NEEDED }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlugIndex <= steps.indexOf( USER_INPUT_QUESTION_SEARCH_TERMS ) && (
				<UserInputQuestionWrapper
					slug={ USER_INPUT_QUESTION_SEARCH_TERMS }
					isActive={ activeSlug === USER_INPUT_QUESTION_SEARCH_TERMS }
					questionNumber={ 5 }
					title={ __( 'To help us identify opportunities for your site, enter the top three search terms that best describe your site’s content.', 'google-site-kit' ) }
					description={ __( 'Site Kit will keep you informed if people start finding you in Search for these terms.', 'google-site-kit' ) }
					next={ nextCallback }
					nextLabel={ nextLabel === undefined ? __( 'Preview', 'google-site-kit' ) : nextLabel }
					back={ backCallback }
					error={ error }
					allowEmptyValues
				>
					<UserInputKeywords
						slug={ USER_INPUT_QUESTION_SEARCH_TERMS }
						max={ 3 }
					/>
				</UserInputQuestionWrapper>
			) }

			{ activeSlug === 'preview' && (
				<UserInputPreview
					submitChanges={ submitChanges }
					goTo={ goTo }
					error={ error }
				/>
			) }

	);
}
<|endoftext|>


<commit_before>var exports=null;//IGNORE
function String$(a,b){}//IGNORE
function StringBuilder(){}//IGNORE

function string(/*Iterable<Character>*/chars) {
    if (chars === undefined) return String$('',0);
    var s = StringBuilder();
    var iter = chars.iterator();
    var c; while ((c = iter.next()) !== getFinished()) {
        s.appendCharacter(c);
    }
    return s.string;
}

function internalSort(comp, elems, $$$mptypes) {
    if (elems===undefined) {return getEmpty();}
    var arr = [];
    var it = elems.iterator();
    var e;
    while ((e=it.next()) !== getFinished()) {arr.push(e);}
    if (arr.length === 0) {return getEmpty();}
    arr.sort(function(a, b) {
        var cmp = comp(a,b);
        return (cmp===larger) ? 1 : ((cmp===smaller) ? -1 : 0);
    });
    return ArraySequence(arr, $$$mptypes);
}

exports.string=string;

function flatten(tf, $$$mptypes) {
    var rf = function() {
        var t = getEmpty();
        var e = null;
        var argc = arguments.length;
        var last = argc>0 ? arguments[argc-1] : undefined;
        if (typeof(last) === 'object' && typeof(last.Args) === 'object' && typeof(last.Args.t) === 'function') {
            argc--;
        }
        for (var i=0; i < argc; i++) {
            var c = arguments[i]===null ? Null :
                arguments[i] === undefined ? Empty :
                arguments[i].getT$all ? arguments[i].getT$all() :
                Anything;
            if (e === null) {
                e = c;
            } else if (e.t === 'u' && e.l.length > 0) {
                var l = [c];
                for (var j=0; j < e.l.length; j++) {
                    l[j+1] = e.l[j];
                }
            } else {
                e = {t:'u', l:[e, c]};
            }
            var rest;
            if (t === getEmpty()) {
                rest={t:Empty};
            } else {
                rest={t:Tuple, a:t.$$$targs$$$};
            }
            t = Tuple(arguments[i], t, {First:c, Element:e, Rest:rest});
        }
        return tf(t, t.$$targs$$);
    };
    rf.$$targs$$=$$$mptypes;
    return rf;
}
function unflatten(ff, $$$mptypes) {
    var ru = function ru(seq) {
        if (seq===undefined || seq.size === 0) { return ff(); }
        var a = [];
        for (var i = 0; i < seq.size; i++) {
            a[i] = seq.get(i);
        }
        a[i]=ru.$$targs$$;
        return ff.apply(ru, a);
    }
    ru.$$targs$$=$$$mptypes;
    return ru;
}
exports.flatten=flatten;
exports.unflatten=unflatten;

//internal
function toTuple(iterable) {
  var seq = iterable.sequence;
  return Tuple(seq.first, seq.rest.sequence,
    {First:seq.$$targs$$.Element, Element:seq.$$targs$$.Element, Rest:{t:Sequential, a:seq.$$targs$$}});
}
exports.toTuple=toTuple;

function integerRangeByIterable(range, step, $$$mptypes) {
    return Comprehension(function(){
        var a = range.first;
        var b = range.last;
        if (a>b) {
            a += step;
            return function() {
                a -= step;
                return a<b ? getFinished() : a;
            }
        }
        a-=step;
        return function() {
            a += step;
            return a>b ? getFinished() : a;
        }
    }, {Element:range.$$targs$$.Element, Absent:range.$$targs$$.Absent});
}
exports.integerRangeByIterable=integerRangeByIterable;
<commit_msg>Use metamodel in unflatten whenever available<commit_after>var exports=null;//IGNORE
function String$(a,b){}//IGNORE
function StringBuilder(){}//IGNORE

function string(/*Iterable<Character>*/chars) {
    if (chars === undefined) return String$('',0);
    var s = StringBuilder();
    var iter = chars.iterator();
    var c; while ((c = iter.next()) !== getFinished()) {
        s.appendCharacter(c);
    }
    return s.string;
}

function internalSort(comp, elems, $$$mptypes) {
    if (elems===undefined) {return getEmpty();}
    var arr = [];
    var it = elems.iterator();
    var e;
    while ((e=it.next()) !== getFinished()) {arr.push(e);}
    if (arr.length === 0) {return getEmpty();}
    arr.sort(function(a, b) {
        var cmp = comp(a,b);
        return (cmp===larger) ? 1 : ((cmp===smaller) ? -1 : 0);
    });
    return ArraySequence(arr, $$$mptypes);
}

exports.string=string;

function flatten(tf, $$$mptypes) {
    var rf = function() {
        var t = getEmpty();
        var e = null;
        var argc = arguments.length;
        var last = argc>0 ? arguments[argc-1] : undefined;
        if (typeof(last) === 'object' && typeof(last.Args) === 'object' && typeof(last.Args.t) === 'function') {
            argc--;
        }
        for (var i=0; i < argc; i++) {
            var c = arguments[i]===null ? Null :
                arguments[i] === undefined ? Empty :
                arguments[i].getT$all ? arguments[i].getT$all() :
                Anything;
            if (e === null) {
                e = c;
            } else if (e.t === 'u' && e.l.length > 0) {
                var l = [c];
                for (var j=0; j < e.l.length; j++) {
                    l[j+1] = e.l[j];
                }
            } else {
                e = {t:'u', l:[e, c]};
            }
            var rest;
            if (t === getEmpty()) {
                rest={t:Empty};
            } else {
                rest={t:Tuple, a:t.$$$targs$$$};
            }
            t = Tuple(arguments[i], t, {First:c, Element:e, Rest:rest});
        }
        return tf(t, t.$$targs$$);
    };
    rf.$$targs$$=$$$mptypes;
    return rf;
}
function unflatten(ff, $$$mptypes) {
    if (ff.$$metamodel$$ && ff.$$metamodel$$['$ps']) {
        var ru = function ru(seq) {
            if (seq===undefined || seq.size === 0) { return ff(); }
            var pmeta = ff.$$metamodel$$['$ps'];
            var a = [];
            for (var i = 0; i < pmeta.length; i++) {
                if (pmeta[i]['seq'] == 1) {
                    a[i] = seq.skipping(i).sequence;
                } else {
                    a[i] = seq.get(i);
                }
            }
            a[i]=ru.$$targs$$;
            return ff.apply(ru, a);
        }
    } else {
        var ru = function ru(seq) {
            if (seq===undefined || seq.size === 0) { return ff(); }
            var a = [];
            for (var i = 0; i < seq.size; i++) {
                a[i] = seq.get(i);
            }
            a[i]=ru.$$targs$$;
            return ff.apply(ru, a);
        }
    }
    ru.$$targs$$=$$$mptypes;
    return ru;
}
exports.flatten=flatten;
exports.unflatten=unflatten;

//internal
function toTuple(iterable) {
  var seq = iterable.sequence;
  return Tuple(seq.first, seq.rest.sequence,
    {First:seq.$$targs$$.Element, Element:seq.$$targs$$.Element, Rest:{t:Sequential, a:seq.$$targs$$}});
}
exports.toTuple=toTuple;

function integerRangeByIterable(range, step, $$$mptypes) {
    return Comprehension(function(){
        var a = range.first;
        var b = range.last;
        if (a>b) {
            a += step;
            return function() {
                a -= step;
                return a<b ? getFinished() : a;
            }
        }
        a-=step;
        return function() {
            a += step;
            return a>b ? getFinished() : a;
        }
    }, {Element:range.$$targs$$.Element, Absent:range.$$targs$$.Absent});
}
exports.integerRangeByIterable=integerRangeByIterable;
<|endoftext|>


<commit_before>/*
 * Copyright 2011 eBay Software Foundation
 *
 * 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
 *
 *    http://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.
 */
/**
 * This module provides the runtime for rendering compiled templates.
 *
 *
 *
The code for the Marko compiler is kept separately
 * in the {@link raptor/templating/compiler} module.
 */
var asyncWriter = require('async-writer');
var helpers = require('./helpers');
var loader = require('./loader');
var cache = {};
var Readable;
var AsyncWriter = asyncWriter.AsyncWriter;

exports.AsyncWriter = AsyncWriter;

var stream;
var STREAM = 'stream';

var streamPath;
try {
    streamPath = require.resolve(STREAM);
} catch(e) {}

if (streamPath) {
    stream = require(streamPath);
}

function renderWithCallback(template, data, out, callback) {
    out
        .on('end', function() {
            callback(null, out.getOutput());
        })
        .on('error', callback);

    template._(data, out);    //Invoke the template rendering function with the required arguments

    out.end();
}

function Template(renderFunc) {
    this._ = renderFunc;
}

Template.prototype = {
    renderSync: function(data) {
        var out = new AsyncWriter();
        out.sync();
        this._(data, out);
        out.end();
        return out.getOutput();
    },
    render: function(data, out, callback) {
        if (data == null) {
            data = {};
        }

        // callback is last argument if provided
        callback = arguments[arguments.length - 1];
        if (typeof callback === 'function') {
            if (arguments.length === 2) { // data, out, callback
                callback = out;
                out = new AsyncWriter();
            }
            renderWithCallback(this, data, out, callback);
        } else {
            if (out.isAsyncWriter) {
                this._(data, out);
            } else {
                // Assume the "out" is really a stream
                out = new AsyncWriter(out);
                this._(data, out);
                out.end(); // End the out and the underlying stream
            }
        }

        return out;
    },
    stream: function(data) {
        if (!stream) {
            return function() {
                throw new Error('Module not found: stream');
            };
        }

        return new Readable(this, data);
    }
};

if (stream) {
    Readable = function(template, data) {
        Readable.$super.call(this);
        this._t = template;
        this._d = data;
        this._rendered = false;
    };

    Readable.prototype = {
        write: function(data) {
            if (data != null) {
                this.push(data);
            }
        },
        end: function() {
            this.push(null);
        },
        _read: function() {
            if (this._rendered) {
                return;
            }

            this._rendered = true;

            var template = this._t;
            var data = this._d;

            var out = exports.createWriter(this);
            template.render(data, out);
            out.end();
        }
    };

    require('raptor-util/inherit')(Readable, stream.Readable);
}

function load(templatePath) {
    var template;

    if (typeof templatePath === 'string') {
        template = cache[templatePath];
        if (!template) {
            template = cache[templatePath] = new Template(loader(templatePath)(helpers));
        }
    } else {
        // Instead of a path, assume we got a compiled template module
        template = templatePath._ || (templatePath._ = new Template(templatePath(helpers)));
    }

    return template;
}

exports.load = load;

exports.render = function (templatePath, data, out) {
    return load(templatePath).render(data, out);
};

exports.stream = function(templatePath, data) {
    return load(templatePath).stream(data);
};

exports.unload = function(templatePath) {
    delete cache[templatePath];
};

exports.createWriter = function(writer) {
    return new AsyncWriter(writer);
};

exports.helpers = helpers;
<commit_msg>Copy $global props to out.global<commit_after>/*
 * Copyright 2011 eBay Software Foundation
 *
 * 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
 *
 *    http://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.
 */
/**
 * This module provides the runtime for rendering compiled templates.
 *
 *
 *
The code for the Marko compiler is kept separately
 * in the {@link raptor/templating/compiler} module.
 */
var asyncWriter = require('async-writer');
var helpers = require('./helpers');
var loader = require('./loader');
var cache = {};
var Readable;
var AsyncWriter = asyncWriter.AsyncWriter;
var extend = require('raptor-util/extend');

exports.AsyncWriter = AsyncWriter;

var stream;
var STREAM = 'stream';

var streamPath;
try {
    streamPath = require.resolve(STREAM);
} catch(e) {}

if (streamPath) {
    stream = require(streamPath);
}

function Template(renderFunc) {
    this._ = renderFunc;
}

Template.prototype = {
    renderSync: function(data) {
        var out = new AsyncWriter();
        out.sync();
        this._(data, out);
        out.end();
        return out.getOutput();
    },
    render: function(data, out, callback) {
        if (data == null) {
            data = {};
        }
        var renderFunc = this._;

        // callback is last argument if provided
        callback = arguments[arguments.length - 1];
        var shouldEnd = true;
        if (typeof callback === 'function') {
            if (arguments.length === 2) { // data, out, callback
                callback = out;
                out = new AsyncWriter();
            }
            out.on('end', function() {
                    callback(null, out.getOutput());
                })
                .on('error', callback);
        } else {
            if (out.isAsyncWriter) {
                shouldEnd = false;
            } else {
                // Assume the "out" is really a stream
                out = new AsyncWriter(out);
            }
        }

        var $global = data.$global;
        if ($global) {
            extend(out.global, $global);
        }

        renderFunc(data, out);
        if (shouldEnd) {
            out.end(); // End the async writer and the underlying stream
        }

        return out;
    },
    stream: function(data) {
        if (!stream) {
            throw new Error('Module not found: stream');
        }

        return new Readable(this, data);
    }
};

if (stream) {
    Readable = function(template, data) {
        Readable.$super.call(this);
        this._t = template;
        this._d = data;
        this._rendered = false;
    };

    Readable.prototype = {
        write: function(data) {
            if (data != null) {
                this.push(data);
            }
        },
        end: function() {
            this.push(null);
        },
        _read: function() {
            if (this._rendered) {
                return;
            }

            this._rendered = true;

            var template = this._t;
            var data = this._d;

            var out = new AsyncWriter(this);
            template.render(data, out);
            out.end();
        }
    };

    require('raptor-util/inherit')(Readable, stream.Readable);
}

function load(templatePath) {
    var template;

    if (typeof templatePath === 'string') {
        template = cache[templatePath];
        if (!template) {
            template = cache[templatePath] = new Template(loader(templatePath)(helpers));
        }
    } else {
        // Instead of a path, assume we got a compiled template module
        template = templatePath._ || (templatePath._ = new Template(templatePath(helpers)));
    }

    return template;
}

exports.load = load;

exports.render = function (templatePath, data, out) {
    return load(templatePath).render(data, out);
};

exports.stream = function(templatePath, data) {
    return load(templatePath).stream(data);
};

exports.unload = function(templatePath) {
    delete cache[templatePath];
};

exports.createWriter = function(writer) {
    return new AsyncWriter(writer);
};

exports.helpers = helpers;
<|endoftext|>


<commit_before>'use strict';

/**
 * ButtressJS - Realtime datastore for software
 *
 * @file bootstrap-rest.js
 * @description
 * @module Config
 * @author Chris Bates-Keegan
 *
 */

const path = require('path');
const fs = require('fs');
const os = require('os');
const cluster = require('cluster');
const express = require('express');
const methodOverride = require('method-override');
const bodyParser = require('body-parser');
const morgan = require('morgan');

const Config = require('node-env-obj')('../');
const Model = require('./model');
const Routes = require('./routes');
const Logging = require('./logging');
const Schema = require('./schema');
const MongoClient = require('mongodb').MongoClient;
const NRP = require('node-redis-pubsub');
const shortId = require('./helpers').ShortId;

Error.stackTraceLimit = Infinity;
class BootstrapRest {
	constructor() {
		Logging.setLogLevel(Logging.Constants.LogLevel.INFO);

		this.processes = os.cpus().length;
		this.workers = [];

		let restInitTask = null;
		if (cluster.isMaster) {
			restInitTask = (db) => this.__initMaster(db);
		} else {
			restInitTask = (db) => this.__initWorker(db);
		}

		return this.__nativeMongoConnect()
			.then(restInitTask)
			.then(() => cluster.isMaster);
	}

	__initMaster(db) {
		const isPrimary = Config.rest.app === 'primary';
		let initMasterTask = Promise.resolve();

		if (isPrimary) {
			Logging.logVerbose(`Primary Master REST`);
			initMasterTask = Model.initCoreModels(db)
				.then(() => this.__systemInstall())
				.then(() => Model.App.findAll().toArray())
				.then((apps) => this.__updateAppSchema(apps))
				.then(() => Model.initSchema())
				.catch((e) => Logging.logError(e));
		} else {
			Logging.logVerbose(`Secondary Master REST`);
		}

		const nrp = new NRP(Config.redis);
		nrp.on('app-metadata:changed', (data) => {
			Logging.logDebug(`App Metadata Changed: ${data.appId}, ${this.workers.length} Workers`);
			this.workers.forEach((w) => w.send({appId: data.appId}));
		});

		return initMasterTask
			.then(() => this.__spawnWorkers());
	}

	__initWorker(db) {
		const app = express();
		app.use(morgan('short'));
		app.enable('trust proxy', 1);
		app.use(bodyParser.json({limit: '20mb'}));
		app.use(bodyParser.urlencoded({extended: true}));
		app.use(methodOverride());
		app.use(express.static(`${Config.paths.appData}/public`));

		process.on('unhandledRejection', (error) => {
			Logging.logError(error);
		});

		process.on('message', (payload) => {
			Logging.logDebug(`App Metadata Changed: ${payload.appId}`);
			Model.appMetadataChanged = true;
		});

		return Model.init(db)
			.then(() => {
				const localSchema = this._getLocalSchemas();
				Model.App.setLocalSchema(localSchema);

				return [
					Routes.init(app),
				];
			})
			.then((tasks) => Promise.all(tasks))
			.then(() => app.listen(Config.listenPorts.rest))
			.catch(Logging.Promise.logError());
	}

	__nativeMongoConnect() {
		const dbName = `${Config.app.code}-${Config.env}`;
		const mongoUrl = `mongodb://${Config.mongoDb.url}`;
		Logging.logDebug(`Attempting connection to ${mongoUrl}`);

		return MongoClient.connect(mongoUrl, Config.mongoDb.options)
			.then((client) => client.db(dbName))
			.catch(Logging.Promise.logError());
	}

	__spawnWorkers() {
		Logging.logVerbose(`Spawning ${this.processes} REST Workers`);

		const __spawn = (idx) => {
			this.workers[idx] = cluster.fork();
		};

		for (let x = 0; x < this.processes; x++) {
			__spawn(x);
		}
	}

	__systemInstall() {
		let isInstalled = false;

		Logging.log('Checking for existing apps.');

		return Model.App.findAll().toArray()
			.then((apps) => {
				if (apps.length > 0) {
					isInstalled = true;
					Logging.log('Existing apps found - Skipping install.');
					return {app: apps[0], token: null}; // If any apps, assume we've got a Super Admin app
				}

				Logging.log('No apps found - Creating super app.');
				return Model.App.add({
					name: `${Config.app.title} ADMIN`,
					type: Model.App.Constants.Type.SERVER,
					authLevel: Model.Token.Constants.AuthLevel.SUPER,
					permissions: [{route: '*', permission: '*'}],
					domain: '',
				});
			})
			.then((res) => {
				if (isInstalled) {
					return res.app;
				}

				const pathName = path.join(Config.paths.appData, 'super.json');
				Logging.log(`Super app created: ${res.app.id}`);
				return new Promise((resolve, reject) => {
					const app = Object.assign(res.app, {token: res.token.value});
					fs.writeFile(pathName, JSON.stringify(app), (err) => {
						if (err) {
							return reject(err);
						}
						Logging.log(`Created ${pathName}`);

						resolve(res.app);
					});
				});
			});
	}

	/**
	 * @return {Array} - content of json files loaded from local system
	 */
	_getLocalSchemas() {
		const filenames = fs.readdirSync(`${__dirname}/schema`);

		const files = [];
		for (let x = 0; x < filenames.length; x++) {
			const file = filenames[x];
			if (path.extname(file) === '.json') {
				files.push(require(`${__dirname}/schema/${path.basename(file, '.js')}`));
			}
		}
		return files;
	}

	__updateAppSchema(apps) {
		const schemaUpdates = [];

		// Load local defined schemas into super app
		const localSchema = this._getLocalSchemas();

		// Add local schema to Model.App
		Model.App.setLocalSchema(localSchema);

		// Build a update queue for merging local schema with each app schema
		apps.forEach((app) => {
			const appSchema = Schema.decode(app.__schema);
			const appShortId = shortId(app._id);
			Logging.log(`Adding ${localSchema.length} local schema for ${appShortId}:${app.name}:${appSchema.length}`);
			localSchema.forEach((cS) => {
				const appSchemaIdx = appSchema.findIndex((s) => s.name === cS.name);
				const schema = appSchema[appSchemaIdx];
				if (!schema) {
					return appSchema.push(cS);
				}
				schema.properties = Object.assign(schema.properties, cS.properties);
				appSchema[appSchemaIdx] = schema;
			});

			schemaUpdates.push(() => Model.App.updateSchema(app._id, appSchema));
		});

		// Run update queue
		return schemaUpdates.reduce((prev, task) => {
			return prev.then(() => task());
		}, Promise.resolve());
	}
}

module.exports = BootstrapRest;
<commit_msg>TWEAK: Expresss morgan log with prefix with the ISO date to match other log items<commit_after>'use strict';

/**
 * ButtressJS - Realtime datastore for software
 *
 * @file bootstrap-rest.js
 * @description
 * @module Config
 * @author Chris Bates-Keegan
 *
 */

const path = require('path');
const fs = require('fs');
const os = require('os');
const cluster = require('cluster');
const express = require('express');
const methodOverride = require('method-override');
const bodyParser = require('body-parser');
const morgan = require('morgan');

const Config = require('node-env-obj')('../');
const Model = require('./model');
const Routes = require('./routes');
const Logging = require('./logging');
const Schema = require('./schema');
const MongoClient = require('mongodb').MongoClient;
const NRP = require('node-redis-pubsub');
const shortId = require('./helpers').ShortId;

Error.stackTraceLimit = Infinity;
class BootstrapRest {
	constructor() {
		Logging.setLogLevel(Logging.Constants.LogLevel.INFO);

		this.processes = os.cpus().length;
		this.workers = [];

		let restInitTask = null;
		if (cluster.isMaster) {
			restInitTask = (db) => this.__initMaster(db);
		} else {
			restInitTask = (db) => this.__initWorker(db);
		}

		return this.__nativeMongoConnect()
			.then(restInitTask)
			.then(() => cluster.isMaster);
	}

	__initMaster(db) {
		const isPrimary = Config.rest.app === 'primary';
		let initMasterTask = Promise.resolve();

		if (isPrimary) {
			Logging.logVerbose(`Primary Master REST`);
			initMasterTask = Model.initCoreModels(db)
				.then(() => this.__systemInstall())
				.then(() => Model.App.findAll().toArray())
				.then((apps) => this.__updateAppSchema(apps))
				.then(() => Model.initSchema())
				.catch((e) => Logging.logError(e));
		} else {
			Logging.logVerbose(`Secondary Master REST`);
		}

		const nrp = new NRP(Config.redis);
		nrp.on('app-metadata:changed', (data) => {
			Logging.logDebug(`App Metadata Changed: ${data.appId}, ${this.workers.length} Workers`);
			this.workers.forEach((w) => w.send({appId: data.appId}));
		});

		return initMasterTask
			.then(() => this.__spawnWorkers());
	}

	__initWorker(db) {
		const app = express();
		app.use(morgan(`:date[iso] [${cluster.worker.id}] :method :status :url :res[content-length] - :response-time ms - :remote-addr`));
		app.enable('trust proxy', 1);
		app.use(bodyParser.json({limit: '20mb'}));
		app.use(bodyParser.urlencoded({extended: true}));
		app.use(methodOverride());
		app.use(express.static(`${Config.paths.appData}/public`));

		process.on('unhandledRejection', (error) => {
			Logging.logError(error);
		});

		process.on('message', (payload) => {
			Logging.logDebug(`App Metadata Changed: ${payload.appId}`);
			Model.appMetadataChanged = true;
		});

		return Model.init(db)
			.then(() => {
				const localSchema = this._getLocalSchemas();
				Model.App.setLocalSchema(localSchema);

				return [
					Routes.init(app),
				];
			})
			.then((tasks) => Promise.all(tasks))
			.then(() => app.listen(Config.listenPorts.rest))
			.catch(Logging.Promise.logError());
	}

	__nativeMongoConnect() {
		const dbName = `${Config.app.code}-${Config.env}`;
		const mongoUrl = `mongodb://${Config.mongoDb.url}`;
		Logging.logDebug(`Attempting connection to ${mongoUrl}`);

		return MongoClient.connect(mongoUrl, Config.mongoDb.options)
			.then((client) => client.db(dbName))
			.catch(Logging.Promise.logError());
	}

	__spawnWorkers() {
		Logging.logVerbose(`Spawning ${this.processes} REST Workers`);

		const __spawn = (idx) => {
			this.workers[idx] = cluster.fork();
		};

		for (let x = 0; x < this.processes; x++) {
			__spawn(x);
		}
	}

	__systemInstall() {
		let isInstalled = false;

		Logging.log('Checking for existing apps.');

		return Model.App.findAll().toArray()
			.then((apps) => {
				if (apps.length > 0) {
					isInstalled = true;
					Logging.log('Existing apps found - Skipping install.');
					return {app: apps[0], token: null}; // If any apps, assume we've got a Super Admin app
				}

				Logging.log('No apps found - Creating super app.');
				return Model.App.add({
					name: `${Config.app.title} ADMIN`,
					type: Model.App.Constants.Type.SERVER,
					authLevel: Model.Token.Constants.AuthLevel.SUPER,
					permissions: [{route: '*', permission: '*'}],
					domain: '',
				});
			})
			.then((res) => {
				if (isInstalled) {
					return res.app;
				}

				const pathName = path.join(Config.paths.appData, 'super.json');
				Logging.log(`Super app created: ${res.app.id}`);
				return new Promise((resolve, reject) => {
					const app = Object.assign(res.app, {token: res.token.value});
					fs.writeFile(pathName, JSON.stringify(app), (err) => {
						if (err) {
							return reject(err);
						}
						Logging.log(`Created ${pathName}`);

						resolve(res.app);
					});
				});
			});
	}

	/**
	 * @return {Array} - content of json files loaded from local system
	 */
	_getLocalSchemas() {
		const filenames = fs.readdirSync(`${__dirname}/schema`);

		const files = [];
		for (let x = 0; x < filenames.length; x++) {
			const file = filenames[x];
			if (path.extname(file) === '.json') {
				files.push(require(`${__dirname}/schema/${path.basename(file, '.js')}`));
			}
		}
		return files;
	}

	__updateAppSchema(apps) {
		const schemaUpdates = [];

		// Load local defined schemas into super app
		const localSchema = this._getLocalSchemas();

		// Add local schema to Model.App
		Model.App.setLocalSchema(localSchema);

		// Build a update queue for merging local schema with each app schema
		apps.forEach((app) => {
			const appSchema = Schema.decode(app.__schema);
			const appShortId = shortId(app._id);
			Logging.log(`Adding ${localSchema.length} local schema for ${appShortId}:${app.name}:${appSchema.length}`);
			localSchema.forEach((cS) => {
				const appSchemaIdx = appSchema.findIndex((s) => s.name === cS.name);
				const schema = appSchema[appSchemaIdx];
				if (!schema) {
					return appSchema.push(cS);
				}
				schema.properties = Object.assign(schema.properties, cS.properties);
				appSchema[appSchemaIdx] = schema;
			});

			schemaUpdates.push(() => Model.App.updateSchema(app._id, appSchema));
		});

		// Run update queue
		return schemaUpdates.reduce((prev, task) => {
			return prev.then(() => task());
		}, Promise.resolve());
	}
}

module.exports = BootstrapRest;
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright (c) 2017 Google Inc. All rights reserved.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt
 * Code distributed by Google as part of this project is also
 * subject to an additional IP rights grant found at
 * http://polymer.github.io/PATENTS.txt
 */

import {assert} from '../platform/assert-web.js';
import {Affordance} from './affordance.js';

export class SlotComposer {
  /**
   * |options| must contain:
   * - affordance: the UI affordance the slots composer render to (for example: dom).
   * - rootContext: the context containing top level context to be used for slots.
   * and may contain:
   * - containerKind: the type of container wrapping each slot's context (for example, div).
   */
  constructor(options) {
    assert(options.affordance, 'Affordance is mandatory');
    assert(options.rootContext, 'Root context is mandatory');

    this._containerKind = options.containerKind;
    this._affordance = Affordance.forName(options.affordance);
    assert(this._affordance.slotClass);

    let slotContextByName = this._affordance.slotClass.findRootSlots(options.rootContext) || {};
    if (Object.keys(slotContextByName).length == 0) {
      // fallback to single 'root' slot using the rootContext.
      slotContextByName['root'] = options.rootContext;
    }

    this._contextSlots = [];
    Object.keys(slotContextByName).forEach(slotName => {
      this._contextSlots.push({id: `rootslotid-${slotName}`, name: slotName, tags: [`${slotName}`], context: slotContextByName[slotName], handleConnections: [], handles: 0, getProvidedSlotSpec: () => { return {isSet: false}; }});
    });

    this._slots = [];
  }

  get affordance() { return this._affordance.name; }

  getSlot(particle, slotName) {
    return this._slots.find(s => s.consumeConn.particle == particle && s.consumeConn.name == slotName);
  }

  _findContext(slotId) {
    let contextSlot = this._contextSlots.find(slot => slot.id == slotId);
    if (contextSlot) {
      return contextSlot.context;
    }
  }

  createHostedSlot(transformationParticle, transformationSlotName, hostedParticleName, hostedSlotName) {
    let hostedSlotId = this.arc.generateID();

    let transformationSlot = this.getSlot(transformationParticle, transformationSlotName);
    assert(transformationSlot,
           `Unexpected transformation slot particle ${transformationParticle.name}:${transformationSlotName}, hosted particle ${hostedParticleName}, slot name ${hostedSlotName}`);
    transformationSlot.addHostedSlot(hostedSlotId, hostedParticleName, hostedSlotName);
    return hostedSlotId;
  }
  _findSlotByHostedSlotId(hostedSlotId) {
    for (let slot of this._slots) {
      let hostedSlot = slot.getHostedSlot(hostedSlotId);
      if (hostedSlot) {
        return slot;
      }
    }
  }
  findHostedSlot(hostedParticle, hostedSlotName) {
    for (let slot of this._slots) {
      let hostedSlot = slot.findHostedSlot(hostedParticle, hostedSlotName);
      if (hostedSlot) {
        return hostedSlot;
      }
    }
  }

  initializeRecipe(recipeParticles) {
    let newSlots = [];
    // Create slots for each of the recipe's particles slot connections.
    recipeParticles.forEach(p => {
      Object.values(p.consumedSlotConnections).forEach(cs => {
        if (!cs.targetSlot) {
          assert(!cs.slotSpec.isRequired, `No target slot for particle's ${p.name} required consumed slot: ${cs.name}.`);
          return;
        }

        if (this._initHostedSlot(cs.targetSlot.id, p)) {
          // Skip slot creation for hosted slots.
          return;
        }

        let slot = new this._affordance.slotClass(cs, this.arc, this._containerKind);
        slot.startRenderCallback = this.arc.pec.startRender.bind(this.arc.pec);
        slot.stopRenderCallback = this.arc.pec.stopRender.bind(this.arc.pec);
        slot.innerSlotsUpdateCallback = this.updateInnerSlots.bind(this);
        newSlots.push(slot);
      });
    });

    // Attempt to set context for each of the slots.
    newSlots.forEach(s => {
      assert(!s.getContext(), `Unexpected context in new slot`);

      let context = null;
      let sourceConnection = s.consumeConn.targetSlot && s.consumeConn.targetSlot.sourceConnection;
      if (sourceConnection) {
        let sourceConnSlot = this.getSlot(sourceConnection.particle, sourceConnection.name);
        if (sourceConnSlot) {
          context = sourceConnSlot.getInnerContext(s.consumeConn.name);
        }
      } else { // External slots provided at SlotComposer ctor (eg 'root')
        context = this._findContext(s.consumeConn.targetSlot.id);
      }

      this._slots.push(s);

      if (context) {
        s.updateContext(context);
      }
    });
  }

  _initHostedSlot(hostedSlotId, hostedParticle) {
    let transformationSlot = this._findSlotByHostedSlotId(hostedSlotId);
    if (!transformationSlot) {
      return false;
    }
    transformationSlot.initHostedSlot(hostedSlotId, hostedParticle);
    return true;
  }

  async renderSlot(particle, slotName, content) {
    let slot = this.getSlot(particle, slotName);
    if (slot) {
      // Set the slot's new content.
      await slot.setContent(content, eventlet => {
        this.arc.pec.sendEvent(particle, slotName, eventlet);
      });
      return;
    }

    if (this._renderHostedSlot(particle, slotName, content)) {
      return;
    }

    assert(slot, `Cannot find slot (or hosted slot) ${slotName} for particle ${particle.name}`);
  }

  _renderHostedSlot(particle, slotName, content) {
    let hostedSlot = this.findHostedSlot(particle, slotName);
    if (!hostedSlot) {
      return false;
    }
    let transformationSlot = this._findSlotByHostedSlotId(hostedSlot.slotId);
    assert(transformationSlot, `No transformation slot found for ${hostedSlot.slotId}`);

    this.arc.pec.innerArcRender(transformationSlot.consumeConn.particle, transformationSlot.consumeConn.name, hostedSlot.slotId,
      transformationSlot.formatHostedContent(hostedSlot, content));

    return true;
  }

  updateInnerSlots(slot) {
    assert(slot, 'Cannot update inner slots of null');
    // Update provided slot contexts.
    Object.keys(slot.consumeConn.providedSlots).forEach(providedSlotName => {
      let providedContext = slot.getInnerContext(providedSlotName);
      let providedSlot = slot.consumeConn.providedSlots[providedSlotName];
      providedSlot.consumeConnections.forEach(cc => {
        // This will trigger 'start' or 'stop' render, if applicable.
        this.getSlot(cc.particle, cc.name).updateContext(providedContext);
      });
    });
  }

  getAvailableSlots() {
    let availableSlots = this.arc.activeRecipe.slots.slice();

    this._contextSlots.forEach(contextSlot => {
      if (!availableSlots.find(s => s.id == contextSlot.id)) {
        availableSlots.push(contextSlot);
      }
    });
    return availableSlots;
  }

  dispose() {
    this._slots.forEach(slot => slot.dispose());
    this._slots.forEach(slot => slot.setContext(null));
    this._affordance.contextClass.dispose();
    this._contextSlots.forEach(contextSlot => this._affordance.contextClass.clear(contextSlot));
  }
}
<commit_msg>check for slot existence when updating inner slots (#1474)<commit_after>/**
 * @license
 * Copyright (c) 2017 Google Inc. All rights reserved.
 * This code may only be used under the BSD style license found at
 * http://polymer.github.io/LICENSE.txt
 * Code distributed by Google as part of this project is also
 * subject to an additional IP rights grant found at
 * http://polymer.github.io/PATENTS.txt
 */

import {assert} from '../platform/assert-web.js';
import {Affordance} from './affordance.js';

export class SlotComposer {
  /**
   * |options| must contain:
   * - affordance: the UI affordance the slots composer render to (for example: dom).
   * - rootContext: the context containing top level context to be used for slots.
   * and may contain:
   * - containerKind: the type of container wrapping each slot's context (for example, div).
   */
  constructor(options) {
    assert(options.affordance, 'Affordance is mandatory');
    assert(options.rootContext, 'Root context is mandatory');

    this._containerKind = options.containerKind;
    this._affordance = Affordance.forName(options.affordance);
    assert(this._affordance.slotClass);

    let slotContextByName = this._affordance.slotClass.findRootSlots(options.rootContext) || {};
    if (Object.keys(slotContextByName).length == 0) {
      // fallback to single 'root' slot using the rootContext.
      slotContextByName['root'] = options.rootContext;
    }

    this._contextSlots = [];
    Object.keys(slotContextByName).forEach(slotName => {
      this._contextSlots.push({id: `rootslotid-${slotName}`, name: slotName, tags: [`${slotName}`], context: slotContextByName[slotName], handleConnections: [], handles: 0, getProvidedSlotSpec: () => { return {isSet: false}; }});
    });

    this._slots = [];
  }

  get affordance() { return this._affordance.name; }

  getSlot(particle, slotName) {
    return this._slots.find(s => s.consumeConn.particle == particle && s.consumeConn.name == slotName);
  }

  _findContext(slotId) {
    let contextSlot = this._contextSlots.find(slot => slot.id == slotId);
    if (contextSlot) {
      return contextSlot.context;
    }
  }

  createHostedSlot(transformationParticle, transformationSlotName, hostedParticleName, hostedSlotName) {
    let hostedSlotId = this.arc.generateID();

    let transformationSlot = this.getSlot(transformationParticle, transformationSlotName);
    assert(transformationSlot,
           `Unexpected transformation slot particle ${transformationParticle.name}:${transformationSlotName}, hosted particle ${hostedParticleName}, slot name ${hostedSlotName}`);
    transformationSlot.addHostedSlot(hostedSlotId, hostedParticleName, hostedSlotName);
    return hostedSlotId;
  }
  _findSlotByHostedSlotId(hostedSlotId) {
    for (let slot of this._slots) {
      let hostedSlot = slot.getHostedSlot(hostedSlotId);
      if (hostedSlot) {
        return slot;
      }
    }
  }
  findHostedSlot(hostedParticle, hostedSlotName) {
    for (let slot of this._slots) {
      let hostedSlot = slot.findHostedSlot(hostedParticle, hostedSlotName);
      if (hostedSlot) {
        return hostedSlot;
      }
    }
  }

  initializeRecipe(recipeParticles) {
    let newSlots = [];
    // Create slots for each of the recipe's particles slot connections.
    recipeParticles.forEach(p => {
      Object.values(p.consumedSlotConnections).forEach(cs => {
        if (!cs.targetSlot) {
          assert(!cs.slotSpec.isRequired, `No target slot for particle's ${p.name} required consumed slot: ${cs.name}.`);
          return;
        }

        if (this._initHostedSlot(cs.targetSlot.id, p)) {
          // Skip slot creation for hosted slots.
          return;
        }

        let slot = new this._affordance.slotClass(cs, this.arc, this._containerKind);
        slot.startRenderCallback = this.arc.pec.startRender.bind(this.arc.pec);
        slot.stopRenderCallback = this.arc.pec.stopRender.bind(this.arc.pec);
        slot.innerSlotsUpdateCallback = this.updateInnerSlots.bind(this);
        newSlots.push(slot);
      });
    });

    // Attempt to set context for each of the slots.
    newSlots.forEach(s => {
      assert(!s.getContext(), `Unexpected context in new slot`);

      let context = null;
      let sourceConnection = s.consumeConn.targetSlot && s.consumeConn.targetSlot.sourceConnection;
      if (sourceConnection) {
        let sourceConnSlot = this.getSlot(sourceConnection.particle, sourceConnection.name);
        if (sourceConnSlot) {
          context = sourceConnSlot.getInnerContext(s.consumeConn.name);
        }
      } else { // External slots provided at SlotComposer ctor (eg 'root')
        context = this._findContext(s.consumeConn.targetSlot.id);
      }

      this._slots.push(s);

      if (context) {
        s.updateContext(context);
      }
    });
  }

  _initHostedSlot(hostedSlotId, hostedParticle) {
    let transformationSlot = this._findSlotByHostedSlotId(hostedSlotId);
    if (!transformationSlot) {
      return false;
    }
    transformationSlot.initHostedSlot(hostedSlotId, hostedParticle);
    return true;
  }

  async renderSlot(particle, slotName, content) {
    let slot = this.getSlot(particle, slotName);
    if (slot) {
      // Set the slot's new content.
      await slot.setContent(content, eventlet => {
        this.arc.pec.sendEvent(particle, slotName, eventlet);
      });
      return;
    }

    if (this._renderHostedSlot(particle, slotName, content)) {
      return;
    }

    assert(slot, `Cannot find slot (or hosted slot) ${slotName} for particle ${particle.name}`);
  }

  _renderHostedSlot(particle, slotName, content) {
    let hostedSlot = this.findHostedSlot(particle, slotName);
    if (!hostedSlot) {
      return false;
    }
    let transformationSlot = this._findSlotByHostedSlotId(hostedSlot.slotId);
    assert(transformationSlot, `No transformation slot found for ${hostedSlot.slotId}`);

    this.arc.pec.innerArcRender(transformationSlot.consumeConn.particle, transformationSlot.consumeConn.name, hostedSlot.slotId,
      transformationSlot.formatHostedContent(hostedSlot, content));

    return true;
  }

  updateInnerSlots(slot) {
    assert(slot, 'Cannot update inner slots of null');
    // Update provided slot contexts.
    Object.keys(slot.consumeConn.providedSlots).forEach(providedSlotName => {
      let providedContext = slot.getInnerContext(providedSlotName);
      let providedSlot = slot.consumeConn.providedSlots[providedSlotName];
      providedSlot.consumeConnections.forEach(cc => {
        // This will trigger 'start' or 'stop' render, if applicable.
        let innerSlot = this.getSlot(cc.particle, cc.name);
        if (innerSlot) {
          innerSlot.updateContext(providedContext);
        }
      });
    });
  }

  getAvailableSlots() {
    let availableSlots = this.arc.activeRecipe.slots.slice();

    this._contextSlots.forEach(contextSlot => {
      if (!availableSlots.find(s => s.id == contextSlot.id)) {
        availableSlots.push(contextSlot);
      }
    });
    return availableSlots;
  }

  dispose() {
    this._slots.forEach(slot => slot.dispose());
    this._slots.forEach(slot => slot.setContext(null));
    this._affordance.contextClass.dispose();
    this._contextSlots.forEach(contextSlot => this._affordance.contextClass.clear(contextSlot));
  }
}
<|endoftext|>


<commit_before>// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// 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
//
//      http://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.

/**
 * @fileoverview Utilities for creating and working with iframes
 * cross-browser.
 * @author gboyer@google.com (Garry Boyer)
 */

goog.provide('goog.dom.iframe');

goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.safe');
goog.require('goog.html.SafeHtml');
goog.require('goog.html.SafeStyle');
goog.require('goog.html.TrustedResourceUrl');
goog.require('goog.string.Const');
goog.require('goog.userAgent');

/**
 * Safe source for a blank iframe.
 *
 * Intentionally not about:blank for IE, which gives mixed content warnings in
 * IE6 over HTTPS. Using 'about:blank' for all other browsers to support Content
 * Security Policy (CSP). According to http://www.w3.org/TR/CSP/ CSP does not
 * allow inline javascript by default.
 *
 * @const {!goog.html.TrustedResourceUrl}
 */
goog.dom.iframe.BLANK_SOURCE_URL = goog.userAgent.IE ?
    goog.html.TrustedResourceUrl.fromConstant(
        goog.string.Const.from('javascript:""')) :
    goog.html.TrustedResourceUrl.fromConstant(
        goog.string.Const.from('about:blank'));

/**
 * Legacy version of goog.dom.iframe.BLANK_SOURCE_URL.
 * @const {string}
 */
goog.dom.iframe.BLANK_SOURCE =
    goog.html.TrustedResourceUrl.unwrap(goog.dom.iframe.BLANK_SOURCE_URL);

/**
 * Safe source for a new blank iframe that may not cause a new load of the
 * iframe. This is different from `goog.dom.iframe.BLANK_SOURCE` in that
 * it will allow an iframe to be loaded synchronously in more browsers, notably
 * Gecko, following the javascript protocol spec.
 *
 * NOTE: This should not be used to replace the source of an existing iframe.
 * The new src value will be ignored, per the spec.
 *
 * Due to cross-browser differences, the load is not guaranteed  to be
 * synchronous. If code depends on the load of the iframe,
 * then `goog.net.IframeLoadMonitor` or a similar technique should be
 * used.
 *
 * According to
 * http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#javascript-protocol
 * the 'javascript:""' URL should trigger a new load of the iframe, which may be
 * asynchronous. A void src, such as 'javascript:undefined', does not change
 * the browsing context document's, and thus should not trigger another load.
 *
 * Intentionally not about:blank, which also triggers a load.
 *
 * NOTE: 'javascript:' URL handling spec compliance varies per browser. IE
 * throws an error with 'javascript:undefined'. Webkit browsers will reload the
 * iframe when setting this source on an existing iframe.
 *
 * @const {!goog.html.TrustedResourceUrl}
 */
goog.dom.iframe.BLANK_SOURCE_NEW_FRAME_URL = goog.userAgent.IE ?
    goog.html.TrustedResourceUrl.fromConstant(
        goog.string.Const.from('javascript:""')) :
    goog.html.TrustedResourceUrl.fromConstant(
        goog.string.Const.from('javascript:undefined'));

/**
 * Legacy version of goog.dom.iframe.BLANK_SOURCE_NEW_FRAME_URL.
 * @const {string}
 */
goog.dom.iframe.BLANK_SOURCE_NEW_FRAME = goog.html.TrustedResourceUrl.unwrap(
    goog.dom.iframe.BLANK_SOURCE_NEW_FRAME_URL);

/**
 * Styles to help ensure an undecorated iframe.
 * @const {string}
 * @private
 */
goog.dom.iframe.STYLES_ = 'border:0;vertical-align:bottom;';

/**
 * Creates a completely blank iframe element.
 *
 * The iframe will not caused mixed-content warnings for IE6 under HTTPS.
 * The iframe will also have no borders or padding, so that the styled width
 * and height will be the actual width and height of the iframe.
 *
 * This function currently only attempts to create a blank iframe.  There
 * are no guarantees to the contents of the iframe or whether it is rendered
 * in quirks mode.
 *
 * @param {goog.dom.DomHelper} domHelper The dom helper to use.
 * @param {!goog.html.SafeStyle=} opt_styles CSS styles for the iframe.
 * @return {!HTMLIFrameElement} A completely blank iframe.
 */
goog.dom.iframe.createBlank = function(domHelper, opt_styles) {
  var styles;
  if (opt_styles) {
    // SafeStyle has to be converted back to a string for now, since there's
    // no safe alternative to createDom().
    styles = goog.html.SafeStyle.unwrap(opt_styles);
  } else {  // undefined.
    styles = '';
  }
  return domHelper.createDom(goog.dom.TagName.IFRAME, {
    'frameborder': 0,
    // Since iframes are inline elements, we must align to bottom to
    // compensate for the line descent.
    'style': goog.dom.iframe.STYLES_ + styles,
    'src': goog.dom.iframe.BLANK_SOURCE
  });
};

/**
 * Writes the contents of a blank iframe that has already been inserted
 * into the document.
 * @param {!HTMLIFrameElement} iframe An iframe with no contents, such as
 *     one created by {@link #createBlank}, but already appended to
 *     a parent document.
 * @param {!goog.html.SafeHtml} content Content to write to the iframe,
 *     from doctype to the HTML close tag.
 */
goog.dom.iframe.writeSafeContent = function(iframe, content) {
  var doc = goog.dom.getFrameContentDocument(iframe);
  doc.open();
  goog.dom.safe.documentWrite(doc, content);
  doc.close();
};

// TODO(gboyer): Provide a higher-level API for the most common use case, so
// that you can just provide a list of stylesheets and some content HTML.
/**
 * Creates a same-domain iframe containing preloaded content.
 *
 * This is primarily useful for DOM sandboxing.  One use case is to embed
 * a trusted JavaScript app with potentially conflicting CSS styles.  The
 * second case is to reduce the cost of layout passes by the browser -- for
 * example, you can perform sandbox sizing of characters in an iframe while
 * manipulating a heavy DOM in the main window.  The iframe and parent frame
 * can access each others' properties and functions without restriction.
 *
 * @param {!Element} parentElement The parent element in which to append the
 *     iframe.
 * @param {!goog.html.SafeHtml=} opt_headContents Contents to go into the
 *     iframe's head.
 * @param {!goog.html.SafeHtml=} opt_bodyContents Contents to go into the
 *     iframe's body.
 * @param {!goog.html.SafeStyle=} opt_styles CSS styles for the iframe itself,
 *     before adding to the parent element.
 * @param {boolean=} opt_quirks Whether to use quirks mode (false by default).
 * @return {!HTMLIFrameElement} An iframe that has the specified contents.
 */
goog.dom.iframe.createWithContent = function(
    parentElement, opt_headContents, opt_bodyContents, opt_styles, opt_quirks) {
  var domHelper = goog.dom.getDomHelper(parentElement);

  var content = goog.html.SafeHtml.create(
      'html', {},
      goog.html.SafeHtml.concat(
          goog.html.SafeHtml.create('head', {}, opt_headContents),
          goog.html.SafeHtml.create('body', {}, opt_bodyContents)));
  if (!opt_quirks) {
    content =
        goog.html.SafeHtml.concat(goog.html.SafeHtml.DOCTYPE_HTML, content);
  }

  var iframe = goog.dom.iframe.createBlank(domHelper, opt_styles);

  // Cannot manipulate iframe content until it is in a document.
  parentElement.appendChild(iframe);
  goog.dom.iframe.writeSafeContent(iframe, content);

  return iframe;
};
<commit_msg>Use goog.dom.safe in goog.dom.iframe.createBlank.<commit_after>// Copyright 2008 The Closure Library Authors. All Rights Reserved.
//
// 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
//
//      http://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.

/**
 * @fileoverview Utilities for creating and working with iframes
 * cross-browser.
 * @author gboyer@google.com (Garry Boyer)
 */

goog.provide('goog.dom.iframe');

goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.dom.safe');
goog.require('goog.html.SafeHtml');
goog.require('goog.html.SafeStyle');
goog.require('goog.html.TrustedResourceUrl');
goog.require('goog.string.Const');
goog.require('goog.userAgent');

/**
 * Safe source for a blank iframe.
 *
 * Intentionally not about:blank for IE, which gives mixed content warnings in
 * IE6 over HTTPS. Using 'about:blank' for all other browsers to support Content
 * Security Policy (CSP). According to http://www.w3.org/TR/CSP/ CSP does not
 * allow inline javascript by default.
 *
 * @const {!goog.html.TrustedResourceUrl}
 */
goog.dom.iframe.BLANK_SOURCE_URL = goog.userAgent.IE ?
    goog.html.TrustedResourceUrl.fromConstant(
        goog.string.Const.from('javascript:""')) :
    goog.html.TrustedResourceUrl.fromConstant(
        goog.string.Const.from('about:blank'));

/**
 * Legacy version of goog.dom.iframe.BLANK_SOURCE_URL.
 * @const {string}
 */
goog.dom.iframe.BLANK_SOURCE =
    goog.html.TrustedResourceUrl.unwrap(goog.dom.iframe.BLANK_SOURCE_URL);

/**
 * Safe source for a new blank iframe that may not cause a new load of the
 * iframe. This is different from `goog.dom.iframe.BLANK_SOURCE` in that
 * it will allow an iframe to be loaded synchronously in more browsers, notably
 * Gecko, following the javascript protocol spec.
 *
 * NOTE: This should not be used to replace the source of an existing iframe.
 * The new src value will be ignored, per the spec.
 *
 * Due to cross-browser differences, the load is not guaranteed  to be
 * synchronous. If code depends on the load of the iframe,
 * then `goog.net.IframeLoadMonitor` or a similar technique should be
 * used.
 *
 * According to
 * http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#javascript-protocol
 * the 'javascript:""' URL should trigger a new load of the iframe, which may be
 * asynchronous. A void src, such as 'javascript:undefined', does not change
 * the browsing context document's, and thus should not trigger another load.
 *
 * Intentionally not about:blank, which also triggers a load.
 *
 * NOTE: 'javascript:' URL handling spec compliance varies per browser. IE
 * throws an error with 'javascript:undefined'. Webkit browsers will reload the
 * iframe when setting this source on an existing iframe.
 *
 * @const {!goog.html.TrustedResourceUrl}
 */
goog.dom.iframe.BLANK_SOURCE_NEW_FRAME_URL = goog.userAgent.IE ?
    goog.html.TrustedResourceUrl.fromConstant(
        goog.string.Const.from('javascript:""')) :
    goog.html.TrustedResourceUrl.fromConstant(
        goog.string.Const.from('javascript:undefined'));

/**
 * Legacy version of goog.dom.iframe.BLANK_SOURCE_NEW_FRAME_URL.
 * @const {string}
 */
goog.dom.iframe.BLANK_SOURCE_NEW_FRAME = goog.html.TrustedResourceUrl.unwrap(
    goog.dom.iframe.BLANK_SOURCE_NEW_FRAME_URL);

/**
 * Styles to help ensure an undecorated iframe.
 * @const {string}
 * @private
 */
goog.dom.iframe.STYLES_ = 'border:0;vertical-align:bottom;';

/**
 * Creates a completely blank iframe element.
 *
 * The iframe will not caused mixed-content warnings for IE6 under HTTPS.
 * The iframe will also have no borders or padding, so that the styled width
 * and height will be the actual width and height of the iframe.
 *
 * This function currently only attempts to create a blank iframe.  There
 * are no guarantees to the contents of the iframe or whether it is rendered
 * in quirks mode.
 *
 * @param {goog.dom.DomHelper} domHelper The dom helper to use.
 * @param {!goog.html.SafeStyle=} opt_styles CSS styles for the iframe.
 * @return {!HTMLIFrameElement} A completely blank iframe.
 */
goog.dom.iframe.createBlank = function(domHelper, opt_styles) {
  var styles;
  if (opt_styles) {
    // SafeStyle has to be converted back to a string for now, since there's
    // no safe alternative to createDom().
    styles = goog.html.SafeStyle.unwrap(opt_styles);
  } else {  // undefined.
    styles = '';
  }
  var iframe = domHelper.createDom(goog.dom.TagName.IFRAME, {
    'frameborder': 0,
    // Since iframes are inline elements, we must align to bottom to
    // compensate for the line descent.
    'style': goog.dom.iframe.STYLES_ + styles
  });
  goog.dom.safe.setIframeSrc(iframe, goog.dom.iframe.BLANK_SOURCE_URL);
  return iframe;
};

/**
 * Writes the contents of a blank iframe that has already been inserted
 * into the document.
 * @param {!HTMLIFrameElement} iframe An iframe with no contents, such as
 *     one created by {@link #createBlank}, but already appended to
 *     a parent document.
 * @param {!goog.html.SafeHtml} content Content to write to the iframe,
 *     from doctype to the HTML close tag.
 */
goog.dom.iframe.writeSafeContent = function(iframe, content) {
  var doc = goog.dom.getFrameContentDocument(iframe);
  doc.open();
  goog.dom.safe.documentWrite(doc, content);
  doc.close();
};

// TODO(gboyer): Provide a higher-level API for the most common use case, so
// that you can just provide a list of stylesheets and some content HTML.
/**
 * Creates a same-domain iframe containing preloaded content.
 *
 * This is primarily useful for DOM sandboxing.  One use case is to embed
 * a trusted JavaScript app with potentially conflicting CSS styles.  The
 * second case is to reduce the cost of layout passes by the browser -- for
 * example, you can perform sandbox sizing of characters in an iframe while
 * manipulating a heavy DOM in the main window.  The iframe and parent frame
 * can access each others' properties and functions without restriction.
 *
 * @param {!Element} parentElement The parent element in which to append the
 *     iframe.
 * @param {!goog.html.SafeHtml=} opt_headContents Contents to go into the
 *     iframe's head.
 * @param {!goog.html.SafeHtml=} opt_bodyContents Contents to go into the
 *     iframe's body.
 * @param {!goog.html.SafeStyle=} opt_styles CSS styles for the iframe itself,
 *     before adding to the parent element.
 * @param {boolean=} opt_quirks Whether to use quirks mode (false by default).
 * @return {!HTMLIFrameElement} An iframe that has the specified contents.
 */
goog.dom.iframe.createWithContent = function(
    parentElement, opt_headContents, opt_bodyContents, opt_styles, opt_quirks) {
  var domHelper = goog.dom.getDomHelper(parentElement);

  var content = goog.html.SafeHtml.create(
      'html', {},
      goog.html.SafeHtml.concat(
          goog.html.SafeHtml.create('head', {}, opt_headContents),
          goog.html.SafeHtml.create('body', {}, opt_bodyContents)));
  if (!opt_quirks) {
    content =
        goog.html.SafeHtml.concat(goog.html.SafeHtml.DOCTYPE_HTML, content);
  }

  var iframe = goog.dom.iframe.createBlank(domHelper, opt_styles);

  // Cannot manipulate iframe content until it is in a document.
  parentElement.appendChild(iframe);
  goog.dom.iframe.writeSafeContent(iframe, content);

  return iframe;
};
<|endoftext|>