I'm trying to implement GCM server using PHP and Zend Framework on Google App Engine. So far it works fine locally, but fails with this message when uploaded to App Engine:
Here is the code:
$ids = '....my device id....';
$apiKey = '...my api key...';
$data = array( 'message' => 'Hello World!' );
$gcm_client = new Client();
$gcm_client->setApiKey($apiKey);
$gcm_message = new Message();
$gcm_message->setTimeToLive(86400);
$gcm_message->setData($data);
$gcm_message->setRegistrationIds($ids);
$response = $gcm_client->send($gcm_message);
var_dump($response);
And it fails with this error message:
PHP Fatal error: Uncaught exception 'ErrorException' with message
'stream_socket_client(): unable to connect to
android.googleapis.com:443 (Unknown error 4294967295)' in
/base/data/home/..../backend:v1.375711862873219029/vendor/zendframework/zend-http/Zend/Http/Client/Adapter/Socket.php:253
I know App Engine doesn't allow socket connections and offers urlFetch wrapper for http and https, but how do I tell Zend Framework to use this transport?
Try enabling Billing. As far as I remember sockets are enabled only for paid apps.
This won't charge you anything (unless you exceed free quota) but should get rid of the error.
Promoted this from a comment - I ended up making my own class implementing Zend\Http\Client\Adapter\AdapterInterface that uses URLFetch by opening a URL using the usual fopen with stream context to send POST request. Although this works I'm not sure it's the best way. Would prefer to use the framework capabilities, if possible.
I'm not sure if this is going to help anyone, as both ZendFramework and AppEngine have evolved since I asked the question, but here is the adapter I've implemented:
use Zend\Http\Client\Adapter\AdapterInterface;
use Zend\Http\Client\Adapter\Exception\RuntimeException;
use Zend\Http\Client\Adapter\Exception\TimeoutException;
use Zend\Stdlib\ErrorHandler;
class URLFetchHttpAdapter implements AdapterInterface
{
protected $stream;
protected $options;
/**
* Set the configuration array for the adapter
*
* #param array $options
*/
public function setOptions($options = array())
{
$this->options = $options;
}
/**
* Connect to the remote server
*
* #param string $host
* #param int $port
* #param bool $secure
*/
public function connect($host, $port = 80, $secure = false)
{
// no connection yet - it's performed in "write" method
}
/**
* Send request to the remote server
*
* #param string $method
* #param \Zend\Uri\Uri $url
* #param string $httpVer
* #param array $headers
* #param string $body
*
* #throws \Zend\Loader\Exception\RuntimeException
* #return string Request as text
*/
public function write($method, $url, $httpVer = '1.1', $headers = array(), $body = '')
{
$headers_str = '';
foreach ($headers as $k => $v) {
if (is_string($k))
$v = ucfirst($k) . ": $v";
$headers_str .= "$v\r\n";
}
if (!is_array($this->options))
$this->options = array();
$context_arr = array("http" =>
array( "method" => $method,
"content" => $body,
"header" => $headers_str,
"protocol_version" => $httpVer,
'ignore_errors' => true,
'follow_location' => false,
) + $this->options
);
$context = stream_context_create($context_arr);
ErrorHandler::start();
$this->stream = fopen((string)$url, 'r', null, $context);
$error = ErrorHandler::stop();
if (!$this->stream) {
throw new \Zend\Loader\Exception\RuntimeException('', 0, $error);
}
}
/**
* Read response from server
*
* #throws \Zend\Http\Client\Adapter\Exception\RuntimeException
* #return string
*/
public function read()
{
if ($this->stream) {
ErrorHandler::start();
$metadata = stream_get_meta_data($this->stream);
$headers = join("\r\n", $metadata['wrapper_data']);
$contents = stream_get_contents($this->stream);
$error = ErrorHandler::stop();
if ($error)
throw $error;
$this->close();
//echo $headers."\r\n\r\n".$contents;
return $headers."\r\n\r\n".$contents;
} else {
throw new RuntimeException("No connection exists");
}
}
/**
* Close the connection to the server
*
*/
public function close()
{
if (is_resource($this->stream)) {
ErrorHandler::start();
fclose($this->stream);
ErrorHandler::stop();
$this->stream = null;
}
}
/**
* Check if the socket has timed out - if so close connection and throw
* an exception
*
* #throws TimeoutException with READ_TIMEOUT code
*/
protected function _checkSocketReadTimeout()
{
if ($this->stream) {
$info = stream_get_meta_data($this->stream);
$timedout = $info['timed_out'];
if ($timedout) {
$this->close();
throw new TimeoutException(
"Read timed out after {$this->options['timeout']} seconds",
TimeoutException::READ_TIMEOUT
);
}
}
}
}
public function sendAndroidPushNotification($registration_ids, $message)
{
$registrationIds = array($registration_ids);
$msg = array(
'message' => $message,
'title' => 'notification center',
'vibrate' => 1,
'sound' => 1
);
$fields = array(
'registration_ids' => $registrationIds,
'data' => $msg
);
$fields = json_encode($fields);
$arrContextOptions=array(
"http" => array(
"method" => "POST",
"header" =>
"Authorization: key = <YOUR_APP_KEY>". "\r\n" .
"Content-Type: application/json". "\r\n",
"content" => $fields,
),
"ssl"=>array(
"allow_self_signed"=>true,
"verify_peer"=>false,
),
);
$arrContextOptions = stream_context_create($arrContextOptions);
$result = file_get_contents('https://android.googleapis.com/gcm/send', false, $arrContextOptions);
return $result;
}
Related
I have a small Symfony project I want to document with doxygen. There are two .php files that should be included. One is documented, the other is not, and I cannot figure out why that may be.
Folder structure is:
project
└--src
|--Controller
| └--FormController.php
└--Model
└--Inquiry.php
Doxygen is reading and parsing both files...
Reading /form-handler/src/Controller/FormController.php...
Parsing file /form-handler/src/Controller/FormController.php...
Reading /form-handler/src/Model/Inquiry.php...
Parsing file /form-handler/src/Model/Inquiry.php...
...but only documents FormController.php, not Inquiry.php:
Generating docs for compound App::Controller::FormController...
For some reason doxygen does not seem to recognizeInquiry.php as a class.
What I have tried:
Removed decorators from docstrings that might offend doxygen.
Checked format of docstrings
Enabled/disabled various Doxyfile options
FormController.php:
<?php
declare(strict_types=1);
namespace App\Controller;
use App\Model\Inquiry;
use Psr\Log\LoggerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\TransportExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface;
/**
* Handles incoming requests.
*/
class FormController extends AbstractController
{
/**
* Handles POST requests.
*
* #return Response contains JSON object with 'message' value
*/
#[Route('/', methods: ['POST'])]
public function handleRequest(
HttpClientInterface $client,
Inquiry $inquiry,
LoggerInterface $logger,
RateLimiterFactory $formApiLimiter,
Request $request,
): Response {
$logger->debug('Received a POST request');
// set up a rate limiter by IP
// rules are defined in /config/packages/rate-limiter.yaml
$limiter = $formApiLimiter->create($request->getClientIp());
$limit = $limiter->consume();
// configure headers exposing rate limit info
$headers = [
'Content-Type' => 'application/json',
'X-RateLimit-Remaining' => $limit->getRemainingTokens(),
'X-RateLimit-Retry-After' => $limit->getRetryAfter()->getTimestamp(),
'X-RateLimit-Limit' => $limit->getLimit(),
];
if (false === $limit->isAccepted()) {
return new Response(
content: json_encode(['message' => null]),
status: Response::HTTP_TOO_MANY_REQUESTS,
headers: $headers
);
}
// make sure all required fields are included in request and not empty
$requiredFields = ['subject', 'message', 'consent', 'h-captcha-response'];
$providedFields = $request->request->keys();
foreach ($requiredFields as $field) {
if (!in_array($field, $providedFields)) {
return new Response(
content: json_encode(['message' => "Pflichtfeld '".$field."' fehlt."]),
status: Response::HTTP_BAD_REQUEST,
headers: $headers
);
} elseif ('' == filter_var($request->request->get($field), FILTER_SANITIZE_SPECIAL_CHARS)) {
return new Response(
content: json_encode(['message' => "Pflichtfeld '".$field."' darf nicht leer sein."]),
status: Response::HTTP_BAD_REQUEST,
headers: $headers
);
}
}
// verify captcha success
$captcha = filter_var($request->request->get('h-captcha-response'), FILTER_SANITIZE_SPECIAL_CHARS);
$data = [
'secret' => $this->getParameter('app.captcha.secret'),
'response' => $captcha,
];
try {
$hCaptchaResponse = $client->request(
method: 'POST',
url: 'https://hcaptcha.com/siteverify',
options: [
'body' => $data,
],
);
$hCaptchaResponseJson = json_decode($hCaptchaResponse->getContent(), true);
if (!$hCaptchaResponseJson['success']) {
return new Response(
content: json_encode(['message' => 'Captcha fehlgeschlagen']),
status: Response::HTTP_BAD_REQUEST,
headers: $headers
);
}
// exceptions on the side of hCaptcha are logged, but the request is processed anyway
} catch (TransportExceptionInterface $e) {
$logger->debug('Could not reach hCaptcha verification server: '.$e);
} catch (ClientExceptionInterface|RedirectionExceptionInterface|ServerExceptionInterface $e) {
$logger->debug('Error when verifying hCaptcha response: '.$e);
}
// get values from request data
$name = filter_var($request->request->get('name'), FILTER_SANITIZE_SPECIAL_CHARS);
$email = filter_var($request->request->get('email'), FILTER_SANITIZE_EMAIL);
$phone = filter_var($request->request->get('phone'), FILTER_SANITIZE_SPECIAL_CHARS);
$subject = filter_var($request->request->get('subject'), FILTER_SANITIZE_SPECIAL_CHARS);
$message = filter_var($request->request->get('message'), FILTER_SANITIZE_SPECIAL_CHARS);
$consent = filter_var($request->request->get('consent'), FILTER_SANITIZE_SPECIAL_CHARS);
// translate into a boolean (else the string 'false' will be evaluated as true)
$consent = filter_var($consent, FILTER_VALIDATE_BOOLEAN);
// populate Inquiry with request data
$inquiry->createInquiry(
subject: $subject,
message: $message,
consent: $consent,
name: $name,
email: $email,
phone: $phone,
);
// validate Inquiry
$validationResult = $inquiry->validateInquiry();
// if Inquiry is invalid, return validation violation message(s)
if (count($validationResult) > 0) {
$logger->debug($validationResult);
// assemble list of error messages
$validationMessages = [];
foreach ($validationResult as $result) {
$validationMessages += [$result->getPropertyPath() => $result->getMessage()];
}
return new Response(
content: json_encode([
'message' => 'Anfrage enthält ungültige Werte',
'errors' => $validationMessages,
]),
status: Response::HTTP_BAD_REQUEST,
headers: $headers
);
}
// send mail to office
$emailResult = $inquiry->sendOfficeEmail();
$logger->debug(implode(' ,', $emailResult));
$message = 'Die Anfrage war erfolgreich';
if (!$emailResult['success']) {
$message = 'Die Anfrage war nicht erfolgreich.';
}
// TODO compile email to user
$data = [
'message' => $message,
'officeEmail' => $emailResult,
'confirmationEmail' => true,
];
return new Response(
content: json_encode($data),
status: Response::HTTP_OK,
headers: $headers
);
}
/**
* Handles disallowed request methods.
*
* #return Response contains JSON object with 'message' value
*/
#[Route('/', condition: "context.getMethod() not in ['POST']")]
public function handleDisallowedMethods(LoggerInterface $logger): Response
{
$logger->debug('Received a request with a disallowed method.');
return new Response(
content: json_encode(['message' => 'Only POST requests allowed']),
status: Response::HTTP_METHOD_NOT_ALLOWED
);
}
}
Inquiry.php:
<?php
declare(strict_types=1);
namespace App\Model;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
use Symfony\Component\Mailer\MailerInterface;
use Symfony\Component\Mime\Email;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* Represents an inquiry received from the front end.
*
* The required fields 'subject', 'message', and
* 'consent' must be provided to the constructor.
*/
class Inquiry
{
private string $name;
private string $email;
private string $phone;
private string $subject;
private string $message;
private bool $consent;
private bool $validated = false;
/**
* #param LoggerInterface $logger
* #param MailerInterface $mailer
* #param array $officeRecipients configured in services.yaml
* #param ValidatorInterface $validator
*/
public function __construct(
private readonly LoggerInterface $logger,
private readonly MailerInterface $mailer,
private readonly array $officeRecipients,
private readonly ValidatorInterface $validator,
) {
}
/**
* Populates an inquiry with data.
*
* 'subject', 'message', and 'consent are required,
* all other values are optional.
*
* Sets 'validated' to false, in case createInquiry()
* is called multiple times.
*/
public function createInquiry(
string $subject,
string $message,
bool $consent,
string $name = '',
string $email = '',
string $phone = '',
): void {
$this->subject = $subject;
$this->message = $message;
$this->consent = $consent;
$this->name = $name;
$this->email = $email;
$this->phone = $phone;
$this->validated = false;
}
/**
* Validates the inquiry.
*
* If successful, sets 'validated' to true
*
* #return ConstraintViolationListInterface if valid: empty
* if not valid: list of validation violation messages
*/
public function validateInquiry(): ConstraintViolationListInterface
{
$validationResult = $this->validator->validate($this);
if (0 == count($validationResult)) {
$this->validated = true;
}
return $validationResult;
}
/**
* Sends an email with the customer inquiry data to the office.
*
* #return array containing 'success' boolean and 'message' string
*/
public function sendOfficeEmail(): array
{
if (!$this->validated) {
return [
'success' => false,
'message' => 'Inquiry has not been validated. Use Inquiry->validate() first',
];
}
// convert 'consent' and empty values in to human-readable format
$plainTextConsent = $this->consent ? 'Ja' : 'Nein';
$plainTextName = $this->name ?: 'Keine Angabe';
$plainTextEmail = $this->email ?: 'Keine Angabe';
$plainTextPhone = $this->phone ?: 'Keine Angabe';
$emailBody = <<<END
Das Kontaktformular hat eine Anfrage erhalten.
Betreff: $this->subject
Nachricht: $this->message
Einwilligung: $plainTextConsent
Name: $plainTextName
Email: $plainTextEmail
Telefon: $plainTextPhone
END;
$email = (new Email())
->to(...$this->officeRecipients)
->subject('Anfrage vom Kontaktformular')
->text($emailBody);
try {
$this->mailer->send($email);
$this->logger->debug('Email sent');
return [
'success' => true,
'message' => 'Email wurde gesendet',
];
} catch (TransportExceptionInterface $e) {
$this->logger->debug('Error sending email: '.$e);
return [
'success' => false,
'message' => 'Email konnte nicht gesendet werden: '.$e,
];
}
}
/**
* #codeCoverageIgnore
*/
public function sendConfirmationEmail(): string
{
return '';
}
/**
* Checks whether Inquiry has been validated.
*/
public function isValidated(): bool
{
return $this->validated;
}
}
Doxyfile (EDITED as per #albert's comment):
# Difference with default Doxyfile 1.9.3 (c0b9eafbfb53286ce31e75e2b6c976ee4d345473)
PROJECT_NAME = "Form handler"
PROJECT_BRIEF = "Stand-alone Symfony backend to handle contact forms."
OUTPUT_DIRECTORY = ./doc/
INPUT = ./src/ \
README.md
RECURSIVE = YES
EXCLUDE = ./src/Kernel.php
USE_MDFILE_AS_MAINPAGE = README.md
GENERATE_LATEX = NO
As of PHP version 7.3.0 the syntax of the here document changed slightly, see https://www.php.net/manual/en/language.types.string.php#language.types.string.syntax.heredoc
The closing identifier may be indented by space or tab, in which case the indentation will be stripped from all lines in the doc string. Prior to PHP 7.3.0, the closing identifier must begin in the first column of the line.
This has now been corrected in the proposed patch, pull request: https://github.com/doxygen/doxygen/pull/9398
Workarounds:
place the end identifier of the here document at the beginning of the line
place a doxygen conditional block /** \cond / / /* \endcond */ around the here document.
I have the following 2 codes from library https://github.com/javiertelioz/mercadolibre to connect with MercadoLibre's API:
Class Meli.php:
<?php
namespace App\Sources;
use App\Sources\MercadoLibre\Utils;
class Meli extends Utils {
/**
* #version 1.0.0
*/
const VERSION = "1.0.0";
/**
* Configuration for urls
*/
protected $urls = array(
'API_ROOT_URL' => 'https://api.mercadolibre.com',
'AUTH_URL' => 'http://auth.mercadolibre.com.ar/authorization',
'OAUTH_URL' => '/oauth/token'
);
/**
* Configuration for CURL
*/
protected $curl_opts = array(
CURLOPT_USERAGENT => "MELI-PHP-SDK-1.0.0",
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_CONNECTTIMEOUT => 10,
CURLOPT_RETURNTRANSFER => 1,
CURLOPT_TIMEOUT => 60
);
protected $client_id;
protected $client_secret;
/**
* Constructor method. Set all variables to connect in Meli
*
* #param string $client_id
* #param string $client_secret
* #param string $access_token
*/
public function __construct($client_id, $client_secret, $urls = null, $curl_opts = null) {
$this->client_id = $client_id;
$this->client_secret = $client_secret;
$this->urls = $urls ? $urls : $this->urls;
$this->curl_opts = $curl_opts ? $curl_opts : $this->curl_opts;
}
/**
* Return an string with a complete Meli login url.
*
* #param string $redirect_uri
* #return string
*/
public function getAuthUrl($redirect_uri) {
$params = array("client_id" => $this->client_id, "response_type" => "code", "redirect_uri" => $redirect_uri);
$auth_uri = $this->urls['AUTH_URL'] . "?" . http_build_query($params);
return $auth_uri;
}
}
and the Controller MeliController.php with the following code:
class MeliController extends Controller
{
/**
* Login Page (Mercado Libre)
*/
public function login() {
session()->regenerate();
return view('auth/melilogin')->with('auth', [
'url' => meli::getAuthUrl(env('ML_AUTHENTICATION_URL', '')),
]);
}
public function logout() {
if(session('profile')) {
session()->forget('profile');
session()->flush();
}
return \Redirect::to('/auth/melilogin');
}
}
But Im receiving error:
Non-static method App\Sources\Meli::getAuthUrl() should not be called
statically
Three procedures I made with no success:
1- using facade (meli) as in the first example
meli::getAuthUrl
2- replacing code:
public function getAuthUrl($redirect_uri) {
$params = array("client_id" => $this->client_id, "response_type" => "code", "redirect_uri" => $redirect_uri);
$auth_uri = $this->urls['AUTH_URL'] . "?" . http_build_query($params);
return $auth_uri;
}
}
with public static function and $self instead of $this but with no success.
3- Making the call dynamic using:
'url' => (new \App\Sources\Meli)->getAuthUrl(env('ML_AUTHENTICATION_URL', '')),
But receiving error
Too few arguments to function App\Sources\Meli::__construct(), 0
passed in
/Applications/MAMP/htdocs/price2b/app/Http/Controllers/MeliController.php
any help appreciated.
The error tells you the problem: you are calling the method statically (meli::getAuthUrl(...)), but it's not a static method. You have to call it on an instance of the class. This means that your third approach:
'url' => (new \App\Sources\Meli)->getAuthUrl(env('ML_AUTHENTICATION_URL', '')),
is the right one.
But, as you pointed out, you get a "too few arguments" error. This is because you are passing no arguments when you instantiate the Meli class. That is, new \App\Sources\Meli is equivalent to new \App\Sources\Meli(), passing zero arguments to the constructor.
But the constructor for the Meli class, which you posted above, looks like this:
public function __construct($client_id, $client_secret, $urls = null, $curl_opts = null)
So, you need to pass at least 2 arguments, not zero. In other words, at a minimum, something like this:
'url' => (new \App\Sources\Meli($someClientId, $someClientSecret))->getAuthUrl(env('ML_AUTHENTICATION_URL', '')),
I'm trying to test my api that's made with JWT_auth: https://github.com/tymondesigns/jwt-auth
class UpdateTest extends TestCase
{
use DatabaseTransactions;
public $token;
public function signIn($data = ['email'=>'mail#gmail.com', 'password'=>'secret'])
{
$this->post('api/login', $data);
$content = json_decode($this->response->getContent());
$this->assertObjectHasAttribute('token', $content);
$this->token = $content->token;
return $this;
}
/** #test */
public function a_user_updates_his_account()
{
factory(User::class)->create([
'name' => 'Test',
'last_name' => 'Test',
'email' => 'mail#gmail.com',
'mobile' => '062348383',
'function' => 'ceo',
'about' => 'About me.....',
'corporation_id' => 1
]);
$user = User::first();
$user->active = 2;
$user->save();
$this->signIn();
$url = '/api/user/' . $user->slug . '?token=' . $this->token;
$result = $this->json('GET', $url);
dd($result);
}
}
Result is always:
The token could not be parsed from the request
How do I get this t work!?
Source (https://github.com/tymondesigns/jwt-auth/issues/206)
One way to test your API in this situation is to bypass the actual token verification, but still log your user in (if you need to identify the user). Here is a snippet of the helper method we used in our recent API-based application.
/**
* Simulate call api
*
* #param string $endpoint
* #param array $params
* #param string $asUser
*
* #return mixed
*/
protected function callApi($endpoint, $params = [], $asUser = 'user#example.org')
{
$endpoint = starts_with($endpoint, '/')
? $endpoint
: '/' . $endpoint;
$headers = [];
if (!is_null($asUser)) {
$token = auth()->guard('api')
->login(\Models\User::whereEmail($asUser)->first());
$headers['Authorization'] = 'Bearer ' . $token;
}
return $this->json(
'POST',
'http://api.dev/' . $endpoint,
$params,
$headers
);
}
And is used like this:
$this->callApi('orders/list', [
'type' => 'customers'
])
->seeStatusOk()
Basically, there is not really a way for now. The fake request that is created during testing and is passed to Laravel to handle, somehow drops the token data.
It has alredy been reported in an issue (https://github.com/tymondesigns/jwt-auth/issues/852) but as far as I know, there is no solution yet.
Since twitter has switched to streaming his API, we have to collect the data by ourselves.
How we can do it using GNIP API in php?
Answering this, I’ve just wanted to ensure that I’ve done everything right and maybe to improve my TwitterGnipClient class with your help.
But if there are no answers, let it be FAQ style question.
Main methods see below:
/**
* Add rules to PowerTrack stream’s ruleset.
* #example ['id' => 'url', ...]
* #param array $data
*/
public function addRules(array $data)
{
$rules = [];
foreach ($data as $id => $url) {
$rules[] = $this->buildRuleForUrl($url, $id);
}
$this->httpClient->post($this->getRulesUrl(), [
'auth' => [$this->getUser(), $this->getPassword()],
'json' => ['rules' => $rules]
]);
}
/**
* Retrieves all existing rules for a stream.
* #return \Generator
*/
public function getRules()
{
$response = $this->httpClient->get($this->getRulesUrl(), [
'auth' => [$this->getUser(), $this->getPassword()]
]);
$batchStr = '';
$body = $response->getBody();
while (!$body->eof()) {
$batchStr .= $body->read(1024);
}
$batchArray = explode(PHP_EOL, $batchStr);
unset($batchStr);
foreach ($batchArray as $itemJson) {
yield $this->unpackJson($itemJson);
}
}
/**
* Removes the specified rules from the stream.
* #param $data
*/
public function deleteRules($data)
{
$rules = [];
foreach ($data as $id => $url) {
$rules[] = $this->buildRuleForUrl($url, $id);
}
$this->httpClient->delete($this->getRulesUrl(), [
'auth' => [$this->getUser(), $this->getPassword()],
'json' => ['rules' => array_values($rules)]
]);
}
/**
* Open stream through which the social data will be delivered.
* #return \Generator
*/
public function listenStream()
{
$response = $this->httpClient->get($this->getStreamUrl(), [
'auth' => [$this->getUser(), $this->getPassword()],
'stream' => true
]);
$batchStr = '';
$body = $response->getBody();
while (!$body->eof()) {
$batchStr .= $body->read(1024);
$batchArray = explode(PHP_EOL, $batchStr);
// leave the last piece of response as it can be incomplete
$batchStr = array_pop($batchArray);
foreach ($batchArray as $itemJson) {
yield $this->processBroadcastItem($this->unpackJson($itemJson));
}
}
$body->close();
}
/**
* Process broadcast item data
* #param $data
* #return array
*/
protected function processBroadcastItem($data)
{
if (is_array($data)) {
$url = str_replace('url_contains:', '', $data['gnip']['matching_rules'][0]['value']);
switch ($data['verb']) {
// Occurs when a user posts a new Tweet.
case 'post':
return $this->getMappedResponse($url, 'tweet', 1);
break;
// Occurs when a user Retweets another user's Tweet
case 'share':
return $this->getMappedResponse($url, 'retweet', $data['retweetCount']);
break;
}
}
return [];
}
All class I shared as Gist - here
P.S. If you see an evident issue - comment it please.
I have to work with the techcrunch wp-async-task to run a synchronization task in background in my wordpress plugin.
So to test, at the bottom of the main file I have :
//top of the php file
require_once(dirname(__FILE__) . '/lib/WP_Async_Task.php');
require_once(dirname(__FILE__) . '/class/my_api_status.class.php');
define('API_URL', '...');
/* ... */
// At the bottom of the file
function my_api_status($api_url)
{
sleep(5);
$r = wp_safe_remote_get($api_url);
if (!is_wp_error($r)) {
$body = json_decode(wp_remote_retrieve_body($r));
if (isset($body->success)) {
return;
}
}
}
add_action('wp_async_api_status', 'my_api_status');
function my_init_api_status()
{
new ApiStatusTask();
do_action('api_status', constant('API_URL'));
}
add_action('plugins_loaded', 'my_init_api_status');
And api status task class
class ApiStatusTask extends WP_Async_Task {
protected $action = 'api_status';
/**
* Prepare data for the asynchronous request
* #throws Exception If for any reason the request should not happen
* #param array $data An array of data sent to the hook
* #return array
*/
protected function prepare_data( $data ) {
return array(
'api_url' => $data[0]
);
}
/**
* Run the async task action
*/
protected function run_action() {
if(isset($_POST['api_url'])){
do_action("wp_async_$this->action", $_POST['api_url']);
}
}
}
The function prepare_data is correctly called by launchand after that launch_on_shutdown is also correctly called and finally wp_remote_post is called at the end of launch_on_shutdown with admin-post.php.
But the function run_action is never called ... and so the my_api_status in the main file.
What it possibly go wrong ?
I will put a complete example of a plugin here soon. But for now, I found my problem :
// In the `launch_on_shutdown` method of `WP_Async_Task` class
public function launch_on_shutdown() {
GcLogger::getLogger()->debug('WP_Async_Task::launch_on_shutdown');
if ( ! empty( $this->_body_data ) ) {
$cookies = array();
foreach ( $_COOKIE as $name => $value ) {
$cookies[] = "$name=" . urlencode( is_array( $value ) ? serialize( $value ) : $value );
}
$request_args = array(
'timeout' => 0.01,
'blocking' => false,
'sslverify' => false, //apply_filters( 'https_local_ssl_verify', true ),
'body' => $this->_body_data,
'headers' => array(
'cookie' => implode( '; ', $cookies ),
),
);
$url = admin_url( 'admin-post.php' );
GcLogger::getLogger()->debug('WP_Async_Task::launch_on_shutdown wp_remote_post');
wp_remote_post( $url, $request_args );
}
}
The sslverify option failed in my local environment. I just had to put it on false if we are not in production.
With this option set, the run_action is correctly trigger.