<?php
/**
* @package XenCentral Feedback System
* @author DNF Technology
* @copyright Drnoyan & Nalyan LDA, Portugal, EU
* @license http://dnf.technology/terms/
* @link http://customers.dnf.technology
* @version 2.0.0 Beta 10
* @revision 12
*/
/**
* Behavior for feedback
*
* @package XenCentral_Feedback
*/
namespace XenCentral\Feedback\Behavior;
use XF\Db\Exception;
use XF\Mvc\Entity\Behavior;
class Feedback extends Behavior
{
use FeedbackAbstractBehavior;
protected $_originalFeedbackWriter;
public function preSave()
{
$amount=$this->entity->get('amount');
$review=$this->entity->get('review');
$type= $this->entity->get('type');
$dealurl= $this->entity->get('dealurl');
if(!$this->_verifyamount($amount)){
return;
}
if(!$this->_verifyreview($review)){
return;
}
if(!($this->_verifytype($type))){
return;
}
if (!$this->_getOptionsModel()->getImportMode() AND !$this->_verifydealurl($dealurl)) {
return;
};
if ($this->entity->isInsert()) {
$this->entity->set('has_reply', 1);
if ($this->_getOptionsModel()->getRequireFeedbackReply()) {
if ($this->entity->get('threadid')) {
// check if this is a reply feedback
$originalFeedbackId = $this->entity->db()->fetchOne('
SELECT fb_id FROM xf_xc_feedback_feedback
WHERE foruserid=?
AND fromuserid=?
AND threadid=?
', array(
$this->entity->get('fromuserid'),
$this->entity->get('foruserid'),
$this->entity->get('threadid')
));
if ($originalFeedbackId) {
$feedbackWriter = \XF::em()->find('XenCentral\Feedback:Feedback', $originalFeedbackId);
$feedbackWriter->set('has_reply', 1);
$feedbackWriter->save();
} else if ($this->entity->get('amount') == 1) {
$this->entity->set('has_reply', 0);
}
}
}
}
$this->_preSave();
parent::preSave();
}
protected function _preSave()
{
if (!$this->entity->get('foruserid')) {
$this->entity->error(\XF::phrase('xcfs_invalid_user_id'));
} else if (!$this->entity->get('fromuserid')) {
$this->entity->error(\XF::phrase('xcfs_invalid_user_id'));
} else if ($this->entity->get('foruserid') == $this->entity->get('fromuserid')) {
$this->entity->error(\XF::phrase('xcfs_invalid_user_id'));
}
// check for duplicate feedback
if (!$this->_getOptionsModel()->getImportMode() AND $this->entity->isInsert()
&& $this->_getOptionsModel()->getDuplicateTimeLimit()
AND ($latestFeedbackDate = $this->_getFeedbackModel()->getLastFeedbackDate($this->entity->get('fromuserid'), $this->entity->get('foruserid'))) != false
) {
// get last feedback by the user
if (\XF::$time - $latestFeedbackDate < $this->_getOptionsModel()->getDuplicateTimeLimit()) {
$hours = $this->_getOptionsModel()->getDuplicateTimeLimit() - (\XF::$time - $latestFeedbackDate);
$hours = ceil($hours / 3600);
$this->entity->error(\XF::phrase('xcfs_please_wait_x', array('hours' => $hours)));
}
}
if (!$this->_getOptionsModel()->getImportMode() AND
$this->entity->isInsert()
&& !\XF::visitor()->isAdministratorFeedback()
&& $feedbackLimit = $this->_getOptionsModel()->getFeedbackLimit()
) {
$feedbackLeftCount = $this->_getFeedbackModel()->getLeftFeedbackCountForTime($this->entity->get('fromuserid'), $feedbackLimit['time']);
if ($feedbackLeftCount >= $feedbackLimit['count']) {
$this->entity->error(\XF::phrase('xcfs_you_are_allowed_x_in_y', $feedbackLimit));
}
}
}
protected function _getDefaultOptions()
{
return array(
'saveip' => true
);
}
public function postSave()
{
if (is_object($this->_originalFeedbackWriter)) {
$this->_originalFeedbackWriter->save();
}
if(!$this->_getOptionsModel()->getImportMode()) {
if ($this->entity->isInsert() && !$this->entity->get('ip_id')) {
$ipId = \XF::app()->repository('XF:Ip')->logIp($this->entity->get('fromuserid'), $this->entity->get('ip_id'), 'feedback', $this->entity->get('fb_id'), 'insert');
$this->entity->set('ip_id', $ipId['ip_id'], array(
'setAfterPreSave' => true,
'forceSet' => true
));
$this->entity->db()->update('xf_xc_feedback_feedback', array(
'ip_id' => $ipId['ip_id']
), 'fb_id = ' . $this->entity->db()->quote($this->entity->get('fb_id')));
}
} else {
if ($this->entity->isInsert() && $this->entity->getOption('saveip')) {
$ipAddress = $this->entity->getOption('saveip');
$ipId = \XF::app()->repository('XF:Ip')->logIp($this->entity->get('fromuserid'), $ipAddress, 'feedback', $this->entity->get('fb_id'), 'insert');
$this->entity->set('ip_id', $ipId['ip_id'], array(
'setAfterPreSave' => true,
'forceSet' => true
));
$this->entity->db()->update('xf_xc_feedback_feedback', array(
'ip_id' => $ipId['ip_id']
), 'fb_id = ' . $this->entity->db()->quote($this->entity->get('fb_id')));
}
}
if (!$this->_getOptionsModel()->getImportMode()) {
if ($this->entity->isInsert()) {
if ($this->entity->get('amount') < 0) {
\XF::repository('XenCentral\Feedback:NotifyNegative')->sendNotification($this->entity->getNewValues());
}
\XF::repository('XenCentral\Feedback:Notification')->notifyNewFeedback($this->entity->get('fb_id'));
}
$this->_getFeedbackModel()->rebuildUserData($this->entity->get('foruserid'));
}
}
public function delete()
{
if (parent::delete()) {
$this->_getFeedbackModel()->rebuildUserData($this->entity->get('foruserid'));
return true;
}
return false;
}
protected function _verifyamount(&$value)
{
if (!in_array($value, array(
1,
0,
-1
))
) {
$value = 0;
$this->entity->error(\XF::phrase('xcfs_please_select_feedback_type'));
return true;
}
return true;
}
protected function _verifytype(&$value)
{
if (!in_array($value, array(
'sell',
'buy',
'trade'
))
) {
$value = 'trade';
$this->entity->error(\XF::phrase('xcfs_please_select_your_feedback'));
return true;
}
return true;
}
protected function _verifyreview(&$value)
{
if (!$value) {
$value = 'required';
$this->entity->error(\XF::phrase('xcfs_please_enter_your_short_review'));
return true;
}
return true;
}
protected function _verifydealurl(&$value)
{
$forceUrl = \XF::options()->{'xcfs_force_valid_deal_url'};
if ($forceUrl && filter_var($value, FILTER_VALIDATE_URL) === FALSE) {
$this->entity->error(\XF::phrase('please_enter_valid_url'));
return false;
}
if ($forceUrl && empty($value)) {
$this->entity->error(\XF::phrase('please_enter_valid_url'));
return false;
}
if (!$forceUrl && !empty($value) && (filter_var($value, FILTER_VALIDATE_URL) === FALSE)) {
$this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
if(!empty($value)) {
if($this->checkIfAddonIsEnabled('xcmss')){
$currentboardUrl = \XF::options()->{'currentDomain'};
$currUrl = parse_url($currentboardUrl['boardUrl']);
$getUrlFromInput = parse_url($value);
if($currUrl['host']!=$getUrlFromInput['host']){
$this->entity->error(\XF::phrase('xcfs_this_url_not_from_this_board'));
return false;
}
} else {
$currentboardUrl = \XF::options()->boardUrl;
$currUrl = parse_url($currentboardUrl);
$getUrlFromInput = parse_url($value);
if($currUrl['host']!=$getUrlFromInput['host']){
$this->entity->error(\XF::phrase('xcfs_this_url_not_from_this_board'));
return false;
}
}
if(\XF::options()->{'useFriendlyUrls'} && !(\XF::options()->{'includeTitleInUrls'})){
$urlinfo= parse_url(str_replace('&', '&', $value));
$getParts = explode( '/', $urlinfo['path'] );
if ( isset( $getParts[2] ) && empty( $getParts[3] ) ) {
$prefix = $getParts[1];
$getId = $getParts[2];
}
elseif ( !empty( $getParts[3] ) ){
$prefix = $getParts[2];
$getId = $getParts[3];
}
else {
$this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
} elseif (\XF::options()->{'includeTitleInUrls'} && !(\XF::options()->{'useFriendlyUrls'})) {
$urlinfo = parse_url(str_replace('&', '&', $value));
if (isset($urlinfo['query'])) {
$getParts = explode('/', $urlinfo['query']);
}
else { $this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
$prefix = $getParts[0];
if (isset($getParts[1])) {
$getId = explode( '.', $getParts[1] );
if (isset($getId[1])) {
$getId = $getId[1];
}
else {
$this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
}
else { $this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
} elseif (\XF::options()->{'includeTitleInUrls'} && \XF::options()->{'useFriendlyUrls'}){
$urlinfo= parse_url(str_replace('&', '&', $value));
if (isset($urlinfo['path'])) {
$getParts = explode('/', $urlinfo['path']);
}
else { $this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
$prefix = $getParts[0];
//Board URL is primary domain or subdomain
if (isset($getParts[2])) {
$getId = explode( '.', $getParts[2] );
if ( isset( $getId[1] ) ) {
$getId = $getId[1];
}
//Board URL is subdirectory
elseif (isset($getParts[3])) {
$getId = explode( '.', $getParts[3] );
if ( isset( $getId[1] ) ) {
$getId = $getId[1];
}
}
else {
$this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
}
else { $this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
} else {
// Use Full Friendly URLs and Include Content Title in URLs options are disabled.
$urlinfo = parse_url(str_replace('&', '&', $value));
if (isset($urlinfo['query'])) {
$getParts = explode('/', $urlinfo['query']);
}
else {
$this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
$prefix = $getParts[0];
if (isset($getParts[1])) {
$getId = explode( '.', $getParts[1] );
if (!empty($getId[1])) {
$this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
else {
$getId = $getId[0];
}
}
else { $this->entity->error( \XF::phrase('please_enter_valid_url'));
return false;
}
}
if ($this->_getOptionsModel()->getUniqueDealUrl()) {
if ($this->_getFeedbackModel()->getFeedbackForItem($value, $this->entity->get('fromuserid'), $this->entity->get('fb_id'))) {
$this->entity->error(\XF::phrase('xcfs_already_left_feedback'));
return false;
}
}
if ($this->_getOptionsModel()->getCheckThreadStarter()) {
// get thread according to these criteria
$threads = $this->_getToolsModel()->getThreadSuggestions(
'',
array(
$this->entity->get('fromuserid'),
$this->entity->get('foruserid')
),
$getId
);
}
if($prefix !='threads' && $prefix != 'resources' && $prefix != 'media'){
$prefix = \XF::repository('XenCentral\Feedback:Feedback')->getRoutMatchingTheURL($prefix);
$prefix = trim($prefix, '\/');
}
if ($prefix == 'threads') {
if (!$value && !strlen($value)) {
if (!$this->entity->get('threadid')) {
if ($this->_getOptionsModel()->forceThreadUrl() && !$this->_getOptionsModel()->getImportMode()) {
$this->entity->error(\XF::phrase('xcfs_please_enter_valid_url'));
}
$this->entity->set('threadid', 0);
// passed
return true;
}
}
if ($this->_getOptionsModel()->getCheckThreadStarter()) {
if (count($threads[0]) !== 1) {
$this->entity->error(\XF::phrase('xcfs_thread_starter_only'));
return false;
}
}
if (!$this->entity->get('threadid')) {
if (empty($urlinfo)) {
$this->entity->error(\XF::phrase('xcfs_failed_to_parse_url'));
return false;
}
$error = '';
$threadid = $this->_setThreadIdFromUrl($value, $urlinfo, $error, $prefix);
if ($threadid == false && $error) {
$this->entity->error($error);
return false;
}
} else {
$threadid = $this->entity->get('threadid');
}
$thread = $this->app()->em()->find('XF:Thread', $threadid, []);
if (empty($thread)) {
$this->entity->error(\XF::phrase('xcfs_please_enter_valid_url'));
return false;
}
$validForumIDs = $this->_getOptionsModel()->getThreadValidForums();
if (!empty($validForumIDs)) {
if (!in_array($thread['node_id'], $validForumIDs)) {
$forumNames = array();
$nodeModel = $this->_getNodeModel();
$nodes = $nodeModel->getNodeList();
foreach ($validForumIDs AS $forumid) {
if ($nodes[$forumid]) {
$forumNames[] = $nodes[$forumid]['title'];
}
}
$this->entity->error(\XF::phrase('xcfs_threads_are_allowed_from_x', array('forumlist' => implode(', ', $forumNames))));
return false;
}
}
if ($this->_getOptionsModel()->getUniqueDealUrl()) {
if ($this->_getFeedbackModel()->getFeedbackForThread($thread['thread_id'], $this->entity->get('fromuserid'), $this->entity->get('fb_id'))) {
$this->entity->error(\XF::phrase('xcfs_already_left_feedback'));
return false;
}
}
$this->entity->set('threadid', $thread['thread_id']);
}
if ($prefix == 'resources') {
if ($this->checkIfAddonIsEnabled('XFRM')) {
$validResourceIds = $this->_getOptionsModel()->getValidResources();
$resource=$this->app()->em()->find('XFRM:ResourceItem', $getId);
if (empty($resource)) {
$this->entity->error(\XF::phrase('xcfs_please_enter_valid_url'));
return false;
}
if (!empty($validResourceIds)) {
if (!in_array($resource['resource_category_id'], $validResourceIds)) {
$resourceNames = array();
$resModel = $this->_getResourceModel();
$res = $resModel->getViewableCategories();
foreach ($validResourceIds AS $resid) {
if ($res[$resid]) {
$resourceNames[] = $res[$resid]['category_title'];
}
}
$this->entity->error(\XF::phrase('xcfs_resources_are_allowed_from_x', array('resourcelist' => implode(', ', $resourceNames))));
return false;
}
}
if ($this->_getOptionsModel()->getCheckThreadStarter()) {
if (count($threads[1]) !== 1) {
$this->entity->error(\XF::phrase('xcfs_thread_starter_only'));
return false;
}
}
} else {
$this->entity->error(\XF::phrase('xcfs_addon_is_disabled'));
return false;
}
}
if ($prefix == 'media') {
if ($this->checkIfAddonIsEnabled('XFMG')) {
$media = $this->app()->em()->find('XFMG:MediaItem', $getId);
if (empty($media)) {
;
$this->entity->error(\XF::phrase('xcfs_please_enter_valid_url'));
return false;
}
$validMediaIds = $this->_getOptionsModel()->getValidMedia();
if (!empty($validMediaIds)) {
if (!in_array($media['category_id'], $validMediaIds)) {
$mediaNames = array();
$mediaModel = $this->_getMediaModel();
$med = $mediaModel->getViewableCategories();
foreach ($validMediaIds AS $medid) {
if ($med[$medid]) {
$mediaNames[] = $med[$medid]['category_title'];
}
}
$this->entity->error(\XF::phrase('xcfs_media_are_allowed_from_x', array('medialist' => implode(', ', $mediaNames))));
return false;
}
}
if ($this->_getOptionsModel()->getCheckThreadStarter()) {
if (count($threads[2]) !== 1) {
$this->entity->error(\XF::phrase('xcfs_thread_starter_only'));
return false;
}
}
} else {
$this->entity->error(\XF::phrase('xcfs_addon_is_disabled'));
return false;
}
}
if (isset($resourse)) {
$value = $this->app()->router('public')->buildLink('full:resources', $resource);
}
if (isset($media)) {
$value = $this->app()->router('public')->buildLink('full:xengallery', $media);
}
if (isset($thread)) {
$value = $this->app()->router('public')->buildLink('full:threads', $thread);
}
} else {
return true;
}
return true;
}
protected function _setThreadIdFromUrl($value, $urlinfo, &$error, $prefix)
{
// strict check
$boardUrl = \XF::options()->boardUrl;
$domaininfo = parse_url($boardUrl);
$urlinfo = parse_url(str_replace('&', '&', $value));
if ($this->_getDomainNameFromHost($urlinfo['host']) != $this->_getDomainNameFromHost($domaininfo['host'])) {
$error = \XF::phrase('xcfs_only_url_from_this_board_is_allowed');
return false;
}
$uri = (isset($urlinfo['path']) ? $urlinfo['path'] : '') . (isset($urlinfo['query']) ? '?' . $urlinfo['query'] : '');
/*
* if (XenForo_Application::getInstance()->isRegistered('routeFiltersOut')) {
$routeFilters = XenForo_Application::get('routeFiltersOut');
if (isset($routeFilters['threads'])) {
foreach ($routeFilters['threads'] AS $filter) {
if(isset($filter['find_route'])) {
list($from, $to) = XenForo_Link::translateRouteFilterToRegex(
$filter['find_route'], $filter['replace_route']
);
} else {
$from=$filter['match_regex'];
$to=$filter['match_replace'];
}
$newPrefix = preg_replace($from, $to, $prefix);
if ($newPrefix != $prefix) {
$prefix = $newPrefix;
break;
} else {
$newPrefix = preg_replace($from, $to, $prefix . '/');
if ($newPrefix != $prefix . '/') {
$prefix = rtrim($newPrefix, '/');
break;
}
}
}
}
}
*/
if (!preg_match('#(/)?' . $prefix . '/([^/]+\.)?(\d+)(/|$)#', $uri, $match)) {
$error = \XF::phrase('xcfs_please_enter_valid_url');
return false;
}
$threadid = $match[3];
return $threadid;
}
/**
* @param $domain
* @param bool $debug
* @return string
* Reused from https://gist.github.com/pocesar/5366899
*/
protected function _getDomainNameFromHost($domain, $debug = false)
{
$original = $domain = strtolower($domain);
if (filter_var($domain, FILTER_VALIDATE_IP)) {
return $domain;
}
$debug ? print('<strong style="color:green">»</strong> Parsing: ' . $original) : false;
$arr = array_slice(array_filter(explode('.', $domain, 4), array($this, '_filterNoEmpty')), 0); //rebuild array indexes
if (count($arr) > 2) {
$count = count($arr);
$_sub = explode('.', $count === 4 ? $arr[3] : $arr[2]);
$debug ? print(" (parts count: {$count})") : false;
if (count($_sub) === 2) // two level TLD
{
$removed = array_shift($arr);
if ($count === 4) // got a subdomain acting as a domain
{
$removed = array_shift($arr);
}
$debug ? print("<br>\n" . '[*] Two level TLD: <strong>' . join('.', $_sub) . '</strong> ') : false;
} elseif (count($_sub) === 1) // one level TLD
{
$removed = array_shift($arr); //remove the subdomain
if (strlen($_sub[0]) === 2 && $count === 3) // TLD domain must be 2 letters
{
array_unshift($arr, $removed);
} else {
// non country TLD according to IANA
$tlds = array(
'aero',
'arpa',
'asia',
'biz',
'cat',
'com',
'coop',
'edu',
'gov',
'info',
'jobs',
'mil',
'mobi',
'museum',
'name',
'net',
'org',
'post',
'pro',
'tel',
'travel',
'xxx',
);
if (count($arr) > 2 && in_array($_sub[0], $tlds) !== false) //special TLD don't have a country
{
array_shift($arr);
}
}
$debug ? print("<br>\n" . '[*] One level TLD: <strong>' . join('.', $_sub) . '</strong> ') : false;
} else // more than 3 levels, something is wrong
{
for ($i = count($_sub); $i > 1; $i--) {
$removed = array_shift($arr);
}
$debug ? print("<br>\n" . '[*] Three level TLD: <strong>' . join('.', $_sub) . '</strong> ') : false;
}
} elseif (count($arr) === 2) {
$arr0 = array_shift($arr);
if (strpos(join('.', $arr), '.') === false
&& in_array($arr[0], array('localhost', 'test', 'invalid')) === false
) // not a reserved domain
{
$debug ? print("<br>\n" . 'Seems invalid domain: <strong>' . join('.', $arr) . '</strong> re-adding: <strong>' . $arr0 . '</strong> ') : false;
// seems invalid domain, restore it
array_unshift($arr, $arr0);
}
}
$debug ? print("<br>\n" . '<strong style="color:gray">«</strong> Done parsing: <span style="color:red">' . $original . '</span> as <span style="color:blue">'
. join('.', $arr) . "</span><br>\n") : false;
return join('.', $arr);
}
function _filterNoEmpty($value)
{
return $value !== 'www';
}
}