How to change the order of the product list in cart_products in TYPO3 11? - typo3

I would like to create links that let the user sort the product list in cart_products ascending and descending.
For this I created a Fluid-link in Grid.html of cart_products that passes an argument for sorting to the controller:
<f:link.action action="list" arguments="{sorting:'up'}">Sorting up</f:link.action>
The method listAction() in ProductController.php gets the argument with:
if ($this->request->hasArgument('sorting')) {
$sorting = $this->request->getArgument('sorting');
}
With this if-statement I control what is happening based on the given argument:
if ($sorting === "up") {
// Get all products sorted ascending
} elseif ($sorting === "down"){
// Get all products sorted decending
}
The products are received with the following command (original):
$products = $this->productRepository->findDemanded($demand);
The documentation says that the following function does the sorting:
$query->setOrderings(
[
'organization.name' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING,
'title' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING
]
);
I would like to know how to combine the both parts to receive the products ordered as wished.

Change your view helper to directly use sorting argument
<f:link.action action="listSorted" arguments="{sorting:'asc'}">Sorting up</f:link.action>
Add your own controller action (extending class ProductController)
<?php
// ...
public function listSortedAction(int $currentPage = 1): void
{
$demand = $this->createDemandObjectFromSettings($this->settings);
// ...
// Code from cart_products/Classes/Controller/ProductController.php listAction()
// ...
// instead of $products = $this->productRepository->findDemanded($demand);
if ($this->request->hasArgument('sorting')) {
$sorting = $this->request->getArgument('sorting');
$products = $this->productRepository->findDemandedSorted($demand,$sorting);
}
//...
?>
Then add you own repository function (extending class ProductRepository)
<?php
public function findDemandedSorted(ProductDemand $demand, $sortOrder)
{
$query = $this->createQuery();
// ...
// Code from cart_products/Classes/Domain/Repository/Product/ProductRepository.php findDemanded(ProductDemand $demand)
// ...
// instead of
// if ($orderings = $this->createOrderingsFromDemand($demand)) {
// $query->setOrderings($orderings);
// }
$query->setOrderings($sortOrder);
return $query->execute();
}

Related

Show xml data in Frontend

Is it possible fetching data from a cached xml file and then showing them on front end?
I was thinking doing it in a TYPO3 extension and with its domain model (and getter/setter) but without a database table. And then filling in data with SimpleXML just to "store" them in memory. At least display the data from domain model with fluid on front end. But I don't know is this approach right or is there a better way to do that? In particular setting up the persistence layer I don't understand.
For any help I thank you very much for your effort in advance.
I found an "acceptable" solution. My approach for that was:
Get all items from xml file
Add a slug field
Sort the items
Display sorted items on the front end
Create unique pretty url
1. Get all items from xml file
Controller: listAction, detailAction
public function listAction() {
$jobs = $this->xmlDataRepository->findAll();
$jobsArray = $this->simpleXmlObjToArr($jobs);
$jobsArraySorted = $this->sortJobsByTitle($jobsArray);
$this->view->assign('jobs', $jobsArraySorted);
}
public function detailAction($slugid) {
$job = $this->xmlDataRepository->findBySlugWithId($slugid);
$this->view->assign('job', $job[0]);
}
Repository: findAll, findBySlugWithId
public function findAll() {
$objectStorage = new ObjectStorage();
$dataFolder = ConfigurationService::setDataFolder();
$xmlFile = glob($dataFolder . '*.xml')[0];
$xmlData = simplexml_load_file($xmlFile,'SimpleXMLElement',LIBXML_NOWARNING);
// error handling
if ($xmlData === false) {
...
}
foreach($xmlData->children() as $job) {
$objectStorage->attach($job);
}
return $objectStorage;
}
public function findBySlugWithId($slugid) {
// get id from slugid
$id = substr($slugid,strrpos($slugid,'-',-1)+1);
$objectStorage = new ObjectStorage();
$dataFolder = ConfigurationService::setDataFolder();
$xmlFile = glob($dataFolder . '*.xml')[0];
$xmlData = simplexml_load_file($xmlFile,'SimpleXMLElement',LIBXML_NOWARNING);
// error handling
if ($xmlData === false) {
...
}
$jobfound = false;
foreach($xmlData->children() as $job) {
if ($job->JobId == $id) {
$objectStorage->attach($job);
$jobfound = true;
}
}
// throw 404-error
if (!$jobfound) {
$response = GeneralUtility::makeInstance(ErrorController::class)->pageNotFoundAction(
$GLOBALS['TYPO3_REQUEST'],
'Ihre angeforderte Seite wurde nicht gefunden',
['code' => PageAccessFailureReasons::PAGE_NOT_FOUND]
);
throw new ImmediateResponseException($response, 9000006460);
}
return $objectStorage;
}
2. Add a slug field (controller)
protected function simpleXmlObjToArr($obj) {
// 2-dimensional array
$array = [];
foreach($obj as $item){
$row = [];
foreach($item as $key => $val){
$row[(string)$key] = (string)$val;
}
//add slug field, build it with Title
$row['Slug'] = $this->convertToPathSegment($row['Titel']);
// add $row to $array
array_push($array,$row);
}
return $array;
}
3. Sort the items (controller)
protected function sortJobsByTitle(array $jobs) {
$title = array();
$id = array();
foreach ($jobs as $key => $job) {
$title[$key] = $job['Titel'];
$id[$key] = $job['JobId'];
}
// sort jobs array according to title, uid (uid because if there are courses with the same title!)
array_multisort($title,SORT_ASC, $id,SORT_ASC, $jobs,SORT_STRING);
return $jobs;
}
4. Display sorted items on the front end (templates)
List.html:
...
<ul>
<f:for each="{jobs}" as="job">
<li>
<f:comment>
<f:link.action class="" pageUid="2" action="show" arguments="{id: job.JobId, slug: job.Slug}">{job.Titel}</f:link.action> ({job.JobId})<br>
<f:link.action class="" pageUid="2" action="detail" arguments="{xml: job}">NEW {job.Titel}</f:link.action> ({job.JobId})
</f:comment>
<f:variable name="slugid" value="{job.Slug}-{job.JobId}"/>
<f:link.action class="" pageUid="2" action="detail" arguments="{slugid: slugid}"><f:format.raw>{job.Titel}</f:format.raw></f:link.action> ({job.JobId})
</li>
</f:for>
</ul>
...
Detail.html:
...
<f:image src="{job.Grafik}" width="500" alt="Detailstellenbild" />
<p><strong><f:format.raw>{job.Titel}</f:format.raw></strong> ({job.JobId})</p>
<p>Region: {job.Region}</p>
<f:format.html>{job.Beschreibung}</f:format.html>
...
5. Create unique pretty url
...
routeEnhancers:
XmlJobDetail:
type: Extbase
limitToPages:
- 2
extension: Wtdisplayxmldata
plugin: Displayxmldata
routes:
-
routePath: '/{job-slugid}'
_controller: 'XmlData::detail'
_arguments:
job-slugid: slugid
defaultController: 'XmlData::list'
aspects:
job-slugid:
type: XmlDetailMapper
Routing/Aspect/XmlDetailMapper.php:
use TYPO3\CMS\Core\Routing\Aspect\StaticMappableAspectInterface;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
class XmlDetailMapper implements StaticMappableAspectInterface {
/**
* {#inheritdoc}
*/
public function generate(string $value): ?string
{
return $value !== false ? (string)$value : null;
}
/**
* {#inheritdoc}
*/
public function resolve(string $value): ?string
{
return isset($value) ? (string)$value : null;
}
}

Eloquent, how to select rows if a value is present in any of two tables?

I have tables that have the following structure, one product can have many skus:
product skus
id product_id
sku_prin sku
other fields other fields
If a search value is present in either sku_prin of products table or sku of skus table the row should be selected.
$search_value = "ramdom_value";
$query = product::query();
$result = $query->with(['skus' => function($q) use($search_value){
// this code won't work the orWhere cannot change the context of skus table
$q->where('sku', $search_value)->orWhere('products.sku_prin', $search_value)
}])->paginate(50);
Above is my failed attempt. How can accomplish what I want?
An approach is to use whereHas function of Eloquent
Consider you have the product and skus model like this
class Product extends Model
{
protected $table = 'product';
public function sku()
{
return $this->belongsTo('App\Skus', 'product_id', 'id');
}
}
class Skus extends Model
{
protected $table = 'skus';
public function products()
{
return $this->hasMany('App\Product', 'id', 'product_id');
}
}
You can obtain your data with Eloquent like this
$keyword = 'some keyword'
Product::where('sku_prin', '=', $keyword) // for product table
->orWhereHas('sku', function($query) use ($keyword) {
$query->where('sku', '=', $keyword); // for sku table
});
Or even more, do the fuzzy whereLike query with exploding keywords from a single string
$keywordString = 'keyword1 keyword2';
$keywords = explode(' ', $keywordString);
Product::where(function($query) use ($keywords) { // for product table
foreach ($keywords as $keyword)
{
$query->orWhere('sku_prin', 'like', "%$keyword%");
}
})
->orWhereHas('sku', function($query) use ($keywords) { // for sku table
foreach ($keywords as $keyword)
{
$query->orWhere('sku', '=', $keyword);
}
});

Get the ID of a selected value from a dropdown selectbox using Laravel 5.1

I've been on this for a while, what I want to do is to get the id of the selected value from a select box. Using this snippet, it doesn't get the id and I wonder why. I'm confused what what could be wrong
This code is to get the id of the selected value:
private static function compareCompany(ProductRequest $productRequest){
$companyPicked = $productRequest->companyname;
$listedCompanies = Company::where('user_id', '=', Auth::user()->id);
$companies = new Company;
if($companies->user_id === Auth::user()->id)
{
foreach($listedCompanies as $company) {
if($company->companyname === $companyPicked)
{
return $company->id;
}
}
}
}
This is to create a new product using the id returned for a company
public function store(ProductRequest $productRequest)
{
$product = new Product;
$company = new Company;
if($productRequest->isMethod('post')){
$product->user_id = Auth::user()->id;
$product->company_id = $this->compareCompany($productRequest);
$product->companyname = $productRequest->companyname;
$product->productname = $productRequest->productname;
$product->save();
return redirect()->route('companyindex')->with('message', 'Your question has been posted.');
}else{
return redirect('company-create')->withErrors($productRequest)->withInput();
}
}
THis is the view:
<p>{!! Form::select('companyname', array('' => 'Select a Company') + $listCompanies) !!} </p>
THis is the code used in binding the returned value to the view:
public function create()
{
$listCompanies = Company::where('user_id', '=', Auth::user()->id)->orderBy('companyname', 'desc')->lists('companyname', 'companyname')->toArray();
return view('product.create')
->with('listCompanies', $listCompanies);
}
I suspect that the problem is with your compareCompany method. There are a couple of issues there.
Here's your code (cleaned up a bit):
private static function compareCompany(ProductRequest $productRequest)
{
$companyPicked = $productRequest->companyname;
$listedCompanies = Company::where('user_id', '=', Auth::user()->id);
$companies = new Company;
if($companies->user_id === Auth::user()->id)
{
foreach($listedCompanies as $company) {
if($company->companyname === $companyPicked)
{
return $company->id;
}
}
}
}
And here are the issues:
$listedCompanies = Company::where('user_id', '=', Auth::user()->id);
This statement doesn't actually produce a list of companies. You need to add ->get() at the end, like this:
$listedCompanies = Company::where('user_id', '=', Auth::user()->id)->get();
$companies = new Company;
This creates a new, uninitialized instance of the Company model. On the next line you go on to check the user_id property of that instance, but it's not set to anything because it's brand new. So the first if() statement always fails. It's not clear what you're trying to do here, so I'm not sure how to fix this one. My guess is that you're trying to filter out only the companies that belong to the current user, but you've already done that with your Company::where(...)->get(), so this is not necessary.
Change ->lists('companyname', 'companyname') to ->lists('companyname', 'id')
It will return the id of the table of Company model.

how to save a collection form in symfony 1.4

I have created a collection form in symfony 1.4 and Propel 1.5 and everything displays properly but I cannot get the form to save to the database.
The form is used to edit multiple users at once.
I found this question and I implemented the suggestion of extending my collectionForm class with sfFormPropel, but when I do that I run out of memory. I cannot find what is being pulled from the database that would fill up the processes memory.
In my new save function I am not even doing anything.
Any ideas?
class ContactCollectionForm extends sfFormPropel
{
public function getModelName()
{
return 'ContactCollectionForm';
}
public function retrieveSubObject($fieldname, $model)
{
switch($fieldname)
{
default:
break;
}
return array();
}
public function save($con = null)
{
}
public function configure()
{
$user = $this->getOption('user');
$embedded = $this->getOption('embedded');
$custom = $this->getOption('custom');
$contact_list = $this->getOption('contact_list');
$cf = $custom['form'];
if(!array_key_exists(0, $cf['fields']['field']))
$cf['fields']['field'] = array($cf['fields']['field']);
$use_fields = array();
for($i=0;$i<count($contact_list);$i++)
{
foreach($cf['fields']['field'] as $field)
{
if($field['type'] == 'object')
{
// embed object form (ala: PersonData, Coordinate etc...)
$model = $field['model'];
$model_form = $model.'Form';
$sub_object = $contact_list[$i];
$sub_form = new $model_form($sub_object, array('user' => $user, 'embedded' => true, 'custom' => $field['fields']));
$this->embedForm($field['name'], $sub_form);
array_push($use_fields, $field['name']);
} // end field type == object
else
{
// standard form field
$this->setWidget($field['name'], CustomWidgetExtender::createSfWidget($field, $user, $this));
$this->widgetSchema->setLabel($field['name'], $field['label']);
if(trim($field['default']) != '')
$this->setDefault($field['name'], $field['default']);
// add field name to use_fields array
array_push($use_fields, $field['name']);
} // end field type != object
}
}
}
}
I ended up doing a rough hack by processing each form manually rather than trying to shoehorn this type of form into the symfony 1.x form framework.

Yii form model validation- either one is required

I have two fields on the form ( forgotpassword form ) username and email Id . User should enter one of them . I mean to retrieve the password user can enter user name or the email id . Could some one point me the validation rule for this ?
Is there any inbuilt rule I can use ?
( Sorry if it is already discussed or if I missed)
Thanks for your help
Regards
Kiran
I was trying to solve same problem today. What I've got is the code below.
public function rules()
{
return array(
// array('username, email', 'required'), // Remove these fields from required!!
array('email', 'email'),
array('username, email', 'my_equired'), // do it below any validation of username and email field
);
}
public function my_required($attribute_name, $params)
{
if (empty($this->username)
&& empty($this->email)
) {
$this->addError($attribute_name, Yii::t('user', 'At least 1 of the field must be filled up properly'));
return false;
}
return true;
}
General idea is to move 'required' validation to custom my_required() method which can check if any of field is filled up.
I see this post is from 2011 however I couldn't find any other solution for it. I Hope it will work for you or other in the future.
Enjoy.
Something like this is a bit more generic and can be reused.
public function rules() {
return array(
array('username','either','other'=>'email'),
);
}
public function either($attribute_name, $params)
{
$field1 = $this->getAttributeLabel($attribute_name);
$field2 = $this->getAttributeLabel($params['other']);
if (empty($this->$attribute_name) && empty($this->$params['other'])) {
$this->addError($attribute_name, Yii::t('user', "either {$field1} or {$field2} is required."));
return false;
}
return true;
}
Yii2
namespace common\components;
use yii\validators\Validator;
class EitherValidator extends Validator
{
/**
* #inheritdoc
*/
public function validateAttributes($model, $attributes = null)
{
$labels = [];
$values = [];
$attributes = $this->attributes;
foreach($attributes as $attribute) {
$labels[] = $model->getAttributeLabel($attribute);
if(!empty($model->$attribute)) {
$values[] = $model->$attribute;
}
}
if (empty($values)) {
$labels = '«' . implode('» or «', $labels) . '»';
foreach($attributes as $attribute) {
$this->addError($model, $attribute, "Fill {$labels}.");
}
return false;
}
return true;
}
}
in model:
public function rules()
{
return [
[['attribute1', 'attribute2', 'attribute3', ...], EitherValidator::className()],
];
}
I don't think there is a predefined rule that would work in that case, but it would be easy enough to define your own where for username and password fields the rule was "if empty($username . $password) { return error }" - you might want to check for a min length or other field-level requirements as well.
This works for me:
['clientGroupId', 'required', 'when' => function($model) {
return empty($model->clientId);
}, 'message' => 'Client group or client selection is required'],
You can use private property inside model class for preventing displays errors two times (do not assign error to model's attribute, but only add to model without specifying it):
class CustomModel extends CFormModel
{
public $username;
public $email;
private $_addOtherOneOfTwoValidationError = true;
public function rules()
{
return array(
array('username, email', 'requiredOneOfTwo'),
);
}
public function requiredOneOfTwo($attribute, $params)
{
if(empty($this->username) && empty($this->email))
{
// if error is not already added to model, add it!
if($this->_addOtherOneOfTwoValidationError)
{
$this->addErrors(array('Please enter your username or emailId.'));
// after first error adding, make error addition impossible
$this->_addOtherOneOfTwoValidationError = false;
}
return false;
}
return true;
}
}
don't forget "skipOnEmpty" attr. It cost me some hours.
protected function customRules()
{
return [
[['name', 'surname', 'phone'], 'compositeRequired', 'skipOnEmpty' => false,],
];
}
public function compositeRequired($attribute_name, $params)
{
if (empty($this->name)
&& empty($this->surname)
&& empty($this->phone)
) {
$this->addError($attribute_name, Yii::t('error', 'At least 1 of the field must be filled up properly'));
return false;
}
return true;
}
Yii 1
It can be optimized of course but may help someone
class OneOfThemRequiredValidator extends \CValidator
{
public function validateAttribute($object, $attribute)
{
$all_empty = true;
foreach($this->attributes as $_attribute) {
if (!$this->isEmpty($object->{$_attribute})) {
$all_empty = false;
break;
}
}
if ($all_empty) {
$message = "Either of the following attributes are required: ";
$attributes_labels = array_map(function($a) use ($object) {
return $object->getAttributeLabel($a);
}, $this->attributes);
$this->addError($object, $_attribute, $message . implode(',',
$attributes_labels));
}
}
}
yii1
public function rules(): array
{
return [
[
'id', // attribute for error
'requiredOneOf', // validator func
'id', // to params array
'name', // to params array
],
];
}
public function requiredOneOf($attribute, $params): void
{
$arr = array_filter($params, function ($key) {
return isset($this->$key);
});
if (empty($arr)) {
$this->addError(
$attribute,
Yii::t('yii', 'Required one of: [{attributes}]', [
'{attributes}' => implode(', ', $params),
])
);
}
}