Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?
 
PerlMonks  

Bod

by Bod (Chaplain)
on Nov 15, 2020 at 00:48 UTC ( #11123653=user: print w/replies, xml ) Need Help??

Long time amateur coder since growing up with a ZX Spectrum and BBC Micro...

Introduced to Perl in the early 1990's which quickly became the language of choice. Built many websites and backend applications using Perl including the sites for my property business:
Lets Delight - company site
Lets Stay - booking site
Also a few simple TK based desktop apps to speed things up.

Guilty of only learning what I need to get the job done - a recipe for propagating bad practice and difficult to maintain code...difficult for me so good luck to anyone else!

Now (Nov 2020) decided to improve my coding skills although I'm not really sure what "improve" means in this context. It seems Perl and best practice have come along way since I last checked in and my programming is approach is stuck in the last decade.

Onwards and upwards...


Find me on LinkedIn


CPAN Release

Business::Stripe::WebCheckout


Posts by Bod
Injecting a value into HTTP::Header in Seekers of Perl Wisdom
1 direct reply — Read more / Contribute
by Bod
on Apr 18, 2021 at 16:52

    As part of connecting to LinkedIn using LWP::Authen::OAuth2, I have decided to write a sub-class of LWP::Authen::OAuth2::ServiceProvider which is designed to be sub-classed for exactly this kind of application. This way, it will hopefully be useful to other people.

    However, I need to override a method which has not been designed to be overridden! The LinkedIn API doesn't comply with the OAuth2 spec. The token_type parameter is mandatory but missing. Therefore, I am trying to inject it into the response from LinkedIn by sub-classing the method, adding the missing parameter and then calling the method in the super class. But I am not managing to get the injection to work.

    Here is the sub in LWP::Authen::OAuth2::ServiceProvider that I am overridding:

    The author has commented the point it fails as # Someone failed to follow the spec...!

    This is my sub that overrides the above...

    sub construct_tokens { my ($self, $oauth2, $response) = @_; my $content = eval {$response->decoded_content}; eval {decode_json($content)}; $response->push_header( 'token_type', 'Bearer' ) unless $@; $self->SUPER::construct_tokens($oauth2, $response); }
    I'm trying to set token_type as Bearer so that the rest of the sub in the superclass doesn't complain.

    Is there a good way to to inject this parameter or am I approaching this in the wrong way?

Making distribution on Strawberry Perl for CPAN in Seekers of Perl Wisdom
2 direct replies — Read more / Contribute
by Bod
on Apr 16, 2021 at 19:09

    I've uploaded Business::Stripe::WebCheckout to CPAN as a developer release. Release 1 failed when Stripe couldn't be contacted. Release 2 has cured this problem and, so far, all the CPAN Testers reports have passed 😊

    However, Windows doesn't have gzip. So in Makefile.PL I have this line to use IO::Compress::Gzip which is included with Strawberry Perl:

    dist => { COMPRESS => q{perl -MIO::Compress::Gzip=gzip,:constants -e" +my $$in = $$ARGV[0]; gzip($$in => qq($$in.gz), q(Level) => Z_BEST_COM +PRESSION, q(BinModeIn) => 1) or die q(gzip failed); unlink $$in;"}, S +UFFIX => 'gz', },
    This is included in the tarball that was uploaded.

    Does this need changing in the release?
    My best guess is that it is OK as it is only used when building the distribution, not when installing it but I wanted to check this is correct.

Obtaining OAuth2 Access Token in Seekers of Perl Wisdom
2 direct replies — Read more / Contribute
by Bod
on Apr 16, 2021 at 10:07

    I am trying to connect to LinkedIn using LWP::Authen::OAuth2. Authorisation goes fine but when I come to exchange the authorisation token for an access token, I get this error:

    Endpoint: https://api.linkedin.com/v2/accessToken JSON: { "serviceErrorCode":65604, "message":"Empty oauth2 access token", "status":401 }
    That doesn't seem to make alot of sense to me as I would expect the OAuth2 Access Token to be empty in a request to get it!

    This is the bare bones of what I am doing...

    my $linkedin = LWP::Authen::OAuth2->new( client_id => 'xxxxxxx', client_secret => 'xxxxxxx', authorization_endpoint => 'https://api.linkedin.com/uas/oauth2/a +uthorization', token_endpoint => 'https://api.linkedin.com/v2/accessTok +en', redirect_uri => "https://$ENV{'HTTP_HOST'}/cgi-bin/pos +tdog.pl?command=authorize_linkedin", scope => 'w_member_social', save_tokens => \&save_linkedin_token, ); ######################### # LinkedIn button clicked sub linkedin { my $auth_url = $linkedin->authorization_url; print "Location: $auth_url\n\n"; exit 0; }
    The code above behaves as expected by going off to LinkedIn, authorising the app and calling the callback URL
    The callback URL does this:
    sub authorize_linkedin { my $token = $linkedin->request_tokens( code => $data{'code'}, ); print "Content-type: text-plain\n\n"; print "ERROR: $data{'error'}\n\nMessage: $data{'error_description' +}\n\n"; print "TOKEN: $token\n"; print $data{'code'}; exit 0; }
    The error (above) is generated at the request_tokens call. $data{'code'} contains the code passed as a query parameter to the callback URL.

    I feel I must be missing something obvious here...

[RFC] Module code and POD for CPAN in Meditations
6 direct replies — Read more / Contribute
by Bod
on Apr 13, 2021 at 17:27

    Having needed to implement a simple workflow for taking card payments by Stripe and knowing that I need to do the same again soon for a different product set, I have created a module that I think will be useful to other people. Existing modules to connect with Stripe either do not cater for the latest security measures of 3D card payments in Europe or are a wrapper for the Stripe API. Both of which are, of course useful. But I wanted to create something easy to use that can be used for the typical simple workflow required by many small businesses.

    This gives a simple workflow of adding products to a 'Trolley' and then sending the user directly to the Stripe hosted checkout. From there, Stripe returns to either a success URL if payment was taken successfully or a cancel URL if, for any reason, the transaction failed.

    Could you please provide me with some feedback regarding both the code and the POD ahead of uploading it to CPAN?

    I was thinking Business::Stripe::Simple as the module name - is that sensible?

    Note - at the moment, the examples in the documentation have not been tested. They will be before uploading!

    Thank you to the Monks who have helped me develop the skills to get the module this far and to the ones who will give useful feedback.
    It is very much appreciated.

    UPDATE:
    I have run the code through Perl::Critic and it passes at 'stern' but generates warnings at 'harsh'. One thing it has found are tab characters instead of spaces despite changing my editor to use spaces after this discussion. I will take out the tabs that have crept in from copying and pasting a few bits of code.

    On the suggestion of Critic, I have moved the declaration of $VERSION to after use strict;. Although I thought that had to be first for the CPAN toolchain?

Useless use of string in return statement in Seekers of Perl Wisdom
7 direct replies — Read more / Contribute
by Bod
on Apr 12, 2021 at 19:06

    Is there something strange about the way return treats conditions?

    I have this and it doesn't work as expected...

    sub get_ids { my ($self, %attrs) = @_; # Do stuff... my %result; # $result{'message'} = ''; if ($self->{'error'}) { $result{'status'} = 'error'; $result{'message'} = $self->{'error'}; } else { $result{'status'} = 'success'; $result{'api-key'} = $self->{'api-public'}; $result{'session'} = $intent_id; } return encode_json(\%result) if lc($attrs{'format'}) eq 'json'; return $result{'message'} or "$result{'api-key'}:$result{'session' +}"; # <- line 229 return "SOMETHING"; }
    If it is called as get_ids( 'format' => 'json' ); it works fine but asking it to return a text string returns undef and warns Useless use of string in void context at line 229. The way I think it should work is that if $result{'message'} evaluates as true, that will get returned but if it evaluates as false then "$result{'api-key'}:$result{'session'}" wil be returned instead.

    Can you explain why this is not behaving as expected?

    As an aside, in searching for an answer I found this post -> Useless use of string in void context
    There it is suggested that Perl reports the wrong line number for this warning so it is quite possible that I'm actually looking in the wrong place!

LinkedIn module in Seekers of Perl Wisdom
1 direct reply — Read more / Contribute
by Bod
on Apr 12, 2021 at 08:07

    Searching CPAN I was rather surprised that the only general modules for connecting to LinkedIn seem to be WWW::LinkedIn and Net::Linkedin::OAuth2 - both of these date from before Microsoft acquired LinkedIn 5 years ago...unbelievable that it is that long ago!

    Since the 5th April, Hootsuite have reduced the number of scheduled posts permitted on free accounts down to just 5 and we don't use the other features to warrant the paid price tag. We already have an automated Twitter scheduler and I want to add LinkedIn to it so just need something to authenticate, read the timestamp of the latest post and be able to create a post containing media.

    Before I write something to do this, are there any modules available or should I try out the modules above despite their age?

Pushing hash ref onto array ref in Seekers of Perl Wisdom
2 direct replies — Read more / Contribute
by Bod
on Apr 11, 2021 at 18:18

    A module I am creating has a blessed hash ref as is common. One of the hashes is a reference to an array of anonymous hashes created like so:

    sub new { my $class = shift; my %attrs = @_; my @products = ({ 'id' => 0, 'name' => 'Test', 'description' => 'Some test data', 'qty' => 1, 'price' => 1000, }); $attrs{'trolley'} = \@products; return bless \%attrs, $class; }
    Later on I want to push another anonymous hash onto @products.

    What is the best way to do this?
    Both of these push lines appear to work identically in testing

    sub add_product { my ($self, $product_data) = @_; # create $new_product hasfref push $self->{'trolley'}, $new_product; push @{$self->{'trolley'}}, $new_product; }
    However, I suspect there is some subtle difference between the two which might trip me up in the future!

    Is there a practical difference and is there a 'best' option to use?

How old is too old? in Meditations
4 direct replies — Read more / Contribute
by Bod
on Apr 08, 2021 at 16:01

    Over on Re: How Xerces validation access http schemas ?, hippo wrote "It's worth noting that the most recent versions of XML::Validate and XML::Xerces are from 15 years ago and may not play so well with modern systems". This immediately reminded me of a recent search for modules to connect with PayPal where I found Business::PayPal::IPN but didn't look any further than the date which is AUG 19, 2003.

    Clearly for things that connect to frequently evolving APIs, being quite up to date is pretty important unless the API allows use of a particular past version. But for things that don't change much, like XML, the need for recent updates is less apparent. As hippo puts it, they need to "play well with modern systems".

    So, when deciding whether to use a module, or anything else for that matter, how old is too old?

[RFC] Review of module code and POD in Meditations
6 direct replies — Read more / Contribute
by Bod
on Mar 31, 2021 at 15:45

    Esteemed Monks,

    For all past time we have connected directly to our CRM from the scripts that need to read or write the data. It has been my intention for a very long time to create some standard subroutines in a require file to do all the things we regularly need to do. Thanks to The Monastery, I now know I need to use a module to do this. So I have set about creating a suitable module. I also thought this would be a good opportunity to do it properly and include some POD. This module will never be used outside of our use case but it seems like good practice to include documentation.

    Could you please look over the code and documentation and for me before I go too much further and advise if I am making any horrible mistakes, what I can improve and how clear the documentation is...

    I have pulled out anything that could pose a security threat to Bod::Variables. Not just database username and password but also schema name and table names.

    The documentation as generated by pod2html is here.

Not an ARRAY reference error in Seekers of Perl Wisdom
2 direct replies — Read more / Contribute
by Bod
on Mar 28, 2021 at 18:45

    Much as I thought my understanding of references was going well, again misunderstanding strikes and I seek your wisdom.

    The following code is a bare-bones test and demonstration of the issue I am having. In the module's new method I read an array from a database which is simulated here by @columns.

    package crmtest; use strict; use warnings; sub new { my ($class, $env) = @_; my @columns = ('one', 'two', 'three', 'four'); my $self = bless { 'env' => $env, 'column' => \@columns, # <-- Reference t +o an array given here }, $class; return $self; } sub add { my ($self, $fields) = @_; foreach my $field(@$self->{'column'}) { # <-- Not an AR +RAY reference here print $field . " -- " . $self->{'column'} . "\n"; # do stuff... } } 1;

    Here is what I think is happening...
    I put a reference to the @columns array into the blessed anonymous hash to which $self holds the reference. The constructor returns the reference to the anonymous hash.
    Later on, in the add method I want to iterate over the array so I dereference the key in the anonymous hash using @$self->{'column'}

    Except I get the error - Not an ARRAY reference at crmtest.pm line 18
    So I added the print line and removed the dereferencing so I tried iterating over $self->{'column'} without the '@'. This tells me that both $field and $self->{'column'} are the same as I would expect and that they are both ARRAY(0xe3c1f0)

    This is the code I am using to test the bare-bones of the module:

    use strict; use warnings; use lib('.'); use crmtest; my $crm = crmtest->new('someenv'); my $cols = { 'col1' => 'something', 'col2' => 'anotherthing', 'four' => 'moretest', }; $crm->add($cols);
    Am I not creating the reference to @columns properly or am I trying to dereference wrongly or I am completely missing how references work?

    EDIT:
    To test what I am doing I have tried an even simpler version:

    my @columns = ('one', 'two', 'three', 'four'); my $ref = \@columns; print @$ref;
    And that does exactly what I expected - prints out the array. So it seems I am not totally on the wrong track!

Log In?
Username:
Password:

What's my password?
Create A New User
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (2)
As of 2021-04-22 02:59 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?