Service.php 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // |
  4. // +----------------------------------------------------------------------
  5. // |
  6. // +----------------------------------------------------------------------
  7. // |
  8. // +----------------------------------------------------------------------
  9. // |
  10. // +----------------------------------------------------------------------
  11. // +----------------------------------------------------------------------
  12. // | cms处理类
  13. // +----------------------------------------------------------------------
  14. namespace addons\cms\library;
  15. use addons\cms\model\ModelField;
  16. use think\facade\Config;
  17. use think\facade\Db;
  18. use think\facade\Validate;
  19. class Service
  20. {
  21. private static $ext_table = '_data';
  22. protected static function physicalTableName($tableName)
  23. {
  24. $prefix = Config::get('database.connections.mysql.prefix', '');
  25. return strpos($tableName, $prefix) === 0 ? $tableName : $prefix . $tableName;
  26. }
  27. protected static function tableQuery($tableName)
  28. {
  29. return Db::table(self::physicalTableName($tableName));
  30. }
  31. //查询解析模型数据用以构造from表单
  32. public static function getFieldList($modelId, $id = null, $lang = null)
  33. {
  34. $list = ModelField::where('modelid', $modelId)->where('status', 1)->order('listorder DESC,id DESC')->select();
  35. if (!empty($list)) {
  36. //编辑信息时查询出已有信息
  37. if ($id) {
  38. $modelInfo = Db::name('cms_model')->where('id', $modelId)->field('tablename,type')->find();
  39. $mainTable = $modelInfo['tablename'];
  40. $dataInfo = self::tableQuery($mainTable)->where('id', $id)->find();
  41. $currentLang = $lang ?: request()->param('lang/s', '');
  42. if (!$currentLang && function_exists('cms_current_lang')) {
  43. $currentLang = cms_current_lang();
  44. }
  45. if (!$currentLang) {
  46. $currentLang = Db::name('lang')->where('is_default', 1)->value('mark') ?: 'cn';
  47. }
  48. if ($modelInfo['type'] == 2 && !empty($dataInfo)) {
  49. $extTable = $modelInfo['tablename'] . self::$ext_table;
  50. $extFields = array_map('strtolower', array_keys(Db::getFields(self::physicalTableName($extTable))));
  51. if (in_array('lang', $extFields, true)) {
  52. $defaultLang = Db::name('lang')->where('is_default', 1)->value('mark') ?: 'cn';
  53. $dataInfoExt = self::tableQuery($extTable)->where(['did' => $dataInfo['id'], 'lang' => $currentLang])->find();
  54. if (!$dataInfoExt && $currentLang !== $defaultLang) {
  55. $dataInfoExt = self::tableQuery($extTable)->where(['did' => $dataInfo['id'], 'lang' => $defaultLang])->find();
  56. }
  57. } else {
  58. $dataInfoExt = self::tableQuery($extTable)->where(['did' => $dataInfo['id']])->find();
  59. }
  60. }
  61. }
  62. $langMirrorFields = ['title', 'keywords', 'description', 'tags'];
  63. $defaultLang = Db::name('lang')->where('is_default', 1)->value('mark') ?: 'cn';
  64. $currentLang = $lang ?: request()->param('lang/s', '');
  65. if (!$currentLang && function_exists('cms_current_lang')) {
  66. $currentLang = cms_current_lang();
  67. }
  68. $currentLang = $currentLang ?: $defaultLang;
  69. $fieldBuckets = [];
  70. foreach ($list as $key => &$value) {
  71. if ($value['iscore']) {
  72. unset($list[$key]);
  73. continue;
  74. }
  75. $fieldBuckets[$value['name']][] = $key;
  76. }
  77. unset($value);
  78. foreach ($fieldBuckets as $fieldName => $indexes) {
  79. if (count($indexes) <= 1) {
  80. continue;
  81. }
  82. $mainIndex = null;
  83. $extIndex = null;
  84. foreach ($indexes as $idx) {
  85. if ((int) $list[$idx]['ifsystem'] === 1) {
  86. $mainIndex = $idx;
  87. } else {
  88. $extIndex = $idx;
  89. }
  90. }
  91. if ($mainIndex === null || $extIndex === null) {
  92. continue;
  93. }
  94. if (in_array($fieldName, $langMirrorFields, true)) {
  95. $keepIndex = $currentLang === $defaultLang ? $mainIndex : $extIndex;
  96. } else {
  97. $keepIndex = $extIndex;
  98. }
  99. $dropIndex = $keepIndex === $mainIndex ? $extIndex : $mainIndex;
  100. unset($list[$dropIndex]);
  101. }
  102. foreach ($list as $key => &$value) {
  103. $isLangMirror = in_array($value['name'], $langMirrorFields, true);
  104. if ($value['ifsystem'] && !$isLangMirror) {
  105. $value['fieldArr'] = 'modelField';
  106. if (isset($dataInfo[$value['name']])) {
  107. $value['value'] = $dataInfo[$value['name']];
  108. }
  109. } else {
  110. $value['fieldArr'] = 'modelFieldExt';
  111. if (isset($dataInfoExt[$value['name']])) {
  112. $value['value'] = $dataInfoExt[$value['name']];
  113. } elseif ($isLangMirror && isset($dataInfo[$value['name']])) {
  114. $value['value'] = $dataInfo[$value['name']];
  115. } elseif (isset($dataInfo[$value['name']])) {
  116. $value['value'] = $dataInfo[$value['name']];
  117. }
  118. }
  119. //扩展配置
  120. $value['options'] = $value['setting']['options'] ?? '';
  121. //在新增时候添加默认值
  122. if (!$id) {
  123. $value['value'] = $value['setting']['value'] ?? '';
  124. }
  125. if ($value['type'] == 'custom') {
  126. if ($value['options'] != '') {
  127. $tpar = explode(".", $value['options'], 2);
  128. $value['options'] = \think\Response::create('admin@custom/' . $tpar[0], 'view')->assign('vo', $value)->getContent();
  129. unset($tpar);
  130. }
  131. } elseif ($value['options'] != '') {
  132. $value['options'] = parse_attr($value['options']);
  133. }
  134. /*if ('' != $value['options']) {
  135. $value['options'] = parse_attr($value['options']);
  136. }*/
  137. if ($value['type'] == 'checkbox') {
  138. $value['value'] = empty($value['value']) ? [] : explode(',', $value['value']);
  139. }
  140. if ($value['type'] == 'datetime') {
  141. $value['value'] = empty($value['value']) ? date('Y-m-d H:i:s') : date('Y-m-d H:i:s', $value['value']);
  142. }
  143. if ($value['type'] == 'date') {
  144. $value['value'] = empty($value['value']) ? '' : date('Y-m-d', $value['value']);
  145. }
  146. }
  147. }
  148. return $list;
  149. }
  150. //处理post提交的模型数据
  151. public static function dealModelPostData($modeId, $data, $dataExt = [], $ignoreField = [])
  152. {
  153. //字段类型
  154. $query = ModelField::where('modelid', $modeId)->where('status', 1);
  155. if ([] != $ignoreField) {
  156. $query = $query->where('name', 'not in', $ignoreField);
  157. }
  158. $filedTypeList = $query->order('listorder DESC, id DESC')->column('title,type,ifsystem,ifrequire,pattern,errortips', 'name');
  159. $currentLang = request()->post('modelFieldExt.lang/s', '') ?: request()->param('lang/s', '');
  160. if (!$currentLang && function_exists('cms_current_lang')) {
  161. $currentLang = cms_current_lang();
  162. }
  163. $defaultLang = Db::name('lang')->where('is_default', 1)->value('mark') ?: 'cn';
  164. $langMirrorFields = ['title', 'keywords', 'description', 'tags'];
  165. foreach ($filedTypeList as $name => $vo) {
  166. $arr = $vo['ifsystem'] ? 'data' : 'dataExt';
  167. if ($currentLang && $currentLang !== $defaultLang && in_array($name, $langMirrorFields, true)) {
  168. if ($arr === 'data' && (!isset($data[$name]) || $data[$name] === '') && isset($dataExt[$name]) && $dataExt[$name] !== '') {
  169. $data[$name] = $dataExt[$name];
  170. }
  171. if ($arr === 'dataExt' && (!isset($dataExt[$name]) || $dataExt[$name] === '') && isset($data[$name]) && $data[$name] !== '') {
  172. $dataExt[$name] = $data[$name];
  173. }
  174. }
  175. if (!isset(${$arr}[$name])) {
  176. switch ($vo['type']) {
  177. // 开关
  178. case 'switch':
  179. ${$arr}[$name] = 0;
  180. break;
  181. case 'checkbox':
  182. ${$arr}[$name] = '';
  183. break;
  184. case 'radio':
  185. ${$arr}[$name] = '';
  186. break;
  187. }
  188. } else {
  189. if (is_array(${$arr}[$name])) {
  190. ${$arr}[$name] = implode(',', ${$arr}[$name]);
  191. }
  192. switch ($vo['type']) {
  193. // 开关
  194. case 'switch':
  195. ${$arr}[$name] = 1;
  196. break;
  197. // 日期+时间
  198. case 'datetime':
  199. //if ($vo['ifeditable']) {
  200. ${$arr}[$name] = strtotime(${$arr}[$name]);
  201. //}
  202. break;
  203. // 日期
  204. case 'date':
  205. ${$arr}[$name] = strtotime(${$arr}[$name]);
  206. break;
  207. }
  208. }
  209. //数据必填验证
  210. if ($vo['ifrequire']) {
  211. $currentValue = ${$arr}[$name] ?? '';
  212. if (in_array($name, $langMirrorFields, true)) {
  213. $mirrorValue = $arr === 'data' ? ($dataExt[$name] ?? '') : ($data[$name] ?? '');
  214. if ($currentValue === '' && $mirrorValue !== '') {
  215. $currentValue = $mirrorValue;
  216. }
  217. }
  218. if ($currentValue === '') {
  219. throw new \Exception("'" . $vo['title'] . "'必须填写~");
  220. }
  221. }
  222. //正则校验
  223. if (isset(${$arr}[$name]) && ${$arr}[$name] && $vo['pattern'] && !Validate::regex(${$arr}[$name], $vo['pattern'])) {
  224. throw new \Exception("'" . $vo['title'] . "'" . (!empty($vo['errortips']) ? $vo['errortips'] : '正则校验失败') . "");
  225. }
  226. //数据格式验证
  227. if (!empty(${$arr}[$name]) && in_array($vo['type'], ['number']) && !Validate::isNumber(${$arr}[$name])) {
  228. throw new \Exception("'" . $vo['title'] . "'格式错误~");
  229. //安全过滤
  230. } else {
  231. }
  232. }
  233. return [$data, $dataExt];
  234. }
  235. /**
  236. * 数据处理 前端显示
  237. * @param $fieldinfo
  238. * @param $data
  239. */
  240. public static function dealModelShowData($fieldinfo, $data)
  241. {
  242. $newdata = [];
  243. foreach ($data as $key => $value) {
  244. if (isset($fieldinfo[$key])) {
  245. switch ($fieldinfo[$key]['type']) {
  246. case 'array':
  247. $newdata[$key] = (array) json_decode($value, true);
  248. break;
  249. case 'radio':
  250. case 'select':
  251. if (!empty($value)) {
  252. if (!empty($fieldinfo[$key]['options'])) {
  253. $optionArr = parse_attr($fieldinfo[$key]['options']);
  254. $newdata[$key . '_text'] = isset($optionArr[$value]) ? $optionArr[$value] : $value;
  255. }
  256. }
  257. $newdata[$key] = $value;
  258. break;
  259. case 'selects':
  260. case 'checkbox':
  261. if (!empty($value)) {
  262. if (!empty($fieldinfo[$key]['options'])) {
  263. $optionArr = parse_attr($fieldinfo[$key]['options']);
  264. $valueArr = explode(',', $value);
  265. foreach ($valueArr as $v) {
  266. if (isset($optionArr[$v])) {
  267. $newdata[$key][$v] = $optionArr[$v];
  268. } elseif ($v) {
  269. $newdata[$key][$v] = $v;
  270. }
  271. }
  272. //其他表关联
  273. } else {
  274. $newdata[$key] = [];
  275. }
  276. }
  277. break;
  278. case 'files':
  279. case 'images':
  280. $newdata[$key] = empty($value) ? [] : explode(',', $value);
  281. break;
  282. case 'markdown':
  283. $parser = new \util\Parser;
  284. $newdata[$key] = $parser->makeHtml($value);
  285. break;
  286. default:
  287. $newdata[$key] = $value;
  288. break;
  289. }
  290. } else {
  291. // 允许前台拿到已合并进来的附表自定义字段(如 title1/title2),不要清空
  292. $newdata[$key] = $value;
  293. }
  294. if (!isset($newdata[$key])) {
  295. $newdata[$key] = '';
  296. }
  297. }
  298. return $newdata;
  299. }
  300. /**
  301. * 文本处理
  302. */
  303. public static function getAfterText(&$data, &$dataExt)
  304. {
  305. //自动提取摘要,如果有设置自动提取,且description为空,且有内容字段才执行
  306. if (isset($data['get_introduce']) && (($data['description'] ?? '') == '') && (isset($dataExt['content']) || isset($data['content']))) {
  307. $content = isset($dataExt['content']) ? $dataExt['content'] : (isset($data['content']) ? $data['content'] : '');
  308. $data['description'] = mb_substr(str_replace(["\r\n", "\t", '&ldquo;', '&rdquo;', '&nbsp;'], '', strip_tags($content)), 0, 200);
  309. }
  310. //自动提取缩略图
  311. if (isset($data['auto_thumb']) && empty($data['thumb']) && (isset($dataExt['content']) || isset($data['content']))) {
  312. $thumb = isset($dataExt['content']) ? \util\GetImgSrc::src($dataExt['content']) : (isset($data['content']) ? \util\GetImgSrc::src($data['content']) : false);
  313. $data['thumb'] = $thumb ? $thumb : '';
  314. }
  315. //关键词加链接
  316. $autolinks = get_addon_config('cms')['autolinks'];
  317. if (!empty($autolinks) && isset($dataExt['content'])) {
  318. $kwarray = [];
  319. if (strpos($autolinks, '|')) {
  320. //解析关键词数组
  321. $kwsets = array_filter(preg_split("/(\r|\n|\r\n)/", $autolinks));
  322. foreach ($kwsets as $kwset) {
  323. $kwarray[] = explode('|', $kwset);
  324. }
  325. }
  326. foreach ($kwarray as $i => $row) {
  327. $txt = isset($row[0]) ? trim($row[0]) : '';
  328. $link = isset($row[1]) ? trim($row[1]) : '';
  329. $set = isset($row[2]) ? trim($row[2]) : '';
  330. if ($txt && $link) {
  331. $rel = '';
  332. $open = '_blank';
  333. //处理标记与打开方式
  334. if ($set) {
  335. if (false !== stripos($set, 'e')) {
  336. $rel = ' rel="external nofollow"';
  337. } elseif (false !== stripos($set, 'n')) {
  338. $rel = ' rel="nofollow"';
  339. }
  340. $open = false !== stripos($set, 'b') ? '_self' : $open;
  341. }
  342. $dataExt['content'] = false !== strpos($dataExt['content'], $txt)
  343. //正则排除参数和链接
  344. ? preg_replace('/(?!<[^>]*)' . $txt . '(?![^<]*(>|<\/[a|sc]))/s'
  345. , '<a href="' . $link . '"' . $rel . 'target="' . $open . '" title="' . $txt . '">' . $txt . '</a>', $dataExt['content']) : $dataExt['content'];
  346. }
  347. }
  348. }
  349. unset($data['get_introduce']);
  350. unset($data['auto_thumb']);
  351. }
  352. /**
  353. * 获取缓存标签和时长
  354. */
  355. public static function getCacheConfig(string $type): array
  356. {
  357. $cachetime = get_addon_config('cms')['site_cache_time'] ?? 0;
  358. if (in_array($cachetime, [true, 'true'], true)) {
  359. $cacheDuration = 0;
  360. } elseif (in_array($cachetime, [false, 'false'], true)) {
  361. $cacheDuration = -1;
  362. } else {
  363. $cacheDuration = (int) $cachetime;
  364. }
  365. $cacheKey = $cacheDuration !== -1 ? "cms-{$type}" : false;
  366. $cacheExpire = $cacheDuration !== -1 ? $cacheDuration : null;
  367. return [$cacheKey, $cacheExpire];
  368. }
  369. }