diff options
33 files changed, 1211 insertions, 12 deletions
diff --git a/admin/category/delete.php b/admin/category/delete.php new file mode 100644 index 0000000..e92387c --- /dev/null +++ b/admin/category/delete.php @@ -0,0 +1,56 @@ +<?php +#=============================================================================== +# DEFINE: Administration +#=============================================================================== +const ADMINISTRATION = TRUE; +const AUTHENTICATION = TRUE; + +#=============================================================================== +# INCLUDE: Initialization +#=============================================================================== +require '../../core/application.php'; + +#=============================================================================== +# Get repositories +#=============================================================================== +$CategoryRepository = Application::getRepository('Category'); + +#=============================================================================== +# Throw 404 error if category could not be found +#=============================================================================== +if(!$Category = $CategoryRepository->find(HTTP::GET('id'))) { + Application::error404(); +} + +#=============================================================================== +# Check for delete request +#=============================================================================== +if(HTTP::issetPOST(['token' => Application::getSecurityToken()], 'delete')) { + try { + if($CategoryRepository->delete($Category)) { + HTTP::redirect(Application::getAdminURL('category/')); + } + } catch(PDOException $Exception) { + $messages[] = $Exception->getMessage(); + } +} + +#=============================================================================== +# Build document +#=============================================================================== +$FormTemplate = Template\Factory::build('category/form'); +$FormTemplate->set('HTML', parseEntityContent($Category)); +$FormTemplate->set('FORM', [ + 'TYPE' => 'DELETE', + 'INFO' => $messages ?? [], + 'DATA' => array_change_key_case($Category->getAll(), CASE_UPPER), + 'TOKEN' => Application::getSecurityToken() +]); + +$DeleteTemplate = Template\Factory::build('category/delete'); +$DeleteTemplate->set('HTML', $FormTemplate); + +$MainTemplate = Template\Factory::build('main'); +$MainTemplate->set('NAME', $Language->text('title_category_delete')); +$MainTemplate->set('HTML', $DeleteTemplate); +echo $MainTemplate; diff --git a/admin/category/index.php b/admin/category/index.php new file mode 100644 index 0000000..429435a --- /dev/null +++ b/admin/category/index.php @@ -0,0 +1,72 @@ +<?php +#=============================================================================== +# DEFINE: Administration +#=============================================================================== +const ADMINISTRATION = TRUE; +const AUTHENTICATION = TRUE; + +#=============================================================================== +# INCLUDE: Initialization +#=============================================================================== +require '../../core/application.php'; + +#=============================================================================== +# Get repositories +#=============================================================================== +$CategoryRepository = Application::getRepository('Category'); + +#=============================================================================== +# Pagination +#=============================================================================== +$site_size = Application::get('ADMIN.CATEGORY.LIST_SIZE'); + +$count = $CategoryRepository->getCount(); +$lastSite = ceil($count / $site_size); + +$currentSite = HTTP::GET('site') ?? 1; +$currentSite = intval($currentSite); + +#=============================================================================== +# Redirect to category create form if no category exists +#=============================================================================== +if($count === 0) { + HTTP::redirect(Application::getAdminURL('category/insert.php')); +} + +if($currentSite < 1 OR ($currentSite > $lastSite AND $lastSite > 0)) { + Application::error404(); +} + +#=============================================================================== +# Get paginated category list +#=============================================================================== +$categories = $CategoryRepository->getPaginatedTree( + $site_size, ($currentSite-1) * $site_size); + +foreach($categories as $Category) { + $templates[] = generateCategoryItemTemplate($Category, TRUE); +} + +#=============================================================================== +# Build document +#=============================================================================== +$PaginationTemplate = Template\Factory::build('pagination'); +$PaginationTemplate->set('THIS', $currentSite); +$PaginationTemplate->set('LAST', $lastSite); +$PaginationTemplate->set('HREF', Application::getAdminURL('category/?site=%d')); + +$ListTemplate = Template\Factory::build('category/index'); +$ListTemplate->set('LIST', [ + 'CATEGORIES' => $templates ?? [] +]); + +$ListTemplate->set('PAGINATION', [ + 'THIS' => $currentSite, + 'LAST' => $lastSite, + 'HTML' => $PaginationTemplate +]); + +$MainTemplate = Template\Factory::build('main'); +$MainTemplate->set('NAME', $Language->text('title_category_overview', $currentSite)); +$MainTemplate->set('HTML', $ListTemplate); +echo $MainTemplate; diff --git a/admin/category/insert.php b/admin/category/insert.php new file mode 100644 index 0000000..f6f193e --- /dev/null +++ b/admin/category/insert.php @@ -0,0 +1,80 @@ +<?php +#=============================================================================== +# DEFINE: Administration +#=============================================================================== +const ADMINISTRATION = TRUE; +const AUTHENTICATION = TRUE; + +#=============================================================================== +# INCLUDE: Initialization +#=============================================================================== +require '../../core/application.php'; + +#=============================================================================== +# Get repositories +#=============================================================================== +$CategoryRepository = Application::getRepository('Category'); + +#=============================================================================== +# Instantiate new Category entity +#=============================================================================== +$Category = new ORM\Entities\Category; + +#=============================================================================== +# Check for insert request +#=============================================================================== +if(HTTP::issetPOST('parent', 'slug', 'name', 'body', 'argv', 'time_insert', 'time_update', 'insert')) { + $Category->set('parent', HTTP::POST('parent') ?: NULL); + $Category->set('slug', HTTP::POST('slug') ?: generateSlug(HTTP::POST('name'))); + $Category->set('name', HTTP::POST('name') ?: NULL); + $Category->set('body', HTTP::POST('body') ?: NULL); + $Category->set('argv', HTTP::POST('argv') ?: NULL); + $Category->set('time_insert', HTTP::POST('time_insert') ?: date('Y-m-d H:i:s')); + $Category->set('time_update', HTTP::POST('time_update') ?: date('Y-m-d H:i:s')); + + if(HTTP::issetPOST(['token' => Application::getSecurityToken()])) { + try { + if($CategoryRepository->insert($Category)) { + HTTP::redirect(Application::getAdminURL('category/')); + } + } catch(PDOException $Exception) { + $messages[] = $Exception->getMessage(); + } + } + + else { + $messages[] = $Language->text('error_security_csrf'); + } +} + +#=============================================================================== +# Generate category list +#=============================================================================== +foreach($CategoryRepository->getAll([], 'name ASC') as $_Category) { + $categoryList[] = [ + 'ID' => $_Category->getID(), + 'NAME' => $_Category->get('name'), + 'PARENT' => $_Category->get('parent'), + ]; +} + +#=============================================================================== +# Build document +#=============================================================================== +$FormTemplate = Template\Factory::build('category/form'); +$FormTemplate->set('FORM', [ + 'TYPE' => 'INSERT', + 'INFO' => $messages ?? [], + 'DATA' => array_change_key_case($Category->getAll(), CASE_UPPER), + 'CATEGORY_LIST' => $categoryList ?? [], + 'CATEGORY_TREE' => generateCategoryDataTree($categoryList ?? []), + 'TOKEN' => Application::getSecurityToken() +]); + +$InsertTemplate = Template\Factory::build('category/insert'); +$InsertTemplate->set('HTML', $FormTemplate); + +$MainTemplate = Template\Factory::build('main'); +$MainTemplate->set('NAME', $Language->text('title_category_insert')); +$MainTemplate->set('HTML', $InsertTemplate); +echo $MainTemplate; diff --git a/admin/category/update.php b/admin/category/update.php new file mode 100644 index 0000000..7cee587 --- /dev/null +++ b/admin/category/update.php @@ -0,0 +1,84 @@ +<?php +#=============================================================================== +# DEFINE: Administration +#=============================================================================== +const ADMINISTRATION = TRUE; +const AUTHENTICATION = TRUE; + +#=============================================================================== +# INCLUDE: Initialization +#=============================================================================== +require '../../core/application.php'; + +#=============================================================================== +# Get repositories +#=============================================================================== +$CategoryRepository = Application::getRepository('Category'); + +#=============================================================================== +# Throw 404 error if category could not be found +#=============================================================================== +if(!$Category = $CategoryRepository->find(HTTP::GET('id'))) { + Application::error404(); +} + +#=============================================================================== +# Check for update request +#=============================================================================== +if(HTTP::issetPOST('parent', 'slug', 'name', 'body', 'argv', 'time_insert', 'time_update', 'update')) { + $Category->set('slug', HTTP::POST('slug') ?: generateSlug(HTTP::POST('name'))); + $Category->set('name', HTTP::POST('name') ?: NULL); + $Category->set('body', HTTP::POST('body') ?: NULL); + $Category->set('argv', HTTP::POST('argv') ?: NULL); + $Category->set('time_insert', HTTP::POST('time_insert') ?: date('Y-m-d H:i:s')); + $Category->set('time_update', HTTP::POST('time_update') ?: date('Y-m-d H:i:s')); + + # Modify parent field only if it is not a self-reference + if(HTTP::POST('parent') != $Category->getID()) { + $Category->set('parent', HTTP::POST('parent') ?: NULL); + } + + if(HTTP::issetPOST(['token' => Application::getSecurityToken()])) { + try { + $CategoryRepository->update($Category); + } catch(PDOException $Exception) { + $messages[] = $Exception->getMessage(); + } + } + + else { + $messages[] = $Language->text('error_security_csrf'); + } +} + +#=============================================================================== +# Generate category list +#=============================================================================== +foreach($CategoryRepository->getAll([], 'name ASC') as $_Category) { + $categoryList[] = [ + 'ID' => $_Category->getID(), + 'NAME' => $_Category->get('name'), + 'PARENT' => $_Category->get('parent'), + ]; +} + +#=============================================================================== +# Build document +#=============================================================================== +$FormTemplate = Template\Factory::build('category/form'); +$FormTemplate->set('FORM', [ + 'TYPE' => 'UPDATE', + 'INFO' => $messages ?? [], + 'DATA' => array_change_key_case($Category->getAll(), CASE_UPPER), + 'CATEGORY_LIST' => $categoryList ?? [], + 'CATEGORY_TREE' => generateCategoryDataTree($categoryList ?? []), + 'TOKEN' => Application::getSecurityToken() +]); + +$InsertTemplate = Template\Factory::build('category/update'); +$InsertTemplate->set('HTML', $FormTemplate); + +$MainTemplate = Template\Factory::build('main'); +$MainTemplate->set('NAME', $Language->text('title_category_update')); +$MainTemplate->set('HTML', $InsertTemplate); +echo $MainTemplate; diff --git a/admin/post/insert.php b/admin/post/insert.php index b4404fa..bad55ae 100644 --- a/admin/post/insert.php +++ b/admin/post/insert.php @@ -13,6 +13,7 @@ require '../../core/application.php'; #=============================================================================== # Get repositories #=============================================================================== +$CategoryRepository = Application::getRepository('Category'); $PostRepository = Application::getRepository('Post'); $UserRepository = Application::getRepository('User'); @@ -24,7 +25,8 @@ $Post = new ORM\Entities\Post; #=============================================================================== # Check for insert request #=============================================================================== -if(HTTP::issetPOST('user', 'slug', 'name', 'body', 'argv', 'time_insert', 'time_update', 'insert')) { +if(HTTP::issetPOST('category', 'user', 'slug', 'name', 'body', 'argv', 'time_insert', 'time_update', 'insert')) { + $Post->set('category', HTTP::POST('category') ?: NULL); $Post->set('user', HTTP::POST('user')); $Post->set('slug', HTTP::POST('slug') ?: generateSlug(HTTP::POST('name'))); $Post->set('name', HTTP::POST('name') ?: NULL); @@ -60,6 +62,17 @@ foreach($UserRepository->getAll([], 'fullname ASC') as $User) { } #=============================================================================== +# Generate category list +#=============================================================================== +foreach($CategoryRepository->getAll([], 'name ASC') as $Category) { + $categoryList[] = [ + 'ID' => $Category->getID(), + 'NAME' => $Category->get('name'), + 'PARENT' => $Category->get('parent'), + ]; +} + +#=============================================================================== # Build document #=============================================================================== $FormTemplate = Template\Factory::build('post/form'); @@ -68,6 +81,8 @@ $FormTemplate->set('FORM', [ 'INFO' => $messages ?? [], 'DATA' => array_change_key_case($Post->getAll(), CASE_UPPER), 'USER_LIST' => $userList ?? [], + 'CATEGORY_LIST' => $categoryList ?? [], + 'CATEGORY_TREE' => generateCategoryDataTree($categoryList ?? []), 'TOKEN' => Application::getSecurityToken() ]); diff --git a/admin/post/update.php b/admin/post/update.php index 3e4d0ab..e68b3ec 100644 --- a/admin/post/update.php +++ b/admin/post/update.php @@ -13,6 +13,7 @@ require '../../core/application.php'; #=============================================================================== # Get repositories #=============================================================================== +$CategoryRepository = Application::getRepository('Category'); $PostRepository = Application::getRepository('Post'); $UserRepository = Application::getRepository('User'); @@ -26,7 +27,8 @@ if(!$Post = $PostRepository->find(HTTP::GET('id'))) { #=============================================================================== # Check for update request #=============================================================================== -if(HTTP::issetPOST('user', 'slug', 'name', 'body', 'argv', 'time_insert', 'time_update', 'update')) { +if(HTTP::issetPOST('category', 'user', 'slug', 'name', 'body', 'argv', 'time_insert', 'time_update', 'update')) { + $Post->set('category', HTTP::POST('category') ?: NULL); $Post->set('user', HTTP::POST('user')); $Post->set('slug', HTTP::POST('slug') ?: generateSlug(HTTP::POST('name'))); $Post->set('name', HTTP::POST('name') ?: NULL); @@ -60,6 +62,17 @@ foreach($UserRepository->getAll([], 'fullname ASC') as $User) { } #=============================================================================== +# Generate category list +#=============================================================================== +foreach($CategoryRepository->getAll([], 'name ASC') as $Category) { + $categoryList[] = [ + 'ID' => $Category->getID(), + 'NAME' => $Category->get('name'), + 'PARENT' => $Category->get('parent'), + ]; +} + +#=============================================================================== # Build document #=============================================================================== $FormTemplate = Template\Factory::build('post/form'); @@ -68,6 +81,8 @@ $FormTemplate->set('FORM', [ 'INFO' => $messages ?? [], 'DATA' => array_change_key_case($Post->getAll(), CASE_UPPER), 'USER_LIST' => $userList ?? [], + 'CATEGORY_LIST' => $categoryList ?? [], + 'CATEGORY_TREE' => generateCategoryDataTree($categoryList ?? []), 'TOKEN' => Application::getSecurityToken() ]); diff --git a/core/application.php b/core/application.php index 5bbc5d1..0e1324a 100644 --- a/core/application.php +++ b/core/application.php @@ -63,23 +63,29 @@ foreach([ 'PATHINFO.PROT' => $_SERVER['REQUEST_SCHEME'] ?? 'https', 'PATHINFO.HOST' => $_SERVER['HTTP_HOST'] ?? 'localhost', 'PATHINFO.BASE' => '', + 'CATEGORY.DIRECTORY' => 'category', 'PAGE.DIRECTORY' => 'page', 'POST.DIRECTORY' => 'post', 'USER.DIRECTORY' => 'user', + 'CATEGORY.SLUG_URLS' => TRUE, 'PAGE.SLUG_URLS' => TRUE, 'POST.SLUG_URLS' => TRUE, 'USER.SLUG_URLS' => TRUE, + 'CATEGORY.EMOTICONS' => TRUE, 'PAGE.EMOTICONS' => TRUE, 'POST.EMOTICONS' => TRUE, 'USER.EMOTICONS' => TRUE, + 'CATEGORY.LIST_SIZE' => 10, 'PAGE.LIST_SIZE' => 10, 'POST.LIST_SIZE' => 10, 'USER.LIST_SIZE' => 10, 'PAGE.FEED_SIZE' => 25, 'POST.FEED_SIZE' => 25, + 'CATEGORY.DESCRIPTION_SIZE' => 200, 'PAGE.DESCRIPTION_SIZE' => 200, 'POST.DESCRIPTION_SIZE' => 200, 'USER.DESCRIPTION_SIZE' => 200, + 'CATEGORY.SINGLE_REDIRECT' => FALSE, 'PAGE.SINGLE_REDIRECT' => FALSE, 'POST.SINGLE_REDIRECT' => FALSE, 'USER.SINGLE_REDIRECT' => FALSE, @@ -98,6 +104,7 @@ foreach([ # Set default configuration (for admin prefixes) #=============================================================================== foreach([ + 'ADMIN.CATEGORY.LIST_SIZE' => 12, # for 1/2/3-column grid layout 'ADMIN.PAGE.LIST_SIZE' => 12, # for 1/2/3-column grid layout 'ADMIN.POST.LIST_SIZE' => 12, # for 1/2/3-column grid layout 'ADMIN.USER.LIST_SIZE' => Application::get('USER.LIST_SIZE'), diff --git a/core/functions.php b/core/functions.php index dadaab8..ea4b7eb 100644 --- a/core/functions.php +++ b/core/functions.php @@ -1,8 +1,9 @@ <?php +use ORM\EntityInterface; +use ORM\Entities\Category; use ORM\Entities\Page; use ORM\Entities\Post; use ORM\Entities\User; -use ORM\EntityInterface; use Template\Template as Template; use Template\Factory as TemplateFactory; @@ -48,6 +49,37 @@ function generateUserNaviTemplate($current): Template { #=============================================================================== # Helper function to reduce duplicate code #=============================================================================== +function generateCategoryNaviTemplate($current): Template { + return generateNaviTemplate($current, Application::getCategoryURL(), 'Category'); +} + +#=============================================================================== +# Helper function to reduce duplicate code +#=============================================================================== +function generateCategoryItemTemplate(Category $Category, bool $is_root = FALSE): Template { + $CategoryRepository = Application::getRepository('Category'); + $PostRepository = Application::getRepository('Post'); + + foreach($CategoryRepository->findWithParents($Category->getID()) as $Category) { + $category_data = generateItemTemplateData($Category); + $category_list[] = $category_data; + } + + $Template = TemplateFactory::build('category/item'); + $Template->set('IS_ROOT', $is_root); + $Template->set('CATEGORY', $category_data ?? []); + $Template->set('CATEGORIES', $category_list ?? []); + $Template->set('COUNT', [ + 'POST' => $PostRepository->getCountByCategory($Category), + 'CHILDREN' => $CategoryRepository->getChildrenCount($Category) + ]); + + return $Template; +} + +#=============================================================================== +# Helper function to reduce duplicate code +#=============================================================================== function generatePageItemTemplate(Page $Page, User $User): Template { $Template = TemplateFactory::build('page/item'); $Template->set('PAGE', generateItemTemplateData($Page)); @@ -60,9 +92,18 @@ function generatePageItemTemplate(Page $Page, User $User): Template { # Helper function to reduce duplicate code #=============================================================================== function generatePostItemTemplate(Post $Post, User $User): Template { + $CategoryRepository = Application::getRepository('Category'); + + foreach($CategoryRepository->findWithParents($Post->get('category')) as $Category) { + $category_data = generateItemTemplateData($Category); + $categories[] = $category_data; + } + $Template = TemplateFactory::build('post/item'); $Template->set('POST', generateItemTemplateData($Post)); $Template->set('USER', generateItemTemplateData($User)); + $Template->set('CATEGORY', $category_data ?? []); + $Template->set('CATEGORIES', $categories ?? []); return $Template; } @@ -112,6 +153,24 @@ function generateItemTemplateData(EntityInterface $Entity): array { } #=============================================================================== +# Generate a nested tree from a category data array +#=============================================================================== +function generateCategoryDataTree(array $category_data, $root = 0): array { + foreach($category_data as &$category){ + $tree[intval($category['PARENT'])][] = &$category; + unset($category['PARENT']); + } + + foreach($category_data as &$category){ + if (isset($tree[$category['ID']])){ + $category['CHILDS'] = $tree[$category['ID']]; + } + } + + return $tree[$root] ?? []; +} + +#=============================================================================== # Generate pseudo GUID for entity #=============================================================================== function generatePseudoGUID(EntityInterface $Entity) { @@ -166,6 +225,9 @@ function parseContentTags(string $text): string { #=============================================================================== function parseEntityContent(EntityInterface $Entity): string { switch($class = get_class($Entity)) { + case 'ORM\Entities\Category': + $prefix = 'CATEGORY'; + break; case 'ORM\Entities\Page': $prefix = 'PAGE'; break; diff --git a/core/include/category/list.php b/core/include/category/list.php new file mode 100644 index 0000000..9bd2a68 --- /dev/null +++ b/core/include/category/list.php @@ -0,0 +1,69 @@ +<?php +#=============================================================================== +# Get instances +#=============================================================================== +$Language = Application::getLanguage(); + +#=============================================================================== +# Get repositories +#=============================================================================== +$CategoryRepository = Application::getRepository('Category'); +$PostRepository = Application::getRepository('Post'); + +#=============================================================================== +# Pagination +#=============================================================================== +$site_size = Application::get('CATEGORY.LIST_SIZE'); +$site_sort = Application::get('CATEGORY.LIST_SORT'); + +#$count = $CategoryRepository->getCount(['parent' => NULL]); +$count = $CategoryRepository->getCount(); +$lastSite = ceil($count / $site_size); + +$currentSite = HTTP::GET('site') ?? 1; +$currentSite = intval($currentSite); + +if($currentSite < 1 OR ($currentSite > $lastSite AND $lastSite > 0)) { + Application::error404(); +} + +#=============================================================================== +# Single redirect +#=============================================================================== +if(Application::get('CATEGORY.SINGLE_REDIRECT') === TRUE AND $count === 1) { + $Category = $CategoryRepository->getLast(); + HTTP::redirect(Application::getEntityURL($Category)); +} + +#=============================================================================== +# Get paginated category list +#=============================================================================== +$categories = $CategoryRepository->getPaginatedTree( + $site_size, + ($currentSite-1) * $site_size +); + +foreach($categories as $Category) { + $templates[] = generateCategoryItemTemplate($Category, TRUE); +} + +#=============================================================================== +# Build document +#=============================================================================== +$ListTemplate = Template\Factory::build('category/list'); +$ListTemplate->set('PAGINATION', [ + 'THIS' => $currentSite, + 'LAST' => $lastSite, + 'HTML' => generateCategoryNaviTemplate($currentSite) +]); +$ListTemplate->set('LIST', [ + 'CATEGORIES' => $templates ?? [] +]); + +$MainTemplate = Template\Factory::build('main'); +$MainTemplate->set('HTML', $ListTemplate); +$MainTemplate->set('HEAD', [ + 'NAME' => $Language->text('title_category_overview', $currentSite) +]); + +echo $MainTemplate; diff --git a/core/include/category/main.php b/core/include/category/main.php new file mode 100644 index 0000000..57f8625 --- /dev/null +++ b/core/include/category/main.php @@ -0,0 +1,134 @@ +<?php +#=============================================================================== +# Get repositories +#=============================================================================== +$CategoryRepository = Application::getRepository('Category'); +$PostRepository = Application::getRepository('Post'); +$UserRepository = Application::getRepository('User'); + +#=============================================================================== +# Try to find category (with parents) by slug URL or unique ID +#=============================================================================== +if(Application::get('CATEGORY.SLUG_URLS')) { + if(!$categories = $CategoryRepository->findWithParentsBy('slug', $param)) { + if($categories = $CategoryRepository->findWithParents($param)) { + $redirect_scheduled = TRUE; + } + } +} + +else { + if(!$categories = $CategoryRepository->findWithParents($param)) { + if($categories = $CategoryRepository->findWithParentsBy('slug', $param)) { + $redirect_scheduled = TRUE; + } + } +} + +#=============================================================================== +# Throw 404 error if category (with parents) could not be found +#=============================================================================== +if(!isset($categories)) { + Application::error404(); +} + +#=============================================================================== +# The last element represents the current category +#=============================================================================== +$Category = $categories[array_key_last($categories)]; + +#=============================================================================== +# If category with parents was found by alternative, redirect to correct URL +#=============================================================================== +if(isset($redirect_scheduled)) { + HTTP::redirect(Application::getEntityURL($Category)); +} + +#=============================================================================== +# Generate category template data (including parents) +#=============================================================================== +foreach($categories as $_Category) { + $category_list[] = generateItemTemplateData($_Category); +} + +#=============================================================================== +# Define data variable for current category +#=============================================================================== +$category_data = $category_list[array_key_last($category_list)]; + +#=============================================================================== +# Generate category children list +#=============================================================================== +$child_categories = $CategoryRepository->getAll( + ['parent' => $Category->getID()], + Application::get('CATEGORY.LIST_SORT') +); + +foreach($child_categories as $ChildCategory) { + $child_templates[] = generateCategoryItemTemplate($ChildCategory); +} + +#=============================================================================== +# Pagination (for posts in this category) +#=============================================================================== +$site_size = Application::get('POST.LIST_SIZE'); +$site_sort = Application::get('POST.LIST_SORT'); + +$count = $PostRepository->getCountByCategory($Category); +$lastSite = ceil($count / $site_size); + +$currentSite = HTTP::GET('site') ?? 1; +$currentSite = intval($currentSite); + +if($currentSite < 1 OR ($currentSite > $lastSite AND $lastSite > 0)) { + Application::error404(); +} + +#=============================================================================== +# Get paginated post list for this category +#=============================================================================== +$posts = $PostRepository->getAll( + ['category' => $Category->getID()], + $site_sort, + ($currentSite-1) * $site_size.','.$site_size +); + +foreach($posts as $Post) { + $User = $UserRepository->find($Post->get('user')); + $post_templates[] = generatePostItemTemplate($Post, $User); +} + +#=============================================================================== +# Build document +#=============================================================================== +$CategoryTemplate = Template\Factory::build('category/main'); +$CategoryTemplate->set('CATEGORY', $category_data); +$CategoryTemplate->set('CATEGORIES', $category_list ?? []); +$CategoryTemplate->set('COUNT', [ + 'POST' => $PostRepository->getCountByCategory($Category), + 'CHILDREN' => $CategoryRepository->getChildrenCount($Category) +]); +$CategoryTemplate->set('PAGINATION', [ + 'THIS' => $currentSite, + 'LAST' => $lastSite, + 'HTML' => generatePostNaviTemplate($currentSite) +]); +$CategoryTemplate->set('LIST', [ + 'POSTS' => $post_templates ?? [], + 'CATEGORIES' => $child_templates ?? [] +]); + +$MainTemplate = Template\Factory::build('main'); +$MainTemplate->set('TYPE', 'CATEGORY'); +$MainTemplate->set('CATEGORY', $category_data); +$MainTemplate->set('CATEGORIES', $category_list ?? []); +$MainTemplate->set('HTML', $CategoryTemplate); +$MainTemplate->set('HEAD', [ + 'NAME' => $category_data['ATTR']['NAME'], + 'DESC' => description($category_data['BODY']['HTML'](), + Application::get('CATEGORY.DESCRIPTION_SIZE')), + 'PERM' => $category_data['URL'], + 'OG_IMAGES' => $category_data['FILE']['LIST'] +]); + +echo $MainTemplate; diff --git a/core/include/post/main.php b/core/include/post/main.php index 86008f6..bfccc7b 100644 --- a/core/include/post/main.php +++ b/core/include/post/main.php @@ -2,6 +2,7 @@ #=============================================================================== # Get repositories #=============================================================================== +$CategoryRepository = Application::getRepository('Category'); $PostRepository = Application::getRepository('Post'); $UserRepository = Application::getRepository('User'); @@ -50,11 +51,27 @@ if($NextPost = $PostRepository->findNext($Post)) { } #=============================================================================== +# Generate category template data (including parents) +#=============================================================================== +foreach($CategoryRepository->findWithParents($Post->get('category')) as $Category) { + $category_list[] = generateItemTemplateData($Category); +} + +#=============================================================================== +# Define data variable for current category +#=============================================================================== +if(isset($category_list)) { + $category_data = $category_list[array_key_last($category_list)]; +} + +#=============================================================================== # Build document #=============================================================================== $PostTemplate = Template\Factory::build('post/main'); $PostTemplate->set('POST', $post_data); $PostTemplate->set('USER', $user_data); +$PostTemplate->set('CATEGORY', $category_data ?? []); +$PostTemplate->set('CATEGORIES', $category_list ?? []); $MainTemplate = Template\Factory::build('main'); $MainTemplate->set('TYPE', 'POST'); diff --git a/core/namespace/Application.php b/core/namespace/Application.php index 2329253..5690841 100644 --- a/core/namespace/Application.php +++ b/core/namespace/Application.php @@ -141,6 +141,13 @@ class Application { } #=============================================================================== + # Return absolute category URL + #=============================================================================== + public static function getCategoryURL($more = ''): string { + return self::getURL(self::get('CATEGORY.DIRECTORY')."/{$more}"); + } + + #=============================================================================== # Return absolute post URL #=============================================================================== public static function getPostURL($more = ''): string { @@ -188,6 +195,9 @@ class Application { #=============================================================================== public static function getEntityURL(EntityInterface $Entity) { switch($class = get_class($Entity)) { + case 'ORM\Entities\Category': + $attr = self::get('CATEGORY.SLUG_URLS') ? 'slug' : 'id'; + return self::getCategoryURL($Entity->get($attr).'/'); case 'ORM\Entities\Page': $attr = self::get('PAGE.SLUG_URLS') ? 'slug' : 'id'; return self::getPageURL($Entity->get($attr).'/'); diff --git a/core/namespace/ORM/Entities/Category.php b/core/namespace/ORM/Entities/Category.php new file mode 100644 index 0000000..17560a7 --- /dev/null +++ b/core/namespace/ORM/Entities/Category.php @@ -0,0 +1,14 @@ +<?php +namespace ORM\Entities; +use ORM\Entity; + +class Category extends Entity { + protected $id = FALSE; + protected $parent = FALSE; + protected $slug = FALSE; + protected $name = FALSE; + protected $body = FALSE; + protected $argv = FALSE; + protected $time_insert = FALSE; + protected $time_update = FALSE; +} diff --git a/core/namespace/ORM/Entities/Post.php b/core/namespace/ORM/Entities/Post.php index e6bff36..080f01f 100644 --- a/core/namespace/ORM/Entities/Post.php +++ b/core/namespace/ORM/Entities/Post.php @@ -3,12 +3,13 @@ namespace ORM\Entities; use ORM\Entity; class Post extends Entity { - protected $id = FALSE; - protected $user = FALSE; - protected $slug = FALSE; - protected $name = FALSE; - protected $body = FALSE; - protected $argv = FALSE; + protected $id = FALSE; + protected $user = FALSE; + protected $category = FALSE; + protected $slug = FALSE; + protected $name = FALSE; + protected $body = FALSE; + protected $argv = FALSE; protected $time_insert = FALSE; protected $time_update = FALSE; } diff --git a/core/namespace/ORM/Repositories/Category.php b/core/namespace/ORM/Repositories/Category.php new file mode 100644 index 0000000..bd7d060 --- /dev/null +++ b/core/namespace/ORM/Repositories/Category.php @@ -0,0 +1,138 @@ +<?php +namespace ORM\Repositories; +use ORM\Repository; +use ORM\EntityInterface; +use ORM\Entities\Category as CategoryEntity; + +class Category extends Repository { + public static function getTableName(): string { return 'category'; } + public static function getClassName(): string { return 'ORM\Entities\Category'; } + + #=============================================================================== + # Find category with parents based on primary key + #=============================================================================== + public function findWithParents($id): array { + return $this->findWithParentsBy('id', $id); + } + + #=============================================================================== + # Find category with parents based on specific field comparison + #=============================================================================== + public function findWithParentsBy(string $field, $value): array { + $query = 'WITH RECURSIVE tree AS ( + SELECT *, 0 AS _depth FROM %s WHERE %s %s UNION + SELECT c.*, _depth+1 FROM %s c, tree WHERE tree.parent = c.id + ) SELECT * FROM tree ORDER BY _depth DESC'; + + $table = static::getTableName(); + $check = is_null($value) ? 'IS NULL': '= ?'; + $query = sprintf($query, $table, $field, $check, $table); + + $Statement = $this->Database->prepare($query); + $Statement->execute([$value]); + + # TODO: Virtual column _depth shall not be fetched into the entity class + if($entities = $Statement->fetchAll($this->Database::FETCH_CLASS, static::getClassName())) { + $this->storeMultipleInstances($entities); + return $entities; + } + + return []; + } + + #=============================================================================== + # Get paginated category tree list + #=============================================================================== + public function getPaginatedTree(int $limit, int $offset = 0): array { + $query = 'WITH RECURSIVE tree AS ( + SELECT *, name AS _depth FROM %s WHERE parent IS NULL UNION + SELECT c.*, CONCAT(_depth, "/", c.name) AS _depth FROM %s c INNER JOIN tree ON tree.id = c.parent + ) SELECT * FROM tree ORDER BY _depth %s'; + + $_limit = "LIMIT $limit"; + + if($offset) { + $_limit = "LIMIT $offset,$limit"; + } + + $table = static::getTableName(); + $query = sprintf($query, $table, $table, $_limit); + + $Statement = $this->Database->prepare($query); + $Statement->execute(); + + if($entities = $Statement->fetchAll($this->Database::FETCH_CLASS, static::getClassName())) { + $this->storeMultipleInstances($entities); + return $entities; + } + + return []; + } + + #=============================================================================== + # Get children count of $Category + #=============================================================================== + public function getChildrenCount(CategoryEntity $Category): int { + $query = 'WITH RECURSIVE tree AS ( + SELECT * FROM %s WHERE id = ? UNION + SELECT c.* FROM %s c, tree WHERE tree.id = c.parent + ) SELECT COUNT(id) FROM tree WHERE id != ?'; + + $query = sprintf($query, + static::getTableName(), + static::getTableName() + ); + + $Statement = $this->Database->prepare($query); + $Statement->execute([$Category->getID(), $Category->getID()]); + + return $Statement->fetchColumn(); + } + + #=============================================================================== + # Update category (and check for parent/child circular loops) + #=============================================================================== + public function update(EntityInterface $Entity): bool { + # Entity parent might have changed *in memory*, so we re-fetch the original + # parent of the entity from the database and save it in a variable. + # TODO: Repository/Entity class should have a mechanism to detect changes! + $query = 'SELECT parent FROM %s WHERE id = ?'; + $query = sprintf($query, static::getTableName()); + + $Statement = $this->Database->prepare($query); + $Statement->execute([$Entity->getID()]); + + $parent = $Statement->fetchColumn(); + + # If parent is unchanged, circular loop check is not needed. + if($parent === $Entity->get('parent')) { + return parent::update($Entity); + } + + $query = 'SELECT parent FROM %s WHERE id = ?'; + $query = sprintf($query, static::getTableName()); + + $Statement = $this->Database->prepare($query); + $_parent = $Entity->get('parent'); + + # Fetch the parent of the *new* parent category and let the while loop run through + # the tree until either a parent of "NULL" was found or if the new parent category + # is a *child* of the *current* category which would cause a circular loop. + while($Statement->execute([$_parent]) && $_parent = $Statement->fetchColumn()) { + if($_parent == $Entity->get('id')) { + # Set parent of the *new* parent category to the *original* parent category + # of the *current* category (one level up) to prevent a circular loop. + $query = 'UPDATE %s SET parent = ? WHERE id = ?'; + $query = sprintf($query, static::getTableName()); + + $UpdateStatement = $this->Database->prepare($query); + $UpdateStatement->execute([$parent, $Entity->get('parent')]); + break; + } else if($_parent === NULL) { + break; + } + } + + return parent::update($Entity); + } +} diff --git a/core/namespace/ORM/Repositories/Post.php b/core/namespace/ORM/Repositories/Post.php index 8eac12f..d6328e6 100644 --- a/core/namespace/ORM/Repositories/Post.php +++ b/core/namespace/ORM/Repositories/Post.php @@ -2,6 +2,7 @@ namespace ORM\Repositories; use ORM\Repository; use ORM\Entities\User; +use ORM\Entities\Category; class Post extends Repository { public static function getTableName(): string { return 'post'; } @@ -16,4 +17,15 @@ class Post extends Repository { return $Statement->fetchColumn(); } + + # TODO: This only gets the count of the direct category, not its children + public function getCountByCategory(Category $Category): int { + $query = 'SELECT COUNT(id) FROM %s WHERE category = ?'; + $query = sprintf($query, static::getTableName()); + + $Statement = $this->Database->prepare($query); + $Statement->execute([$Category->getID()]); + + return $Statement->fetchColumn(); + } } @@ -7,6 +7,7 @@ require 'core/application.php'; #=============================================================================== # Item base directory paths #=============================================================================== +$CATEGORYPATH = Application::get('CATEGORY.DIRECTORY'); $PAGEPATH = Application::get('PAGE.DIRECTORY'); $POSTPATH = Application::get('POST.DIRECTORY'); $USERPATH = Application::get('USER.DIRECTORY'); @@ -14,6 +15,7 @@ $USERPATH = Application::get('USER.DIRECTORY'); #=============================================================================== # ROUTE: Item #=============================================================================== +Router::add("{$CATEGORYPATH}/([^/]+)/", function($param) { require 'core/include/category/main.php'; }); Router::add("{$PAGEPATH}/([^/]+)/", function($param) { require 'core/include/page/main.php'; }); Router::add("{$POSTPATH}/([^/]+)/", function($param) { require 'core/include/post/main.php'; }); Router::add("{$USERPATH}/([^/]+)/", function($param) { require 'core/include/user/main.php'; }); @@ -21,6 +23,7 @@ Router::add("{$USERPATH}/([^/]+)/", function($param) { require 'core/include/use #=============================================================================== # ROUTE: Item overview #=============================================================================== +Router::add("{$CATEGORYPATH}/", function() { require 'core/include/category/list.php'; }); Router::add("{$PAGEPATH}/", function() { require 'core/include/page/list.php'; }); Router::add("{$POSTPATH}/", function() { require 'core/include/post/list.php'; }); Router::add("{$USERPATH}/", function() { require 'core/include/user/list.php'; }); @@ -28,6 +31,7 @@ Router::add("{$USERPATH}/", function() { require 'core/include/user/list.php'; } #=============================================================================== # REDIRECT: Item (trailing slash) #=============================================================================== +Router::addRedirect("{$CATEGORYPATH}/([^/]+)", Application::getCategoryURL('$1/')); Router::addRedirect("{$PAGEPATH}/([^/]+)", Application::getPageURL('$1/')); Router::addRedirect("{$POSTPATH}/([^/]+)", Application::getPostURL('$1/')); Router::addRedirect("{$USERPATH}/([^/]+)", Application::getUserURL('$1/')); @@ -35,6 +39,7 @@ Router::addRedirect("{$USERPATH}/([^/]+)", Application::getUserURL('$1/')); #=============================================================================== # REDIRECT: Item overview (trailing slash) #=============================================================================== +Router::addRedirect("{$CATEGORYPATH}", Application::getCategoryURL()); Router::addRedirect("{$PAGEPATH}", Application::getPageURL()); Router::addRedirect("{$POSTPATH}", Application::getPostURL()); Router::addRedirect("{$USERPATH}", Application::getUserURL()); diff --git a/theme/admin/html/category/delete.php b/theme/admin/html/category/delete.php new file mode 100644 index 0000000..635e9a8 --- /dev/null +++ b/theme/admin/html/category/delete.php @@ -0,0 +1,6 @@ +<main id="main-content"> +<h1><i class="fa fa-trash-o"></i><?=$Language->text('delete_category')?></h1> +<p><?=$Language->text('delete_category_desc')?></p> + +<?=$HTML?> +</main> diff --git a/theme/admin/html/category/form.php b/theme/admin/html/category/form.php new file mode 100644 index 0000000..2cb7b20 --- /dev/null +++ b/theme/admin/html/category/form.php @@ -0,0 +1,112 @@ +<?php +function categorySelectList($category_tree, $selected = NULL, $current = NULL, $prefix = '') { + foreach($category_tree as $category) { + $option = '<option value="%s"%s>%s%s [%d]</option>'; + $select = ($category['ID'] == $selected) ? ' selected' : ''; + $select = ($category['ID'] == $current) ? ' disabled' : $select; + + printf($option, $category['ID'], $select, $prefix, escapeHTML($category['NAME']), $category['ID']); + + if(isset($category['CHILDS'])) { + # If there are children, call self and pass children array. + (__FUNCTION__)($category['CHILDS'], $selected, $current, $prefix.'– '); + } + } +} +?> +<?php if($FORM['INFO']): ?> + <div id="message-list-wrapper"> + <ul id="message-list"> + <?php foreach($FORM['INFO'] as $message): ?> + <li><?=$message?></li> + <?php endforeach ?> + </ul> + </div> +<?php endif ?> + +<form action="" method="POST"> + <input type="hidden" name="token" value="<?=$FORM['TOKEN']?>" /> + +<?php if($FORM['TYPE'] !== 'DELETE'): ?> + <div class="form-grid"> + <label for="form_name"> + <i class="fa fa-tag"></i><?=$Language->text('label_name')?></label> + + <div class="form-grid-item first"> + <input id="form_name" name="name" value="<?=escapeHTML($FORM['DATA']['NAME'])?>" /> + </div> + + <label for="form_slug"> + <i class="fa fa-link"></i><?=$Language->text('label_slug')?></label> + + <div class="form-grid-item"> + <input id="form_slug" name="slug" value="<?=escapeHTML($FORM['DATA']['SLUG'])?>" /> + </div> + + <label for="form_category_parent"> + <i class="fa fa-tags"></i><?=$Language->text('label_category_parent')?></label> + + <div class="form-grid-item"> + <select id="form_category_parent" name="parent"> + <option value="">[ –– <?=$Language->text('label_category')?> –– ]</option> + <?=categorySelectList($FORM['CATEGORY_TREE'], $FORM['DATA']['PARENT'], $FORM['DATA']['ID']);?> + </select> + </div> + + <label for="form_time_insert"> + <i class="fa fa-clock-o"></i><?=$Language->text('label_insert')?></label> + + <div class="form-grid-item"> + <input id="form_time_insert" name="time_insert" placeholder="YYYY-MM-DD HH:II:SS" value="<?=escapeHTML($FORM['DATA']['TIME_INSERT'])?>" /> + </div> + + <label for="form_time_update"> + <i class="fa fa-pencil"></i><?=$Language->text('label_update')?></label> + + <div class="form-grid-item"> + <input id="form_time_update" name="time_update" placeholder="<?=escapeHTML($FORM['DATA']['TIME_UPDATE'] ?: 'CURRENT_TIMESTAMP')?>" value="" /> + </div> + </div> + + <div id="content-editor-wrapper" class="form-border-box"> + <div id="button-list-wrapper"> + <ul id="markdown-list" class="button-list markdown"> + <li data-markdown="bold" class="fa fa-bold" title="<?=$Language->text('markdown_bold')?>"></li> + <li data-markdown="italic" class="fa fa-italic" title="<?=$Language->text('markdown_italic')?>"></li> + <li data-markdown="heading" class="fa fa-header" title="<?=$Language->text('markdown_heading')?>"></li> + <li data-markdown="link" class="fa fa-link" title="<?=$Language->text('markdown_link')?>"></li> + <li data-markdown="image" class="fa fa-picture-o" title="<?=$Language->text('markdown_image')?>"></li> + <li data-markdown="code" class="fa fa-code" title="<?=$Language->text('markdown_code')?>"></li> + <li data-markdown="quote" class="fa fa-quote-right" title="<?=$Language->text('markdown_quote')?>"></li> + <li data-markdown="list_ul" class="fa fa-list-ul" title="<?=$Language->text('markdown_list_ul')?>"></li> + <li data-markdown="list_ol" class="fa fa-list-ol" title="<?=$Language->text('markdown_list_ol')?>"></li> + </ul> + </div> + <textarea id="content-editor" name="body" placeholder="[…]"><?=escapeHTML($FORM['DATA']['BODY'])?></textarea> + </div> + <div id="emoticon-list-wrapper" class="form-border-box background padding"> + <ul id="emoticon-list" class="button-list emoticons"> + <?php foreach(getUnicodeEmoticons() as $emoticon => $explanation):?> + <li data-emoticon="<?=$emoticon?>" title="<?=$explanation?>"><?=$emoticon?></li> + <?php endforeach; ?> + </ul> + </div> + <div class="form-border-box background padding"> + <input id="form_argv" name="argv" maxlength="250" placeholder="[ARGUMENT_ONE=foo|ARGUMENT_TWO=bar …]" value="<?=escapeHTML($FORM['DATA']['ARGV'])?>" /> + </div> +<?php else: ?> + <div class="form-border-box background padding"> + <?=$HTML?> + </div> +<?php endif; ?> + + <div class="form-border-box background padding"> + <?php if($FORM['TYPE'] === 'INSERT'): ?> + <input id="insert-button" type="submit" name="insert" value="<?=$Language->text('insert')?>" /> + <?php elseif($FORM['TYPE'] === 'UPDATE'): ?> + <input id="update-button" type="submit" name="update" value="<?=$Language->text('update')?>" /> + <?php elseif($FORM['TYPE'] === 'DELETE'): ?> + <input id="delete-button" type="submit" name="delete" value="<?=$Language->text('delete')?>" data-text="<?=$Language->text('sure')?>" /> + <?php endif; ?> + </div> +</form> diff --git a/theme/admin/html/category/index.php b/theme/admin/html/category/index.php new file mode 100644 index 0000000..e2c35a4 --- /dev/null +++ b/theme/admin/html/category/index.php @@ -0,0 +1,15 @@ +<main id="main-content" class="wide"> +<h1><i class="fa fa-tags"></i><?=$Language->text('category_overview')?></h1> +<p class="actions-before"><?=$Language->text('overview_category_desc')?></p> +<ul class="actions"> + <li><a href="<?=Application::getAdminURL('category/insert.php')?>" title="<?=$Language->text('insert_category')?>"><i class="fa fa-plus"></i><?=$Language->text('insert')?></a></li> +</ul> + +<div class="item-container category grid"> + <?php foreach($LIST['CATEGORIES'] as $category): ?> + <?php echo $category; ?> + <?php endforeach; ?> +</div> + +<?=$PAGINATION['HTML']?> +</main> diff --git a/theme/admin/html/category/insert.php b/theme/admin/html/category/insert.php new file mode 100644 index 0000000..be9fdfe --- /dev/null +++ b/theme/admin/html/category/insert.php @@ -0,0 +1,6 @@ +<main id="main-content"> +<h1><i class="fa fa-pencil-square-o"></i><?=$Language->text('insert_category')?></h1> +<p><?=$Language->text('insert_category_desc')?></p> + +<?=$HTML?> +</main> diff --git a/theme/admin/html/category/item.php b/theme/admin/html/category/item.php new file mode 100644 index 0000000..05212e7 --- /dev/null +++ b/theme/admin/html/category/item.php @@ -0,0 +1,33 @@ +<article class="item"> + <header> + <h2><i class="fa fa-tag"></i><?=escapeHTML($CATEGORY['ATTR']['NAME'])?></h2> + <div> + <span class="brackets item-id">#<?=$CATEGORY['ATTR']['ID']?></span> + <time class="brackets" datetime="<?=$CATEGORY['ATTR']['TIME_INSERT']?>"><?=parseDatetime($CATEGORY['ATTR']['TIME_INSERT'], $Language->text('date_format'))?></time> + <span class="brackets"><?=$Language->text('posts')?>: <?=$COUNT['POST']?></span> + <span class="brackets"><?=$Language->text('categories')?>: <?=$COUNT['CHILDREN']?></span> + </div> + </header> + <blockquote cite="<?=$CATEGORY['URL']?>"> + <?php if(isset($CATEGORY['FILE']['LIST'][0])): ?> + <img class="item-image" src="<?=$CATEGORY['FILE']['LIST'][0]?>" alt="" /> + <?php endif; ?> + <p><?=excerpt($CATEGORY['BODY']['HTML']())?></p> + </blockquote> + + <?php if($CATEGORY['ARGV']): ?> + <ul class="arguments"> + <?php foreach($CATEGORY['ARGV'] as $argument => $value): ?> + <li><strong><?=$argument?>:</strong> <span><?=escapeHTML($value)?></span></li> + <?php endforeach; ?> + </ul> + <?php endif; ?> + + <footer> + <ul> + <li><a href="<?=$CATEGORY['URL']?>" target="_blank" title="<?=$Language->text('select_category')?>"><i class="fa fa-external-link"></i><span class="hidden"><?=$Language->text('select_category')?></span></a></li> + <li><a href="<?=Application::getAdminURL("category/update.php?id={$CATEGORY['ATTR']['ID']}")?>" title="<?=$Language->text('update_category')?>"><i class="fa fa-pencil-square-o"></i><span class="hidden"><?=$Language->text('update_category')?></span></a></li> + <li><a href="<?=Application::getAdminURL("category/delete.php?id={$CATEGORY['ATTR']['ID']}")?>" title="<?=$Language->text('delete_category')?>"><i class="fa fa-trash-o"></i><span class="hidden"><?=$Language->text('delete_category')?></span></a></li> + </ul> + </footer> +</article> diff --git a/theme/admin/html/category/update.php b/theme/admin/html/category/update.php new file mode 100644 index 0000000..fefaa18 --- /dev/null +++ b/theme/admin/html/category/update.php @@ -0,0 +1,6 @@ +<main id="main-content"> +<h1><i class="fa fa-pencil-square-o"></i><?=$Language->text('update_category')?></h1> +<p><?=$Language->text('update_category_desc')?></p> + +<?=$HTML?> +</main> diff --git a/theme/admin/html/main.php b/theme/admin/html/main.php index 9a5de23..45cdc41 100644 --- a/theme/admin/html/main.php +++ b/theme/admin/html/main.php @@ -26,6 +26,7 @@ <?php if(Application::isAuthenticated()): ?> <li><a href="<?=Application::getAdminURL()?>" title="<?=$Language->text('overview_dashboard_text')?>"><i class="fa fa-dashboard"></i><span>Dashboard</span></a></li> <li><a href="<?=Application::getAdminURL('post/')?>" title="<?=$Language->text('post_overview')?>"><i class="fa fa-newspaper-o"></i><span><?=$Language->text('posts')?></span></a></li> + <li><a href="<?=Application::getAdminURL('category/')?>" title="<?=$Language->text('category_overview')?>"><i class="fa fa-tags"></i><span><?=$Language->text('categories')?></span></a></li> <li><a href="<?=Application::getAdminURL('page/')?>" title="<?=$Language->text('page_overview')?>"><i class="fa fa-folder-open"></i><span><?=$Language->text('pages')?></span></a></li> <li><a href="<?=Application::getAdminURL('user/')?>" title="<?=$Language->text('user_overview')?>"><i class="fa fa-users"></i><span><?=$Language->text('users')?></span></a></li> <li><a href="<?=Application::getAdminURL('database.php')?>" title="<?=$Language->text('overview_database_text')?>"><i class="fa fa-database"></i><span><?=$Language->text('overview_database_text')?></span></a></li> diff --git a/theme/admin/html/post/form.php b/theme/admin/html/post/form.php index 346d6f4..4ae07bd 100644 --- a/theme/admin/html/post/form.php +++ b/theme/admin/html/post/form.php @@ -1,3 +1,18 @@ +<?php +function categorySelectList($category_tree, $selected = NULL, $prefix = '') { + foreach($category_tree as $category) { + $option = '<option value="%s"%s>%s%s [%d]</option>'; + $select = ($category['ID'] == $selected) ? ' selected' : ''; + + printf($option, $category['ID'], $select, $prefix, escapeHTML($category['NAME']), $category['ID']); + + if(isset($category['CHILDS'])) { + # If there are children, call self and pass children array. + (__FUNCTION__)($category['CHILDS'], $selected, $prefix.'– '); + } + } +} +?> <?php if($FORM['INFO']): ?> <div id="message-list-wrapper"> <ul id="message-list"> @@ -16,7 +31,7 @@ <label for="form_name"> <i class="fa fa-newspaper-o"></i><?=$Language->text('label_name')?></label> - <div class="form-grid-item first"> + <div class="form-grid-item"> <input id="form_name" name="name" value="<?=escapeHTML($FORM['DATA']['NAME'])?>" /> </div> @@ -27,6 +42,16 @@ <input id="form_slug" name="slug" value="<?=escapeHTML($FORM['DATA']['SLUG'])?>" /> </div> + <label for="form_category"> + <i class="fa fa-tag"></i><?=$Language->text('label_category')?></label> + + <div class="form-grid-item"> + <select id="form_category" name="category"> + <option value="">[ –– <?=$Language->text('label_category')?> –– ]</option> + <?=categorySelectList($FORM['CATEGORY_TREE'], $FORM['DATA']['CATEGORY']);?> + </select> + </div> + <label for="form_user"> <i class="fa fa-user"></i><?=$Language->text('label_user')?></label> diff --git a/theme/admin/html/post/item.php b/theme/admin/html/post/item.php index ae06752..41991d5 100644 --- a/theme/admin/html/post/item.php +++ b/theme/admin/html/post/item.php @@ -4,6 +4,9 @@ <div> <span class="brackets item-id">#<?=$POST['ATTR']['ID']?></span> <a class="brackets" href="<?=Application::getAdminURL("user/update.php?id={$USER['ATTR']['ID']}")?>" title="<?=$Language->text('update_user')?>"><?=escapeHTML($USER['ATTR']['FULLNAME'])?></a> + <?php if($CATEGORY): ?> + <a class="brackets" href="<?=Application::getAdminURL("category/update.php?id={$CATEGORY['ATTR']['ID']}")?>" title="<?=$Language->text('update_category')?>"><?=escapeHTML($CATEGORY['ATTR']['NAME'])?></a> + <?php endif ?> <time class="brackets" datetime="<?=$POST['ATTR']['TIME_INSERT']?>"><?=parseDatetime($POST['ATTR']['TIME_INSERT'], $Language->text('date_format'))?></time> </div> </header> diff --git a/theme/default/html/category/item.php b/theme/default/html/category/item.php new file mode 100644 index 0000000..9435581 --- /dev/null +++ b/theme/default/html/category/item.php @@ -0,0 +1,27 @@ +<?php +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +# Category Item Template [Thomas Lange <code@nerdmind.de>] # +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +# # +# [see documentation] # +# # +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +?> +<article class="item"> + <header> + <h2 class="fa fa-tag"> + <a title="<?=$Language->text('select_category')?>: »<?=escapeHTML($CATEGORY['ATTR']['NAME'])?>«" href="<?=$CATEGORY['URL']?>"> + <?=escapeHTML($CATEGORY['ATTR']['NAME'])?> + </a> + </h2> + <span class="brackets info"> + <?=$Language->text('posts')?>: <?=$COUNT['POST']?> – + <?=$Language->text('categories')?>: <?=$COUNT['CHILDREN']?> + </span> + </header> + <?php if($IS_ROOT): ?> + <blockquote cite="<?=$CATEGORY['URL']?>"> + <?=$CATEGORY['BODY']['HTML']()?> + </blockquote> + <?php endif ?> +</article> diff --git a/theme/default/html/category/list.php b/theme/default/html/category/list.php new file mode 100644 index 0000000..9fffb2b --- /dev/null +++ b/theme/default/html/category/list.php @@ -0,0 +1,23 @@ +<?php +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +# Category List Template [Thomas Lange <code@nerdmind.de>] # +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +# # +# [see documentation] # +# # +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +?> +<h1><i class="fa fa-tags"></i><?=$Language->text('category_overview')?></h1> +<p><?=$Language->text('category_list_title', $PAGINATION['THIS'])?></p> + +<?php if($LIST['CATEGORIES']): ?> + <div class="item-container category"> + <?php foreach($LIST['CATEGORIES'] as $category): ?> + <?php echo $category; ?> + <?php endforeach; ?> + </div> +<?php else: ?> + <p><?=$Language->text('category_list_empty')?></p> +<?php endif ?> + +<?=$PAGINATION['HTML']?> diff --git a/theme/default/html/category/main.php b/theme/default/html/category/main.php new file mode 100644 index 0000000..86b50e0 --- /dev/null +++ b/theme/default/html/category/main.php @@ -0,0 +1,44 @@ +<?php +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +# Category Main Template [Thomas Lange <code@nerdmind.de>] # +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +# # +# [see documentation] # +# # +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%# +?> +<h1 id="category-heading"><i class="fa fa-tag"></i><?=escapeHTML($CATEGORY['ATTR']['NAME'])?></h1> +<?php if($CATEGORIES && array_pop($CATEGORIES)): ?> + <ul class="category-heading-list"> + <?php foreach($CATEGORIES as $category): ?> + <li><a href="<?=$category['URL']?>"><?=escapeHTML($category['ATTR']['NAME'])?></a></li> + <?php endforeach ?> + </ul> +<?php endif ?> + +<div id="category-body"> + <?=$CATEGORY['BODY']['HTML']()?> +</div> + +<?php if(!empty($LIST['CATEGORIES'])): ?> + <div class="item-container category"> + <?php foreach($LIST['CATEGORIES'] as $category): ?> + <?php echo $category; ?> + <?php endforeach; ?> + </div> +<?php endif ?> + +<h2 id="category-posts-heading"><i class="fa fa-newspaper-o"></i><?=$Language->text('posts')?></h2> +<div id="category-posts-page"><?=$Language->text('category_posts_page', $PAGINATION['THIS'])?></div> +<?php if($LIST['POSTS']): ?> + <div class="item-container post"> + <?php foreach($LIST['POSTS'] as $post): ?> + <?php echo $post; ?> + <?php endforeach; ?> + </div> + <?=$PAGINATION['HTML']?> +<?php else: ?> + <div class="item-container post"> + <?=$Language->text('category_empty')?> + </div> +<?php endif ?> diff --git a/theme/default/html/main.php b/theme/default/html/main.php index e84618b..5a067ef 100644 --- a/theme/default/html/main.php +++ b/theme/default/html/main.php @@ -74,6 +74,11 @@ $BLOGMETA_DESC = escapeHTML($BLOGMETA['DESC']); </a> </li> <li> + <a href="<?=Application::getCategoryURL()?>" title="<?=$Language->text('category_overview')?>"> + <i class="fa fa-tags"></i><?=$Language->text('categories')?> + </a> + </li> + <li> <a href="<?=Application::getPageURL()?>" title="<?=$Language->text('page_overview')?>"> <i class="fa fa-file-text-o"></i><?=$Language->text('pages')?> </a> diff --git a/theme/default/html/post/main.php b/theme/default/html/post/main.php index 4b84ed9..328a614 100644 --- a/theme/default/html/post/main.php +++ b/theme/default/html/post/main.php @@ -17,6 +17,14 @@ $time = "<time datetime=\"{$POST['ATTR']['TIME_INSERT']}\" title=\"".parseDateti <?=$POST['BODY']['HTML']()?> </div> +<?php if($CATEGORIES): ?> + <ul class="category-list fa fa-tag"> + <?php foreach($CATEGORIES as $category): ?> + <li><a href="<?=$category['URL']?>"><?=escapeHTML($category['ATTR']['NAME'])?></a></li> + <?php endforeach ?> + </ul> +<?php endif ?> + <section id="site-navi"> <?php if($POST['PREV']): ?> diff --git a/theme/default/rsrc/css/main.css b/theme/default/rsrc/css/main.css index 11cae12..e49412b 100644 --- a/theme/default/rsrc/css/main.css +++ b/theme/default/rsrc/css/main.css @@ -281,9 +281,11 @@ a.brackets:before, a.brackets:after { overflow: hidden; margin: 0.5rem 0; } +.item-container { + clear: both; +} .item header { padding: 0.25rem 1rem; - border-bottom: 0.05rem solid #AAA; overflow: hidden; text-transform: uppercase; } @@ -303,6 +305,7 @@ a.brackets:before, a.brackets:after { margin: 0; padding: 0 1rem; font-family: inherit; + border-top: 0.05rem solid #AAA; } .item blockquote img { display: block; @@ -322,6 +325,49 @@ a.brackets:before, a.brackets:after { } /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Category list +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +#category-heading, +#category-posts-heading { + float: left; +} + +#category-posts-page { + float: right; + font-size: 0.6rem; +} + +#category-body { + clear: both; +} + +.category-list, +.category-heading-list { + margin: 0; + padding: 0; + list-style: none; + font-size: 0.6rem; + text-align: center; +} +.category-list li, +.category-heading-list li { + display: inline-block; +} +.category-list li + li:before, +.category-heading-list li + li:before { + content: " ➜ "; +} +.category-list a, +.category-heading-list a { + text-decoration: underline; +} + +.category-heading-list { + text-align: left; + float: right; +} + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Responsive ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ #toogle-nav { diff --git a/theme/default/rsrc/css/main.scss b/theme/default/rsrc/css/main.scss index ba6e823..4ea6a2b 100644 --- a/theme/default/rsrc/css/main.scss +++ b/theme/default/rsrc/css/main.scss @@ -303,9 +303,12 @@ a.brackets:before, a.brackets:after { overflow: hidden; margin: 0.5rem 0; + &-container { + clear: both; + } + header { padding: 0.25rem 1rem; - border-bottom: 0.05rem solid #AAA; overflow: hidden; text-transform: uppercase; @@ -329,6 +332,7 @@ a.brackets:before, a.brackets:after { margin: 0; padding: 0 1rem; font-family: inherit; + border-top: 0.05rem solid #AAA; img { display: block; @@ -351,6 +355,50 @@ a.brackets:before, a.brackets:after { } /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +# Categories +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ +#category-heading, +#category-posts-heading { + float: left; +} + +#category-posts-page { + float: right; + font-size: 0.6rem; +} + +#category-body { + clear: both; +} + +.category-list, +.category-heading-list { + margin: 0; + padding: 0; + list-style: none; + font-size: 0.6rem; + text-align: center; + + li { + display: inline-block; + + + li:before { + content: ' ➜ '; + } + } + + a { + text-decoration: underline; + } +} + +.category-heading-list { + + text-align: left; + float: right; +} + +/*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ # Responsive ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/ #toogle-nav { |