I have a form where the user can enter their credit information. When they submit I keep getting 'Object not found'.
I checked the keys and subdomain everything is correct. What could be the issue?
public function createSubscription($plan,$token,$email,$fname,$lname,$currency,$starts_at,$company_name)
{
try {
$subscription = new \Recurly_Subscription();
$subscription->plan_code = $plan;
$subscription->currency = $currency;
$subscription->starts_at = $starts_at;
$account = new \Recurly_Account();
$account->account_code = $email;
$account->first_name = $fname;
$account->last_name = $lname;
$account->email = $email;
$account->company_name = $company_name;
$billing_info = new \Recurly_BillingInfo();
$billing_info->token_id = $token;
$account->billing_info = $billing_info;
$subscription->account = $account;
$subscription->create();
}
catch (\Recurly_ValidationError $e){
throw new CreditDeclined($e->getMessage());
}
catch ( \Exception $e) {
throw new InvalidGeneral($e->getMessage());
}
}
The two things I'd check first are that the plan code and billing token are valid.
The other thing to check is that you've loaded our PHP libraries. You need to have this someplace in your code:
require_once('lib/recurly.php');
Require the lib/recurly.php script and setup your authentication credentials:
<?php
require_once('lib/recurly.php');
// Required for the API
Recurly_Client::$subdomain = 'your-subdomain';
Recurly_Client::$apiKey = 'abcdef01234567890abcdef01234567890';
?>
It should be correct.Please re-check.
I have lots of images into mysql database, which are painted so: <img src="/PrintImage.php?$id&width&height&quality" />.
The problem is that for each image, I am doing a call to the database through a class. To explain it better, here is the code:
edited: posted entirely class code
HTML View (example):
<img src="/PrintImage.php?id=10&width=120&height=120&quality=100" />
<img src="/PrintImage.php?id=20&width=120&height=120&quality=100" />
PrintImage.php
<?php
include_once($_SERVER['DOCUMENT_ROOT'].'/php/classes/Galerias.php');
$a = new \Galerias();
$i = $_GET['i'];
$w = $_GET['w'];
$h = $_GET['h'];
$q = $_GET['q'];
$a->Pintar($i, $w, $h, $q);
unset($a);
Gallery.php:
<?php
namespace Galerias;
include_once 'Database.php';
include_once 'Utils.php';
use \Database;
use \Utils;
class Galerias {
private $img_max_width = 1024;
private $img_max_height = 768;
private $img_quality = 85;
function __construct(){
$this->Database = new Database();
$this->Utils = new Utils();
}
public function Pintar($id, $width, $height, $quality){
$query = "select (select titulo from imagenes where id=$id) as titulo, (select imagen from imagenes where id=$id) as imagen";
$data = $this->Database->GetData($query);
$titulo = $data[0]['titulo'];
$tmp = $data[0]['imagen'];
$dataimg = $this->Utils->formatImage("string", $tmp, $width, $height, false);
$mime = $dataimg[1];
header("Content-type: $mime");
header("Content-Disposition: inline; filename=$titulo");
imagejpeg($dataimg[0], null, $quality);
}
}
Database.php:
<?php
namespace Database;
include_once 'Utils.php';
use \Utils;
class Database {
private $host = "localhost";
private $user = "user";
private $pass = "pass";
private $daba = "database";
private $link;
public $Utils;
function __construct(){
$this->Open();
$this->Utils = new Utils();
}
function __destruct(){
error_log('Database destruct');
}
private function Open(){
$this->link = mysql_connect($this->host, $this->user, $this->pass);
error_log('open succeeded');
mysql_select_db($this->daba, $this->link) or $this->Utils->newEx("Class Database->Open(): ".mysql_error());
}
private function Close(){
mysql_close($this->link) or $this->Utils->newEx("Class Database->Close(): ".mysql_error());
error_log('close succeeded');
}
public function GetData($query){
$data = array();
$query = mysql_query($query) or $this->Utils->newEx("Class Database->GetData(): ".mysql_error(), true);
while( $result = mysql_fetch_array($query) ){
$data[] = $result;
}
$this->FreeResults($query);
return $data;
}
public function InsertData($query){
if( !mysql_query($query) ){
$this->Utils->newEx("Class Database->InsertData(): ". mysql_error(), true);
return false;
}
return true;
}
public function DeleteData($query){
if( !mysql_query($query) ){
$this->Utils->newEx("Class Database->DeleteData(): ". mysql_error(), true);
return false;
}
return true;
}
public function FreeResults($res = null){
if( !is_null($res) ) mysql_free_result($res);
}
}
I am calling database too many times, because I have to load ~50/100 images per gallery.
The question is, how can I do this task efficiently? Thanks in advance
Your real question is: How can i minimize the amount of calls to the database.
The data is stored in the database so will have to do a call to retrieve the data. However what do you need to ask yourself, do i really need to ask the question every time?
In your current object model, you retrieve the unique data for every picture only when the object is created. This will add one call every time you want to read the info. This is a very common mistake to do and is something that (as you already realized) doesn't scale at all.
What you need is to make the object already have the data before the call. There is numerous ways to do this. Object Factories, Cached object, memcache, redis the list can be as long as there is active developers out there.
I'm inviting you to try to think outside the box and find a solution. Because if you understand the problem and solve it you will get a better grasp of object models and the pitfalls.
Lets take it again, are you sure you need to make the database call in the construction of the object? If you know the subset of the data you need, you should ask for in bulk. That will remove a lot of queries to the database.
Perhaps the Galerias could have function where you grab a lot of Pintar in one go?
I know this isn't a "do this" answer and probably will get down voted. But at least try :).
Also: Never use the query parameters $_GET $_POST directly without sanitize them first!!
Finally, I got the logical system to reduce -considerably- the amount of calls to the database. Before, I used a PHP file as image src to print images (with its headers, etc) directly from the database. This is a terrible fail. The problems to solve were:
I need to use a PHP file as image src, passing an ID, width, height and quality. And, of course, it has to be a friendly uri. So I can't use base64 encoding to print images. It has to be a PHP file (other file, other process, it isn't connected to the first one).
I use a shared hosting, the thinking of loading -nice- extensions like memcache is not viable.
Problem 2 tells me that I can't save images (or other data) in any place to use along the site. (It's wrong).
What I did (after thinking and searching and thinking...) is to use $_SESSION to store, previous serializing, all images. So, I solved all the problems. Now, have to look for a valid logic to populate the code. The result looks like this:
// Session Cache Class (static)
namespace SessionCache;
ini_set('memory_limit', '256M'); // this is the best issue of this system
session_start();
class SessionCache {
// todo: add the possible to expire the session
private static $SessionName = 'SessionCache';
public static function Check($name){
if( empty($_SESSION[self::$SessionName]) ) return false;
if( empty($_SESSION[self::$SessionName][$name]) ) return false;
else return true;
}
public static function Set($name, $data){
if( self::Check($name) ) return;
$data = serialize($data);
$_SESSION[self::$SessionName][$name] = $data;
}
public static function Get($name){
if( self::Check($name) ){
$data = unserialize($_SESSION[self::$SessionName][$name]);
return $data;
}
else return null;
}
public static function Flush($name){
if( self::Check($name) ) unset($_SESSION[self::$SessionName][$name]);
}
public static function Destroy(){
session_destroy();
}
}
Now, the (current) logic:
// Images Class. Here I use some extra stuff. Feel free to read the comments
namespace AdminPanel;
include_once 'SessionCache.php';
include_once 'Database.php';
include_once 'Utils.php';
use \AdminPanel\Database;
use \AdminPanel\Utils;
use SessionCache\SessionCache;
use stdClass;
class Patrocinios {
private $cache_name = 'Patrocinadores';
private $img_width = 225;
private $img_height = 70;
private $img_quality = 100;
public function __construct(){
$this->Database = new \AdminPanel\Database();
$this->Utils = new \AdminPanel\Utils();
}
private function CreateImageCache(){
if( SessionCache::Check($this->cache_name) ) return null;
$query = "select * from patrocinadores";
$this->Database->Open();
$data = $this->Database->GetData($query);
$this->Database->Close();
$patros = new stdClass();
if( count($data) > 0 ){
for( $i=0; $i<count($data); $i++ ){
$id = $data[$i]['id'];
$nombre = $data[$i]['nombre'];
$uri = $data[$i]['web'];
$mimetype = $data[$i]['tipo'];
$imagedata = $data[$i]['imagen'];
$patros->patro[$id] = new stdClass();
$patros->patro[$id]->id = $id;
$patros->patro[$id]->nombre = $nombre;
$patros->patro[$id]->uri = $uri;
$patros->patro[$id]->mimetype = $mimetype;
$patros->patro[$id]->data = $imagedata; // the image BLOB
}
}
SessionCache::Set($this->cache_name, $patros);
}
public function GetPatrocinadores(){ // this method is the only one called from the main view
$this->CreateImageCache();
$patros = SessionCache::Get($this->cache_name);
return $patros;
}
public function Pintar($id, $width, $height, $quality){ // this method is called from the PHP file used to print the images
if( !SessionCache::Check($this->cache_name) ) $patros = $this->GetPatrocinadores();
else $patros = SessionCache::Get($this->cache_name);
$dataimg = $this->Utils->formatImage("string", $patros->patro[$id]->data, $width, $height); // creates new image with desired measures and quality
header('Content-Type: '.$patros->patro[$id]->mimetype);
header('Content-Length: '.strlen($patros->patro[$id]->data));
imagejpeg($dataimg[0], null, $quality);
}
}
I just have a variable with the name of the session object ($cache_name). First I check if exists (from previous call). If not, I populate a stdClass() object with the information from the database and store it in the session.
<ul id="ulPatrocinadores">
<?php
include_once $_SERVER['DOCUMENT_ROOT'].'/php/classes/Patrocinios.php';
$p = new \AdminPanel\Patrocinios();
$patros = $p->GetPatrocinadores();
$str = '';
foreach( $patros as $value ){
foreach( $value as $patro ){
$id = $patro->id;
$nombre = str_replace(" ", "_", $patro->nombre);
$web = $patro->uri;
$str .= '<li>';
$str .= '<img src="/'.$nombre.'_pat-i='.$id.'&w=225&h=70&q=85" alt="'.$nombre.'" />';
}
}
echo $str;
?>
</ul>
Above is the main view which images are printed. Note that anchors and image sources goes to the PHP file that calls to the method Pintar(). I use a RewriteRule to redirect it.
<?php
include_once '../scripts/PrintPartner.php';
$a = new \AdminPanel\Patrocinios();
$i = $_GET['i'];
$w = $_GET['w'];
$h = $_GET['h'];
$q = $_GET['q'];
$a->Pintar($i, $w, $h, $q);
unset($a);
And so, I finally achieve it. I don't know if this is a good system because $_SESSION is relational to php memory_limit, but after two days of thinking about it, I couldn't get something better.
What I achieve:
Before: one query for each image to the database. Now: one query for all images for one session (or the time that I could need).
Keep of the friendly uris with creating of the image file on the fly as needed, still using a PHP file as image src.
A good system to reduce calls to the database, also using a shared hosting.
Hope this experience helps someone.
I start saying that I HATE OpenID, because it's poorly implemented/documented.
I'm trying to use "openid-php-openid-2.2.2-24". Here the source code: https://github.com/openid/php-openid
When I try to use the authentication example, it returns to me:
"You have successfully verified https://www.google.com/accounts/o8/id?id=[...] as your identity.
No PAPE response was sent by the provider."
but there's no shadow of email, nickname or fullname of google openid login data.
While reading the file ("/openid/examples/consumer/finish_auth.php"), I note that SREG variables have to be printed between the "You have successfully verified" and "No PAPE response" messages, but they don't:
$success = sprintf('You have successfully verified ' .
'%s as your identity.',
$esc_identity, $esc_identity);
if ($response->endpoint->canonicalID) {
$escaped_canonicalID = escape($response->endpoint->canonicalID);
$success .= ' (XRI CanonicalID: '.$escaped_canonicalID.') ';
}
$sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
$sreg = $sreg_resp->contents();
if (#$sreg['email']) {
$success .= " You also returned '".escape($sreg['email']).
"' as your email.";
}
if (#$sreg['nickname']) {
$success .= " Your nickname is '".escape($sreg['nickname']).
"'.";
$_SESSION['nickname'] = escape($sreg['nickname']);
}
if (#$sreg['fullname']) {
$success .= " Your fullname is '".escape($sreg['fullname']).
"'.";
}
$pape_resp = Auth_OpenID_PAPE_Response::fromSuccessResponse($response);
if ($pape_resp) {
[...]
} else {
$success .= "<p>No PAPE response was sent by the provider.</p>";
}
I've tried to print the content of $sreg['email'], $sreg['nickname'] and $sreg['fullname'], but they return all blank contents (null/empty values).
I need to retrieve the email address of the account which users use to login in..
Dante
To get the question off the unanswered list, I post dante's answer here as answer:
I solved my problem.
Example usage of AX in PHP OpenID: Example usage of AX in PHP OpenID
After 2 days of research, I've just now found the answer ("but Google uses AX (attribute exchange) instead of SReg for additional data"). Why Google must always be so different?
However, the code in that stackoverflow answer page doesn't work for me (my hosting server returns 500 internal server error code).
So, I post here "my code" (it's so rough):
oid_ax_common.php
<?php
// Circumnavigate bugs in the GMP math library that can be result in signature
// validation errors
define('Auth_OpenID_BUGGY_GMP', true);
$path_extra = dirname(dirname(dirname(__FILE__)));
$path = ini_get('include_path');
$path = $path_extra . PATH_SEPARATOR . $path;
ini_set('include_path', $path);
function displayError($message) {
$error = $message;
include './index.php';
exit(0);
}
function doIncludes() {
/**
* Require the OpenID consumer code.
*/
require_once "Auth/OpenID/Consumer.php";
/**
* Require the "file store" module, which we'll need to store
* OpenID information.
*/
require_once "Auth/OpenID/FileStore.php";
/**
* Require the Simple Registration extension API.
*/
//require_once "Auth/OpenID/SReg.php";
require_once "Auth/OpenID/AX.php";
/**
* Require the PAPE extension module.
*/
require_once "Auth/OpenID/PAPE.php";
}
doIncludes();
global $pape_policy_uris;
$pape_policy_uris = array(
PAPE_AUTH_MULTI_FACTOR_PHYSICAL,
PAPE_AUTH_MULTI_FACTOR,
PAPE_AUTH_PHISHING_RESISTANT
);
function &getStore() {
/**
* This is where the example will store its OpenID information.
* You should change this path if you want the example store to be
* created elsewhere. After you're done playing with the example
* script, you'll have to remove this directory manually.
*/
$store_path = null;
if (function_exists('sys_get_temp_dir')) {
$store_path = sys_get_temp_dir();
}
else {
if (strpos(PHP_OS, 'WIN') === 0) {
$store_path = $_ENV['TMP'];
if (!isset($store_path)) {
$dir = 'C:\Windows\Temp';
}
}
else {
$store_path = #$_ENV['TMPDIR'];
if (!isset($store_path)) {
$store_path = '/tmp';
}
}
}
$store_path = './tmp/';
$store_path .= DIRECTORY_SEPARATOR . '_php_consumer_test';
if (!file_exists($store_path) &&
!mkdir($store_path)) {
print "Could not create the FileStore directory '$store_path'. ".
" Please check the effective permissions.";
exit(0);
}
$r = new Auth_OpenID_FileStore($store_path);
return $r;
}
function &getConsumer() {
/**
* Create a consumer object using the store object created
* earlier.
*/
$store = getStore();
$r = new Auth_OpenID_Consumer($store);
return $r;
}
function getScheme() {
$scheme = 'http';
if (isset($_SERVER['HTTPS']) and $_SERVER['HTTPS'] == 'on') {
$scheme .= 's';
}
return $scheme;
}
function getReturnTo() {
return sprintf("%s://%s:%s%s/oid_ax_receive.php",
getScheme(), $_SERVER['SERVER_NAME'],
$_SERVER['SERVER_PORT'],
dirname($_SERVER['PHP_SELF']));
}
function getTrustRoot() {
return sprintf("%s://%s:%s%s/",
getScheme(), $_SERVER['SERVER_NAME'],
$_SERVER['SERVER_PORT'],
dirname($_SERVER['PHP_SELF']));
}
?>
oid_ax_send.php
<?php
require_once "oid_ax_common.php";
// Starts session (needed for YADIS)
session_start();
function getOpenIDURL() {
// Render a default page if we got a submission without an openid
// value.
if (empty($_GET['openid_identifier'])) {
$error = "Expected an OpenID URL.";
include './index.php';
exit(0);
}
return $_GET['openid_identifier'];
}
function run() {
// https://www.google.com/accounts/o8/id
// $openid = 'http://openid-provider.appspot.com/';
$openid = 'https://www.google.com/accounts/o8/id';
// $openid .= getOpenIDURL();
$consumer = getConsumer();
// Begin the OpenID authentication process.
$auth_request = $consumer->begin($openid);
// Create attribute request object
// See http://code.google.com/apis/accounts/docs/OpenID.html#Parameters for parameters
// Usage: make($type_uri, $count=1, $required=false, $alias=null)
$attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/contact/email',2,1, 'email');
$attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/namePerson/first',1,1, 'firstname');
$attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/namePerson/last',1,1, 'lastname');
// Create AX fetch request
$ax = new Auth_OpenID_AX_FetchRequest;
// Add attributes to AX fetch request
foreach($attribute as $attr){
$ax->add($attr);
}
// Add AX fetch request to authentication request
$auth_request->addExtension($ax);
// No auth request means we can't begin OpenID.
if (!$auth_request) {
displayError("Authentication error; not a valid OpenID.");
}
/* $sreg_request = Auth_OpenID_SRegRequest::build(
// Required
array('nickname'),
// Optional
array('fullname', 'email'));
if ($sreg_request) {
$auth_request->addExtension($sreg_request);
} */
$policy_uris = null;
if (isset($_GET['policies'])) {
$policy_uris = $_GET['policies'];
}
$pape_request = new Auth_OpenID_PAPE_Request($policy_uris);
if ($pape_request) {
$auth_request->addExtension($pape_request);
}
// Redirect the user to the OpenID server for authentication.
// Store the token for this authentication so we can verify the
// response.
// For OpenID 1, send a redirect. For OpenID 2, use a Javascript
// form to send a POST request to the server.
if ($auth_request->shouldSendRedirect()) {
$redirect_url = $auth_request->redirectURL(getTrustRoot(),
getReturnTo());
// If the redirect URL can't be built, display an error
// message.
if (Auth_OpenID::isFailure($redirect_url)) {
displayError("Could not redirect to server: " . $redirect_url->message);
} else {
// Send redirect.
header("Location: ".$redirect_url);
}
} else {
// Generate form markup and render it.
$form_id = 'openid_message';
$form_html = $auth_request->htmlMarkup(getTrustRoot(), getReturnTo(),
false, array('id' => $form_id));
// Display an error if the form markup couldn't be generated;
// otherwise, render the HTML.
if (Auth_OpenID::isFailure($form_html)) {
displayError("Could not redirect to server: " . $form_html->message);
} else {
print $form_html;
}
}
}
run();
?>
oid_ax_receive.php
<?php
require_once "oid_ax_common.php";
// Starts session (needed for YADIS)
session_start();
function escape($thing) {
return htmlentities($thing);
}
function run() {
$consumer = getConsumer();
// Complete the authentication process using the server's
// response.
$return_to = getReturnTo();
$response = $consumer->complete($return_to);
// Check the response status.
if ($response->status == Auth_OpenID_CANCEL) {
// This means the authentication was cancelled.
$msg = 'Verification cancelled.';
} else if ($response->status == Auth_OpenID_FAILURE) {
// Authentication failed; display the error message.
$msg = "OpenID authentication failed: " . $response->message;
} else if ($response->status == Auth_OpenID_SUCCESS) {
// Get registration informations
$ax = new Auth_OpenID_AX_FetchResponse();
$obj = $ax->fromSuccessResponse($response);
// Print me raw
echo '<pre>';
print_r($obj->data);
echo '</pre>';
exit;
$pape_resp = Auth_OpenID_PAPE_Response::fromSuccessResponse($response);
if ($pape_resp) {
if ($pape_resp->auth_policies) {
$success .= "<p>The following PAPE policies affected the authentication:</p><ul>";
foreach ($pape_resp->auth_policies as $uri) {
$escaped_uri = escape($uri);
$success .= "<li><tt>$escaped_uri</tt></li>";
}
$success .= "</ul>";
} else {
$success .= "<p>No PAPE policies affected the authentication.</p>";
}
if ($pape_resp->auth_age) {
$age = escape($pape_resp->auth_age);
$success .= "<p>The authentication age returned by the " .
"server is: <tt>".$age."</tt></p>";
}
if ($pape_resp->nist_auth_level) {
$auth_level = escape($pape_resp->nist_auth_level);
$success .= "<p>The NIST auth level returned by the " .
"server is: <tt>".$auth_level."</tt></p>";
}
} else {
$success .= "<p>No PAPE response was sent by the provider.</p>";
}
}
include './index.php';
}
run();
?>
Enjoy.
Dante
P.S.: to complete the opera of OpenID, although I solved my problem with user info / login data with Google, I still have one problem with Light OpenID (https://stackoverflow.com/questions/10735708/lightopenid-openid-authurl-does-not-return-any-value).
If you want to help me, we will completely work out and conclude with the OpenID story.
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*