Special == 'product' && !$this->Application->isAdmin ) { // rev.product should auto-link return ''; } return parent::getMainSpecial($event); } /** * Checks REVIEW/REVIEW.PENDING permission by main object primary category (not current category) * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { if ( $event->Name == 'OnAddReview' || $event->Name == 'OnCreate' ) { $perm_helper = $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $parent_prefix = $event->getUnitConfig()->getParentPrefix(); $main_object = $this->Application->recallObject($parent_prefix); /* @var $main_object kCatDBItem */ $perm_name = $this->getPermPrefix($event).'.REVIEW'; $res = $this->Application->CheckPermission($perm_name, 0, $main_object->GetDBField('CategoryId')) || $this->Application->CheckPermission($perm_name.'.PENDING', 0, $main_object->GetDBField('CategoryId')); if ( !$res ) { $event->status = kEvent::erPERM_FAIL; } return $res; } $check_events = Array ( 'OnItemBuild', 'OnUpdate', /*'OnMassApprove', 'OnMassDecline'*/ ); $perm_category = $this->_getReviewCategory($event); if ( in_array($event->Name, $check_events) ) { // check for PRODUCT.VIEW permission $perm_helper = $this->Application->recallObject('PermissionsHelper'); /* @var $perm_helper kPermissionsHelper */ $perm_prefix = $this->getPermPrefix($event); if ( $perm_category === false ) { // no item id present -> allow return true; } switch ($event->Name) { case 'OnItemBuild': $res = $this->Application->CheckPermission($perm_prefix . '.VIEW', 0, $perm_category); break; case 'OnUpdate': case 'OnMassApprove': case 'OnMassDecline': $res = $this->Application->CheckPermission($perm_prefix . '.ADD', 0, $perm_category) || $this->Application->CheckPermission($perm_prefix . '.MODIFY', 0, $perm_category); break; default: $res = false; break; } if ( !$res ) { $event->status = kEvent::erPERM_FAIL; } return $res; } return parent::CheckPermission($event); } /** * Returns primary category of review's main item * * @param kEvent $event * @return int */ function _getReviewCategory($event) { $items_info = $this->Application->GetVar($event->getPrefixSpecial()); if ($items_info) { // rev:PresetFormFields is used to initialize new review creation list ($review_id, ) = each($items_info); } else { // when adding new review in admin $review_id = false; } if (!$review_id) { return false; } $object = $event->getObject(); /* @var $object kDBItem */ // 1. get main item resource id (use object, because of temp tables in admin) $sql = 'SELECT ItemId FROM ' . $object->TableName . ' WHERE ' . $object->IDField . ' = ' . $review_id; $resource_id = $this->Conn->GetOne($sql); // 2. set main item id (for permission checks) $parent_prefix = $event->getUnitConfig()->getParentPrefix(); $parent_config = $this->Application->getUnitConfig($parent_prefix); $sql = 'SELECT ' . $parent_config->getIDField() .' FROM ' . $parent_config->getTableName() .' WHERE ResourceId = ' . $resource_id; $this->Application->SetVar($parent_prefix . '_id', $this->Conn->GetOne($sql)); // 3. get main item category $sql = 'SELECT CategoryId FROM ' . $this->Application->getUnitConfig('ci')->getTableName() .' WHERE ItemResourceId = ' . $resource_id .' AND PrimaryCat = 1'; return $this->Conn->GetOne($sql); } /** * Returns prefix for permissions * * @param kEvent $event */ function getPermPrefix($event) { $main_prefix = $this->Application->GetTopmostPrefix($event->Prefix, true); // this will return LINK for l, ARTICLE for n, TOPIC for bb, PRODUCT for p return $this->Application->getUnitConfig($main_prefix)->getPermItemPrefix(); } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @access protected * @see OnListBuild */ protected function SetCustomQuery(kEvent $event) { parent::SetCustomQuery($event); $object = $event->getObject(); /* @var $object kDBList */ if ( !$this->Application->isAdminUser ) { $object->addFilter('active', '%1$s.Status = ' . STATUS_ACTIVE); } switch ($event->Special) { case 'showall': $object->clearFilters(); break; case 'item': // used ? $object->clearFilters(); $parent_info = $object->getLinkedInfo(); $parent = $this->Application->recallObject($parent_info['ParentPrefix']); /* @var $parent kDBItem */ $object->addFilter('item_reviews', '%1$s.ItemId = ' . $parent->GetDBField('ResourceId')); break; case 'products': // used in In-Portal (Structure & Data -> Reviews section) $object->removeFilter('parent_filter'); // this is important $object->addFilter('product_reviews', 'pr.ResourceId IS NOT NULL'); break; } if ( preg_match('/(.*)-rev/', $event->Prefix, $regs) ) { // "Structure & Data" -> "Reviews" (section in K4) $item_type = $this->Application->getUnitConfig($regs[1])->getItemType(); $object->addFilter('itemtype_filter', '%1$s.ItemType = ' . $item_type); if ( $this->Application->isAdmin ) { // temporarily solution so we can see sub-items on separate grid in Admin $object->removeFilter('parent_filter'); } } if ( $event->getEventParam('type') == 'current_user' ) { $object->addFilter('current_user', '%1$s.CreatedById = ' . $this->Application->RecallVar('user_id')); $object->addFilter('current_ip', '%1$s.IPAddress = "' . $this->Application->getClientIp() . '"'); } } /** * Adds review from front in case if user is logged in * * @param kEvent $event */ function OnAddReview($event) { $event->CallSubEvent('OnCreate'); } /** * Get new review status on user review permission * * @param kEvent $event * @return int */ function getReviewStatus($event) { $parent_prefix = $event->getUnitConfig()->getParentPrefix(); $main_object = $this->Application->recallObject($parent_prefix); /* @var $main_object kCatDBItem */ $ret = STATUS_DISABLED; $perm_name = $this->getPermPrefix($event).'.REVIEW'; if ($this->Application->CheckPermission($perm_name, 0, $main_object->GetDBField('CategoryId'))) { $ret = STATUS_ACTIVE; } else if ($this->Application->CheckPermission($perm_name.'.PENDING', 0, $main_object->GetDBField('CategoryId'))) { $ret = STATUS_PENDING; } return $ret; } /** * Prefills all fields on front-end * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); $object = $event->getObject(); /* @var $object kDBItem */ $parent_info = $object->getLinkedInfo(); $item_type = $this->Application->getUnitConfig($parent_info['ParentPrefix'])->getItemType(); $object->SetDBField('IPAddress', $this->Application->getClientIp()); $object->SetDBField('ItemType', $item_type); $object->SetDBField('Module', $this->Application->findModule('Var', $parent_info['ParentPrefix'], 'Name')); if ( $this->Application->isAdminUser ) { // don't perform spam control on admin return ; } $spam_helper = $this->Application->recallObject('SpamHelper'); /* @var $spam_helper SpamHelper */ $spam_helper->InitHelper($parent_info['ParentId'], 'Review', 0); if ( $spam_helper->InSpamControl() ) { $event->status = kEvent::erFAIL; $object->SetError('ReviewText', 'too_frequent', 'lu_ferror_review_duplicate'); return; } $rating = $object->GetDBField('Rating'); if ( $rating < 1 || $rating > 5 ) { $object->SetDBField('Rating', null); } $object->SetDBField('ItemId', $parent_info['ParentId']); // ResourceId $object->SetDBField('CreatedById', $this->Application->RecallVar('user_id')); $object->SetDBField('Status', $this->getReviewStatus($event)); $object->SetDBField('TextFormat', 0); // set plain text format directly } /** * Sets correct rating value * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); $object = $event->getObject(); /* @var $object kDBItem */ $rating = $object->GetDBField('Rating'); if ( !$rating ) { $object->SetDBField('Rating', null); } } /** * Updates item review counter * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemCreate(kEvent $event) { parent::OnAfterItemCreate($event); $this->updateSubitemCounters($event); if ( !$this->Application->isAdminUser ) { $spam_helper = $this->Application->recallObject('SpamHelper'); /* @var $spam_helper SpamHelper */ $object = $event->getObject(); /* @var $object kDBItem */ $parent_info = $object->getLinkedInfo(); $config_mapping = $event->getUnitConfig()->getConfigMapping(); $review_settings = $config_mapping['ReviewDelayValue'] . ':' . $config_mapping['ReviewDelayInterval']; $spam_helper->InitHelper($parent_info['ParentId'], 'Review', $review_settings); $spam_helper->AddToSpamControl(); $review_status = $object->GetDBField('Status'); if ( $review_status == STATUS_ACTIVE || $review_status == STATUS_PENDING ) { $send_params = $object->getEmailParams(); $email_event = $this->getPermPrefix($event) . '.REVIEW.' . ($review_status == STATUS_ACTIVE ? 'ADD' : 'ADD.PENDING'); $this->Application->emailUser($email_event, $object->GetDBField('CreatedById'), $send_params); $this->Application->emailAdmin($email_event, null, $send_params); } } } /** * Updates item review counter * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { parent::OnAfterItemUpdate($event); $this->updateSubitemCounters($event); $object = $event->getObject(); /* @var $object kDBItem */ if ( $this->Application->isAdminUser && !$object->IsTempTable() ) { // send email on review status change from reviews grid in admin $review_status = $object->GetDBField('Status'); $process_status = Array (STATUS_ACTIVE, STATUS_DISABLED); if ( ($review_status != $object->GetOriginalField('Status')) && in_array($review_status, $process_status) ) { $this->_loadMainObject($event); $email_event = $this->getPermPrefix($event) . '.REVIEW.' . ($review_status == STATUS_ACTIVE ? 'APPROVE' : 'DENY'); $this->Application->emailUser($email_event, $object->GetDBField('CreatedById'), $object->getEmailParams()); } } } /** * Loads main object of review (link, article, etc.) * * @param kEvent $event * @return kCatDBItem */ function _loadMainObject($event) { $object = $event->getObject(); /* @var $object kDBItem */ $config = $event->getUnitConfig(); $parent_prefix = $config->getParentPrefix(); $main_object = $this->Application->recallObject($parent_prefix, null, Array ('skip_autoload' => true)); /* @var $main_object kDBItem */ $main_object->Load($object->GetDBField($config->getForeignKey()), $config->getParentTableKey()); } /** * Updates total review counter, cached rating, votes count * * @param kEvent $event */ function updateSubitemCounters($event) { if ( $event->Special == '-item' ) { // ignore Main Item Copy/Pasting and Deleting return; } $object = $event->getObject(); /* @var $object kDBItem */ $parent_prefix = $event->getUnitConfig()->getParentPrefix(); $parent_table = $this->Application->getUnitConfig($parent_prefix)->getTableName(); if ( $object->IsTempTable() ) { $parent_table = $this->Application->GetTempName($parent_table, 'prefix:' . $object->Prefix); } $fields_hash = Array ('CachedReviewsQty' => 0, 'CachedRating' => 0, 'CachedVotesQty' => 0); // 1. update review counter $sql = 'SELECT COUNT(ReviewId) FROM ' . $object->TableName . ' WHERE ItemId = ' . $object->GetDBField('ItemId'); $fields_hash['CachedReviewsQty'] = $this->Conn->GetOne($sql); // 2. update votes counter + rating $rating = $object->GetDBField('Rating'); $sql = 'SELECT CachedRating, CachedVotesQty FROM ' . $parent_table . ' WHERE ResourceId = ' . $object->GetDBField('ItemId'); $parent_data = $this->Conn->GetRow($sql); $avg_rating = $parent_data['CachedRating']; $votes_count = $parent_data['CachedVotesQty']; switch ($event->Name) { case 'OnAfterItemCreate': // adding new review with rating $this->changeRating($avg_rating, $votes_count, $rating, '+'); break; case 'OnAfterItemDelete': $this->changeRating($avg_rating, $votes_count, $rating, '-'); break; case 'OnAfterItemUpdate': $this->changeRating($avg_rating, $votes_count, $object->GetOriginalField('Rating'), '-'); $this->changeRating($avg_rating, $votes_count, $rating, '+'); break; } $fields_hash['CachedRating'] = "$avg_rating"; $fields_hash['CachedVotesQty'] = $votes_count; $this->Conn->doUpdate($fields_hash, $parent_table, 'ResourceId = ' . $object->GetDBField('ItemId')); } /** * Changes average rating and votes count based on requested operation * * @param float $avg_rating average rating before new vote * @param int $votes_count votes count before new vote * @param int $rating new vote (from 1 to 5) * @param string $operation requested operation (+ / -) */ function changeRating(&$avg_rating, &$votes_count, $rating, $operation) { if ( $rating < 1 || $rating > 5 ) { return; } if ( $operation == '+' ) { $avg_rating = (($avg_rating * $votes_count) + $rating) / ($votes_count + 1); ++$votes_count; } else { if ( $votes_count > 1 ) { // escape division by 0 $avg_rating = (($avg_rating * $votes_count) - $rating) / ($votes_count - 1); } else { $avg_rating = (($avg_rating * $votes_count) - $rating) / 1; } --$votes_count; } } /** * Updates main item cached review counter * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemDelete(kEvent $event) { parent::OnAfterItemDelete($event); $this->updateSubitemCounters($event); } /** * Creates review & redirect to confirmation template * * @param kEvent $event * @return void * @access protected */ protected function OnCreate(kEvent $event) { parent::OnCreate($event); if ( $event->status != kEvent::erSUCCESS || $this->Application->isAdmin ) { return; } $object = $event->getObject(); /* @var $object kDBItem */ if ( $this->Application->GetVar('ajax') == 'yes' ) { $ajax_form_helper = $this->Application->recallObject('AjaxFormHelper'); /* @var $ajax_form_helper AjaxFormHelper */ $params = Array ('status' => 'OK'); if ( $event->status != kEvent::erSUCCESS ) { $ajax_form_helper->prepareJSONErrors($event, $params); } // let FormManager decide what template to show $params['review_status'] = $object->GetDBField('Status'); $ajax_form_helper->sendResponse($event, $params); } else { $event->SetRedirectParam('opener', 's'); $next_template = $object->GetDBField('Status') == STATUS_ACTIVE ? 'success_template' : 'success_pending_template'; $event->redirect = $this->Application->GetVar($next_template); $parent_prefix = $event->getUnitConfig()->getParentPrefix(); $event->SetRedirectParam('pass', 'm,'.$parent_prefix); } } /** * Makes left join to item's table, when in separate grid * * @param kEvent $event * @return void * @access protected */ protected function OnAfterConfigRead(kEvent $event) { parent::OnAfterConfigRead($event); if (preg_match('/(.*)-rev/', $event->Prefix, $regs) && $this->Application->prefixRegistred($regs[1])) { // "Structure & Data" -> "Reviews" (section in K4) $config = $event->getUnitConfig(); $item_config = $this->Application->getUnitConfig($regs[1]); // 1. add join to items table (for "Structure & Data" -> "Reviews" section) $item_table = $item_config->getTableName(); $ci_table = $this->Application->getUnitConfig('ci')->getTableName(); $list_sql = $config->getListSQLsBySpecial(''); $list_sql .= ' LEFT JOIN '.$item_table.' item_table ON item_table.ResourceId = %1$s.ItemId'; $list_sql .= ' LEFT JOIN '.$ci_table.' ci ON item_table.ResourceId = ci.ItemResourceId AND ci.PrimaryCat = 1'; $config->setListSQLsBySpecial('', $list_sql); // 2. add calculated field $config->addCalculatedFieldsBySpecial('', Array ( 'CatalogItemName' => 'item_table.' . $this->getTitleField($regs[1]), 'CatalogItemId' => 'item_table.' . $item_config->getIDField(), 'CatalogItemCategory' => 'ci.CategoryId', )); } } /** * Convert TitleField field of kMultiLanguage formatter used for it * * @param string $prefix * @return string */ function getTitleField($prefix) { $config = $this->Application->getUnitConfig($prefix); $lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_'; $title_field = $config->getTitleField(); $field_options = $config->getFieldByName($title_field); $formatter_class = isset($field_options['formatter']) ? $field_options['formatter'] : ''; if ($formatter_class == 'kMultiLanguage' && !isset($field_options['master_field'])) { $title_field = $lang_prefix.$title_field; } return $title_field; } /** * Set's new perpage for Category Item Reviews (used on Front-end) * * @param kEvent $event * @return void * @access protected */ protected function OnSetPerPage(kEvent $event) { parent::OnSetPerPage($event); $parent_prefix = $event->getUnitConfig()->getParentPrefix(); $event->SetRedirectParam('pass', 'm,' . $event->getPrefixSpecial() . ',' . $parent_prefix); } }