I would like to implement a shippo webhook in order to know the delivery status of my shipments, their documentation is a little unclear... I don't know what information will be passed to my script
I have setup a test URL and a live one and have added those to my account, in API -> Webhooks.
Whenever my script is requested either via the live or test URLs I get empty arrays, no data. Please help me figure this out. Anyone from Shippo??
Here is what I have so far:
<?php
namespace MW\PublicBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ShippoController extends Controller
{
/**
* #Route("/shippo/", name="shippo_web_hook")
* #Method("GET|POST")
*/
public function webHookAction(Request $request)
{
if ($request->getMethod() == 'POST'){
$post = $request->request->all();
} elseif ($request->getMethod() == 'GET'){
$post = $request->query->all();
}
file_put_contents(__DIR__ . '/shippo.txt', print_r($post,true));
$mailer = $this->get('swiftmailer.mailer.transactional');
$messageObject = \Swift_Message::newInstance()
->setSubject('Shippo Webhook Posted DATA')
->setFrom('emai#example.com')
->setTo('email#example.com')
->setBody(print_r($post,true) . "\n" . print_r($_REQUEST,true) . "\n" . print_r($_POST,true));
try {
$mailer->send($messageObject);
} catch (\Exception $e){
}
return new Response('OK');
}
}
As you can see I should be able to catch some incoming data but I get nothing but empty arrays..
Indeed my script is receiving straight up JSON, thank you to mootrichard for sharing the requestb.in tool, with it I was able to see all the headers and data sent, just for future reference this is what I got.
namespace MW\PublicBundle\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ShippoController extends Controller
{
/**
* #Route("/shippo/", name="shippo_web_hook")
* #Method("GET|POST")
*/
public function webHookAction(Request $request)
{
$headers = $request->headers->all();
$content = $request->getContent();
if (!empty($content))
{
$post = json_decode($content, true);
}
if (isset($headers['x-shippo-event'][0]) && $headers['x-shippo-event'][0] == 'track_updated' &&
(isset($headers['content-type'][0]) && $headers['content-type'][0] == 'application/json')){
if (count($post) > 0) {
file_put_contents(__DIR__ . '/shippo.txt', print_r($headers, true) . "\n\n\n" . print_r($post, true));
}
}
return new Response('OK');
}
}
And the contents of shippo.txt is:
Array
(
[host] => Array
(
[0] => ******
)
[user-agent] => Array
(
[0] => python-requests/2.9.1
)
[content-length] => Array
(
[0] => 1021
)
[accept] => Array
(
[0] => */*
)
[accept-encoding] => Array
(
[0] => gzip, deflate
)
[content-type] => Array
(
[0] => application/json
)
[shippo-api-version] => Array
(
[0] => 2014-02-11
)
[x-forwarded-for] => Array
(
[0] => **.**.***.**
)
[x-original-host] => Array
(
[0] => *****
)
[x-shippo-event] => Array
(
[0] => track_updated
)
[x-php-ob-level] => Array
(
[0] => 0
)
)
Array
(
[messages] => Array
(
)
[carrier] => usps
[tracking_number] => 123
[address_from] => Array
(
[city] => Las Vegas
[state] => NV
[zip] => 89101
[country] => US
)
[address_to] => Array
(
[city] => Spotsylvania
[state] => VA
[zip] => 22551
[country] => US
)
[eta] => 2017-09-05T01:35:10.231
[original_eta] => 2017-09-05T01:35:10.231
[servicelevel] => Array
(
[token] => usps_priority
[name] => Priority Mail
)
[metadata] => Shippo test webhook
[tracking_status] => Array
(
[status] => UNKNOWN
[object_created] => 2017-08-31T01:35:10.240
[status_date] => 2017-08-31T01:35:10.240
[object_id] => ac0e0c060d6e43b295c460414ebc831f
[location] => Array
(
[city] => Las Vegas
[state] => NV
[zip] => 89101
[country] => US
)
[status_details] => testing
)
[tracking_history] => Array
(
[0] => Array
(
[status] => UNKNOWN
[object_created] => 2017-08-31T01:35:10.240
[status_date] => 2017-08-31T01:35:10.240
[object_id] => ac0e0c060d6e43b295c460414ebc831f
[location] => Array
(
[city] => Las Vegas
[state] => NV
[zip] => 89101
[country] => US
)
[status_details] => testing
)
)
[transaction] =>
)
According to their documentation they're just sending you a straight JSON response, not a key/value pair of data that you can get from the request parameters. You would want to do something like this instead:
$data = json_decode($request->getContent(), true);
This documentation is from Silex but it's using the same components as Symfony to receive a accept a JSON request body.
Related
so I have a controller that essentially submits an edit of a category for approval by sending an email to the admin. This was fine before I decided to add in an actions table to store action history (e.g. category: edit).
The problem that's arose is that, by using entityManager to add data to the actions table, it's automatically updating the category entity due to the Event Watcher.
I tried a google and couldn't find anything on setting entityManager to one entity only.
This is my current controller:
<?php
namespace App\Controller\Category\Edit;
use App\Entity\Action;
use App\Entity\Category;
use App\Entity\User;
use App\Form\Category\EditCategoryType;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
class EditController extends Controller
{
public function edit($id, Request $request, \Swift_Mailer $mailer)
{
$this->denyAccessUnlessGranted('IS_AUTHENTICATED_FULLY');
$category = $this->getDoctrine()->getRepository(Category::class)->find($id);
$categoryGuru = $category->getGuru();
$guruName = $categoryGuru->getUsername();
$category->setGuru($categoryGuru);
$form = $this->createForm(EditCategoryType::class, $category);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$formData = $form->getData();
$name = $formData->getName();
$description = $formData->getDescription();
$newGuruId = $form['guru']->getData();
$newGuru = $this->getDoctrine()->getRepository(User::class)->find($newGuruId);
$actionId = $this->setAction();
$approveUrl = $category->getId();
$approveUrl .= '/'. $actionId;
$approveUrl .= '/'. $name;
$approveUrl .= '/'. $description;
$approveUrl .= '/'. $newGuru->getId();
$message = (new \Swift_Message('Category Edit Request - '. $category->getName()))
->setFrom('some#email.com')
->setTo('another#email.co.uk')
->setBody(
$this->renderView(
'emails/category/edit-request.html.twig',
array(
'category' => $category->getName(),
'category_new_name' => $name,
'description' => $category->getDescription(),
'category_new_description' => $description,
'guru' => $guruName,
'category_new_guru' => $newGuru->getUsername(),
'category_new_guru_id' => $newGuru->getId(),
'category_id' => $category->getId(),
'category_author' => $this->getUser()->getUsername(),
'approve_url' => $approveUrl,
'action_id' => $actionId
)
),
'text/html'
);
$mailer->send($message);
$this->addFlash('success', 'Category Edit Submitted for Review.');
return $this->redirectToRoute('category_list');
}
return $this->render(
'category/edit.html.twig',
array('form' => $form->createView(), 'category' => $category, 'guru' => $categoryGuru)
);
}
# this was originally a part of the above controller
# tried separating to see if it would work - didn't
public function setAction()
{
$action = new Action();
$entityManager = $this->getDoctrine()->getManager();
# set action data
$action->setDate(new \DateTime());
$action->setUserId($this->getUser()->getId());
$action->setDescription($this->getUser()->getUsername(). ' has edited a category');
$action->setStatus('pending');
$action->setType('Category: edit');
$entityManager->persist($action);
$entityManager->flush();
return $action->getId();
}
}
The answer I came to was, don't. Use the Repository class:
CategoryRepository.php
public function addAction($userId, $description, $status, $type)
{
$entityManager = $this->getEntityManager();
$connection = $entityManager->getConnection();
$date = date('Y-m-d H:i:s', strtotime('now'));
$connection->insert(
'action',
array(
'type' => $type,
'user_id' => $userId,
'date' => $date,
'description' => $description,
'status' => $status
)
);
return $entityManager->getConnection()->lastInsertId();
}
then call it in the controller - no longer classing entityManager issues.
I have a DataObject Confirmation that looks like this:
<?php
class Confirmation extends DataObject
{
private static $db = array(
'Reimbursement' => 'Money',
'SomeText' => 'Varchar(255)'
);
private static $has_one = array(
'Page' => 'Page'
);
public function getCMSFields()
{
$fields = parent::getCMSFields();
$reimbursementField = MoneyField::create('Reimbursement');
$someTextField = TextField::create('SomeText');
$reimbursementField->setAllowedCurrencies(array('SEK'));
$fields->addFieldsToTab(
'Root.Main',
array(
$reimbursementField,
$someTextField
)
);
return $fields;
}
public function onBeforeWrite()
{
parent::onBeforeWrite();
if($this->isChanged('Reimbursement')) SS_Log::log( print_r ( 'changed', true ), SS_Log::WARN );
}
}
if($this->isChanged('Reimbursement')) in the onBeforeWrite() function will always evaluate to true. Regardless of what is being changed. Even when saving without making any changes - it will fire the log function.
Edit: Checking the stacktrace with SS_Backtrace::backtrace() in Money.php's setValue() function I find that every time I save the Confirmation DataObject, setValue() is called without passing the $markChanged parameter set to false, from DataObject.php line 1281, resulting in the $isChanged flag being set to true, and the field always behaving as if it has changed.
When logging DataObject's getChangedFields() function I get the output below (when saving without making any changes). Notice the Reimbursement field being present, and before being empty. What am I missing here?
[22-Mar-2016 16:28:07] Warning at framework/model/DataObject.php line 2597: Array
(
[Reimbursement] => Array
(
[before] =>
[after] => Money Object
(
[currency:protected] => SEK
[amount:protected] => 25000
[isChanged:protected] => 1
[locale:protected] =>
[currencyLib:protected] => Zend_Currency Object
(
[_options:protected] => Array
(
[position] => 8
[script] =>
[format] =>
[display] => 2
[precision] => 2
[name] => svensk krona
[currency] => SEK
[symbol] => kr
[locale] => sv_SE
[value] => 0
[service] =>
[tag] => Zend_Locale
)
)
[allowedCurrencies:protected] =>
[value:protected] =>
[tableName:protected] =>
[name:protected] => Reimbursement
[arrayValue:protected] =>
[defaultVal:protected] =>
[failover:protected] =>
[customisedObject:protected] =>
[objCache:ViewableData:private] => Array
(
)
[class] => Money
[extension_instances:protected] => Array
(
)
[beforeExtendCallbacks:protected] => Array
(
)
[afterExtendCallbacks:protected] => Array
(
)
)
[level] => 2
)
)
I am facing this error in laravel 5.
Here is the controller function in which I am facing the error (in the line where I do ($vendor->client[0]->id) :
public function show($username) {
Log::info('Vendors Controller : show function with '.$username);
$vendor = VendorProfile::where('username', $username)->first();
$output = print_r($vendor,1);
Log::info($output);
if($vendor) {
Log::info('client '. $vendor->client);
$client = Client::find($vendor->client[0]->id);
$title = $client->profile->company_name;
$output is printed as:
[2015-06-15 21:34:43] local.INFO: App\models\VendorProfile Object
(
[table:protected] => vendor_profile
[guarded:protected] => Array
(
[0] => id
)
[morphClass:protected] => MorphVendorProfile
[connection:protected] =>
[primaryKey:protected] => id
[perPage:protected] => 15
[incrementing] => 1
[timestamps] => 1
[attributes:protected] => Array
(
[id] => 16
[first_name] => some name
[last_name] =>
[company_name] => some name
[contact_number] => 1234567890
[username] => username
[profile_photo] =>
[photo_mime_type] =>
[cover_photo] =>
[cover_photo_mime_type] =>
[address] =>
[city_id] => 1
[zip_code] =>
[story] =>
[establishment_date] =>
[pricing] =>
[education] =>
[services_offered] =>
[assignments_undertook] =>
[advanced_fees] =>
[equipments] =>
[about_service] =>
[coins] => 500
[created_at] => 2015-06-15 20:21:45
[updated_at] => 2015-06-15 20:21:45
)
[original:protected] => Array
(
[id] => 16
[first_name] => some name
[last_name] =>
[company_name] => some name
[contact_number] => 1234567890
[username] => username
[profile_photo] =>
[photo_mime_type] =>
[cover_photo] =>
[cover_photo_mime_type] =>
[address] =>
[city_id] => 1
[zip_code] =>
[story] =>
[establishment_date] =>
[pricing] =>
[education] =>
[services_offered] =>
[assignments_undertook] =>
[advanced_fees] =>
[equipments] =>
[about_service] =>
[coins] => 500
[created_at] => 2015-06-15 20:21:45
[updated_at] => 2015-06-15 20:21:45
)
[relations:protected] => Array
(
)
[hidden:protected] => Array
(
)
[visible:protected] => Array
(
)
[appends:protected] => Array
(
)
[fillable:protected] => Array
(
)
[dates:protected] => Array
(
)
[casts:protected] => Array
(
)
[touches:protected] => Array
(
)
[observables:protected] => Array
(
)
[with:protected] => Array
(
)
[exists] => 1
)
The models VendorProfile and Client are connected as:
in VendorProfile model:
protected $morphClass = 'MorphVendorProfile';
// Defining 'Polymorphic' Relationship with Client Model
public function client() {
return $this->morphMany('App\models\Client', 'profile');
}
and I have an alias in my config/app.php:
'MorphVendorProfile'=> 'App\models\VendorProfile'
in Client model :
public function profile() {
return $this->morphTo();
}
Update:
This error has occurred while migrating the code from laravel 4.2 to laravel 5. So right now, when I run the previous code that was based on 4.2 version with the SAME database, and it didn't throw me any error, so I think problem is with the code, not database. I am certain that there is a problem with 'morph' relationships, I had to modify a bit in the process of migrating to make it work on other pages.
here is the morphTo function in my Eloquent/Model.php:
/**
* Define a polymorphic, inverse one-to-one or many relationship.
*
* #param string $name
* #param string $type
* #param string $id
* #return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function morphTo($name = null, $type = null, $id = null)
{
// If no name is provided, we will use the backtrace to get the function name
// since that is most likely the name of the polymorphic interface. We can
// use that to get both the class and foreign key that will be utilized.
if (is_null($name))
{
list(, $caller) = debug_backtrace(false, 2);
$name = snake_case($caller['function']);
}
list($type, $id) = $this->getMorphs($name, $type, $id);
// If the type value is null it is probably safe to assume we're eager loading
// the relationship. When that is the case we will pass in a dummy query as
// there are multiple types in the morph and we can't use single queries.
if (is_null($class = $this->$type))
{
Log::info('eagerly loading');
return new MorphTo(
$this->newQuery(), $this, $id, null, $type, $name
);
}
// If we are not eager loading the relationship we will essentially treat this
// as a belongs-to style relationship since morph-to extends that class and
// we will pass in the appropriate values so that it behaves as expected.
else
{
Log::info('not eagerly loading');
Log::info($class);
$instance = \App::make('\App\models\\'.$class);
$output = print_r($instance,1);
Log::info('*'.$output.'*');
return new MorphTo(
$instance->newQuery(), $this, $id, $instance->getKeyName(), $type, $name
);
}
}
Check your namespaces. Make sure that the polymorphic relationship that you have setup references the full namespace in both the Vendor and Client models
Can someone point me out to how can I retrieve the up to date version number of a plugin via its web page from the WordPress plugin directory?
For example, in http://wordpress.org/extend/plugins/advanced-custom-fields/
I want to grab the 4.0.1
You can interact with the WordPress Repository API:
The WordPress Repository API is the API used to fetch plug-in and theme information.
http://wp.tutsplus.com/tutorials/creative-coding/interacting-with-wordpress-plug-in-theme-api/
I use the following in a plugin of mine. Check the comments for details.
/**
* Function used to print the data
*/
function b5f_print_repository_info( $echo = true )
{
// Grab data and do nothing if fail
$plugin_data = b5f_get_repository_info();
if( false === $plugin_data )
return;
// Custom function used to format the rating
$total_downloads = number_format_i18n( $plugin_data['total_downloads'] );
$rating = b5f_format_rating( $plugin_data['rating'] / 20 );
$updated = date_i18n( get_option( 'date_format' ), strtotime( $plugin_data['updated'] ) );
$num_rating = number_format_i18n( $plugin_data['num_ratings'] );
$version = $plugin_data['version'];
if( $echo )
echo 'Your stuff using the variables above.';
else
return 'Your stuff using the variables above.';
}
/**
* Call WP API and return the data
*/
function b5f_get_repository_info()
{
$plugin_url = 'http://wpapi.org/api/plugin/advanced-custom-fields.json';
// Cache
$cache = get_transient( 'my_plugin_transient' );
if( false !== $cache )
return $cache;
// Fetch the data
if( $response = wp_remote_retrieve_body( wp_remote_get( $plugin_url ) ) )
{
// Decode the json response
if( $response = json_decode( $response, true ) )
{
// Double check we have all our data
if( !empty( $response['added'] ) )
{
set_transient( 'my_plugin_transient', $response, 720 );
return $response;
}
}
}
return false;
}
/**
* Auxiliary function to format the Rating
*/
function b5f_format_rating( $number, $cents = 1 )
{
// Check if value can be dealt with
if( !is_numeric( $number ) )
return $number;
if( !$number ) {
$rating = ($cents == 2) ? '0.00' : '0';
}
else {
if( floor( $number ) == $number ) {
$rating = number_format( $number, ($cents == 2 ? 2 : 0 ) );
}
else {
$rating = number_format( round( $number, 2 ), ($cents == 0 ? 0 : 2 ) );
}
}
return $rating;
}
And the following is a shortened version of the response, description and stats fields are really big.
Array
(
[added] => 2011-03-25
[author] => Array
(
[name] => Elliot Condon
[url] => http://www.elliotcondon.com/
[profile] => http://profiles.wordpress.org/elliotcondon
)
[average_downloads] => 1415.61
[contributors] => Array
(
[contributor-Elliot Condon] =>
)
[download_link] => http://downloads.wordpress.org/plugin/advanced-custom-fields.zip
[hits] => 0
[homepage] => http://www.advancedcustomfields.com/
[last_update_details] => 2013-04-30 17:36:06
[last_update_stats] => 2013-04-30 17:36:05
[name] => Advanced Custom Fields
[num_ratings] => 905
[rating] => 98
[requires] => 3.0.0
[sections] => Array
(
[description] => <p>Advanced Custom Fields is
)
[slug] => advanced-custom-fields
[stats] => Array
(
[2011-11-09] => 683
)
[tags] => Array
(
[tag-admin] => admin
[tag-advanced] => advanced
[tag-custom] => custom
[tag-custom-field] => custom field
[tag-edit] => edit
[tag-field] => field
[tag-file] => file
[tag-image] => image
[tag-magic-fields] => magic fields
[tag-matrix] => matrix
[tag-more-fields] => more fields
[tag-post] => Post
[tag-repeater] => repeater
[tag-simple-fields] => simple fields
[tag-text] => text
[tag-textarea] => textarea
[tag-type] => type
)
[tested] => 3.5.1
[total_days] => 539
[total_downloads] => 763012
[type] => plugin
[updated] => 2013-04-30
[version] => 4.1.0
)
I have to test an array with inner arrays.
my array looks like the following.
$testdata=Array
(
[0] => Array
(
[label] => 'Ammy'
[idr] => 'user7'
[rel] => 7
)
[1] => Array
(
[label] => 'sidh'
[idr] => user8
[rel] => 8
)
[2] => Array
(
[label] => 'Alan'
[idr] => 'user9'
[rel] => 9
)
)
in this case my requirement is to assert whether the keys for inner array present using assertArrayHasKey() assertion of phpunit. I tried to do it like this
foreach ($testdata as $values) {
//print_r($values);
$this->assertArrayHasKey('idr', $values);
$this->assertArrayHasKey('rel', $values);
}
but this is not working for me. even the control does not go inside the foreach() loop.
please suggest me some solution for this.
foreach ($testdata as $values) {
//print_r($values);
$this->assertArrayHasKey('idr', $values);
$this->assertArrayHasKey('rel', $values);
}
this part in my question works fine. actually i was not getting the array itself in the test scenario. so it was not going inside the foreach(). now it is solved. i had a mistake in passing args to the function.
This is the example usage
/** Example One */
$testData = [
[
'label' => '',
'idr' => ''
], [
'label' => '',
'idr' => ''
], [
'label' => '',
'idr' => ''
]
];
$this->assertArrayStructure([
['label','idr']
], $testData);
/** Example Two */
$testData = [
'result' => true,
'data' => [
'col_1' => '',
'col_2' => ''
],
];
$this->assertArrayStructure([
'result', 'data' => ['col_1', 'col_2']
], $testData);
/** Example Three */
$testData = [
'result' => true,
'data' => [
[
'col_1' => '',
'col_2' => ''
],
[
'col_1' => '',
'col_2' => ''
]
],
];
$this->assertArrayStructure([
'result', 'data' => ['col_1', 'col_2']
], $testData, true);
Here is function
/**
* Like as assertJsonStructure
*
* #param array $data
* #param array $structures #e.g., [ key_1, key_2 => [child_key]]
* #param bool $dataHasMultiArray #e.g., $data[0][key]
*/
protected function assertArrayStructure(array $structures, array $data, bool $dataHasMultiArray = false)
{
$i = 0;
foreach ($structures as $index => $key) {
if (!is_numeric($index)) {
$this->assertArrayHasKey($index, $data);
}
if (is_string($key)) {
$this->assertArrayHasKey($key, $data);
}
if (is_array($key)) {
$this->assertArrayHasKeys($key, $dataHasMultiArray ? $data[$index][$i] : $data[$index]);
$i++;
}
}
}
/**
* #param $structures
* #param array $data
*/
protected function assertArrayHasKeys($structures, array $data)
{
foreach ($structures as $key) {
$this->assertArrayHasKey($key, $data);
}
}
You can also use
assertArraySubset()
from: https://phpunit.de/manual/current/en/appendixes.assertions.html#appendixes.assertions.assertArraySubset
another solution is to compare arrays and then check if true:
$arrays_are_equal = ($array1 == $array2); // or === if you want identical
$this->assertTrue($arrays_are_equal);
Another option is to test only the first element of the multidimensional Array, like this:
$this->assertArrayHasKey('idr', $testdata[0]);
$this->assertArrayHasKey('rel', $testdata[0]);
I believe that that control is not going inside the foreach loop.
Remove whole :
$testdata= Array
(
[0] => Array
(
[label] => 'Ammy'
[idr] => 'user7'
[rel] => 7
)
[1] => Array
(
[label] => 'sidh'
[idr] => user8
[rel] => 8
)
[2] => Array
(
[label] => 'Alan'
[idr] => 'user9'
[rel] => 9
)
)