index('/action/void?wordcount_preview=1'); $previewUrl = trim(ob_get_clean()); $cssPath = 'assets/admin/editor/stats.css'; $jsPath = 'assets/admin/editor/stats.js'; $cssVersion = self::assetVersion($cssPath); $jsVersion = self::assetVersion($jsPath); $config = array( 'previewUrl' => $previewUrl ); ?> fetchAll("SHOW COLUMNS FROM `".$table."`"); foreach ($rows as $row) { if (isset($row['Field']) && $row['Field'] === $field) { return true; } } return false; } private static function hasTable($table) { $db = Typecho_Db::get(); // 兼容性:改为精确匹配,避免 LIKE 模糊匹配导致误判 $rows = $db->fetchAll("SHOW TABLES"); foreach ($rows as $row) { if ($table === reset($row)) { return true; } } return false; } private static function hasIndex($table, $indexName) { $db = Typecho_Db::get(); $rows = $db->fetchAll("SHOW INDEX FROM `".$table."`"); foreach ($rows as $row) { if (isset($row['Key_name']) && $row['Key_name'] === $indexName) { return true; } } return false; } private static function queryAndCatch($sql) { $db = Typecho_Db::get(); try { $db->query($sql); } catch (Typecho_Db_Query_Exception $th) { throw new Typecho_Plugin_Exception($th->getMessage()); } } /** * 激活插件方法,如果激活失败,直接抛出异常 * * @access public * @return void * @throws Typecho_Plugin_Exception */ public static function activate() { // 检查数据库类型 $db = Typecho_Db::get(); $prefix = $db->getPrefix(); $adapterName = strtolower($db->getAdapterName() ?? ''); if (strpos($adapterName, 'mysql') === false) { throw new Typecho_Plugin_Exception('启用失败,本插件暂时只支持 MySQL 数据库,您的数据库是:'.$adapterName); } // 检查是否存在对应扩展 if (!extension_loaded('openssl')) { throw new Typecho_Plugin_Exception('启用失败,PHP 需启用 OpenSSL 扩展。'); } if (!class_exists('DOMDocument') || !class_exists('DOMXPath')) { throw new Typecho_Plugin_Exception('启用失败,PHP 需启用 DOM 扩展。'); } try { VOID_IpDb::bootstrapOrFail(); } catch (Exception $e) { throw new Typecho_Plugin_Exception('启用失败,IP 数据库初始化失败:' . $e->getMessage()); } /** 图片附件尺寸解析,注册 hook */ Typecho_Plugin::factory('Widget_Upload')->upload = array('VOID_Plugin', 'upload'); /** 字数统计 */ // contents 表中若无 wordCount 字段则添加 if (!self::hasColumn($prefix.'contents', 'wordCount')) { self::queryAndCatch('ALTER TABLE `'. $prefix .'contents` ADD COLUMN `wordCount` INT(10) DEFAULT 0;'); } // 更新一次字数统计 VOID_WordCount::updateAllWordCount(); // 注册 hook Typecho_Plugin::factory('Widget_Contents_Post_Edit')->finishPublish = array('VOID_Plugin', 'updateContent'); Typecho_Plugin::factory('Widget_Contents_Page_Edit')->finishPublish = array('VOID_Plugin', 'updateContent'); Typecho_Plugin::factory('admin/write-post.php')->bottom = array('VOID_Plugin', 'editorStatsPanel'); Typecho_Plugin::factory('admin/write-page.php')->bottom = array('VOID_Plugin', 'editorStatsPanel'); // 加入查询 Typecho_Plugin::factory('Widget_Archive')->___wordCount = array('VOID_Plugin', 'wordCount'); /** 文章点赞 */ // 创建字段 if (!self::hasColumn($prefix.'contents', 'likes')) { self::queryAndCatch('ALTER TABLE `'. $prefix .'contents` ADD COLUMN `likes` INT(10) DEFAULT 0;'); } // 加入查询 Typecho_Plugin::factory('Widget_Archive')->___likes = array('VOID_Plugin', 'likes'); /** 评论赞踩 */ // 创建字段 if (!self::hasColumn($prefix.'comments', 'likes')) { self::queryAndCatch('ALTER TABLE `'. $prefix .'comments` ADD COLUMN `likes` INT(10) DEFAULT 0;'); } if (!self::hasColumn($prefix.'comments', 'dislikes')) { self::queryAndCatch('ALTER TABLE `'. $prefix .'comments` ADD COLUMN `dislikes` INT(10) DEFAULT 0;'); } /** 浏览量统计 */ // 创建字段 if (!self::hasColumn($prefix.'contents', 'viewsNum')) { self::queryAndCatch('ALTER TABLE `'. $prefix .'contents` ADD COLUMN `viewsNum` INT(10) DEFAULT 0;'); } //增加浏览数 Typecho_Plugin::factory('Widget_Archive')->beforeRender = array('VOID_Plugin', 'updateViewCount'); // 加入查询 Typecho_Plugin::factory('Widget_Archive')->___viewsNum = array('VOID_Plugin', 'viewsNum'); /** 点赞与投票数据库 */ // 创建表,保存点赞与投票相关信息 $table_name = $prefix . 'votes'; if (!self::hasTable($table_name)) { $sql = 'create table IF NOT EXISTS `'.$table_name.'` ( `vid` int unsigned auto_increment, `id` int unsigned not null, `table` char(32) not null, `type` char(32) not null, `agent` text, `ip` varchar(128), `created` int unsigned default 0, primary key (`vid`) ) default charset=utf8; CREATE INDEX index_ip ON '.$table_name.'(`ip`); CREATE INDEX index_id ON '.$table_name.'(`id`); CREATE INDEX index_table ON '.$table_name.'(`table`); CREATE INDEX index_created ON '.$table_name.'(`created`)'; $sqls = explode(';', $sql); foreach ($sqls as $sql) { self::queryAndCatch($sql); } } else { if (!self::hasColumn($prefix.'votes', 'created')) { self::queryAndCatch('ALTER TABLE `'. $prefix .'votes` ADD COLUMN `created` INT(10) DEFAULT 0;'); } if (!self::hasIndex($prefix.'votes', 'index_created')) { self::queryAndCatch('CREATE INDEX index_created ON `'. $prefix .'votes`(`created`)'); } } // 添加一个面板,展示互动信息,例如评论赞踩、文章点赞 self::cleanupLegacyActivityPanel(); Helper::addPanel(3, 'VOID/pages/activity.php', '互动', '查看访客互动', 'administrator'); // 历史版本曾注册独立 IP 数据库面板,当前已迁移到插件设置页中管理。 Helper::removePanel(3, 'VOID/pages/ipdb.php'); // 添加投票路由,文章与评论 Helper::addAction('void', 'VOID_Action'); // 评论列表显示来源 Typecho_Plugin::factory('Widget_Comments_Admin')->callIp = array('VOID_Plugin', 'commentLocation'); } /** * 禁用插件方法,如果禁用失败,直接抛出异常 * * @static * @access public * @return void * @throws Typecho_Plugin_Exception */ public static function deactivate() { Helper::removeAction('void'); Helper::removeAction('void_vote'); Helper::removePanel(3, 'VOID/pages/activity.php'); Helper::removePanel(3, 'VOID/pages/showActivity.php'); Helper::removePanel(3, 'VOID/pages/ipdb.php'); } /** * 获取插件配置面板 * * @access public * @param Typecho_Widget_Helper_Form $form 配置面板 * @return void */ public static function config(Typecho_Widget_Helper_Form $form) { // 可设置每次获取图片基础信息数量上限 $parseImgLimit = new Typecho_Widget_Helper_Form_Element_Text('parseImgLimit', NULL, '10', _t('单次图片处理数量上限'), _t('这里是每次获取图片基础信息的数量上限。不建议设置过大的数值,太大可能导致处理超时。')); $form->addInput($parseImgLimit); } /** * 个人用户的配置面板 * * @access public * @param Typecho_Widget_Helper_Form $form * @return void */ public static function personalConfig(Typecho_Widget_Helper_Form $form){} /** * 返回文章字数 */ public static function viewsNum($archive) { $db = Typecho_Db::get(); $row = $db->fetchRow($db->select('viewsNum') ->from('table.contents') ->where('cid = ?', $archive->cid)); return $row['viewsNum']; } /** * 返回文章点赞数 */ public static function likes($archive) { $db = Typecho_Db::get(); $row = $db->fetchRow($db->select('likes') ->from('table.contents') ->where('cid = ?', $archive->cid)); return $row['likes']; } /** * 返回文章字数 */ public static function wordCount($archive) { $db = Typecho_Db::get(); $row = $db->fetchRow($db->select('wordCount') ->from('table.contents') ->where('cid = ?', $archive->cid)); return $row['wordCount']; } /** * 更新文章字数统计 * * @access public * @param mixed $archive * @return void */ public static function updateContent($contents, $widget) { VOID_WordCount::wordCountByCid($widget->cid); $ret = VOID_ParseImgInfo::parse($widget->cid); } /** * 更新文章浏览量 * * @param Widget_Archive $archive * @return void */ public static function updateViewCount($archive) { if($archive->is('single')){ $cid = $archive->cid; $views = Typecho_Cookie::get('__void_post_views'); if(empty($views)){ $views = array(); } else { $views = explode(',', $views ?? ''); } if(!in_array($cid,$views)){ $db = Typecho_Db::get(); $row = $db->fetchRow($db->select('viewsNum') ->from('table.contents') ->where('cid = ?', $cid)); $db->query($db->update('table.contents') ->rows(array('viewsNum' => (int)$row['viewsNum']+1)) ->where('cid = ?', $cid)); array_push($views, $cid); $views = implode(',', $views); Typecho_Cookie::set('__void_post_views', $views); //记录查看cookie } } } /** * 在附件链接尾部添加后缀 * * @access public * @param Widget_Upload $uploadObj 上传对象 * @return void */ public static function upload($uploadObj) { // 若是图片,则增加后缀 if ($uploadObj->attachment->isImage) { $meta = getimagesize(__TYPECHO_ROOT_DIR__.$uploadObj->attachment->path); if ($meta != false) { $uploadObj->attachment->url = $uploadObj->attachment->url.'#vwid='.$meta[0].'&vhei='.$meta[1]; } } } /** * 插件实现方法 * * @access public * @param Typecho_Widget $comments 评论 * @return void */ public static function commentLocation($comments) { $location = VOID_IpDb::locate($comments->ip); echo $comments->ip . '
' . $location; } }