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' ) { /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); $parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix'); /** @var kCatDBItem $main_object */ $main_object = $this->Application->recallObject($parent_prefix); $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 /** @var kPermissionsHelper $perm_helper */ $perm_helper = $this->Application->recallObject('PermissionsHelper'); $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 $review_id = key($items_info); } else { // when adding new review in admin $review_id = false; } if (!$review_id) { return false; } /** @var kDBItem $object */ $object = $event->getObject(); // 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 = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix'); $sql = 'SELECT ' . $this->Application->getUnitOption($parent_prefix, 'IDField') .' FROM ' . $this->Application->getUnitOption($parent_prefix, 'TableName') .' 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->getUnitOption('ci', 'TableName') .' 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->getUnitOption($main_prefix, 'PermItemPrefix'); } /** * Apply any custom changes to list's sql query * * @param kEvent $event * @access protected * @see OnListBuild */ protected function SetCustomQuery(kEvent $event) { parent::SetCustomQuery($event); /** @var kDBList $object */ $object = $event->getObject(); 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(); /** @var kDBItem $parent */ $parent = $this->Application->recallObject($parent_info['ParentPrefix']); $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->getUnitOption($regs[1], 'ItemType'); $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 = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix'); /** @var kCatDBItem $main_object */ $main_object = $this->Application->recallObject($parent_prefix); $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); /** @var kDBItem $object */ $object = $event->getObject(); $parent_info = $object->getLinkedInfo(); $item_type = $this->Application->getUnitOption($parent_info['ParentPrefix'], 'ItemType'); $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 ; } /** @var SpamHelper $spam_helper */ $spam_helper = $this->Application->recallObject('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); /** @var kDBItem $object */ $object = $event->getObject(); $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 ) { /** @var SpamHelper $spam_helper */ $spam_helper = $this->Application->recallObject('SpamHelper'); /** @var kDBItem $object */ $object = $event->getObject(); $parent_info = $object->getLinkedInfo(); $config_mapping = $this->Application->getUnitOption($event->Prefix, 'ConfigMapping'); $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 ) { $email_event = $this->getPermPrefix($event) . '.REVIEW.' . ($review_status == STATUS_ACTIVE ? 'ADD' : 'ADD.PENDING'); $this->Application->emailUser($email_event, $object->GetDBField('CreatedById')); $this->Application->emailAdmin($email_event); } } } /** * Updates item review counter * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemUpdate(kEvent $event) { parent::OnAfterItemUpdate($event); $this->updateSubitemCounters($event); /** @var kDBItem $object */ $object = $event->getObject(); 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')); } } } /** * Loads main object of review (link, article, etc.) * * @param kEvent $event * @return kCatDBItem */ function _loadMainObject($event) { /** @var kDBItem $object */ $object = $event->getObject(); $parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix'); $parent_table_key = $this->Application->getUnitOption($event->Prefix, 'ParentTableKey'); $foreign_key = $this->Application->getUnitOption($event->Prefix, 'ForeignKey'); /** @var kDBItem $main_object */ $main_object = $this->Application->recallObject($parent_prefix, null, Array ('skip_autoload' => true)); $main_object->Load($object->GetDBField($foreign_key), $parent_table_key); } /** * 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; } /** @var kDBItem $object */ $object = $event->getObject(); $parent_prefix = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix'); $parent_table = $this->Application->getUnitOption($parent_prefix, 'TableName'); 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; } /** @var kDBItem $object */ $object = $event->getObject(); if ( $this->Application->GetVar('ajax') == 'yes' ) { /** @var AjaxFormHelper $ajax_form_helper */ $ajax_form_helper = $this->Application->recallObject('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 = $this->Application->getUnitOption($event->Prefix, 'ParentPrefix'); $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) // 1. add join to items table (for "Structure & Data" -> "Reviews" section) $item_table = $this->Application->getUnitOption($regs[1], 'TableName'); $ci_table = $this->Application->getUnitOption('ci', 'TableName'); /** @var array $list_sqls */ $list_sqls = $this->Application->getUnitOption($event->Prefix, 'ListSQLs'); $list_sqls[''] .= PHP_EOL . 'LEFT JOIN ' . $item_table . ' item_table ON item_table.ResourceId = %1$s.ItemId'; $list_sqls[''] .= PHP_EOL . 'LEFT JOIN ' . $ci_table . ' ci ON item_table.ResourceId = ci.ItemResourceId AND ci.PrimaryCat = 1'; $this->Application->setUnitOption($event->Prefix, 'ListSQLs', $list_sqls); // 2. add calculated field $calculated_fields = $this->Application->getUnitOption($event->Prefix, 'CalculatedFields'); $calculated_fields['']['CatalogItemName'] = 'item_table.' . $this->getTitleField($regs[1]); $calculated_fields['']['CatalogItemId'] = 'item_table.' . $this->Application->getUnitOption($regs[1], 'IDField'); $calculated_fields['']['CatalogItemCategory'] = 'ci.CategoryId'; $this->Application->setUnitOption($event->Prefix, 'CalculatedFields', $calculated_fields); } } /** * Convert TitleField field of kMultiLanguage formatter used for it * * @param string $prefix * @return string */ function getTitleField($prefix) { $lang_prefix = 'l'.$this->Application->GetVar('m_lang').'_'; $title_field = $this->Application->getUnitOption($prefix, 'TitleField'); $field_options = $this->Application->getUnitOption($prefix.'.'.$title_field, 'Fields'); $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->Application->getUnitOption($event->Prefix, 'ParentPrefix'); $event->SetRedirectParam('pass', 'm,' . $event->getPrefixSpecial() . ',' . $parent_prefix); } }