text:
<?php

/**
 *
 *
 * @package CRM
 * @copyright CiviCRM LLC (c) 2004-2015
 * $Id$
 *
 */

/**
 * This class generates form components for processing Event
 *
 */
class CRM_Event_Form_Registration_Confirm extends CRM_Event_Form_Registration {

  /**
   * The values for the contribution db object.
   *
   * @var array
   */
  public $_values;

  /**
   * The total amount.
   *
   * @var float
   */
  public $_totalAmount;

  /**
   * Set variables up before form is built.
   *
   * @return void
   */
  public function preProcess() {
    parent::preProcess();

    // lineItem isn't set until Register postProcess
    $this->_lineItem = $this->get('lineItem');

    $this->_params = $this->get('params');
    $this->_params[0]['tax_amount'] = $this->get('tax_amount');

    $this->_params[0]['is_pay_later'] = $this->get('is_pay_later');
    $this->assign('is_pay_later', $this->_params[0]['is_pay_later']);
    if ($this->_params[0]['is_pay_later']) {
      $this->assign('pay_later_receipt', $this->_values['event']['pay_later_receipt']);
    }

    CRM_Utils_Hook::eventDiscount($this, $this->_params);

    if (!empty($this->_params[0]['discount']) && !empty($this->_params[0]['discount']['applied'])) {
      $this->set('hookDiscount', $this->_params[0]['discount']);
      $this->assign('hookDiscount', $this->_params[0]['discount']);
    }

    if ($this->_contributeMode == 'express') {
      $params = array();
      // rfp == redirect from paypal
      $rfp = CRM_Utils_Request::retrieve('rfp', 'Boolean',
        CRM_Core_DAO::$_nullObject, FALSE, NULL, 'GET'
      );

      //we lost rfp in case of additional participant. So set it explicitly.
      if ($rfp || CRM_Utils_Array::value('additional_participants', $this->_params[0], FALSE)) {
        $payment = CRM_Core_Payment::singleton($this->_mode, $this->_paymentProcessor, $this);
        $paymentObjError = ts('The system did not record payment details for this payment and so could not process the transaction. Please report this error to the site administrator.');
        if (is_object($payment)) {
          $expressParams = $payment->getExpressCheckoutDetails($this->get('token'));
        }
        else {
          CRM_Core_Error::fatal($paymentObjError);
        }

        $params['payer'] = CRM_Utils_Array::value('payer', $expressParams);
        $params['payer_id'] = $expressParams['payer_id'];
        $params['payer_status'] = $expressParams['payer_status'];

        CRM_Core_Payment_Form::mapParams($this->_bltID, $expressParams, $params, FALSE);

        // fix state and country id if present
        if (isset($params["billing_state_province_id-{$this->_bltID}"])) {
          $params["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($params["billing_state_province_id-{$this->_bltID}"]);
        }
        if (isset($params['billing_country_id'])) {
          $params["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($params["billing_country_id-{$this->_bltID}"]);
        }

        // set a few other parameters for PayPal
        $params['token'] = $this->get('token');
        $params['amount'] = $this->_params[0]['amount'];
        if (!empty($this->_params[0]['discount'])) {
          $params['discount'] = $this->_params[0]['discount'];
          $params['discountAmount'] = $this->_params[0]['discountAmount'];
          $params['discountMessage'] = $this->_params[0]['discountMessage'];
        }
        if (!empty($this->_params[0]['amount_priceset_level_radio'])) {
          $params['amount_priceset_level_radio'] = $this->_params[0]['amount_priceset_level_radio'];
        }
        $params['amount_level'] = $this->_params[0]['amount_level'];
        $params['currencyID'] = $this->_params[0]['currencyID'];
        $params['payment_action'] = 'Sale';

        // also merge all the other values from the profile fields
        $values = $this->controller->exportValues('Register');
        $skipFields = array(
          'amount',
          "street_address-{$this->_bltID}",
          "city-{$this->_bltID}",
          "state_province_id-{$this->_bltID}",
          "postal_code-{$this->_bltID}",
          "country_id-{$this->_bltID}",
        );

        foreach ($values as $name => $value) {
          // skip amount field
          if (!in_array($name, $skipFields)) {
            $params[$name] = $value;
          }
          if (substr($name, 0, 6) == 'price_') {
            $params[$name] = $this->_params[0][$name];
          }
        }
        $this->set('getExpressCheckoutDetails', $params);
      }
      else {
        $params = $this->get('getExpressCheckoutDetails');
      }
      $this->_params[0] = $params;
      $this->_params[0]['is_primary'] = 1;
    }
    else {
      //process only primary participant params.
      $registerParams = $this->_params[0];
      if (isset($registerParams["billing_state_province_id-{$this->_bltID}"])
        && $registerParams["billing_state_province_id-{$this->_bltID}"]
      ) {
        $registerParams["billing_state_province-{$this->_bltID}"] = CRM_Core_PseudoConstant::stateProvinceAbbreviation($registerParams["billing_state_province_id-{$this->_bltID}"]);
      }

      if (isset($registerParams["billing_country_id-{$this->_bltID}"]) && $registerParams["billing_country_id-{$this->_bltID}"]) {
        $registerParams["billing_country-{$this->_bltID}"] = CRM_Core_PseudoConstant::countryIsoCode($registerParams["billing_country_id-{$this->_bltID}"]);
      }
      if (isset($registerParams['credit_card_exp_date'])) {
        $registerParams['year'] = CRM_Core_Payment_Form::getCreditCardExpirationYear($registerParams);
        $registerParams['month'] = CRM_Core_Payment_Form::getCreditCardExpirationMonth($registerParams);
      }
      if ($this->_values['event']['is_monetary']) {
        $registerParams['ip_address'] = CRM_Utils_System::ipAddress();
        $registerParams['currencyID'] = $this->_params[0]['currencyID'];
        $registerParams['payment_action'] = 'Sale';
      }
      //assign back primary participant params.
      $this->_params[0] = $registerParams;
    }

    if ($this->_values['event']['is_monetary']) {
      $this->_params[0]['invoiceID'] = $this->get('invoiceID');
    }
    $this->assign('defaultRole', FALSE);
    if (CRM_Utils_Array::value('defaultRole', $this->_params[0]) == 1) {
      $this->assign('defaultRole', TRUE);
    }

    if (empty($this->_params[0]['participant_role_id']) &&
      $this->_values['event']['default_role_id']
    ) {
      $this->_params[0]['participant_role_id'] = $this->_values['event']['default_role_id'];
    }

    if (isset($this->_values['event']['confirm_title'])) {
      CRM_Utils_System::setTitle($this->_values['event']['confirm_title']);
    }

    if ($this->_pcpId) {
      $params = CRM_Contribute_Form_Contribution_Confirm::processPcp($this, $this->_params[0]);
      $this->_params[0] = $params;
    }

    $this->set('params', $this->_params);
  }

  /**
   * Overwrite action, since we are only showing elements in frozen mode
   * no help display needed
   *
   * @return int
   */
  public function getAction() {
    if ($this->_action & CRM_Core_Action::PREVIEW) {
      return CRM_Core_Action::VIEW | CRM_Core_Action::PREVIEW;
    }
    else {
      return CRM_Core_Action::VIEW;
    }
  }

  /**
   * Build the form object.
   *
   * @return void
   */
  public function buildQuickForm() {
    $this->assignToTemplate();

    if ($this->_values['event']['is_monetary'] &&
      ($this->_params[0]['amount'] || $this->_params[0]['amount'] == 0)
    ) {
      $this->_amount = array();

      $taxAmount = 0;
      foreach ($this->_params as $k => $v) {
        if ($v == 'skip') {
          continue;
        }
        $individualTaxAmount = 0;
        //display tax amount on confirmation page
        $taxAmount += $v['tax_amount'];
        if (is_array($v)) {
          foreach (array(
                     'first_name',
                     'last_name',
                   ) as $name) {
            if (isset($v['billing_' . $name]) &&
              !isset($v[$name])
            ) {
              $v[$name] = $v['billing_' . $name];
            }
          }

          if (!empty($v['first_name']) && !empty($v['last_name'])) {
            $append = $v['first_name'] . ' ' . $v['last_name'];
          }
          else {
            //use an email if we have one
            foreach ($v as $v_key => $v_val) {
              if (substr($v_key, 0, 6) == 'email-') {
                $append = $v[$v_key];
              }
            }
          }

          $this->_amount[$k]['amount'] = $v['amount'];
          if (!empty($v['discountAmount'])) {
            $this->_amount[$k]['amount'] -= $v['discountAmount'];
          }

          $this->_amount[$k]['label'] = preg_replace('//', '', $v['amount_level']) . '  -  ' . $append;
          $this->_part[$k]['info'] = CRM_Utils_Array::value('first_name', $v) . ' ' . CRM_Utils_Array::value('last_name', $v);
          if (empty($v['first_name'])) {
            $this->_part[$k]['info'] = $append;
          }

          /*CRM-16320 */
          $individual[$k]['totalAmtWithTax'] = $this->_amount[$k]['amount'];
          $individual[$k]['totalTaxAmt'] = $individualTaxAmount + $v['tax_amount'];
          $this->_totalAmount = $this->_totalAmount + $this->_amount[$k]['amount'];
          if (!empty($v['is_primary'])) {
            $this->set('primaryParticipantAmount', $this->_amount[$k]['amount']);
          }
        }
      }

      $invoiceSettings = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings');
      $taxTerm = CRM_Utils_Array::value('tax_term', $invoiceSettings);
      $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings);
      if ($invoicing) {
        $this->assign('totalTaxAmount', $taxAmount);
        $this->assign('taxTerm', $taxTerm);
        $this->assign('individual', $individual);
        $this->set('individual', $individual);
      }

      $this->assign('part', $this->_part);
      $this->set('part', $this->_part);
      $this->assign('amounts', $this->_amount);
      $this->assign('totalAmount', $this->_totalAmount);
      $this->set('totalAmount', $this->_totalAmount);
    }

    if ($this->_priceSetId && !CRM_Core_DAO::getFieldValue('CRM_Price_DAO_PriceSet', $this->_priceSetId, 'is_quick_config')) {
      $lineItemForTemplate = array();
      $getTaxDetails = FALSE;
      if (!empty($this->_lineItem) && is_array($this->_lineItem)) {
        foreach ($this->_lineItem as $key => $value) {
          if (!empty($value)) {
            $lineItemForTemplate[$key] = $value;
          }
          if ($invoicing) {
            foreach ($value as $v) {
              if (isset($v['tax_rate'])) {
                $getTaxDetails = TRUE;
              }
            }
          }
        }
      }
      if (!empty($lineItemForTemplate)) {
        $this->assign('lineItem', $lineItemForTemplate);
      }
      $this->assign('getTaxDetails', $getTaxDetails);
    }

    //display additional participants profile.
    self::assignProfiles($this);

    //consider total amount.
    $this->assign('isAmountzero', ($this->_totalAmount <= 0) ? TRUE : FALSE);

    if ($this->_paymentProcessor['payment_processor_type'] == 'Google_Checkout' && empty($this->_params[0]['is_pay_later']) && !($this->_params[0]['amount'] == 0) &&
      !$this->_allowWaitlist && !$this->_requireApproval
    ) {
      $this->_checkoutButtonName = $this->getButtonName('next', 'checkout');
      $this->add('image',
        $this->_checkoutButtonName,
        $this->_paymentProcessor['url_button'],
        array('class' => 'crm-form-submit')
      );

      $this->addButtons(array(
          array(
            'type' => 'back',
            'name' => ts('Go Back'),
          ),
        )
      );
    }
    else {
      $contribButton = ts('Continue');
      $this->addButtons(array(
          array(
            'type' => 'back',
            'name' => ts('Go Back'),
          ),
          array(
            'type' => 'next',
            'name' => $contribButton,
            'isDefault' => TRUE,
            'js' => array('onclick' => "return submitOnce(this,'" . $this->_name . "','" . ts('Processing') . "');"),
          ),
        )
      );
    }

    $defaults = array();
    $fields = array();
    if (!empty($this->_fields)) {
      foreach ($this->_fields as $name => $dontCare) {
        $fields[$name] = 1;
      }
    }
    $fields["billing_state_province-{$this->_bltID}"] = $fields["billing_country-{$this->_bltID}"] = $fields["email-{$this->_bltID}"] = 1;
    foreach ($fields as $name => $dontCare) {
      if (isset($this->_params[0][$name])) {
        $defaults[$name] = $this->_params[0][$name];
        if (substr($name, 0, 7) == 'custom_') {
          $timeField = "{$name}_time";
          if (isset($this->_params[0][$timeField])) {
            $defaults[$timeField] = $this->_params[0][$timeField];
          }
          if (isset($this->_params[0]["{$name}_id"])) {
            $defaults["{$name}_id"] = $this->_params[0]["{$name}_id"];
          }
        }
        elseif (in_array($name, CRM_Contact_BAO_Contact::$_greetingTypes)
          && !empty($this->_params[0][$name . '_custom'])
        ) {
          $defaults[$name . '_custom'] = $this->_params[0][$name . '_custom'];
        }
      }
    }

    $this->setDefaults($defaults);
    $this->freeze();

    //lets give meaningful status message, CRM-4320.
    $this->assign('isOnWaitlist', $this->_allowWaitlist);
    $this->assign('isRequireApproval', $this->_requireApproval);

    // Assign Participant Count to Lineitem Table
    $this->assign('pricesetFieldsCount', CRM_Price_BAO_PriceSet::getPricesetCount($this->_priceSetId));
    $this->addFormRule(array('CRM_Event_Form_Registration_Confirm', 'formRule'), $this);
  }

  /**
   * @param $fields
   * @param $files
   * @param $self
   *
   * @return array|bool
   */
  public static function formRule($fields, $files, $self) {
    $errors = array();
    $eventFull = CRM_Event_BAO_Participant::eventFull($self->_eventId, FALSE, CRM_Utils_Array::value('has_waitlist', $self->_values['event']));
    if ($eventFull && empty($self->_allowConfirmation)) {
      if (empty($self->_allowWaitlist)) {
        CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/event/register', "reset=1&id={$self->_eventId}", FALSE, NULL, FALSE, TRUE));
      }
    }
    $self->_feeBlock = $self->_values['fee'];
    CRM_Event_Form_Registration_Register::formatFieldsForOptionFull($self);

    if (!empty($self->_priceSetId) &&
      !$self->_requireApproval && !$self->_allowWaitlist
      ) {
      $priceSetErrors = self::validatePriceSet($self, $self->_params);
      if (!empty($priceSetErrors)) {
        CRM_Core_Session::setStatus(ts('You have been returned to the start of the registration process and any sold out events have been removed from your selections. You will not be able to continue until you review your booking and select different events if you wish.'), ts('Unfortunately some of your options have now sold out for one or more participants.'), 'error');
        CRM_Core_Session::setStatus(ts('Please note that the options which are marked or selected are sold out for participant being viewed.'), ts('Sold out:'), 'error');
        CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/event/register', "_qf_Register_display=true&qfKey={$fields['qfKey']}"));
      }
    }

    return empty($priceSetErrors) ? TRUE : $priceSetErrors;
  }

  /**
   * Process the form submission.
   *
   *
   * @return void
   */
  public function postProcess() {
    $now = date('YmdHis');

    $this->_params = $this->get('params');
    if (!empty($this->_params[0]['contact_id'])) {
      // unclear when this would be set & whether it could be checked in getContactID.
      // perhaps it relates to when cid is in the url
      //@todo someone who knows add comments on the various contactIDs in this form
      $contactID = $this->_params[0]['contact_id'];
    }
    else {
      $contactID = $this->getContactID();
    }

    // if a discount has been applied, lets now deduct it from the amount
    // and fix the fee level
    if (!empty($this->_params[0]['discount']) && !empty($this->_params[0]['discount']['applied'])) {
      foreach ($this->_params as $k => $v) {
        if (CRM_Utils_Array::value('amount', $this->_params[$k]) > 0 && !empty($this->_params[$k]['discountAmount'])) {
          $this->_params[$k]['amount'] -= $this->_params[$k]['discountAmount'];
          $this->_params[$k]['amount_level'] .= CRM_Utils_Array::value('discountMessage', $this->_params[$k]);
        }
      }
      $this->set('params', $this->_params);
    }

    // CRM-4320, lets build array of cancelled additional participant ids
    // those are drop or skip by primary at the time of confirmation.
    // get all in and then unset those we want to process.
    $cancelledIds = $this->_additionalParticipantIds;

    $params = $this->_params;
    if ($this->_values['event']['is_monetary']) {
      $this->set('finalAmount', $this->_amount);
    }
    $participantCount = array();

    //unset the skip participant from params.
    //build the $participantCount array.
    //maintain record for all participants.
    foreach ($params as $participantNum => $record) {
      if ($record == 'skip') {
        unset($params[$participantNum]);
        $participantCount[$participantNum] = 'skip';
      }
      elseif ($participantNum) {
        $participantCount[$participantNum] = 'participant';
      }

      //lets get additional participant id to cancel.
      if ($this->_allowConfirmation && is_array($cancelledIds)) {
        $additonalId = CRM_Utils_Array::value('participant_id', $record);
        if ($additonalId && $key = array_search($additonalId, $cancelledIds)) {
          unset($cancelledIds[$key]);
        }
      }
    }

    $payment = $registerByID = $primaryCurrencyID = $contribution = NULL;
    $paymentObjError = ts('The system did not record payment details for this payment and so could not process the transaction. Please report this error to the site administrator.');

    $this->participantIDS = array();
    $fields = array();
    foreach ($params as $key => $value) {
      CRM_Event_Form_Registration_Confirm::fixLocationFields($value, $fields, $this);
      //unset the billing parameters if it is pay later mode
      //to avoid creation of billing location
      if ($this->_allowWaitlist
        || $this->_requireApproval
        || (!empty($value['is_pay_later']) && !$this->_isBillingAddressRequiredForPayLater)
        || empty($value['is_primary'])
      ) {
        $billingFields = array(
          "email-{$this->_bltID}",
          'billing_first_name',
          'billing_middle_name',
          'billing_last_name',
          "billing_street_address-{$this->_bltID}",
          "billing_city-{$this->_bltID}",
          "billing_state_province-{$this->_bltID}",
          "billing_state_province_id-{$this->_bltID}",
          "billing_postal_code-{$this->_bltID}",
          "billing_country-{$this->_bltID}",
          "billing_country_id-{$this->_bltID}",
          "address_name-{$this->_bltID}",
        );
        foreach ($billingFields as $field) {
          unset($value[$field]);
        }
        if (!empty($value['is_pay_later'])) {
          $this->_values['params']['is_pay_later'] = TRUE;
        }
      }

      //Unset ContactID for additional participants and set RegisterBy Id.
      if (empty($value['is_primary'])) {
        $contactID = CRM_Utils_Array::value('contact_id', $value);
        $registerByID = $this->get('registerByID');
        if ($registerByID) {
          $value['registered_by_id'] = $registerByID;
        }
      }
      else {
        $value['amount'] = $this->_totalAmount;
      }

      $contactID = CRM_Event_Form_Registration_Confirm::updateContactFields($contactID, $value, $fields, $this);

      // lets store the contactID in the session
      // we dont store in userID in case the user is doing multiple
      // transactions etc
      // for things like tell a friend
      if (!$this->getContactID() && !empty($value['is_primary'])) {
        $session = CRM_Core_Session::singleton();
        $session->set('transaction.userID', $contactID);
      }

      $value['description'] = ts('Online Event Registration') . ': ' . $this->_values['event']['title'];
      $value['accountingCode'] = CRM_Utils_Array::value('accountingCode',
        $this->_values['event']
      );

      $pending = FALSE;
      if ($this->_allowWaitlist || $this->_requireApproval) {
        //get the participant statuses.
        $waitingStatuses = CRM_Event_PseudoConstant::participantStatus(NULL, "class = 'Waiting'");
        if ($this->_allowWaitlist) {
          $value['participant_status_id'] = $value['participant_status'] = array_search('On waitlist', $waitingStatuses);
        }
        else {
          $value['participant_status_id'] = $value['participant_status'] = array_search('Awaiting approval', $waitingStatuses);
        }

        //there might be case user seleted pay later and
        //now becomes part of run time waiting list.
        $value['is_pay_later'] = FALSE;
      }

      // required only if paid event
      if ($this->_values['event']['is_monetary'] && !($this->_allowWaitlist || $this->_requireApproval)) {
        if (is_array($this->_paymentProcessor)) {
          $payment = CRM_Core_Payment::singleton($this->_mode, $this->_paymentProcessor, $this);
        }
        $result = NULL;

        if (!empty($value['is_pay_later']) ||
          $value['amount'] == 0 ||
          $this->_contributeMode == 'checkout' ||
          $this->_contributeMode == 'notify'
        ) {
          if ($value['amount'] != 0) {
            $pending = TRUE;
            //get the participant statuses.
            $pendingStatuses = CRM_Event_PseudoConstant::participantStatus(NULL, "class = 'Pending'");
            $status = !empty($value['is_pay_later']) ? 'Pending from pay later' : 'Pending from incomplete transaction';
            $value['participant_status_id'] = $value['participant_status'] = array_search($status, $pendingStatuses);
          }
        }
        elseif ($this->_contributeMode == 'express' && !empty($value['is_primary'])) {
          if (is_object($payment)) {
            $result = $payment->doExpressCheckout($value);
          }
          else {
            CRM_Core_Error::fatal($paymentObjError);
          }
        }
        elseif (!empty($value['is_primary'])) {
          CRM_Core_Payment_Form::mapParams($this->_bltID, $value, $value, TRUE);
          // payment email param can be empty for _bltID mapping
          // thus provide mapping for it with a different email value
          if (empty($value['email'])) {
            $value['email'] = CRM_Utils_Array::valueByRegexKey('/^email-/', $value);
          }

          if (is_object($payment)) {
            $result = $payment->doDirectPayment($value);
          }
          else {
            CRM_Core_Error::fatal($paymentObjError);
          }
        }

        if (is_a($result, 'CRM_Core_Error')) {
          CRM_Core_Error::displaySessionError($result);
          CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/event/register', "id={$this->_eventId}"));
        }

        if ($result) {
          $value = array_merge($value, $result);
        }

        $value['receive_date'] = $now;
        if ($this->_allowConfirmation) {
          $value['participant_register_date'] = $this->_values['participant']['register_date'];
        }

        $createContrib = ($value['amount'] != 0) ? TRUE : FALSE;
        // force to create zero amount contribution, CRM-5095
        if (!$createContrib && ($value['amount'] == 0)
          && $this->_priceSetId && $this->_lineItem
        ) {
          $createContrib = TRUE;
        }

        if ($createContrib && !empty($value['is_primary']) &&
          !$this->_allowWaitlist && !$this->_requireApproval
        ) {
          // if paid event add a contribution record
          //if primary participant contributing additional amount
          //append (multiple participants) to its fee level. CRM-4196.
          $isAdditionalAmount = FALSE;
          if (count($params) > 1) {
            $isAdditionalAmount = TRUE;
          }

          //passing contribution id is already registered.
          $contribution = self::processContribution($this, $value, $result, $contactID, $pending, $isAdditionalAmount);
          $value['contributionID'] = $contribution->id;
          $value['contributionTypeID'] = $contribution->financial_type_id;
          $value['receive_date'] = $contribution->receive_date;
          $value['trxn_id'] = $contribution->trxn_id;
          $value['contributionID'] = $contribution->id;
          $value['contributionTypeID'] = $contribution->financial_type_id;
        }
        $value['contactID'] = $contactID;
        $value['eventID'] = $this->_eventId;
        $value['item_name'] = $value['description'];
      }

      if (!empty($value['contributionID'])) {
        $this->_values['contributionId'] = $value['contributionID'];
      }

      //CRM-4453.
      if (!empty($value['is_primary'])) {
        $primaryCurrencyID = CRM_Utils_Array::value('currencyID', $value);
      }
      if (empty($value['currencyID'])) {
        $value['currencyID'] = $primaryCurrencyID;
      }

      // CRM-11182 - Confirmation page might not be monetary
      if ($this->_values['event']['is_monetary']) {
        if (!$pending && !empty($value['is_primary']) &&
          !$this->_allowWaitlist && !$this->_requireApproval
        ) {
          // transactionID & receive date required while building email template
          $this->assign('trxn_id', $value['trxn_id']);
          $this->assign('receive_date', CRM_Utils_Date::mysqlToIso($value['receive_date']));
          $this->set('receiveDate', CRM_Utils_Date::mysqlToIso($value['receive_date']));
          $this->set('trxnId', CRM_Utils_Array::value('trxn_id', $value));
        }
      }

      $value['fee_amount'] = CRM_Utils_Array::value('amount', $value);
      $this->set('value', $value);

      // handle register date CRM-4320
      if ($this->_allowConfirmation) {
        $registerDate = CRM_Utils_Array::value('participant_register_date', $params);
      }
      elseif (!empty($params['participant_register_date']) &&
        is_array($params['participant_register_date']) &&
        !empty($params['participant_register_date'])
      ) {
        $registerDate = CRM_Utils_Date::format($params['participant_register_date']);
      }
      else {
        $registerDate = date('YmdHis');
      }
      $this->assign('register_date', $registerDate);

      $this->confirmPostProcess($contactID, $contribution, $payment);
    }

    //handle if no additional participant.
    if (!$registerByID) {
      $registerByID = $this->get('registerByID');
    }

    $this->set('participantIDs', $this->_participantIDS);

    // create line items, CRM-5313
    if ($this->_priceSetId &&
      !empty($this->_lineItem)
    ) {
      // take all processed participant ids.
      $allParticipantIds = $this->_participantIDS;

      // when participant re-walk wizard.
      if ($this->_allowConfirmation &&
        !empty($this->_additionalParticipantIds)
      ) {
        $allParticipantIds = array_merge(array($registerByID), $this->_additionalParticipantIds);
      }

      $entityTable = 'civicrm_participant';
      $invoiceSettings = CRM_Core_BAO_Setting::getItem(CRM_Core_BAO_Setting::CONTRIBUTE_PREFERENCES_NAME, 'contribution_invoice_settings');
      $invoicing = CRM_Utils_Array::value('invoicing', $invoiceSettings);
      $totalTaxAmount = 0;
      $dataArray = array();
      foreach ($this->_lineItem as $key => $value) {
        if ($value == 'skip') {
          continue;
        }
        if ($entityId = CRM_Utils_Array::value($key, $allParticipantIds)) {
          // do cleanup line  items if participant re-walking wizard.
          if ($this->_allowConfirmation) {
            CRM_Price_BAO_LineItem::deleteLineItems($entityId, $entityTable);
          }
          $lineItem[$this->_priceSetId] = $value;
          CRM_Price_BAO_LineItem::processPriceSet($entityId, $lineItem, $contribution, $entityTable);
        }
        if ($invoicing) {
          foreach ($value as $line) {
            if (isset($line['tax_amount']) && isset($line['tax_rate'])) {
              $totalTaxAmount = $line['tax_amount'] + $totalTaxAmount;
              if (isset($dataArray[$line['tax_rate']])) {
                $dataArray[$line['tax_rate']] = $dataArray[$line['tax_rate']] + CRM_Utils_Array::value('tax_amount', $line);
              }
              else {
                $dataArray[$line['tax_rate']] = CRM_Utils_Array::value('tax_amount', $line);
              }
            }
          }
        }
      }
      if ($invoicing) {
        $this->assign('dataArray', $dataArray);
        $this->assign('totalTaxAmount', $totalTaxAmount);
      }
    }

    //update status and send mail to cancelled additonal participants, CRM-4320
    if ($this->_allowConfirmation && is_array($cancelledIds) && !empty($cancelledIds)) {
      $cancelledId = array_search('Cancelled',
        CRM_Event_PseudoConstant::participantStatus(NULL, "class = 'Negative'")
      );
      CRM_Event_BAO_Participant::transitionParticipants($cancelledIds, $cancelledId);
    }

    $isTest = FALSE;
    if ($this->_action & CRM_Core_Action::PREVIEW) {
      $isTest = TRUE;
    }

    // for Transfer checkout.
    if (($this->_contributeMode == 'checkout' ||
        $this->_contributeMode == 'notify'
      ) && empty($params[0]['is_pay_later']) &&
      !$this->_allowWaitlist && !$this->_requireApproval &&
      $this->_totalAmount > 0
    ) {

      $primaryParticipant = $this->get('primaryParticipant');

      if (empty($primaryParticipant['participantID'])) {
        $primaryParticipant['participantID'] = $registerByID;
      }

      //build an array of custom profile and assigning it to template
      $customProfile = CRM_Event_BAO_Event::buildCustomProfile($registerByID, $this->_values, NULL, $isTest);
      if (count($customProfile)) {
        $this->assign('customProfile', $customProfile);
        $this->set('customProfile', $customProfile);
      }

      // do a transfer only if a monetary payment greater than 0
      if ($this->_values['event']['is_monetary'] && $primaryParticipant) {
        if ($payment && is_object($payment)) {
          //CRM 14512 provide line items of all participants to payment gateway
          $primaryContactId = $this->get('primaryContactId');

          //build an array of cId/pId of participants
          $additionalIDs = CRM_Event_BAO_Event::buildCustomProfile($registerByID, NULL, $primaryContactId, $isTest, TRUE);

          //need to copy, since we are unsetting on the way.
          $copyParticipantCountLines = $participantCount;

          //lets carry all participant params w/ values.
          foreach ($additionalIDs as $participantID => $contactId) {
            $participantNum = NULL;
            $participantNum = $participantID;
            if ($participantID == $registerByID) {
              $participantNum = 0;  // is primary particpant
            }
            else {
              if ($participantNum = array_search('participant', $copyParticipantCountLines)) {
                //if no participant found break.
                if ($participantNum === NULL) {
                  break;
                }
                //unset current particpant so we don't check them again
                unset($copyParticipantCountLines[$participantNum]);
              }
            }
            // get values of line items
            if ($this->_amount) {
              $amount = array();
              $amount[$participantNum]['label'] = preg_replace('//', '', $params[$participantNum]['amount_level']);
              $amount[$participantNum]['amount'] = $params[$participantNum]['amount'];
              $params[$participantNum]['amounts'] = $amount;
            }

            if (!empty($this->_lineItem)) {
              $lineItems = $this->_lineItem;
              $lineItem = array();
              if ($lineItemValue = CRM_Utils_Array::value($participantNum, $lineItems)) {
                $lineItem[] = $lineItemValue;
              }
              $params[$participantNum]['lineItem'] = $lineItem;
            }

            //only add additional particpants and not the primary particpant as we already have that
            //added to $primaryParticipant so that this change doesn't break or require changes to
            //existing gateway implementations
            $primaryParticipant['participants_info'][$participantID] = $params[$participantNum];
          }

          //get event custom field information
          $groupTree = CRM_Core_BAO_CustomGroup::getTree('Event', $this, $this->_eventId, 0, $this->_values['event']['event_type_id']);
          $primaryParticipant['eventCustomFields'] = $groupTree;

          // call postprocess hook before leaving
          $this->postProcessHook();
          // this does not return
          $payment->doTransferCheckout($primaryParticipant, 'event');
        }
        else {
          CRM_Core_Error::fatal($paymentObjError);
        }
      }
    }
    else {
      //otherwise send mail Confirmation/Receipt
      $primaryContactId = $this->get('primaryContactId');

      //build an array of cId/pId of participants
      $additionalIDs = CRM_Event_BAO_Event::buildCustomProfile($registerByID,
        NULL, $primaryContactId, $isTest,
        TRUE
      );
      //lets send  mails to all with meaningful text, CRM-4320.
      $this->assign('isOnWaitlist', $this->_allowWaitlist);
      $this->assign('isRequireApproval', $this->_requireApproval);

      //need to copy, since we are unsetting on the way.
      $copyParticipantCount = $participantCount;

      //lets carry all paticipant params w/ values.
      foreach ($additionalIDs as $participantID => $contactId) {
        $participantNum = NULL;
        if ($participantID == $registerByID) {
          $participantNum = 0;
        }
        else {
          if ($participantNum = array_search('participant', $copyParticipantCount)) {
            unset($copyParticipantCount[$participantNum]);
          }
        }
        if ($participantNum === NULL) {
          break;
        }

        //carry the participant submitted values.
        $this->_values['params'][$participantID] = $params[$participantNum];
      }

      foreach ($additionalIDs as $participantID => $contactId) {
        $participantNum = 0;
        if ($participantID == $registerByID) {
          //set as Primary Participant
          $this->assign('isPrimary', 1);
          //build an array of custom profile and assigning it to template.
          $customProfile = CRM_Event_BAO_Event::buildCustomProfile($participantID, $this->_values, NULL, $isTest);

          if (count($customProfile)) {
            $this->assign('customProfile', $customProfile);
            $this->set('customProfile', $customProfile);
          }
          $this->_values['params']['additionalParticipant'] = FALSE;
        }
        else {
          //take the Additional participant number.
          if ($participantNum = array_search('participant', $participantCount)) {
            unset($participantCount[$participantNum]);
          }
          $this->assign('isPrimary', 0);
          $this->assign('customProfile', NULL);
          //Additional Participant should get only it's payment information
          if (!empty($this->_amount)) {
            $amount = array();
            $params = $this->get('params');
            $amount[$participantNum]['label'] = preg_replace('//', '', $params[$participantNum]['amount_level']);
            $amount[$participantNum]['amount'] = $params[$participantNum]['amount'];
            $this->assign('amounts', $amount);
          }
          if ($this->_lineItem) {
            $lineItems = $this->_lineItem;
            $lineItem = array();
            if ($lineItemValue = CRM_Utils_Array::value($participantNum, $lineItems)) {
              $lineItem[] = $lineItemValue;
            }
            if ($invoicing) {
              $individual = $this->get('individual');
              $dataArray[key($dataArray)] = $individual[$participantNum]['totalTaxAmt'];
              $this->assign('dataArray', $dataArray);
              $this->assign('totalAmount', $individual[$participantNum]['totalAmtWithTax']);
              $this->assign('totalTaxAmount', $individual[$participantNum]['totalTaxAmt']);
              $this->assign('individual', array($individual[$participantNum]));
            }
            $this->assign('lineItem', $lineItem);
          }
          $this->_values['params']['additionalParticipant'] = TRUE;
          $this->assign('isAdditionalParticipant', $this->_values['params']['additionalParticipant']);
        }

        //pass these variables since these are run time calculated.
        $this->_values['params']['isOnWaitlist'] = $this->_allowWaitlist;
        $this->_values['params']['isRequireApproval'] = $this->_requireApproval;

        //send mail to primary as well as additional participants.
        $this->assign('contactID', $contactId);
        $this->assign('participantID', $participantID);
        CRM_Event_BAO_Event::sendMail($contactId, $this->_values, $participantID, $isTest);
      }
    }
  }

  /**
   * Process the contribution.
   *
   * @param CRM_Core_Form $form
   * @param array $params
   * @param $result
   * @param int $contactID
   * @param bool $pending
   * @param bool $isAdditionalAmount
   *
   * @return void
   */
  public static function processContribution(
    &$form, $params, $result, $contactID,
    $pending = FALSE, $isAdditionalAmount = FALSE
  ) {
    $transaction = new CRM_Core_Transaction();

    $now = date('YmdHis');
    $receiptDate = NULL;

    if (!empty($form->_values['event']['is_email_confirm'])) {
      $receiptDate = $now;
    }
    //CRM-4196
    if ($isAdditionalAmount) {
      $params['amount_level'] = $params['amount_level'] . ts(' (multiple participants)') . CRM_Core_DAO::VALUE_SEPARATOR;
    }

    $contribParams = array(
      'contact_id' => $contactID,
      'financial_type_id' => !empty($form->_values['event']['financial_type_id']) ? $form->_values['event']['financial_type_id'] : $params['financial_type_id'],
      'receive_date' => $now,
      'total_amount' => $params['amount'],
      'tax_amount' => $params['tax_amount'],
      'amount_level' => $params['amount_level'],
      'invoice_id' => $params['invoiceID'],
      'currency' => $params['currencyID'],
      'source' => !empty($params['participant_source']) ? $params['participant_source'] : $params['description'],
      'is_pay_later' => CRM_Utils_Array::value('is_pay_later', $params, 0),
      'campaign_id' => CRM_Utils_Array::value('campaign_id', $params),
    );

    if (empty($params['is_pay_later'])) {
      $contribParams['payment_instrument_id'] = 1;
    }

    if (!$pending && $result) {
      $contribParams += array(
        'fee_amount' => CRM_Utils_Array::value('fee_amount', $result),
        'net_amount' => CRM_Utils_Array::value('net_amount', $result, $params['amount']),
        'trxn_id' => $result['trxn_id'],
        'receipt_date' => $receiptDate,
      );
    }

    $allStatuses = CRM_Contribute_PseudoConstant::contributionStatus(NULL, 'name');
    $contribParams['contribution_status_id'] = array_search('Completed', $allStatuses);
    if ($pending) {
      $contribParams['contribution_status_id'] = array_search('Pending', $allStatuses);
    }

    $contribParams['is_test'] = 0;
    if ($form->_action & CRM_Core_Action::PREVIEW || CRM_Utils_Array::value('mode', $params) == 'test') {
      $contribParams['is_test'] = 1;
    }

    $contribID = NULL;
    if (!empty($contribParams['invoice_id'])) {
      $contribID = CRM_Core_DAO::getFieldValue('CRM_Contribute_DAO_Contribution',
        $contribParams['invoice_id'],
        'id',
        'invoice_id'
      );
    }

    $ids = array();
    if ($contribID) {
      $ids['contribution'] = $contribID;
      $contribParams['id'] = $contribID;
    }

    //create an contribution address
    if ($form->_contributeMode != 'notify' && empty($params['is_pay_later'])) {
      $contribParams['address_id'] = CRM_Contribute_BAO_Contribution::createAddress($params, $form->_bltID);
    }

    // Prepare soft contribution due to pcp or Submit Credit / Debit Card Contribution by admin.
    if (!empty($params['pcp_made_through_id']) || !empty($params['soft_credit_to'])) {

      // if its due to pcp
      if (!empty($params['pcp_made_through_id'])) {
        $contribSoftContactId = CRM_Core_DAO::getFieldValue('CRM_PCP_DAO_PCP',
          $params['pcp_made_through_id'],
          'contact_id'
        );
      }
      else {
        $contribSoftContactId = CRM_Utils_Array::value('soft_credit_to', $params);
      }

      // Pass these details onto with the contribution to make them
      // available at hook_post_process, CRM-8908
      $contribParams['soft_credit_to'] = $params['soft_credit_to'] = $contribSoftContactId;
    }
    $contribParams['payment_processor'] = CRM_Utils_Array::value('payment_processor', $params);
    $contribParams['skipLineItem'] = 1;
    // create contribution record
    $contribution = CRM_Contribute_BAO_Contribution::add($contribParams, $ids);
    // CRM-11124
    CRM_Event_BAO_Participant::createDiscountTrxn($form->_eventId, $contribParams, CRM_Utils_Array::value('amount_priceset_level_radio', $params, NULL));

    // process soft credit / pcp pages
    CRM_Contribute_Form_Contribution_Confirm::processPcpSoft($params, $contribution);

    $transaction->commit();

    return $contribution;
  }

  /**
   * Fix the Location Fields.
   *
   * @param array $params
   * @param $fields
   * @param CRM_Core_Form $form
   *
   * @return void
   */
  public static function fixLocationFields(&$params, &$fields, &$form) {
    if (!empty($form->_fields)) {
      foreach ($form->_fields as $name => $dontCare) {
        $fields[$name] = 1;
      }
    }

    if (is_array($fields)) {
      if (!array_key_exists('first_name', $fields)) {
        $nameFields = array('first_name', 'middle_name', 'last_name');
        foreach ($nameFields as $name) {
          $fields[$name] = 1;
          if (array_key_exists("billing_$name", $params)) {
            $params[$name] = $params["billing_{$name}"];
            $params['preserveDBName'] = TRUE;
          }
        }
      }
    }

    // also add location name to the array
    if ($form->_values['event']['is_monetary']) {
      $params["address_name-{$form->_bltID}"] = CRM_Utils_Array::value('billing_first_name', $params) . ' ' . CRM_Utils_Array::value('billing_middle_name', $params) . ' ' . CRM_Utils_Array::value('billing_last_name', $params);
      $fields["address_name-{$form->_bltID}"] = 1;
    }
    $fields["email-{$form->_bltID}"] = 1;
    $fields['email-Primary'] = 1;

    //if its pay later or additional participant set email address as primary.
    if ((!empty($params['is_pay_later']) || empty($params['is_primary']) ||
        !$form->_values['event']['is_monetary'] ||
        $form->_allowWaitlist ||
        $form->_requireApproval
      ) && !empty($params["email-{$form->_bltID}"])
    ) {
      $params['email-Primary'] = $params["email-{$form->_bltID}"];
    }
  }

  /**
   * Update contact fields.
   *
   * @param int $contactID
   * @param array $params
   * @param $fields
   * @param CRM_Core_Form $form
   *
   * @return void
   */
  public static function updateContactFields($contactID, $params, $fields, &$form) {
    //add the contact to group, if add to group is selected for a
    //particular uf group

    // get the add to groups
    $addToGroups = array();

    if (!empty($form->_fields)) {
      foreach ($form->_fields as $key => $value) {
        if (!empty($value['add_to_group_id'])) {
          $addToGroups[$value['add_to_group_id']] = $value['add_to_group_id'];
        }
      }
    }

    // check for profile double opt-in and get groups to be subscribed
    $subscribeGroupIds = CRM_Core_BAO_UFGroup::getDoubleOptInGroupIds($params, $contactID);

    foreach ($addToGroups as $k) {
      if (array_key_exists($k, $subscribeGroupIds)) {
        unset($addToGroups[$k]);
      }
    }

    // since we are directly adding contact to group lets unset it from mailing
    if (!empty($addToGroups)) {
      foreach ($addToGroups as $groupId) {
        if (isset($subscribeGroupIds[$groupId])) {
          unset($subscribeGroupIds[$groupId]);
        }
      }
    }
    if ($contactID) {
      $ctype = CRM_Core_DAO::getFieldValue(
        'CRM_Contact_DAO_Contact',
        $contactID,
        'contact_type'
      );

      if (array_key_exists('contact_id', $params) && empty($params['contact_id'])) {
        // we unset this here because the downstream function ignores the contactID we give it
        // if it is set & it is difficult to understand the implications of 'fixing' this downstream
        // but if we are passing a contact id into this function it's reasonable to assume we don't
        // want it ignored
        unset($params['contact_id']);
      }

      $contactID = CRM_Contact_BAO_Contact::createProfileContact(
        $params,
        $fields,
        $contactID,
        $addToGroups,
        NULL,
        $ctype,
        TRUE
      );
    }
    else {

      foreach (CRM_Contact_BAO_Contact::$_greetingTypes as $greeting) {
        if (!isset($params[$greeting . '_id'])) {
          $params[$greeting . '_id'] = CRM_Contact_BAO_Contact_Utils::defaultGreeting('Individual', $greeting);
        }
      }

      $contactID = CRM_Contact_BAO_Contact::createProfileContact($params,
        $fields,
        NULL,
        $addToGroups,
        NULL,
        NULL,
        TRUE
      );
      $form->set('contactID', $contactID);
    }

    //get email primary first if exist
    $subscribtionEmail = array('email' => CRM_Utils_Array::value('email-Primary', $params));
    if (!$subscribtionEmail['email']) {
      $subscribtionEmail['email'] = CRM_Utils_Array::value("email-{$form->_bltID}", $params);
    }
    // subscribing contact to groups
    if (!empty($subscribeGroupIds) && $subscribtionEmail['email']) {
      CRM_Mailing_Event_BAO_Subscribe::commonSubscribe($subscribeGroupIds, $subscribtionEmail, $contactID);
    }

    return $contactID;
  }

  /**
   * @param $form
   */
  public static function assignProfiles(&$form) {
    $participantParams = $form->_params;
    $formattedValues = $profileFields = array();
    $count = 1;
    foreach ($participantParams as $participantNum => $participantValue) {
      if ($participantNum) {
        $prefix1 = 'additional';
        $prefix2 = 'additional_';
      }
      else {
        $prefix1 = '';
        $prefix2 = '';
      }
      if ($participantValue != 'skip') {
        //get the customPre profile info
        if (!empty($form->_values[$prefix2 . 'custom_pre_id'])) {
          $values = $groupName = array();
          CRM_Event_BAO_Event::displayProfile($participantValue,
            $form->_values[$prefix2 . 'custom_pre_id'],
            $groupName,
            $values,
            $profileFields
          );

          if (count($values)) {
            $formattedValues[$count][$prefix1 . 'CustomPre'] = $values;
          }
          $formattedValues[$count][$prefix1 . 'CustomPreGroupTitle'] = CRM_Utils_Array::value('groupTitle', $groupName);
        }
        //get the customPost profile info
        if (!empty($form->_values[$prefix2 . 'custom_post_id'])) {
          $values = $groupName = array();
          foreach ($form->_values[$prefix2 . 'custom_post_id'] as $gids) {
            $val = array();
            CRM_Event_BAO_Event::displayProfile($participantValue,
              $gids,
              $group,
              $val,
              $profileFields
            );
            $values[$gids] = $val;
            $groupName[$gids] = $group;
          }

          if (count($values)) {
            $formattedValues[$count][$prefix1 . 'CustomPost'] = $values;
          }

          if (isset($formattedValues[$count][$prefix1 . 'CustomPre'])) {
            $formattedValues[$count][$prefix1 . 'CustomPost'] = array_diff_assoc($formattedValues[$count][$prefix1 . 'CustomPost'],
              $formattedValues[$count][$prefix1 . 'CustomPre']
            );
          }

          $formattedValues[$count][$prefix1 . 'CustomPostGroupTitle'] = $groupName;
        }
        $count++;
      }
      $form->_fields = $profileFields;
    }
    if (!empty($formattedValues)) {
      $form->assign('primaryParticipantProfile', $formattedValues[1]);
      $form->set('primaryParticipantProfile', $formattedValues[1]);
      if ($count > 2) {
        unset($formattedValues[1]);
        $form->assign('addParticipantProfile', $formattedValues);
        $form->set('addParticipantProfile', $formattedValues);
      }
    }
  }

}


text:
/******************************************************************************
 *                    _   _____   __________                                  *
 *                   | | / / _ | / __/_  __/     Visibility                   *
 *                   | |/ / __ |_\ \  / /          Across                     *
 *                   |___/_/ |_/___/ /_/       Space and Time                 *
 *                                                                            *
 * This file is part of VAST. It is subject to the license terms in the       *
 * LICENSE file found in the top-level directory of this distribution and at  *
 * http://vast.io/license. No part of VAST, including this file, may be       *
 * copied, modified, propagated, or distributed except according to the terms *
 * contained in the LICENSE file.                                             *
 ******************************************************************************/

#pragma once

#include "vast/data.hpp"

#include "vast/concept/printable/numeric.hpp"
#include "vast/concept/printable/print.hpp"
#include "vast/concept/printable/string.hpp"
#include "vast/concept/printable/core/printer.hpp"
#include "vast/concept/printable/std/chrono.hpp"
#include "vast/concept/printable/vast/address.hpp"
#include "vast/concept/printable/vast/subnet.hpp"
#include "vast/concept/printable/vast/pattern.hpp"
#include "vast/concept/printable/vast/port.hpp"
#include "vast/concept/printable/vast/none.hpp"
#include "vast/concept/printable/vast/type.hpp"
#include "vast/detail/escapers.hpp"
#include "vast/detail/overload.hpp"
#include "vast/detail/string.hpp"

namespace vast {

struct data_printer : printer<data_printer> {
  using attribute = data;

  template <class Iterator>
  bool print(Iterator& out, const data& d) const {
    return caf::visit(detail::overload(
      [&](const auto& x) {
        return make_printer<std::decay_t<decltype(x)>>{}(out, x);
      },
      [&](integer x) {
        return printers::integral<integer, policy::force_sign>(out, x);
      },
      [&](const std::string& x) {
        static auto escaper = detail::make_extra_print_escaper("\"");
        static auto p = '"' << printers::escape(escaper) << '"';
        return p(out, x);
      }
    ), d);
  }
};

template <>
struct printer_registry<data> {
  using type = data_printer;
};

namespace printers {
  auto const data = data_printer{};
} // namespace printers

struct vector_printer : printer<vector_printer> {
  using attribute = vector;

  template <class Iterator>
  bool print(Iterator& out, const vector& v) const {
    auto p = '[' << ~(data_printer{} % ", ") << ']';
    return p.print(out, v);
  }
};

template <>
struct printer_registry<vector> {
  using type = vector_printer;
};

struct set_printer : printer<set_printer> {
  using attribute = set;

  template <class Iterator>
  bool print(Iterator& out, const set& s) const {
    auto p = '{' << ~(data_printer{} % ", ") << '}';
    return p.print(out, s);
  }
};

template <>
struct printer_registry<set> {
  using type = set_printer;
};

struct map_printer : printer<map_printer> {
  using attribute = map;

  template <class Iterator>
  bool print(Iterator& out, const map& xs) const {
    if (xs.empty())
      return printers::str.print(out, "{-}");
    auto kvp = printers::data << " -> " << printers::data;
    auto p = '{' << (kvp % ", ") << '}';
    return p.print(out, xs);
  }
};

template <>
struct printer_registry<map> {
  using type = map_printer;
};

} // namespace vast

redpajama_set_name: RedPajamaGithub


text:
namespace Enterprise.Northwind.Forms
{
    using Serenity.ComponentModel;
    using System;
    using System.ComponentModel;

    [ColumnsScript("Northwind.Region")]
    [BasedOnRow(typeof(Entities.RegionRow))]
    public class RegionColumns
    {
        [EditLink, DisplayName("Db.Shared.RecordId"), AlignRight]
        public Int32 RegionID { get; set; }
        [EditLink, Width(300)]
        public String RegionDescription { get; set; }
    }
}


text:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="material_margin_horizontal">16dp</dimen>
    <dimen name="material_margin_vertical">16dp</dimen>
    <dimen name="material_list_item_height">48dp</dimen>
    <dimen name="material_space_between_containers">8dp</dimen>
    <dimen name="material_status_bar_height">24dp</dimen>
    <dimen name="material_menu_item_aspect">24dp</dimen>
    <dimen name="material_drawer_top_margin">36dp</dimen>
    <dimen name="drawer_header_height">128dp</dimen>
    <dimen name="drawer_image_aspect">56dp</dimen>
</resources>


text:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<title>Uses of Class org.deidentifier.arx.metric.v2.AbstractILMultiDimensionalReduced (ARX GUI Documentation)</title>
<link rel="stylesheet" type="text/css" href="../../../../../../stylesheet.css" title="Style">

JavaScript is disabled on your browser.

Overview

Package

Class

Use

Tree

Deprecated

Index

Help

Prev

Next

Frames

No Frames

All Classes

Uses of Class
org.deidentifier.arx.metric.v2.AbstractILMultiDimensionalReduced

Packages that use AbstractILMultiDimensionalReduced

Package
Description

org.deidentifier.arx.metric.v2

Uses of AbstractILMultiDimensionalReduced in org.deidentifier.arx.metric.v2

Subclasses of AbstractILMultiDimensionalReduced in org.deidentifier.arx.metric.v2

Modifier and Type
Class and Description

class
ILMultiDimensionalArithmeticMean

This class implements an information loss which can be represented as a
 decimal number per quasi-identifier.

class
ILMultiDimensionalGeometricMean

This class implements an information loss which can be represented as a
 decimal number per quasi-identifier.

class
ILMultiDimensionalMax

This class implements an information loss which can be represented as a
 decimal number per quasi-identifier.

class
ILMultiDimensionalSum

This class implements an information loss which can be represented as a
 decimal number per quasi-identifier.

Methods in org.deidentifier.arx.metric.v2 that return AbstractILMultiDimensionalReduced

Modifier and Type
Method and Description

protected AbstractILMultiDimensionalReduced
AbstractILMultiDimensionalReduced.convert(InformationLoss<?> other)

Converter method.

Overview

Package

Class

Use

Tree

Deprecated

Index

Help

Prev

Next

Frames

No Frames

All Classes


text:
 //just an example script - to be moved into other repo

 WoT.newThing("counter")
    .then(function(thing) {
        console.log("created " + thing.name);

        thing
        .addProperty("count", { type: "integer" })
        .setProperty("count",0)
        .onUpdateProperty("count",
            function(newValue, oldValue) {
                console.log(oldValue + " -> " + newValue);
                var message = (oldValue < newValue)? "increased " : "decreased";
                console.log("counter " + message + " to " + newValue);
            }
         );

         thing
         .addAction("increment")
         .onInvokeAction("increment", function() {
            console.log("incrementing counter");
            var value = thing.getProperty("count") + 1;
            thing.setProperty("count", value);
            return value;
         });

        thing
        .addAction("decrement")
        .onInvokeAction("decrement", function() {
             console.log("decrementing counter");
             var value = thing.getProperty("count") - 1;
             thing.setProperty("count", value);
             return value;
        });
    });


text:
v2.0.0 - For a detailed documentation of the changes, please see the [change log](./CHANGELOG.md).

## Introduction

This project is focused on comparing the performance of web servers implemented in a small subset of programming languages. The project currently has benchmarks for:

- .NET Core
- Clojure
- Go
- NodeJS

A comparison between the languages can be found in the [benchmark](./BENCHMARK.md).

## Web Server

The HTTP API of the web server is defined in the following chapters, and the benchmark will run each of these API aspects in sequence.

## Health Benchmark

Route returning empty responses back to the client. This benchmark is focused on determining the performance of the web server and its efficiency in handling the request and response pipeline.

![Health benchmark UML](https://g.gravizo.com/source/custom_health_uml?https%3A%2F%2Fraw.githubusercontent.com%2FFantasticFiasco%2Fweb-server-tech-benchmark%2Fmaster%2Fdesign%2FUML.md)

### Health Service

The following HTTP API should be implemented by the web server.

#### Request

```
GET /health HTTP/1.1
```

#### Response

```
204 No Content
```

## Echo Benchmark

Route echoing a text back to the client. This benchmark is focused on reading incoming requests and return responses based on the parameters in the request URI.

![Echo benchmark UML](https://g.gravizo.com/source/custom_echo_uml?https%3A%2F%2Fraw.githubusercontent.com%2FFantasticFiasco%2Fweb-server-tech-benchmark%2Fmaster%2Fdesign%2FUML.md)

### Echo Service

The following HTTP API should be implemented by the web server.

#### Request

```
GET /echo/{text} HTTP/1.1
```

#### Response

```
200 OK
Content-Type: text/plain

{text}
```

## Relay Benchmark

Route acting as a relay between a client and another service on the network. This benchmark reads URI parameters, i.e. keys, from incoming requests, passes the keys on to a key/value service, and then responds with the keys and their values to the client. It highlights latency penalties associated with service dependencies.

![Relay benchmark UML](https://g.gravizo.com/source/custom_relay_uml?https%3A%2F%2Fraw.githubusercontent.com%2FFantasticFiasco%2Fweb-server-tech-benchmark%2Fmaster%2Fdesign%2FUML.md)

### Key/Value Service

The key/value service is already implemented, and has the following HTTP API.

#### Request

```
GET /store/{key} HTTP/1.1
```

#### Response

```
200 OK
Content-Type: application/json

{
    "value": "{value}"
}
```

### Relay Service

The following HTTP API should be implemented by the web server.

#### Environment Variables

The relay service is dependent on the key/value service, and the implementation of the relay service should expect that the following environment variables are set.

| Name                          | Description                                         |
| ----------------------------- | --------------------------------------------------- |
| Relay_KeyValueServiceHostname | The hostname or IP address of the key/value service |
| Relay_KeyValueServicePort     | The port of the key/value service                   |

#### Request

```
GET /relay/{key} HTTP/1.1
```

The key should be used in a request to the key/value service.

#### Response

```
200 OK
Content-Type: application/json

{
    "key": "{key}",
    "value": "{value}"
}
```

## Contacts Benchmark

Routes describing a standard CRUD application that stores its state in a database. This benchmark is focused on database operations and highlights latency penalties associated with using a database.

![Contacts benchmark UML](https://g.gravizo.com/source/custom_contacts_uml?https%3A%2F%2Fraw.githubusercontent.com%2FFantasticFiasco%2Fweb-server-tech-benchmark%2Fmaster%2Fdesign%2FUML.md)

### Database

The database has the following schema.

```
CREATE TABLE contact
(
  id integer NOT NULL DEFAULT nextval('contact_id_seq'::regclass),
  firstname character varying,
  surname character varying,
  CONSTRAINT contact_pkey PRIMARY KEY (id)
)
```

### Contacts Service

The following HTTP API should be implemented by the web server.

#### Environment Variables

The contacts service is dependent on a database, and the implementation of the service should expect that the following environment variables are set.

| Name                      | Description                                       |
| ------------------------- | ------------------------------------------------- |
| Contacts_DatabaseHost     | The host of the database                          |
| Contacts_DatabaseName     | The name of the database                          |
| Contacts_DatabaseUsername | The username used when connecting to the database |
| Contacts_DatabasePassword | The password used when connecting to the database |

#### Create Contact Request

```
POST /contacts HTTP/1.1
Content-Type: application/json

{
    "firstName": "{firstName}",
    "surname": "{surname}"
}
```

#### Create Contact Response

```
201 Created
Content-Type: application/json
Location: http://{hostname}:{port}/contacts/{id}

{
    "id": {id},
    "firstName": "{firstName}",
    "surname": "{surname}"
}
```

#### Get Contact Request

```
GET /contacts/{id} HTTP/1.1
```

#### Get Contact Response

```
200 OK
Content-Type: application/json

{
    "id": {id},
    "firstName":"{firstName}",
    "surname":"{surname}"
}
```

#### Get Contacts Request

```
GET /contacts HTTP/1.1
```

#### Get Contacts Response

```
200 OK
Content-Type: application/json

[
    {
        "id": {id},
        "firstName":"{firstName}",
        "surname":"{surname}"
    }
]
```

#### Remove Contact Request

```
DELETE /contacts/{id} HTTP/1.1
```

#### Remove Contact Response

```
204 No Content
```


text:
function Queue() {
    this.requests = {};
    this.refreshTime = 5;
    this.timeout = 4000;
}

Queue.prototype.enqueue = function (key, ajaxRequest) {
    console.log("Adding: " + key);
    this.requests[key] = ajaxRequest;
};

Queue.prototype.remove = function (key) {
    console.log("Removing: " + key);
    delete this.requests[key];
};

Queue.prototype.start = function () {
    setInterval($.proxy(this.executeCalls, this),
        this.refreshTime * 1000);
};

Queue.prototype.executeCalls = function () {
    for (var property in this.requests) {
        if (this.requests.hasOwnProperty(property)) {
            this.requests[property]();
        }
    }
};

function Tile() {
    this.id = null;
    this.type = null;
    this.tables = null;
    this.table = null;
    this.title = "";
    this.displayWidth = null;
    this.displayHeight = null;
    this.width = 0;
    this.height = 0;
    this.query = null;
    this.refresh = true;
    this.queue = null;
    this.cachedData = null;
    this.setupModalName = null;
    this.url = hostDetails.url("/foxtrot/v1/analytics");
    this.contentType = "application/json";
    this.httpMethod = "POST";
    this.ignoreDigits = 0;

}

Tile.prototype.init = function (id, queue, tables) {
    this.id = id;
    this.queue = queue;
    this.tables = tables;
    if (this.refresh) {
        queue.enqueue(this.id, $.proxy(this.reloadData, this));
    }
}

Tile.prototype.cleanup = function () {
    if (this.queue) {
        this.queue.remove(this.id);
    }
};

Tile.prototype.reloadData = function () {
    this.query = this.getQuery();
    if (!this.query) {
        console.log("did not update since query setup is not complete " + this.id);
        return;
    }
    $.ajax({
        method: this.httpMethod,
        dataType: 'json',
        accepts: {
            json: 'application/json'
        },
        url: this.url,
        contentType: this.contentType,
        timeout: this.queue.timeout,
        data: this.query,
        success: $.proxy(this.newDataReceived, this)
    });
};

Tile.prototype.newDataReceived = function (data) {
    this.cachedData = data;
    this.render(data, true);
};

Tile.prototype.handleResize = function (event, ui) {
    this.render(this.cachedData, false);
};

Tile.prototype.getRepresentation = function () {
    var chartAreaId = "#content-for-" + this.id;
    var chartContent = $("#" + this.id).find(chartAreaId);
    this.height = chartContent.parent().height();
    this.width = chartContent.parent().width();
    var representation = {
        id: this.id,
        typeName: this.typeName,
        width: this.width,
        height: this.height,
        title: this.title,
        table: this.table,
        ignoreDigits: this.ignoreDigits
    };
    this.registerSpecificData(representation);
    return representation;
};

Tile.prototype.loadTileFromRepresentation = function (representation) {
    this.id = representation.id;
    this.width = representation.width;
    this.height = representation.height;
    this.title = representation.title;
    this.table = representation.table;
    this.ignoreDigits = representation.hasOwnProperty('ignoreDigits')
                            ? representation.ignoreDigits : 0;
    this.loadSpecificData(representation);
}

Tile.prototype.isSetupDone = function () {
    return false;
};

Tile.prototype.render = function (data, animate) {

};

Tile.prototype.configChanged = function () {
    console.log("Widget config changed");
};

Tile.prototype.getQuery = function () {
    return this.query;
}

Tile.prototype.loadTableList = function () {
    var selected_table_tag = $(this.setupModalName).find(".tile-table").first();
    selected_table_tag.find('option').remove();

    for (var i = this.tables.tables.length - 1; i >= 0; i--) {
        selected_table_tag.append('<option>' + this.tables.tables[i].name + '</option>');
    }

    selected_table_tag.val(this.table);
    selected_table_tag.selectpicker('refresh');
};

Tile.prototype.populateSetupDialog = function () {

};

Tile.prototype.registerSpecificData = function (representation) {
    console.log("Base representation called for " + this.typeName + ": " + this.id);
};

Tile.prototype.loadSpecificData = function (representation) {
    console.log("Base load called for " + this.typeName + ": " + this.id)
};

Tile.prototype.registerComplete = function () {
}

Tile.prototype.getUniqueValues = function () {
}

Tile.prototype.filterValues = function (values) {
}

Tile.prototype.customInterval = function() {
    return $("#" + this.id).find(".period-select").val();
}

function TileSet(id, tables) {
    this.id = id;
    this.tables = tables;
    this.currentTiles = {};
}

TileSet.prototype.closeHandler = function (eventData) {
    var tileId = eventData.currentTarget.parentNode.parentNode.parentNode.getAttribute('id');
    this.unregister(tileId);
    var tileContainer = $(this.id);
    if (tileContainer.find(".tile").length == 0) {
        tileContainer.find(".removable-text").css("display", "block");
    }
};

TileSet.prototype.register = function (tile) {
    var tileContainer = $(this.id);
    this.currentTiles[tile.id] = tile;
    var newDiv = $(handlebars("#tile-template", {tileId: tile.id, title: tile.title}));
    tileContainer.find(".removable-text").css("display", "none");
    newDiv.insertBefore('.float-clear');
    $(newDiv).resizable();
    if (tile.displayWidth && tile.displayHeight) {
        newDiv.height(tile.displayHeight);
        newDiv.width(tile.displayWidth);
    }
    newDiv.find(".widget-toolbox").find(".glyphicon-remove").click($.proxy(this.closeHandler, this));
    newDiv.find(".widget-toolbox").find(".glyphicon-cog").click(function () {
        if (tile.setupModalName) {
            var modal = null;
            modal = $(tile.setupModalName);
            modal.find(".tileId").val(tile.id);
            var form = modal.find("form");
            form.off('submit');
            form.on('submit', $.proxy(function (e) {
                this.configChanged();
                $(this.setupModalName).modal('hide');
                if (tile.isSetupDone()) {
                    $("#" + tile.id).removeClass("tile-drag");
                }
                e.preventDefault();
            }, tile));
            tile.populateSetupDialog();
            modal.modal('show');
        }
    });
    newDiv.find(".widget-toolbox").find(".glyphicon-fullscreen").click($.proxy(function () {
        launchIntoFullscreen(document.getElementById("content-for-" + this.id));
    }, tile));
    newDiv.find(".widget-toolbox").find(".glyphicon-filter").click(function () {
        var modal = $("#setupFiltersModal");
        modal.find(".tileId").val(tile.id);
        var fv = $("#filter_values");
        fv.multiselect('dataprovider', tile.getUniqueValues());
        fv.multiselect('refresh');
        var form = modal.find("form");
        form.off('submit');
        form.on('submit', $.proxy(function (e) {
            this.filterValues($("#filter_values").val());
            $("#setupFiltersModal").modal('hide');
            e.preventDefault();
        }, tile));
        tile.populateSetupDialog();
        modal.modal('show');
    });

    if (!tile.isSetupDone()) {
        newDiv.addClass("tile-drag");
    }
    tile.registerComplete();

};

TileSet.prototype.unregister = function (tileId) {
    var tile = this.currentTiles[tileId];
    if (!tile) {
        return;
    }
    $("#" + tileId).remove();
    tile.cleanup();
    delete this.currentTiles[tileId];
};

function GenericTile() {
    this.query = JSON.stringify({
        opcode: "group",
        table: "abcd",
        nesting: ["header.configName", "data.name"]
    });
    this.refresh = true;
}

GenericTile.prototype = new Tile();

function TileFactory() {
}

TileFactory.create = function (type) {
    if (type === "donut") {
        return new DonutTile();
    } else if (type === "bar") {
        return new BarTile();
    } else if (type === "histogram") {
        return new Histogram();
    } else if (type === "eventbrowser") {
        return new EventBrowser();
    } else if (type === "stacked_bar") {
        return new StackedBar();
    } else if (type === "stats") {
        return new Stats();
    } else if (type === "statstrend") {
        return new StatsTrend();
    } else if (type === 'fql_table') {
        return new FqlTable()
    } else if (type == 'stacked') {
        return new Stacked();
    }
};

function extractSelectedTable(tableName, tableList) {
    for (var i = tableList.length - 1; i >= 0; i--) {
        if (tableList[i].name === tableName) {
            return tableList[i];
        }
    }
}


text:
<!DOCTYPE html>

  <meta charset="UTF-8">
  <title>Dashboard for D:\xampp\htdocs\Zf2Unitest/vendor/zendframework/zendframework/library/Zend/Filter</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link href="css/bootstrap.min.css" rel="stylesheet">
  <link href="css/bootstrap-responsive.min.css" rel="stylesheet">
  <link href="css/style.css" rel="stylesheet">

D:\xampp\htdocs\Zf2Unitest /

vendor /

zendframework /

zendframework /

library /

Zend /

Filter

(Dashboard)

Class Coverage Distribution

Class Complexity

Top Project Risks

FilterChain (426)

AbstractFilter (140)

StringTrim (24)

FilterPluginManager (10)

Least Tested Methods

StringTrim::setCharList (0%)

AbstractFilter::hasPcreUnicodeSupport (0%)

FilterChain::merge (0%)

FilterChain::getFilters (0%)

FilterChain::__sleep (0%)

FilterChain::plugin (0%)

FilterChain::count (0%)

AbstractFilter::setOptions (0%)

AbstractFilter::getOptions (0%)

AbstractFilter::isOptions (0%)

     Generated by PHP_CodeCoverage 1.2.11 using PHP 5.5.24 and PHPUnit 3.7.21 at Wed Nov 11 4:49:31 CET 2015.


text:
namespace GUI {

class RenderArea : public QOpenGLWidget
{
	Q_OBJECT

public:
	explicit RenderArea(QWidget *parent = 0);
	virtual ~RenderArea() override = default;

public:
	void SetSceneModel(Model::SceneModelPtr scene_model);

signals:
	void ShowItemDesciprtionSignal(QString description);

protected:
	virtual void paintEvent(QPaintEvent *event) override;
	virtual void mousePressEvent(QMouseEvent *) override;
	virtual void mouseReleaseEvent(QMouseEvent *) override;
	virtual void mouseMoveEvent(QMouseEvent *) override;
	virtual void wheelEvent(QWheelEvent *) override;
	virtual void resizeEvent(QResizeEvent *) override;

private:
	Model::SceneItem::UID FindItemToHoverUnderViewPoint(const QPointF& point);

	QString BuildDescriptionOfItem(Model::SceneItem::UID item_uid) const;

private:
	Model::SceneModelPtr scene_model_;

	class CoordinatesTransformer;
	using CoordinatesTransformerUniqPtr = std::unique_ptr<CoordinatesTransformer>;
	const CoordinatesTransformerUniqPtr coordinates_transformer_;

	// ïåðåìåùåíèå êàìåðû
	QPoint mouse_pos_;

	// TODO: do we need that
	bool mouse_pressed_ = false;

	Model::SceneItem::UID hovered_item_uid_ = 0;
};

} // namespace GUI


text:
namespace cgc1::details
{
  inline auto global_kernel_state_t::gc_allocator() const noexcept -> gc_allocator_t &
  {
    return m_gc_allocator;
  }
  inline auto global_kernel_state_t::_bitmap_allocator() const noexcept -> bitmap_allocator_type &
  {
    return m_bitmap_allocator;
  }
  inline auto global_kernel_state_t::_internal_allocator() const noexcept -> internal_allocator_t &
  {
    return m_cgc_allocator;
  }
  inline auto global_kernel_state_t::_internal_slab_allocator() const noexcept -> internal_slab_allocator_type &
  {
    return m_slab_allocator;
  }
  inline auto global_kernel_state_t::tlks(::std::thread::id id) -> thread_local_kernel_state_t *
  {
    MCPPALLOC_CONCURRENCY_LOCK_GUARD(m_thread_mutex);
    auto it = ::std::find_if(m_threads.begin(), m_threads.end(),
                             [id](thread_local_kernel_state_t *tlks) { return tlks->thread_id() == id; });
    if (it == m_threads.end()) {
      return nullptr;
    }
    return *it;
  }
  template <typename Container>
  void global_kernel_state_t::_add_freed_in_last_collection(Container &container)
  {
    MCPPALLOC_CONCURRENCY_LOCK_GUARD(m_mutex);
    m_freed_in_last_collection.insert(m_freed_in_last_collection.end(), container.begin(), container.end());
  }
  template <typename Container>
  void global_kernel_state_t::_add_need_special_finalizing_collection(Container &container)
  {
    MCPPALLOC_CONCURRENCY_LOCK_GUARD(m_mutex);
    m_need_special_finalizing_collection_sparse.insert(m_need_special_finalizing_collection_sparse.end(), container.begin(),
                                                       container.end());
  }

  inline bool global_kernel_state_t::is_valid_object_state(const gc_sparse_object_state_t *os) const
  {
    // We may assume this because the internal allocator may only grow.
    // At worse this may be falsely negative, but then it is undef behavior race condition.
    if ((reinterpret_cast<uintptr_t>(os) & 15) != 0) {
      return false;
    }
    MCPPALLOC_CONCURRENCY_LOCK_ASSUME(_internal_allocator()._mutex());
    return ::mcppalloc::sparse::details::is_valid_object_state(os, _internal_allocator().underlying_memory().begin(),
                                                               _internal_allocator()._u_current_end());
  }
  inline auto global_kernel_state_t::_mutex() const -> mutex_type &
  {
    return m_mutex;
  }
  inline auto global_kernel_state_t::root_collection()
  {
    return m_roots;
  }
  inline auto global_kernel_state_t::root_collection() const
  {
    return m_roots;
  }
}


text:
<?php
class ModelCatalogSocialDiscount extends Model {
	public function getCustomSocialDiscount($product_id) {
		$this->checkTables();

		$results = $this->db->query('SELECT * FROM ' . DB_PREFIX . 'social_discount WHERE product_id = ' . (int)$product_id . ' LIMIT 1');
		if ($results->num_rows > 0) {
			$result = unserialize($results->row['value']);
			return $result;
		} else {
			return false;
		}
	}

	public function setCustomSocialDiscount($product_id, $data) {
		$this->checkTables();

		$value = serialize($data);
		$this->db->query('INSERT INTO ' . DB_PREFIX . 'social_discount(product_id, `value`) VALUES(' . (int)$product_id . ', "' . $this->db->escape($value) . '") ON DUPLICATE KEY UPDATE `value`="' . $this->db->escape($value) . '"');
	}

	private function checkTables() {
		$sql = 'CREATE TABLE IF NOT EXISTS `' . DB_PREFIX . 'social_discount`('
			. '`product_id` int(11) NOT NULL,'
			. '`value` text NOT NULL,'
			. ' PRIMARY KEY (`product_id`) '
			. ') ENGINE=MyISAM DEFAULT CHARSET=utf8;';

		$this->db->query($sql);
	}
}
?>


text:
package main

func loadBuiltinMacros(preprocessor *Preprocessor, store ConfigStore, config *Config) {
	preprocessor.Register("$value", func(input macroinput) interface{} {
		path := input["$value"].(string)
		go store.WatchToUpdate(config, path)
		value := store.Get(path)
		if value != "" {
			return value
		}
		if input["default"] != nil {
			return input["default"].(string)
		}
		return ""
	})

	preprocessor.Register("$file", func(input macroinput) interface{} {
		path := input["$file"].(string)
		go store.WatchToUpdate(config, path)
		value := store.Get(path)
		if value != "" {
			return value
		}
		if input["default"] != nil {
			return input["default"].(string)
		}
		return ""
	})

	// $keys
	// $file
	// $environ

	// $for
	// $if
	// $eq
	// $ne

	// $service
	// $services
}


text:
# Impac Developer Toolkit

### Steps to get an Impac! Workspace loaded:

**Before getting started, we recommend you read the "Backend Ecosystem" section just below.**

1. Fork impac-angular (https://github.com/maestrano/impac-angular) and clone it to your local machine. _Note this method for running impac-angular is only valid from versions >= 1.4.5_.
2. Run `bower install`, `npm install` to ensure you have all the required dependencies.
3. Run `gulp serve` or just `gulp`.
4. A new tab with the Developer Workspace served will open in your browser, just **create an account or log in!**
5. Check your emails, and **confirm your account**.
6. You should be able to start coding in the project impac-angular: just saving any file in the `/src` or `/workspace` directory will automatically reload your Developer Workspace, applying your code. (Note that unfortunately the reload time can take up to 8 seconds to finish... This is on our todo list.)

##### Generating test data

The best way to populate the Widgets and KPIs with data, is to add an App to your Dashboard. We recommend generating a demo account with Xero.

Steps to add a Xero demo account to your Dashboard:

1. Navigate to Impac Express: [https://impac-mnoe-uat.maestrano.io](https://impac-mnoe-uat.maestrano.io).
2. Log in with your same details used to register with the Developer Toolkit.
3. Access your Dashboard.
4. At the top-center of the screen, there is an "App dock" (similar to a Macintosh app dock) with a plus icon. Clicking that will take you to the Marketplace (#!/marketplace).
5. Here, search for & select "Xero".
6. Click the "Launch App button".
7. This will redirect you to Xero, at the bottom of the screen click on the link "**Don't have a login? Try Xero for free**". Follow the instructions to register your account (no need to use real information here), you will need to complete an email confirmation.
8. Once redirected back to Xero from the email confirmation action, create your password, then, on the next screen, **it is important that you click the "Try the Demo Company" link at the bottom of the "Add your organisation to start using Xero now" screen!**.
9. Now youe Xero account is setup, you can sync the data that has been generated by the Xero Demo Account, and you can also add more records later on to add to your dataset.
10. Navigate back to your Dashboard on Impac Express, and you should see the Xero app added to your App Dock.
11. To force a sync, click the Sync Data button that is on the left of the Currency Selector on your dashboard (you will need to have added at least one dashboard to see this element).

### Backend Ecosystem

There are a few backend APIs which impac-angular interacts with. It is important to have a bit of an understanding on this before developing on Impac! Angular.

###### Maestrano Express

Maestrano Express is a heavily customisatable Rails app instance (bootstrapped by a Maestrano Rails Engine) which hosts all the Maestrano products.

[https://impac-mnoe-uat.maestrano.io](https://impac-mnoe-uat.maestrano.io) named "Impac! Express", is a Maestrano Express app that has been bootstraped and hosted just for Impac! development and testing.

This Impac! Angular Developer Workspace is configured to query Impac! Express which mostly (for the Impac! endpoints) redirects queries to "Maestrano Hub".

**Maestrano Hub** is an API which exposes the core Maestrano APIs to manage organizations, users, applications, dashboards, widgets etc.

If you navigate to Impac! Express, you can sign in with the same account you registered on the developer toolkit login with, and you will be accessing the same backend data. The only difference is the front-end is served via deployed public static JS assets. So for example, a distributed version of Impac! Angular.

This is very helpful for accessing the full range of features, associated to the same account you are developing with for Impac! Angular (e.g integrating an App and running a data sync). Or, for testing a stable state of the front-end agaist works you have been doing.

###### Impac!

Impac! is a Rails API which acts as a reporting server, retrieving raw data (from another Maestrano micro-service called "**Connec!**"), calculating metrics / analytics, and formatting the reports into json responses for front-end consumers.

The Impac! Angular front-end mainly queries Impac! retrieving data to display in Widgets & KPIs.

### How the Workspace app works

The `/workspace` directory serves as a mini angular app, which includes the Impac! Angular library as a Bower dependency, and loads it using Angular's module dependency system.

```javascript
angular.module('impacWorkspace', ['maestrano.impac']);
```

The `impacWorkspace` bower dependencies are injected using Gulp Wiredep into the  `/workspace/index.html`, and is then served to the browser. The `/workspace` and `/src` directory are watched for changes.

#### How-to: Create a widget
---

#### Shortcut!

We have built a **Yoeman generator** to generate the boilerplate and some extras to help get you going!

1. Run `npm update` to make sure you have all the latest npm packages.
2. Simply run, `yo widget`, and follow the prompts to generate your new Widget Component!

** Please read the Full Process below, as it will provide more details on getting your widget up and running & help with understanding the basics.**

You want to make the generator better? Of course. See the [README](./generators/generator-widget/README.md) and take a look at `generators/generator-widget`.

##### Full Process

1. **Defining the Widgets Template.**

  *Widgets templates are currently kept in the maestrano api. They declare defining attributes for each widget.*

  *It is important to take note of the `path` vs `path` & `metadata.template` attributes. Defining a `metadata.template` enables you to use an existing Impac API engine, and points the front-end to a different template*
  ```javascript
  // Example of a widget template
  // -----
  {
    // engine called in Impac! API
    path: 'accounts/balance',

    // optional - name of the template to use for your widget. In this case, 'accounts-balance.tmpl.html' will be used.
    // If no metadata['template'] is defined, the path is used to determine the template name.
    metadata: {
      template: 'accounts/balance'
    },

    // name to be displayed in the widgets selector
    name: 'Account balance',

    // description tooltip to be displayed in the widgets selector
    desc: "Display the current value of a given account",

    // font awesome icon to be displayed in the widgets selector
    icon: "pie-chart",

    // number of bootstrap columns of the widget (value can be 3 or 6)
    width: 3
  }
  ```
  **Widgets templates can be stubbed in the `workspace/index.js` file, via the `ImpacDeveloper` service.**

  ```javascript
    module.factory('settings', function () {
      return {

        ...

        widgetsTemplates: [
              {
                path: 'invoices/awesome-existing-engine',
                name: 'Awesome Sales Widget',
                metadata: { template: 'sales/your-awesome-component' },
                desc: 'compares awesome things to more awesome things',
                icon: 'pie-chart',
                width: 3
              },
              {
                path: 'accounting/your-engine-and-component-name',
                name: 'Awesome Accounting Widget',
                desc: 'compares awesome things to more awesome things',
                icon: 'pie-chart',
                width: 3
              }
        ]
      }
    });
  ```

  *This will inject your stubbed templates into the angular apps model,  displaying available templates from API and your stubbed templates.*

2. **Create the widget's files:**
  - in `/src/components/widgets/`, add a folder `category-widget-name` (e.g: `accounts-my-new-widget`).
  - in this new folder, add three files:
    - `accounts-my-new-widget.directive.coffee` containing the angular directive and controller defining your widget's behaviour.
    - `accounts-my-new-widget.tmpl.html` containing the template of your widget.
    - `accounts-my-new-widget.less` containing the stylesheet of your widget.
    - `accounts-my-new-widget.spec.js` containing unit-tests for your widget.

3. **Building the directive:**

  *Widget directives get loaded through `widget.directive.coffee`'s template by `ngInclude`, which means it inherits scope.*

  Below are some key variables and methods available through the `ImpacWidget` scope:
  - `$scope.parentDashboard`, which is the dashboard object that contains the widget object in its *widgets* list.
  - `$scope.widget`, which is the widget object.
  - `$scope.widgetDeferred` a `$q` promise object, see step 5.
  - `$scope.updateSettings()`, updates all `widget-settings` directives registered on the widget.

  *The examples below are the basic widget component set-up that is pretty much generic across all other widgets. Make sure you stick to this convention.*

  ```coffeescript
  # Basic components directive structure
  module = angular.module('impac.components.widgets.your-widget',[])

  module.controller('YourWidgetCtrl', ($scope) ->

    w = $scope.widget

  )
  module.directive('yourWidget', ->
    return {
       # avoid restricting by element ('E') please.
       restrict: 'A',
       controller: 'YourWidgetCtrl'
    }
  )
  ```

  ```html
  <!-- Basic component template structure -->

  <div your-widget>
    <!-- edit widget view -->
    <div ng-show="widget.isEditMode" class="edit">
      <h4>Widget settings</h4>

      <!-- settings directive for managing organizations (widget data come from multiple companies) -->
      <div setting-organizations parent-widget="widget" class="part" deferred="::orgDeferred" />

      <!-- actions -->
      <div class="bottom-buttons" align="right">
        <button class="btn btn-default" ng-click="initSettings()">Cancel</button>
        <button class="btn btn-warning" ng-click="updateSettings()">Save</button>
      </div>
    </div>
    <!-- widget view -->
    <div ng-hide="widget.isEditMode">
      <!-- controller bound boolean for switching between widget and 'data not found' message -->
      <div ng-show="(isDataFound==true)">

        <!-- widget content -->

      </div>
      <!-- data not found -->
      <div ng-if="(isDataFound==false)" common-data-not-found on-display-alerts="onDisplayAlerts()" endpoint="::widget.category" width="::widget.width"/>
    </div>
  </div>
  ```

4. **Start implementing the widget's controller**.

  It must contain at least the following elements for a widget without a chart:
    - `settingsPromises`, which is an array of promises, contains a promise for each custom sub-directive that you add to your widget (e.g: a setting, a chart...).
    *It is essential that you pass a deferred object (initialized by $q.defer()) to each setting or chart that you want to add to your widget: it will be used to make sure the setting is properly initialized before the widget can call its functions.*
    - `$scope.widget.initContext()` is the function that will be called just after the widget has retrieved its content from the API. It should be implemented, and used to determine if the widget can be displayed properly, and to initialize potential specific variables.

  ```coffeescript
     w = $scope.widget

     # Define settings
     # --------------------------------------
     $scope.orgDeferred = $q.defer()

     settingsPromises = [
       $scope.orgDeferred.promise
     ]

     # Widget specific methods
     # --------------------------------------
     w.initContext = ->
       $scope.isDataFound = w.content?
  ```
  - If your widget is using a chart:
    - `w.format()` will be required to build the chart. It will be triggered by the `ImpacWidgets` service `show` method, after the data has been successfully retrieved from Impac!.

  ```coffeescript
    $scope.drawTrigger = $q.defer()

    ...

    w.format = ->

      ...

      # formats the widget content in data that will be readable by Chartjs
      # See other widgets directives for examples of different chart types,
      # arguments needed etc. Also take a look at the ChartFormatterSvc methods.
      chartData = ChartFormatterSvc.lineChart([inputData],options)
      # passes chartData to the chart directive, and calls chart.draw()
      $scope.drawTrigger.notify(chartData)
  ```

  ```html
    <div your-widget>
      ...

      <div impac-chart draw-trigger="::drawTrigger.promise" deferred="::chartDeferred"></div>
      ...
    </div>

  ```

5. Notify the widget's main directive that the widget's specific context has been loaded and is ready. To do that, we use a deferred object that is initialized in the main directive (`widget.directive.coffee`), and resolved at the end of the specific directive (`accounts-my-new-widget.directive.coffee`):
  ```coffeescript
  ...

  $scope.widgetDeferred.resolve(settingsPromises)
  ```

  **IMPORTANT**: The settingsPromises array defined in 1/ has to be passed back to the main directive to make sure it will wait for all the settings to be initialized before calling the widget's #show function.

6. Add the new components angular module to the `src/impac-angular.module.js` module declarations.

  ```javascript
    angular.module('impac.components.widgets',
      [
        'impac.components.widgets.your-widget'
      ]
    );
  ```

7. Rebuild via `gulp` or `gulp serve` or `gulp workspace`, and then you should be able to add your new widget to a dashboard!

#### How-to: Create a setting
---

##### Conventions specific to settings development

- A 'setting' is a directive that may be reused by any widget. The purpose of any setting is to handle the management of one 'metadata parameter', which will define the widget configuration. Basically, everytime a configuration information has to be saved before next dashboard reload, a setting should be used.

- **Avoid** using the `$scope.parentWidget` inside of the setting's controller: when you have to call a method belonging to the widget object, pass a callback to the directive as an argument. When you need to access some data contained into `$scope.parentWidget.content`, try passing an object to the directive as well. Eg:

  ```coffeescript
  scope: {
    parentWidget: '='
    deferred: '='
    callBackToWidget: '=onActivate'
    widgetContentData: '=data'
  }
  ```

##### Process

1. Create the setting's files:
  - in `/src/components/widgets-settings/`, add a folder 'setting-name' (e.g: `my-new-setting`).
  - in this new folder, add three files:
    - `my-new-setting.directive.coffee` containing the angular directive and controller defining your setting behaviour.
    - `my-new-setting.tmpl.html` containing the template of your setting.
    - `my-new-setting.less` containing the stylesheet of your setting.

2. Define your setting's directive. It requires at least the following attributes:
  ```coffeescript
  scope: {
    parentWidget: '=' // widget object containing the setting object
    deferred: '=' // deferred object that will be resolved once the setting context is loaded
  }
  ```

3. Start implementing your setting's controller:
  - create a **setting** object with a unique identifier:
    ```coffeescript
    setting = {}
    setting.key = "my-new-setting"
    ```
    - implement the **setting.initialize()** function, which must be used to set the setting's default parameters
    - implement the **setting.toMetadata()** function, which will be called when the setting content has to be stored in the Maestrano config. It must return a javascript hash that will be directly stored into widget.metadata. For instance, if setting.toMetadata() returns `{ my_new_setting: true }`, once the widget is updated, it will contain: `widget.metadata.my_new_setting = true`

4. Push the setting in the parent widget settings list: `$scope.widget.settings.push(setting)`

5. Notify the parent widget that the setting's context has been loaded and is ready: `$scope.deferred.resolve(setting)`.
**IMPORTANT**: The parent widget's #show method (= call to the Impac! API to retrieve the widget's content) will be called only once all the settings are loaded (= once they have resolved their `$scope.deferred` object).

6. Add the new components angular module to the `src/impac-angular.module.js` module declarations.

  ```javascript
    angular.module('impac.components.widgets-settings',
      [
        'impac.components.widgets-settings.my-new-setting'
      ]
    );
  ```

### Code Conventions across impac-angular
---
#### General
- HTML Templates **must not use double-quotes for strings** (I'm looking at you, Ruby devs). Only html attribute values may be wrapped in double qoutes.
  - **REASON**: when gulp-angular-templatecache module works its build magic, having double quotes within double quotes breaks the escaping.

- We have found [this angular style guide](https://github.com/johnpapa/angular-styleguide) to be an excellent reference which outlines good ways to write angular. I try to write CoffeeScript so it compiles in line with this style guide.

#### File Naming

- Slug style file naming, e.g `this-is-slug-style`.
- Add filename extensions to basename describing the type of component it is.
```javascript
  // good
  some-file.svc.coffee
  some-file.modal.html

  // bad
  some-file-svc.coffee
  some-file-modal.html
```

**IMPORTANT:**
Widget folder and file names must be the same as the widget's category that is stored in the back-end, for example:

```javascript
  // widget data returned from maestrano database
  widget: {
    category: "invoices/aged_payables_receivables",
    ...
  }
```
**Component folder & file name should be:**
```
  components/invoices-aged-payables-receivables/invoices-aged-payables-receivables.directive.coffee
```

#### Stylesheets

The goal is to be able to work on a specific component / piece of functionality and be able to quickly isolate the javascript and css without having to dig through a 1000 line + css / js file, and also preventing styles from bleeding.

Stylesheets should be kept within the components file structure, with styles concerning that component.

Only main stylesheets should be kept in the stylesheets folder, like `variables.less`, `global.less`, and `mixins.less`, etc.

Component specific styles should be wrapped in a containing ID to prevent bleeding.

With widgets, there is no need for creating an id for nesting styles within. There is some code in place which adds the class dynamically to the template from the Widget's template data retrieved from the API.

To view how this works, see files `components/src/widget/widget.html` and `component/src/widget/widget.directive.coffee`.

Below is an example of the correct less closure for your widgets components less files.
```less
  // impac-angular/src/components/widgets/sales-list/sales-list.less
  .analytics .widget-item .content.sales-list {
    ul {}
  }
```

With other components / widgets settings components, your less should be closured like below.
```less
  // components/your-component-category/your-component.less
  .analytics .your-component-category.your-component {
    /* styles that wont bleed and are easily identifiable as only within this component */
    ul {}
  }
```
Template to match above:

```html
  <!-- components/your-component-category/your-component.tmpl.html -->
  <div class"your-component-category your-component">
    <!-- html template for component -->
  </div>
```

During the build process gulp will inject `@import` declarations from `.less` files in `components/` into `src/impac-angular.less`, concatinate all less files into `dist/impac-angular.less`, and compile and minify all less files into `dist/impac-angular.css` and `dist/impac-angular.min.css`.

### Tests

Test should be created within service or component folders. Just be sure to mark them with a .spec extension.

Example:

```
  components/
    some-component/
      some-component.directive.coffee
      some-component.spec.js
  services/
    some-service/
      some-service.service.coffee
      some-service.spec.js
```

To run tests, first build impac-angular with `gulp build`. Then run `gulp test`.

#### Gulp tasks

- `gulp` or `gulp serve` will spin up a server, wiredep `workspace/index.html`, run a `gulp build`, and start a watch that will trigger build when any  `workspace/` or `src/` files change.
- `gulp serve:noreload` do the same as above, but without the watch task.
- `gulp build` will build all `/dist` files.
- `gulp build:dist` will run a `gulp clean` first, then build all `/dist` files, ensure only the current src files are included in dist (especially relevant for images).
- `gulp workspace` will inject all dependencies with wiredep, and run a `gulp build`.
- `gulp test` will run unit-tests on `dist/impac-angular.js` and `dist/impac-angular.min.js`

### Licence
Copyright 2015 Maestrano Pty Ltd