feat(theme): add [roi_post_grid] shortcode for static pages
- Create PostGridShortcodeRegistrar for WordPress shortcode registration - Implement RenderPostGridUseCase following Clean Architecture - Add PostGridQueryBuilder for custom WP_Query construction - Add PostGridShortcodeRenderer for HTML/CSS generation - Register shortcode in DIContainer with proper DI - Add shortcode usage guide in post-grid admin panel - Fix sidebar layout: add hide_for_logged_in check to wrapper visibility Shortcode attributes: category, tag, author, posts_per_page, columns, show_pagination, show_thumbnail, show_excerpt, show_meta, etc. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
109
Shared/Infrastructure/Query/PostGridQueryBuilder.php
Normal file
109
Shared/Infrastructure/Query/PostGridQueryBuilder.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ROITheme\Shared\Infrastructure\Query;
|
||||
|
||||
use ROITheme\Shared\Domain\Contracts\PostGridQueryBuilderInterface;
|
||||
|
||||
/**
|
||||
* Implementacion de PostGridQueryBuilderInterface
|
||||
*
|
||||
* RESPONSABILIDAD: Construir WP_Query a partir de parametros de filtro.
|
||||
* No genera HTML ni obtiene settings.
|
||||
*
|
||||
* @package ROITheme\Shared\Infrastructure\Query
|
||||
*/
|
||||
final class PostGridQueryBuilder implements PostGridQueryBuilderInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(array $params): \WP_Query
|
||||
{
|
||||
$args = [
|
||||
'post_type' => 'post',
|
||||
'post_status' => 'publish',
|
||||
'posts_per_page' => (int) ($params['posts_per_page'] ?? 9),
|
||||
'orderby' => $params['orderby'] ?? 'date',
|
||||
'order' => $params['order'] ?? 'DESC',
|
||||
'paged' => (int) ($params['paged'] ?? 1),
|
||||
];
|
||||
|
||||
// Offset
|
||||
if (!empty($params['offset'])) {
|
||||
$args['offset'] = (int) $params['offset'];
|
||||
}
|
||||
|
||||
// Filtro por categoria(s)
|
||||
if (!empty($params['category'])) {
|
||||
$args['category_name'] = $this->sanitizeSlugs($params['category']);
|
||||
}
|
||||
|
||||
// Excluir categoria(s)
|
||||
if (!empty($params['exclude_category'])) {
|
||||
$excludeIds = $this->getCategoryIds($params['exclude_category']);
|
||||
if (!empty($excludeIds)) {
|
||||
$args['category__not_in'] = $excludeIds;
|
||||
}
|
||||
}
|
||||
|
||||
// Filtro por tag(s)
|
||||
if (!empty($params['tag'])) {
|
||||
$args['tag'] = $this->sanitizeSlugs($params['tag']);
|
||||
}
|
||||
|
||||
// Filtro por autor
|
||||
if (!empty($params['author'])) {
|
||||
$author = $params['author'];
|
||||
if (is_numeric($author)) {
|
||||
$args['author'] = (int) $author;
|
||||
} else {
|
||||
$user = get_user_by('login', $author);
|
||||
if ($user) {
|
||||
$args['author'] = $user->ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Excluir posts por ID
|
||||
if (!empty($params['exclude_posts'])) {
|
||||
$excludeIds = array_map('intval', explode(',', $params['exclude_posts']));
|
||||
$excludeIds = array_filter($excludeIds, fn($id) => $id > 0);
|
||||
if (!empty($excludeIds)) {
|
||||
$args['post__not_in'] = $excludeIds;
|
||||
}
|
||||
}
|
||||
|
||||
return new \WP_Query($args);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitiza slugs separados por coma
|
||||
*/
|
||||
private function sanitizeSlugs(string $slugs): string
|
||||
{
|
||||
$parts = explode(',', $slugs);
|
||||
$sanitized = array_map('sanitize_title', $parts);
|
||||
return implode(',', $sanitized);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtiene IDs de categorias desde slugs
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
private function getCategoryIds(string $slugs): array
|
||||
{
|
||||
$parts = explode(',', $slugs);
|
||||
$ids = [];
|
||||
|
||||
foreach ($parts as $slug) {
|
||||
$term = get_term_by('slug', trim($slug), 'category');
|
||||
if ($term instanceof \WP_Term) {
|
||||
$ids[] = $term->term_id;
|
||||
}
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user