bool value, ...] */ public static function getCartItemCapabilities() { return []; } public function setErrors(array $errors) { $this->_errors = $errors; return $this; } public function addErrors(array $errors) { $this->_errors = $this->_errors + $errors; return $this; } /** * @param array $data ['id' =>, 'cartId' =>, 'productId' =>, ...] * * @return $this|mixed */ public function setCartItemData(array $data) { $this->_cartItemData = $data; return $this; } public function getCartItemData() { return $this->_cartItemData; } /** * * * [ * productId - attendeeId * productCategoryId - eventId * ] * @return array */ public function prepareCartItemData() { if ($this->_cartItemData) { $this->_cartItemData['typeTitle'] = static::ITEM_TYPE_TITLE; $eventObj = new EventObj(); $eventObj->setPrimaryKey($this->_cartItemData['productCategoryId']); if ($eventObj->getData()) { $this->_cartItemData['url'] = EventView::getViewUrl($eventObj->getData('alias')); } $this->_cartItemData['allowChangeQuantity'] = in_array(App_ECommerce_Cart_ItemObjFactory::CAPABILITY_CHANGE_QTY, $this->getCartItemCapabilities()) ? 'y' : 'n'; $this->_cartItemData['showDescription'] = 'y'; } return $this->_cartItemData; } public function beforeValidateCart() { $row = $this->_cartItemData; $eventId = $row['productCategoryId']; $this->setPrimaryKey($eventId); self::$_eventData[$eventId] = $this->getData(); } /** * Validate temporary attendees (because they can be already added via admin end) * * $item [ * 'id' int, * 'cartId' int, * 'cartItemType' string, "Event\CartItem" * 'productId' string, // attendeeId * 'productCategoryId' string, * 'title' string, * 'shortDescription' string, * 'description' string, * 'applyTax' string, "y" | "n" * 'applyShipping' string, "y" | "n" * 'rowTax' string, * 'image' string, * 'quantity' string, // 1 * 'price' float, * 'tic' string, * 'weight' string, * 'added' string, * 'changed' string, * 'rowTotal' string, * 'promoValue' string, * 'promoType' string, * 'promoMinOrderValue' string, * 'typeTitle' string, * 'url' string, * 'allowChangeQuantity' string, * 'showDescription' string, "y" | "n" * ] * * @throws Exception * @return array|bool */ public function validateCartItem() { $row = $this->_cartItemData; $this->setPrimaryKey($row['productCategoryId']); $attendee = $this->getAttendeeObj(['primaryKey' => $row['productId']])->getData(); if (empty($row)) { $this->_addError($this->_renderMessage(static::MSG_EVENT_MISSING, [])); return false; } if (empty($attendee)) { $this->_addError($this->_renderMessage(static::MSG_ATTENDEE_MISSING, [])); return false; } if (empty($attendee['userAvailable'])) { $this->_addError($this->_renderMessage(static::MSG_USER_UNAVAILABLE, [])); return false; } if (1 > $row['quantity']) { $this->_addError($this->_renderMessage(static::MSG_REGISTRATION_NOT_ALLOWED, [])); } // self::$_eventData[$eventId]: should operate "approved" attendees // use static var to store changes of registrationSpots $eventData = &self::$_eventData[$row['productCategoryId']]; if (false == $eventData['registrationAllowed']) { $this->_addError($this->_renderMessage(static::MSG_REGISTRATION_NOT_ALLOWED, [$row['title']])); return false; } if (isset($eventData['registrationSpots'])) { $needSpots = empty($attendee['spouseName']) ? 1 : 2; if ($needSpots > $eventData['registrationSpots']) { $this->_addError($this->_renderMessage(static::MSG_REGISTRATION_LIMIT_EXCEEDED, [$row['title']])); return false; } $eventData['registrationSpots'] = ($eventData['registrationSpots'] > $needSpots) ? $eventData['registrationSpots'] - $needSpots : 0; } $errors = []; if ($attendee['userId']) { $attendeePrice = (float)$eventData['memberPrice']; $attendeeName = $attendee['name']; $this->_validateMemberRegistered($errors, $eventData, $attendee); } elseif ($attendee['name']) { $attendeePrice = (float)$eventData['nonmemberPrice']; $attendeeName = $attendee['name']; } else { throw new Exception('Wrong data format'); } if ($errors) { $this->addErrors($errors); return false; } $reloadCartPage = false; $reloadCartMsg = null; // validate "CartItem.applyTax" and "Company.taxExempt" $typeRule = new TypeRule($eventData['type']); $taxExempt = UserModel::getLoggedUserCompanyTaxExempt(); $applyTax = $typeRule->featureAddTaxEvent && !$taxExempt; if ($applyTax !== ($row['applyTax'] === 'y')) { $this->_getCart()->updateItem($row['id'], ['applyTax' => $applyTax ? 'y' : 'n']); $reloadCartPage = true; } if ((float) $attendee['amount'] !== $attendeePrice) { // update attendee price $this->_getTable('EventAttendee')->update(['amount' => $attendeePrice], ['id = ?' => $attendee['id']]); $this->_getCart()->updateItem($row['id'], ['price' => $attendeePrice]); $reloadCartPage = true; $reloadCartMsg = $this->_renderMessage(static::MSG_USER_AMOUNT_UPDATED, [$attendeeName, $row['title']]); } if ($reloadCartPage) { throw new ReloadCartException($reloadCartMsg); } return true; } protected function _getCart() { if (null === $this->_cart) { $this->_cart = new App_ECommerce_Cart_Obj(); $this->_cart->setPrimaryKey($this->_cart->getCartId()); } return $this->_cart; } protected function _validateMemberRegistered(array &$errors, array $event, array $attendee) { $registered = $this->getAttendeeObj()->getBoughtAttendees(); $id2user = Qs_Array::indexToAssoc($registered, 'userId'); if (array_key_exists($attendee['userId'], $id2user)) { $errors[] = $this->_renderMessage( static::MSG_USER_ALREADY_REGISTERED, [$id2user[$attendee['userId']]['name'], $event['title']] ); } return $this; } protected function _renderMessage($messageKey, array $args) { $template = $this->getConfig('messageTemplates')->toArray()[$messageKey]; return vsprintf($template, $args); } /** * [ * $eventId => [ * '' => , * '' => , * 'attendees' => [...], * 'eventTime' => [['date', 'startTime', 'endTime'], ...], * ] * ] * * @param array $attendeeIds * * @return array */ public static function getEventAttendees(array $attendeeIds) { if (null == ($groupedList = self::_getEventAttendeesGroupedList($attendeeIds))) { return []; } if (null == ($events = self::_getEvents(array_keys($groupedList)))) { return []; } $attendeeOptions = self::_getAttendeeOptionsGroups($attendeeIds); $attendeeProducts = self::_getAttendeeProductGroups($attendeeIds); $eventsTime = Model::readEventTimeRanges(array_keys($events)); foreach ($events as &$event) { $event['attendees'] = $groupedList[$event['id']]; if ($event['attendees']) { foreach ($event['attendees'] as &$attendee) { $attendee['options'] = array_key_exists($attendee['id'], $attendeeOptions) ? $attendeeOptions[$attendee['id']] : null; $attendee['products'] = array_key_exists($attendee['id'], $attendeeProducts) ? $attendeeProducts[$attendee['id']] : null; } } if (array_key_exists($event['id'], $eventsTime)) { $event['eventTime'] = $eventsTime[$event['id']]; } } return $events; } protected static function _getEventAttendeesGroupedList(array $attendeeIds) { $spouseSelect = Qs_Db::getSelect(); $spouseSelect->from( Qs_Db::getPair('EventAttendee', 'sea'), ['name' => EventModel::getAttendeeNameWithNicknameExpr('sea')] ); $spouseSelect->where('sea.parentAttendeeId = ea.id'); $spouseSelect->limit(1); $select = Qs_Db::getSelect(); $select->from(Qs_Db::getPair('Event', 'e'), []); $select->join( Qs_Db::getPair('EventAttendee', 'ea'), 'ea.eventId = e.id', [ 'eventId', '*', 'name' => EventModel::getAttendeeNameWithNicknameExpr(), 'spouseName' => new Zend_Db_Expr('(' . $spouseSelect . ')') ] ); $select->where('ea.id IN(?)', $attendeeIds, Qs_Db::INT_TYPE); return Qs_Db::getInstance()->fetchAll($select, null, Qs_Db::FETCH_GROUP); } protected static function _getEvents(array $eventIds) { $eventSelect = Qs_Db::getSelect(); $eventSelect->from(Qs_Db::getPair('Event'), ['*']); $eventSelect->where('id IN (?)', $eventIds); return Qs_Db::getInstance()->fetchAssoc($eventSelect); } protected static function _getAttendeeOptionsGroups(array $attendeeIds) { $select = Qs_Db::getSelect(); $select->from(Qs_Db::getPair('EventAttendeeOption', 'eao'), ['*']); $select->join(Qs_Db::getPair('EventOption', 'eo'), '`eo`.`id` = `eao`.`optionId`', ['optionName' => 'title']); $select->join( Qs_Db::getPair('EventOptionChoice', 'eoc1'), '`eoc1`.`id` = `eao`.`firstChoiceId`', ['firstChoice' => 'title'] ); $select->join( Qs_Db::getPair('EventOptionChoice', 'eoc2'), '`eoc2`.`id` = `eao`.`secondChoiceId`', ['secondChoice' => 'title'] ); $select->where('eao.attendeeId IN(?)', $attendeeIds, Qs_Db::INT_TYPE); return Qs_Db::getInstance()->fetchAll($select, null, Qs_Db::FETCH_GROUP); } protected static function _getAttendeeProductGroups(array $attendeeIds) { $productSelect = Qs_Db::getSelect(); $productSelect->from( Qs_Db::getPair('CartItem'), ['attendeeId' => 'productId', 'productId' => 'productCategoryId', 'title', 'quantity', 'price'] ); $productSelect->where('cartItemType = ?', Entity::CART_PRODUCT_ITEM_TYPE); $productSelect->where('productId IN(?)', $attendeeIds); return Qs_Db::getInstance()->fetchAll($productSelect, null, Qs_Db::FETCH_GROUP); } public function completeItemList(array $itemList) { if (($cartIds = Qs_Array::fetchColAll($itemList, 'cartId'))) { $infoList = $this->_getPaymentInfo($cartIds); $registrationDate = date('Y-m-d H:i:s'); foreach ($itemList as $item) { $data = ['registrationDate' => $registrationDate, 'bought' => 'y']; if (isset($infoList[$item['cartId']])) { $data['paymentType'] = $infoList[$item['cartId']]['paymentType']; $data['paymentDate'] = $infoList[$item['cartId']]['paymentDate']; } // update attendee $this->_getTable('EventAttendee')->update($data, ['id = ?' => $item['productId']]); // update child spouse/partners $this->_getTable('EventAttendee')->update( ['registrationDate' => $data['registrationDate'], 'bought' => 'y'], ['parentAttendeeId = ?' => $item['productId']] ); } } if (($attendeeIds = Qs_Array::fetchColAll($itemList, 'productId'))) { self::sendSignupNotification($attendeeIds); } return $this; } protected function _getPaymentInfo(array $cartIds) { if (empty($cartIds)) { return []; } $select = $this->_db->select(); $select->from($this->_getPair('Cart'), ['cartId' => 'id']); $select->join( $this->_getPair('Transaction'), 'Transaction.id = Cart.transactionId', ['paymentType', 'paymentDate' => 'added'] ); $select->where('Cart.id IN (?)', $cartIds); return $this->_db->fetchAll($select, null, PDO::FETCH_GROUP | PDO::FETCH_UNIQUE); } public function afterCartItemRemove() { $eventAttendee = Qs_Db::getTableName('EventAttendee'); $_cartId = (int) $this->_cartItemData['cartId']; $_eventId = (int) $this->_cartItemData['productId']; $sql = "DELETE ea FROM {$eventAttendee} AS ea " . "WHERE ea.cartId = {$_cartId} AND ea.eventId = {$_eventId} AND ea.bought IS NULL"; $this->_getTable('EventAttendeeOption')->delete([ $this->_db->quoteInto('`attendeeId` = ?', $this->_cartItemData['productId'], Qs_Db::INT_TYPE), ]); $this->_getTable('CartItem')->delete([ $this->_db->quoteInto('`cartId` = ?', $this->_cartItemData['cartId'], Qs_Db::INT_TYPE), $this->_db->quoteInto('`productId` = ?', $this->_cartItemData['productId'], Qs_Db::INT_TYPE), $this->_db->quoteInto('`cartItemType` = ?', \App\EventProduct\CartItemObj::ITEM_TYPE), ]); return Qs_Db::getInstance()->query($sql); } /** * @param $event * @param array $toGroups [OPTIONAL] * * @return $this */ protected static function _sendEventNotifications($event, array $toGroups = null) { $attendees = $event['attendees']; $mailData = self::_getMailData($event, $attendees); $settingsPrefix = 'eventNotification'; $groups = [ self::GROUP_AUTHOR => [ 'to' => [], ], self::GROUP_ATTENDEE => [ 'to' => Qs_Array::fetchColAll($attendees, 'email'), 'attendees' => $attendees, ], self::GROUP_ADMIN => [ 'to' => App_Settings_Obj::getFormEmails('eventNotificationAdminTo'), 'link' => AttendeeAdminView::getPage('url') . '/' . $event['id'] ], ]; if ('production' != constant('APPLICATION_ENV')) { unset($groups[self::GROUP_ATTENDEE]); } if (null === $toGroups) { $toGroups = [self::GROUP_AUTHOR, self::GROUP_ATTENDEE, self::GROUP_ADMIN]; } $groups = array_intersect_key($groups, array_fill_keys($toGroups, null)); $auth = UserAuth::getInstance(); if ($auth->isLoggedIn() && ($authData = $auth->getData())) { $groups['Author']['to'][] = $authData['email']; if (array_key_exists(self::GROUP_ATTENDEE, $groups) && $groups['Attendee']['to'] && false !== ($idx = array_search($authData['email'], $groups['Attendee']['to'])) ) { unset($groups['Attendee']['to'][$idx]); } } foreach ($groups as $group => $options) { $data = $mailData; $prefix = $settingsPrefix . $group; $from = App_Settings_Obj::getEmailFrom($prefix . 'From'); $subject = App_Settings_Obj::get($prefix . 'Subject'); $body = App_Settings_Obj::get($prefix . 'Body'); if (empty($options['to']) || empty($body) || empty($subject) || empty($from)) { continue; } if (isset($options['link'])) { $data['{link}'] = htmlentities($options['link']); } if (isset($options['attendees'])) { self::_sendMailToAttendees($options['attendees'], $options['to'], $data, $from, $subject, $body); } else { self::_sendMailToEmails($options['to'], $data, $from, $subject, $body); } } } protected static function _sendMailToAttendees($attendees, $toEmails, $data, $from, $subject, $body) { /** @var \Qs_Doc $doc */ $doc = Zend_Registry::get('doc'); foreach ($attendees as $attendee) { if (empty($attendee['email']) || !in_array($attendee['email'], (array)$toEmails)) { continue; } $doc->assign('item', new Qs_Doc_Item(['attendee' => $attendee])); $userData = $data; $userData['{additionalInfo}'] = $doc->fetchTemplate( Qs_SiteMap::getTemplate('Event/notification/signup-additionalInfo.tpl') ); $userBody = str_replace(array_keys($userData), array_values($userData), $body); $mail = new Qs_Mail(); $mail->setFrom($from); $mail->setSubject($subject); Qs_Mail::cutImageBaseUrl($userBody, constant('BASE_URL')); $mail->setHtml($userBody, null, constant('WWW_PATH')); $mail->addTo($attendee['email']); $mail->send(); } } protected static function _sendMailToEmails($toEmails, $data, $from, $subject, $body) { $body = str_replace(array_keys($data), array_values($data), $body); $mail = new Qs_Mail(); $mail->setFrom($from); $mail->setSubject($subject); Qs_Mail::cutImageBaseUrl($body, constant('BASE_URL')); $mail->setHtml($body, null, constant('WWW_PATH')); foreach ((array)$toEmails as $email) { $userMail = clone $mail; if ($email) { $userMail->addTo($email); $userMail->send(); } } } public static function sendSignupNotification(array $attendeeIds, array $toGroups = null) { $events = self::getEventAttendees($attendeeIds); foreach ($events as $event) { self::_sendEventNotifications($event, $toGroups); } } protected static function _getMailData($event, $attendees) { /** @var \Qs_Doc $doc */ $doc = Zend_Registry::get('doc'); $smarty = $doc->getSmarty(); require_once 'lib/Smarty/app_plugins/function.date_ranges.php'; require_once 'lib/Smarty/app_plugins/function.time_range.php'; $dateRanges = Model::prepareDateRanges($event['eventTime']); $dateRangeParams = ['ranges' => $dateRanges, 'monthFormat' => '%B']; $dateHtml = smarty_function_date_ranges($dateRangeParams, $smarty); $companyHtml = ''; $attendeesHtml = ''; if ($attendees) { /** @var \Qs_Doc $doc */ $doc = Zend_Registry::get('doc'); $doc->assign('item', new Qs_Doc_Item([ 'attendees' => $attendees, ])); $attendeesHtml = $doc->fetchTemplate(Qs_SiteMap::getTemplate('Event/notification/signup-attendees.tpl')); $companyHtml = $doc->fetchTemplate(Qs_SiteMap::getTemplate('Event/notification/company.tpl')); } $timeHtml = $doc->assign('item', new Qs_Doc_Item(['eventTime' => $event['eventTime']])) ->fetchTemplate(Qs_SiteMap::getTemplate('Event/notification/time.tpl')); $data = [ '{title}' => htmlspecialchars($event['title']), '{location}' => htmlspecialchars($event['location']), '{date}' => $dateHtml, '{time}' => $timeHtml, '{attendees}' => $attendeesHtml, '{company}' => $companyHtml, '{additionalInfo}' => '-', '{link}' => htmlspecialchars( View::getPageUrlByType(Form\ConfigForm::TYPE_DETAILS) . '/' . $event['alias'] ), ]; return $data; } public static function getOptions($attendee) { if (!$attendee['optionList']) { return null; } $choiceIds = []; $optionIds = []; foreach ($attendee['optionList'] as $optionId => $ids) { $optionIds[] = $optionId; $choiceIds = array_merge($choiceIds, $ids); } $db = Qs_Db::getInstance(); $choiseSelect = $optionSelect = $db->select(); $choiseSelect->from(Qs_Db::getPair('EventOptionChoice', 'choice'), ['id', 'title']) ->where('`choice`.`id` IN(?)', $choiceIds, Qs_Db::INT_TYPE); $choices = $db->fetchPairs($choiseSelect); $optionSelect = $db->select(); $optionSelect->from(Qs_Db::getPair('EventOption', 'option'), ['id', 'title']) ->where('option.id in (?)', $optionIds, Qs_Db::INT_TYPE) ->order('option.sorter'); $options = $db->fetchPairs($optionSelect); $result = []; foreach ($attendee['optionList'] as $optionId => $ids) { $optionTitle = Qs_Array::get($options, $optionId); $optionChoices = []; foreach ($ids as $id) { if (null !== ($choiceTitle = Qs_Array::get($choices, $id))) { $optionChoices[] = $choiceTitle; } } if ($optionTitle && $optionChoices) { $result[] = ['title' => $optionTitle, 'value' => implode(', ', $optionChoices)]; } } return $result; } }