Array ('self' => true), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Checks user permission to execute given $event * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { if ( $event->Name == 'OnBecomeAffiliate' || $event->Name == 'OnChangePaymentType' ) { return $this->Application->LoggedIn() && $this->Application->ConfigValue('Comm_RegisterAsAffiliate'); } return parent::CheckPermission($event); } /** * Allows to get ID of affiliate record, associated with currently logged-in user * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { if ( $event->Special == 'user' ) { $event->setEventParam('raise_warnings', 0); $sql = 'SELECT ' . $this->Application->getUnitOption($event->Prefix, 'IDField') . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE PortalUserId = ' . (int)$this->Application->RecallVar('user_id'); $id = $this->Conn->GetOne($sql); if ( $id ) { return $id; } } return parent::getPassedID($event); } /** * Generate new affiliate code * * @param kEvent $event * @return string */ function generateAffiliateCode($event) { // accepts 1 - 36 $number_length = 11; $num_chars = Array( '1'=>'a','2'=>'b','3'=>'c','4'=>'d','5'=>'e','6'=>'f', '7'=>'g','8'=>'h','9'=>'i','10'=>'j','11'=>'k','12'=>'l', '13'=>'m','14'=>'n','15'=>'o','16'=>'p','17'=>'q','18'=>'r', '19'=>'s','20'=>'t','21'=>'u','22'=>'v','23'=>'w','24'=>'x', '25'=>'y','26'=>'z','27'=>'0','28'=>'1','29'=>'2','30'=>'3', '31'=>'4','32'=>'5','33'=>'6','34'=>'7','35'=>'8','36'=>'9'); $ret = ''; for($i=1; $i<=$number_length; $i++) { mt_srand((double)microtime() * 1000000); $num = mt_rand(1,36); $ret .= $num_chars[$num]; } $ret = strtoupper($ret); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $sql = 'SELECT %s FROM %s WHERE AffiliateCode = %s'; $code_found = $this->Conn->GetOne( sprintf($sql, $id_field, $table, $this->Conn->qstr($ret) ) ); if($code_found) return $this->generateAffiliateCode($event); return $ret; } /** * Creates new affiliate code when new affiliate is created * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); /** @var kDBItem $object */ $object = $event->getObject(); $object->SetDBField('AffiliateCode', $this->generateAffiliateCode($event)); if ( $object->getFormName() == 'registration' ) { if ( $this->Application->LoggedIn() ) { $object->SetDBField('PortalUserId', $this->Application->RecallVar('user_id')); } $object->SetDBField('AffiliatePlanId', $this->_getPrimaryAffiliatePlan()); } } /** * Ensures, that user can only update his affiliate record * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); if ( !$this->Application->isAdmin ) { /** @var kDBItem $object */ $object = $event->getObject(); $object->SetDBField('PortalUserId', $object->GetOriginalField('PortalUserId')); if ( $object->GetDBField('PortalUserId') != $this->Application->RecallVar('user_id') ) { $object->SetError('PortalUserId', 'not_owner'); } } } /** * [HOOK] Stores affiliate id using method from Config (session or cookie) if correct code is present in url * * @param kEvent $event * @return bool */ function OnStoreAffiliate($event) { if ( defined('IS_INSTALL') && IS_INSTALL ) { return; } /** @var kDBItem $object */ $object = $this->Application->recallObject($event->Prefix . '.-item', null, Array ('skip_autoload' => true)); $affiliate_storage_method = $this->Application->ConfigValue('Comm_AffiliateStorageMethod'); $affiliate = $this->Application->GetVar('affiliate'); if ( $affiliate ) { $object->Load($affiliate, 'AffiliateCode'); } elseif ( $affiliate_storage_method == 2 ) { $affiliate_id = $this->Application->GetVar('affiliate_id'); $object->Load($affiliate_id); } if ( $object->isLoaded() && ($object->GetDBField('Status') == 1) ) { // user is found with such email /** @var UsersItem $affiliate_user */ $affiliate_user = $this->Application->recallObject('u.affiliate', null, Array ('skip_autoload' => true)); $affiliate_user->Load($object->GetDBField('PortalUserId')); if ( $affiliate_user->GetDBField('Status') == 1 ) { $affiliate_id = $object->GetDBField('AffiliateId'); $this->Application->setVisitField('AffiliateId', $affiliate_id); if ( $affiliate_storage_method == 1 ) { $this->Application->StoreVar('affiliate_id', $affiliate_id); // per session } else { // in cookie $this->Application->Session->SetCookie('affiliate_id', $affiliate_id, $this->getCookieExpiration()); } } } } /** * Returns affiliate cookie expiration date * * @return int */ function getCookieExpiration() { $expire = $this->Application->ConfigValue('Comm_AffiliateCookieDuration'); // days return adodb_mktime() + $expire * 24 * 60 * 60; } /** * Calculate what amount is earned by affiliate based on it's affiliate plan & store it * * @param kEvent $event * @author Alex */ function OnOrderApprove($event) { /** @var OrdersItem $order */ $order = $this->Application->recallObject($event->getEventParam('Order_PrefixSpecial')); $affiliate_id = $order->GetDBField('AffiliateId'); if ( !$affiliate_id ) { return false; } /** @var kDBItem $object */ $object = $event->getObject(Array ('ship_autoload' => true)); if ( $object->Load($affiliate_id) ) { /** @var kDBItem $affiliate_plan */ $affiliate_plan = $this->Application->recallObject('ap', null, Array ('skip_autoload' => true)); $affiliate_plan->Load($object->GetDBField('AffiliatePlanId')); if ( $affiliate_plan->isLoaded() ) { $sql = 'SELECT SUM(Quantity) FROM %s WHERE OrderId = %s'; $orderitems_table = $this->Application->getUnitOption('orditems', 'TableName'); $items_sold = $this->Conn->GetOne(sprintf($sql, $orderitems_table, $order->GetID())); $object->SetDBField('AccumulatedAmount', $object->GetDBField('AccumulatedAmount') + $order->GetDBField('TotalAmount')); $object->SetDBField('ItemsSold', $object->GetDBField('ItemsSold') + $items_sold); switch ($affiliate_plan->GetDBField('PlanType')) { case 1: // by amount $value = $object->GetDBField('AccumulatedAmount'); break; case 2: // by items sold (count) $value = $object->GetDBField('ItemsSold'); break; } $apb_table = $this->Application->getUnitOption('apbrackets', 'TableName'); $sql = 'SELECT Percent FROM %1$s WHERE (%2$s >= FromAmount) AND ( (%2$s <= ToAmount) OR (ToAmount = -1) ) AND (AffiliatePlanId = %3$s)'; $commission_percent = $this->Conn->GetOne(sprintf($sql, $apb_table, $this->Conn->qstr($value), $affiliate_plan->GetID())); // process only orders of current affiliate from period start to this order date $period_ends = $order->GetDBField('OrderDate'); $period_starts = $this->getPeriodStartTS($period_ends, $affiliate_plan->GetDBField('ResetInterval')); $sql = 'SELECT AffiliateCommission, (SubTotal+ShippingCost+VAT) AS TotalAmount, OrderId FROM ' . $order->TableName . ' WHERE OrderDate >= %s AND OrderDate <= %s AND AffiliateId = ' . $affiliate_id; $amount_to_pay_before = 0; $amount_to_pay_after = 0; $order_update_sql = 'UPDATE ' . $order->TableName . ' SET AffiliateCommission = %s WHERE ' . $order->IDField . ' = %s'; $orders = $this->Conn->Query(sprintf($sql, $period_starts, $period_ends), 'OrderId'); if ( $orders ) { foreach ($orders as $order_id => $order_data) { $amount_to_pay_before += $order_data['AffiliateCommission']; $commission = $order_data['TotalAmount'] * ($commission_percent / 100); $this->Conn->Query(sprintf($order_update_sql, $this->Conn->qstr($commission), $order_id)); $amount_to_pay_after += $commission; } } $object->SetDBField('AmountToPay', $object->GetDBField('AmountToPay') - $amount_to_pay_before + $amount_to_pay_after); $object->SetDBField('LastOrderDate_date', $order->GetDBField('OrderDate_date')); $object->SetDBField('LastOrderDate_time', $order->GetDBField('OrderDate_time')); $object->Update(); $order->SetDBField('AffiliateCommission', $commission); // set last commission to this order, because ApproveEvent was called for him } } } /** * [HOOK] Validates affiliate fields on user registration form * * @param kEvent $event * @return void * @access protected */ protected function OnValidateAffiliate($event) { if ( $this->Application->GetVar('RegisterAsAffiliate') != 'on' || $event->MasterEvent->status != kEvent::erSUCCESS ) { return; } /** @var kDBItem $object */ $object = $event->getObject( Array('form_name' => 'registration', 'skip_autoload' => true) ); $object->setID(0); $field_values = $this->getSubmittedFields($event); $object->SetFieldsFromHash($field_values); $event->setEventParam('form_data', $field_values); if ( !$object->Validate() ) { /** @var kDBItem $user */ $user = $event->MasterEvent->getObject(); $user->Validate(); $event->MasterEvent->status = kEvent::erFAIL; } } /** * [AFTER HOOK] to u:OnCreate * * @param kEvent $event */ function OnRegisterAffiliate($event) { if ( $this->Application->GetVar('RegisterAsAffiliate') != 'on' || $event->MasterEvent->status != kEvent::erSUCCESS ) { return; } /** @var kDBItem $object */ $object = $event->getObject(); /** @var UsersItem $user */ $user = $event->MasterEvent->getObject(); $object->SetDBField('PortalUserId', $user->GetID()); if ( $object->Create() ) { $this->Application->emailUser('AFFILIATE.REGISTER', $user->GetID()); $this->Application->emailAdmin('AFFILIATE.REGISTER'); } } /** * Returns primary affiliate plan * * @return int * @access protected */ protected function _getPrimaryAffiliatePlan() { $sql = 'SELECT AffiliatePlanId FROM ' . $this->Application->getUnitOption('ap', 'TableName') . ' WHERE IsPrimary = 1'; return (int)$this->Conn->GetOne($sql); } /** * Creates affiliate record for logged-in user * * @param kEvent $event */ function OnBecomeAffiliate($event) { /** @var UsersItem $object */ $object = $event->getObject( Array('form_name' => 'registration', 'skip_autoload' => true) ); $event->CallSubEvent('OnCreate'); if ( $event->status == kEvent::erSUCCESS ) { $event->SetRedirectParam('opener', 's'); $next_template = $this->Application->GetVar('next_template'); if ( $next_template ) { $event->redirect = $next_template; } } } /** * Change affiliate payment type of affiliate record associated with logged-in user * * @param kEvent $event * @return void * @access protected */ protected function OnChangePaymentType($event) { $event->CallSubEvent('OnUpdate'); if ( $event->status == kEvent::erSUCCESS ) { /** @var kDBItem $object */ $object = $event->getObject(); $this->Application->emailUser('AFFILIATE.PAYMENT.TYPE.CHANGED', $object->GetDBField('PortalUserId')); $this->Application->emailAdmin('AFFILIATE.PAYMENT.TYPE.CHANGED'); $next_template = $this->Application->GetVar('next_template'); if ( $next_template ) { $event->redirect = $this->Application->GetVar('next_template'); } $event->SetRedirectParam('opener', 's'); } } /** * If new payments made, then send email about that * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(kEvent $event) { parent::OnBeforeDeleteFromLive($event); /** @var kDBItem $payment_object */ $payment_object = $this->Application->recallObject('apayments', 'apayments', Array ('skip_autoload' => true)); $id = $event->getEventParam('id'); $ap_table = $this->Application->getUnitOption('apayments', 'TableName'); $sql = 'SELECT AffiliatePaymentId FROM ' . $ap_table . ' WHERE AffiliateId = ' . $id; $live_ids = $this->Conn->GetCol($sql); $sql = 'SELECT AffiliatePaymentId FROM ' . $payment_object->TableName . ' WHERE AffiliateId = ' . $id; $temp_ids = $this->Conn->GetCol($sql); $new_ids = array_diff($temp_ids, $live_ids); foreach ($new_ids as $payment_id) { $payment_object->Load($payment_id); $this->Application->emailUser('AFFILIATE.PAYMENT', $payment_object->GetDBField('PortalUserId')); $this->Application->emailAdmin('AFFILIATE.PAYMENT'); } /** @var kDBItem $object */ $object = $event->getObject(); $passed_id = $event->getEventParam('id'); if ( $object->GetID() != $passed_id ) { $object->Load($passed_id); } $sql = 'SELECT Status FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE ' . $object->IDField . ' = ' . $object->GetID(); $old_status = $this->Conn->GetOne($sql); if ( $old_status == 2 && $object->GetDBField('Status') == 1 ) { $this->Application->emailUser('AFFILIATE.REGISTRATION.APPROVED', $object->GetDBField('PortalUserId')); $this->Application->emailAdmin('AFFILIATE.REGISTRATION.APPROVED'); } } /** * [HOOK] Resets statistics (accumulated amount & items sold) for affiliates based on ResetInterval in their plan * * @param kEvent $event * @author Alex */ function OnResetStatistics($event) { if ( defined('IS_INSTALL') && IS_INSTALL ) { return; } $intervals = Array (86400 => 'la_day', 604800 => 'la_week', 2628000 => 'la_month', 7884000 => 'la_quartely', 31536000 => 'la_year'); $affiliates_table = $this->Application->getUnitOption($event->Prefix, 'TableName'); $affiliate_plan_table = $this->Application->getUnitOption('ap', 'TableName'); $base_time = adodb_mktime(); $where_clause = Array (); foreach ($intervals as $interval_length => $interval_description) { $start_timestamp = $this->getPeriodStartTS($base_time, $interval_length); $where_clause[] = 'ap.ResetInterval = ' . $interval_length . ' AND LastOrderDate < ' . $start_timestamp; } $sql = 'SELECT AffiliateId FROM ' . $affiliates_table . ' a LEFT JOIN ' . $affiliate_plan_table . ' ap ON a.AffiliatePlanId = ap.AffiliatePlanId WHERE (' . implode(') OR (', $where_clause) . ')'; $affiliate_ids = $this->Conn->GetCol($sql); if ( !$affiliate_ids ) { return; } if ( defined('DEBUG_MODE') && DEBUG_MODE && $this->Application->isDebugMode() ) { $this->Application->Debugger->appendHTML('Affiliates Pending Totals Reset: '); $this->Application->Debugger->dumpVars($affiliate_ids); } $fields_hash = Array ( 'AccumulatedAmount' => 0, 'ItemsSold' => 0, 'LastOrderDate' => $base_time, ); $this->Conn->doUpdate($fields_hash, $affiliates_table, 'AffiliateId IN (' . implode(',', $affiliate_ids) . ')'); } /** * Returns calendar period start timestamp based on current timestamp ($base_time) and $period_length * * @param int $base_time * @param int $period_length * @return int * @author Alex */ function getPeriodStartTS($base_time, $period_length) { $start_timestamp = 0; switch ($period_length) { case 86400: // day $start_timestamp = adodb_mktime(0, 0, 0, adodb_date('m', $base_time), adodb_date('d', $base_time), adodb_date('Y', $base_time)); break; case 604800: // week $day_seconds = 86400; $first_week_day = $this->Application->ConfigValue('FirstDayOfWeek'); $morning = adodb_mktime(0, 0, 0, adodb_date('m', $base_time), adodb_date('d', $base_time), adodb_date('Y', $base_time)); $week_day = adodb_date('w', $morning); if ( $week_day == $first_week_day ) { // if it is already first week day, then don't search for previous week day $day_diff = 0; } else { // this way, because sunday is 0, but not 7 as it should be $day_diff = $week_day != 0 ? $week_day - $first_week_day : 7 - $first_week_day; } $start_timestamp = $morning - $day_diff * $day_seconds; break; case 2628000: // month $start_timestamp = adodb_mktime(0, 0, 0, adodb_date('m', $base_time), 1, adodb_date('Y', $base_time)); break; case 7884000: // quartal $first_quartal_month = (ceil(adodb_date('m', $base_time) / 3) - 1) * 3 + 1; $start_timestamp = adodb_mktime(0, 0, 0, $first_quartal_month, 1, adodb_date('Y', $base_time)); break; case 31536000: $start_timestamp = adodb_mktime(0, 0, 0, 1, 1, adodb_date('Y', $base_time)); break; } return $start_timestamp; } /** * Apply same processing to each item being selected in grid * * @param kEvent $event * @return void * @access protected */ protected function iterateItems(kEvent $event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } /** @var kDBItem $object */ $object = $event->getObject(Array ('skip_autoload' => true)); $ids = $this->StoreSelectedIDs($event); if ( $ids ) { $status_field = $object->getStatusField(); foreach ($ids as $id) { $object->Load($id); switch ($event->Name) { case 'OnMassApprove': $object->SetDBField($status_field, 1); break; case 'OnMassDecline': $object->SetDBField($status_field, 0); break; case 'OnMassMoveUp': $object->SetDBField('Priority', $object->GetDBField('Priority') + 1); break; case 'OnMassMoveDown': $object->SetDBField('Priority', $object->GetDBField('Priority') - 1); break; } if ( $object->Update() ) { switch ($event->Name) { case 'OnMassApprove': $this->Application->emailUser('AFFILIATE.REGISTRATION.APPROVED', $object->GetDBField('PortalUserId')); $this->Application->emailAdmin('AFFILIATE.REGISTRATION.APPROVED'); break; case 'OnMassDecline': $this->Application->emailUser('AFFILIATE.REGISTRATION.DENIED', $object->GetDBField('PortalUserId')); $this->Application->emailAdmin('AFFILIATE.REGISTRATION.DENIED'); break; } $event->SetRedirectParam('opener', 's'); //stay! } } } } /** * Checks that user in affiliate record matches current user * (non permission-based) * * @param kEvent $event * @return bool * @access protected */ protected function checkItemStatus(kEvent $event) { if ( $this->Application->isAdminUser ) { return true; } /** @var kDBItem $object */ $object = $event->getObject(); if ( !$object->isLoaded() ) { return true; } return $object->GetDBField('PortalUserId') == $this->Application->RecallVar('user_id'); } }