magento 1.9.2.2 add to cart with custom option image not working - upgrade

I have a script which adds a product in cart with custom option image and it was working perfect till CE 1.9.2.1 but after up gradation to latest version it troughs exception that Please specify the product's required option(s).
Below is code, please guide me if something has to change for newer version .
<?php
$productId = xxx;
$image = 'path to image(tested image exists)';
$product = Mage::getModel('catalog/product')->load($product_id);
$cart = Mage::getModel('checkout/cart');
$cart->init();
$params = array(
'product' => $productId,
'qty' => 1,
'options' => array(
$optionId3inmycase => array(
'type' => 'image/tiff',
'title' => $image,
'quote_path' => '/media/custom/' . $image,
'order_path' => '/media/custom/' . $image,
'fullpath' => Mage::getBaseDir() . '/media/custom/' . $image,
'secret_key' => substr(md5(file_get_contents(Mage::getBaseDir() . '/media/custom/' . $image)), 0, 20)),
)
);
$request = new Varien_Object();
$request->setData($params);
$cart->addProduct($product, $request);
$cart->save();
if ($itemId > 0) {
$cartHelper = Mage::helper('checkout/cart');
$cartHelper->getCart()->removeItem($itemId)->save();
}
Mage::getSingleton('checkout/session')->setCartWasUpdated(true);
$this->_redirect('checkout/cart/');
?>

One quick solution is to rewrite Mage_Catalog_Model_Product_Option_Type_File class and change validateUserValue() function.
around line 129, replace
$fileInfo = $this->_getCurrentConfigFileInfo();
with
$fileInfo = null;
if (isset($values[$option->getId()]) && is_array($values[$option->getId()])) {
// Legacy style, file info comes in array with option id index
$fileInfo = $values[$option->getId()];
} else {
/*
* New recommended style - file info comes in request processing parameters and we
* sure that this file info originates from Magento, not from manually formed POST request
*/
$fileInfo = $this->_getCurrentConfigFileInfo();
}
but its old code and will have APPSEC-1079 security issue.
and to download image this uploaded image in order detail etc. add this function in same model class.
/**
* changed the image save address as we are saving image in custom
* Main Destination directory
*
* #param boolean $relative If true - returns relative path to the webroot
* #return string
*/
public function getTargetDir($relative = false)
{
$fullPath = Mage::getBaseDir('media') . DS . 'custom';
return $relative ? str_replace(Mage::getBaseDir(), '', $fullPath) : $fullPath;
}

Related

How can I save url and local images?

I'm working on a project that uses the Spotify API. I get all the top tracks and top artists from users. I save the information into my database, the image that I get from Spotify is a URL image. Like this https://i.scdn.co/image/ab67616d0000b27300d5163a674cf57fe79866a6
In my CMS I want to be able to change the image of an artist or track. For this, I copied the code from the docs https://backpackforlaravel.com/docs/4.1/crud-fields#image-1 "public function setImageAttribute($value) and put this in my model. Everything is working fine for this part, I can upload a new image and it saves into my public folder and gets displayed in my table.
The problem that I have is that when a new user logs in and the artist/track data needs to be saved in the database it goes through this setImageAttribute($value) in the model and doesn't add the URL into the database column.
Is there a way that I can add the URL image without going through the setImageAttribute function in the model?
The controller:
//get Top Artists
$client = new Client([]);
$topartists = $client->get('https://api.spotify.com/v1/me/top/artists?time_range=medium_term&limit=25&offset=5', [
'headers' => [
'Authorization' => 'Bearer ' . $token,
],
]);
$listtopartists = json_decode($topartists->getBody(), true);
$p = 25;
foreach ($listtopartists['items'] as $topartist) {
$artist = TopArtist::where('artist_id', $topartist['id'])->first();
$artist_genre = json_encode($topartist['genres']);
if ($artist === null) {
$new_artist = TopArtist::create([
'artist_id' => $topartist['id'],
'name' => $topartist['name'],
'genres' => $artist_genre,
'users_popularity' => $p,
'popularity' => $topartist['popularity'],
'image' => $topartist['images'][0]['url'],
'url' => $topartist['external_urls']['spotify'],
]);
$spotify_user->topArtists()->attach($new_artist->id);
} else {
$exists = $spotify_user->topArtists()->where('spotify_user_id', $spotify_user->id)->where('top_artist_id', $artist->id)->get();
if ($exists->isEmpty()) {
$spotify_user->topArtists()->attach($artist->id);
}
}
$p--;
}
The Model:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
class TopArtist extends Model
{
use \Backpack\CRUD\app\Models\Traits\CrudTrait;
use HasFactory;
/**
* The attributes that are mass assignable.
*
* #var array
*/
protected $fillable = [
'artist_id',
'name',
'popularity',
'users_popularity',
'url',
'image',
'genres',
'status',
];
/**
* The attributes that should be cast to native types.
*
* #var array
*/
protected $casts = [
'id' => 'integer',
'status' => 'boolean',
];
public function setImageAttribute($value)
{
$attribute_name = "image";
// or use your own disk, defined in config/filesystems.php
$disk = config('backpack.base.root_disk_name');
// destination path relative to the disk above
$destination_path = "public/storage/artists";
// if the image was erased
if ($value==null) {
// delete the image from disk
\Storage::disk($disk)->delete($this->{$attribute_name});
// set null in the database column
$this->attributes[$attribute_name] = null;
}
// if a base64 was sent, store it in the db
if (Str::startsWith($value, 'data:image'))
{
$image = \Image::make($value)->encode('jpg', 90);
$filename = md5($value.time()).'.jpg';
\Storage::disk($disk)->put($destination_path.'/'.$filename, $image->stream());
\Storage::disk($disk)->delete($this->{$attribute_name});
$public_destination_path = Str::replaceFirst('public/', '', $destination_path);
$this->attributes[$attribute_name] = $public_destination_path.'/'.$filename;
}
}
public function spotifyUsers()
{
return $this->belongsToMany(\App\Models\SpotifyUser::class);
}
}
I fixed it, I had to add an else statement after
if (Str::startsWith($value, 'data:image'))
in the model where I set $this->attributes[$attribute_name] = $value;.

How to update images stored as strings in extbase?

I have recently created an extension that has a file upload feature. I decided to store it as a string. I have used it like this:
In the controller:
public function initializeAction() {
if ($this->arguments->hasArgument('blog')) {
$this->arguments->getArgument('blog')->getPropertyMappingConfiguration()->setTargetTypeForSubProperty('image', 'array');
}
}
In the model:
/**
* Returns the image
*
* #return string $image
*/
public function getImage()
{
return $this->image;
}
/**
* Sets the image
*
* #param \array $image
* #return void
*/
public function setImage(array $image)
{
die(debug::var_dump($image));
if (!empty($image['name']))
{
$imageName = $image['name'];
$imageTempName = $image['tmp_name'];
$basicFileUtility = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Utility\\File\\BasicFileUtility');
$imageNameNew = $basicFileUtility->getUniqueName($imageName, \TYPO3\CMS\Core\Utility\GeneralUtility::getFileAbsFileName('uploads/tx_myextension/'));
\TYPO3\CMS\Core\Utility\GeneralUtility::upload_copy_move($imageTempName, $imageNameNew);
$this->image = basename($imageNameNew);
}
}
The TCA:
'config' => [
'type' => 'group',
'internal_type' => 'file',
'uploadfolder' => 'uploads/tx_myextension',
'show_thumbs' => 1,
'size' => 1,
'allowed' => $GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'],
'disallowed' => ''
],
In my form:
<f:form action="update" name="blog" object="{blog}" >
<f:form.upload property="image" class="form-control" />
...
Now this works perfectly when a new object is created, however when I try to change this image (using updateAction), I get the this error message:
Exception while property mapping at property path "image":
No converter found which can be used to convert from "string" to "array".
I would like to avoid uploading via FAL or writing my own conversion. I'm hoping that I just missed something trivial.
Take an look to an update script from tt_address to see how to make an update.
To make it short: You must read all entries of your files and move them from upload dir to your file storage and then you must add an file_reference which connect the sys_file with your domain model:
GeneralUtility::upload_copy_move(
PATH_site . 'uploads/tx_myextension/' . $imageName,
PATH_site . 'fileadmin/tx_myextension/' . $imageName);
$fileObject = $this->storage->getFile('fileadmin/tx_myextension/' . $imageName);
if ($fileObject instanceof \TYPO3\CMS\Core\Resource\File) {
$this->fileRepository->add($fileObject);
$dataArray = [
'uid_local' => $fileObject->getUid(),
'tablenames' => 'tx_your_domain_model',
'fieldname' => 'image',
'uid_foreign' => 'your model uid',
'table_local' => 'sys_file',
'cruser_id' => 999,
'pid' => 'your_model_pid',
'sorting_foreign' => $imageCount,
'sys_language_uid' => 0
];
if ($this->getDatabaseConnection()->exec_INSERTquery('sys_file_reference', $dataArray)) {
$imageCount++;
}
}
It was a very stupid and simple mistake from my part. In the edit form I forgot to add this: enctype="multipart/form-data"
<f:form action="update" enctype="multipart/form-data" name="blog" object="{blog}" >

Typo3: How to upload a file and create a file reference?

i'll try to upload a file (or later multiple files) in FE. This works, like my current code. But how can i get a file reference of this file now?
/**
*
* #var array $fileData
* #var integer $feUserId
* #return \TYPO3\CMS\Extbase\Domain\Model\FileReference
*/
private function uploadFile($fileData, $feUserId) {
$storageRepository = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Core\\Resource\\StorageRepository');
$storage = $storageRepository->findByUid(1); # Fileadmin = 1
$saveFolder = $storage->getFolder($this->settings['uploadFolder']);
// Datei speichern
$fileObject = $storage->addFile($fileData['tmp_name'], $saveFolder, $feUserId.'_'.$fileData['name']);
// Dateiobjekt
$repositoryFileObject = $storage->getFile($fileObject->getIdentifier());
die(\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($repositoryFileObject));
#$newFileReference = $this->objectManager->get('TYPO3\CMS\Extbase\Domain\Model\FileReference');
#$newFileReference->setOriginalResource($repositoryFileObject);
return $newFileReference;
}
There should be something like »setFileReference« by now, but I can not find the like in the API http://typo3.org/api/typo3cms/class_t_y_p_o3_1_1_c_m_s_1_1_core_1_1_resource_1_1_file_reference.html
Well, you may wanna use the following script as temporary solution, which uses the datamap process to create file references.
$sys_file_uid = $file->getUid();
$tt_content_uid = 42;
$tt_content_pid = 1337;
// Do not directly insert a record into sys_file_reference, as this bypasses all sanity checks and automatic updates done!
$data = array();
$data['sys_file_reference']['NEW' . $sys_file_uid] = array(
'uid_local' => $sys_file_uid,
'table_local' => 'sys_file',
'uid_foreign' => $tt_content_uid,
'tablenames' => 'tt_content',
'fieldname' => 'image',
'pid' => $tt_content_pid,
);
$data['tt_content'][$tt_content_uid] = array('image' => 'NEW' . $sys_file_uid);
$tce = t3lib_div::makeInstance('t3lib_TCEmain'); // create TCE instance
$tce->start($data, array());
$tce->process_datamap();
if ($tce->errorLog) {
// Error - Reference not created
// t3lib_utility_Debug::viewArray($tce->errorLog);
}
else {
// Success - Reference created
}
after hitting google for a while i figured out a article that sounded quite well to me.. gonna examine it tomorrow:
http://insight.helhum.io/post/85015526410/file-upload-using-extbase-and-fal-in-typo3-6-2
(just in case somebody else needs this before i could test it)

Zend Framework Router Hostname and Multi-Language support

Last two days I was fighting with Zend Framework Router and still didn't find solution.
Existing project has 2 different modules which work with the same domain e.g. www.domain1.com:
default - Contains public information, can be accessed via www.domain1.com
admin - Administrator interface, can be accessed via www.domain1.com/admin
Project is multi-langual and to keep language code it is transmitted as first parameter of every URL, e.g. www.domain1.com/en/ www.domain1.com/en/admin
Part of code which takes care is next plugin:
class Foo_Plugin_Language extends Zend_Controller_Plugin_Abstract
{
const LANGUAGE_KEY = 'lang';
public function routeStartup(Zend_Controller_Request_Abstract $request)
{
$languagesRegexp = implode('|', array_map('preg_quote', $this->_bootstrap->getLanguages()));
$routeLang = new Zend_Controller_Router_Route(
':' . self::LANGUAGE_KEY,
array(self::LANGUAGE_KEY => $this->_bootstrap->getLanguage()->toString()),
array(self::LANGUAGE_KEY => $languagesRegexp)
);
$router = $this->getFrontController()->getRouter();
$router->addDefaultRoutes();
$chainSeparator = '/';
foreach ($router->getRoutes() as $name => $route) {
$chain = new Zend_Controller_Router_Route_Chain();
$chain
->chain($routeLang, $chainSeparator)
->chain($route, $chainSeparator);
$new_name = $this->_formatLanguageRoute($name);
$router->addRoute($new_name, $chain);
}
protected function _formatLanguageRoute($name)
{
$suffix = '_' . self::LANGUAGE_KEY;
if (substr($name, -strlen($suffix)) == $suffix) return $name;
return $name . '_' . self::LANGUAGE_KEY;
}
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$lang = $request->getParam(self::LANGUAGE_KEY, null);
$this->_bootstrap->setLanguage($lang);
$actual_lang = $this->_bootstrap->getLanguage()->toString();
$router = $this->getFrontController()->getRouter();
$router->setGlobalParam(self::LANGUAGE_KEY, $lang);
// Do not redirect image resize requests OR get js, css files
if (preg_match('/.*\.(jpg|jpeg|gif|png|bmp|js|css)$/i', $request->getPathInfo())) {
return true;
}
// redirect to appropriate language
if ($lang != $actual_lang) {
$redirector = Zend_Controller_Action_HelperBroker::getStaticHelper('redirector');
$params = array(self::LANGUAGE_KEY => $actual_lang);
$route = $this->_formatLanguageRoute($router->getCurrentRouteName());
return $redirector->gotoRouteAndExit($params, $route, false);
}
}
}
One of the first question is what do you think about such way to provide multi-lang support. What I've noticed is that all this chains dramatically decrease operation speed of the server, response time from the server is about 4 seconds...
Main question is: Currently I have to implement such feature: I have domain www.domain2.com that should work just with single module e.g. "foobar"... and it should be available via second url... or, of course, it should work like www.domain1.com/en/foobar by default...
To provide this functionality in Bootstrap class I'be implemented such part of code
// Instantiate default module route
$routeDefault = new Zend_Controller_Router_Route_Module(
array(),
$front->getDispatcher(),
$front->getRequest()
);
$foobarHostname = new Zend_Controller_Router_Route_Hostname(
'www.domain2.com',
array(
'module' => 'foobar'
)
);
$router->addRoute("foobarHostname", $foobarHostname->chain($routeDefault));
And that is not working and as I've found routeDefault always rewrite found correct model name "foobar" with value "default"
Then I've implemented default router like this:
new Zend_Controller_Router_Route(
':controller/:action/*',
array(
'controller' => 'index',
'action' => 'index'
);
);
But that still didn't work, and started work without language only when I comment "routeStartup" method in Foo_Plugin_Language BUT I need language support, I've played a lot with all possible combinations of code and in the end made this to provide language support by default:
class Project_Controller_Router_Route extends Zend_Controller_Router_Route_Module
{
/**
* #param string $path
* #param bool $partial
* #return array
*/
public function match($path, $partial = false)
{
$result = array();
$languageRegexp = '%^\/([a-z]{2})(/.*)%i';
if (preg_match($languageRegexp, $path, $matches)) {
$result['lang'] = $matches[1];
$path = $matches[2];
}
$parentMatch = parent::match($path);
if (is_array($parentMatch)) {
$result = array_merge($result, $parentMatch);
}
return $result;
}
}
So language parameter was carefully extracted from path and regular processed left part as usual...
But when I did next code I was not able to get access to the foobar module via www.domain2.com url, because of module name in request was always "default"
$front = Zend_Controller_Front::getInstance();
/** #var Zend_Controller_Router_Rewrite $router */
$router = $front->getRouter();
$dispatcher = $front->getDispatcher();
$request = $front->getRequest();
$routerDefault = new Project_Controller_Router_Route(array(), $dispatcher, $request);
$router->removeDefaultRoutes();
$router->addRoute('default', $routerDefault);
$foobarHostname = new Zend_Controller_Router_Route_Hostname(
'www.domain2.com',
array(
'module' => 'foobar'
)
);
$router->addRoute("foobar", $foobarHostname->chain($routerDefault));
Instead of summary:
Problem is that I should implement feature that will provide access for the secondary domain to the specific module of ZendFramework, and I should save multi-language support. And I cannot find a way, how to manage all of this...
Secondary question is about performance of chain router, it makes site work very-very slow...
The way I have solved problem with multilanguage page is in this thread:
Working with multilanguage routers in Zend (last post).
Ofcourse my sollution need some caching to do, but I think it will solve your problem.
Cheers.

Form bindAndSave method doesn't save files

Parsing an XML feed and save it this way:
$action = new CouponActionForm();
$values = array(
'name' => $offer->name,
'link' => $offer->url,
'description' => $offer->description,
'discount' => $offer->discount,
'price' => $offer->price
);
$files = $this->getImages($offer->picture);
if(!$action->bindAndSave($values, $files)){
echo $action->renderGlobalErrors();
die('BAD THING');
}
Here is the getImages method:
private function getImages($url){
$ret = array('error' => 0);
$tmp_dir = sys_get_temp_dir();
$tmp_file = tempnam($tmp_dir, 'kupon');
if(copy($url, $tmp_file)){
$ret['tmp_name'] = $tmp_file;
$ret['name'] = basename($url);
$ret['size'] = filesize($tmp_file);
$ret['type'] = 'image/jpeg';
return array('filename' => $ret);
}else{
die('HELP');
}
}
Why do I use form saving instead of object saving with ->fromArray method ? Well, I already made the form and all validation, so I don't want to implement the same thing twice (DRY), but as important, I actually don't know how to use validators in doctrine model.
So the problem is that the form doesn't save files in specific directory specified by the path property of sfValidatorFile validator, but it exists in tmp directory.