diff --git a/.gitignore b/.gitignore index 8264c2e..6c761b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ -.idea -.DS_Store -composer.phar -composer.lock -/templates/ -!/templates/default.html -!/templates/template1.html -!/templates/template2.html -!/templates/template3.html -!/templates/template4.html -!/templates/template5.html +.idea +.DS_Store +composer.phar +composer.lock +/templates/ +!/templates/default.html +!/templates/template1.html +!/templates/template2.html +!/templates/template3.html +!/templates/template4.html +!/templates/template5.html diff --git a/Action.php b/Action.php index bce107a..7f104d4 100644 --- a/Action.php +++ b/Action.php @@ -1,227 +1,227 @@ -db = Typecho_Db::get(); - $this->options = self::getOptions(); - $this->options->setDefault('logoUrl=' . Helper::options()->pluginUrl . '/ShortLinks/logo.png&siteCreatedYear=' . Helper::options()->plugin('ShortLinks')->siteCreatedYear . '¤tYear=' . date('Y')); - } - - /** - * 添加新的链接转换 - * - */ - public function add() - { - $key = $this->request->key; - $key = $key ? $key : Typecho_Common::randString(8); - $target = $this->request->target; - if ($target === "" || $target === "http://") { - $this->widget('Widget_Notice')->set(_t('请输入目标链接。'), null, 'error'); - } //判断key是否被占用 - elseif ($this->getTarget($key)) { - $this->widget('Widget_Notice')->set(_t('该key已被使用,请更换key值。'), null, 'error'); - } else { - $links = array( - 'key' => $key, - 'target' => $this->request->target, - 'count' => 0, - ); - $insertId = $this->db->query($this->db->insert('table.shortlinks')->rows($links)); - } - } - - /** - * 修改链接 - * - */ - - public function edit() - { - $target = $this->request->url; - $id = $this->request->id; - if (trim($target) == "" || $target == "http://") { - Typecho_Response::throwJson('error'); - } else { - if ($id) { - $this->db->query($this->db->update('table.shortlinks')->rows(array('target' => $target)) - ->where('id = ?', $id)); - Typecho_Response::throwJson('success'); - } - } - } - - /** - * 删除链接转换 - * - * @param int $id - */ - public function del($id) - { - $this->db->query($this->db->delete('table.shortlinks') - ->where('id = ?', $id)); - - } - - /** - * 链接重定向 - * - */ - public function shortlink() - { - $requestString = $this->request->key; - $siteUrl = preg_replace("/https?:\/\//", "", Typecho_Widget::widget('Widget_Options')->siteUrl); - $pOption = Typecho_Widget::widget('Widget_Options')->Plugin('ShortLinks'); // 插件选项 - $referer = $this->request->getReferer(); - $template = $pOption->goTemplate == 'NULL' ? null : $pOption->goTemplate; - // 允许空 referer - if (empty($referer) && $pOption->nullReferer === "1") { - $referer = $siteUrl; - } - - $refererList = ShortLinks_Plugin::textareaToArr($pOption->refererList); // 允许的referer列表 - $target = $this->getTarget($requestString); - // 设置nofollow属性 - $this->response->setHeader('X-Robots-Tag', 'noindex, nofollow'); - if ($target) { - // 自定义短链 - // 增加统计 - $count = $this->db->fetchObject($this->db->select('count') - ->from('table.shortlinks') - ->where('key = ?', $key))->count; - $count = $count + 1; - $this->db->query($this->db->update('table.shortlinks') - ->rows(array('count' => $count)) - ->where('key = ?', $key)); - } else if ($requestString === ShortLinks_Plugin::urlSafeB64Encode(ShortLinks_Plugin::urlSafeB64Decode($requestString))) { - // 自动转换链接处理 - $target = ShortLinks_Plugin::urlSafeB64Decode($requestString); - $allow_redirect = false; // 默认不允许跳转 - // 检查 referer - $allow_redirect = ShortLinks_Plugin::checkDomain($referer, $refererList); - if (strpos($referer, $siteUrl) !== false) { - $allow_redirect = true; - } - if (!$allow_redirect) { - // referer 非法跳转到首页 - $this->response->redirect($siteUrl, 301); - exit(); - } - } else { - throw new Typecho_Widget_Exception(_t('您访问的网页不存在'), 404); - } - if ($template === 'NULL' || $template === null) { - // 无跳转页面 - $this->response->redirect(htmlspecialchars_decode($target), 301); - } else { - $filename = __DIR__ . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . $template . '.html'; - if (PATH_SEPARATOR !== ':') { - $filename = mb_convert_encoding($filename, 'GBK', "auto"); - } - $contents = file_get_contents($filename); - $html = str_replace(array('{{url}}', '{{delay}}'), array($target, $pOption->goDelay), $contents); - echo preg_replace_callback("/\{\{([_a-z0-9]+)\}\}/i", - array(__CLASS__, '__relpaceCallback'), $html); - exit(); - } - } - /** - * 获取目标链接 - * - * @param string $key - * @return void - */ - public function getTarget($key) - { - $target = $this->db->fetchRow($this->db->select('target') - ->from('table.shortlinks') - ->where(' key = ?', $key)); - if (isset($target['target'])) { - return $target['target']; - } else { - return false; - } - } - - /** - * 重设自定义链接 - */ - public function resetLink() - { - $link = $this->request->link; - Helper::removeRoute('go'); - Helper::addRoute('go', $link, 'ShortLinks_Action', 'shortlink'); - Typecho_Response::throwJson('success'); - } - - public function action() - { - $this->widget('Widget_User')->pass('administrator'); - $this->on($this->request->is('add'))->add(); - $this->on($this->request->is('edit'))->edit(); - $this->on($this->request->is('del'))->del($this->request->del); - $this->on($this->request->is('resetLink'))->resetLink(); - $this->response->goBack(); - } - - public function getOptions() - { - $values = $this->db->fetchAll($this->db->select('name', 'value')->from('table.options')->where('user = 0')); - $options = array(); - foreach ($values as $value) { - if (strpos($value['name'], "plugin:") === 0) { - continue; - } - - $options[$value['name']] = $value['value']; - } - /** 主题变量重载 */ - if (!empty($options['theme:' . $options['theme']])) { - $themeOptions = null; - - /** 解析变量 */ - if ($themeOptions = unserialize($options['theme:' . $options['theme']])) { - /** 覆盖变量 */ - $options = array_merge($options, $themeOptions); - } - } - $options['rootUrl'] = defined('__TYPECHO_ROOT_URL__') ? __TYPECHO_ROOT_URL__ : $this->request->getRequestRoot(); - if (defined('__TYPECHO_ADMIN__')) { - /** 识别在admin目录中的情况 */ - $adminDir = '/' . trim(defined('__TYPECHO_ADMIN_DIR__') ? __TYPECHO_ADMIN_DIR__ : '/admin/', '/'); - $options['rootUrl'] = substr($options['rootUrl'], 0, -strlen($adminDir)); - } - if (defined('__TYPECHO_SITE_URL__')) { - $options['siteUrl'] = __TYPECHO_SITE_URL__; - } else if (defined('__TYPECHO_DYNAMIC_SITE_URL__') && __TYPECHO_DYNAMIC_SITE_URL__) { - $options['siteUrl'] = $options['rootUrl']; - } - $options['originalSiteUrl'] = $options['siteUrl']; - $options['siteUrl'] = Typecho_Common::url(null, $options['siteUrl']); - - return Typecho_Config::factory($options); - } - /** - * 替换回调 - * - * @param Array $matches - * @return String - * @date 2020-05-01 - */ - private function __relpaceCallback($matches) - { - return $this->options->{$matches[1]}; - } -} +db = Typecho_Db::get(); + $this->options = self::getOptions(); + $this->options->setDefault('logoUrl=' . Helper::options()->pluginUrl . '/ShortLinks/logo.png&siteCreatedYear=' . Helper::options()->plugin('ShortLinks')->siteCreatedYear . '¤tYear=' . date('Y')); + } + + /** + * 添加新的链接转换 + * + */ + public function add() + { + $key = $this->request->key; + $key = $key ? $key : Typecho_Common::randString(8); + $target = $this->request->target; + if ($target === "" || $target === "http://") { + $this->widget('Widget_Notice')->set(_t('请输入目标链接。'), null, 'error'); + } //判断key是否被占用 + elseif ($this->getTarget($key)) { + $this->widget('Widget_Notice')->set(_t('该key已被使用,请更换key值。'), null, 'error'); + } else { + $links = array( + 'key' => $key, + 'target' => $this->request->target, + 'count' => 0, + ); + $insertId = $this->db->query($this->db->insert('table.shortlinks')->rows($links)); + } + } + + /** + * 修改链接 + * + */ + + public function edit() + { + $target = $this->request->url; + $id = $this->request->id; + if (trim($target) == "" || $target == "http://") { + Typecho_Response::throwJson('error'); + } else { + if ($id) { + $this->db->query($this->db->update('table.shortlinks')->rows(array('target' => $target)) + ->where('id = ?', $id)); + Typecho_Response::throwJson('success'); + } + } + } + + /** + * 删除链接转换 + * + * @param int $id + */ + public function del($id) + { + $this->db->query($this->db->delete('table.shortlinks') + ->where('id = ?', $id)); + + } + + /** + * 链接重定向 + * + */ + public function shortlink() + { + $requestString = $this->request->key; + $siteUrl = preg_replace("/https?:\/\//", "", Typecho_Widget::widget('Widget_Options')->siteUrl); + $pOption = Typecho_Widget::widget('Widget_Options')->Plugin('ShortLinks'); // 插件选项 + $referer = $this->request->getReferer(); + $template = $pOption->goTemplate == 'NULL' ? null : $pOption->goTemplate; + // 允许空 referer + if (empty($referer) && $pOption->nullReferer === "1") { + $referer = $siteUrl; + } + + $refererList = ShortLinks_Plugin::textareaToArr($pOption->refererList); // 允许的referer列表 + $target = $this->getTarget($requestString); + // 设置nofollow属性 + $this->response->setHeader('X-Robots-Tag', 'noindex, nofollow'); + if ($target) { + // 自定义短链 + // 增加统计 + $count = $this->db->fetchObject($this->db->select('count') + ->from('table.shortlinks') + ->where('key = ?', $requestString))->count; + $count = $count + 1; + $this->db->query($this->db->update('table.shortlinks') + ->rows(array('count' => $count)) + ->where('key = ?', $requestString)); + } else if ($requestString === ShortLinks_Plugin::urlSafeB64Encode(ShortLinks_Plugin::urlSafeB64Decode($requestString))) { + // 自动转换链接处理 + $target = ShortLinks_Plugin::urlSafeB64Decode($requestString); + $allow_redirect = false; // 默认不允许跳转 + // 检查 referer + $allow_redirect = ShortLinks_Plugin::checkDomain($referer, $refererList); + if (strpos($referer, $siteUrl) !== false) { + $allow_redirect = true; + } + if (!$allow_redirect) { + // referer 非法跳转到首页 + $this->response->redirect($siteUrl, 301); + exit(); + } + } else { + throw new Typecho_Widget_Exception(_t('您访问的网页不存在'), 404); + } + if ($template === 'NULL' || $template === null) { + // 无跳转页面 + $this->response->redirect(htmlspecialchars_decode($target), 301); + } else { + $filename = __DIR__ . DIRECTORY_SEPARATOR . 'templates' . DIRECTORY_SEPARATOR . $template . '.html'; + if (PATH_SEPARATOR !== ':') { + $filename = mb_convert_encoding($filename, 'GBK', "auto"); + } + $contents = file_get_contents($filename); + $html = str_replace(array('{{url}}', '{{delay}}'), array($target, $pOption->goDelay), $contents); + echo preg_replace_callback("/\{\{([_a-z0-9]+)\}\}/i", + array(__CLASS__, '__relpaceCallback'), $html); + exit(); + } + } + /** + * 获取目标链接 + * + * @param string $key + * @return void + */ + public function getTarget($key) + { + $target = $this->db->fetchRow($this->db->select('target') + ->from('table.shortlinks') + ->where(' key = ?', $key)); + if (isset($target['target'])) { + return $target['target']; + } else { + return false; + } + } + + /** + * 重设自定义链接 + */ + public function resetLink() + { + $link = $this->request->link; + Helper::removeRoute('go'); + Helper::addRoute('go', $link, 'ShortLinks_Action', 'shortlink'); + Typecho_Response::throwJson('success'); + } + + public function action() + { + $this->widget('Widget_User')->pass('administrator'); + $this->on($this->request->is('add'))->add(); + $this->on($this->request->is('edit'))->edit(); + $this->on($this->request->is('del'))->del($this->request->del); + $this->on($this->request->is('resetLink'))->resetLink(); + $this->response->goBack(); + } + + public function getOptions() + { + $values = $this->db->fetchAll($this->db->select('name', 'value')->from('table.options')->where('user = 0')); + $options = array(); + foreach ($values as $value) { + if (strpos($value['name'], "plugin:") === 0) { + continue; + } + + $options[$value['name']] = $value['value']; + } + /** 主题变量重载 */ + if (!empty($options['theme:' . $options['theme']])) { + $themeOptions = null; + + /** 解析变量 */ + if ($themeOptions = unserialize($options['theme:' . $options['theme']])) { + /** 覆盖变量 */ + $options = array_merge($options, $themeOptions); + } + } + $options['rootUrl'] = defined('__TYPECHO_ROOT_URL__') ? __TYPECHO_ROOT_URL__ : $this->request->getRequestRoot(); + if (defined('__TYPECHO_ADMIN__')) { + /** 识别在admin目录中的情况 */ + $adminDir = '/' . trim(defined('__TYPECHO_ADMIN_DIR__') ? __TYPECHO_ADMIN_DIR__ : '/admin/', '/'); + $options['rootUrl'] = substr($options['rootUrl'], 0, -strlen($adminDir)); + } + if (defined('__TYPECHO_SITE_URL__')) { + $options['siteUrl'] = __TYPECHO_SITE_URL__; + } else if (defined('__TYPECHO_DYNAMIC_SITE_URL__') && __TYPECHO_DYNAMIC_SITE_URL__) { + $options['siteUrl'] = $options['rootUrl']; + } + $options['originalSiteUrl'] = $options['siteUrl']; + $options['siteUrl'] = Typecho_Common::url(null, $options['siteUrl']); + + return Typecho_Config::factory($options); + } + /** + * 替换回调 + * + * @param Array $matches + * @return String + * @date 2020-05-01 + */ + private function __relpaceCallback($matches) + { + return $this->options->{$matches[1]}; + } +} diff --git a/Plugin.php b/Plugin.php index a7bfdd7..35161e2 100644 --- a/Plugin.php +++ b/Plugin.php @@ -1,303 +1,303 @@ -getPrefix() . 'shortlinks'; - $adapter = $db->getAdapterName(); - if ("Pdo_SQLite" === $adapter || "SQLite" === $adapter) { - $db->query(" CREATE TABLE IF NOT EXISTS " . $shortlinks . " ( - id INTEGER PRIMARY KEY, - key TEXT, - target TEXT, - count NUMERIC)"); - } - if ("Pdo_Mysql" === $adapter || "Mysql" === $adapter) { - $dbConfig = Typecho_Db::get()->getConfig()[0]; - $charset = $dbConfig->charset; - $db->query("CREATE TABLE IF NOT EXISTS " . $shortlinks . " ( - `id` int(8) NOT NULL AUTO_INCREMENT, - `key` varchar(64) NOT NULL, - `target` varchar(10000) NOT NULL, - `count` int(8) DEFAULT '0', - PRIMARY KEY (`id`) - ) DEFAULT CHARSET=${charset} AUTO_INCREMENT=1"); - } - Helper::addAction('shortlinks', 'ShortLinks_Action'); - Helper::addRoute('go', '/go/[key]/', 'ShortLinks_Action', 'shortlink'); - Helper::addPanel(2, 'ShortLinks/panel.php', '短链管理', '短链接管理', 'administrator'); - Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('ShortLinks_Plugin', 'replace'); - Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('ShortLinks_Plugin', 'replace'); - Typecho_Plugin::factory('Widget_Abstract_Contents')->filter = array('ShortLinks_Plugin', 'replace'); - Typecho_Plugin::factory('Widget_Abstract_Comments')->filter = array('ShortLinks_Plugin', 'replace'); - Typecho_Plugin::factory('Widget_Archive')->singleHandle = array('ShortLinks_Plugin', 'replace'); - return ('数据表 ' . $shortlinks . ' 创建成功,插件已经成功激活!'); - } - - /** - * 禁用插件方法,如果禁用失败,直接抛出异常 - * - * @static - * @access public - * @return String - * @throws Typecho_Plugin_Exception - */ - public static function deactivate() - { - $config = Typecho_Widget::widget('Widget_Options')->plugin('ShortLinks'); - Helper::removeRoute('go'); - Helper::removeAction('shortlinks'); - Helper::removePanel(2, 'ShortLinks/panel.php'); - if ($config->isDrop == 0) { - $db = Typecho_Db::get(); - $db->query("DROP TABLE `{$db->getPrefix()}shortlinks`", Typecho_Db::WRITE); - return ('短链接插件已被禁用,其表(_shortlinks)已被删除!'); - } else { - return ('短链接插件已被禁用,但是其表(_shortlinks)并没有被删除!'); - } - } - - /** - * 获取插件配置面板 - * - * @access public - * @param Typecho_Widget_Helper_Form $form 配置面板 - * @return void - */ - public static function config(Typecho_Widget_Helper_Form $form) - { - $radio = new Typecho_Widget_Helper_Form_Element_Radio('convert', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('外链转内链'), _t('开启后会帮你把外链转换成内链')); - $form->addInput($radio); - $radio = new Typecho_Widget_Helper_Form_Element_Radio('convertCommentLink', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('转换评论者链接'), _t('开启后会帮你把评论者链接转换成内链')); - $form->addInput($radio); - $template_files = scandir(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'templates'); - $goTemplates = array('NULL' => '禁用'); - foreach ($template_files as $item) { - if (PATH_SEPARATOR !== ':') { - $item = mb_convert_encoding($item, "UTF-8", "GBK"); - } - - $name = mb_split("\.", $item)[0]; - if (empty($name)) { - continue; - } - - $goTemplates[$name] = $name; - } - $edit = new Typecho_Widget_Helper_Form_Element_Select('goTemplate', $goTemplates, 'NULL', _t('跳转页面模板')); - $form->addInput($edit); - $edit = new Typecho_Widget_Helper_Form_Element_Text('goDelay', null, _t('3'), _t('跳转延时'), _t('跳转页面停留时间(秒)')); - $form->addInput($edit); - $edit = new Typecho_Widget_Helper_Form_Element_Text('siteCreatedYear', null, _t('2020'), _t('建站年份'), _t('建站年份,用于模板内容替换模板中使用 {siteCreatedYear} 来代表建站年份')); - $form->addInput($edit); - - $radio = new Typecho_Widget_Helper_Form_Element_Radio('target', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('新窗口打开文章中的链接'), _t('开启后给文章中的链接新增 target 属性')); - $form->addInput($radio); - - $radio = new Typecho_Widget_Helper_Form_Element_Radio('authorPermalinkTarget', array('1' => _t('开启'), '0' => _t('关闭')), '0', _t('新窗口打开评论者链接'), _t('开启后给评论者链接新增 target 属性。(URL 中 target 属性,开启可能会引起主题异常)')); - $form->addInput($radio); - - $textarea = new Typecho_Widget_Helper_Form_Element_Textarea('convertCustomField', null, null, _t('需要处理的自定义字段'), _t('在这里设置需要处理的自定义字段,一行一个(实验性功能)')); - $form->addInput($textarea); - $radio = new Typecho_Widget_Helper_Form_Element_Radio('nullReferer', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('允许空 referer'), _t('开启后会允许空 referer')); - $form->addInput($radio); - $refererList = new Typecho_Widget_Helper_Form_Element_Textarea('refererList', null, null, _t('referer 白名单'), _t('在这里设置 referer 白名单,一行一个')); - $form->addInput($refererList); - $nonConvertList = new Typecho_Widget_Helper_Form_Element_Textarea('nonConvertList', null, _t("b0.upaiyun.com" . PHP_EOL . "glb.clouddn.com" . PHP_EOL . "qbox.me" . PHP_EOL . "qnssl.com"), _t('外链转换白名单'), _t('在这里设置外链转换白名单(评论者链接不生效)')); - $form->addInput($nonConvertList); - $isDrop = new Typecho_Widget_Helper_Form_Element_Radio('isDrop', array('0' => '删除', '1' => '不删除'), '1', '彻底卸载', '请选择是否在禁用插件时,删除数据表'); - $form->addInput($isDrop); - } - - /** - * 个人用户的配置面板 - * - * @access public - * @param Typecho_Widget_Helper_Form $form - * @return void - */ - public static function personalConfig(Typecho_Widget_Helper_Form $form) - { - } - - /** - * 外链转内链 - * - * @access public - * @param $content - * @param $class - * @return $content - */ - public static function replace($text, $widget, $lastResult) - { - $text = empty($lastResult) ? $text : $lastResult; - $pluginOption = Typecho_Widget::widget('Widget_Options')->Plugin('ShortLinks'); // 插件选项 - $siteUrl = Helper::options()->siteUrl; - $target = ($pluginOption->target) ? ' target="_blank" ' : ''; // 新窗口打开 - if ($pluginOption->convert == 1) { - if (!is_string($text) && $text instanceof Widget_Archive) { - // 自定义字段处理 - $fieldsList = self::textareaToArr($pluginOption->convertCustomField); - if ($fieldsList) { - foreach ($fieldsList as $field) { - if (isset($text->fields[$field])) { - @preg_match_all('//', $text->fields[$field], $matches); - if ($matches) { - foreach ($matches[2] as $link) { - $text->fields[$field] = str_replace("href=\"$link\"", "href=\"" . self::convertLink($link) . "\"", $text->fields[$field]); - } - } - } - } - } - } - if (($widget instanceof Widget_Archive) || ($widget instanceof Widget_Abstract_Comments)) { - $fields = unserialize($widget->fields); - if (is_array($fields) && array_key_exists("noshort", $fields)) { - return $text; - } - - // 文章内容和评论内容处理 - @preg_match_all('//', $text, $matches); - if ($matches) { - foreach ($matches[2] as $link) { - $text = str_replace("href=\"$link\"", "href=\"" . self::convertLink($link) . "\"" . $target, $text); - } - } - } - if ($pluginOption->convertCommentLink == 1 && $widget instanceof Widget_Abstract_Comments) { - // 评论者链接处理 - $url = $text['url']; - if (strpos($url, '://') !== false && strpos($url, rtrim($siteUrl, '/')) === false) { - $text['url'] = self::convertLink($url, false); - if ($pluginOption->authorPermalinkTarget) { - $text['url'] = $text['url'] . '" target="_blank'; - } - } - } - } - return $text; - } - - /** - * 转换链接形式 - * - * @access public - * @param $link - * @return $string - */ - public static function convertLink($link, $check = true) - { - $rewrite = (Helper::options()->rewrite) ? '' : 'index.php/'; // 伪静态处理 - $pluginOption = Typecho_Widget::widget('Widget_Options')->Plugin('ShortLinks'); // 插件选项 - $linkBase = ltrim(rtrim(Typecho_Router::get('go')['url'], '/'), '/'); // 防止链接形式修改后不能用 - $siteUrl = Helper::options()->siteUrl; - $target = ($pluginOption->target) ? ' target="_blank" ' : ''; // 新窗口打开 - $nonConvertList = self::textareaToArr($pluginOption->nonConvertList); // 不转换列表 - if ($check) { - if (strpos($link, '://') !== false && strpos($link, rtrim($siteUrl, '/')) !== false) { - return $link; - } - //本站链接不处理 - if (self::checkDomain($link, $nonConvertList)) { - return $link; - } - // 不转换列表中的不处理 - if (preg_match('/\.(jpg|jepg|png|ico|bmp|gif|tiff)/i', $link)) { - return $link; - } - // 图片不处理 - } - return $siteUrl . $rewrite . str_replace('[key]', self::urlSafeB64Encode(htmlspecialchars_decode($link)), $linkBase); - } - - /** - * 检查域名是否在数组中存在 - * - * @access public - * @param $url $arr - * @param $class - * @return boolean - */ - public static function checkDomain($url, $arr) - { - if ($arr === null) { - return false; - } - - if (count($arr) === 0) { - return false; - } - - foreach ($arr as $a) { - if (strpos($url, $a) !== false) { - return true; - } - } - return false; - } - - /** - * 一行一个文本框转数组 - * - * @access public - * @param $textarea - * @param $class - * @return $arr - */ - public static function textareaToArr($textarea) - { - $str = str_replace(array("\r\n", "\r", "\n"), "|", $textarea); - if ($str == "") { - return null; - } - - return explode("|", $str); - } - /** - * Base64 解码 - * - * @param string $str - * @return string - * @date 2020-05-01 - */ - public static function urlSafeB64Decode($str) - { - $data = str_replace(array('-', '_'), array('+', '/'), $str); - $mod = strlen($data) % 4; - if ($mod) { - $data .= substr('====', $mod); - } - return base64_decode($data); - } - /** - * Base64 编码 - * - * @param string $str - * @return string - * @date 2020-05-01 - */ - public static function urlSafeB64Encode($str) - { - $data = base64_encode($str); - $data = str_replace(array('+', '/', '='), array('-', '_', ''), $data); - return $data; - } -} +getPrefix() . 'shortlinks'; + $adapter = $db->getAdapterName(); + if ("Pdo_SQLite" === $adapter || "SQLite" === $adapter) { + $db->query(" CREATE TABLE IF NOT EXISTS " . $shortlinks . " ( + id INTEGER PRIMARY KEY, + key TEXT, + target TEXT, + count NUMERIC)"); + } + if ("Pdo_Mysql" === $adapter || "Mysql" === $adapter) { + $dbConfig = Typecho_Db::get()->getConfig()[0]; + $charset = $dbConfig->charset; + $db->query("CREATE TABLE IF NOT EXISTS " . $shortlinks . " ( + `id` int(8) NOT NULL AUTO_INCREMENT, + `key` varchar(64) NOT NULL, + `target` varchar(10000) NOT NULL, + `count` int(8) DEFAULT '0', + PRIMARY KEY (`id`) + ) DEFAULT CHARSET=${charset} AUTO_INCREMENT=1"); + } + Helper::addAction('shortlinks', 'ShortLinks_Action'); + Helper::addRoute('go', '/go/[key]/', 'ShortLinks_Action', 'shortlink'); + Helper::addPanel(2, 'ShortLinks/panel.php', '短链管理', '短链接管理', 'administrator'); + Typecho_Plugin::factory('Widget_Abstract_Contents')->contentEx = array('ShortLinks_Plugin', 'replace'); + Typecho_Plugin::factory('Widget_Abstract_Contents')->excerptEx = array('ShortLinks_Plugin', 'replace'); + Typecho_Plugin::factory('Widget_Abstract_Contents')->filter = array('ShortLinks_Plugin', 'replace'); + Typecho_Plugin::factory('Widget_Abstract_Comments')->filter = array('ShortLinks_Plugin', 'replace'); + Typecho_Plugin::factory('Widget_Archive')->singleHandle = array('ShortLinks_Plugin', 'replace'); + return ('数据表 ' . $shortlinks . ' 创建成功,插件已经成功激活!'); + } + + /** + * 禁用插件方法,如果禁用失败,直接抛出异常 + * + * @static + * @access public + * @return String + * @throws Typecho_Plugin_Exception + */ + public static function deactivate() + { + $config = Typecho_Widget::widget('Widget_Options')->plugin('ShortLinks'); + Helper::removeRoute('go'); + Helper::removeAction('shortlinks'); + Helper::removePanel(2, 'ShortLinks/panel.php'); + if ($config->isDrop == 0) { + $db = Typecho_Db::get(); + $db->query("DROP TABLE `{$db->getPrefix()}shortlinks`", Typecho_Db::WRITE); + return ('短链接插件已被禁用,其表(_shortlinks)已被删除!'); + } else { + return ('短链接插件已被禁用,但是其表(_shortlinks)并没有被删除!'); + } + } + + /** + * 获取插件配置面板 + * + * @access public + * @param Typecho_Widget_Helper_Form $form 配置面板 + * @return void + */ + public static function config(Typecho_Widget_Helper_Form $form) + { + $radio = new Typecho_Widget_Helper_Form_Element_Radio('convert', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('外链转内链'), _t('开启后会帮你把外链转换成内链')); + $form->addInput($radio); + $radio = new Typecho_Widget_Helper_Form_Element_Radio('convertCommentLink', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('转换评论者链接'), _t('开启后会帮你把评论者链接转换成内链')); + $form->addInput($radio); + $template_files = scandir(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'templates'); + $goTemplates = array('NULL' => '禁用'); + foreach ($template_files as $item) { + if (PATH_SEPARATOR !== ':') { + $item = mb_convert_encoding($item, "UTF-8", "GBK"); + } + + $name = mb_split("\.", $item)[0]; + if (empty($name)) { + continue; + } + + $goTemplates[$name] = $name; + } + $edit = new Typecho_Widget_Helper_Form_Element_Select('goTemplate', $goTemplates, 'NULL', _t('跳转页面模板')); + $form->addInput($edit); + $edit = new Typecho_Widget_Helper_Form_Element_Text('goDelay', null, _t('3'), _t('跳转延时'), _t('跳转页面停留时间(秒)')); + $form->addInput($edit); + $edit = new Typecho_Widget_Helper_Form_Element_Text('siteCreatedYear', null, _t('2020'), _t('建站年份'), _t('建站年份,用于模板内容替换模板中使用 {siteCreatedYear} 来代表建站年份')); + $form->addInput($edit); + + $radio = new Typecho_Widget_Helper_Form_Element_Radio('target', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('新窗口打开文章中的链接'), _t('开启后给文章中的链接新增 target 属性')); + $form->addInput($radio); + + $radio = new Typecho_Widget_Helper_Form_Element_Radio('authorPermalinkTarget', array('1' => _t('开启'), '0' => _t('关闭')), '0', _t('新窗口打开评论者链接'), _t('开启后给评论者链接新增 target 属性。(URL 中 target 属性,开启可能会引起主题异常)')); + $form->addInput($radio); + + $textarea = new Typecho_Widget_Helper_Form_Element_Textarea('convertCustomField', null, null, _t('需要处理的自定义字段'), _t('在这里设置需要处理的自定义字段,一行一个(实验性功能)')); + $form->addInput($textarea); + $radio = new Typecho_Widget_Helper_Form_Element_Radio('nullReferer', array('1' => _t('开启'), '0' => _t('关闭')), '1', _t('允许空 referer'), _t('开启后会允许空 referer')); + $form->addInput($radio); + $refererList = new Typecho_Widget_Helper_Form_Element_Textarea('refererList', null, null, _t('referer 白名单'), _t('在这里设置 referer 白名单,一行一个')); + $form->addInput($refererList); + $nonConvertList = new Typecho_Widget_Helper_Form_Element_Textarea('nonConvertList', null, _t("b0.upaiyun.com" . PHP_EOL . "glb.clouddn.com" . PHP_EOL . "qbox.me" . PHP_EOL . "qnssl.com"), _t('外链转换白名单'), _t('在这里设置外链转换白名单(评论者链接不生效)')); + $form->addInput($nonConvertList); + $isDrop = new Typecho_Widget_Helper_Form_Element_Radio('isDrop', array('0' => '删除', '1' => '不删除'), '1', '彻底卸载', '请选择是否在禁用插件时,删除数据表'); + $form->addInput($isDrop); + } + + /** + * 个人用户的配置面板 + * + * @access public + * @param Typecho_Widget_Helper_Form $form + * @return void + */ + public static function personalConfig(Typecho_Widget_Helper_Form $form) + { + } + + /** + * 外链转内链 + * + * @access public + * @param $content + * @param $class + * @return $content + */ + public static function replace($text, $widget, $lastResult) + { + $text = empty($lastResult) ? $text : $lastResult; + $pluginOption = Typecho_Widget::widget('Widget_Options')->Plugin('ShortLinks'); // 插件选项 + $siteUrl = Helper::options()->siteUrl; + $target = ($pluginOption->target) ? ' target="_blank" ' : ''; // 新窗口打开 + if ($pluginOption->convert == 1) { + if (!is_string($text) && $text instanceof Widget_Archive) { + // 自定义字段处理 + $fieldsList = self::textareaToArr($pluginOption->convertCustomField); + if ($fieldsList) { + foreach ($fieldsList as $field) { + if (isset($text->fields[$field])) { + @preg_match_all('//', $text->fields[$field], $matches); + if ($matches) { + foreach ($matches[2] as $link) { + $text->fields[$field] = str_replace("href=\"$link\"", "href=\"" . self::convertLink($link) . "\"", $text->fields[$field]); + } + } + } + } + } + } + if (($widget instanceof Widget_Archive) || ($widget instanceof Widget_Abstract_Comments)) { + $fields = unserialize($widget->fields); + if (is_array($fields) && array_key_exists("noshort", $fields)) { + return $text; + } + + // 文章内容和评论内容处理 + @preg_match_all('//', $text, $matches); + if ($matches) { + foreach ($matches[2] as $link) { + $text = str_replace("href=\"$link\"", "href=\"" . self::convertLink($link) . "\"" . $target, $text); + } + } + } + if ($pluginOption->convertCommentLink == 1 && $widget instanceof Widget_Abstract_Comments) { + // 评论者链接处理 + $url = $text['url']; + if (strpos($url, '://') !== false && strpos($url, rtrim($siteUrl, '/')) === false) { + $text['url'] = self::convertLink($url, false); + if ($pluginOption->authorPermalinkTarget) { + $text['url'] = $text['url'] . '" target="_blank'; + } + } + } + } + return $text; + } + + /** + * 转换链接形式 + * + * @access public + * @param $link + * @return $string + */ + public static function convertLink($link, $check = true) + { + $rewrite = (Helper::options()->rewrite) ? '' : 'index.php/'; // 伪静态处理 + $pluginOption = Typecho_Widget::widget('Widget_Options')->Plugin('ShortLinks'); // 插件选项 + $linkBase = ltrim(rtrim(Typecho_Router::get('go')['url'], '/'), '/'); // 防止链接形式修改后不能用 + $siteUrl = Helper::options()->siteUrl; + $target = ($pluginOption->target) ? ' target="_blank" ' : ''; // 新窗口打开 + $nonConvertList = self::textareaToArr($pluginOption->nonConvertList); // 不转换列表 + if ($check) { + if (strpos($link, '://') !== false && strpos($link, rtrim($siteUrl, '/')) !== false) { + return $link; + } + //本站链接不处理 + if (self::checkDomain($link, $nonConvertList)) { + return $link; + } + // 不转换列表中的不处理 + if (preg_match('/\.(jpg|jepg|png|ico|bmp|gif|tiff)/i', $link)) { + return $link; + } + // 图片不处理 + } + return $siteUrl . $rewrite . str_replace('[key]', self::urlSafeB64Encode(htmlspecialchars_decode($link)), $linkBase); + } + + /** + * 检查域名是否在数组中存在 + * + * @access public + * @param $url $arr + * @param $class + * @return boolean + */ + public static function checkDomain($url, $arr) + { + if ($arr === null) { + return false; + } + + if (count($arr) === 0) { + return false; + } + + foreach ($arr as $a) { + if (strpos($url, $a) !== false) { + return true; + } + } + return false; + } + + /** + * 一行一个文本框转数组 + * + * @access public + * @param $textarea + * @param $class + * @return $arr + */ + public static function textareaToArr($textarea) + { + $str = str_replace(array("\r\n", "\r", "\n"), "|", $textarea); + if ($str == "") { + return null; + } + + return explode("|", $str); + } + /** + * Base64 解码 + * + * @param string $str + * @return string + * @date 2020-05-01 + */ + public static function urlSafeB64Decode($str) + { + $data = str_replace(array('-', '_'), array('+', '/'), $str); + $mod = strlen($data) % 4; + if ($mod) { + $data .= substr('====', $mod); + } + return base64_decode($data); + } + /** + * Base64 编码 + * + * @param string $str + * @return string + * @date 2020-05-01 + */ + public static function urlSafeB64Encode($str) + { + $data = base64_encode($str); + $data = str_replace(array('+', '/', '='), array('-', '_', ''), $data); + return $data; + } +} diff --git a/README.md b/README.md index 4cc8a9e..411cfa8 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,54 @@ -# ShortLinks - -有时候我们为了减少权重的流失,或者是为了隐藏某些推荐链接(比如:淘宝客、主机推荐),因此需要将外链转化为内链(淘宝客、主机推荐都是隐藏 AFF)。 - -Typecho 外链转内链插件,支持正文和评论者链接。 - -从比 1.0.9 更老的版本升级上来建议先禁用再启用。 - -## 获取 Download - -[最稳定版下载地址](https://github.com/benzBrake/ShortLinks/releases/latest) - -## 简介 Introduction - -1. 把外部链接转换为 your_blog_path/go/key/,撰写链接页面支持修改 -2. 通过菜单“创建->短链接”设置; -3. 自定义短链功能来自[golinks](http://defe.me/prg/429.html "golinks"); -4. 支持 referer 白名单和外链转换白名单; -5. 支持跳转页面,可以自行制作模板放到 templates 目录下,插件设置里可选择,目前自带 5 个模板; -6. 支持自定义字段转换(实验性功能); -7. 支持关闭指定页面的链接转换功能。添加自定义字段 `noshort` 即可; - -## 使用方法 Usage - -- 使用 Git 命令直接克隆至插件目录即可,例如: `/var/www/html/usr/plugins/` 下 -- 然后启用插件即可 - -## 其他 Others - -### 模板使用 Template Usage - -模板功能自 1.1.0 b1 开始支持更多的字段替换。 - -支持 Typecho 选项和主题选项字段替换。 - -就是平常用 `$this->options->logoUrl` 这样的形式调用的字段,可以直接在模板里使用 `{{logoUrl}}` 定义,ShortLinks 插件会自动替换。 - -如果发现有不支持的字段,别尝试了,就是 ShortLinks 没适配。 - -### 计划功能 Todo - -- 自定义短链接增加密码功能 - -### 感谢 Thanks - -- [小咪兔](http://forum.typecho.org/viewtopic.php?t=5576 "小咪兔") - -- [DEFE](http://defe.me/prg/429.html "GoLinks") - -- [左岸](https://www.zrahh.com/archives/451.html "左岸") - -### 预览 Preview - -暂无 +# ShortLinks + +有时候我们为了减少权重的流失,或者是为了隐藏某些推荐链接(比如:淘宝客、主机推荐),因此需要将外链转化为内链(淘宝客、主机推荐都是隐藏 AFF)。 + +Typecho 外链转内链插件,支持正文和评论者链接。 + +从比 1.0.9 更老的版本升级上来建议先禁用再启用。 + +## 获取 Download + +[最稳定版下载地址](https://github.com/benzBrake/ShortLinks/releases/latest) + +## 简介 Introduction + +1. 把外部链接转换为 your_blog_path/go/key/,撰写链接页面支持修改 +2. 通过菜单“创建->短链接”设置; +3. 自定义短链功能来自[golinks](http://defe.me/prg/429.html "golinks"); +4. 支持 referer 白名单和外链转换白名单; +5. 支持跳转页面,可以自行制作模板放到 templates 目录下,插件设置里可选择,目前自带 5 个模板; +6. 支持自定义字段转换(实验性功能); +7. 支持关闭指定页面的链接转换功能。添加自定义字段 `noshort` 即可; + +## 使用方法 Usage + +- 使用 Git 命令直接克隆至插件目录即可,例如: `/var/www/html/usr/plugins/` 下 +- 然后启用插件即可 + +## 其他 Others + +### 模板使用 Template Usage + +模板功能自 1.1.0 b1 开始支持更多的字段替换。 + +支持 Typecho 选项和主题选项字段替换。 + +就是平常用 `$this->options->logoUrl` 这样的形式调用的字段,可以直接在模板里使用 `{{logoUrl}}` 定义,ShortLinks 插件会自动替换。 + +如果发现有不支持的字段,别尝试了,就是 ShortLinks 没适配。 + +### 计划功能 Todo + +- 自定义短链接增加密码功能 + +### 感谢 Thanks + +- [小咪兔](http://forum.typecho.org/viewtopic.php?t=5576 "小咪兔") + +- [DEFE](http://defe.me/prg/429.html "GoLinks") + +- [左岸](https://www.zrahh.com/archives/451.html "左岸") + +### 预览 Preview + +暂无 diff --git a/panel.php b/panel.php index 091fd12..f6e40f7 100644 --- a/panel.php +++ b/panel.php @@ -1,137 +1,137 @@ - -
-
- -
-
-
-
    -
  • -
    -     KEY:     - 目标: - -
    -
  • - -
  • - - 自定义链接: - -
  • -
-
-
- - - - - - - - - - - - - - - - - - - page) ? $request->page : 1; ?> - fetchAll($db->select()->from('table.shortlinks')->page($page, 15)->order('table.shortlinks.id', Typecho_Db::SORT_DESC)); ?> - - - - - - - - - - -
- - - - index($rourl); ?> - - 修改 - -
-
-
-
-
    - fetchObject($db->select(array('COUNT(id)' => 'num'))->from('table.shortlinks'))->num; ?> - -
  • -
  • - - -
-
-
-
-
-
-
- - + +
+
+ +
+
+
+
    +
  • +
    +     KEY:     + 目标: + +
    +
  • + +
  • + + 自定义链接: + +
  • +
+
+
+ + + + + + + + + + + + + + + + + + + page) ? $request->page : 1; ?> + fetchAll($db->select()->from('table.shortlinks')->page($page, 15)->order('table.shortlinks.id', Typecho_Db::SORT_DESC)); ?> + + + + + + + + + + +
+ + + + index($rourl); ?> + + 修改 + +
+
+
+
+
    + fetchObject($db->select(array('COUNT(id)' => 'num'))->from('table.shortlinks'))->num; ?> + +
  • +
  • + + +
+
+
+
+
+
+
+ + diff --git a/templates/default.html b/templates/default.html index 46662b2..f98c18f 100644 --- a/templates/default.html +++ b/templates/default.html @@ -1,72 +1,72 @@ - - - - - 页面加载中,请稍候... - - - -

页面加载中...

- + + + + + 页面加载中,请稍候... + + + +

页面加载中...

+ \ No newline at end of file diff --git a/templates/template1.html b/templates/template1.html index b55d639..785434b 100644 --- a/templates/template1.html +++ b/templates/template1.html @@ -1,23 +1,23 @@ - - - - - - - - -页面加载中,请稍候... - - - -
- -
- + + + + + + + + +页面加载中,请稍候... + + + +
+ +
+ \ No newline at end of file diff --git a/templates/template2.html b/templates/template2.html index b11a08b..9422aa4 100644 --- a/templates/template2.html +++ b/templates/template2.html @@ -1,45 +1,45 @@ - - - - - - - - -页面加载中,请稍候... - - - -
-
- L -
-
- O -
-
- A -
-
- D -
-
- I -
-
- N -
-
- G -
-
-
-
-
-
-
-
- + + + + + + + + +页面加载中,请稍候... + + + +
+
+ L +
+
+ O +
+
+ A +
+
+ D +
+
+ I +
+
+ N +
+
+ G +
+
+
+
+
+
+
+
+ \ No newline at end of file diff --git a/templates/template3.html b/templates/template3.html index f762f16..5963ad7 100644 --- a/templates/template3.html +++ b/templates/template3.html @@ -1,37 +1,37 @@ - - - - - - - - -页面加载中,请稍候... - - - -
- - LOADING - - - - - - - - - - - - - -
- + + + + + + + + +页面加载中,请稍候... + + + +
+ + LOADING + + + + + + + + + + + + + +
+ \ No newline at end of file diff --git a/templates/template4.html b/templates/template4.html index 8215480..d437c3c 100644 --- a/templates/template4.html +++ b/templates/template4.html @@ -1,32 +1,32 @@ - - - - - - - - -页面加载中,请稍候... - - - - - - -
- -
- - - - -
-
- - + + + + + + + + +页面加载中,请稍候... + + + + + + +
+ +
+ + + + +
+
+ + \ No newline at end of file diff --git a/templates/template5.html b/templates/template5.html index 956d596..cefa890 100644 --- a/templates/template5.html +++ b/templates/template5.html @@ -1,42 +1,42 @@ - - - - - - - {{title}} - 跳转提示 - - - - -
-
- -
-
-
-
-
-
- 您将要访问 -
-
-
- {{url}} -
- 温馨提示:
该网页不属于"{{title}}",我们无法确认该网页是否安全,它可能包含未知的安全隐患,请注意保护好个人信息! -
-
-
-
-
- -
- - - + + + + + + + {{title}} - 跳转提示 + + + + +
+
+ +
+
+
+
+
+
+ 您将要访问 +
+
+
+ {{url}} +
+ 温馨提示:
该网页不属于"{{title}}",我们无法确认该网页是否安全,它可能包含未知的安全隐患,请注意保护好个人信息! +
+
+
+
+
+ +
+ + + \ No newline at end of file