PHP WhatsApp Client - Unable to get messages - php

I am having issue to send messages via WhatsApp PHP Client. Details given below:
Error
Due to length it's given here: http://pastie.org/10794465
```
Debug log
http://pastie.org/10794474
Code
<?php
ini_set('display_errors',1);
error_reporting(E_ALL);
set_time_limit(10);
var_dump(extension_loaded('curve25519'));
var_dump( extension_loaded('protobuf'));
//require_once __DIR__.'../vendor/whatsapp/autoload.php';
date_default_timezone_set('Europe/Madrid');
//require_once __DIR__.'../vendor/whatsapp/chat-api/src/whatsprot.class.php';
require_once 'vendor/whatsapp/chat-api/src/whatsprot.class.php';
require_once 'vendor/whatsapp/chat-api/src/events/MyEvents.php';
//require_once __DIR__.'/../src//events/MyEvents.php';
$username = '92xxxxxxxxx'; // Telephone number including the country code without '+' or '00'.
$password = 't7+YzhqpUd8P7LgeU9NdttaIpc4='; // Use registerTool.php or exampleRegister.php to obtain your password
$nickname = 'ADD Agent'; // This is the username (or nickname) displayed by WhatsApp clients.
$target = "92xxxxxxxxx"; // Destination telephone number including the country code without '+' or '00'.
$target = "92xxxxxxxxx"; // Destination telephone number including the country code without '+' or '00'.
$debug = true; // Set this to true, to see debug mode.
echo "[] Logging in as '$nickname' ($username)\n";
//Create the whatsapp object and setup a connection.
$w = new WhatsProt($username, $nickname, $debug,true,__DIR__.'/wadata/');
$events = new MyEvents($w);
$events->setEventsToListenFor($events->activeEvents);
$w->connect();
// Now loginWithPassword function sends Nickname and (Available) Presence
$w->loginWithPassword($password);
$w->sendMessage($target, 'Salam kia haal hain?!');
echo "<b>Message Sent to $target</b>";
echo "<br>Getting message<br>";
$w->pollMessage();
$msgs = $w->GetMessages();
foreach ($msgs as $m) {
var_dump($m);
}
In MyEvents.php
public function onGetMessage( $mynumber, $from, $id, $type, $time, $name, $body )
{
echo "<br>Message Got from $name:\n$body\n\n<br>"; // NOT being fired.
exit;
}

To receive message you need bind onGetMessage and call pollMessage in a loop
while (1) {
$w->pollMessage();
}
Check this example complete:
<?php
//set_time_limit(10);
require_once __DIR__.'/../src/whatsprot.class.php';
require_once __DIR__.'/../src//events/MyEvents.php';
//Change to your time zone
date_default_timezone_set('Europe/Madrid');
//######### DO NOT COMMIT THIS FILE WITH YOUR CREDENTIALS ###########
///////////////////////CONFIGURATION///////////////////////
//////////////////////////////////////////////////////////
$username = '*************'; // Telephone number including the country code without '+' or '00'.
$password = '*************'; // Use registerTool.php or exampleRegister.php to obtain your password
$nickname = 'LuisN'; // This is the username (or nickname) displayed by WhatsApp clients.
$target = "************"; // Destination telephone number including the country code without '+' or '00'.
$debug = false; // Set this to true, to see debug mode.
///////////////////////////////////////////////////////////
function onPresenceAvailable($username, $from)
{
$dFrom = str_replace(['#s.whatsapp.net', '#g.us'], '', $from);
echo "<$dFrom is online>\n\n";
}
function onPresenceUnavailable($username, $from, $last)
{
$dFrom = str_replace(['#s.whatsapp.net', '#g.us'], '', $from);
echo "<$dFrom is offline> Last seen: $last seconds\n\n";
}
function onGetMessage($mynumber, $from, $id, $type, $time, $name, $body){
echo sprintf("Message from %s: [%s]\r\n",$from,$body);
}
echo "[] Logging in as '$nickname' ($username)\n";
// Create the whatsapp object and setup a connection.
$w = new WhatsProt($username, $nickname, $debug);
$w->connect();
// Now loginWithPassword function sends Nickname and (Available) Presence
$w->loginWithPassword($password);
$w->sendGetServerProperties();
$w->sendGetGroups();
$w->sendGetBroadcastLists();
// Set the profile picture
//$w->sendSetProfilePicture(Constants::PICTURES_FOLDER . '/314484_300x300.jpg');
$w->sendStatusUpdate("La vida es un carnaval \xF0\x9F\x8E\xB6");
// Synchronizes contacts with the server, very important to avoid bans
$w->sendSync([$target]);
// Print when the user goes online/offline (you need to bind a function to the event onPressence
// so the script knows what to do)
$w->eventManager()->bind('onPresenceAvailable', 'onPresenceAvailable');
$w->eventManager()->bind('onPresenceUnavailable', 'onPresenceUnavailable');
// Receives and processes messages, this includes decrypted
$w->eventManager()->bind('onGetMessage','onGetMessage');
echo "[*] Connected to WhatsApp\n\n";
$w->sendMessage($target, 'Guess the number :)');
$w->sendMessage($target, 'Sent from WhatsApi at '.date('H:i'));
while (1) {
$w->pollMessage();
}
PD: I tested this method in 3 environments
php 5.5 Cli NTS VC11 Windows 10
php 5.5 Cli NTS VC11 Windows 7
PHP 5.5.9-1ubuntu4.14 Cli

Related

Where to put global variable declarations in a robust file? [duplicate]

My application has a production or development setting that I can toggle. When setting up the server, I set up this flag in Applications > Mamp > Conf > Apache > httpd.conf. Its purpose is to give my local API directory a server alias. It also defines the document root etc.
Listen 44447
<VirtualHost *:44447>
DocumentRoot "/Users/user/Desktop/PushChatServer/api"
ServerName 192.168.1.5:44447
ServerAlias pushchat.local
CustomLog "/Users/user/Desktop/PushChatServer/log/apache_access.log" combined
ErrorLog "/Users/user/Desktop/PushChatServer/log/apache_error.log"
SetEnv APPLICATION_ENV development
php_flag magic_quotes_gpc off
<Directory "/Users/user/Desktop/PushChatServer/api">
Options Indexes MultiViews FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
My goal is to set up a global variable in my api and pass it to another file. The php file my app uses is called api.php and I'd like to use a global variable named $Var1. I set up $Var1 like this in api.php
//In api.php
global $Var1;
$Var1 = '1';
When I try to call it, no out put is received. I am calling it like this:
<?php
//Checks for warnings
error_reporting(E_ALL);
ini_set("display_errors", 1);
error_reporting(E_ALL|E_STRICT); ini_set('display_errors', 'on');
include 'api.php'
echo "Var1";
?>
The thing is no output at all is echoed in the browser. I now realize the code block in api.php that is interfering with the global variable. I still am not sure why though. When I delete this code block from api.php the global variable is successfully displayed in Test.php. How can I keep the code block and successfully display the global in Test.php?
try
{
// Are we running in development or production mode? You can easily switch
// between these two in the Apache VirtualHost configuration.
if (!defined('APPLICATION_ENV'))
define('APPLICATION_ENV', getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production');
// In development mode, we show all errors because we obviously want to
// know about them. We don't do this in production mode because that might
// expose critical details of our app or our database. Critical PHP errors
// will still be logged in the PHP and Apache error logs, so it's always
// a good idea to keep an eye on them.
if (APPLICATION_ENV == 'development')
{
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors', 'on');
}
else
{
error_reporting(0);
ini_set('display_errors', 'off');
}
// Load the config file. I prefer to keep all configuration settings in a
// separate file so you don't have to mess around in the main code if you
// just want to change some settings.
require_once 'api_config.php';
$config = $config[APPLICATION_ENV];
// In development mode, we fake a delay that makes testing more realistic.
// You're probably running this on a fast local server but in production
// mode people will be using it on a mobile device over a slow connection.
if (APPLICATION_ENV == 'development')
sleep(0);
// To keep the code clean, I put the API into its own class. Create an
// instance of that class and let it handle the request.
$api = new API($config);
$api->handleCommand();
echo "OK" . PHP_EOL;
}
catch (Exception $e)
{
// The code throws an exception when something goes horribly wrong; e.g.
// no connection to the database could be made. In development mode, we
// show these exception messages. In production mode, we simply return a
// "500 Server Error" message.
if (APPLICATION_ENV == 'development')
var_dump($e);
else
exitWithHttpError(500);
}
////////////////////////////////////////////////////////////////////////////////
function exitWithHttpError($error_code, $message = '')
{
switch ($error_code)
{
case 400: header("HTTP/1.0 400 Bad Request"); break;
case 403: header("HTTP/1.0 403 Forbidden"); break;
case 404: header("HTTP/1.0 404 Not Found"); break;
case 500: header("HTTP/1.0 500 Server Error"); break;
}
header('Content-Type: text/plain');
if ($message != '')
header('X-Error-Description: ' . $message);
exit;
}
function isValidUtf8String($string, $maxLength, $allowNewlines = false)
{
if (empty($string) || strlen($string) > $maxLength)
return false;
if (mb_check_encoding($string, 'UTF-8') === false)
return false;
// Don't allow control characters, except possibly newlines
for ($t = 0; $t < strlen($string); $t++)
{
$ord = ord($string{$t});
if ($allowNewlines && ($ord == 10 || $ord == 13))
continue;
if ($ord < 32)
return false;
}
return true;
}
function truncateUtf8($string, $maxLength)
{
$origString = $string;
$origLength = $maxLength;
while (strlen($string) > $origLength)
{
$string = mb_substr($origString, 0, $maxLength, 'utf-8');
$maxLength--;
}
return $string;
}
UPDATE
Full source code of api.php. My goal is to declare a global variable and pass it a long using a require statement to a new file. I am only able to do so if I delete the entire try block at the beginning of this file and I need to know why.
<?php
global $Var1;
$Var1 = '1';
// This is the server API for the PushChat iPhone app. To use the API, the app
// sends an HTTP POST request to our URL. The POST data contains a field "cmd"
// that indicates what API command should be executed.
try
{
// Are we running in development or production mode? You can easily switch
// between these two in the Apache VirtualHost configuration.
if (!defined('APPLICATION_ENV'))
define('APPLICATION_ENV', getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production');
// In development mode, we show all errors because we obviously want to
// know about them. We don't do this in production mode because that might
// expose critical details of our app or our database. Critical PHP errors
// will still be logged in the PHP and Apache error logs, so it's always
// a good idea to keep an eye on them.
if (APPLICATION_ENV == 'development')
{
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors', 'on');
}
else
{
error_reporting(0);
ini_set('display_errors', 'off');
}
// Load the config file. I prefer to keep all configuration settings in a
// separate file so you don't have to mess around in the main code if you
// just want to change some settings.
require_once 'api_config.php';
$config = $config[APPLICATION_ENV];
// In development mode, we fake a delay that makes testing more realistic.
// You're probably running this on a fast local server but in production
// mode people will be using it on a mobile device over a slow connection.
if (APPLICATION_ENV == 'development')
sleep(0);
// To keep the code clean, I put the API into its own class. Create an
// instance of that class and let it handle the request.
$api = new API($config);
$api->handleCommand();
echo "OK" . PHP_EOL;
}
catch (Exception $e)
{
// The code throws an exception when something goes horribly wrong; e.g.
// no connection to the database could be made. In development mode, we
// show these exception messages. In production mode, we simply return a
// "500 Server Error" message.
if (APPLICATION_ENV == 'development')
var_dump($e);
else
exitWithHttpError(500);
}
////////////////////////////////////////////////////////////////////////////////
function exitWithHttpError($error_code, $message = '')
{
switch ($error_code)
{
case 400: header("HTTP/1.0 400 Bad Request"); break;
case 403: header("HTTP/1.0 403 Forbidden"); break;
case 404: header("HTTP/1.0 404 Not Found"); break;
case 500: header("HTTP/1.0 500 Server Error"); break;
}
header('Content-Type: text/plain');
if ($message != '')
header('X-Error-Description: ' . $message);
exit;
}
function isValidUtf8String($string, $maxLength, $allowNewlines = false)
{
if (empty($string) || strlen($string) > $maxLength)
return false;
if (mb_check_encoding($string, 'UTF-8') === false)
return false;
// Don't allow control characters, except possibly newlines
for ($t = 0; $t < strlen($string); $t++)
{
$ord = ord($string{$t});
if ($allowNewlines && ($ord == 10 || $ord == 13))
continue;
if ($ord < 32)
return false;
}
return true;
}
function truncateUtf8($string, $maxLength)
{
$origString = $string;
$origLength = $maxLength;
while (strlen($string) > $origLength)
{
$string = mb_substr($origString, 0, $maxLength, 'utf-8');
$maxLength--;
}
return $string;
}
////////////////////////////////////////////////////////////////////////////////
class API
{
// Because the payload only allows for 256 bytes and there is some overhead
// we limit the message text to 190 characters.
const MAX_MESSAGE_LENGTH = 190;
private $pdo;
function __construct($config)
{
// Create a connection to the database.
$this->pdo = new PDO(
'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['dbname'],
$config['db']['username'],
$config['db']['password'],
array());
// If there is an error executing database queries, we want PDO to
// throw an exception. Our exception handler will then exit the script
// with a "500 Server Error" message.
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// We want the database to handle all strings as UTF-8.
$this->pdo->query('SET NAMES utf8');
}
function handleCommand()
{
// Figure out which command the client sent and let the corresponding
// method handle it. If the command is unknown, then exit with an error
// message.
if (isset($_POST['cmd']))
{
switch (trim($_POST['cmd']))
{
case 'join': $this->handleJoin(); return;
case 'leave': $this->handleLeave(); return;
case 'update': $this->handleUpdate(); return;
case 'message': $this->handleMessage(); return;
}
}
exitWithHttpError(400, 'Unknown command');
}
// The "join" API command registers a user to receive notifications that
// are sent in a specific "chat room". Each chat room is identified by a
// secret code. All the users who register with the same secret code can
// see each other's messages.
//
// This command takes the following POST parameters:
//
// - user_Id: A unique identifier. Must be a string of 40 hexadecimal characters.
// - token: The device's device token. Must be a string of 64 hexadecimal
// characters, or "0" if no token is available yet.
// - name: The nickname of the user. Must be a UTF-8 string of maximum 255
// bytes. Only the first 20 bytes are actually shown in the push
// notifications.
// - code: The secret code that identifies the chat room. Must be a UTF-8
// string of maximum 255 bytes.
//
function handleJoin()
{
//function getUserId;
$userId = $this->getUserId();
$token = $this->getDeviceToken(true);
$name = $this->getString('name', 255);
$code = $this->getString('code', 255);
// When the client sends a "join" command, we add a new record to the
// active_users table. We identify the client by the user_id that it
// provides. When the client sends a "leave" command, we delete its
// record from the active_users table.
// It is theoretically possible that a client sends a "join" command
// while its user_id is still present in active_users (because it did not
// send a "leave" command). In that case, we simply remove the old
// record first and then insert the new one.
$this->pdo->beginTransaction();
$stmt = $this->pdo->prepare('DELETE FROM active_users WHERE user_Id = ?');
$stmt->execute(array($userId));
$stmt = $this->pdo->prepare('INSERT INTO active_users (user_Id, device_token, nickname, secret_code, ip_address) VALUES (?, ?, ?, ?, ?)');
$stmt->execute(array($userId, $token, $name, $code, $_SERVER['REMOTE_ADDR']));
$this->pdo->commit();
}
// The "leave" API command removes a user from a chat room. That user will
// no longer receive push notifications for messages sent to that room.
//
// This command takes the following POST parameters:
//
// - user_id: A unique identifier. Must be a string of 40 hexadecimal characters.
//
function handleLeave()
{
$userId = $this->getUserId();
$stmt = $this->pdo->prepare('DELETE FROM active_users WHERE user_Id = ?');
$stmt->execute(array($userId));
}
// The "update" API command gives a user a new device token.
//
// This command takes the following POST parameters:
//
// - user_id: A unique identifier. Must be a string of 40 hexadecimal characters.
// - token: The device's device token. Must be a string of 64 hexadecimal
// characters.
//
function handleUpdate()
{
$userId = $this->getUserId();
$token = $this->getDeviceToken(false);
$stmt = $this->pdo->prepare('UPDATE active_users SET device_token = ? WHERE user_Id = ?');
$stmt->execute(array($token, $userId));
}
// The "message" API command sends a message to all users who are registered
// with the same secret code as the sender of the message.
//
// This command takes the following POST parameters:
//
// - user_id: A unique identifier. Must be a string of 40 hexadecimal characters.
// - text: The message text. Must be a UTF-8 string of maximum 190 bytes.
//
function handleMessage()
{
$userId = $this->getUserId();
/*$text = $this->getString('text', self::MAX_MESSAGE_LENGTH, true);*/
// First, we get the record for the sender of the message from the
// active_users table. That gives us the nickname, device token, and
// secret code for that user.
$stmt = $this->pdo->prepare('SELECT * FROM active_users WHERE user_Id = ? LIMIT 1');
$stmt->execute(array($userId));
$user = $stmt->fetch(PDO::FETCH_OBJ);
if ($user !== false)
{
// Put the sender's name and the message text into the JSON payload
// for the push notification.
$payload = $this->makePayload($user->nickname/*, $text*/);
// Find the device tokens for all other users who are registered
// for this secret code. We exclude the device token of the sender
// of the message, so he will not get a push notification. We also
// exclude users who have not submitted a valid device token yet.
$stmt = $this->pdo->prepare("SELECT device_token FROM active_users WHERE secret_code = ? AND device_token <> ? AND device_token <> '0'");
$stmt->execute(array($user->secret_code, $user->device_token));
$tokens = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Send out a push notification to each of these devices.
foreach ($tokens as $token)
{
$this->addPushNotification($token, $payload);
}
}
}
// Retrieves the user identifier from the POST data. If the user_id does not
// appear to be valid, the script exits with an error message.
function getUserId()
{
if (!isset($_POST['user_id']))
exitWithHttpError(400, 'Missing user_id');
$userId = trim(urldecode($_POST['user_id']));
if (!$this->isValidUserId($userId))
exitWithHttpError(400, 'Invalid user_id');
return $userId;
}
// Checks whether the format of the user identifier is correct (40 hex
// characters or 32 for the simulator).
function isValidUserId($userId)
{
if (strlen($userId) != 40 && strlen($userId) != 32) // 32 for simulator
return false;
if (preg_match("/^[0-9a-fA-F]+$/", $userId) == 0)
return false;
return true;
}
// Retrieves the device token from the POST data. If the token does not
// appear to be valid, the script exits with an error message.
function getDeviceToken($mayBeEmpty = false)
{
if (!isset($_POST['token']))
exitWithHttpError(400, 'Missing device token');
$token = trim($_POST['token']);
// The "join" command allows a token value of "0" to be specified,
// which is necessary in case the client did not yet obtain a device
// token at that point. We allow such clients to join, but they will
// not receive any notifications until they provide a valid token
// using the "update" command.
if ($mayBeEmpty && $token == "0")
return $token;
if (!$this->isValidDeviceToken($token))
exitWithHttpError(400, 'Invalid device token');
return $token;
}
// Checks whether the format of the device token is correct (64 hexadecimal
// characters). Note: we have no means to verify whether the device token
// was really issued by APNS and corresponds to an actual device.
function isValidDeviceToken($deviceToken)
{
if (strlen($deviceToken) != 64)
return false;
if (preg_match("/^[0-9a-fA-F]{64}$/", $deviceToken) == 0)
return false;
return true;
}
// Looks in the POST data for a field with the given name. If the field
// is not a valid UTF-8 string, or it is too long, the script exits with
// an error message.
function getString($name, $maxLength, $allowNewlines = false)
{
if (!isset($_POST[$name]))
exitWithHttpError(400, "Missing $name");
$string = trim($_POST[$name]);
if (!isValidUtf8String($string, $maxLength, $allowNewlines))
exitWithHttpError(400, "Invalid $name");
return $string;
}
// Creates the JSON payload for the push notification message. The "alert"
// text has the following format: "sender_name: message_text". Recipients
// can obtain the name of the sender by parsing the alert text up to the
// first colon followed by a space.
function makePayload($senderName, $text)
{
// Convert the nickname of the sender to JSON and truncate to a maximum
// length of 20 bytes (which may be less than 20 characters).
$nameJson = $this->jsonEncode($senderName);
$nameJson = truncateUtf8($nameJson, 20);
// Convert and truncate the message text
$textJson = $this->jsonEncode($text);
$textJson = truncateUtf8($textJson, self::MAX_MESSAGE_LENGTH);
// Combine everything into a JSON string
$payload = '{"aps":{"alert":"' . $nameJson . ': ' . $textJson . '","sound":"beep.caf"}}';
return $payload;
}
// We don't use PHP's built-in json_encode() function because it converts
// UTF-8 characters to \uxxxx. That eats up 6 characters in the payload for
// no good reason, as JSON already supports UTF-8 just fine.
function jsonEncode($text)
{
static $from = array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"');
static $to = array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"');
return str_replace($from, $to, $text);
}
// Adds a push notification to the push queue. The notification will not
// be sent immediately. The server runs a separate script, push.php, which
// periodically checks for new entries in this database table and sends
// them to the APNS servers.
function addPushNotification($deviceToken, $payload)
{
// Payloads have a maximum size of 256 bytes. If the payload is too
// large (which shouldn't happen), we won't send this notification.
if (strlen($payload) <= 256)
{
$stmt = $this->pdo->prepare('INSERT INTO push_queue (device_token, payload, time_queued) VALUES (?, ?, NOW())');
$stmt->execute(array($deviceToken, $payload));
}
}
}
?>

How to declare a global variable in this API?

My application has a production or development setting that I can toggle. When setting up the server, I set up this flag in Applications > Mamp > Conf > Apache > httpd.conf. Its purpose is to give my local API directory a server alias. It also defines the document root etc.
Listen 44447
<VirtualHost *:44447>
DocumentRoot "/Users/user/Desktop/PushChatServer/api"
ServerName 192.168.1.5:44447
ServerAlias pushchat.local
CustomLog "/Users/user/Desktop/PushChatServer/log/apache_access.log" combined
ErrorLog "/Users/user/Desktop/PushChatServer/log/apache_error.log"
SetEnv APPLICATION_ENV development
php_flag magic_quotes_gpc off
<Directory "/Users/user/Desktop/PushChatServer/api">
Options Indexes MultiViews FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
My goal is to set up a global variable in my api and pass it to another file. The php file my app uses is called api.php and I'd like to use a global variable named $Var1. I set up $Var1 like this in api.php
//In api.php
global $Var1;
$Var1 = '1';
When I try to call it, no out put is received. I am calling it like this:
<?php
//Checks for warnings
error_reporting(E_ALL);
ini_set("display_errors", 1);
error_reporting(E_ALL|E_STRICT); ini_set('display_errors', 'on');
include 'api.php'
echo "Var1";
?>
The thing is no output at all is echoed in the browser. I now realize the code block in api.php that is interfering with the global variable. I still am not sure why though. When I delete this code block from api.php the global variable is successfully displayed in Test.php. How can I keep the code block and successfully display the global in Test.php?
try
{
// Are we running in development or production mode? You can easily switch
// between these two in the Apache VirtualHost configuration.
if (!defined('APPLICATION_ENV'))
define('APPLICATION_ENV', getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production');
// In development mode, we show all errors because we obviously want to
// know about them. We don't do this in production mode because that might
// expose critical details of our app or our database. Critical PHP errors
// will still be logged in the PHP and Apache error logs, so it's always
// a good idea to keep an eye on them.
if (APPLICATION_ENV == 'development')
{
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors', 'on');
}
else
{
error_reporting(0);
ini_set('display_errors', 'off');
}
// Load the config file. I prefer to keep all configuration settings in a
// separate file so you don't have to mess around in the main code if you
// just want to change some settings.
require_once 'api_config.php';
$config = $config[APPLICATION_ENV];
// In development mode, we fake a delay that makes testing more realistic.
// You're probably running this on a fast local server but in production
// mode people will be using it on a mobile device over a slow connection.
if (APPLICATION_ENV == 'development')
sleep(0);
// To keep the code clean, I put the API into its own class. Create an
// instance of that class and let it handle the request.
$api = new API($config);
$api->handleCommand();
echo "OK" . PHP_EOL;
}
catch (Exception $e)
{
// The code throws an exception when something goes horribly wrong; e.g.
// no connection to the database could be made. In development mode, we
// show these exception messages. In production mode, we simply return a
// "500 Server Error" message.
if (APPLICATION_ENV == 'development')
var_dump($e);
else
exitWithHttpError(500);
}
////////////////////////////////////////////////////////////////////////////////
function exitWithHttpError($error_code, $message = '')
{
switch ($error_code)
{
case 400: header("HTTP/1.0 400 Bad Request"); break;
case 403: header("HTTP/1.0 403 Forbidden"); break;
case 404: header("HTTP/1.0 404 Not Found"); break;
case 500: header("HTTP/1.0 500 Server Error"); break;
}
header('Content-Type: text/plain');
if ($message != '')
header('X-Error-Description: ' . $message);
exit;
}
function isValidUtf8String($string, $maxLength, $allowNewlines = false)
{
if (empty($string) || strlen($string) > $maxLength)
return false;
if (mb_check_encoding($string, 'UTF-8') === false)
return false;
// Don't allow control characters, except possibly newlines
for ($t = 0; $t < strlen($string); $t++)
{
$ord = ord($string{$t});
if ($allowNewlines && ($ord == 10 || $ord == 13))
continue;
if ($ord < 32)
return false;
}
return true;
}
function truncateUtf8($string, $maxLength)
{
$origString = $string;
$origLength = $maxLength;
while (strlen($string) > $origLength)
{
$string = mb_substr($origString, 0, $maxLength, 'utf-8');
$maxLength--;
}
return $string;
}
UPDATE
Full source code of api.php. My goal is to declare a global variable and pass it a long using a require statement to a new file. I am only able to do so if I delete the entire try block at the beginning of this file and I need to know why.
<?php
global $Var1;
$Var1 = '1';
// This is the server API for the PushChat iPhone app. To use the API, the app
// sends an HTTP POST request to our URL. The POST data contains a field "cmd"
// that indicates what API command should be executed.
try
{
// Are we running in development or production mode? You can easily switch
// between these two in the Apache VirtualHost configuration.
if (!defined('APPLICATION_ENV'))
define('APPLICATION_ENV', getenv('APPLICATION_ENV') ? getenv('APPLICATION_ENV') : 'production');
// In development mode, we show all errors because we obviously want to
// know about them. We don't do this in production mode because that might
// expose critical details of our app or our database. Critical PHP errors
// will still be logged in the PHP and Apache error logs, so it's always
// a good idea to keep an eye on them.
if (APPLICATION_ENV == 'development')
{
error_reporting(E_ALL|E_STRICT);
ini_set('display_errors', 'on');
}
else
{
error_reporting(0);
ini_set('display_errors', 'off');
}
// Load the config file. I prefer to keep all configuration settings in a
// separate file so you don't have to mess around in the main code if you
// just want to change some settings.
require_once 'api_config.php';
$config = $config[APPLICATION_ENV];
// In development mode, we fake a delay that makes testing more realistic.
// You're probably running this on a fast local server but in production
// mode people will be using it on a mobile device over a slow connection.
if (APPLICATION_ENV == 'development')
sleep(0);
// To keep the code clean, I put the API into its own class. Create an
// instance of that class and let it handle the request.
$api = new API($config);
$api->handleCommand();
echo "OK" . PHP_EOL;
}
catch (Exception $e)
{
// The code throws an exception when something goes horribly wrong; e.g.
// no connection to the database could be made. In development mode, we
// show these exception messages. In production mode, we simply return a
// "500 Server Error" message.
if (APPLICATION_ENV == 'development')
var_dump($e);
else
exitWithHttpError(500);
}
////////////////////////////////////////////////////////////////////////////////
function exitWithHttpError($error_code, $message = '')
{
switch ($error_code)
{
case 400: header("HTTP/1.0 400 Bad Request"); break;
case 403: header("HTTP/1.0 403 Forbidden"); break;
case 404: header("HTTP/1.0 404 Not Found"); break;
case 500: header("HTTP/1.0 500 Server Error"); break;
}
header('Content-Type: text/plain');
if ($message != '')
header('X-Error-Description: ' . $message);
exit;
}
function isValidUtf8String($string, $maxLength, $allowNewlines = false)
{
if (empty($string) || strlen($string) > $maxLength)
return false;
if (mb_check_encoding($string, 'UTF-8') === false)
return false;
// Don't allow control characters, except possibly newlines
for ($t = 0; $t < strlen($string); $t++)
{
$ord = ord($string{$t});
if ($allowNewlines && ($ord == 10 || $ord == 13))
continue;
if ($ord < 32)
return false;
}
return true;
}
function truncateUtf8($string, $maxLength)
{
$origString = $string;
$origLength = $maxLength;
while (strlen($string) > $origLength)
{
$string = mb_substr($origString, 0, $maxLength, 'utf-8');
$maxLength--;
}
return $string;
}
////////////////////////////////////////////////////////////////////////////////
class API
{
// Because the payload only allows for 256 bytes and there is some overhead
// we limit the message text to 190 characters.
const MAX_MESSAGE_LENGTH = 190;
private $pdo;
function __construct($config)
{
// Create a connection to the database.
$this->pdo = new PDO(
'mysql:host=' . $config['db']['host'] . ';dbname=' . $config['db']['dbname'],
$config['db']['username'],
$config['db']['password'],
array());
// If there is an error executing database queries, we want PDO to
// throw an exception. Our exception handler will then exit the script
// with a "500 Server Error" message.
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
// We want the database to handle all strings as UTF-8.
$this->pdo->query('SET NAMES utf8');
}
function handleCommand()
{
// Figure out which command the client sent and let the corresponding
// method handle it. If the command is unknown, then exit with an error
// message.
if (isset($_POST['cmd']))
{
switch (trim($_POST['cmd']))
{
case 'join': $this->handleJoin(); return;
case 'leave': $this->handleLeave(); return;
case 'update': $this->handleUpdate(); return;
case 'message': $this->handleMessage(); return;
}
}
exitWithHttpError(400, 'Unknown command');
}
// The "join" API command registers a user to receive notifications that
// are sent in a specific "chat room". Each chat room is identified by a
// secret code. All the users who register with the same secret code can
// see each other's messages.
//
// This command takes the following POST parameters:
//
// - user_Id: A unique identifier. Must be a string of 40 hexadecimal characters.
// - token: The device's device token. Must be a string of 64 hexadecimal
// characters, or "0" if no token is available yet.
// - name: The nickname of the user. Must be a UTF-8 string of maximum 255
// bytes. Only the first 20 bytes are actually shown in the push
// notifications.
// - code: The secret code that identifies the chat room. Must be a UTF-8
// string of maximum 255 bytes.
//
function handleJoin()
{
//function getUserId;
$userId = $this->getUserId();
$token = $this->getDeviceToken(true);
$name = $this->getString('name', 255);
$code = $this->getString('code', 255);
// When the client sends a "join" command, we add a new record to the
// active_users table. We identify the client by the user_id that it
// provides. When the client sends a "leave" command, we delete its
// record from the active_users table.
// It is theoretically possible that a client sends a "join" command
// while its user_id is still present in active_users (because it did not
// send a "leave" command). In that case, we simply remove the old
// record first and then insert the new one.
$this->pdo->beginTransaction();
$stmt = $this->pdo->prepare('DELETE FROM active_users WHERE user_Id = ?');
$stmt->execute(array($userId));
$stmt = $this->pdo->prepare('INSERT INTO active_users (user_Id, device_token, nickname, secret_code, ip_address) VALUES (?, ?, ?, ?, ?)');
$stmt->execute(array($userId, $token, $name, $code, $_SERVER['REMOTE_ADDR']));
$this->pdo->commit();
}
// The "leave" API command removes a user from a chat room. That user will
// no longer receive push notifications for messages sent to that room.
//
// This command takes the following POST parameters:
//
// - user_id: A unique identifier. Must be a string of 40 hexadecimal characters.
//
function handleLeave()
{
$userId = $this->getUserId();
$stmt = $this->pdo->prepare('DELETE FROM active_users WHERE user_Id = ?');
$stmt->execute(array($userId));
}
// The "update" API command gives a user a new device token.
//
// This command takes the following POST parameters:
//
// - user_id: A unique identifier. Must be a string of 40 hexadecimal characters.
// - token: The device's device token. Must be a string of 64 hexadecimal
// characters.
//
function handleUpdate()
{
$userId = $this->getUserId();
$token = $this->getDeviceToken(false);
$stmt = $this->pdo->prepare('UPDATE active_users SET device_token = ? WHERE user_Id = ?');
$stmt->execute(array($token, $userId));
}
// The "message" API command sends a message to all users who are registered
// with the same secret code as the sender of the message.
//
// This command takes the following POST parameters:
//
// - user_id: A unique identifier. Must be a string of 40 hexadecimal characters.
// - text: The message text. Must be a UTF-8 string of maximum 190 bytes.
//
function handleMessage()
{
$userId = $this->getUserId();
/*$text = $this->getString('text', self::MAX_MESSAGE_LENGTH, true);*/
// First, we get the record for the sender of the message from the
// active_users table. That gives us the nickname, device token, and
// secret code for that user.
$stmt = $this->pdo->prepare('SELECT * FROM active_users WHERE user_Id = ? LIMIT 1');
$stmt->execute(array($userId));
$user = $stmt->fetch(PDO::FETCH_OBJ);
if ($user !== false)
{
// Put the sender's name and the message text into the JSON payload
// for the push notification.
$payload = $this->makePayload($user->nickname/*, $text*/);
// Find the device tokens for all other users who are registered
// for this secret code. We exclude the device token of the sender
// of the message, so he will not get a push notification. We also
// exclude users who have not submitted a valid device token yet.
$stmt = $this->pdo->prepare("SELECT device_token FROM active_users WHERE secret_code = ? AND device_token <> ? AND device_token <> '0'");
$stmt->execute(array($user->secret_code, $user->device_token));
$tokens = $stmt->fetchAll(PDO::FETCH_COLUMN);
// Send out a push notification to each of these devices.
foreach ($tokens as $token)
{
$this->addPushNotification($token, $payload);
}
}
}
// Retrieves the user identifier from the POST data. If the user_id does not
// appear to be valid, the script exits with an error message.
function getUserId()
{
if (!isset($_POST['user_id']))
exitWithHttpError(400, 'Missing user_id');
$userId = trim(urldecode($_POST['user_id']));
if (!$this->isValidUserId($userId))
exitWithHttpError(400, 'Invalid user_id');
return $userId;
}
// Checks whether the format of the user identifier is correct (40 hex
// characters or 32 for the simulator).
function isValidUserId($userId)
{
if (strlen($userId) != 40 && strlen($userId) != 32) // 32 for simulator
return false;
if (preg_match("/^[0-9a-fA-F]+$/", $userId) == 0)
return false;
return true;
}
// Retrieves the device token from the POST data. If the token does not
// appear to be valid, the script exits with an error message.
function getDeviceToken($mayBeEmpty = false)
{
if (!isset($_POST['token']))
exitWithHttpError(400, 'Missing device token');
$token = trim($_POST['token']);
// The "join" command allows a token value of "0" to be specified,
// which is necessary in case the client did not yet obtain a device
// token at that point. We allow such clients to join, but they will
// not receive any notifications until they provide a valid token
// using the "update" command.
if ($mayBeEmpty && $token == "0")
return $token;
if (!$this->isValidDeviceToken($token))
exitWithHttpError(400, 'Invalid device token');
return $token;
}
// Checks whether the format of the device token is correct (64 hexadecimal
// characters). Note: we have no means to verify whether the device token
// was really issued by APNS and corresponds to an actual device.
function isValidDeviceToken($deviceToken)
{
if (strlen($deviceToken) != 64)
return false;
if (preg_match("/^[0-9a-fA-F]{64}$/", $deviceToken) == 0)
return false;
return true;
}
// Looks in the POST data for a field with the given name. If the field
// is not a valid UTF-8 string, or it is too long, the script exits with
// an error message.
function getString($name, $maxLength, $allowNewlines = false)
{
if (!isset($_POST[$name]))
exitWithHttpError(400, "Missing $name");
$string = trim($_POST[$name]);
if (!isValidUtf8String($string, $maxLength, $allowNewlines))
exitWithHttpError(400, "Invalid $name");
return $string;
}
// Creates the JSON payload for the push notification message. The "alert"
// text has the following format: "sender_name: message_text". Recipients
// can obtain the name of the sender by parsing the alert text up to the
// first colon followed by a space.
function makePayload($senderName, $text)
{
// Convert the nickname of the sender to JSON and truncate to a maximum
// length of 20 bytes (which may be less than 20 characters).
$nameJson = $this->jsonEncode($senderName);
$nameJson = truncateUtf8($nameJson, 20);
// Convert and truncate the message text
$textJson = $this->jsonEncode($text);
$textJson = truncateUtf8($textJson, self::MAX_MESSAGE_LENGTH);
// Combine everything into a JSON string
$payload = '{"aps":{"alert":"' . $nameJson . ': ' . $textJson . '","sound":"beep.caf"}}';
return $payload;
}
// We don't use PHP's built-in json_encode() function because it converts
// UTF-8 characters to \uxxxx. That eats up 6 characters in the payload for
// no good reason, as JSON already supports UTF-8 just fine.
function jsonEncode($text)
{
static $from = array("\\", "/", "\n", "\t", "\r", "\b", "\f", '"');
static $to = array('\\\\', '\\/', '\\n', '\\t', '\\r', '\\b', '\\f', '\"');
return str_replace($from, $to, $text);
}
// Adds a push notification to the push queue. The notification will not
// be sent immediately. The server runs a separate script, push.php, which
// periodically checks for new entries in this database table and sends
// them to the APNS servers.
function addPushNotification($deviceToken, $payload)
{
// Payloads have a maximum size of 256 bytes. If the payload is too
// large (which shouldn't happen), we won't send this notification.
if (strlen($payload) <= 256)
{
$stmt = $this->pdo->prepare('INSERT INTO push_queue (device_token, payload, time_queued) VALUES (?, ?, NOW())');
$stmt->execute(array($deviceToken, $payload));
}
}
}
?>

while (true) loop only activates upon receiving data from IRC socket, not Telegram socket

OK, this is a big problem of mine and I've spent over an hour creating a working example so I'm hoping someone out there can sympathise enough with my problem enough to give some help or feedback.
I am prepared to give you bash access to a centos system running php 5.4 so you can test the code yourself and I'll even invite you into the telegram group "test chat" so you can see results for yourself. If you accept donations I'm prepared to donate $$$$$$ just name your price.
Here's scenario:
I'm running a communications bot that connects to irc and telegram. The idea is people type !uptime in the chat channel (#public on irc.glx-alliance.com:6668) and in Telegram group test chat and the bot tells them how long they have been online for. The example is working, both IRC and TG return uptime stats upon request through !uptime command. So far so good.
Here's the problem:
When i type !uptime in the IRC client, i get a super fast 0.02 seconds response time. Excellent. That works. However for the Telegram integration, !uptime can take up to 30 seconds to respond. The reason why is below.
Some more details:
The initiator for Telegram responding is not that the loop is checking very slowly, but that IRC has sent data to the IRC connection. Which then prompts the telegram code to run. I can reproduce this easily by simply typing into the chat channel and immediately the Telegram test chat channel receives the uptime response.
Here's my working example. For this to work, you will need to open port 6668 in your firewall, have telegram-cli installed and be running it from a directory with telegram-cli-php installed. See https://github.com/zyberspace/php-telegram-cli-client. Simply type composer require zyberspace/telegram-cli-client in the project directory and crete a telegramIntegrations.php file in an includes directory with the call to vendor.php in there. (require('vendor/autoload.php');). My phone number is +447935499706 add to me telegram and I'll invite you to the test chat group.
Here's the code. Short of playing code golf I can't get the file size down any further. I will describe the important bits after the code itself.
<?php
set_time_limit(0);
ini_set('display_errors', 'on');
global $configure;
$configure = array(
'server' => 'irc.glx-alliance.com',
'port' => 6668,
'nick' => 'ExampleBot',
'name' => 'Example Bot'
);
include_once('../includes/telegramIntegration.php');
class IRCBot{
// TCP connection holder.
public $socket;
// Message holder.
public $msg = array();
/*
* Constucter.
* Opens the server connection, and logs in the bot.
*
* #param array.
*/
function __construct($configure){
echo '-----------Socket opening...----------------------------------' ."\r\n";
$this->socket = fsockopen($configure['server'], $configure['port']);
$this->login($configure);
$this->timestamp = time();
$this->main();
}
/*
* Logs bot in to server
*
* #param array.
*/
function login ($configure){
$this->send_data('USER', $configure['nick'] . ' rogues-alliance.com ' . $configure['nick'] . ' :' . $configure['name']);
$this->send_data('NICK', $configure['nick']);
}
/*
* Startup commands
*/
function startup () {
echo 'Startup initiated...' . PHP_EOL;
echo 'Startup finished' . PHP_EOL;
}
/*
* Bot Command
*/
function intel () {
return $this->intel;
}
/*
* Main function, used to grab all data.
*/
function main(){
while (true):
/* Fetch Data From Telegram Socket */
$this->telegram = new \Zyberspace\Telegram\Cli\Client('unix:///tmp/tg.sck');
/* Fetch Data From IRC Socket */
$data = fgets($this->socket, 1024);
flush();
$data = preg_replace('/\s{2,}/ ', ' ', $data) . PHP_EOL;
$this->ex = explode(' ', $data);
/* Ping Pong */
if($this->ex[0] == 'PING'):
$this->send_data('PONG', $this->ex[1]);
endif;
/* Internal While Loops */
if (!$this->firstRun) {
$this->firstRun = true;
// do some stuff
}
if ($this->inited){ // have we had the server motd etc
}
/* Format Text */
$command = str_replace(array(chr(10), chr(13)), '', $this->ex[3]);
if (strtoupper($this->ex[1]) == $this->ex[1]):
$request = $this->ex[1];
endif;
/* Handle Text from IRC $data */
switch ($request):
case 'PRIVMSG':
/* Setup Variables */
$host = $this->ex[0];
$username = substr($host, 1, strpos($host, '!')-1);
$target = $this->ex[2];
// list of commands the bot responds to
switch ($command){
case ':!uptime':
$this->sendMessage($this->uptime());
break;
break;
case ':!help':
$this->sendMessage('Available commands: !uptime', $this->ex[2]);
break;
}
break;
case '372':
case '375':
case '265':
case '255':
case '254':
case '003':
case '002':
echo $text;
break;
case '376':
/* Startup Commands */
$this->startup();
break;
case 'QUIT':
break;
default:
endswitch;
/* Handle Text From Telegram $telegram */
if (!$channels)
$channels = array(
'test chat'
);
foreach ($channels as $channel):
if (!$this->_tgData[$channel]):
$this->_tgData[$channel] = $this->telegram->getHistory($channel, 1);
$this->_tgData[$channel] = substr($this->_tgData[$channel], strpos($this->_tgData[$channel], ']')+3);
endif;
// fetch data
$this->_history[$channel] = $this->telegram->getHistory($channel, 1, 0);
$this->_history[$channel] = substr($this->_history[$channel], strpos($this->_history[$channel], ']')+3);
flush();
$b=0;
$output = array();
while (str_replace('>>>' , '', str_replace('»»»', '', str_replace('«««', '', str_replace('<<<', '', $this->_tgData[$channel])))) != str_replace('>>>' , '', str_replace('»»»', '', str_replace('«««', '', str_replace('<<<', '', $this->_history[$channel]))))):
// fetch data
$this->_history[$channel] = $this->telegram->getHistory($channel, 1, $b);
$this->_history[$channel] = substr($this->_history[$channel], strpos($this->_history[$channel], ']')+3);
flush();
if (preg_match("/(.+) [«><»]{3} (![\w\d]+) (.+)/", $this->_history[$channel], $matches)):
$username = substr(str_replace($channel, '', $matches[1]), 1);
$command = $matches[2];
$tokens = explode(' ', $matches[3]);
switch($command):
case '!uptime':
echo 'got here';
$this->telegram->msg($channel, $this->uptime());
endswitch;
endif;
$b++;
endwhile;
endforeach;
endwhile;
}
function sendMessage ($message, $to = false){
$this->send_data("PRIVMSG", (!$to?$this->ex[2]:$to) . " :" . $message);
}
/*
* Sends data to the server.
*/
function send_data($cmd, $msg = null){
if($msg == null){
fputs($this->socket, $cmd . "\n");
} else {
fputs($this->socket, $cmd.' '.$msg."\n");
}
}
function uptime () {
echo '------time-----';
$days = round((time() - $this->timestamp)/60/60/24);
$hours = round((time() - $this->timestamp)/60/60%24);
$minutes = round((time() - $this->timestamp)/60%60);
echo $this->timestamp;
echo '---time----';
return "I have been online for $days days, $hours hours and $minutes minutes";
}
}
$bot = new IRCBot($configure);
?>
So the important parts of the code are as follows:
while (true): /*code*/ endwhile;
/* Fetch Data From Telegram Socket */
$this->telegram = new \Zyberspace\Telegram\Cli\Client('unix:///tmp/tg.sck');
/* Fetch Data From IRC Socket */
$data = fgets($this->socket, 1024);
flush();
Data is fetched from IRC socket and Telegram socket, but the IRC socket is receiving data at this point, telegram has to make an additional call to receive data. See below.
$this->_tgData[$channel] = $this->telegram->getHistory($channel, 1);
$this->_tgData[$channel] = substr($this->_tgData[$channel], strpos($this->_tgData[$channel], ']')+3);
Telegram picks its data up here.
So that covers everything I can think of. I will be using the "show this question to a friend" feature so please no posts about how it's too much code; I have explained the relevant parts so please go easy. Also, the code could not be any shorter if I tried. This is a complete example case and I'm prepared to offer you an environment to test within, for which I will donate to you or a charity of your choosing.
Also please note: can someone create the tags telegram-cli and telegram-cli-php, both are very useful projects to the community.
So in the end the result was to change MySQL session lifetime to something more apt.

Wordpress + Constant Contact PHP SDK include path

I'm current trying to implement the Constant Contact PHP sdk into a wordpress site. Ask part of the SDK you have to include some core PHP files in order for the email code to work. If those files aren't found, the page dies with no PHP error. Currently I have the folder called "src" 9their naming convention not mine) in the root of the site's theme (/wp-content/themes/mytheme/src...).
Here is the email form code that calls the files:
<?php
// require the autoloader
require_once(TEMPLATEPATH . '/src/Ctct/autoload.php');
use Ctct\ConstantContact;
use Ctct\Components\Contacts\Contact;
use Ctct\Components\Contacts\ContactList;
use Ctct\Components\Contacts\EmailAddress;
use Ctct\Exceptions\CtctException;
// Enter your Constant Contact APIKEY and ACCESS_TOKEN
define("APIKEY", "XXXXXX");
define("ACCESS_TOKEN", "XXXXXX");
$cc = new ConstantContact(APIKEY);
// attempt to fetch lists in the account, catching any exceptions and printing the errors to screen
try {
$lists = $cc->getLists(ACCESS_TOKEN);
} catch (CtctException $ex) {
foreach ($ex->getErrors() as $error) {
print_r($error);
}
}
// check if the form was submitted
if (isset($_POST['email']) && strlen($_POST['email']) > 1) {
$action = "Getting Contact By Email Address";
try {
// check to see if a contact with the email addess already exists in the account
$response = $cc->getContactByEmail(ACCESS_TOKEN, $_POST['email']);
// create a new contact if one does not exist
if (empty($response->results)) {
$action = "Creating Contact";
$contact = new Contact();
$contact->addEmail($_POST['email']);
$contact->addList($_POST['list']);
$contact->first_name = $_POST['first_name'];
$contact->last_name = $_POST['last_name'];
/*
* The third parameter of addContact defaults to false, but if this were set to true it would tell Constant
* Contact that this action is being performed by the contact themselves, and gives the ability to
* opt contacts back in and trigger Welcome/Change-of-interest emails.
*
* See: http://developer.constantcontact.com/docs/contacts-api/contacts-index.html#opt_in
*/
$returnContact = $cc->addContact(ACCESS_TOKEN, $contact, false);
// update the existing contact if address already existed
} else {
$action = "Updating Contact";
$contact = $response->results[0];
$contact->addList($_POST['list']);
$contact->first_name = $_POST['first_name'];
$contact->last_name = $_POST['last_name'];
/*
* The third parameter of updateContact defaults to false, but if this were set to true it would tell
* Constant Contact that this action is being performed by the contact themselves, and gives the ability to
* opt contacts back in and trigger Welcome/Change-of-interest emails.
*
* See: http://developer.constantcontact.com/docs/contacts-api/contacts-index.html#opt_in
*/
$returnContact = $cc->updateContact(ACCESS_TOKEN, $contact, false);
}
// catch any exceptions thrown during the process and print the errors to screen
} catch (CtctException $ex) {
echo '<span class="label label-important">Error ' . $action . '</span>';
echo '<div class="container alert-error"><pre class="failure-pre">';
print_r($ex->getErrors());
echo '</pre></div>';
die();
}
}
?>
autoload.php is available where it should be at http://www.thedaileymethod.com/_main_site/wp-content/themes/dailey-method/src/Ctct/autoload.php which is what I thought I called in the PHP file, but the page keeps on breaking for me.
Do I have the require_once path incorrect?
EDIT:
When the for code is loaded, the page 500 errors. When I remove the form code, that error goes away and the page loads fine. Nothing nothing in the log from a PHP error standpoint.
Remove statements with use use keyword and change
$cc = new ConstantContact(APIKEY);
to
$cc = new \Ctct\ConstantContact(APIKEY);

Update existing ticket in VTiger

I am a newbie to VTiger, I am using the 5.2.0 version to try, learn implementing Issue Tracking.
Intro:
A client sends an email to say support#company.com, Mail Converter or Mail Scanner.. scans for the new email and if found creates a new ticket.
If admin sees a new ticket which is being raised in Trouble Tickets, makes some changes such as assigning it to someone, or making a comment etc.. VTiger CRM sends an email to a client saying that admin has made modifications to the ticket.
Lets say Client wants some changes, so he replies to support#company.com, a new ticket is raised because a new mail as arrived and mail scanner creates a new ticket.
Problem:
Instead of updating an existing ticket which the client has sent before, creating a new ticket everytime duplicates the problem by making many tickets for one issue, which is a big problem.
When ever a client sends a mail to support#company.com, Subject of email goes as Title of the Ticket and Body of Email as Description of Ticket.
Lets say
Title of Ticket is SubjectClientSent
Client didnot like something after admin makes some modifications and the client decides to reply to the email which VTiger has sent him, it is generally in this manner.
Re: TT17 [ Ticket Id : 22 ] Re : SubjectClientSent
I dont want Mail Scanner to create a new ticket with the title of ticket as Re: TT17 [ Ticket Id : 22 ] Re : SubjectClientSent , I want it to update the exiting ticket with title SubjectClientSent
I tried to do that with creating a new rule something like this..
But, its still creating a new ticket.
Could you help me correct this?
Is there a better way of updating the existing ticket?
Thanks for the help and support.
Found the solution!
The entire answer was written stealing information from the VTiger PDF document, VTiger Forum, VTiger Bug Link
The below pictures shows the basic process involved in automating ticketing with MailScanner or MailConverter
![Mail Scanner Basic Process][4]
1:
Customer (having a Contact/Account record) sends email to support#company.com, with subject
“Test Trouble Ticket”
2:
Mail Scanner creates ticket, links it to matching contact/account record filtered by emailid
lookup.
HelpDeskHandler will send a acknowledgment email with more information on how to respond
further to Customer. The email subject looks like “TT15 [Ticket Id: 1483] Test Trouble Ticket”
3:
Customer replies to the acknowledgment email keeping part of the subject intact to
support#company.com. As mail scanner is configured with Regex rule on subject and finds a
matching Trouble Ticket linked to Customer, it updates the comments with the email body.
4:
When support team update their comment, an email is sent to Customer again.
The following steps will help us achieve this functionality
Step 1: Setup Outgoing Mail Server
If your Outgoing Mail Server is Gmail, the following settings should work for you
Mail Server Settings (SMTP)
Server Name ssl://smtp.gmail.com:465
User Name username#gmail.com
Password ******
From Email from.email#gmail.com
Requires Authentication? Yes
Step 2: Setup MailScanner or MailConverter
DEFAULT Information
Scanner Name DEFAULT
Server Name imap.gmail.com
Protocol imap4
User Name support.company#gmail.com
SSL Type ssl
SSL Method novalidate-cert
Connect URL {imap.gmail.com:993/imap4/ssl/novalidate-cert}
Status Enabled
Scanning Information
Look for All Messages from lastscan
After scan Mark message as Read
Step 3: Setup Rules to create & update tickets in MailScanner or MailConverter
Rules For Mail Converter [DEFAULT]
Priority
From
To
Subject Regex Ticket Id[^:]?: ([0-9]+)
Body
Match All Condition
Action Update Ticket
Priority
From
To
Subject
Body
Match Any Condition
Action Create Ticket
Step 4: Configure config.inc.php
update the following variables in your config.inc.php
$HELPDESK_SUPPORT_EMAIL_ID
FROM address information to be used when sending mails
Example: automated-reply#company.com
$HELPDESK_SUPPORT_EMAIL_ID = 'auto.reply.company#gmail.com';
$HELPDESK_SUPPORT_NAME
FROM name to be used for purpose of display for emails sentout.
Example: Automated Reply
$HELPDESK_SUPPORT_NAME = 'Company Support';
$HELPDESK_SUPPORT_EMAIL_REPLY_ID
REPLY-TO address to be set in the email sent.
Example: support#company.com
$HELPDESK_SUPPORT_EMAIL_REPLY_ID = 'support.company#gmail.com';
Setting this information is one of the important step for autoticketing.
When user tries to Reply for the automated emails the TO
address will be set by the mail-client and reaches the MailBox for
which we have setup the scanning.
Step 5: Create HelpDeskHandler.php in VTigerCRM/modules/HelpDesk/
<?php
/*+**********************************************************************************
* The contents of this file are subject to the vtiger CRM Public License Version 1.0
* ("License"); You may not use this file except in compliance with the License
* The Original Code is: vtiger CRM Open Source
* The Initial Developer of the Original Code is vtiger.
* Portions created by vtiger are Copyright (C) vtiger.
* All Rights Reserved.
************************************************************************************/
class HelpDeskHandler extends VTEventHandler {
function __getSendToEmail($crmid) {
if(empty($crmid)) return false;
$sendtoemail = false;
global $adb;
$metaresult = $adb->pquery("SELECT setype FROM vtiger_crmentity WHERE crmid=? AND deleted = 0", array($crmid));
if($metaresult && $adb->num_rows($metaresult)) {
$metaresultrow = $adb->fetch_array($metaresult);
$emailres = false;
if($metaresultrow['setype'] == 'Contacts') {
$emailres = $adb->pquery("SELECT email,yahooid FROM vtiger_contactdetails WHERE contactid = ?", array($crmid));
} else if($metaresultrow['setype'] == 'Accounts') {
$emailres = $adb->pquery("SELECT email1,email2 FROM vtiger_account WHERE accountid = ?", array($crmid));
}
if($emailres && $adb->num_rows($emailres)) {
$emailresrow = $adb->fetch_array($emailres);
if(!empty($emailresrow[0])) $sendtoemail = $emailresrow[0];
if(!empty($emailresrow[1])) $sendtoemail = $emailresrow[1];
}
}
return $sendtoemail;
}
function handleEvent($eventName, $entityData) {
global $log, $adb;
if($eventName == 'vtiger.entity.aftersave') {
$moduleName = $entityData->getModuleName();
// Event not related to HelpDesk - IGNORE
if($moduleName != 'HelpDesk') {
return;
}
// Take action if the service running is MailScanner (either via Cron/Scan Now)
if(isset($_REQUEST) && $_REQUEST['service'] == 'MailScanner' ) {
$focus = $entityData->focus;
$sendToEmail = $this->__getSendToEmail($focus->column_fields['parent_id']);
// If the entity is create new and we know whom to send the mail proceed.
if($entityData->isNew() && $sendToEmail) {
global $HELPDESK_SUPPORT_EMAIL_ID, $HELPDESK_SUPPORT_NAME, $HELPDESK_SUPPORT_EMAIL_REPLY_ID;
include_once 'vtlib/Vtiger/Mailer.php';
$mailer = new Vtiger_Mailer();
$mailer->ConfigSenderInfo($HELPDESK_SUPPORT_EMAIL_ID, $HELPDESK_SUPPORT_NAME);
$mailer->AddReplyTo($HELPDESK_SUPPORT_EMAIL_REPLY_ID);
$mailer->initFromTemplate('Auto Ticket First Response Template');
// Update the email subject
$mailer->Subject = sprintf("%s [ Ticket Id : %s ] Re : %s",
$focus->column_fields['ticket_no'],
$focus->id,
$focus->column_fields['ticket_title']
);
$mailer->SendTo( $sendToEmail, '', false, false, true );
}
}
}
}
}
?>
Step 6: Create email template named as "Auto Ticket First Response"
This is an acknowledgement email Company Support automatically sends after client sends email to support.company#gmail.com
To create Email Template go to Settings/E-Mail Templates; Select New Template and name it as "Auto Ticket First Response"
Step 7: Create a new PHP file and name it as RegisterHelpDeskHandler.php
Place the following code & execute the file
<?php
/*+**********************************************************************************
* The contents of this file are subject to the vtiger CRM Public License Version 1.0
* ("License"); You may not use this file except in compliance with the License
* The Original Code is: vtiger CRM Open Source
* The Initial Developer of the Original Code is vtiger.
* Portions created by vtiger are Copyright (C) vtiger.
* All Rights Reserved.
************************************************************************************/
$Vtiger_Utils_Log = true;
include_once 'vtlib/Vtiger/Module.php';
include_once 'vtlib/Vtiger/Event.php';
$moduleInstance = Vtiger_Module::getInstance('HelpDesk');
Vtiger_Event::register($moduleInstance, 'vtiger.entity.aftersave', 'HelpDeskHandler', 'modules/HelpDesk/HelpDeskHandler.php');
?>
To execute, simply type the following URL
http://localhost:8888/registerHelpDeskHandler.php
You should see the following output in the browser
Registering Event vtiger.entity.aftersave with [modules/HelpDesk/HelpDeskHandler.php] HelpDeskHandler ... DONE
Step 9: Check for the bug!
If you are on VTiger 5.2.0, the bug is already fixed!
If not, go to modules/Settings/MailScanner/core/MailScannerAction.php and replace the entire code with the following code
<?php
/*********************************************************************************
** The contents of this file are subject to the vtiger CRM Public License Version 1.0
* ("License"); You may not use this file except in compliance with the License
* The Original Code is: vtiger CRM Open Source
* The Initial Developer of the Original Code is vtiger.
* Portions created by vtiger are Copyright (C) vtiger.
* All Rights Reserved.
*
********************************************************************************/
require_once('modules/Emails/Emails.php');
require_once('modules/HelpDesk/HelpDesk.php');
require_once('modules/Users/Users.php');
require_once('modules/Documents/Documents.php');
/**
* Mail Scanner Action
*/
class Vtiger_MailScannerAction {
// actionid for this instance
var $actionid = false;
// scanner to which this action is associated
var $scannerid = false;
// type of mailscanner action
var $actiontype= false;
// text representation of action
var $actiontext= false;
// target module for action
var $module = false;
// lookup information while taking action
var $lookup = false;
// Storage folder to use
var $STORAGE_FOLDER = 'storage/mailscanner/';
/** DEBUG functionality */
var $debug = false;
function log($message) {
global $log;
if($log && $this->debug) { $log->debug($message); }
else if($this->debug) echo "$message\n";
}
/**
* Constructor.
*/
function __construct($foractionid) {
$this->initialize($foractionid);
}
/**
* Initialize this instance.
*/
function initialize($foractionid) {
global $adb;
$result = $adb->pquery("SELECT * FROM vtiger_mailscanner_actions WHERE actionid=? ORDER BY sequence", Array($foractionid));
if($adb->num_rows($result)) {
$this->actionid = $adb->query_result($result, 0, 'actionid');
$this->scannerid = $adb->query_result($result, 0, 'scannerid');
$this->actiontype = $adb->query_result($result, 0, 'actiontype');
$this->module = $adb->query_result($result, 0, 'module');
$this->lookup = $adb->query_result($result, 0, 'lookup');
$this->actiontext = "$this->actiontype,$this->module,$this->lookup";
}
}
/**
* Create/Update the information of Action into database.
*/
function update($ruleid, $actiontext) {
global $adb;
$inputparts = explode(',', $actiontext);
$this->actiontype = $inputparts[0]; // LINK, CREATE
$this->module = $inputparts[1]; // Module name
$this->lookup = $inputparts[2]; // FROM, TO
$this->actiontext = $actiontext;
if($this->actionid) {
$adb->pquery("UPDATE vtiger_mailscanner_actions SET scannerid=?, actiontype=?, module=?, lookup=? WHERE actionid=?",
Array($this->scannerid, $this->actiontype, $this->module, $this->lookup, $this->actionid));
} else {
$this->sequence = $this->__nextsequence();
$adb->pquery("INSERT INTO vtiger_mailscanner_actions(scannerid, actiontype, module, lookup, sequence) VALUES(?,?,?,?,?)",
Array($this->scannerid, $this->actiontype, $this->module, $this->lookup, $this->sequence));
$this->actionid = $adb->database->Insert_ID();
}
$checkmapping = $adb->pquery("SELECT COUNT(*) AS ruleaction_count FROM vtiger_mailscanner_ruleactions
WHERE ruleid=? AND actionid=?", Array($ruleid, $this->actionid));
if($adb->num_rows($checkmapping) && !$adb->query_result($checkmapping, 0, 'ruleaction_count')) {
$adb->pquery("INSERT INTO vtiger_mailscanner_ruleactions(ruleid, actionid) VALUES(?,?)",
Array($ruleid, $this->actionid));
}
}
/**
* Delete the actions from tables.
*/
function delete() {
global $adb;
if($this->actionid) {
$adb->pquery("DELETE FROM vtiger_mailscanner_actions WHERE actionid=?", Array($this->actionid));
$adb->pquery("DELETE FROM vtiger_mailscanner_ruleactions WHERE actionid=?", Array($this->actionid));
}
}
/**
* Get next sequence of Action to use.
*/
function __nextsequence() {
global $adb;
$seqres = $adb->pquery("SELECT max(sequence) AS max_sequence FROM vtiger_mailscanner_actions", Array());
$maxsequence = 0;
if($adb->num_rows($seqres)) {
$maxsequence = $adb->query_result($seqres, 0, 'max_sequence');
}
++$maxsequence;
return $maxsequence;
}
/**
* Apply the action on the mail record.
*/
function apply($mailscanner, $mailrecord, $mailscannerrule, $matchresult) {
$returnid = false;
if($this->actiontype == 'CREATE') {
if($this->module == 'HelpDesk') {
$returnid = $this->__CreateTicket($mailscanner, $mailrecord);
}
} else if($this->actiontype == 'LINK') {
$returnid = $this->__LinkToRecord($mailscanner, $mailrecord);
} else if($this->actiontype == 'UPDATE') {
if($this->module == 'HelpDesk') {
$returnid = $this->__UpdateTicket($mailscanner, $mailrecord,
$mailscannerrule->hasRegexMatch($matchresult));
}
}
return $returnid;
}
/**
* Update ticket action.
*/
function __UpdateTicket($mailscanner, $mailrecord, $regexMatchInfo) {
global $adb;
$returnid = false;
$usesubject = false;
if($this->lookup == 'SUBJECT') {
// If regex match was performed on subject use the matched group
// to lookup the ticket record
if($regexMatchInfo) $usesubject = $regexMatchInfo['matches'];
else $usesubject = $mailrecord->_subject;
// Get the ticket record that was created by SENDER earlier
$fromemail = $mailrecord->_from[0];
$linkfocus = $mailscanner->GetTicketRecord($usesubject, $fromemail);
$relatedid = $linkfocus->column_fields[parent_id];
// If matching ticket is found, update comment, attach email
if($linkfocus) {
$timestamp = $adb->formatDate(date('YmdHis'), true);
$adb->pquery("INSERT INTO vtiger_ticketcomments(ticketid, comments, ownerid, ownertype, createdtime) VALUES(?,?,?,?,?)",
Array($linkfocus->id, $mailrecord->getBodyText(), $relatedid, 'customer', $timestamp));
// Set the ticket status to Open if its Closed
$adb->pquery("UPDATE vtiger_troubletickets set status=? WHERE ticketid=? AND status='Closed'", Array('Open', $linkfocus->id));
$returnid = $this->__CreateNewEmail($mailrecord, $this->module, $linkfocus);
} else {
// TODO If matching ticket was not found, create ticket?
// $returnid = $this->__CreateTicket($mailscanner, $mailrecord);
}
}
return $returnid;
}
/**
* Create ticket action.
*/
function __CreateTicket($mailscanner, $mailrecord) {
// Prepare data to create trouble ticket
$usetitle = $mailrecord->_subject;
$description = $mailrecord->getBodyText();
// There will be only on FROM address to email, so pick the first one
$fromemail = $mailrecord->_from[0];
$linktoid = $mailscanner->LookupContact($fromemail);
if(!$linktoid) $linktoid = $mailscanner->LookupAccount($fromemail);
/** Now Create Ticket **/
global $current_user;
if(!$current_user) $current_user = new Users();
$current_user->id = 1;
// Create trouble ticket record
$ticket = new HelpDesk();
$ticket->column_fields['ticket_title'] = $usetitle;
$ticket->column_fields['description'] = $description;
$ticket->column_fields['ticketstatus'] = 'Open';
$ticket->column_fields['assigned_user_id'] = $current_user->id;
if($linktoid) $ticket->column_fields['parent_id'] = $linktoid;
$ticket->save('HelpDesk');
// Associate any attachement of the email to ticket
$this->__SaveAttachements($mailrecord, 'HelpDesk', $ticket);
return $ticket->id;
}
/**
* Add email to CRM record like Contacts/Accounts
*/
function __LinkToRecord($mailscanner, $mailrecord) {
$linkfocus = false;
$useemail = false;
if($this->lookup == 'FROM') $useemail = $mailrecord->_from;
else if($this->lookup == 'TO') $useemail = $mailrecord->_to;
if($this->module == 'Contacts') {
foreach($useemail as $email) {
$linkfocus = $mailscanner->GetContactRecord($email);
if($linkfocus) break;
}
} else if($this->module == 'Accounts') {
foreach($useemail as $email) {
$linkfocus = $mailscanner->GetAccountRecord($email);
if($linkfocus) break;
}
}
$returnid = false;
if($linkfocus) {
$returnid = $this->__CreateNewEmail($mailrecord, $this->module, $linkfocus);
}
return $returnid;
}
/**
* Create new Email record (and link to given record) including attachements
*/
function __CreateNewEmail($mailrecord, $module, $linkfocus) {
global $current_user, $adb;
if(!$current_user) $current_user = new Users();
$current_user->id = 1;
$focus = new Emails();
$focus->column_fields['parent_type'] = $module;
$focus->column_fields['activitytype'] = 'Emails';
$focus->column_fields['parent_id'] = "$linkfocus->id#-1|";
$focus->column_fields['subject'] = $mailrecord->_subject;
$focus->column_fields['description'] = $mailrecord->getBodyHTML();
$focus->column_fields['assigned_user_id'] = $linkfocus->column_fields['assigned_user_id'];
$focus->column_fields["date_start"]= date('Y-m-d', $mailrecord->_date);
$from=$mailrecord->_from[0];
$to = $mailrecord->_to[0];
$cc = (!empty($mailrecord->_cc))? implode(',', $mailrecord->_cc) : '';
$bcc= (!empty($mailrecord->_bcc))? implode(',', $mailrecord->_bcc) : '';
$flag=''; // 'SENT'/'SAVED'
//emails field were restructured and to,bcc and cc field are JSON arrays
$focus->column_fields['from_email'] = $from;
$focus->column_fields['saved_toid'] = $to;
$focus->column_fields['ccmail'] = $cc;
$focus->column_fields['bccmail'] = $bcc;
$focus->save('Emails');
$emailid = $focus->id;
$this->log("Created [$focus->id]: $mailrecord->_subject linked it to " . $linkfocus->id);
// TODO: Handle attachments of the mail (inline/file)
$this->__SaveAttachements($mailrecord, 'Emails', $focus);
return $emailid;
}
/**
* Save attachments from the email and add it to the module record.
*/
function __SaveAttachements($mailrecord, $basemodule, $basefocus) {
global $adb;
// If there is no attachments return
if(!$mailrecord->_attachments) return;
$userid = $basefocus->column_fields['assigned_user_id'];
$setype = "$basemodule Attachment";
$date_var = $adb->formatDate(date('YmdHis'), true);
foreach($mailrecord->_attachments as $filename=>$filecontent) {
$attachid = $adb->getUniqueId('vtiger_crmentity');
$description = $filename;
$usetime = $adb->formatDate($date_var, true);
$adb->pquery("INSERT INTO vtiger_crmentity(crmid, smcreatorid, smownerid,
modifiedby, setype, description, createdtime, modifiedtime, presence, deleted)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
Array($attachid, $userid, $userid, $userid, $setype, $description, $usetime, $usetime, 1, 0));
$issaved = $this->__SaveAttachmentFile($attachid, $filename, $filecontent);
if($issaved) {
// Create document record
$document = new Documents();
$document->column_fields['notes_title'] = $filename;
$document->column_fields['filename'] = $filename;
$document->column_fields['filestatus'] = 1;
$document->column_fields['filelocationtype'] = 'I';
$document->column_fields['folderid'] = 1; // Default Folder
$document->column_fields['assigned_user_id'] = $userid;
$document->save('Documents');
// Link file attached to document
$adb->pquery("INSERT INTO vtiger_seattachmentsrel(crmid, attachmentsid) VALUES(?,?)",
Array($document->id, $attachid));
// Link document to base record
$adb->pquery("INSERT INTO vtiger_senotesrel(crmid, notesid) VALUES(?,?)",
Array($basefocus->id, $document->id));
}
}
}
/**
* Save the attachment to the file
*/
function __SaveAttachmentFile($attachid, $filename, $filecontent) {
global $adb;
$dirname = $this->STORAGE_FOLDER;
if(!is_dir($dirname)) mkdir($dirname);
$description = $filename;
$filename = str_replace(' ', '-', $filename);
$saveasfile = "$dirname$attachid" . "_$filename";
if(!file_exists($saveasfile)) {
$this->log("Saved attachement as $saveasfile\n");
$fh = fopen($saveasfile, 'wb');
fwrite($fh, $filecontent);
fclose($fh);
}
$mimetype = MailAttachmentMIME::detect($saveasfile);
$adb->pquery("INSERT INTO vtiger_attachments SET attachmentsid=?, name=?, description=?, type=?, path=?",
Array($attachid, $filename, $description, $mimetype, $dirname));
return true;
}
}
?>
Step10: If you still face problems, and still cant get the update ticket functionality;
Check out the VTiger Forums.
[4]: http://i.stack.imgur.com/5ZU7Q.jpgemphasized text*emphasized text*

Categories