my zend session name spacing does not work - oop

I am new to Zend and very keen to learn, so I would really appreciate some help and guidance.
I am trying to create a 'method in a class' that will save the session variables of product pages visited by members to a site i.e
i,e examplesite com/product/?producttype= 6
I want to save the number 6 in a session variable. I also do not want to have a global session for the entire site; I just want it for selected pages. So, I guess I have to have Zend_Session::start() on the selected page; but I am not clear how this should be done.
Should I instantiate it in the page view page. i.e products page or do this in the indexAction() method for the products page. I have attempted to instantiate it below but it did not work.
public function rememberLastProductSearched()
{ //my attempt to start a session start for this particular page.
Zend_Session::start();
}
$session->productSearchCategory = $this->_request->getParam('product-search-category');
return" $session->productSearchCategory ";
}
else
{
//echo " nothing there
return " $session->productSearchCategory";
//";
}
}
With the rememberLastProductSearched() method I was trying to get the method to first check whether the user had searched for a new product or just arrived at the page by default. i.e whether he had used the get() action to search for a new product. If the answer is no, then I wanted the system to check whether their had been a previous saved session variable. so in procedural syntax it would have gone like this:
if(isset($_Get['producttype']))
{
//$dbc database connection
$producttype = mysqli_real_escape_string($dbc,trim($_GET['producttype']));
}
else
if(isset($_SESSION['producttype'])){
$producttype = mysqli_real_escape_string($dbc,trim($_SESSION['producttype']));
}
Can you please help me with the Zend/oop syntax. I am totally confused how it should be?

you're asking about simple work flow in an action, it should begin something like:
//in any controller
public function anyAction()
{
//open seesion, start will be called if needed
$session = new Zend_Session_Namespace('products');
//get value
$productCategory = $this->getRequest()->getParam('producttype');
//save value to namespace
$session->productType = $productCategory;
//...
}
now to move this off to a separate method you have to pass the data to the method...
protected function rememberLastProductSearched($productType)
{
//open seesion, start will be called if needed
$session = new Zend_Session_Namespace('products');
$session->productType = $productType;
}
So now if you want to test for presence of a value...
//in any controller
public function anyAction()
{
//open seesion, call the namespace whenever you need to access it
$session = new Zend_Session_Namespace('products');
if (!isset($session->productType)) {
$productCategory = $this->getRequest()->getParam('producttype');
//save value to session
$this->rememberLastProductSearched($productCategory)
} else {
$productCategory = $session->productType;
}
}
That's the idea.
Be mindful of your work flow as it can sometimes be very simple to inadvertently overwrite your session values.

$session = new Zend_Session_Namespace("productSearch");
if ($this->getRequest()->getParam('producttype')) { //isset GET param ?
$session->productType = $this->getRequest()->getParam('producttype');
$searchedProductType = $session->productType;
} else { //take the session saved value
if ($session->productType) {
$searchedProductType = $session->productType;
}
}
//now use $searchedProductType for your query

Related

Shopware 6 custom element type image not showing any data on storefront

I have created my component to add some desired config fields in Shopware 6. Everything is working fine but one problem that is image is looking as it is being saved in the administration but is not showing any src or else in dump.
And here is my dump preiew having #data null.
can anyone tell what should I do else here?
I will be very thankful.
There is a guide in the docs that explains exactly what your case is.
You can likely extend the \Shopware\Core\Content\Media\Cms\ImageCmsElementResolver and override the getType function:
public function getType(): string
{
return 'my-component-name';
}
The important part of the default ImageCmsElementResolver is the loading the media information. For that you also need in your CMS element resolver. I explain some parts of the existing ImageCmsElementResolver so you can see which steps you need:
public function collect(CmsSlotEntity $slot, ResolverContext $resolverContext): ?CriteriaCollection
{
// read the configuration, that is defined in the Admin JS. Likely also media for you
$mediaConfig = $slot->getFieldConfig()->get('media');
// if this config is NOT containing useful info
if (
$mediaConfig === null
|| $mediaConfig->isMapped()
|| $mediaConfig->isDefault()
|| $mediaConfig->getValue() === null
) {
// return nothing
return null;
}
// otherwise use the configured value as mediaId to load the media entry from the database
$criteria = new Criteria([$mediaConfig->getStringValue()]);
$criteriaCollection = new CriteriaCollection();
$criteriaCollection->add('media_' . $slot->getUniqueIdentifier(), MediaDefinition::class, $criteria);
// return the criterias to execute later, when all needed entities for the CMS page are fetched
return $criteriaCollection;
}
Now the data is fetched and as next step you need to put it into a variable accessible from the Twig template. For this you write into the same CMS element resolver this:
public function enrich(CmsSlotEntity $slot, ResolverContext $resolverContext, ElementDataCollection $result): void
{
$config = $slot->getFieldConfig();
$image = new ImageStruct();
// this is important for accessing data in Twig
$slot->setData($image);
// read the config again
$mediaConfig = $config->get('media');
// if the configuration looks promising
if ($mediaConfig && $config->isStatic() && $mediaConfig->getValue()) {
$image->setMediaId($config->getStringValue());
// look up the media from the entity loading step
$searchResult = $result->get('media_' . $slot->getUniqueIdentifier());
if (!$searchResult) {
return;
}
/** #var MediaEntity|null $media */
$media = $searchResult->get($config->getValue());
// if we do not have a media, then skip it
if (!$media) {
return;
}
// set the media entity to the slot data we just assigned to the slot
$image->setMedia($media);
}
}
After that you should have more info in the slot variable in Twig to embed a media.

How to attach pdf from trigger to an object?

I'm a bit lost trying to attach a pdf with populated values from an opportunity record
Here is the code:
Trigger:
trigger OpportunityTrigger on Opportunity (after insert)
if(trigger.isAfter && trigger.isUpdate) {
opportunityTriggerHelper.attachFileToOpportunityRecord(trigger.new);
}
Helper Class:
private void attachFileToOpportunityRecord(List<Opportunity> lstOpp) {
List<Id> oppListIdsForAttach = new List<Id>();
for(Opportunity opp : lstOpp) {
oppListIdsForAttach .add(opp.Id);
}
attachFileToOpportunities(oppListIdsForAttach);
}
#future(callout=true)
private static void attachFileToOppotunities(List<Id> OpportunityIds) {
List<Attachment> attachList = new List<Attachment>();
for(Id oppId : opportunityIds) {
OpportunityPdfController file = new OpportunityPdfController();
file.getData(oppId);
PageReference pdfPage = Page.PdfAttachmentForOpp;
blob pdfBody;
pdfBody = pdfPage.getContent();
Attachment attach = new Attachment();
attach.Body = pdfBody;
attach.Name = 'Pdf file';
attach.IsPrivate = false;
attach.ParenId = oppId;
attachList.add(attach);
}
insert attachList;
}
VF Page:
<apex:page controller="OpportunityPdfController" renderAs="pdf">
<apex:repeat value="{!pricingDetails}" var="pd">
<apex:outputText>{!pd.basePrice}</apex:outputText>
</apex:repeat>
</apex:page>
VF Page Controller:
public with sharing class OpportunityPdfController {
public List<PricingDetailWrapper> pricingDetails {get;set;}
public void getData(Id opportunityId) {
List<Pricing_Detail__c> pdList = [
SELECT basePrice
FROM Pricing_Detail__c
WHERE OpportunityId =: opportunityId
];
for(Pricing_Detail__c pd : pdList) {
PricingDetailWrapper pdw = new PricingDetailWrapper();
pdw.basePrice = pd.basePrice;
pricingDetails.add(pdw);
}
}
public class PricingDetailWrapper {
public String basePrice {get;set;}
}
}
The result is whenever I update an opportunity it attaches the corresponding pdf file but it is blank and if I add for example the following to vf page body: "<h1> hello World!</h1>" this works and shows as expected, but this is not happening to what I required above.
You didn't really pass the opportunity id to the VF page. And I doubt this actually works at all? If you manually access the VF page as /apex/PdfAttachmentForOpp?id=006... does it render the content ok? I'm assuming it doesn't.
Fixing the page
You didn't specify constructor so SF generates one for you, fine. I think you need to add something like
public OpportunityPdfController(){
if(ApexPages.currentPage() != null){
Id oppId = ApexPages.currentPage().getParameters().get('id');
System.debug(oppId);
getData(oppId);
}
}
Add this, try to access the page passing valid opp id and see if it renders ok, if right stuff shows in debug log. /apex/PdfAttachmentForOpp?id=006...
(VF page constructors are bigger topic, this might be simpler with standardController + extension class)
Fixing the callout
VF page (especially accessed as callout) will not share memory with the OpportunityPdfController controller you've created in the code. New object of this class will be created to support the page and your file will be ignored. You might try to make-do with some static variable holding current opportunity's id but it feels bit yucky.
In normal execute anonymous try if this returns correct pdf:
PageReference pdfPage = Page.PdfAttachmentForOpp;
pdfPage.getParameters().put('id', '006...');
Blob pdfBody = pdfPage.getContent();
System.debug(pdfBody.toString());
If it works - use similar trick in the actual code, pass the id as url parameter.

ETrade API unattended authentication

Background
The ETrade authentication system has me creating a RequestToken, then executing an Authorization URL, which opens an ETrade page.
The user logs in to authorize the activity on their account.
They receive a pin, which they enter in my app.
I call ExchangeRequestTokenForAccessToken with the RequestToken and the Pin.
Then we are off and running.
Question
The problem is I'm creating a service that runs continuously in the background. There won't be any user to log in. Conversely, I won't be making any trades. Just crunching numbers, looking for stocks that meet certain criteria.
I can't figure how to get this to work unattended.
Thanks, Brad.
Previously, I have used a series of WebRequests and manually added headers to simulate the authorization pages. This worked until about a year ago when ETrade complicated their headers with something that appears to be tracking information. I now use http://watin.org/ to log in, and to strip the Auth Code.
Sloppy code looks like this:
using WatiN.Core; // IE Automation
...
// verify current thread in STA.
Settings.Instance.MakeNewIeInstanceVisible = false;
var ieStaticInstanceHelper = new IEStaticInstanceHelper();
Settings.AutoStartDialogWatcher = false;
using (ieStaticInstanceHelper.IE = new IE())
{
string authCode = "";
ieStaticInstanceHelper.IE.GoTo(GetAuthorizationLink());
if (ieStaticInstanceHelper.IE.ContainsText("Scheduled System Maintenance"))
{
throw new ApplicationException("eTrade down for maintenance.");
}
TextField user = ieStaticInstanceHelper.IE.TextField(Find.ByName("USER"));
TextField pass = ieStaticInstanceHelper.IE.TextField(Find.ById("txtPassword"));
TextField pass2 = ieStaticInstanceHelper.IE.TextField(Find.ByName("PASSWORD"));
Button btn = ieStaticInstanceHelper.IE.Button(Find.ByClass("log-on-btn"));
Button btnAccept = ieStaticInstanceHelper.IE.Button(Find.ByValue("Accept"));
TextField authCodeBox = ieStaticInstanceHelper.IE.TextField(Find.First());
if (user != null && pass != null && btn != null &&
user.Exists && pass2.Exists && btn.Exists)
{
user.Value = username;
pass2.Value = password;
btn.Click();
}
btnAccept.WaitUntilExists(30);
btnAccept.Click();
authCodeBox.WaitUntilExists(30);
authCode = authCodeBox.Value;
SavePin(authCode);
}
Current version of Brad Melton's code.
WatiN has changed and no longer contains the IE.AttachToIE function.
So, IEStaticInstanceHelper is now called StaticBrowserInstanceHelper, but that code is hard to find, so I've included it here.
class StaticBrowserInstanceHelper<T> where T : Browser {
private Browser _browser;
private int _browserThread;
private string _browserHwnd;
public Browser Browser {
get {
int currentThreadId = GetCurrentThreadId();
if (currentThreadId != _browserThread) {
_browser = Browser.AttachTo<T>(Find.By("hwnd", _browserHwnd));
_browserThread = currentThreadId;
}
return _browser;
}
set {
_browser = value;
_browserHwnd = _browser.hWnd.ToString();
_browserThread = GetCurrentThreadId();
}
}
private int GetCurrentThreadId() {
return Thread.CurrentThread.GetHashCode();
}
}
ETrade's login pages have changed as well. They have several. All the login pages I checked consistently had a USER field and a PASSWORD field, but the login buttons had various names that look fragile. So if this doesn't work, that's the first thing I'd check.
Second, if I go directly to the auth page, it prompts to log in, but then it frequently doesn't take you to the auth page.
I got more consistent results by going to the home page to log in, then going to the auth page.
static public string GetPin(string username, string password, string logonLink, string authLink) {
// Settings.Instance.MakeNewIeInstanceVisible = false;
var StaticInstanceHelper = new StaticBrowserInstanceHelper<IE>();
Settings.AutoStartDialogWatcher = false;
// This code doesn't always handle it well when IE is already running, but it won't be in my case. You may need to attach to existing, depending on your context.
using (StaticInstanceHelper.Browser = new IE(logonLink)) {
string authCode = "";
// Browser reference was failing because IE hadn't started up yet.
// I'm in the background, so I don't care how long it takes.
// You may want to do a WaitFor to make it snappier.
Thread.Sleep(5000);
if (StaticInstanceHelper.Browser.ContainsText("Scheduled System Maintenance")) {
throw new ApplicationException("eTrade down for maintenance.");
}
TextField user = StaticInstanceHelper.Browser.TextField(Find.ByName("USER"));
TextField pass2 = StaticInstanceHelper.Browser.TextField(Find.ByName("PASSWORD"));
// Class names of the Logon and Logoff buttons vary by page, so I find by text. Seems likely to be more stable.
Button btnLogOn = StaticInstanceHelper.Browser.Button(Find.ByText("Log On"));
Element btnLogOff = StaticInstanceHelper.Browser.Element(Find.ByText("Log Off"));
Button btnAccept = StaticInstanceHelper.Browser.Button(Find.ByValue("Accept"));
TextField authCodeBox = StaticInstanceHelper.Browser.TextField(Find.First());
if (user != null && btnLogOn != null &&
user.Exists && pass2.Exists && btnLogOn.Exists) {
user.Value = username;
pass2.Value = password;
btnLogOn.Click();
}
Thread.Sleep(1000);
if (StaticInstanceHelper.Browser.ContainsText("Scheduled System Maintenance")) {
Element btnContinue = StaticInstanceHelper.Browser.Element(Find.ByName("continueButton"));
if (btnContinue.Exists)
btnContinue.Click();
}
btnLogOff.WaitUntilExists(30);
// Here we go, finally.
StaticInstanceHelper.Browser.GoTo(authLink);
btnAccept.WaitUntilExists(30);
btnAccept.Click();
authCodeBox.WaitUntilExists(30);
authCode = authCodeBox.Value;
StaticInstanceHelper.Browser.Close();
return authCode;
}
}
Being able to automate it like this means that I no longer care about how long the token is valid. Thanks BradM!
This was amazingly helpful. I used your code plus what was posted here to automate this (because tokens expire daily): E*Trade API frequently returns HTTP 401 Unauthorized when fetching an access token but not always
I made two edits:
Changed the authorize URL to what was posted here: https://seansoper.com/blog/connecting_etrade.html
For the log on button, changed it to search by ID: Button btnLogOn = StaticInstanceHelper.Browser.Button(Find.ById("logon_button"));
I ran into issues with Watin and setting up the Apartmentstate. So did this:
static void Main(string[] args)
{
System.Threading.Thread th = new Thread(new ThreadStart(TestAuth));
th.SetApartmentState(ApartmentState.STA);
th.Start();
th.Join();
}
Then put the code in the TestAuth method.

Failed to set unsafe attribute upload yii

uploading images has been causing much trouble. I'm always having two problems. First it always fail to upload or unable to find the files when using asset manager when it is there in the folder!! So when my controller says unlink the file, it won't unlink anything. Second, it'll show failed to set unsafe attribute when my rules are set to safe.
Here is my rule:
array('product_image,product_gallery_1, product_gallery_2, product_gallery_3, product_gallery_4, product_gallery_5, product_gallery_6', 'file','types'=>'jpg, jpeg, gif, png','allowEmpty'=>true, 'on'=>'update', 'safe'=>true),
on view I do have
'htmlOptions'=>array('enctype'=>'multipart/form-data'),
Here's a section on update controller. It does upload multiple images:
public function actionUpdate($id)
{
$model=$this->loadModel($id);
$old_img = $model->product_image;
if(isset($_POST['Product']))
{
$model->update_date = time();
$model->product_approval_status = "N";
$t_product_image = $model->product_image;
$model->attributes=$_POST['Product'];
$product_image=CUploadedFile::getInstance($model,'product_image');
$storeid = $model->store_id;
$date = date('mdy');
$rnd = rand(0,9999);
$f_product_image = "{$rnd}-{$date}{$storeid}-{$product_image}";
//main img
if(!empty($product_image))
{
$model->product_image = $f_product_image;
}
else
{
if(file_exists(Yii::app()->basePath.'/images/shop/thumbnail/thumb_'.$model->product_image))
unlink(Yii::app()->basePath.'/images/shop/thumbnail/thumb_'.$old_img);
if(file_exists(Yii::app()->basePath.'/images/shop/product/'.$model->product_image)&& ($old_img !==null))
unlink(Yii::app()->basePath.'/images/shop/thumbnail/'.$old_img);
$model->product_image = $t_product_image;
}
if($model->save())
{
$url = Yii::app()->basePath.'/images/shop/product/';
$thumb = Yii::app()->basePath.'/images/shop/thumbnail/';
if(!empty($product_image))
{
$product_image->saveAs($url.$f_product_image);
$this->createThumb($url.$f_product_image, $thumb.'thumb_' . $f_product_image);
}
$this->redirect(array('submitted'));
}
}
$this->render('update',array(
'model'=>$model,
));
}
The problem is with your current defined scenario
In your model, you specify the attributes safe under the update scenario.
array('product_image, ..., product_gallery_6', ... 'on'=>'update', 'safe'=>true)
Therefore, in your controller, you need to set the scenario.
$model = new MyActiveRecord('update');
Since you are using a function you may not have control over, you can explicitly set this:
$model->scenario = 'update';
Note that your problem might be elsewhere, depending on the operation of loadModule(), because ceratin ActiveRecord scenarios are automatically loaded. See the section 'CActiveRecord scenarios' in the linked page.

Pros & cons bean vs SSJS?

I was trying to build a bean that always retrieves the same document ( a counter document), gets the current value, increment it and save the document with the new value. Finally it should return the value to the calling method and that would get me a new sequential number in my Xpage.
Since the Domino objects cannot be serialized or singleton'ed what's the benefit creating a bean doing this, over creating a SSJS function doing the exact same thing?
My bean must have calls to session, database, view and document, which then will be called every time.
The same within the SSJS-function except for session and database.
Bean:
public double getTransNo() {
try {
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
View view = db.getView("vCount");
view.refresh();
doc = view.getFirstDocument();
transNo = doc.getItemValueDouble("count");
doc.replaceItemValue("count", ++transNo);
doc.save();
doc.recycle();
view.recycle();
} catch (NotesException e) {
e.printStackTrace();
}
return transNo;
}
SSJS:
function getTransNo() {
var view:NotesView = database.getView("vCount");
var doc:NotesDocument = view.getFirstDocument();
var transNo = doc.getItemValueDouble("count");
doc.replaceItemValue("count", ++transNo);
doc.save();
doc.recycle();
view.recycle();
return transNo;
}
Thank you
Both pieces of code are not good (sorry to be blunt).
If you have one document in your view, you don't need a view refresh which might be queued behind a refresh on another view and be very slow. Presumably you are talking about a single sever solution (since replication of the counter document would for sure lead to conflicts).
What you do in XPages is to create a Java class and declare it as application bean:
public class SequenceGenerator {
// Error handling is missing in this class
private double sequence = 0;
private String docID;
public SequenceGenerator() {
// Here you load from the document
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
View view = db.getView("vCount");
doc = view.getFirstDocument();
this.sequence = doc.getItemValueDouble("count");
this.docID = doc.getUniversalId();
Utils.shred(doc, view); //Shred currenDatabase isn't a good idea
}
public synchronized double getNextSequence() {
return this.updateSequence();
}
private double updateSequence() {
this.sequence++;
// If speed if of essence I would spin out a new thread here
Session session = ExtLibUtil.getCurrentSession();
Database db = session.getCurrentDatabase();
doc = db.getDocumentByUnid(this.docID);
doc.ReplaceItemValue("count", this.sequence);
doc.save(true,true);
Utils.shred(doc);
// End of the candidate for a thread
return this.sequence;
}
}
The problem for the SSJS code: what happens if 2 users hit that together? At least you need to use synchronized there too. Using a bean makes it accessible in EL too (you need to watch out not to call it too often). Also in Java you can defer the writing back to a different thread - or not write it back at all and in your class initialization code read the view with the actual documents and pick the value from there.
Update: Utils is a class with static methods:
/**
* Get rid of all Notes objects
*
* #param morituri = the one designated to die, read your Caesar!
*/
public static void shred(Base... morituri) {
for (Base obsoleteObject : morituri) {
if (obsoleteObject != null) {
try {
obsoleteObject.recycle();
} catch (NotesException e) {
// We don't care we want go get
// rid of it anyway
} finally {
obsoleteObject = null;
}
}
}
}