diff --git a/app/views/admin/post-edit.js b/app/views/admin/post-edit.js
index cbbdca1..6981d09 100644
--- a/app/views/admin/post-edit.js
+++ b/app/views/admin/post-edit.js
@@ -7,7 +7,7 @@ window.Post = {
data: window.$data,
post: window.$data.post,
sections: []
- }
+ };
},
created: function () {
diff --git a/index.php b/index.php
index 41b681e..91b00e7 100644
--- a/index.php
+++ b/index.php
@@ -36,6 +36,7 @@
'name' => '@blog/api',
'controller' => [
'Pagekit\\Blog\\Controller\\PostApiController',
+ 'Pagekit\\Blog\\Controller\\TagApiController',
'Pagekit\\Blog\\Controller\\CommentApiController'
]
]
@@ -159,6 +160,26 @@
new PostListener(),
new ReadmorePlugin
);
+ //temp add db in dev environment
+ //todo remove here, add to update/install scripts
+ $util = $app['db']->getUtility();
+ if ($util->tableExists('@blog_tag') === false) {
+ $util->createTable('@blog_tag', function ($table) {
+ $table->addColumn('id', 'integer', ['unsigned' => true, 'length' => 10, 'autoincrement' => true]);
+ $table->addColumn('name', 'string', ['length' => 255]);
+ $table->addColumn('slug', 'string', ['length' => 255]);
+ $table->setPrimaryKey(['id']);
+ });
+ }
+
+ if ($util->tableExists('@blog_post_tags') === false) {
+ $util->createTable('@blog_post_tags', function ($table) {
+ $table->addColumn('id', 'integer', ['unsigned' => true, 'length' => 10, 'autoincrement' => true]);
+ $table->addColumn('post_id', 'integer', ['unsigned' => true, 'length' => 10]);
+ $table->addColumn('tag_id', 'integer', ['unsigned' => true, 'length' => 10]);
+ $table->setPrimaryKey(['id']);
+ });
+ }
},
'view.scripts' => function ($event, $scripts) {
diff --git a/scripts.php b/scripts.php
index d1f86d9..45bbf5d 100644
--- a/scripts.php
+++ b/scripts.php
@@ -50,6 +50,24 @@
$table->addIndex(['post_id', 'status'], '@BLOG_COMMENT_POST_ID_STATUS');
});
}
+
+ if ($util->tableExists('@blog_tag') === false) {
+ $util->createTable('@blog_tag', function ($table) {
+ $table->addColumn('id', 'integer', ['unsigned' => true, 'length' => 10, 'autoincrement' => true]);
+ $table->addColumn('name', 'string', ['length' => 255]);
+ $table->addColumn('slug', 'string', ['length' => 255]);
+ $table->setPrimaryKey(['id']);
+ });
+ }
+
+ if ($util->tableExists('@blog_post_tags') === false) {
+ $util->createTable('@blog_post_tags', function ($table) {
+ $table->addColumn('id', 'integer', ['unsigned' => true, 'length' => 10, 'autoincrement' => true]);
+ $table->addColumn('post_id', 'integer', ['unsigned' => true, 'length' => 10]);
+ $table->addColumn('tag_id', 'integer', ['unsigned' => true, 'length' => 10]);
+ $table->setPrimaryKey(['id']);
+ });
+ }
},
@@ -64,6 +82,14 @@
if ($util->tableExists('@blog_comment')) {
$util->dropTable('@blog_comment');
}
+
+ if ($util->tableExists('@blog_tag')) {
+ $util->dropTable('@blog_tag');
+ }
+
+ if ($util->tableExists('@blog_post_tags')) {
+ $util->dropTable('@blog_post_tags');
+ }
},
'updates' => [
diff --git a/src/Controller/BlogController.php b/src/Controller/BlogController.php
index d4312ef..c1506a5 100644
--- a/src/Controller/BlogController.php
+++ b/src/Controller/BlogController.php
@@ -5,6 +5,7 @@
use Pagekit\Application as App;
use Pagekit\Blog\Model\Comment;
use Pagekit\Blog\Model\Post;
+use Pagekit\Blog\Model\Tag;
use Pagekit\User\Model\Role;
/**
@@ -44,7 +45,7 @@ public function editAction($id = 0)
{
try {
- if (!$post = Post::where(compact('id'))->related('user')->first()) {
+ if (!$post = Post::where(compact('id'))->related('user', 'tags')->first()) {
if ($id) {
App::abort(404, __('Invalid post id.'));
@@ -88,6 +89,8 @@ public function editAction($id = 0)
],
'$data' => [
'post' => $post,
+ 'post_tags' => array_values($post->tags ? : []),
+ 'tags' => array_values(Tag::findAll()),
'statuses' => Post::getStatuses(),
'roles' => array_values(Role::findAll()),
'canEditAll' => $user->hasAccess('blog: manage all posts'),
diff --git a/src/Controller/PostApiController.php b/src/Controller/PostApiController.php
index 329a57b..ed1c23a 100644
--- a/src/Controller/PostApiController.php
+++ b/src/Controller/PostApiController.php
@@ -51,7 +51,7 @@ public function indexAction($filter = [], $page = 0)
$pages = ceil($count / $limit);
$page = max(0, min($pages - 1, $page));
- $posts = array_values($query->offset($page * $limit)->related('user', 'comments')->limit($limit)->orderBy($order[1], $order[2])->get());
+ $posts = array_values($query->offset($page * $limit)->related('user', 'comments', 'tags')->limit($limit)->orderBy($order[1], $order[2])->get());
return compact('posts', 'pages', 'count');
}
diff --git a/src/Controller/SiteController.php b/src/Controller/SiteController.php
index 1324f56..69af60e 100644
--- a/src/Controller/SiteController.php
+++ b/src/Controller/SiteController.php
@@ -25,21 +25,26 @@ public function __construct()
* @Route("/")
* @Route("/page/{page}", name="page", requirements={"page" = "\d+"})
*/
- public function indexAction($page = 1)
+ public function indexAction($page = 1, $tag_id = 0)
{
if (!App::node()->hasAccess(App::user())) {
App::abort(403, __('Insufficient User Rights.'));
}
- $query = Post::where(['status = ?', 'date < ?'], [Post::STATUS_PUBLISHED, new \DateTime])->where(function ($query) {
+ $query = Post::where(['status = ?', 'date < ?'], [Post::STATUS_PUBLISHED, new \DateTime])->from('@blog_post i')->where(function ($query) {
return $query->where('roles IS NULL')->whereInSet('roles', App::user()->roles, false, 'OR');
- })->related('user');
+ })->related('user', 'tags');
+
+ if ($tag_id) {
+ //taxonomy trick?
+ $query->join('@blog_post_tags t', 'post_id = i.id')->where('tag_id = ?', [$tag_id]);
+ }
if (!$limit = $this->blog->config('posts.posts_per_page')) {
$limit = 10;
}
- $count = $query->count('id');
+ $count = $query->count('i.id');
$total = ceil($count / $limit);
$page = max(1, min($total, $page));
@@ -97,7 +102,7 @@ public function feedAction($type = '')
foreach (Post::where(['status = ?', 'date < ?'], [Post::STATUS_PUBLISHED, new \DateTime])->where(function ($query) {
return $query->where('roles IS NULL')->whereInSet('roles', App::user()->roles, false, 'OR');
- })->related('user')->limit($this->blog->config('feed.limit'))->orderBy('date', 'DESC')->get() as $post) {
+ })->related('user', 'tags')->limit($this->blog->config('feed.limit'))->orderBy('date', 'DESC')->get() as $post) {
$url = App::url('@blog/id', ['id' => $post->id], 0);
$feed->addItem(
$feed->createItem([
@@ -119,7 +124,7 @@ public function feedAction($type = '')
*/
public function postAction($id = 0)
{
- if (!$post = Post::where(['id = ?', 'status = ?', 'date < ?'], [$id, Post::STATUS_PUBLISHED, new \DateTime])->related('user')->first()) {
+ if (!$post = Post::where(['id = ?', 'status = ?', 'date < ?'], [$id, Post::STATUS_PUBLISHED, new \DateTime])->related('user', 'tags')->first()) {
App::abort(404, __('Post not found!'));
}
@@ -168,4 +173,13 @@ public function postAction($id = 0)
'post' => $post
];
}
+
+ /**
+ * @Route("/tag/{id}", name="tag")
+ * @Route("/tag/{id}/page/{page}", name="tag/page", requirements={"page" = "\d+", "id" = "\d+"})
+ */
+ public function tagAction($page = 1, $id = '')
+ {
+ return $this->indexAction($page, $id);
+ }
}
diff --git a/src/Controller/TagApiController.php b/src/Controller/TagApiController.php
new file mode 100644
index 0000000..d008d29
--- /dev/null
+++ b/src/Controller/TagApiController.php
@@ -0,0 +1,113 @@
+where(function ($query) use ($search) {
+ $query->orWhere(['name LIKE :search', 'slug LIKE :search'], ['search' => "%{$search}%"]);
+ });
+ }
+
+ if (!preg_match('/^(name|slug)\s(asc|desc)$/i', $order, $order)) {
+ $order = [1 => 'name', 2 => 'asc'];
+ }
+
+ $limit = (int) $limit ?: App::module('blog')->config('posts.posts_per_page');
+ $count = $query->count();
+ $pages = ceil($count / $limit);
+ $page = max(0, min($pages - 1, $page));
+
+ $tags = array_values($query->offset($page * $limit)->limit($limit)->orderBy($order[1], $order[2])->get());
+
+ return compact('tags', 'pages', 'count');
+ }
+
+ /**
+ * @Route("/", methods="POST")
+ * @Route("/{id}", methods="POST", requirements={"id"="\d+"})
+ * @Request({"tag": "array", "id": "int"}, csrf=true)
+ */
+ public function saveAction($data, $id = 0)
+ {
+ if (!$id || !$tag = Tag::find($id)) {
+
+ if ($id) {
+ App::abort(404, __('Post not found.'));
+ }
+
+ $tag = Tag::create();
+ }
+
+ if (!$data['slug'] = App::filter($data['slug'] ?: $data['name'], 'slugify')) {
+ App::abort(400, __('Invalid slug.'));
+ }
+
+ $tag->save($data);
+
+ return ['message' => 'success', 'tag' => $tag];
+ }
+
+ /**
+ * @Route("/{id}", methods="DELETE", requirements={"id"="\d+"})
+ * @Request({"id": "int"}, csrf=true)
+ */
+ public function deleteAction($id)
+ {
+ if ($tag = Tag::find($id)) {
+
+ if(!App::user()->hasAccess('blog: manage all posts')) {
+ App::abort(400, __('Access denied.'));
+ }
+
+ $tag->delete();
+ }
+
+ return ['message' => 'success'];
+ }
+
+ /**
+ * @Route("/bulk", methods="POST")
+ * @Request({"tags": "array"}, csrf=true)
+ */
+ public function bulkSaveAction($tags = [])
+ {
+ foreach ($tags as $data) {
+ $this->saveAction($data, isset($data['id']) ? $data['id'] : 0);
+ }
+
+ return ['message' => 'success'];
+ }
+
+ /**
+ * @Route("/bulk", methods="DELETE")
+ * @Request({"ids": "array"}, csrf=true)
+ */
+ public function bulkDeleteAction($ids = [])
+ {
+ foreach (array_filter($ids) as $id) {
+ $this->deleteAction($id);
+ }
+
+ return ['message' => 'success'];
+ }
+}
diff --git a/src/Event/RouteListener.php b/src/Event/RouteListener.php
index 3840293..90ac0b5 100644
--- a/src/Event/RouteListener.php
+++ b/src/Event/RouteListener.php
@@ -24,6 +24,9 @@ public function onConfigureRoute($event, $route)
if ($route->getName() == '@blog/id' && UrlResolver::getPermalink()) {
App::routes()->alias(dirname($route->getPath()).'/'.ltrim(UrlResolver::getPermalink(), '/'), '@blog/id', ['_resolver' => 'Pagekit\Blog\UrlResolver']);
}
+ if ($route->getName() == '@blog/tag') {
+ App::routes()->alias(dirname($route->getPath()).'/{tag}/', '@blog/tag', ['_resolver' => 'Pagekit\Blog\TagUrlResolver']);
+ }
}
/**
diff --git a/src/Model/Post.php b/src/Model/Post.php
index a94f9c4..d871f66 100755
--- a/src/Model/Post.php
+++ b/src/Model/Post.php
@@ -70,11 +70,22 @@ class Post implements \JsonSerializable
*/
public $comments;
+ /**
+ * @ManyToMany(
+ * targetEntity="Tag",
+ * tableThrough="@blog_post_tags",
+ * keyThroughFrom="post_id",
+ * keyThroughTo="tag_id"
+ * )
+ */
+ public $tags;
+
/** @var array */
protected static $properties = [
'author' => 'getAuthor',
'published' => 'isPublished',
- 'accessible' => 'isAccessible'
+ 'accessible' => 'isAccessible',
+ 'tag_names' => 'getTagNames'
];
public static function getStatuses()
@@ -117,6 +128,18 @@ public function isAccessible(User $user = null)
return $this->isPublished() && $this->hasAccess($user ?: App::user());
}
+ /**
+ * @return array
+ */
+ public function getTagNames () {
+ if ($this->tags) {
+ return array_values(array_map(function ($tag) {
+ return $tag->name;
+ }, $this->tags));
+ }
+ return [];
+ }
+
/**
* {@inheritdoc}
*/
diff --git a/src/Model/PostTags.php b/src/Model/PostTags.php
new file mode 100644
index 0000000..7511f6f
--- /dev/null
+++ b/src/Model/PostTags.php
@@ -0,0 +1,31 @@
+toArray();
+ }
+}
diff --git a/src/Model/Tag.php b/src/Model/Tag.php
new file mode 100644
index 0000000..ebc08e4
--- /dev/null
+++ b/src/Model/Tag.php
@@ -0,0 +1,40 @@
+id;
+
+ if (!$tag->slug) {
+ $tag->slug = $tag->title;
+ }
+
+ while (self::where(['slug = ?'], [$tag->slug])->where(function ($query) use ($id) {
+ if ($id) $query->where('id <> ?', [$id]);
+ })->first()) {
+ $tag->slug = preg_replace('/-\d+$/', '', $tag->slug).'-'.$i++;
+ }
+
+ }
+
+}
diff --git a/src/TagUrlResolver.php b/src/TagUrlResolver.php
new file mode 100644
index 0000000..6fb7b53
--- /dev/null
+++ b/src/TagUrlResolver.php
@@ -0,0 +1,109 @@
+cacheEntries = App::cache()->fetch(self::CACHE_KEY) ?: [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function match(array $parameters = [])
+ {
+ if (isset($parameters['id'])) {
+ return $parameters;
+ }
+
+ if (!isset($parameters['tag'])) {
+ App::abort(404, 'Tag not found.');
+ }
+
+ $slug = $parameters['tag'];
+
+ $id = false;
+ foreach ($this->cacheEntries as $entry) {
+ if ($entry['slug'] === $slug) {
+ $id = $entry['id'];
+ }
+ }
+
+ if (!$id) {
+
+ if (!$tag = Tag::where(compact('slug'))->first()) {
+ App::abort(404, 'Tag not found.');
+ }
+
+ $this->addCache($tag);
+ $id = $tag->id;
+ }
+
+ $parameters['id'] = $id;
+ return $parameters;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function generate(array $parameters = [])
+ {
+ $id = $parameters['id'];
+
+ if (!isset($this->cacheEntries[$id])) {
+
+ if (!$tag = Tag::where(compact('id'))->first()) {
+ throw new RouteNotFoundException('Tag not found!');
+ }
+
+ $this->addCache($tag);
+ }
+
+ $meta = $this->cacheEntries[$id];
+
+ $parameters['tag'] = $meta['slug'];
+
+ unset($parameters['id']);
+ return $parameters;
+ }
+
+ public function __destruct()
+ {
+ if ($this->cacheDirty) {
+ App::cache()->save(self::CACHE_KEY, $this->cacheEntries);
+ }
+ }
+
+ protected function addCache($tag)
+ {
+ $this->cacheEntries[$tag->id] = [
+ 'id' => $tag->id,
+ 'slug' => $tag->slug
+ ];
+
+ $this->cacheDirty = true;
+ }
+}
diff --git a/views/admin/post-index.php b/views/admin/post-index.php
index cc9aa80..acedc15 100644
--- a/views/admin/post-index.php
+++ b/views/admin/post-index.php
@@ -48,6 +48,7 @@
{{ 'Comments' | trans }} |
+
{{ 'Tags' | trans }} |
{{ 'Date' | trans }} |
{{ 'URL' | trans }} |
@@ -73,6 +74,9 @@
{{ post.comment_count }}
|
+
+ {{ post.tag_names }}
+ |
{{ post.date | date }}
|
diff --git a/views/post.php b/views/post.php
index d892997..11c2a91 100644
--- a/views/post.php
+++ b/views/post.php
@@ -10,6 +10,10 @@
= __('Written by %name% on %date%', ['%name%' => $post->user->name, '%date%' => '' ]) ?>
+
+ tag_names) : ?>
+
= __('Tags: %tags%', ['%tags%' => implode(', ', $post->tag_names)]) ?>
+
= $post->content ?>
diff --git a/views/posts.php b/views/posts.php
index d03b231..83dae68 100755
--- a/views/posts.php
+++ b/views/posts.php
@@ -11,6 +11,10 @@
= __('Written by %name% on %date%', ['%name%' => $post->user->name, '%date%' => '' ]) ?>
+
+ tag_names) : ?>
+
= __('Tags: %tags%', ['%tags%' => implode(', ', $post->tag_names)]) ?>
+
= $post->excerpt ?: $post->content ?>