I wish to add an action to another to the index action with a predefined filter.
To build the filter I need the get current entity in the configureActions method.
public function configureActions(Actions $actions): Actions
{
parent::configureActions($actions);
$adminUrlGenerator = $this->get(AdminUrlGenerator::class);
$url = $adminUrlGenerator
->setController(SiteCrudController::class)
->setAction(Action::INDEX)
->set('filters', [
'agent' => [
'comparison' => '=',
'value' => 2194, // How to get current entity here??
]
])
->generateUrl()
;
$viewRelatesSites = Action::new('viewRelatedSites', 'Sites', 'fa fa-file-invoice')
->linkToUrl($url)
;
$actions->add(Action::DETAIL, $viewRelatesSites);
$actions->add(Action::EDIT, $viewRelatesSites);
return $actions;
}
}
How can I get the entity here?
To get the current entity the AdminContext is needed.
The best place to get this with the entity set is in a BeforeCrudActionEvent.
final class AgentCrudActionEventListen implements EventSubscriberInterface
{
private AdminUrlGenerator $adminUrlGenerator;
private AdminContextProvider $adminContextProvider;
public function __construct(AdminUrlGenerator $adminUrlGenerator, AdminContextProvider $adminContextProvider)
{
$this->adminUrlGenerator = $adminUrlGenerator;
$this->adminContextProvider = $adminContextProvider;
}
public static function getSubscribedEvents(): array
{
return [
BeforeCrudActionEvent::class => 'onBeforeCrudActionEvent',
];
}
public function onBeforeCrudActionEvent(BeforeCrudActionEvent $event): void
{
$crud = $event->getAdminContext()->getCrud();
if ($crud->getControllerFqcn() !== AgentCrudController::class) {
return;
}
$entity = $this->adminContextProvider->getContext()->getEntity();
if (!$entity) {
return;
}
$url = $this->adminUrlGenerator
->setController(SiteCrudController::class)
->setAction(Action::INDEX)
->set('filters', [
'agent' => [
'comparison' => '=',
'value' => $entity->getPrimaryKeyValue(),
]
])
->generateUrl()
;
$viewRelatedSites = Action::new('viewRelatedSites', 'Sites', 'fa fa-file-invoice')
->linkToUrl($url)
;
$actions = $crud->getActionsConfig();
$actions->appendAction(Action::DETAIL, $viewRelatedSites->getAsDto());
$actions->appendAction(Action::EDIT, $viewRelatedSites->getAsDto());
}
}
After a day looking for the current entity into my AbstractCrudController, I found this :
$currentEntity = $this->getContext()->getEntity()->getInstance();
Since EasyAdmin 3 events are discouraged per the docs:
Starting from EasyAdmin 3.0 everything is defined with PHP. That's why it's easier to customize backend behavior overloading PHP classes and methods and calling to your own services. However, the events still remain in case you want to use them.
public function configureActions(Actions $actions): Actions
{
// Create your action
$viewRelatedSites = Action::new('viewRelatedSites', 'Sites', 'fa fa-file-invoice');
//set the link using a string or a callable (function like its being used here)
$viewRelatedSites->linkToUrl(function($entity) {
$adminUrlGenerator = $this->get(AdminUrlGenerator::class);
$url = $adminUrlGenerator
->setController(SiteCrudController::class)
->setAction(Action::INDEX)
->set('filters', [
'agent' => [
'comparison' => '=',
'value' => $entity->getId()
]
])
->generateUrl();
return $url;
});
$actions->add(Crud::PAGE_INDEX, $viewRelatedSites);
return $actions;
}
Related
I'm trying to import products from an XML with variations.
The import for the products works so far but it doesn't create the variations.
Here is my code (simplified):
/**
* #return int
* #throws \Exception
*/
public function execute()
{
// avoid reaching memory limit
ini_set('memory_limit', '-1');
// set tax id
$this->setTaxId();
if (empty($this->taxId)) {
return 1;
}
// read products from import xml file
$importProducts = $this->loadProducts();
$csvBatch = array_chunk($importProducts, self::BATCH);
$productNumbers = [];
foreach ($csvBatch as $products) {
$productNumbers[] = $this->processImportProducts($products, false);
}
$this->deleteProducts(array_merge(...$productNumbers));
return 0;
}
/**
* #param $productsData
* #param $progressBar
* #return array
*/
private function processImportProducts($productsData, $progressBar)
{
$products = [];
$productNumbers = [];
foreach ($productsData as $product) {
$products[$product['SKU']['#cdata']] = $this->importProducts($product, $progressBar);
$productNumbers[] = $product['SKU']['#cdata'];
}
// upsert product
try {
$this->cleanProductProperties($products, $this->context);
$this->productRepository->upsert(array_values($products), $this->context);
} catch (WriteException $exception) {
$this->logger->info(' ');
$this->logger->info('<error>Products could not be imported. Message: '. $exception->getMessage() .'</error>');
}
unset($products);
return $productNumbers;
}
/**
* #param $product
* #param $progressBar
* #return array
*/
private function importProducts($product, $progressBar)
{
...
$productData = [
'id' => $productId,
'productNumber' => $productNumber,
'price' => [
[
'currencyId' => Defaults::CURRENCY,
'net' => !empty($product['net']) ? $product['net'] : 0,
'gross' => !empty($product['net']) ? $product['net'] : 0,
'linked' => true
]
],
'stock' => 99999,
'unit' => [
'id' => '3fff95a8077b4f5ba3d1d2a41cb53fab'
],
'unitId' => '3fff95a8077b4f5ba3d1d2a41cb53fab',
'taxId' => $this->taxId,
'name' => $productNames,
'description' => $productDescriptions
];
if(isset($product['Variations'])) {
$variationIds = $product['Variations']['#cdata'] ?? '';
$productData['variation'] = [$this->getProductVariationIds($variationIds)];
}
return $productData;
}
/**
* Get product variation ids
*
* #param string $productVariations
* #return string
*/
private function getProductVariationIds($productVariations)
{
$productVariationIds = explode(',', $productVariations);
// get product variationIds in form of a string list
$ids = $this->productRepository->search(
(new Criteria())->addFilter(new EqualsAnyFilter('productNumber', $productVariationIds)),
$this->context
)->getIds();
return implode(',', $ids);
}
It loads correctly the ids but nothing happen. Also no error.
Anyone an idea how to import variations as well?
The variation field is not meant to be persisted or to create variants of a product. It has the Runtime flag, meaning it's not an actual database column but processed during runtime.
You have to create/update variants just like you create the parent product. Additionally you have to set the parentId and the options. The latter being associations to property_group_option, which you'll have to create first.
So in addition to your existing payload when creating parent products, you'll have to add this data to the variants:
$productData = [
// ...
'parentId' => '...'
'options' => [
['id' => '...'],
['id' => '...'],
['id' => '...'],
// ...
],
];
Finally you'll have to create the product_configurator_setting records. That's one record for each option used across all variants. Also the productId for the records has to be the one of the parent product.
$repository = $this->container->get('product_configurator_setting.repository');
$configuratorSettings = [];
foreach ($options as $option) {
$configuratorSetting = [
'optionId' => $option['id'],
'productId' => $parentId,
];
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productId', $parentId));
$criteria->addFilter(new EqualsFilter('optionId', $option['id']));
$id = $repository->searchIds($criteria, $context)->firstId();
// if the configurator setting already exists, update or skip
if ($id) {
$configuratorSetting['id'] = $id;
}
$configuratorSettings[] = $configuratorSetting;
}
$repository->upsert(configuratorSettings, $context);
Just as an addition to make things easier. When creating a product with variants you can just update the configuratorSettings of the parent/father/main-product (whatever you call it).
Then Shopware6 will go and create the variant products automatically. Also the uuids of the children are created automatically. So if need to keep track of these you have to query them after the creation process.
But for a fast creation this might be much faster, if you have a lot of variants the only "variation" are the options. So no special images or texts.
I added this custom field to my customer and to the register form in storefront/component/account/register.html.twig:
<input type="checkbox" class="custom-control-input" id="alumni" name="custom_kw_dau" value="1">
The field is type checkbox. It works fine in the backend but it is not filled during customer registration.
You have to manually store it. Subscribe to event and add the field to customFields in the output like this:
public static function getSubscribedEvents(): array
{
return [
CustomerEvents::MAPPING_REGISTER_CUSTOMER => 'addCustomField'
];
}
public function addCustomField(DataMappingEvent $event): bool
{
$inputData = $event->getInput();
$outputData = $event->getOutput();
$custom_field = (bool)$inputData->get('custom_kw_dau', false);
$outputData['customFields'] = array('custom_kw_dau' => $custom_field);
$event->setOutput($outputData);
return true;
}
Yeah you need to subscribe to the event - but I have done it like this instead of the above event, and it works fine as well.
public static function getSubscribedEvents(): array
{
return [
CustomerRegisterEvent::class => 'onRegister',
GuestCustomerRegisterEvent::class => 'onRegister'
];
}
public function onRegister(CustomerRegisterEvent $event): void
{
$request = $this->requestStack->getCurrentRequest();
if ($request) {
$params = $request->request->all();
$customer = $event->getCustomer();
$data = [
'id' => $customer->getId(),
'customFields' => [
'your_field' => $params['your_field']
]
];
$this->customerRepository->update([$data], $event->getContext());
}
}
But I think the answer above might be more suitable as it does not require any additional services.
I want to make export results with Laravel Excel, but the results did not match what I wanted.
what I want is like this
I want all cells to be wrapped in text and have a complete border.
This is my code:
class SdgsExportView2 implements FromView, WithTitle {
protected $id_perangkat_daerah;
function __construct($id_perangkat_daerah) {
$this->id_perangkat_daerah = $id_perangkat_daerah;
}
public function view(): View
{
$data_sdgs = DataSdgs::where('id_perangkat_daerah',$this->id_perangkat_daerah)
->with('indikator.target.sdgs')
->with('detail_data_sdgs.kegiatan.program.perangkat_daerah')
->get();
return view('template.exportMatrik2', [
'data_sdgs' => $data_sdgs
]);
}
public function registerEvents(): array
{
return [
AfterSheet::class => function(AfterSheet $event) {
$cellRange = 'A1:W100'; // All headers
$event->sheet->getDelegate()->getStyle($cellRange)->getFont()->setSize(14);
$event->sheet->getDelegate()->getStyle($cellRange)->getAlignment()->setWrapText(true);
}
];
}
public function title() : string
{
return 'MATRIK 2';
}
}
I really hope for your help
I know it is a bit late to answer (after 8 months), but to add border format to your code is relatively simple.
In your event function AfterSheet::class => function(AfterSheet $event) you need to add following code:
$styleHeader = [
'borders' => [
'allBorders' => [
'borderStyle' => 'thin',
'color' => ['rgb' => '808080']
],
]
];
$event->sheet->getStyle("A1:C1")->applyFromArray($styleHeader);
And replace "A1:C1" with your header range.
For me I have added event by extends WithEvents and use
use Maatwebsite\Excel\Concerns\WithEvents;
Then I apply like this....
public function registerEvents(): array
{
$alphabetRange = range('A', 'Z');
$alphabet = $alphabetRange[$this->totalValue+6]; // returns Alphabet
$totalRow = (count($this->attributeSets) * 3) + count($this->allItems)+1;
$cellRange = 'A1:'.$alphabet.$totalRow;
return [
AfterSheet::class => function(AfterSheet $event) use($cellRange) {
$event->sheet->getStyle($cellRange)->applyFromArray([
'borders' => [
'allBorders' => [
'borderStyle' => \PhpOffice\PhpSpreadsheet\Style\Border::BORDER_THIN,
'color' => ['argb' => '000000'],
],
],
])->getAlignment()->setWrapText(true);
},
];
}
For Details or more you can follow The doc https://docs.laravel-excel.com/3.1/getting-started/
#Zahid answer little bit update for v3.1
public function registerEvents(): array
{
return [
AfterSheet::class => function(AfterSheet $event) {
$alphabet = $event->sheet->getHighestDataColumn();
$totalRow = $event->sheet->getHighestDataRow();
$cellRange = 'A7:'.$alphabet.$totalRow;
$event->sheet->getStyle($cellRange)->applyFromArray([
'borders' => [
'allBorders' => [
'borderStyle' => Border::BORDER_HAIR,
],
],
])->getAlignment()->setWrapText(true);
},
];
}
I think this might help you what you looking for : phpspreadsheet simpler
There is a method
// $titleName : pass data that you wants to set (not array)
// $mergeTill : pass count of array dataset (Int)
public function setTitle($titleName, $mergeTill, $startCell ,$styleName = "")
go through this example given link
I am trying to overwrite a template file located in vendor/bolt/bolt/app/view/twig/editcontent/fields/_block.twig (I want to replace the "block selection" dropdown). Regarding to #1173, #1269, #5588, #3768 and #5102 this is not supported by default so I have to write a extension for this. So I tried this:
BackendBlockSelectionExtension:
namespace Bundle\Site;
use Bolt\Filesystem\Adapter\Local;
use Bolt\Filesystem\Filesystem;
use Silex\Application;
use Bolt\Extension\SimpleExtension;
class BackendBlockSelectionExtension extends SimpleExtension
{
public function getServiceProviders()
{
return [
$this,
new BackendBlockSelectionProvider(),
];
}
}
BackendBlockSelectionProvider:
namespace Bundle\Site;
use Bolt\Filesystem\Adapter\Local;
use Bolt\Filesystem\Filesystem;
use Silex\Application;
use Silex\ServiceProviderInterface;
class BackendBlockSelectionProvider implements ServiceProviderInterface
{
public function register(Application $app)
{
$side = $app['config']->getWhichEnd();
if ($side == 'backend') {
$path = __DIR__ . '/App/templates/Backend';
$filesystem = $app['filesystem'];
$filesystem->mountFilesystem('bolt', new Filesystem(new Local($path)));
$app['twig.loader.bolt_filesystem'] = $app->share(
$app->extend(
'twig.loader.bolt_filesystem',
function ($filesystem, $app) {
$path = __DIR__ . 'src/App/templates/Backend/';
$filesystem->prependPath($path, 'bolt');
return $filesystem;
}
)
);
}
}
public function boot(Application $app)
{
}
}
This seems to do the job, but I got an error I don't understand at all: The "bolt://app/theme_defaults" directory does not exist.
So my final question is: Does anyone have some example code how to overwrite/modify vendor/bolt/bolt/app/view/twig/editcontent/fields/_block.twig without touching the vendor folder?
This should be much simplier than this.
In your extension class overwrite protected function registerTwigPaths() function like this:
protected function registerTwigPaths()
{
if ($this->getEnd() == 'backend') {
return [
'view' => ['position' => 'prepend', 'namespace' => 'bolt']
];
}
return [];
}
private function getEnd()
{
$backendPrefix = $this->container['config']->get('general/branding/path');
$end = $this->container['config']->getWhichEnd();
switch ($end) {
case 'backend':
return 'backend';
case 'async':
// we have async request
// if the request begin with "/admin" (general/branding/path)
// it has been made on backend else somewhere else
$url = '/' . ltrim($_SERVER['REQUEST_URI'], $this->container['paths']['root']);
$adminUrl = '/' . trim($backendPrefix, '/');
if (strpos($url, $adminUrl) === 0) {
return 'backend';
}
default:
return $end;
}
}
Now you can crete a view directory in your extensions directory in which you can define templates in structure like in Bolt's default. I would start with copy and overwrite.
Here the part of the stacktrace where I have a problem:
Zend\Stdlib\Exception\BadMethodCallException
File:
/var/www/html/zf2/vendor/zendframework/zendframework/library/Zend/Stdlib/Hydrator/ArraySerializable.php:28
Message:
Zend\Stdlib\Hydrator\ArraySerializable::extract expects the provided object to implement getArrayCopy()
Stack trace:
0 /var/www/html/zf2/vendor/zendframework/zendframework/library/Zend/Form/Fieldset.php(631): Zend\Stdlib\Hydrator\ArraySerializable->extract(Object(BookList\Model\Book))
1 /var/www/html/zf2/vendor/zendframework/zendframework/library/Zend/Form/Form.php(942): Zend\Form\Fieldset->extract()
2 /var/www/html/zf2/vendor/zendframework/zendframework/library/Zend/Form/Form.php(303): Zend\Form\Form->extract()
3 /var/www/html/zf2/module/BookList/src/BookList/Controller/BookController.php(59): Zend\Form\Form->bind(Object(BookList\Model\Book))
The action method in my Controller that call bind:
public function editAction()
{
$id = (int) $this->params()->fromRoute('id', 0);
if (!$id) {
return $this->redirect()->toRoute('book');
}
try {
$book = $this->getBookTable()->getBook($id);
}
catch (\Exception $ex) {
return $this->redirect()->toRoute('book', array(
'action' => 'index'
));
}
$form = new BookForm();
$form->bind($book); // this is the line 59 of BookController
$form->get('submit')->setAttribute('value', 'Edit');
$request = $this->getRequest();
if ($request->isPost()) {
$form->setInputFilter($book->getInputFilter());
$form->setData($request->getPost());
if ($form->isValid()) {
$this->getBookTable()->saveBook($book);
// Redirect to list of books
return $this->redirect()->toRoute('book');
}
}
return array(
'id' => $id,
'form' => $form,
);
}
I checked also the BookTable class to see the object returned from the resulset and it's an istance of Book.
Than I opened the ArratSerializable.php and check the object passed and tre response is:
BookList\Model\Book Object ( [id] => 5 [author] => Gotye [title] => Making Mirrors [inputFilter:protected] => )
So it's a correct object, why doesn't it work?
How the result is returned you generally tell that to the ResultSet object while building your model. You actually set a prototype there for returning your result set saying, hey! "Use this prototype" which is, in your case, Book model. It does have a method called getArrayCopy() which is missing. That actually rises error in this case. So please add this to the Book model thus
class Book
{
// other properties and methods should be here
// add this method here
public function getArrayCopy()
{
return get_object_vars($this);
}
}