Finished category related content management

This commit is contained in:
franck
2013-09-17 13:50:01 +02:00
parent d097851522
commit 3d78af8b97
16 changed files with 486 additions and 24 deletions

View File

@@ -36,6 +36,10 @@ use Thelia\Core\Event\CategoryDeleteEvent;
use Thelia\Model\ConfigQuery;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\CategoryToggleVisibilityEvent;
use Thelia\Core\Event\CategoryAddContentEvent;
use Thelia\Core\Event\CategoryDeleteContentEvent;
use Thelia\Model\CategoryAssociatedContent;
use Thelia\Model\CategoryAssociatedContentQuery;
class Category extends BaseAction implements EventSubscriberInterface
{
@@ -147,6 +151,33 @@ class Category extends BaseAction implements EventSubscriberInterface
}
}
public function addContent(CategoryAddContentEvent $event) {
if (CategoryAssociatedContentQuery::create()
->filterByContentId($event->getContentId())
->filterByCategory($event->getCategory())->count() <= 0) {
$content = new CategoryAssociatedContent();
$content
->setCategory($event->getCategory())
->setContentId($event->getContentId())
->save()
;
}
}
public function removeContent(CategoryDeleteContentEvent $event) {
$content = CategoryAssociatedContentQuery::create()
->filterByContentId($event->getContentId())
->filterByCategory($event->getCategory())->findOne()
;
if ($content !== null) $content->delete();
}
/**
* {@inheritDoc}
*/
@@ -157,7 +188,12 @@ class Category extends BaseAction implements EventSubscriberInterface
TheliaEvents::CATEGORY_UPDATE => array("update", 128),
TheliaEvents::CATEGORY_DELETE => array("delete", 128),
TheliaEvents::CATEGORY_TOGGLE_VISIBILITY => array("toggleVisibility", 128),
TheliaEvents::CATEGORY_UPDATE_POSITION => array("updatePosition", 128)
TheliaEvents::CATEGORY_UPDATE_POSITION => array("updatePosition", 128),
TheliaEvents::CATEGORY_ADD_CONTENT => array("addContent", 128),
TheliaEvents::CATEGORY_REMOVE_CONTENT => array("removeContent", 128),
);
}
}

View File

@@ -95,12 +95,25 @@
<route id="admin.categories.update-position" path="/admin/categories/update-position">
<default key="_controller">Thelia\Controller\Admin\CategoryController::updatePositionAction</default>
</route>
<route id="admin.categories.related-content.add" path="/admin/categories/related-content/add">
<default key="_controller">Thelia\Controller\Admin\CategoryController::addRelatedContentAction</default>
</route>
<route id="admin.categories.related-content.delete" path="/admin/categories/related-content/delete">
<default key="_controller">Thelia\Controller\Admin\CategoryController::deleteRelatedContentAction</default>
</route>
<route id="admin.category.available-related-content" path="/admin/category/{categoryId}/available-related-content/{folderId}.{_format}" methods="GET">
<default key="_controller">Thelia\Controller\Admin\CategoryController::getAvailableRelatedContentAction</default>
<requirement key="_format">xml|json</requirement>
</route>
<route id="admin.category.ajax" path="/admin/catalog/category/parent/{parentId}.{_format}" methods="GET">
<default key="_controller">Thelia\Controller\Admin\CategoryController::getByParentIdAction</default>
<requirement key="_format">xml|json</requirement>
</route>
<!-- Route to the Coupon controller (process Coupon browsing) -->
<route id="admin.coupon.list" path="/admin/coupon/">

View File

@@ -224,7 +224,7 @@ abstract class AbstractCrudController extends BaseAdminController
* @param unknown $updateEvent the update event
* @return Response a response, or null to continue normal processing
*/
protected function performAdditionalUpdateAction($updateeEvent)
protected function performAdditionalUpdateAction($updateEvent)
{
return null;
}

View File

@@ -32,6 +32,13 @@ use Thelia\Form\CategoryModificationForm;
use Thelia\Form\CategoryCreationForm;
use Thelia\Core\Event\UpdatePositionEvent;
use Thelia\Core\Event\CategoryToggleVisibilityEvent;
use Thelia\Core\Event\CategoryDeleteContentEvent;
use Thelia\Core\Event\CategoryAddContentEvent;
use Thelia\Model\CategoryAssociatedContent;
use Thelia\Model\FolderQuery;
use Thelia\Model\ContentQuery;
use Propel\Runtime\ActiveQuery\Criteria;
use Thelia\Model\CategoryAssociatedContentQuery;
/**
* Manages categories
@@ -162,14 +169,23 @@ class CategoryController extends AbstractCrudController
}
protected function renderEditionTemplate() {
return $this->render('category-edit', array('category_id' => $this->getRequest()->get('category_id', 0)));
return $this->render('category-edit',
array(
'category_id' => $this->getRequest()->get('category_id', 0),
'folder_id' => $this->getRequest()->get('folder_id', 0),
'current_tab' => $this->getRequest()->get('current_tab', 'general')
));
}
protected function redirectToEditionTemplate() {
$this->redirectToRoute(
"admin.categories.update",
array('category_id' => $this->getRequest()->get('category_id', 0))
);
array(
'category_id' => $this->getRequest()->get('category_id', 0),
'folder_id' => $this->getRequest()->get('folder_id', 0),
'current_tab' => $this->getRequest()->get('current_tab', 'general')
));
}
protected function redirectToListTemplate() {
@@ -209,6 +225,18 @@ class CategoryController extends AbstractCrudController
);
}
protected function performAdditionalUpdateAction($updateEvent)
{
if ($this->getRequest()->get('save_mode') != 'stay') {
// Redirect to parent category list
$this->redirectToRoute(
'admin.categories.default',
array('category_id' => $updateEvent->getCategory()->getParent())
);
}
}
protected function performAdditionalUpdatePositionAction($event)
{
@@ -224,4 +252,80 @@ class CategoryController extends AbstractCrudController
return null;
}
public function getAvailableRelatedContentAction($categoryId, $folderId) {
$result = array();
$folders = FolderQuery::create()->filterById($folderId)->find();
if ($folders !== null) {
$list = ContentQuery::create()
->filterByFolder($folders, Criteria::IN)
->filterById(CategoryAssociatedContentQuery::create()->select('content_id')->findByCategoryId($categoryId), Criteria::NOT_IN)
->find();
;
if ($list !== null) {
foreach($list as $item) {
$result[] = array('id' => $item->getId(), 'title' => $item->getTitle());
}
}
}
return $this->jsonResponse(json_encode($result));
}
public function addRelatedContentAction() {
// Check current user authorization
if (null !== $response = $this->checkAuth("admin.categories.update")) return $response;
$content_id = intval($this->getRequest()->get('content_id'));
if ($content_id > 0) {
$event = new CategoryAddContentEvent(
$this->getExistingObject(),
$content_id
);
try {
$this->dispatch(TheliaEvents::CATEGORY_ADD_CONTENT, $event);
}
catch (\Exception $ex) {
// Any error
return $this->errorPage($ex);
}
}
$this->redirectToEditionTemplate();
}
public function deleteRelatedContentAction() {
// Check current user authorization
if (null !== $response = $this->checkAuth("admin.categories.update")) return $response;
$content_id = intval($this->getRequest()->get('content_id'));
if ($content_id > 0) {
$event = new CategoryDeleteContentEvent(
$this->getExistingObject(),
$content_id
);
try {
$this->dispatch(TheliaEvents::CATEGORY_REMOVE_CONTENT, $event);
}
catch (\Exception $ex) {
// Any error
return $this->errorPage($ex);
}
}
$this->redirectToEditionTemplate();
}
}

View File

@@ -62,6 +62,14 @@ class BaseController extends ContainerAware
return new Response();
}
/**
* Return a JSON response
*/
protected function jsonResponse($json_data)
{
return new Response($json_data, 200, array('content-type' => 'application/json'));
}
/**
* Dispatch a Thelia event
*

View File

@@ -0,0 +1,48 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
namespace Thelia\Core\Event;
use Thelia\Model\Category;
class CategoryAddContentEvent extends CategoryEvent
{
protected $content_id;
public function __construct(Category $category, $content_id)
{
parent::__construct($category);
$this->content_id = $content_id;
}
public function getContentId()
{
return $this->content_id;
}
public function setContentId($content_id)
{
$this->content_id = $content_id;
}
}

View File

@@ -0,0 +1,48 @@
<?php
/*************************************************************************************/
/* */
/* Thelia */
/* */
/* Copyright (c) OpenStudio */
/* email : info@thelia.net */
/* web : http://www.thelia.net */
/* */
/* This program is free software; you can redistribute it and/or modify */
/* it under the terms of the GNU General Public License as published by */
/* the Free Software Foundation; either version 3 of the License */
/* */
/* This program is distributed in the hope that it will be useful, */
/* but WITHOUT ANY WARRANTY; without even the implied warranty of */
/* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the */
/* GNU General Public License for more details. */
/* */
/* You should have received a copy of the GNU General Public License */
/* along with this program. If not, see <http://www.gnu.org/licenses/>. */
/* */
/*************************************************************************************/
namespace Thelia\Core\Event;
use Thelia\Model\Category;
class CategoryDeleteContentEvent extends CategoryEvent
{
protected $content_id;
public function __construct(Category $category, $content_id)
{
parent::__construct($category);
$this->content_id = $content_id;
}
public function getContentId()
{
return $this->content_id;
}
public function setContentId($content_id)
{
$this->content_id = $content_id;
}
}

View File

@@ -153,6 +153,9 @@ final class TheliaEvents
const CATEGORY_TOGGLE_VISIBILITY = "action.toggleCategoryVisibility";
const CATEGORY_UPDATE_POSITION = "action.updateCategoryPosition";
const CATEGORY_ADD_CONTENT = "action.categoryAddContent";
const CATEGORY_REMOVE_CONTENT = "action.categoryRemoveContent";
const BEFORE_CREATECATEGORY = "action.before_createcategory";
const AFTER_CREATECATEGORY = "action.after_createcategory";

View File

@@ -53,8 +53,12 @@ class AssociatedContent extends Content
{
$argumentCollection = parent::getArgDefinitions();
$argumentCollection->addArgument(Argument::createIntTypeArgument('product'))
->addArgument(Argument::createIntTypeArgument('category'));
$argumentCollection
->addArgument(Argument::createIntTypeArgument('product'))
->addArgument(Argument::createIntTypeArgument('category'))
->addArgument(Argument::createIntTypeArgument('exclude_product'))
->addArgument(Argument::createIntTypeArgument('exclude_category'))
;
$argumentCollection->get('order')->default = "associated_content";
@@ -91,6 +95,28 @@ class AssociatedContent extends Content
$search->filterByCategoryId($category, Criteria::EQUAL);
}
$exclude_product = $this->getExcludeProduct();
// If we have to filter by template, find all attributes assigned to this template, and filter by found IDs
if (null !== $exclude_product) {
// Exclure tous les attribut qui sont attachés aux templates indiqués
$search->filterById(
ProductAssociatedContentQuery::create()->filterByProductId($exclude_product)->select('product_id')->find(),
Criteria::NOT_IN
);
}
$exclude_category = $this->getExcludeCategory();
// If we have to filter by template, find all attributes assigned to this template, and filter by found IDs
if (null !== $exclude_category) {
// Exclure tous les attribut qui sont attachés aux templates indiqués
$search->filterById(
CategoryAssociatedContentQuery::create()->filterByProductId($exclude_category)->select('category_id')->find(),
Criteria::NOT_IN
);
}
$order = $this->getOrder();
$orderByAssociatedContent = array_search('associated_content', $order);
$orderByAssociatedContentReverse = array_search('associated_content_reverse', $order);

View File

@@ -43,15 +43,18 @@ class CategoryCreationForm extends BaseForm
"label" => Translator::getInstance()->trans("Parent category *"),
"constraints" => array(
new NotBlank()
)
),
"label_attr" => array("for" => "parent_create")
))
->add("locale", "text", array(
"constraints" => array(
new NotBlank()
)
),
"label_attr" => array("for" => "locale_create")
))
->add("visible", "integer", array(
"label" => Translator::getInstance()->trans("This category is online on the front office.")
"label" => Translator::getInstance()->trans("This category is online on the front office."),
"label_attr" => array("for" => "visible_create")
))
;
}

View File

@@ -39,7 +39,8 @@ class CategoryModificationForm extends CategoryCreationForm
->add("url", "text", array(
"label" => Translator::getInstance()->trans("Rewriten URL *"),
"constraints" => array(new NotBlank())
"constraints" => array(new NotBlank()),
"label_attr" => array("for" => "rewriten_url")
))
;

View File

@@ -58,7 +58,8 @@ trait StandardDescriptionFieldsTrait
"label" => Translator::getInstance()->trans("Title"),
"label_attr" => array(
"for" => "title"
)
),
"label_attr" => array("for" => "title_field")
)
);
@@ -67,7 +68,7 @@ trait StandardDescriptionFieldsTrait
->add("chapo", "text", array(
"label" => Translator::getInstance()->trans("Summary"),
"label_attr" => array(
"for" => "summary"
"for" => "summary_field"
)
));
@@ -76,7 +77,7 @@ trait StandardDescriptionFieldsTrait
->add("description", "text", array(
"label" => Translator::getInstance()->trans("Detailed description"),
"label_attr" => array(
"for" => "detailed_description"
"for" => "detailed_description_field"
)
));
@@ -85,7 +86,7 @@ trait StandardDescriptionFieldsTrait
->add("postscriptum", "text", array(
"label" => Translator::getInstance()->trans("Conclusion"),
"label_attr" => array(
"for" => "conclusion"
"for" => "conclusion_field"
)
));
}

View File

@@ -39,8 +39,8 @@
<div class="row">
<div class="col-md-12">
<ul class="nav nav-tabs">
<li class="active"><a href="#general_description" data-toggle="tab">{intl l="General description"}</a></li>
<ul class="nav nav-tabs" id="tabbed-menu">
<li class="active"><a href="#general" data-toggle="tab">{intl l="General description"}</a></li>
<li><a href="#details" data-toggle="tab">{intl l="Details"}</a></li>
<li><a href="#images" data-toggle="tab">{intl l="Images"}</a></li>
@@ -50,7 +50,7 @@
<div class="tab-content">
<div class="tab-pane fade active in" id="general_description">
<div class="tab-pane fade active in" id="general">
<div class="form-container">
@@ -62,6 +62,8 @@
{* Be sure to get the category ID, even if the form could not be validated *}
<input type="hidden" name="category_id" value="{$category_id}" />
<input type="hidden" name="current_tab" value="general" />
{form_hidden_fields form=$form}
{form_field form=$form field='success_url'}
@@ -140,7 +142,107 @@
</div>
<div class="tab-pane fade" id="details">
klljkmk
<div class="form-container">
<div class="form-group">
<form action="{url path='/admin/categories/related-content/add'}" id="related_content_form">
{include
file="includes/inner-form-toolbar.html"
hide_submit_buttons=true
close_url="{url path='/admin/categories' category_id=$category_id}"
}
<input type="hidden" name="category_id" value="{$category_id}" />
<input type="hidden" name="current_tab" value="details" />
{ifloop rel="folders"}
<div class="row">
<div class="col-md-6">
<div class="form-group">
<select name="folder_id" id="folder_id" class="form-control">
<option value="">Select a folder...</option>
{loop name="folders" type="folder" backend_context="1" lang="$edit_language_id"}
<option value="{$ID}">{$TITLE}</option>
{/loop}
</select>
</div>
<span class="help-block">{intl l='Select a folder to get its content'}</span>
</div>
<div class="col-md-6">
<div id="content_selector" class="hide">
<div class="input-group">
<select required="required" name="content_id" id="content_id" class="form-control">
<option value="">Select a folder content...</option>
</select>
<span class="input-group-btn" id="content_add_button">
<button class="btn btn-default btn-primary action-btn" type="submit"><span class="glyphicon glyphicon-plus-sign"></span></button>
</span>
</div>
<span class="help-block">{intl l='Select a content and click (+) to add it to this category'}</span>
</div>
</div>
</div>
{/ifloop}
{elseloop rel="folders"}
<div class="alert alert-info">{intl l="No folders found"}</div>
{/elseloop}
</form>
</div>
<table class="table table-striped table-condensed table-left-aligned">
<thead>
<tr>
<th>{intl l='ID'}</th>
<th>{intl l='Attribute title'}</th>
{module_include location='category_contents_table_header'}
<th class="actions">{intl l="Actions"}</th>
</tr>
</thead>
<tbody>
{loop name="assigned_contents" type="associated_content" category="$category_id" backend_context="1" lang="$edit_language_id"}
<tr>
<td>{$ID}</td>
<td>
{$TITLE}
</td>
{module_include location='category_contents_table_row'}
<td class="actions">
<div class="btn-group">
{loop type="auth" name="can_create" roles="ADMIN" permissions="admin.configuration.category.content.delete"}
<a class="btn btn-default btn-xs delete-content" title="{intl l='Delete this content'}" href="#delete_content_dialog" data-id="{$ID}" data-toggle="modal">
<span class="glyphicon glyphicon-trash"></span>
</a>
{/loop}
</div>
</td>
</tr>
{/loop}
{elseloop rel="assigned_contents"}
<tr>
<td colspan="3">
<div class="alert alert-info">
{intl l="This category contains no contents"}
</div>
</td>
</tr>
{/elseloop}
</tbody>
</table>
</div>
</div>
<div class="tab-pane fade" id="images">
@@ -159,6 +261,28 @@
</div>
</div>
</div>
{* Delete related content confirmation dialog *}
{capture "delete_content_dialog"}
<input type="hidden" name="category_id" value="{$category_id}" />
<input type="hidden" name="content_id" id="content_delete_id" value="" />
<input type="hidden" name="folder_id" id="folder_delete_id" value="" />
<input type="hidden" name="current_tab" value="details" />
{/capture}
{include
file = "includes/generic-confirm-dialog.html"
dialog_id = "delete_content_dialog"
dialog_title = {intl l="Remove related content"}
dialog_message = {intl l="Do you really want to remove this related content ?"}
form_action = {url path='/admin/categories/related-content/delete'}
form_content = {$smarty.capture.delete_content_dialog nofilter}
}
{/block}
{block name="javascript-initialization"}
@@ -171,7 +295,50 @@ $(function() {
ev.preventDefault();
});
})
// Show proper tab, if defined
{if ! empty($current_tab)}
$('#tabbed-menu a[href="#{$current_tab}"]').tab('show')
{/if}
// Set proper content ID in delete content from
$('a.delete-content').click(function(ev) {
$('#content_delete_id').val($(this).data('id'));
$('#folder_delete_id').val($('#folder_id').val());
});
// Load content on folder selection
$('#folder_id').change(function(event) {
$.ajax({
url : '{url path="/admin/category/$category_id/available-related-content/"}' + $(this).val() + '.xml',
type : 'get',
dataType : 'json',
success : function(json) {
$('#content_id :not(:first-child)').remove();
var have_content = false;
$.each(json, function(idx, value) {
$('#content_id').append($('<option>').text(value.title).attr('value', value.id));
have_content = true; // Lame...
});
if (have_content)
$('#content_selector').removeClass('hide');
else
$('#content_selector').addClass('hide');
}
});
});
// Initialize folder (id={$folder_id}) select value
{if $folder_id != 0}
$('#folder_id').val("{$folder_id}").change();
{/if}
});
</script>
{/block}

View File

@@ -21,8 +21,10 @@
</div>
<div class="col-md-6 inner-actions">
{if $hide_submit_buttons != true}
<button type="submit" name="save_mode" value="stay" class="btn btn-default btn-success" title="{intl l='Save'}">{intl l='Save'} <span class="glyphicon glyphicon-ok"></span></button>
<button type="submit" name="save_mode" value="close" class="btn btn-default btn-info" title="{intl l='Save and close'}">{intl l='Save and close'} <span class="glyphicon glyphicon-remove"></span></button>
{/if}
{if ! empty($close_url)}
<a href="{$close_url}" class="btn btn-default">{intl l='Close'} <span class="glyphicon glyphicon-remove"></span></a>
{/if}

View File

@@ -51,5 +51,7 @@ $response = $thelia->handle($request)->prepare($request)->send();
$thelia->terminate($request, $response);
echo "\n<!-- page parsed in : " . (microtime(true) - $thelia->getStartTime())." s. -->";
echo "\n<!-- memory peak : " . memory_get_peak_usage()/1024/1024 . " MiB. -->";
if (strstr($response->headers->get('content-type'), 'text/html') !== false) {
echo "\n<!-- page parsed in : " . (microtime(true) - $thelia->getStartTime())." s. -->";
echo "\n<!-- memory peak : " . memory_get_peak_usage()/1024/1024 . " MiB. -->";
}