749 lines
25 KiB
PHP
749 lines
25 KiB
PHP
|
|
<?php
|
||
|
|
/**
|
||
|
|
* Geo SaaS universal publisher endpoint for PBootCMS.
|
||
|
|
*
|
||
|
|
* Install path in a PBootCMS site:
|
||
|
|
* apps/api/controller/GeoPublisherController.php
|
||
|
|
*
|
||
|
|
* The controller only depends on PBootCMS core Controller/Model and the
|
||
|
|
* standard content/sort tables, so users can drop the apps directory into
|
||
|
|
* the PBootCMS root and use it without extra database migrations.
|
||
|
|
*/
|
||
|
|
namespace app\api\controller;
|
||
|
|
|
||
|
|
use core\basic\Controller;
|
||
|
|
use core\basic\Model;
|
||
|
|
|
||
|
|
class GeoPublisherController extends Controller
|
||
|
|
{
|
||
|
|
const CMS_TYPE = 'pbootcms';
|
||
|
|
const PLUGIN_VERSION = '1.2.0';
|
||
|
|
|
||
|
|
private $model;
|
||
|
|
private $payload = array();
|
||
|
|
private $contentFields = null;
|
||
|
|
|
||
|
|
public function __construct()
|
||
|
|
{
|
||
|
|
if (function_exists('cache_config')) {
|
||
|
|
cache_config();
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->model = new Model();
|
||
|
|
$this->payload = $this->readPayload();
|
||
|
|
$this->assertApiAccess();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function ping()
|
||
|
|
{
|
||
|
|
$this->success(array(
|
||
|
|
'cms_type' => self::CMS_TYPE,
|
||
|
|
'plugin_version' => self::PLUGIN_VERSION,
|
||
|
|
'site_url' => $this->siteUrl(),
|
||
|
|
'site_name' => $this->config('cmsname') ?: 'PBootCMS',
|
||
|
|
'api_auth' => (bool) $this->config('api_auth'),
|
||
|
|
'time' => date('Y-m-d H:i:s')
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function categories()
|
||
|
|
{
|
||
|
|
$acode = $this->param('acode', $this->defaultAcode());
|
||
|
|
$mcode = $this->param('mcode', $this->param('model_hash', '2'));
|
||
|
|
|
||
|
|
if (! $this->isSafeCode($acode) || ! $this->isSafeCode($mcode)) {
|
||
|
|
$this->fail(1400, 'invalid acode or mcode');
|
||
|
|
}
|
||
|
|
|
||
|
|
$rows = $this->model->table('ay_content_sort')
|
||
|
|
->where("acode='" . $this->sqlValue($acode) . "'")
|
||
|
|
->where("mcode='" . $this->sqlValue($mcode) . "'")
|
||
|
|
->where('status=1')
|
||
|
|
->field('scode,pcode,mcode,name,filename,sorting')
|
||
|
|
->order('sorting ASC,id ASC')
|
||
|
|
->select();
|
||
|
|
|
||
|
|
$items = array();
|
||
|
|
if ($rows) {
|
||
|
|
foreach ($rows as $row) {
|
||
|
|
$items[] = array(
|
||
|
|
'id' => (string) $row->scode,
|
||
|
|
'scode' => (string) $row->scode,
|
||
|
|
'parent_id' => (string) $row->pcode,
|
||
|
|
'mcode' => (string) $row->mcode,
|
||
|
|
'title' => (string) $row->name,
|
||
|
|
'name' => (string) $row->name,
|
||
|
|
'filename' => (string) $row->filename,
|
||
|
|
'sorting' => (int) $row->sorting
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->success($items);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function publish()
|
||
|
|
{
|
||
|
|
$data = $this->buildContentData(false);
|
||
|
|
$id = $this->model->table('ay_content')->autoTime()->insertGetId($this->filterContentFields($data));
|
||
|
|
|
||
|
|
if (! $id) {
|
||
|
|
$this->fail(1403, 'insert ay_content error', '文章发布失败');
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->clearRuntimeCache();
|
||
|
|
$this->success(array(
|
||
|
|
'id' => (int) $id,
|
||
|
|
'cms_type' => self::CMS_TYPE,
|
||
|
|
'status' => (int) $data['status'],
|
||
|
|
'url' => $this->contentUrl(
|
||
|
|
$id,
|
||
|
|
$this->param('scode', $this->param('category_id', '')),
|
||
|
|
$this->param('filename', '')
|
||
|
|
)
|
||
|
|
), 'published');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function update()
|
||
|
|
{
|
||
|
|
$id = (int) $this->param('id', 0);
|
||
|
|
if ($id <= 0) {
|
||
|
|
$this->fail(1404, 'id is empty', '文章 ID 不能为空');
|
||
|
|
}
|
||
|
|
|
||
|
|
$current = $this->model->table('ay_content')->where('id=' . $id)->find();
|
||
|
|
if (! $current) {
|
||
|
|
$this->fail(1404, 'content not found', '文章不存在');
|
||
|
|
}
|
||
|
|
|
||
|
|
$data = $this->buildContentData(true, $current);
|
||
|
|
$ok = $this->model->table('ay_content')->where('id=' . $id)->autoTime()->update($this->filterContentFields($data));
|
||
|
|
|
||
|
|
if ($ok === false) {
|
||
|
|
$this->fail(1403, 'update ay_content error', '文章更新失败');
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->clearRuntimeCache();
|
||
|
|
$this->success(array(
|
||
|
|
'id' => $id,
|
||
|
|
'cms_type' => self::CMS_TYPE,
|
||
|
|
'status' => (int) $data['status'],
|
||
|
|
'url' => $this->contentUrl(
|
||
|
|
$id,
|
||
|
|
$this->param('scode', $this->param('category_id', $current->scode)),
|
||
|
|
$this->param('filename', $current->filename)
|
||
|
|
)
|
||
|
|
), 'updated');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function status()
|
||
|
|
{
|
||
|
|
$id = (int) $this->param('id', 0);
|
||
|
|
if ($id <= 0) {
|
||
|
|
$this->fail(1404, 'id is empty', '文章 ID 不能为空');
|
||
|
|
}
|
||
|
|
|
||
|
|
$row = $this->model->table('ay_content')->where('id=' . $id)->find();
|
||
|
|
if (! $row) {
|
||
|
|
$this->fail(1404, 'content not found', '文章不存在');
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->success(array(
|
||
|
|
'id' => (int) $row->id,
|
||
|
|
'title' => (string) $row->title,
|
||
|
|
'scode' => (string) $row->scode,
|
||
|
|
'status' => (int) $row->status,
|
||
|
|
'date' => (string) $row->date,
|
||
|
|
'url' => $this->contentUrl($row->id, $row->scode, $row->filename)
|
||
|
|
));
|
||
|
|
}
|
||
|
|
|
||
|
|
// Backward compatible names used by older Aiseo-style plugins.
|
||
|
|
public function categoryLists()
|
||
|
|
{
|
||
|
|
$this->categories();
|
||
|
|
}
|
||
|
|
|
||
|
|
public function articleAdd()
|
||
|
|
{
|
||
|
|
$this->publish();
|
||
|
|
}
|
||
|
|
|
||
|
|
private function buildContentData($isUpdate = false, $current = null)
|
||
|
|
{
|
||
|
|
$title = $this->param('title', $isUpdate && $current ? $current->title : '');
|
||
|
|
if (! $title) {
|
||
|
|
$this->fail(1404, 'title is empty', '标题不能为空');
|
||
|
|
}
|
||
|
|
|
||
|
|
$content = $this->param('content', $isUpdate && $current ? $current->content : '');
|
||
|
|
if (! $content) {
|
||
|
|
$this->fail(1404, 'content is empty', '内容不能为空');
|
||
|
|
}
|
||
|
|
|
||
|
|
$scode = $this->param('scode', $this->param('category_id', $isUpdate && $current ? $current->scode : ''));
|
||
|
|
if (! $scode) {
|
||
|
|
$this->fail(1404, 'scode is empty', '内容栏目编码不能为空');
|
||
|
|
}
|
||
|
|
if (! $this->isSafeCode($scode)) {
|
||
|
|
$this->fail(1400, 'invalid scode', '栏目编码不合法');
|
||
|
|
}
|
||
|
|
|
||
|
|
$sort = $this->getSort($scode);
|
||
|
|
if (! $sort) {
|
||
|
|
$this->fail(1404, 'sort not found', '栏目不存在或不可用');
|
||
|
|
}
|
||
|
|
|
||
|
|
$content = $this->localizeContentImages($content);
|
||
|
|
$ico = $this->param('ico', $isUpdate && $current ? $current->ico : '');
|
||
|
|
if ($ico && ($this->isRemoteUrl($ico) || $this->isDataImageUrl($ico))) {
|
||
|
|
$ico = $this->localizeImage($ico);
|
||
|
|
}
|
||
|
|
|
||
|
|
$status = $this->param('status', $isUpdate && $current ? $current->status : 1);
|
||
|
|
if ($status === '' || $status === null) {
|
||
|
|
$status = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return array(
|
||
|
|
'acode' => $this->sqlValue($this->param('acode', $isUpdate && $current ? $current->acode : $this->defaultAcode())),
|
||
|
|
'scode' => $this->sqlValue($scode),
|
||
|
|
'subscode' => $this->sqlValue($this->param('subscode', $isUpdate && $current ? $current->subscode : '')),
|
||
|
|
'title' => $this->sqlValue($title),
|
||
|
|
'titlecolor' => $this->sqlValue($this->param('titlecolor', $isUpdate && $current ? $current->titlecolor : '#333333')),
|
||
|
|
'subtitle' => $this->sqlValue($this->param('subtitle', $isUpdate && $current ? $current->subtitle : '')),
|
||
|
|
'filename' => $this->sqlValue($this->param('filename', $isUpdate && $current ? $current->filename : '')),
|
||
|
|
'author' => $this->sqlValue($this->param('author', $isUpdate && $current ? $current->author : 'Geo SaaS')),
|
||
|
|
'source' => $this->sqlValue($this->param('source', $isUpdate && $current ? $current->source : 'Geo SaaS')),
|
||
|
|
'outlink' => $this->sqlValue($this->param('outlink', $isUpdate && $current ? $current->outlink : '')),
|
||
|
|
'date' => $this->sqlValue($this->normalizeDate($this->param('date', $isUpdate && $current ? $current->date : ''))),
|
||
|
|
'ico' => $this->sqlValue($ico),
|
||
|
|
'pics' => $this->sqlValue($this->normalizePics($this->param('pics', $isUpdate && $current ? $current->pics : ''))),
|
||
|
|
'content' => $this->sqlValue($content),
|
||
|
|
'tags' => $this->sqlValue($this->param('tags', $isUpdate && $current ? $current->tags : '')),
|
||
|
|
'enclosure' => $this->sqlValue($this->param('enclosure', $isUpdate && $current ? $current->enclosure : '')),
|
||
|
|
'keywords' => $this->sqlValue($this->param('keywords', $isUpdate && $current ? $current->keywords : '')),
|
||
|
|
'description' => $this->sqlValue($this->param('description', $isUpdate && $current ? $current->description : $this->summary($content))),
|
||
|
|
'sorting' => (int) $this->param('sorting', $isUpdate && $current ? $current->sorting : 255),
|
||
|
|
'status' => (int) $status,
|
||
|
|
'istop' => (int) $this->param('istop', $isUpdate && $current ? $current->istop : 0),
|
||
|
|
'isrecommend' => (int) $this->param('isrecommend', $isUpdate && $current ? $current->isrecommend : 0),
|
||
|
|
'isheadline' => (int) $this->param('isheadline', $isUpdate && $current ? $current->isheadline : 0),
|
||
|
|
'visits' => $isUpdate && $current ? (int) $current->visits : 0,
|
||
|
|
'likes' => $isUpdate && $current ? (int) $current->likes : 0,
|
||
|
|
'oppose' => $isUpdate && $current ? (int) $current->oppose : 0,
|
||
|
|
'create_user' => $this->sqlValue($isUpdate && $current ? $current->create_user : 'geo_saas'),
|
||
|
|
'update_user' => $this->sqlValue('geo_saas'),
|
||
|
|
'gid' => (int) $this->param('gid', $isUpdate && $current ? $current->gid : 0),
|
||
|
|
'gtype' => (int) $this->param('gtype', $isUpdate && $current ? $current->gtype : 4),
|
||
|
|
'gnote' => $this->sqlValue($this->param('gnote', $isUpdate && $current ? $current->gnote : '')),
|
||
|
|
'picstitle' => $this->sqlValue($this->param('picstitle', $isUpdate && $current ? $current->picstitle : ''))
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function getSort($scode)
|
||
|
|
{
|
||
|
|
return $this->model->table('ay_content_sort a')
|
||
|
|
->field('a.*,m.urlname,m.type')
|
||
|
|
->join(array(
|
||
|
|
array('ay_model m', 'a.mcode=m.mcode', 'LEFT')
|
||
|
|
))
|
||
|
|
->where("a.scode='" . $this->sqlValue($scode) . "'")
|
||
|
|
->where('a.status=1')
|
||
|
|
->find();
|
||
|
|
}
|
||
|
|
|
||
|
|
private function contentUrl($id, $scode, $filename = '')
|
||
|
|
{
|
||
|
|
$sort = $this->getSort($scode);
|
||
|
|
if (! $sort) {
|
||
|
|
return $this->siteUrl();
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
if (class_exists('\\app\\home\\controller\\ParserController')) {
|
||
|
|
$parserClass = '\\app\\home\\controller\\ParserController';
|
||
|
|
$parser = new $parserClass();
|
||
|
|
$link = $parser->parserLink(
|
||
|
|
$sort->type,
|
||
|
|
$sort->urlname,
|
||
|
|
'content',
|
||
|
|
$scode,
|
||
|
|
$sort->filename,
|
||
|
|
$id,
|
||
|
|
$filename
|
||
|
|
);
|
||
|
|
return $this->absoluteUrl($link);
|
||
|
|
}
|
||
|
|
} catch (\Exception $e) {
|
||
|
|
}
|
||
|
|
|
||
|
|
$urlName = $sort->urlname ?: $sort->filename;
|
||
|
|
if (! $urlName) {
|
||
|
|
$urlName = $scode;
|
||
|
|
}
|
||
|
|
return $this->absoluteUrl('/?' . $urlName . '/' . $id . '.html');
|
||
|
|
}
|
||
|
|
|
||
|
|
private function filterContentFields($data)
|
||
|
|
{
|
||
|
|
$fields = $this->contentFields();
|
||
|
|
if (! $fields) {
|
||
|
|
return $data;
|
||
|
|
}
|
||
|
|
|
||
|
|
$filtered = array();
|
||
|
|
foreach ($data as $key => $value) {
|
||
|
|
if (isset($fields[$key])) {
|
||
|
|
$filtered[$key] = $value;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $filtered;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function contentFields()
|
||
|
|
{
|
||
|
|
if ($this->contentFields !== null) {
|
||
|
|
return $this->contentFields;
|
||
|
|
}
|
||
|
|
|
||
|
|
$this->contentFields = array();
|
||
|
|
try {
|
||
|
|
$fields = $this->model->tableFields('ay_content');
|
||
|
|
if (is_array($fields)) {
|
||
|
|
foreach ($fields as $field) {
|
||
|
|
$field = trim((string) $field);
|
||
|
|
if ($field !== '') {
|
||
|
|
$this->contentFields[$field] = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
} catch (\Exception $e) {
|
||
|
|
$this->contentFields = array();
|
||
|
|
}
|
||
|
|
return $this->contentFields;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function readPayload()
|
||
|
|
{
|
||
|
|
$payload = array();
|
||
|
|
$raw = file_get_contents('php://input');
|
||
|
|
if ($raw) {
|
||
|
|
$json = json_decode($raw, true);
|
||
|
|
if (is_array($json)) {
|
||
|
|
$payload = $json;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($_REQUEST as $key => $value) {
|
||
|
|
$payload[$key] = $value;
|
||
|
|
}
|
||
|
|
|
||
|
|
return $payload;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function param($name, $default = '')
|
||
|
|
{
|
||
|
|
if (array_key_exists($name, $this->payload)) {
|
||
|
|
return $this->payload[$name];
|
||
|
|
}
|
||
|
|
return $default;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function assertApiAccess()
|
||
|
|
{
|
||
|
|
$config = $this->config();
|
||
|
|
if (! isset($config['api_open']) || ! $config['api_open']) {
|
||
|
|
$this->fail(1401, 'api is closed', '系统尚未开启 API 功能');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (! empty($config['api_auth'])) {
|
||
|
|
if (empty($config['api_appid']) || empty($config['api_secret'])) {
|
||
|
|
$this->fail(1401, 'api credential is not configured', '后台 API 认证配置不完整');
|
||
|
|
}
|
||
|
|
|
||
|
|
$appid = $this->param('appid', '');
|
||
|
|
$timestamp = $this->param('timestamp', '');
|
||
|
|
$signature = $this->param('signature', '');
|
||
|
|
|
||
|
|
if (! $appid || ! $timestamp || ! $signature) {
|
||
|
|
$this->fail(1401, 'missing appid timestamp or signature', '缺少 API 认证参数');
|
||
|
|
}
|
||
|
|
|
||
|
|
if ((string) $appid !== (string) $config['api_appid']) {
|
||
|
|
$this->fail(1401, 'invalid appid', 'API 用户不正确');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (! is_numeric($timestamp) || abs(time() - (int) $timestamp) > 300) {
|
||
|
|
$this->fail(1401, 'timestamp expired', 'API 时间戳已过期');
|
||
|
|
}
|
||
|
|
|
||
|
|
$expected = md5(md5($config['api_appid'] . $config['api_secret'] . $timestamp));
|
||
|
|
if (! $this->hashEquals($expected, $signature)) {
|
||
|
|
$this->fail(1401, 'invalid signature', 'API 签名不正确');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private function hashEquals($known, $user)
|
||
|
|
{
|
||
|
|
if (function_exists('hash_equals')) {
|
||
|
|
return hash_equals((string) $known, (string) $user);
|
||
|
|
}
|
||
|
|
return (string) $known === (string) $user;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function localizeContentImages($content)
|
||
|
|
{
|
||
|
|
return preg_replace_callback('/<img\\b[^>]*>/i', function ($imgMatch) {
|
||
|
|
$tag = $imgMatch[0];
|
||
|
|
preg_match_all('/\\b(src|data-src|_src)=[\"\']([^\"\']+)[\"\']/i', $tag, $attrMatches, PREG_SET_ORDER);
|
||
|
|
if (! $attrMatches) {
|
||
|
|
return $tag;
|
||
|
|
}
|
||
|
|
|
||
|
|
$replacements = array();
|
||
|
|
foreach ($attrMatches as $attrMatch) {
|
||
|
|
$url = trim($attrMatch[2]);
|
||
|
|
if (! $this->isRemoteUrl($url) && ! $this->isDataImageUrl($url)) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (! isset($replacements[$url])) {
|
||
|
|
$replacements[$url] = $this->localizeImage($url);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
foreach ($replacements as $remote => $local) {
|
||
|
|
$tag = str_replace($remote, $local, $tag);
|
||
|
|
}
|
||
|
|
return $tag;
|
||
|
|
}, $content);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function normalizePics($pics)
|
||
|
|
{
|
||
|
|
if (! $pics) {
|
||
|
|
return '';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (is_array($pics)) {
|
||
|
|
$items = $pics;
|
||
|
|
} else {
|
||
|
|
$items = explode(',', $pics);
|
||
|
|
}
|
||
|
|
|
||
|
|
$result = array();
|
||
|
|
foreach ($items as $item) {
|
||
|
|
$item = trim((string) $item);
|
||
|
|
if (! $item) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if ($this->isRemoteUrl($item)) {
|
||
|
|
$item = $this->localizeImage($item);
|
||
|
|
} elseif ($this->isDataImageUrl($item)) {
|
||
|
|
$item = $this->localizeImage($item);
|
||
|
|
}
|
||
|
|
if ($item) {
|
||
|
|
$result[] = $item;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return implode(',', $result);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function localizeImage($value)
|
||
|
|
{
|
||
|
|
if ($this->isDataImageUrl($value)) {
|
||
|
|
$decoded = $this->decodeDataImage($value);
|
||
|
|
return $this->saveImageData($decoded['data'], $decoded['ext']);
|
||
|
|
}
|
||
|
|
return $this->downloadImage($value);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function downloadImage($url)
|
||
|
|
{
|
||
|
|
if (! $this->isRemoteUrl($url)) {
|
||
|
|
return $url;
|
||
|
|
}
|
||
|
|
|
||
|
|
$data = $this->httpGet($url);
|
||
|
|
if (! $data) {
|
||
|
|
$this->fail(1405, 'download image failed: ' . $url, '图片下载失败');
|
||
|
|
}
|
||
|
|
|
||
|
|
$path = parse_url($url, PHP_URL_PATH);
|
||
|
|
if (! $path) {
|
||
|
|
$path = '';
|
||
|
|
}
|
||
|
|
$ext = strtolower(pathinfo($path, PATHINFO_EXTENSION));
|
||
|
|
if (! in_array($ext, array('jpg', 'jpeg', 'png', 'gif', 'webp'))) {
|
||
|
|
$ext = $this->imageExtensionFromData($data);
|
||
|
|
}
|
||
|
|
if (! $ext) {
|
||
|
|
$ext = 'jpg';
|
||
|
|
}
|
||
|
|
|
||
|
|
return $this->saveImageData($data, $ext);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function saveImageData($data, $ext = '')
|
||
|
|
{
|
||
|
|
if (! $data || ! $this->isImageData($data, '')) {
|
||
|
|
$this->fail(1405, 'invalid image data', '图片数据无效');
|
||
|
|
}
|
||
|
|
if (! $ext || ! in_array(strtolower($ext), array('jpg', 'jpeg', 'png', 'gif', 'webp'))) {
|
||
|
|
$ext = $this->imageExtensionFromData($data);
|
||
|
|
}
|
||
|
|
if (! $ext) {
|
||
|
|
$ext = 'jpg';
|
||
|
|
}
|
||
|
|
|
||
|
|
$relativeDir = '/static/upload/image/' . date('Ymd');
|
||
|
|
$absoluteDir = $this->rootPath() . $relativeDir;
|
||
|
|
if (! is_dir($absoluteDir) && ! mkdir($absoluteDir, 0777, true)) {
|
||
|
|
$this->fail(1405, 'create upload directory failed', '图片保存目录创建失败');
|
||
|
|
}
|
||
|
|
|
||
|
|
$fileName = 'geo_' . date('His') . '_' . mt_rand(1000, 9999) . '.' . $ext;
|
||
|
|
$absoluteFile = $absoluteDir . '/' . $fileName;
|
||
|
|
$relativeFile = $relativeDir . '/' . $fileName;
|
||
|
|
|
||
|
|
if (file_put_contents($absoluteFile, $data) === false) {
|
||
|
|
$this->fail(1405, 'save image failed', '图片保存失败');
|
||
|
|
}
|
||
|
|
|
||
|
|
return $relativeFile;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function decodeDataImage($value)
|
||
|
|
{
|
||
|
|
if (! preg_match('/^data:image\\/(jpeg|jpg|png|gif|webp);base64,([A-Za-z0-9+\\/\\r\\n=]+)$/i', trim((string) $value), $matches)) {
|
||
|
|
$this->fail(1405, 'invalid data image', '图片数据无效');
|
||
|
|
}
|
||
|
|
$data = base64_decode(preg_replace('/\\s+/', '', $matches[2]), true);
|
||
|
|
if (! $data) {
|
||
|
|
$this->fail(1405, 'decode data image failed', '图片数据无效');
|
||
|
|
}
|
||
|
|
$ext = strtolower($matches[1]);
|
||
|
|
if ($ext === 'jpeg') {
|
||
|
|
$ext = 'jpg';
|
||
|
|
}
|
||
|
|
return array('data' => $data, 'ext' => $ext);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function httpGet($url)
|
||
|
|
{
|
||
|
|
if (function_exists('curl_init')) {
|
||
|
|
$curl = curl_init();
|
||
|
|
curl_setopt($curl, CURLOPT_URL, $url);
|
||
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||
|
|
curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);
|
||
|
|
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 8);
|
||
|
|
curl_setopt($curl, CURLOPT_TIMEOUT, 20);
|
||
|
|
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
|
||
|
|
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
|
||
|
|
curl_setopt($curl, CURLOPT_USERAGENT, 'GeoPublisher/' . self::PLUGIN_VERSION);
|
||
|
|
$data = curl_exec($curl);
|
||
|
|
$code = (int) curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
||
|
|
$contentType = (string) curl_getinfo($curl, CURLINFO_CONTENT_TYPE);
|
||
|
|
curl_close($curl);
|
||
|
|
if ($code >= 200 && $code < 300 && $data && $this->isImageData($data, $contentType)) {
|
||
|
|
return $data;
|
||
|
|
}
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
$context = stream_context_create(array(
|
||
|
|
'http' => array(
|
||
|
|
'method' => 'GET',
|
||
|
|
'timeout' => 20,
|
||
|
|
'header' => "User-Agent: GeoPublisher/" . self::PLUGIN_VERSION . "\r\n"
|
||
|
|
)
|
||
|
|
));
|
||
|
|
$data = @file_get_contents($url, false, $context);
|
||
|
|
return $data && $this->isImageData($data, '') ? $data : false;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function isImageData($data, $contentType = '')
|
||
|
|
{
|
||
|
|
if ($contentType && stripos($contentType, 'image/') !== false) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
if (function_exists('getimagesizefromstring')) {
|
||
|
|
if (@getimagesizefromstring($data) !== false) {
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return $this->isWebPData($data);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function isWebPData($data)
|
||
|
|
{
|
||
|
|
return strlen($data) >= 12 && substr($data, 0, 4) === 'RIFF' && substr($data, 8, 4) === 'WEBP';
|
||
|
|
}
|
||
|
|
|
||
|
|
private function imageExtensionFromData($data)
|
||
|
|
{
|
||
|
|
if ($this->isWebPData($data)) {
|
||
|
|
return 'webp';
|
||
|
|
}
|
||
|
|
if (! function_exists('getimagesizefromstring')) {
|
||
|
|
return 'jpg';
|
||
|
|
}
|
||
|
|
$info = @getimagesizefromstring($data);
|
||
|
|
if (! $info || empty($info['mime'])) {
|
||
|
|
return 'jpg';
|
||
|
|
}
|
||
|
|
switch (strtolower($info['mime'])) {
|
||
|
|
case 'image/jpeg':
|
||
|
|
return 'jpg';
|
||
|
|
case 'image/png':
|
||
|
|
return 'png';
|
||
|
|
case 'image/gif':
|
||
|
|
return 'gif';
|
||
|
|
case 'image/webp':
|
||
|
|
return 'webp';
|
||
|
|
default:
|
||
|
|
return 'jpg';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private function rootPath()
|
||
|
|
{
|
||
|
|
if (defined('ROOT_PATH') && ROOT_PATH) {
|
||
|
|
return rtrim(ROOT_PATH, '/\\');
|
||
|
|
}
|
||
|
|
return rtrim(dirname(dirname(dirname(dirname(__FILE__)))), '/\\');
|
||
|
|
}
|
||
|
|
|
||
|
|
private function normalizeDate($value)
|
||
|
|
{
|
||
|
|
if (! $value) {
|
||
|
|
return date('Y-m-d H:i:s');
|
||
|
|
}
|
||
|
|
if (is_numeric($value)) {
|
||
|
|
return date('Y-m-d H:i:s', (int) $value);
|
||
|
|
}
|
||
|
|
$time = strtotime($value);
|
||
|
|
return $time ? date('Y-m-d H:i:s', $time) : date('Y-m-d H:i:s');
|
||
|
|
}
|
||
|
|
|
||
|
|
private function summary($content)
|
||
|
|
{
|
||
|
|
$text = strip_tags($content);
|
||
|
|
if (function_exists('clear_html_blank')) {
|
||
|
|
$text = clear_html_blank($text);
|
||
|
|
}
|
||
|
|
if (function_exists('substr_both')) {
|
||
|
|
return substr_both($text, 0, 150);
|
||
|
|
}
|
||
|
|
if (function_exists('mb_substr')) {
|
||
|
|
return mb_substr($text, 0, 150, 'UTF-8');
|
||
|
|
}
|
||
|
|
return substr($text, 0, 150);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function defaultAcode()
|
||
|
|
{
|
||
|
|
if (function_exists('get_default_lg')) {
|
||
|
|
return get_default_lg();
|
||
|
|
}
|
||
|
|
return 'cn';
|
||
|
|
}
|
||
|
|
|
||
|
|
private function isSafeCode($value)
|
||
|
|
{
|
||
|
|
return (bool) preg_match('/^[A-Za-z0-9_\\-]+$/', (string) $value);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function isRemoteUrl($url)
|
||
|
|
{
|
||
|
|
return preg_match('/^https?:\\/\\//i', (string) $url);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function isDataImageUrl($url)
|
||
|
|
{
|
||
|
|
return preg_match('/^data:image\\/(jpeg|jpg|png|gif|webp);base64,/i', trim((string) $url));
|
||
|
|
}
|
||
|
|
|
||
|
|
private function sqlValue($value)
|
||
|
|
{
|
||
|
|
if (is_array($value) || is_object($value)) {
|
||
|
|
$value = json_encode($value);
|
||
|
|
}
|
||
|
|
|
||
|
|
$value = (string) $value;
|
||
|
|
if (function_exists('get_magic_quotes_gpc') && get_magic_quotes_gpc()) {
|
||
|
|
$value = stripslashes($value);
|
||
|
|
}
|
||
|
|
return addslashes($value);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function absoluteUrl($link)
|
||
|
|
{
|
||
|
|
if (preg_match('/^https?:\\/\\//i', $link)) {
|
||
|
|
return $link;
|
||
|
|
}
|
||
|
|
|
||
|
|
$base = $this->siteUrl();
|
||
|
|
if (! $link) {
|
||
|
|
return $base;
|
||
|
|
}
|
||
|
|
|
||
|
|
return rtrim($base, '/') . '/' . ltrim($link, '/');
|
||
|
|
}
|
||
|
|
|
||
|
|
private function siteUrl()
|
||
|
|
{
|
||
|
|
if (function_exists('get_http_url')) {
|
||
|
|
return get_http_url();
|
||
|
|
}
|
||
|
|
|
||
|
|
$https = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on';
|
||
|
|
$host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : '';
|
||
|
|
return ($https ? 'https://' : 'http://') . $host;
|
||
|
|
}
|
||
|
|
|
||
|
|
private function clearRuntimeCache()
|
||
|
|
{
|
||
|
|
if (defined('RUN_PATH') && function_exists('path_delete')) {
|
||
|
|
@path_delete(RUN_PATH . '/cache');
|
||
|
|
@path_delete(RUN_PATH . '/complile');
|
||
|
|
@path_delete(RUN_PATH . '/config');
|
||
|
|
@path_delete(RUN_PATH . '/upgrade');
|
||
|
|
}
|
||
|
|
|
||
|
|
if (function_exists('cache_config')) {
|
||
|
|
@cache_config(true);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (function_exists('opcache_reset') && extension_loaded('Zend OPcache')) {
|
||
|
|
@opcache_reset();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private function success($data = array(), $msg = '')
|
||
|
|
{
|
||
|
|
$this->respond(1, $data, $msg);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function fail($code, $data = '', $msg = '')
|
||
|
|
{
|
||
|
|
$this->respond($code, $data, $msg);
|
||
|
|
}
|
||
|
|
|
||
|
|
private function respond($code, $data = '', $msg = '')
|
||
|
|
{
|
||
|
|
if (! headers_sent()) {
|
||
|
|
header('Content-Type: application/json; charset=utf-8');
|
||
|
|
}
|
||
|
|
|
||
|
|
echo json_encode(array(
|
||
|
|
'code' => $code,
|
||
|
|
'data' => $data,
|
||
|
|
'msg' => $msg
|
||
|
|
));
|
||
|
|
exit();
|
||
|
|
}
|
||
|
|
}
|