Service.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. <?php
  2. namespace addons\cms\library;
  3. use addons\cms\library\aip\AipContentCensor;
  4. use addons\cms\library\aip\AipNlp;
  5. use addons\cms\model\Autolink;
  6. use addons\cms\model\Diyform;
  7. use addons\cms\model\Fields;
  8. use addons\cms\model\Modelx;
  9. use addons\cms\model\Tag;
  10. use fast\Http;
  11. use think\Cache;
  12. use think\Config;
  13. use think\Db;
  14. use think\Hook;
  15. class Service
  16. {
  17. /**
  18. * 检测内容是否合法
  19. * @param string $content 检测内容
  20. * @param string $type 类型
  21. * @return bool
  22. */
  23. public static function isContentLegal($content, $type = null)
  24. {
  25. $config = get_addon_config('cms');
  26. $type = is_null($type) ? $config['audittype'] : $type;
  27. if ($type == 'local') {
  28. // 敏感词过滤
  29. $handle = SensitiveHelper::init()->setTreeByFile(ADDON_PATH . 'cms/data/words.dic');
  30. //首先检测是否合法
  31. $isLegal = $handle->islegal($content);
  32. return $isLegal ? true : false;
  33. } elseif ($type == 'baiduyun') {
  34. $client = new AipContentCensor($config['aip_appid'], $config['aip_apikey'], $config['aip_secretkey']);
  35. $result = $client->textCensorUserDefined($content);
  36. if (!isset($result['conclusionType']) || $result['conclusionType'] > 1) {
  37. return false;
  38. }
  39. }
  40. return true;
  41. }
  42. /**
  43. * 获取标题的关键字
  44. * @param $title
  45. * @return array
  46. */
  47. public static function getContentTags($title)
  48. {
  49. $arr = [];
  50. $config = get_addon_config('cms');
  51. if ($config['nlptype'] == 'local') {
  52. !defined('_VIC_WORD_DICT_PATH_') && define('_VIC_WORD_DICT_PATH_', ADDON_PATH . 'cms/data/dict.json');
  53. $handle = new VicWord('json');
  54. $result = $handle->getAutoWord($title);
  55. foreach ($result as $index => $item) {
  56. $arr[] = $item[0];
  57. }
  58. } else {
  59. $client = new AipNlp($config['aip_appid'], $config['aip_apikey'], $config['aip_secretkey']);
  60. $result = $client->lexer($title);
  61. if (isset($result['items'])) {
  62. foreach ($result['items'] as $index => $item) {
  63. if (!in_array($item['pos'], ['v', 'vd', 'nd', 'a', 'ad', 'an', 'd', 'm', 'q', 'r', 'p', 'c', 'u', 'xc', 'w'])) {
  64. $arr[] = $item['item'];
  65. }
  66. }
  67. }
  68. }
  69. foreach ($arr as $index => $item) {
  70. if (mb_strlen($item) == 1) {
  71. unset($arr[$index]);
  72. }
  73. }
  74. return array_filter(array_unique($arr));
  75. }
  76. /**
  77. * 内容关键字自动加链接
  78. * 优先顺序为 站点配置自动链接 > 自动链接表 > 标签内链
  79. */
  80. public static function autolinks($content)
  81. {
  82. $links = [];
  83. //先移除已有的自动链接
  84. $content = preg_replace_callback('~<a data\-rel="autolink" .*?>(.*?)</a>~i', function ($match) {
  85. return $match[1];
  86. }, $content);
  87. //存储所有标签
  88. $content = preg_replace_callback('~(<a .*?>.*?</a>|<.*?>)~i', function ($match) use (&$links) {
  89. return '<' . array_push($links, $match[1]) . '>';
  90. }, $content);
  91. $config = get_addon_config('cms');
  92. $limit = 2; //单一标签最大替换次数
  93. $redirect = $config['redirecturl'] ?? true; //是否是用跳转
  94. $autolinkArr = [];
  95. $tagList = Tag::where('autolink', 1)->cache(true)->where('status', 'normal')->select();
  96. foreach ($tagList as $index => $item) {
  97. $autolinkArr[$item['name']] = ['text' => $item['name'], 'type' => 'tag', 'url' => $item['fullurl']];
  98. }
  99. $autolinkList = Autolink::where('status', 'normal')->cache(true)->order('weigh DESC,id DESC')->select();
  100. foreach ($autolinkList as $index => $item) {
  101. $autolinkArr[$item['title']] = ['text' => $item['title'], 'type' => 'autolink', 'url' => $item['url'], 'target' => $item['target'], 'id' => $item['id']];
  102. }
  103. foreach ($config['autolinks'] as $text => $url) {
  104. $autolinkArr[$text] = ['text' => $text, 'type' => 'config', 'url' => $url];
  105. }
  106. $autolinkArr = array_values($autolinkArr);
  107. //字符串长的优先替换
  108. usort($autolinkArr, function ($a, $b) {
  109. if ($a['text'] == $b['text']) return 0;
  110. return (strlen($a['text']) > strlen($b['text'])) ? -1 : 1;
  111. });
  112. //替换链接
  113. foreach ($autolinkArr as $index => $item) {
  114. $content = preg_replace_callback('/(' . $item['text'] . ')/i', function ($match) use ($item, $redirect, $config) {
  115. if ($item['type'] == 'tag') {
  116. $url = $item['url'];
  117. } elseif ($item['type'] == 'autolink' || $redirect) {
  118. $params = [];
  119. $params['url'] = $item['url'];
  120. if (isset($item['id'])) {
  121. $params['id'] = $item['id'];
  122. }
  123. $url = addon_url('cms/go/index', [], $config['urlsuffix'], true) . '?' . http_build_query($params);
  124. } else {
  125. $url = $item['url'];
  126. }
  127. return '<a data-rel="autolink" href="' . $url . '" ' . (isset($item['target']) && $item['target'] == 'blank' ? ' target="_blank"' : '') . '>' . $match[0] . '</a>';
  128. }, $content, $limit);
  129. $content = preg_replace_callback('~(<a .*?>.*?</a>)~i', function ($match) use (&$links) {
  130. return '<' . array_push($links, $match[1]) . '>';
  131. }, $content);
  132. }
  133. return preg_replace_callback('/<(\d+)>/', function ($match) use (&$links) {
  134. return $links[$match[1] - 1];
  135. }, $content);
  136. }
  137. /**
  138. * 推送消息通知
  139. * @param string $content 内容
  140. * @param string $type 类型
  141. * @param string $template_id 模板ID
  142. */
  143. public static function notice($content, $type, $template_id)
  144. {
  145. try {
  146. if ($type == 'dinghorn') {
  147. Hook::listen('msg_notice', $template_id, [
  148. 'content' => $content
  149. ]);
  150. } elseif ($type == 'vbot') {
  151. Hook::listen('vbot_send_msg', $template_id, [
  152. 'content' => $content
  153. ]);
  154. }
  155. } catch (\Exception $e) {
  156. }
  157. }
  158. /**
  159. * 获取表字段信息
  160. * @param string $table 表名
  161. * @return array
  162. */
  163. public static function getTableFields($table)
  164. {
  165. $tagName = "cms-table-fields-{$table}";
  166. $fieldlist = Cache::get($tagName);
  167. if (!Config::get('app_debug') && $fieldlist) {
  168. return $fieldlist;
  169. }
  170. $dbname = Config::get('database.database');
  171. //从数据库中获取表字段信息
  172. $sql = "SELECT * FROM `information_schema`.`columns` WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION";
  173. //加载主表的列
  174. $columnList = Db::query($sql, [$dbname, $table]);
  175. $fieldlist = [];
  176. foreach ($columnList as $index => $item) {
  177. $fieldlist[] = ['name' => $item['COLUMN_NAME'], 'title' => $item['COLUMN_COMMENT'], 'type' => $item['DATA_TYPE']];
  178. }
  179. Cache::set($tagName, $fieldlist);
  180. return $fieldlist;
  181. }
  182. /**
  183. * 获取指定类型的自定义字段列表
  184. */
  185. public static function getCustomFields($source, $source_id, $values = [], $conditions = [])
  186. {
  187. $fields = Fields::where('source', $source)
  188. ->where('source_id', $source_id)
  189. ->where($conditions)
  190. ->where('status', 'normal')
  191. ->order('weigh desc,id desc')
  192. ->select();
  193. foreach ($fields as $k => $v) {
  194. //优先取编辑的值,再次取默认值
  195. $v->value = isset($values[$v['name']]) ? $values[$v['name']] : (is_null($v['defaultvalue']) ? '' : $v['defaultvalue']);
  196. $v->rule = str_replace(',', '; ', $v->rule);
  197. if (in_array($v['type'], ['checkbox', 'lists', 'images'])) {
  198. $checked = '';
  199. if ($v['minimum'] && $v['maximum']) {
  200. $checked = "{$v['minimum']}~{$v['maximum']}";
  201. } elseif ($v['minimum']) {
  202. $checked = "{$v['minimum']}~";
  203. } elseif ($v['maximum']) {
  204. $checked = "~{$v['maximum']}";
  205. }
  206. if ($checked) {
  207. $v->rule .= (';checked(' . $checked . ')');
  208. }
  209. }
  210. if (in_array($v['type'], ['checkbox', 'radio']) && stripos($v->rule, 'required') !== false) {
  211. $v->rule = str_replace('required', 'checked', $v->rule);
  212. }
  213. if (in_array($v['type'], ['selects'])) {
  214. $v->extend .= (' ' . 'data-max-options="' . $v['maximum'] . '"');
  215. }
  216. }
  217. return $fields;
  218. }
  219. /**
  220. * 获取过滤列表
  221. * @param string $source 来源类型
  222. * @param int $source_id 来源ID
  223. * @param array $filter 过滤条件
  224. * @param array $params 搜索参数
  225. * @param bool $multiple 是否为复选模式
  226. * @return array
  227. */
  228. public static function getFilterList($source, $source_id, $filter, $params = [], $multiple = false)
  229. {
  230. $fieldsList = Fields::where('source', $source)
  231. ->where('source_id', $source_id)
  232. ->where('status', 'normal')
  233. ->cache(true)
  234. ->select();
  235. $filterList = [];
  236. $multiValueFields = [];
  237. $fields = [];
  238. if (in_array($source, ['model', 'diyform'])) {
  239. //查找主表启用过滤搜索的字段
  240. $model = $source == 'model' ? Modelx::get($source_id) : Diyform::get($source_id);
  241. $setting = $model->setting;
  242. if (isset($setting['filterfields'])) {
  243. foreach ($setting['filterfields'] as $index => $name) {
  244. $title = isset($setting['titlelist'][$name]) ? $setting['titlelist'][$name] : $name;
  245. $filterlist = isset($setting['filterlist'][$name]) ? $setting['filterlist'][$name] : '';
  246. $filterlist = \app\common\model\Config::decode($filterlist);
  247. if (!$filterlist) {
  248. continue;
  249. }
  250. if (in_array($name, ['special_ids', 'channel_ids', 'images', 'tags', 'keywords'])) {
  251. $multiValueFields[] = $name;
  252. }
  253. $fields[] = [
  254. 'name' => $name,
  255. 'title' => $title,
  256. 'content' => $filterlist
  257. ];
  258. }
  259. }
  260. }
  261. foreach ($fieldsList as $k => $v) {
  262. if (!$v['isfilter']) {
  263. continue;
  264. }
  265. $content = isset($v['filter_list']) && $v['filter_list'] ? $v['filter_list'] : $v['content_list'];
  266. if (!$content) {
  267. continue;
  268. }
  269. //多选值字段需要做特殊处理
  270. if (in_array($v['type'], ['selects', 'checkbox', 'array', 'selectpages'])) {
  271. $multiValueFields[] = $v['name'];
  272. }
  273. $fields[] = [
  274. 'name' => $v['name'],
  275. 'title' => $v['title'],
  276. 'content' => $content
  277. ];
  278. }
  279. $filter = array_intersect_key($filter, array_flip(array_column($fields, 'name')));
  280. foreach ($fields as $k => $v) {
  281. $content = [];
  282. $all = ['' => __('All')] + (is_array($v['content']) ? $v['content'] : []);
  283. foreach ($all as $m => $n) {
  284. $filterArr = isset($filter[$v['name']]) && $filter[$v['name']] !== '' ? ($multiple ? explode(';', $filter[$v['name']]) : [$filter[$v['name']]]) : [];
  285. $active = ($m === '' && !$filterArr) || ($m !== '' && in_array($m, $filterArr)) ? true : false;
  286. if ($active) {
  287. $current = implode(';', array_diff($filterArr, [$m]));
  288. } else {
  289. $current = $multiple ? implode(';', array_merge($filterArr, [$m])) : $m;
  290. }
  291. $prepare = $m === '' ? array_diff_key($filter, [$v['name'] => $m]) : array_merge($filter, [$v['name'] => $current]);
  292. //$url = '?' . http_build_query(array_merge(['filter' => $prepare], array_diff_key($params, ['filter' => ''])));
  293. $url = '?' . str_replace(['%2C', '%3B'], [',', ';'], http_build_query(array_merge($prepare, array_intersect_key($params, array_flip(['orderby', 'orderway', 'multiple'])))));
  294. $content[] = ['value' => $m, 'title' => $n, 'active' => $active, 'url' => $url];
  295. }
  296. $filterList[] = [
  297. 'name' => $v['name'],
  298. 'title' => $v['title'],
  299. 'content' => $content,
  300. ];
  301. }
  302. foreach ($filter as $index => &$item) {
  303. $item = is_array($item) ? $item : explode(',', str_replace(';', ',', $item));
  304. }
  305. return [$filterList, $filter, $params, $fields, $multiValueFields, $fieldsList];
  306. }
  307. /**
  308. * 获取排序列表
  309. * @param string $orderby
  310. * @param string $orderway
  311. * @param array $orders
  312. * @param array $params
  313. * @param array $fieldsList
  314. * @return array
  315. */
  316. public static function getOrderList($orderby, $orderway, $orders = [], $params = [], $fieldsList = [])
  317. {
  318. $lastOrderby = '';
  319. $lastOrderway = $orderway && in_array(strtolower($orderway), ['asc', 'desc']) ? $orderway : 'desc';
  320. foreach ($fieldsList as $index => $field) {
  321. if ($field['isorder']) {
  322. $orders[] = ['name' => $field['name'], 'field' => $field['name'], 'title' => $field['title']];
  323. }
  324. }
  325. $orderby = in_array($orderby, array_map(function ($item) {
  326. return $item['name'];
  327. }, $orders)) ? $orderby : 'default';
  328. foreach ($orders as $index => $order) {
  329. if ($orderby == $order['name']) {
  330. $lastOrderby = $order['field'];
  331. break;
  332. }
  333. }
  334. $orderList = [];
  335. foreach ($orders as $k => $v) {
  336. $url = '?' . http_build_query(array_merge($params, ['orderby' => $v['name'], 'orderway' => $v['name'] == $orderby ? ($lastOrderway == 'desc' ? 'asc' : 'desc') : 'desc']));
  337. $v['active'] = $orderby == $v['name'] ? true : false;
  338. $v['url'] = $url;
  339. $orderList[] = $v;
  340. }
  341. return [$orderList, $lastOrderby, $lastOrderway];
  342. }
  343. /**
  344. * 获取过滤的最终条件和绑定参数
  345. * @param array $filter 过滤条件
  346. * @param array $multiValueFields 多值字段
  347. * @param bool $multiple 是否为复选模式
  348. * @return array
  349. */
  350. public static function getFilterWhereBind($filter, $multiValueFields, $multiple = false)
  351. {
  352. //构造bind数据
  353. $bind = [];
  354. foreach ($filter as $field => &$item) {
  355. if (in_array($field, $multiValueFields)) {
  356. $item = !is_array($item) && stripos($item, ',') !== false ? explode(',', $item) : $item;
  357. if (is_array($item)) {
  358. foreach ($item as $index => $subitem) {
  359. $bind[$field . $index] = $subitem;
  360. }
  361. } else {
  362. $bind[$field] = $item;
  363. }
  364. }
  365. }
  366. $filterWhere = function ($query) use ($filter, $multiValueFields) {
  367. foreach ($filter as $field => $item) {
  368. $item = is_array($item) ? $item : [$item];
  369. if (in_array($field, $multiValueFields)) {
  370. $query->where(function ($query) use ($field, $item) {
  371. foreach ($item as $subindex => $subitem) {
  372. $query->whereOr("FIND_IN_SET(:" . $field . $subindex . ", `{$field}`)");
  373. }
  374. });
  375. } else {
  376. $query->where(function ($query) use ($field, $item) {
  377. foreach ($item as $subindex => $subitem) {
  378. //如果匹配区间,以~分隔
  379. if (preg_match("/[a-zA-Z0-9\.\-]+\~[a-zA-Z0-9\.\-]+/", $subitem)) {
  380. $condition = explode('~', $subitem);
  381. //判断是否时间区间
  382. $op = preg_match("/\d{4}\-\d{1,2}\-\d{1,2}/", $condition[0]) ? 'between time' : 'between';
  383. $query->whereOr($field, $op, $condition);
  384. } else {
  385. $query->whereOr($field, $subitem);
  386. }
  387. }
  388. });
  389. }
  390. }
  391. };
  392. return [$filterWhere, $bind];
  393. }
  394. /**
  395. * 追加_text属性值
  396. * @param $fieldsContentList
  397. * @param $row
  398. */
  399. public static function appendTextAttr(&$fieldsContentList, &$row)
  400. {
  401. //附加列表字段
  402. array_walk($fieldsContentList, function ($content, $field) use (&$row) {
  403. if (isset($row[$field])) {
  404. if (isset($content[$row[$field]])) {
  405. $list = [$row[$field] => $content[$row[$field]]];
  406. } else {
  407. $keys = $values = explode(',', $row[$field]);
  408. foreach ($values as $index => &$item) {
  409. $item = isset($content[$item]) ? $content[$item] : $item;
  410. }
  411. $list = array_combine($keys, $values);
  412. }
  413. } else {
  414. $list = [];
  415. }
  416. $list = array_filter($list);
  417. $row[$field . '_text'] = implode(',', $list);
  418. $row[$field . '_list'] = $list;
  419. });
  420. }
  421. }