'User "%s" has already been registered for event "%s".', self::MSG_REGISTRATION_NOT_ALLOWED => 'Registration for event "%s" is not allowed.', self::MSG_REGISTRATION_LIMIT_EXCEEDED => 'Registration limit for event "%s" has been exceeded by %d attendee(s). Please make appropriate corrections in your Shopping Cart.', self::MSG_WRONG_USER_AMOUNT => 'Registration price for the user "%s" has been calculated incorrectly.', ); /** @var \DateTime */ protected $_date; protected $_mode = self::LIST_MODE_MONTH; protected $_cart; protected $_userId; protected $_listIncludeTimeRanges; public static function getNextMeetingData($committeeId) { $select = Qs_Db::getSelect(); $select->from(Qs_Db::getPair('Event'), array('id', 'title', 'startDate', 'endDate', 'agendaFileName')) ->where('`committeeId` = ?', $committeeId, \Qs_Db::INT_TYPE) ->where('`show` = "y"') ->where('`startDate` > ?', date('Y-m-d')) ->order('startDate ASC') ->limit(1); if (($event = Qs_Db::getInstance()->fetchRow($select))) { $timeSelect = Qs_Db::getSelect(); $timeSelect->from(Qs_Db::getPair('EventTime', 'et'), array('startTime', 'endTime')); $timeSelect->where('et.eventId = ?', $event['id'], Qs_Db::INT_TYPE); $timeSelect->order('et.date'); $range = Qs_Db::getInstance()->fetchRow($timeSelect); $event['startDate'] .= ' ' . $range['startTime']; $event['endDate'] .= ' ' . $range['endTime']; } return $event; } public function getAttendeeObj(array $options = array()) { $options['cartId'] = $this->_getCart()->getCartId(); return parent::getAttendeeObj($options); } /** * @param $row [ * 'id' => int, * 'cartId' => int, * 'cartItemType' => string, // "Event\" * 'productId' => int, // parentAttendeeId * 'quantity' => int, // 1 * 'price' => float, * 'attendees' => [ * [ * 'firstName' => string, * 'lastName' => string, * 'userId' => int, * 'amount' => float, * ], * // ... * ] * ] * @throws \Exception * @return array|bool */ public function validateCartItem($row) { $errors = array(); if ($row && $row['attendees']) { $event2attendees = array(); foreach ($row['attendees'] as $attendee) { $event2attendees[$attendee['eventId']][] = $attendee; } // validate temporary attendees (attendees can be already added via admin end) $msgAlreadyRegistered = static::$_messageTemplates[static::MSG_USER_ALREADY_REGISTERED]; $msgRegistrationNotAllowed = static::$_messageTemplates[static::MSG_REGISTRATION_NOT_ALLOWED]; $msgWrongAmount = static::$_messageTemplates[static::MSG_WRONG_USER_AMOUNT]; $msgRegistrationLimitExceeded = static::$_messageTemplates[static::MSG_REGISTRATION_LIMIT_EXCEEDED]; $total = 0; // validate cart total foreach ($event2attendees as $eventId => $eventAttendees) { $this->setPrimaryKey($eventId); $registered = $this->getAttendeeObj(array('mode' => AttendeeObj::MODE_APPROVED))->getRegisteredAttendees(); $event = $this->getData(); // should operate "approved" attendees // price for each attendee depends from signup type (member or non member) $memberPrice = (float) $event['memberPrice']; $regularPrice = (float) $event['regularPrice']; $eventTitle = $event['title']; if (false == $event['registrationAllowed']) { $errors[] = sprintf($msgRegistrationNotAllowed, $eventTitle); continue; } if (isset($event['registrationSpots']) && count($eventAttendees) > $event['registrationSpots']) { $errors[] = sprintf( $msgRegistrationLimitExceeded, $eventTitle, count($eventAttendees) - $event['registrationSpots'] ); continue; } $id2user = Qs_Array::indexToAssoc($registered, 'userId'); $email2user = Qs_Array::indexToAssoc($registered, 'email'); foreach ($eventAttendees as $attendee) { if ($attendee['userId']) { $attendeePrice = $memberPrice; if (array_key_exists($attendee['userId'], $id2user)) { $attendeeName = $id2user[$attendee['userId']]['name']; $errors[] = sprintf($msgAlreadyRegistered, $attendeeName, $eventTitle); } else { $attendeeName = '# ' . (int) $attendee['userId']; } if ($attendee['email']) { if (array_key_exists($attendee['email'], $email2user)) { $attendeeName = $email2user[$attendee['email']]['name']; $errors[] = sprintf($msgAlreadyRegistered, $attendeeName, $eventTitle); } else { $attendeeName = $attendee['firstName'] . ' ' . $attendee['lastName']; } } } elseif ($attendee['email']) { $attendeePrice = $regularPrice; if (array_key_exists($attendee['email'], $email2user)) { $attendeeName = $email2user[$attendee['email']]['name']; $errors[] = sprintf($msgAlreadyRegistered, $attendeeName, $eventTitle); } else { $attendeeName = $attendee['firstName'] . ' ' . $attendee['lastName']; } } else { throw new Exception('Wrong data format'); } $total = bcadd($total, $attendee['amount']); if ((float) $attendee['amount'] !== $attendeePrice) { $errors[] = sprintf($msgWrongAmount, $attendeeName, $eventTitle); } } } if (0 !== $total && (float) $total !== (float) $row['price']) { $errors[] = 'Cart Item total is not valid'; } $this->clearData(); } return ($errors) ? $errors : true; } protected function _getFromColumns() { return array('id', 'type', 'title', 'startDate', 'endDate', 'registrationStart', 'registrationEnd', 'location'); } public static function filterEvents(\Zend_Db_Select $select, $alias = 'e') { parent::filterEvents($select, $alias); $select->where(\Qs_Db::quoteField('show', $alias) . ' = "y"'); return; } public function getListSelect() { if (null == $this->_select) { parent::getListSelect(); if (self::LIST_MODE_MONTH === $this->_mode) { $this->_filterMonthEvents($this->_select); } $this->_select->order('e.startDate'); } return $this->_select; } protected function _prepareList(&$list) { parent::_prepareList($list); if ($this->getListIncludeTimeRanges() && $list) { $timeRanges = $this->_readListTimeRanges(Qs_Array::fetchCol($list, 'id')); foreach ($list as &$row) { if (array_key_exists($row['id'], $timeRanges)) { $row['timeRanges'] = $timeRanges[$row['id']]; } } } return $this; } public function _readListTimeRanges(array $eventIds) { if (empty($eventIds)) { return array(); } $select = $this->_db->select(); $select->from($this->_getPair('EventTime', 'et'), array('eventId', 'startTime', 'endTime')); $select->where('et.eventId IN(?)', $eventIds, Qs_Db::INT_TYPE); $select->order('et.date'); return $this->_db->fetchAll($select, array(), Qs_Db::FETCH_GROUP); } protected function _filterMonthEvents(\Zend_Db_Select $select) { $firstDate = clone $this->getDate(); $lastDate = clone $this->getDate(); $firstDate = $firstDate->modify('first day of this month')->format('Y-m-d'); $lastDate = $lastDate->modify('last day of this month')->format('Y-m-d'); $select->where('e.startDate <= ? ', $lastDate); $select->where('e.endDate >= ?', $firstDate); return $this; } protected function _getFromDbColumns() { $columns = parent::_getFromDbColumns(); $columns['registeredCount'] = $this->getAttendeeObj()->getEventRegisteredCountExpr('e'); $columns['belongCommittee'] = $this->_getBelongsCommitteeExpr('e'); return $columns; } protected function _getBelongsCommitteeExpr($eventAlias) { if (null == ($userId = $this->getUserId())) { return new Zend_Db_Expr('NULL'); } $_event = $this->_db->quoteIdentifier($eventAlias); $_userId = (int) $userId; $_typeCommittee = $this->_db->quote(self::TYPE_COMMITTEE); $sql = "SELECT 1 FROM {$this->_getTableName('UserCommittee')} c " . "WHERE c.committeeId = {$_event}.committeeId AND c.userId = {$_userId}"; return new Zend_Db_Expr("IF({$_event}.`type` = {$_typeCommittee}, ({$sql}), NULL)"); } protected function _addDependenciesFromDb(array &$data) { if (self::MORE_INFO_PAGE === $data['moreInfoType']) { $data['moreInfoUrl'] = \Qs_SiteMap::findFirst(array('id' => $data['moreInfoPage']), null, null, 'url'); } $this->_prepareRegistrationAllowed($data); if ($data['registrationAllowed']) { if ($data['registrationLimit']) { $data['registrationSpots'] = (int) $data['registrationLimit'] - (int) $data['registeredCount']; } $data['allowMembers'] = true; $data['allowNonMembers'] = $this->getNonMembersAllowed($data['type']); } $data['timeRanges'] = $this->_readTimeRanges(); return parent::_addDependenciesFromDb($data); } protected function _readTimeRanges() { $select = $this->_db->select(); $select->from($this->_getPair('EventTime', 'et'), array('startTime', 'endTime')); $select->where('et.eventId = ?', $this->getPrimaryKey()); $select->order('et.date'); return $this->_db->fetchAll($select); } /** * @param string|\DateTime $date * @return $this */ public function setDate($date) { if ($date instanceof \DateTime) { $this->_date = $date; } else { $this->_date = new \DateTime((string) $date); } $this->_date->setTime(0, 0, 0); return $this; } /** * @throws \Qs_Exception_EmptyPropertyException * @return \DateTime */ public function getDate() { if (null == $this->_date) { throw new \Qs_Exception_EmptyPropertyException($this, '_date'); } return $this->_date; } public function initFromForm(array $data) { parent::initFromForm($data); $this->_data = $this->_prepareSignupFormData($this->_data); return $this; } /** * @param array $data * [ * 'total' => float, * 'attendees' => [ * [ * 'teamId' => int, * 'userId' => int, * 'firstName' => string, * 'lastName' => string, * 'email' => string, * 'amount' => string, * ] * ], * * ] * @return int|null * @throws \Exception * @throws \Qs_Exception_EmptyPropertyException */ public function insert(array $data = null) { $data = (null === $data) ? $this->_data : $data; if (!$this->hasPrimaryKey()) { throw new \Qs_Exception_EmptyPropertyException($this, '_primaryKey'); } if (!isset($data['total']) || empty($data['attendees'])) { throw new Exception('Not enough data'); } $this->_clearErrors(); $this->_db->beginTransaction(); try { $cartId = $this->_getCart()->getCartId(); $event = $this->getEventData(); $total = (float) $data['total']; $attendees = (array) $data['attendees']; $attendeeTable = $this->_getTable('EventAttendee'); if (($cartItem = $this->_getEventCartItem())) { // update existing event cart item $parentAttendeeId = (int) $cartItem['productId']; $price = bcadd($cartItem['price'], $total); $paymentFee = $this->calcFee($price); $itemData = array( 'quantity' => 1, 'price' => $price, 'paymentFee' => $paymentFee, ); $this->_getCart()->updateItem($cartItem['id'], $itemData); } else { // insert main attendee (current user or first attendee in form) $mainAttendee = reset($attendees); unset($attendees[key($attendees)]); $mainAttendee['eventId'] = $this->_primaryKey; $mainAttendee['cartId'] = $cartId; $mainAttendee['approved'] = null; $parentAttendeeId = $attendeeTable->insert($mainAttendee); // insert shopping cart item $itemData = array( 'cartItemType' => \App_ECommerce_Cart_View::ITEM_TYPE_EVENT, 'productId' => $parentAttendeeId, 'title' => $event['title'], 'description' => $event['description'], 'quantity' => 1, 'price' => $total, 'paymentFee' => $this->calcFee($total), ); $this->_getCart()->addItem($itemData); } foreach ($attendees as &$attendee) { $attendee['eventId'] = $this->_primaryKey; $attendee['parentAttendeeId'] = $parentAttendeeId; $attendee['cartId'] = $cartId; $attendee['approved'] = null; } // insert attendees, update field parentAttendeeId $attendeeTable->insertList($attendees); $this->_updateParentAttendeeIds(); $this->_insertDependency(); $this->_db->commit(); } catch (Exception $e) { $this->_db->rollBack(); Qs_Debug::processException($e); return false; } return $this->getPrimaryKey(); } public function update(array $data = null) { throw new \Exception('Not supported'); } /** * Return shopping cart object * * @return App_ECommerce_Cart_Obj */ 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 _getEventCartItem() { if (($items = $this->_getCart()->getList())) { $eventId = (int) $this->getPrimaryKey(); foreach ($items as $item) { if ($item['cartItemType'] = App_ECommerce_Cart_View::ITEM_TYPE_EVENT && $eventId === (int) Qs_Array::get(reset($item['attendees']), 'eventId') ) { return $item; } } } return null; } /** * Remove cart event attendees * @param $cartId * @param $parentAttendeeId * @return int */ public static function removeCartItems($cartId, $parentAttendeeId) { $eventAttendee = Qs_Db::getTableName('EventAttendee'); $_cartId = (int) $cartId; $_parentAttendeeId = (int) $parentAttendeeId; $sql = "DELETE ea FROM {$eventAttendee} AS ea " . "WHERE ea.cartId = {$_cartId} AND ea.parentAttendeeId = {$_parentAttendeeId}"; return Qs_Db::getInstance()->query($sql)->rowCount(); } /** * Approve temporary attendees (set approved = "y") * @param $cartId * @return int */ public function approveAttendeesByCart($cartId) { $eventAttendee = Qs_Db::getTableName('EventAttendee'); $_cartId = (int) $cartId; $sql = "UPDATE {$eventAttendee} SET `approved` = 'y' WHERE `cartId` = {$_cartId}"; return Qs_Db::getInstance()->query($sql)->rowCount(); } public function setUserId($userId) { $this->_userId = (int) $userId; return $this; } public function getUserId() { return $this->_userId; } public function setListIncludeTimeRanges($listIncludeTimeRanges = true) { $this->_listIncludeTimeRanges = $listIncludeTimeRanges; return $this; } public function getListIncludeTimeRanges() { return (bool) $this->_listIncludeTimeRanges; } }