diff --git a/admin/manage-categories.php b/admin/manage-categories.php index 0851f875..e588cb02 100644 --- a/admin/manage-categories.php +++ b/admin/manage-categories.php @@ -66,8 +66,8 @@ Typecho_Widget::widget('Widget_Metas_Category_Admin')->to($categories); - children > 0): ?> - children); ?> + children) > 0): ?> + children)); ?> diff --git a/var/Typecho/Db/Query.php b/var/Typecho/Db/Query.php index b09b0a52..3a6f45d9 100644 --- a/var/Typecho/Db/Query.php +++ b/var/Typecho/Db/Query.php @@ -171,6 +171,26 @@ class Typecho_Db_Query return $this->filterColumn(implode(' , ', $fields)); } + /** + * 转义参数 + * + * @param array $values + * @access protected + * @return array + */ + protected function quoteValues(array $values) + { + foreach ($values as &$value) { + if (is_array($value)) { + $value = '(' . implode(',', array_map(array($this->_adapter, 'quoteValue'), $value)) . ')'; + } else { + $value = $this->_adapter->quoteValue($value); + } + } + + return $values; + } + /** * 获取查询字串属性值 * @@ -215,7 +235,7 @@ class Typecho_Db_Query } else { $args = func_get_args(); array_shift($args); - $this->_sqlPreBuild['where'] .= $operator . ' (' . vsprintf($condition, array_map(array($this->_adapter, 'quoteValue'), $args)) . ')'; + $this->_sqlPreBuild['where'] .= $operator . ' (' . vsprintf($condition, $this->quoteValues($args)) . ')'; } return $this; @@ -239,7 +259,7 @@ class Typecho_Db_Query } else { $args = func_get_args(); array_shift($args); - $this->_sqlPreBuild['where'] .= $operator . ' (' . vsprintf($condition, array_map(array($this->_adapter, 'quoteValue'), $args)) . ')'; + $this->_sqlPreBuild['where'] .= $operator . ' (' . vsprintf($condition, $this->quoteValues($args)) . ')'; } return $this; @@ -353,7 +373,7 @@ class Typecho_Db_Query } else { $args = func_get_args(); array_shift($args); - $this->_sqlPreBuild['having'] .= $operator . ' (' . vsprintf($condition, array_map(array($this->_adapter, 'quoteValue'), $args)) . ')'; + $this->_sqlPreBuild['having'] .= $operator . ' (' . vsprintf($condition, $this->quoteValues($args)) . ')'; } return $this; diff --git a/var/Typecho/Router/Parser.php b/var/Typecho/Router/Parser.php index de93ae48..3f1a715c 100644 --- a/var/Typecho/Router/Parser.php +++ b/var/Typecho/Router/Parser.php @@ -60,7 +60,7 @@ class Typecho_Router_Parser 'digital'=> '([0-9]%s)', 'alpha' => '([_0-9a-zA-Z-]%s)', 'alphaslash' => '([_0-9a-zA-Z-/]%s)', - 'split' => '((:?[^/]+/)%s)', + 'split' => '((?:[^/]+/)%s[^/]+)', ); } diff --git a/var/Widget/Abstract/Contents.php b/var/Widget/Abstract/Contents.php index 88d41e3d..b4cf541d 100644 --- a/var/Widget/Abstract/Contents.php +++ b/var/Widget/Abstract/Contents.php @@ -617,10 +617,13 @@ class Widget_Abstract_Contents extends Widget_Abstract ->join('table.relationships', 'table.relationships.mid = table.metas.mid') ->where('table.relationships.cid = ?', $value['cid']) ->where('table.metas.type = ?', 'category') - ->order('table.metas.order', Typecho_Db::SORT_ASC), array($this->widget('Widget_Abstract_Metas'), 'filter')); + ->order('table.metas.order', Typecho_Db::SORT_ASC), array($this->widget('Widget_Metas_Category_List'), 'filter')); /** 取出第一个分类作为slug条件 */ - $value['category'] = current(Typecho_Common::arrayFlatten($value['categories'], 'slug')); + $value['category'] = $value['categories'][0]['slug']; + + $value['directory'] = $this->widget('Widget_Metas_Category_List')->getAllParents($value['category']['mid']); + $value['directory'][] = $value['category']; $value['date'] = new Typecho_Date($value['created']); @@ -638,8 +641,10 @@ class Widget_Abstract_Contents extends Widget_Abstract $tmpSlug = $value['slug']; $tmpCategory = $value['category']; + $tmpDirectory = $value['directory']; $value['slug'] = urlencode($value['slug']); $value['category'] = urlencode($value['category']); + $value['directory'] = implode('/', array_map('urlencode', $value['directory'])); /** 生成静态路径 */ $value['pathinfo'] = $routeExists ? Typecho_Router::url($type, $value) : '#'; @@ -683,6 +688,7 @@ class Widget_Abstract_Contents extends Widget_Abstract $value['slug'] = $tmpSlug; $value['category'] = $tmpCategory; + $value['directory'] = $tmpDirectory; /** 处理密码保护流程 */ if (!empty($value['password']) && diff --git a/var/Widget/Archive.php b/var/Widget/Archive.php index 582731bb..19f2affb 100644 --- a/var/Widget/Archive.php +++ b/var/Widget/Archive.php @@ -789,7 +789,9 @@ class Widget_Archive extends Widget_Abstract_Contents $select->limit(1); $this->query($select); - if (!$this->have() || (isset($this->request->category) && $this->category != $this->request->category)) { + if (!$this->have() + || (isset($this->request->category) && $this->category != $this->request->category) + || (isset($this->request->directory) && $this->request->directory != implode('/', $this->directory))) { if (!$this->_invokeFromOutside) { /** 对没有索引情况下的判断 */ throw new Typecho_Widget_Exception(_t('请求的地址不存在'), 404); @@ -869,21 +871,31 @@ class Widget_Archive extends Widget_Abstract_Contents $categorySelect->where('slug = ?', $this->request->slug); } - $category = $this->db->fetchRow($categorySelect, - array($this->widget('Widget_Abstract_Metas'), 'filter')); + if (isset($this->request->directory)) { + $directory = explode('/', $this->request->directory); + $categorySelect->where('slug = ?', $directory[count($directory) - 1]); + } - if (!$category) { + $category = $this->db->fetchRow($categorySelect, + array($this->widget('Widget_Metas_Category_List'), 'filter')); + + if (!$category + || (isset($directory) && ($this->request->directory != implode('/', $category['directory'])))) { throw new Typecho_Widget_Exception(_t('分类不存在'), 404); } + $children = $this->widget('Widget_Metas_Category_List')->getAllChildren($category['mid']); + $children[] = $category['mid']; + /** fix sql92 by 70 */ $select->join('table.relationships', 'table.contents.cid = table.relationships.cid') - ->where('table.relationships.mid = ?', $category['mid']) + ->where('table.relationships.mid IN ?', $children) ->where('table.contents.type = ?', 'post'); /** 设置分页 */ $this->_pageRow = array_merge($category, array( - 'slug' => urlencode($category['slug']) + 'slug' => urlencode($category['slug']), + 'directory' => implode('/', array_map('urlencode', $category['directory'])) )); /** 设置关键词 */ diff --git a/var/Widget/Comments/Archive.php b/var/Widget/Comments/Archive.php index 5e6dd719..c3d64971 100644 --- a/var/Widget/Comments/Archive.php +++ b/var/Widget/Comments/Archive.php @@ -324,7 +324,6 @@ class Widget_Comments_Archive extends Widget_Abstract_Comments /** 重载push函数,使用coid作为数组键值,便于索引 */ $this->stack[$value['coid']] = $value; $this->length ++; - $this->row = $value; return $value; } diff --git a/var/Widget/Metas/Category/Admin.php b/var/Widget/Metas/Category/Admin.php index 057f7ee9..c52d8d33 100644 --- a/var/Widget/Metas/Category/Admin.php +++ b/var/Widget/Metas/Category/Admin.php @@ -3,25 +3,13 @@ /** * Widget_Metas_Category_Admin * - * @uses Widget_Abstract_Metas + * @uses Widget_Metas_Category_List * @copyright Copyright (c) 2012 Typecho Team. (http://typecho.org) * @author Joyqi * @license GNU General Public License 2.0 */ -class Widget_Metas_Category_Admin extends Widget_Abstract_Metas +class Widget_Metas_Category_Admin extends Widget_Metas_Category_List { - /** - * 子评论 - * - * @access protected - * @return array - */ - protected function ___children() - { - return $this->size($this->db->sql() - ->where('type = ? AND parent = ?', 'category', $this->mid)); - } - /** * 执行函数 * @@ -30,11 +18,11 @@ class Widget_Metas_Category_Admin extends Widget_Abstract_Metas */ public function execute() { - $select = $this->select()->where('type = ?', 'category'); + $select = $this->db->select('mid')->from('table.metas')->where('type = ?', 'category'); $select->where('parent = ?', $this->request->parent ? $this->request->parent : 0); - $this->db->fetchAll($select->order('table.metas.order', Typecho_Db::SORT_ASC), - array($this, 'push')); + $this->stack = $this->getCategories(Typecho_Common::arrayFlatten( + $this->db->fetchAll($select->order('table.metas.order', Typecho_Db::SORT_ASC)), 'mid')); } /** diff --git a/var/Widget/Metas/Category/Edit.php b/var/Widget/Metas/Category/Edit.php index d36a4fcf..0095b16a 100644 --- a/var/Widget/Metas/Category/Edit.php +++ b/var/Widget/Metas/Category/Edit.php @@ -137,8 +137,8 @@ class Widget_Metas_Category_Edit extends Widget_Abstract_Metas implements Widget /** 父级分类 */ $options = array(0 => _t('不选择')); - $parents = $this->widget('Widget_Metas_Category_List@options', 'levelWalk=1' - . (isset($this->request->mid) ? '&ignore=' . $this->request->mid : '')); + $parents = $this->widget('Widget_Metas_Category_List@options', + (isset($this->request->mid) ? 'ignore=' . $this->request->mid : '')); while ($parents->next()) { $options[$parents->mid] = $parents->nameByLevel; @@ -258,13 +258,24 @@ class Widget_Metas_Category_Edit extends Widget_Abstract_Metas implements Widget } /** 取出数据 */ + $current = $this->fetchRow($this->select()->where('mid = ?', $category['mid'])); $category = $this->request->from('name', 'slug', 'description', 'parent'); $category['slug'] = Typecho_Common::slugName(empty($category['slug']) ? $category['name'] : $category['slug']); $category['type'] = 'category'; $category['mid'] = $this->request->mid; - if ($this->fetchObject($this->select()->where('mid = ?', $category['mid']))->parent != $category['parent']) { - $category['order'] = $this->getMaxOrder('category', $category['parent']) + 1; + if ($current['parent'] != $category['parent']) { + $parent = $this->fetchRow($this->select()->where('mid = ?', $category['parent'])); + + if ($parent['mid'] == $category['mid']) { + $category['order'] = $parent['order']; + $this->update(array( + 'parent' => $current['parent'], + 'order' => $current['order'] + ), $this->db->sql()->where('mid = ?', $parent['mid'])); + } else { + $category['order'] = $this->getMaxOrder('category', $category['parent']) + 1; + } } /** 更新数据 */ @@ -299,7 +310,7 @@ class Widget_Metas_Category_Edit extends Widget_Abstract_Metas implements Widget $parent = $this->fetchObject($this->select()->where('mid = ?', $category))->parent; if ($this->delete($this->db->sql()->where('mid = ?', $category))) { - $this->db->query($this->db->sql()->delete('table.relationships')->where('mid = ?', $category)); + $this->db->query($this->db->delete('table.relationships')->where('mid = ?', $category)); $this->update(array('parent' => $parent), $this->db->sql()->where('parent = ?', $category)); $deleteCount ++; } diff --git a/var/Widget/Metas/Category/List.php b/var/Widget/Metas/Category/List.php index 93cacd43..e67a05b2 100644 --- a/var/Widget/Metas/Category/List.php +++ b/var/Widget/Metas/Category/List.php @@ -44,12 +44,36 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas private $_singleCategoryOptions = NULL; /** - * 原始的stack备份 + * 顶层分类 * * @var array * @access private */ - private $_stack = array(); + private $_top = array(); + + /** + * 所有分类哈希表 + * + * @var array + * @access private + */ + private $_map = array(); + + /** + * 所有子节点列表 + * + * @var array + * @access private + */ + private $_children = array(); + + /** + * 所有父节点列表 + * + * @var array + * @access private + */ + private $_parents = array(); /** * 构造函数,初始化组件 @@ -63,12 +87,38 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas public function __construct($request, $response, $params = NULL) { parent::__construct($request, $response, $params); - $this->parameter->setDefault('ignore=0&levelWalk=0'); + $this->parameter->setDefault('ignore=0'); /** 初始化回调函数 */ if (function_exists('treeViewCategories')) { $this->_customTreeViewCategoriesCallback = true; } + + $select = $this->select()->where('type = ?', 'category'); + if ($this->parameter->ignore) { + $select->where('mid <> ?', $this->parameter->ignore); + } + + $categories = $this->db->fetchAll($select->order('table.metas.order', Typecho_Db::SORT_ASC)); + foreach ($categories as $category) { + $category['levels'] = 0; + $this->_map[$category['mid']] = $category; + } + + // 读取数据 + foreach ($this->_map as $mid => $category) { + $parent = $category['parent']; + + if (0 != $parent && isset($this->_map[$parent])) { + $this->_treeViewCategories[$parent][] = $mid; + } else { + $this->_top[] = $mid; + } + } + + // 预处理深度 + $this->levelWalkCallback($this->_top); + $this->_map = array_map(array($this, 'filter'), $this->_map); } /** @@ -80,13 +130,6 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas */ private function treeViewCategoriesCallback() { - $levels = $this->levels; - if (0 != $this->parent && isset($this->_stack[$this->parent]) && !$this->parameter->levelWalk) { - $levels = $this->_stack[$this->parent]['levels'] + 1; - $this->levels = $levels; - $this->_stack[$this->mid]['levels'] = $levels; - } - $singleCategoryOptions = $this->_singleCategoryOptions; if ($this->_customTreeViewCategoriesCallback) { return treeViewCategories($this, $singleCategoryOptions); @@ -134,23 +177,30 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas * @access private * @return void */ - private function levelWalkCallback(array $categories) + private function levelWalkCallback(array $categories, $parents = array()) { - foreach ($categories as $mid => $category) { - $parent = $category['parent']; - - if (0 != $parent && isset($this->stack[$parent])) { - $levels = $this->stack[$parent]['levels'] + 1; - $this->stack[$mid]['levels'] = $levels; - $this->_treeViewCategories[$parent][$mid]['levels'] = $levels; - - if (isset($this->_stack[$mid])) { - $this->_stack[$mid]['levels'] = $levels; - } + foreach ($parents as $parent) { + if (!isset($this->_children[$parent])) { + $this->_children[$parent] = array(); } + $this->_children[$parent] = array_merge($this->_children[$parent], $categories); + } + + foreach ($categories as $mid) { + $parent = $this->_map[$mid]['parent']; + + if (0 != $parent && isset($this->_map[$parent])) { + $levels = $this->_map[$parent]['levels'] + 1; + $this->_map[$mid]['levels'] = $levels; + } + + $this->_parents[$mid] = $parents; + if (!empty($this->_treeViewCategories[$mid])) { - $this->levelWalkCallback($this->_treeViewCategories[$mid]); + $new = $parents; + $new[] = $mid; + $this->levelWalkCallback($this->_treeViewCategories[$mid], $new); } } } @@ -163,8 +213,8 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas */ protected function ___children() { - return !empty($this->_treeViewCategories[$this->mid]) - ? $this->_treeViewCategories[$this->mid] : array(); + return isset($this->_treeViewCategories[$this->mid]) ? + $this->getCategories($this->_treeViewCategories[$this->mid]) : array(); } /** @@ -175,7 +225,7 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas */ protected function ___nameByLevel() { - return str_repeat(' ', $this->levels) . $this->name; + return str_repeat('    ', $this->levels) . $this->name; } /** @@ -186,31 +236,7 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas */ public function execute() { - $select = $this->select()->where('type = ?', 'category'); - if ($this->parameter->ignore) { - $select->where('mid <> ?', $this->parameter->ignore); - } - - $this->db->fetchAll($select->order('table.metas.order', Typecho_Db::SORT_ASC), - array($this, 'push')); - - // 读取数据 - foreach ($this->stack as $mid => $category) { - $parent = $category['parent']; - - if (0 != $parent && isset($this->stack[$parent])) { - $this->_treeViewCategories[$parent][$mid] = $category; - } else { - $this->_stack[$mid] = $category; - } - } - - // 预处理深度 - if ($this->parameter->levelWalk) { - $this->levelWalkCallback($this->_stack); - } - - reset($this->stack); + $this->stack = $this->_map; } /** @@ -265,15 +291,12 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas 'feedTemplate' => 'RSS' )); - // 存储原始数据方便树状访问 - $outputCategories = $this->_stack; - $this->_stack = $this->stack; - $this->stack = $outputCategories; - // 插件插件接口 $this->pluginHandle()->trigger($plugged)->listCategories($this->_singleCategoryOptions, $this); if (!$plugged) { + $this->stack = $this->getCategories($this->_top); + if ($this->have()) { echo '<' . $this->_singleCategoryOptions->wrapTag . (empty($this->_singleCategoryOptions->wrapClass) ? '' : ' class="' . $this->_singleCategoryOptions->wrapClass . '"') . '>'; @@ -282,6 +305,8 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas } echo '_singleCategoryOptions->wrapTag . '>'; } + + $this->stack = $this->_map; } } @@ -307,19 +332,82 @@ class Widget_Metas_Category_List extends Widget_Abstract_Metas * @param array $value 每行的值 * @return array */ - public function push(array $value) + public function filter(array $value) { - $value = $this->filter($value); - - /** 计算深度 */ - $value['levels'] = 0; + $value['directory'] = $this->getAllParents($value['mid']); + $value['directory'][] = $value['slug']; + + $tmpCategoryTree = $value['directory']; + $value['directory'] = implode('/', array_map('urlencode', $value['directory'])); + + $value = parent::filter($value); + $value['directory'] = $tmpCategoryTree; - /** 重载push函数,使用coid作为数组键值,便于索引 */ - $this->stack[$value['mid']] = $value; - $this->length ++; - $this->row = $value; - return $value; } + + /** + * 获取某个分类下的所有子节点 + * + * @param mixed $mid + * @access public + * @return array + */ + public function getAllChildren($mid) + { + return isset($this->_children[$mid]) ? $this->_children[$mid] : array(); + } + + /** + * 获取某个分类所有父级节点 + * + * @param mixed $mid + * @access public + * @return array + */ + public function getAllParents($mid) + { + $parents = array(); + + if (isset($this->_parents[$mid])) { + foreach ($this->_parents[$mid] as $parent) { + $parents[] = $this->_map[$parent]['slug']; + } + } + + return $parents; + } + + /** + * 获取单个分类 + * + * @param integer $mid + * @access public + * @return mixed + */ + public function getCategory($mid) + { + return isset($this->_map[$mid]) ? $this->_map[$mid] : NULL; + } + + /** + * 获取多个分类 + * + * @param mixed $mids + * @access public + * @return array + */ + public function getCategories($mids) + { + $result = array(); + + if (!empty($mids)) { + foreach ($mids as $mid) { + $result[] = $this->_map[$mid]; + } + } + + return $result; + } } diff --git a/var/Widget/Options/Permalink.php b/var/Widget/Options/Permalink.php index 0439de79..44b1f9f9 100644 --- a/var/Widget/Options/Permalink.php +++ b/var/Widget/Options/Permalink.php @@ -29,8 +29,8 @@ class Widget_Options_Permalink extends Widget_Abstract_Options implements Widget */ protected function encodeRule($rule) { - return str_replace(array('{cid}', '{slug}', '{category}', '{category_parents}', '{year}', '{month}', '{day}', '{mid}'), - array('[cid:digital]', '[slug]', '[category]', '[category_parents:split:0]', + return str_replace(array('{cid}', '{slug}', '{category}', '{directory}', '{year}', '{month}', '{day}', '{mid}'), + array('[cid:digital]', '[slug]', '[category]', '[directory:split:0]', '[year:digital:4]', '[month:digital:2]', '[day:digital:2]', '[mid:digital]'), $rule); } @@ -103,7 +103,7 @@ class Widget_Options_Permalink extends Widget_Abstract_Options implements Widget */ public function checkCategoryPattern($value) { - return strpos($value, '{slug}') !== false || strpos($value, '{mid}') !== false; + return strpos($value, '{slug}') !== false || strpos($value, '{mid}') !== false || strpos($value, '{directory}') !== false; } /** @@ -243,7 +243,7 @@ RewriteRule . {$basePath}index.php [L] $patterns['custom'] = _t('个性化定义') . ' '; $postPattern = new Typecho_Widget_Helper_Form_Element_Radio('postPattern', $patterns, - $postPatternValue, _t('自定义文章路径'), _t('可用参数: {cid} 日志 ID, {slug} 日志缩略名, {category} 分类, {category_parents} 父级分类, {year} 年, {month} 月, {day} 日') + $postPatternValue, _t('自定义文章路径'), _t('可用参数: {cid} 日志 ID, {slug} 日志缩略名, {category} 分类, {directory} 多级分类, {year} 年, {month} 月, {day} 日') . '
' . _t('选择一种合适的文章静态路径风格, 使得你的网站链接更加友好.') . '
' . _t('一旦你选择了某种链接风格请不要轻易修改它.')); if ($customPatternValue) { @@ -258,7 +258,7 @@ RewriteRule . {$basePath}index.php [L] $form->addInput($pagePattern->addRule(array($this, 'checkPagePattern'), _t('独立页面路径中没有包含 {cid} 或者 {slug} '))); /** 分类页面 */ - $categoryPattern = new Typecho_Widget_Helper_Form_Element_Text('categoryPattern', NULL, $this->decodeRule($this->options->routingTable['category']['url']), _t('分类路径'), _t('可用参数: {mid} 分类 ID, {slug} 分类缩略名, {category_parents} 父级分类') + $categoryPattern = new Typecho_Widget_Helper_Form_Element_Text('categoryPattern', NULL, $this->decodeRule($this->options->routingTable['category']['url']), _t('分类路径'), _t('可用参数: {mid} 分类 ID, {slug} 分类缩略名, {directory} 多级分类') . '
' . _t('请在路径中至少包含上述的一项参数.')); $categoryPattern->input->setAttribute('class', 'mono w-60'); $form->addInput($categoryPattern->addRule(array($this, 'checkCategoryPattern'), _t('分类路径中没有包含 {mid} 或者 {slug} ')));