BxAntispamLassoModeration.php 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup Antispam Antispam
  7. * @ingroup UnaModules
  8. *
  9. * @{
  10. */
  11. class BxAntispamLassoModeration extends BxDol
  12. {
  13. protected $_sModule;
  14. protected $_oModule;
  15. protected $_sEndpoint;
  16. protected $_sApiKey;
  17. protected $_sWebhookSecret;
  18. protected $_sAction;
  19. protected $_bNotify;
  20. protected $_aThresholds;
  21. public function __construct()
  22. {
  23. parent::__construct();
  24. $this->_sModule = 'bx_antispam';
  25. $this->_oModule = BxDolModule::getInstance($this->_sModule);
  26. $this->_sEndpoint = 'https://api.lassomoderation.com/api/v1';
  27. $this->_sApiKey = $this->_oModule->_oConfig->getAntispamOption('lm_api_key');
  28. $this->_sWebhookSecret = $this->_oModule->_oConfig->getAntispamOption('lm_webhook_secret');
  29. $this->_sAction = $this->_oModule->_oConfig->getAntispamOption('lm_action');
  30. $this->_bNotify = $this->_oModule->_oConfig->getAntispamOption('lm_report') == 'on';
  31. $this->_aThresholds = [
  32. 'toxicity' => (int)$this->_oModule->_oConfig->getAntispamOption('lm_thd_toxicity'),
  33. 'threat' => (int)$this->_oModule->_oConfig->getAntispamOption('lm_thd_threat'),
  34. 'identity_attack' => (int)$this->_oModule->_oConfig->getAntispamOption('lm_thd_identity_attack'),
  35. 'profanity' => (int)$this->_oModule->_oConfig->getAntispamOption('lm_thd_profanity')
  36. ];
  37. }
  38. public function processEvent()
  39. {
  40. $sInput = @file_get_contents("php://input");
  41. if(!$sInput)
  42. return 200;
  43. $aHeaders = getallheaders();
  44. if(empty($aHeaders['X-Lasso-Signature'])) {
  45. $this->log($sInput, 'Webhook: Cannot get Signature.');
  46. return 404;
  47. }
  48. $sInputHmac = hash_hmac('sha256', $sInput, $this->_sWebhookSecret, true);
  49. $sInputHmac = 'sha256=' . base64_encode($sInputHmac);
  50. if($sInputHmac !== $aHeaders['X-Lasso-Signature']) {
  51. $this->log($sInput, 'Webhook: Wrong Signature.');
  52. $this->log([
  53. 'secret' => $this->_sWebhookSecret,
  54. 'sig_get' => $aHeaders['X-Lasso-Signature'],
  55. 'sig_calc' => $sInputHmac
  56. ]);
  57. return 404;
  58. }
  59. $aEvent = json_decode($sInput, true);
  60. if(empty($aEvent) || !is_array($aEvent) || !isset($aEvent['actions'])) {
  61. $this->log($sInput, 'Webhook: Wrong input data.');
  62. return 404;
  63. }
  64. foreach($aEvent['actions'] as $aAction) {
  65. $sMethod = '_processEvent' . bx_gen_method_name($aAction['type']);
  66. if(!method_exists($this, $sMethod))
  67. continue;
  68. $sModule = '';
  69. $iContentId = 0;
  70. switch($aAction['type']) {
  71. case 'user':
  72. break;
  73. case 'topic':
  74. break;
  75. case 'content':
  76. $iContentId = $this->_getId($aAction['content']['id']);
  77. $iModuleId = $this->_getId($aAction['content']['topic_id']);
  78. if(!$iContentId || !$iModuleId)
  79. break;
  80. $aModule = $this->_oModule->_oDb->getModuleById($iModuleId);
  81. if(empty($aModule) || !is_array($aModule))
  82. break;
  83. $sModule = $aModule['name'];
  84. $this->$sMethod($sModule, $iContentId, $aAction['status'], $aAction['content']['analysis']);
  85. break;
  86. }
  87. if($this->_bNotify && $sModule != '' && $iContentId != 0)
  88. $this->_onHarmfulContentFound($sModule, $iContentId);
  89. }
  90. return 200;
  91. }
  92. public function addContent($sModule, $iId, $aData = [])
  93. {
  94. $aModule = BxDolModuleQuery::getInstance()->getModuleByName($sModule);
  95. if(empty($aModule) || !is_array($aModule))
  96. return false;
  97. $iAuthorId = !empty($aData['author_id']) ? $aData['author_id'] : bx_srv($sModule, 'get_author', [$iId]);
  98. $sAuthorName = !empty($aData['author_name']) ? $aData['author_name'] : BxDolProfile::getInstanceMagic($iAuthorId)->getDisplayName();
  99. $iDataAdded = !empty($aData['date_added']) ? $aData['date_added'] : bx_srv($sModule, 'get_date_added', [$iId]);
  100. $sText = !empty($aData['text']) ? $aData['text'] : bx_srv($sModule, 'get_text', [$iId]);
  101. $aParams = [
  102. 'project' => [
  103. 'id' => md5(BX_DOL_URL_ROOT),
  104. 'name' => getParam('site_title'),
  105. ],
  106. 'topic' => [
  107. 'id' => 'mod_' . $aModule['id'],
  108. 'name' => $aModule['title'],
  109. ],
  110. 'user' => [
  111. 'id' => 'prof_' . $iAuthorId,
  112. 'name' => $sAuthorName
  113. ],
  114. 'content_id' => $sModule . '_' . $iId,
  115. 'created_at' => bx_time_utc($iDataAdded ? $iDataAdded : time()),
  116. 'text' => $sText,
  117. 'image_urls' => isset($aData['images']) ? $aData['images'] : [],
  118. 'video_urls' => isset($aData['videos']) ? $aData['videos'] : []
  119. ];
  120. return $this->_call('/content', $aParams);
  121. }
  122. public function log($mixedContents, $sTitle = '')
  123. {
  124. $this->_oModule->log($mixedContents, 'Lasso Moderation', $sTitle);
  125. }
  126. /**
  127. * Internal methods.
  128. */
  129. protected function _call($sRequest, $aParams, $sMethod = 'post-json', $aHeaders = [])
  130. {
  131. $aHeaders[] = 'Authorization: Bearer ' . $this->_sApiKey;
  132. $sResult = bx_file_get_contents($this->_sEndpoint . $sRequest, $aParams, $sMethod, $aHeaders);
  133. if(empty($sResult)) {
  134. $this->log($sResult, 'Call (' . $sRequest . '): ');
  135. $this->log($aParams);
  136. return false;
  137. }
  138. $aResult = json_decode($sResult, true);
  139. if(empty($aResult) || !is_array($aResult) || !isset($aResult['success'])) {
  140. $this->log($sResult, 'Call (' . $sRequest . '): ');
  141. $this->log($aParams);
  142. return false;
  143. }
  144. return true;
  145. }
  146. protected function _processEventContent($sModule, $iContentId, $sStatus, $aAnalysis)
  147. {
  148. $sFldStatus = 'status_admin';
  149. $aContentInfo = bx_srv($sModule, 'get_info', [$iContentId, false]);
  150. if(!isset($aContentInfo[$sFldStatus]))
  151. return;
  152. $sContentStatus = '';
  153. switch($sStatus) {
  154. case 'allowed':
  155. if($aContentInfo[$sFldStatus] != 'active')
  156. $sContentStatus = 'active';
  157. break;
  158. case 'hidden':
  159. if($aContentInfo[$sFldStatus] != 'hidden' && $this->_sAction == 'disapprove')
  160. $sContentStatus = 'hidden';
  161. break;
  162. case 'flagged':
  163. foreach($this->_aThresholds as $sName => $iThreshold) {
  164. if(100 * $aAnalysis[$sName] < $iThreshold)
  165. continue;
  166. if($aContentInfo[$sFldStatus] != 'hidden' && $this->_sAction == 'disapprove') {
  167. $sContentStatus = 'hidden';
  168. break;
  169. }
  170. }
  171. break;
  172. }
  173. if(!empty($sContentStatus))
  174. bx_srv($sModule, 'set_status', [$iContentId, $sContentStatus, $sFldStatus]);
  175. }
  176. protected function _onHarmfulContentFound($sModule, $iContentId)
  177. {
  178. $oPermalinks = BxDolPermalinks::getInstance();
  179. $oModule = BxDolModule::getInstance($sModule);
  180. $CNF = &$oModule->_oConfig->CNF;
  181. $sContentUrl = isset($CNF['URI_VIEW_ENTRY']) ? bx_absolute_url($oPermalinks->permalink('page.php?i=' . $CNF['URI_VIEW_ENTRY'] . '&id=' . $iContentId)) : false;
  182. $sManageContentUrl = isset($CNF['URL_MANAGE_ADMINISTRATION']) ? bx_absolute_url($oPermalinks->permalink('page.php?i=' . $CNF['URL_MANAGE_ADMINISTRATION'])) : false;
  183. $iAuthorId = 0;
  184. if(bx_is_srv($sModule, 'get_author'))
  185. $iAuthorId = bx_srv($sModule, 'get_author', [$iContentId]);
  186. $oAuthor = BxDolProfile::getInstanceMagic($iAuthorId);
  187. $aPlus = [
  188. 'AuthorUrl' => $oAuthor->getUrl(),
  189. 'AuthorNickName' => $oAuthor->getDisplayName(),
  190. 'bx_if:content_url' => [
  191. 'condition' => boolval($sContentUrl),
  192. 'content' => ['c_url' => $sContentUrl],
  193. ],
  194. 'bx_if:manage_content_url' => [
  195. 'condition' => boolval($sManageContentUrl),
  196. 'content' => ['m_url' => $sManageContentUrl],
  197. ],
  198. ];
  199. $aTemplate = BxDolEmailTemplates::getInstance()->parseTemplate('bx_antispam_lasso_moderation_report', $aPlus);
  200. if(!$aTemplate)
  201. trigger_error('Email template or translation missing: bx_antispam_lasso_moderation_report', E_USER_ERROR);
  202. sendMail(getParam('site_email'), $aTemplate['Subject'], $aTemplate['Body']);
  203. bx_alert('bx_antispam', 'harmful_content_posted', $iContentId, bx_get_logged_profile_id(), [
  204. 'module' => $sModule,
  205. 'entry_id' => $iContentId,
  206. 'entry_url' => $sContentUrl,
  207. ]);
  208. }
  209. protected function _getId($s)
  210. {
  211. return (int)substr($s, strrpos($s, '_') + 1);
  212. }
  213. }
  214. /** @} */