GuzzleHttp Parallel Progress For Async Client in Azure and Flysystem - azure

I would like to get the actual block progress and not the Progress of all the transfers. Currently i don't know how to detect the blockId of each individual transfer. The information on the progress callback im currently retrieving is pointless.
Here's the progress function, contained within ServiceRestProxy.php
Original Function https://github.com/Azure/azure-storage-php/blob/master/azure-storage-common/src/Common/Internal/ServiceRestProxy.php#L99
/**
* Create a Guzzle client for future usage.
*
* #param array $options Optional parameters for the client.
*
* #return Client
*/
private static function createClient(array $options)
{
$verify = true;
//Disable SSL if proxy has been set, and set the proxy in the client.
$proxy = getenv('HTTP_PROXY');
// For testing with Fiddler
// $proxy = 'localhost:8888';
// $verify = false;
if (!empty($proxy)) {
$options['proxy'] = $proxy;
}
if (!empty($options['verify'])) {
$verify = $options['verify'];
}
$downloadTotal = 0;
return (new \GuzzleHttp\Client(
array_merge(
$options,
array(
"defaults" => array(
"allow_redirects" => true,
"exceptions" => true,
"decode_content" => true,
),
'cookies' => true,
'verify' => $verify,
'progress' => function (
$downloadTotal,
$downloadedBytes,
$uploadTotal,
$uploadedBytes
){
// i need to detect which block the progress is for.
echo ("progress: download: {$downloadedBytes}/{$downloadTotal}, upload: {$uploadedBytes}/{$uploadTotal}");
}
)
)
));
}

I got a solution to get each block progress.
I needed to use the Async Function for this. Updated version.
/**
* Send the requests concurrently. Number of concurrency can be modified
* by inserting a new key/value pair with the key 'number_of_concurrency'
* into the $requestOptions of $serviceOptions. Return only the promise.
*
* #param callable $generator the generator function to generate
* request upon fulfillment
* #param int $statusCode The expected status code for each of the
* request generated by generator.
* #param ServiceOptions $options The service options for the concurrent
* requests.
*
* #return \GuzzleHttp\Promise\Promise|\GuzzleHttp\Promise\PromiseInterface
*/
protected function sendConcurrentAsync(
callable $generator,
$statusCode,
ServiceOptions $options
) {
$client = $this->client;
$middlewareStack = $this->createMiddlewareStack($options);
$progress = [];
$sendAsync = function ($request, $options) use ($client, $progress) {
if ($request->getMethod() == 'HEAD') {
$options['decode_content'] = false;
}
$options["progress"] = function(
$downloadTotal,
$downloadedBytes,
$uploadTotal,
$uploadedBytes) use($request, $progress){
// extract blockid from url
$url = $request->getUri()->getQuery();
parse_str($url, $array);
// this array can be written to file or session etc
$progress[$array["blockid"]] = ["download_total" => $downloadTotal, "downloaded_bytes" => $downloadedBytes, "upload_total" => $uploadTotal, "uploaded_bytes" => $uploadedBytes];
};
return $client->sendAsync($request, $options);
};
$handler = $middlewareStack->apply($sendAsync);
$requestOptions = $this->generateRequestOptions($options, $handler);
$promises = \call_user_func(
function () use (
$generator,
$handler,
$requestOptions
) {
while (is_callable($generator) && ($request = $generator())) {
yield \call_user_func($handler, $request, $requestOptions);
}
}
);
$eachPromise = new EachPromise($promises, [
'concurrency' => $options->getNumberOfConcurrency(),
'fulfilled' => function ($response, $index) use ($statusCode) {
//the promise is fulfilled, evaluate the response
self::throwIfError(
$response,
$statusCode
);
},
'rejected' => function ($reason, $index) {
//Still rejected even if the retry logic has been applied.
//Throwing exception.
throw $reason;
}
]);
return $eachPromise->promise();
}

Related

How to import products with variations in Shopware 6

I'm trying to import products from an XML with variations.
The import for the products works so far but it doesn't create the variations.
Here is my code (simplified):
/**
* #return int
* #throws \Exception
*/
public function execute()
{
// avoid reaching memory limit
ini_set('memory_limit', '-1');
// set tax id
$this->setTaxId();
if (empty($this->taxId)) {
return 1;
}
// read products from import xml file
$importProducts = $this->loadProducts();
$csvBatch = array_chunk($importProducts, self::BATCH);
$productNumbers = [];
foreach ($csvBatch as $products) {
$productNumbers[] = $this->processImportProducts($products, false);
}
$this->deleteProducts(array_merge(...$productNumbers));
return 0;
}
/**
* #param $productsData
* #param $progressBar
* #return array
*/
private function processImportProducts($productsData, $progressBar)
{
$products = [];
$productNumbers = [];
foreach ($productsData as $product) {
$products[$product['SKU']['#cdata']] = $this->importProducts($product, $progressBar);
$productNumbers[] = $product['SKU']['#cdata'];
}
// upsert product
try {
$this->cleanProductProperties($products, $this->context);
$this->productRepository->upsert(array_values($products), $this->context);
} catch (WriteException $exception) {
$this->logger->info(' ');
$this->logger->info('<error>Products could not be imported. Message: '. $exception->getMessage() .'</error>');
}
unset($products);
return $productNumbers;
}
/**
* #param $product
* #param $progressBar
* #return array
*/
private function importProducts($product, $progressBar)
{
...
$productData = [
'id' => $productId,
'productNumber' => $productNumber,
'price' => [
[
'currencyId' => Defaults::CURRENCY,
'net' => !empty($product['net']) ? $product['net'] : 0,
'gross' => !empty($product['net']) ? $product['net'] : 0,
'linked' => true
]
],
'stock' => 99999,
'unit' => [
'id' => '3fff95a8077b4f5ba3d1d2a41cb53fab'
],
'unitId' => '3fff95a8077b4f5ba3d1d2a41cb53fab',
'taxId' => $this->taxId,
'name' => $productNames,
'description' => $productDescriptions
];
if(isset($product['Variations'])) {
$variationIds = $product['Variations']['#cdata'] ?? '';
$productData['variation'] = [$this->getProductVariationIds($variationIds)];
}
return $productData;
}
/**
* Get product variation ids
*
* #param string $productVariations
* #return string
*/
private function getProductVariationIds($productVariations)
{
$productVariationIds = explode(',', $productVariations);
// get product variationIds in form of a string list
$ids = $this->productRepository->search(
(new Criteria())->addFilter(new EqualsAnyFilter('productNumber', $productVariationIds)),
$this->context
)->getIds();
return implode(',', $ids);
}
It loads correctly the ids but nothing happen. Also no error.
Anyone an idea how to import variations as well?
The variation field is not meant to be persisted or to create variants of a product. It has the Runtime flag, meaning it's not an actual database column but processed during runtime.
You have to create/update variants just like you create the parent product. Additionally you have to set the parentId and the options. The latter being associations to property_group_option, which you'll have to create first.
So in addition to your existing payload when creating parent products, you'll have to add this data to the variants:
$productData = [
// ...
'parentId' => '...'
'options' => [
['id' => '...'],
['id' => '...'],
['id' => '...'],
// ...
],
];
Finally you'll have to create the product_configurator_setting records. That's one record for each option used across all variants. Also the productId for the records has to be the one of the parent product.
$repository = $this->container->get('product_configurator_setting.repository');
$configuratorSettings = [];
foreach ($options as $option) {
$configuratorSetting = [
'optionId' => $option['id'],
'productId' => $parentId,
];
$criteria = new Criteria();
$criteria->addFilter(new EqualsFilter('productId', $parentId));
$criteria->addFilter(new EqualsFilter('optionId', $option['id']));
$id = $repository->searchIds($criteria, $context)->firstId();
// if the configurator setting already exists, update or skip
if ($id) {
$configuratorSetting['id'] = $id;
}
$configuratorSettings[] = $configuratorSetting;
}
$repository->upsert(configuratorSettings, $context);
Just as an addition to make things easier. When creating a product with variants you can just update the configuratorSettings of the parent/father/main-product (whatever you call it).
Then Shopware6 will go and create the variant products automatically. Also the uuids of the children are created automatically. So if need to keep track of these you have to query them after the creation process.
But for a fast creation this might be much faster, if you have a lot of variants the only "variation" are the options. So no special images or texts.

Querying with the Query Builder

I'M USING SYMFONY 4.12 I'm trying to write queries to filter my jobs(I've job table ,départements one) I first try with experience but I'm stuck in
here is my offerController:
/**
* #Route("/offres", name="offres")
* #param Request $request
* #param PaginatorInterface $paginator
* #param FormFactoryInterface $formFactory
* #return Response
*/
public function offreSearch(Request $request, PaginatorInterface $paginator ,FormFactoryInterface $formFactory):Response
{
$datas =new OffreEmploi();
$formFilter=$formFactory->create(OfferFilterForm::class,$datas);
$offres = $this->repository->findSearch($datas);
$formFilter->handleRequest($request);
return $this->render('offre/index.html.twig', [
'controller_name' => 'OffreController',
'offres' => $offres,
'formulaire' =>$formFilter->createView(),
]);
}
and this is my query in the offerRepository:
public function findSearch(OffreEmploi $data):?array
{
$query = $this->createQueryBuilder('o');
if ($data->getExperience() !== null) {
$query
->where('o.experience > :experience')
->setParameter('experience', $data->getExperience());
}
return $query->getQuery()->getResult();
}
when it come to enter any number IT gives the same thing it shows all the jobs stored in the database,I don't know where the problem is.
THE RESULT
Try with this solution:
public function findSearch(OffreEmploi $data):?array
{
$query = $this->createQueryBuilder('o');
if (!empty($data->getExperience())
// ...
}
return $query->getQuery()->getResult();
}
If it doesn't work , try to dump $data->getExperience() to see its value
public function findSearch(OffreEmploi $data):?array
{
$query = $this->createQueryBuilder('o');
dd($data->getExperience()) ;
}
EDIT
So try to do like this but be sure you send the form with GET method not POST:
public function offreSearch(Request $request, PaginatorInterface $paginator)
{
$em = $this->getDoctrine()->getManager();
$form = $this->createForm(OfferFilterForm::class);
$form->handleRequest($request);
$data = $request->query->all();
$qb = $em->getRepository(OffreEmploi::class)->findSearch($data);
$offres = $paginator->paginate($qb, $request->query->get('page', 1), 20);
return $this->render('offre/index.html.twig', array(
'formulaire' =>$form->createView(),
'offres' => $offres,
));
}
In the formType:
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('experience', IntegerType::class);
//.....
}
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => null,
'csrf_protection' => false,
));
}
public function getBlockPrefix()
{
return null;
}
and in the Repository:
public function findSearch($data)
{
$query = $this->createQueryBuilder('o');
if (!empty($data['experience'])) {
$query
->where('o.experience > :experience')
->setParameter('experience', $data['experience']);
}
return $query->getQuery()->getResult();
}
I think I found the answer but I just create another class witch contain all the form's field and that's it I don't know how it works because I didn't change significant things for that thinks for your help.

Executing nested WaterfallDialogs - nodejs

I'm trying to build a requirement system for order dialogs in our bot, so that we can reuse the main structure for different procedures.
enum DialogIds {
// Necessary Ids
oauthPrompt = "oauthPrompt",
// Requirement dialogs
itemWaterfallDialog = "itemWaterfallDialog",
// Reset Dialogs
summaryWaterfallDialog = "summaryWaterfallDialog",
// All other prompts
unrecognizedItemPrompt = "unrecognizedItemPrompt",
beneficiaryConfirmPrompt = "beneficiaryConfirmPrompt",
askBeneficiaryPrompt = "askBeneficiaryPrompt",
reasonPrompt = "reasonPrompt",
orderConfirm = "orderConfirm"
}
export class OrderDialog extends ComponentDialog {
private responseManager: ResponseManager;
private requirementManager: RequirementManager;
private luisResult: RecognizerResult | undefined = undefined;
// TODO: get userState and ConversationState
constructor(
private service: BotServices,
telemetryClient: BotTelemetryClient
) {
super(OrderDialog.name);
this.initialDialogId = OrderDialog.name;
// Response manager serving OrderResponses.json
this.responseManager = new ResponseManager(["fr-fr"], [OrderResponses]);
const routeWaterfallDialog: ((
sc: WaterfallStepContext
) => Promise<DialogTurnResult>)[] = [
this.route.bind(this)
];
this.telemetryClient = telemetryClient;
this.addDialog(
new WaterfallDialog(this.initialDialogId, routeWaterfallDialog)
);
/**
* Order specific dialogs and requirements
*/
const itemWaterfallDialog: WaterfallDialog = new WaterfallDialog(
DialogIds.itemWaterfallDialog,
[this.itemStep.bind(this), this.itemEndStep.bind(this)]
);
this.addDialog(itemWaterfallDialog);
const reqs = [
new Requirement<string>("claimant", false, undefined),
new Requirement<string>(
"item",
true,
undefined,
itemWaterfallDialog,
DialogIds.itemWaterfallDialog
),
];
// Create requirement manager for this dialog
this.requirementManager = new RequirementManager(reqs);
// Add all the prompt
this.addDialog(new ConfirmPrompt(DialogIds.beneficiaryConfirmPrompt));
this.addDialog(new TextPrompt(DialogIds.unrecognizedItemPrompt));
this.addDialog(new TextPrompt(DialogIds.askBeneficiaryPrompt));
this.addDialog(new TextPrompt(DialogIds.reasonPrompt));
this.addDialog(new ConfirmPrompt(DialogIds.orderConfirm));
}
/**
* We save the token, query graph is necessary and
* execute the next dialog if any, if not we'll
* execute the summary waterfallDialog.
* #param sc context
*/
async route(sc: WaterfallStepContext): Promise<DialogTurnResult> {
this.requirementManager.set("claimant", 'nothing');
let next = this.requirementManager.getNext();
while (next) {
await sc.beginDialog(next.dialogId!);
// Execute summary if there are no elements left
if (!this.requirementManager.getNextBool()) {
await sc.beginDialog(DialogIds.summaryWaterfallDialog);
}
next = this.requirementManager.getNext();
}
return sc.endDialog();
}
/**
* ITEM
* #param sc
*/
async itemStep(sc: WaterfallStepContext): Promise<DialogTurnResult> {
// Couldn't recgonize any item
if (this.luisResult!.entities.length === 0) {
await sc.context.sendActivity(
this.responseManager.getResponse(
OrderResponses.itemNotRecognized
)
);
// prompt user for the item again
return await sc.prompt(
DialogIds.unrecognizedItemPrompt,
this.responseManager.getResponse(OrderResponses.rePromptItem)
);
}
const entities = this.luisResult!.entities as generalLuis["entities"];
if (entities.PhoneItem || entities.ComputerItem) {
const item = entities.PhoneItem
? entities.PhoneItem
: entities.ComputerItem;
if (item) {
this.requirementManager.set("item", item[0][0]);
}
}
return await sc.next();
}
async itemEndStep(sc: WaterfallStepContext): Promise<DialogTurnResult> {
// Save result from itemStep(prompt triggered) if any
if (sc.result) {
await sc.context.sendActivity(
this.responseManager.getResponse(OrderResponses.thanksUser)
);
// retrieve item from result and save it
const item = sc.result as string;
this.requirementManager.set("item", item);
}
return sc.endDialog();
}
}
The line
const result = await sc.beginDialog(next.dialogId!);
Is starting a WaterfallDialog declared in the constructor of the Dialog, and the route method is also inside a general waterfallDialog.
The problem is that, when one of the child dialogs prompts the user, the code doesn't wait for the user response, and because of the way the route works it will call the same dialog again(if a value on an object is not filled it will call the indicated dialog, that's what the requirement manager does).
If saving the return from that line, we can see that the status is "waiting", how could I fix it, or should I create independent dialogs for each requirement, and not just waterfallDialogs?
Thanks.

Where to store my node-schedules

I'm very new to Node/Express and I'm making an appointment system. I want my users to make an appointment for the day they want and my system sends them notification on that exact time. I found "node-schedule" module which is really good for this task but I don't know where to implemented this. Is there anyway to store all tasks in my app.js or is it enough to just create a node-schedule task every time I hit certain end point for example:
router.get('/', function(req, res, next) {
var j = schedule.scheduleJob(date, function(){
send notification();
});
res.send(200);
}
Note: I do not want to run a constant for loop on my sql table to check for dates
You'll need to persist your application data to some form of permanent storage by either writing to local files using something like SQLite, running your own database server (like MongoDB) or using a cloud-based storage service like Amazon SimpleDb.
Each of these options (and many others) have npm modules you can use to read/write/delete persistent data. For examples, see MongoDb, SQLite3, and SimpleDb, all available using npm on npmjs.com.
UPDATE
Per your comment below: Well, you did ask where you could store your scheduled events. ;)
To persist all scheduled events so they survive possible server failure, you'd need to create a storable data structure to represent them and for each event, create a new instance of your representation and store it to your persistent storage (MySQL).
Typically, you'd use something along the lines of:
{
when:DateTime -- timestamp when the event should fire
what:Action -- what this event should do
args:Arguments -- arguments to pass to Action
pending:Boolean=true -- if false, this event has already fired
}
When you initialize your server, you would query your persistent storage for all events where pending===true and using the results, initialize instances of the node-schedule module.
When you need to schedule a new event while your server was running, you'd create a new event representation, write it into persistent storage and create a new instance of node-schedule using it.
Finally, and most importantly for customer happiness, when a scheduled event completes successfully, just before your event handler (the Action mentioned above) completes, it would need to mark the persistent version of the event it is handling as pending:false so you don't fire any event more than once.
So for example:
'use strict';
var scheduler = require('node-schedule');
/**
* Storable Representation of a Scheduled Event
*
* #param {string|Date} when
* #param {string} what
* #param {array.<string>} [args=[]]
* #param {boolean} [pending=true]
*
* #property {Date} PersistentEvent.when - the datetime this event should fire.
* #property {string} PersistentEvent.what - the name of the action to run (must match key of PersistentEvent.Actions)
* #property {array} PersistentEvent.args - args to pass to action event handler.
* #property {boolean} PersistentEvent.pending - if true, this event has not yet fired.
*
* #constructor
*
* #example
*
* var PersistentEvent = require('PersistentEvent'),
* mysql = require('mysql'),
* conn = mysql.createConnection({ ... });
*
* conn.connect();
*
* // at some point when initializing your app...
*
* // assign your persistent storage connection...
* PersistentEvent.setStore(conn);
*
* // load all pending event from persistent storage...
* PersistentEvent.loadAll$(function (err) {
* if (err) {
* throw new Error('failed to load all PersistentEvents: ' + err);
* }
*
* // from this point on, all persistent events are loaded and running.
*
* });
*/
var PersistentEvent = function (when, what, args, pending) {
// initialize
PersistentEvent.Cache.push(this.init({
when: when,
what: what,
args: args,
pending: pending
}));
};
// ==== PersistentEvent Static Methods ====
/**
* Pre-defined action event handlers.
* <p>
* Where the property key will be used to match the PersistentEvent.what property,
* and the property value is a event handler function that accepts an optional
* array of args and a callback (provided by PersistentEvent.prototype.schedule)
* </p>
*
* #property {object}
* #property {function} Actions.doSomething
* #property {function} Actions.doSomethingElse
*
* #static
*/
PersistentEvent.Actions = {
doSomething: function (args, cb) {
// defaults
args = args || [];
// TODO check specific args here ...
var result = true,
err = null;
// do your action here, possibly with passed args
cb(err, result);
},
doSomethingElse: function (args, cb) {
// defaults
args = args || [];
// TODO check specific args here ...
var result = true,
err = null;
// do your action here, possibly with passed args
cb(err, result);
}
};
/**
* Cache of all PersistentEvents
*
* #type {Array.<PersistentEvent>}
* #static
*/
PersistentEvent.Cache = [];
// Data Management
/**
* Connection to persistent storage.
* TODO - This should be abstracted to handle other engines that MySQL.
* #property {object}
* #static
*/
PersistentEvent.StorageConnection = null;
/**
* Sets the storage connection used to persist events.
*
* #param {object} storageConnection
* #static
*/
PersistentEvent.setStore = function (storageConnection) { // set the persistent storage connection
// TODO - check args here...
// Note: this function isn't really needed unless you're using other kinds of storage engines
// where you'd want to test what engine was used and mutate this interface accordingly.
PersistentEvent.StorageConnection = storageConnection;
};
/**
* Saves a PersistentEvent to StorageConnection.
*
* #param {PersistentEvent} event - event to save
* #param {function} cb - callback on complete
* #static
*/
PersistentEvent.save$ = function (event, cb) {
var conn = PersistentEvent.StorageConnection;
if (null === conn) {
throw new Error('requires a StorageConnection');
}
// TODO - check for active connection here...
// TODO - check args here...
conn.query('INSERT INTO TABLE when = :when, what = :what, args = :args, pending = :pending', event, cb);
};
/**
* Loads all PersistentEvents from StorageConnection.
* #param {function} cb -- callback on complete
* #static
*/
PersistentEvent.loadAll$ = function (cb) {
var conn = PersistentEvent.StorageConnection;
if (null === conn) {
throw new Error('requires a StorageConnection');
}
// check for active connection here...
// check args here...
conn.query('QUERY * FROM TABLE WHERE pending = true', function (err, results) {
if (err) {
return cb(err);
}
results.forEach(function (result) {
// TODO: check for existence of required fields here...
var event = new PersistentEvent(result.when, result.what, result.args, true);
event.schedule();
});
cb(null);
});
};
// ==== PersistentEvent Methods ====
/**
* Initialize an instance of PersistentEvent.
*
* #param {object} opts
* #return {PersistentEvent}
*/
Event.prototype.init = function (opts) {
// check args
if ('object' !== typeof opts) {
throw new Error('opts must be an object');
}
// set defaults
opts.args = opts.args || [];
opts.pending = opts.pending || true;
// convert string to Date, if required
if ('string' === typeof opts.when) {
opts.when = new Date(opts.when);
}
// check that opts contains needed properties
if (!opts.when instanceof Date) {
throw new Error('when must be a string representation of a Date or a Date object');
}
if ('string' !== typeof opts.what) {
throw new Error('what must be a string containing an action name');
}
if (!Array.isArray(opts.args)) {
throw new Error('args must be an array');
}
if ('boolean' !== typeof opts.pending) {
throw new Error('pending must be a boolean');
}
// set our properties
var self = this;
Object.keys(opts).forEach(function (key) {
if (opts.hasOwnProperty(key)) {
self = opts[key];
}
});
return this;
};
/**
* Override for Object.toString()
* #returns {string}
*/
PersistentEvent.prototype.toString = function () {
return JSON.stringify(this);
};
/**
* Schedule the event to run.<br/>
* <em>Side-effect: saves event to persistent storage.</em>
*/
PersistentEvent.prototype.schedule = function () {
var self = this,
handler = Actions[this.what];
if ('function' !== typeof handler) {
throw new Error('no handler found for action:' + this.what);
}
PersistentEvent.save$(self, function () {
self._event = scheduler.scheduleJob(self.when, function () {
handler(self.args, function (err, result) {
if (err) {
console.error('event ' + self + ' failed:' + err);
}
self.setComplete();
});
});
});
};
/**
* Sets this event complete.<br/>
* <em>Side-effect: saves event to persistent storage.</em>
*/
PersistentEvent.prototype.setComplete = function () {
var self = this;
delete this._event;
this.pending = false;
PersistentEvent.save$(this, function (err) {
if (err) {
console.error('failed to save event ' + self + ' :' + err);
}
});
};
Note that is a first-pass boilerplate to show you one way of designing a solution to your problem. It will require further effort on your part to run.
You can have a cron job every morning that will pick all the appointments for that day and schedule pushes for them. This way you'll have to query the DB once that too on the time when the server load is minimal.

How do I debug a form which does not send an email?

I have a form build from the kohana framework which should send an email.
when I press the "send" button, everything seems to work fine...no error messages...but no email turns up!
If I can´t see anything in firebug..where can I look?
the application logs state
"error: Missing i18n entry contact.captcha.valid for language en_US"
but I don´t know how to get to the bottom of the problem..any help welcome..
yours,
Rob
Ill try and find out which version im using.....the application is the latest version of Ushahidi (2.2.1) www.ushahidi.com
<?php defined('SYSPATH') OR die('No direct access allowed.');
/**
* Captcha library.
*
* $Id: Captcha.php 3917 2009-01-21 03:06:22Z zombor $
*
* #package Captcha
* #author Kohana Team
* #copyright (c) 2007-2008 Kohana Team
* #license http://kohanaphp.com/license.html
*/
class Captcha_Core {
// Captcha singleton
protected static $instance;
// Style-dependent Captcha driver
protected $driver;
// Config values
public static $config = array
(
'style' => 'basic',
'width' => 150,
'height' => 50,
'complexity' => 4,
'background' => '',
'fontpath' => '',
'fonts' => array(),
'promote' => FALSE,
);
/**
* Singleton instance of Captcha.
*
* #return object
*/
public static function instance()
{
// Create the instance if it does not exist
empty(self::$instance) and new Captcha;
return self::$instance;
}
/**
* Constructs and returns a new Captcha object.
*
* #param string config group name
* #return object
*/
public static function factory($group = NULL)
{
return new Captcha($group);
}
/**
* Constructs a new Captcha object.
*
* #throws Kohana_Exception
* #param string config group name
* #return void
*/
public function __construct($group = NULL)
{
// Create a singleton instance once
empty(self::$instance) and self::$instance = $this;
// No config group name given
if ( ! is_string($group))
{
$group = 'default';
}
// Load and validate config group
if ( ! is_array($config = Kohana::config('captcha.'.$group)))
throw new Kohana_Exception('captcha.undefined_group', $group);
// All captcha config groups inherit default config group
if ($group !== 'default')
{
// Load and validate default config group
if ( ! is_array($default = Kohana::config('captcha.default')))
throw new Kohana_Exception('captcha.undefined_group', 'default');
// Merge config group with default config group
$config += $default;
}
// Assign config values to the object
foreach ($config as $key => $value)
{
if (array_key_exists($key, self::$config))
{
self::$config[$key] = $value;
}
}
// Store the config group name as well, so the drivers can access it
self::$config['group'] = $group;
// If using a background image, check if it exists
if ( ! empty($config['background']))
{
self::$config['background'] = str_replace('\\', '/', realpath($config['background']));
if ( ! is_file(self::$config['background']))
throw new Kohana_Exception('captcha.file_not_found', self::$config['background']);
}
// If using any fonts, check if they exist
if ( ! empty($config['fonts']))
{
self::$config['fontpath'] = str_replace('\\', '/', realpath($config['fontpath'])).'/';
foreach ($config['fonts'] as $font)
{
if ( ! is_file(self::$config['fontpath'].$font))
throw new Kohana_Exception('captcha.file_not_found', self::$config['fontpath'].$font);
}
}
// Set driver name
$driver = 'Captcha_'.ucfirst($config['style']).'_Driver';
// Load the driver
if ( ! Kohana::auto_load($driver))
throw new Kohana_Exception('core.driver_not_found', $config['style'], get_class($this));
// Initialize the driver
$this->driver = new $driver;
// Validate the driver
if ( ! ($this->driver instanceof Captcha_Driver))
throw new Kohana_Exception('core.driver_implements', $config['style'], get_class($this), 'Captcha_Driver');
Kohana::log('debug', 'Captcha Library initialized');
}
/**
* Validates a Captcha response and updates response counter.
*
* #param string captcha response
* #return boolean
*/
public static function valid($response)
{
// Maximum one count per page load
static $counted;
// User has been promoted, always TRUE and don't count anymore
if (self::instance()->promoted())
return TRUE;
// Challenge result
$result = (bool) self::instance()->driver->valid($response);
// Increment response counter
if ($counted !== TRUE)
{
$counted = TRUE;
// Valid response
if ($result === TRUE)
{
self::instance()->valid_count(Session::instance()->get('captcha_valid_count') + 1);
}
// Invalid response
else
{
self::instance()->invalid_count(Session::instance()->get('captcha_invalid_count') + 1);
}
}
return $result;
}
/**
* Gets or sets the number of valid Captcha responses for this session.
*
* #param integer new counter value
* #param boolean trigger invalid counter (for internal use only)
* #return integer counter value
*/
public function valid_count($new_count = NULL, $invalid = FALSE)
{
// Pick the right session to use
$session = ($invalid === TRUE) ? 'captcha_invalid_count' : 'captcha_valid_count';
// Update counter
if ($new_count !== NULL)
{
$new_count = (int) $new_count;
// Reset counter = delete session
if ($new_count < 1)
{
Session::instance()->delete($session);
}
// Set counter to new value
else
{
Session::instance()->set($session, (int) $new_count);
}
// Return new count
return (int) $new_count;
}
// Return current count
return (int) Session::instance()->get($session);
}
/**
* Gets or sets the number of invalid Captcha responses for this session.
*
* #param integer new counter value
* #return integer counter value
*/
public function invalid_count($new_count = NULL)
{
return $this->valid_count($new_count, TRUE);
}
/**
* Resets the Captcha response counters and removes the count sessions.
*
* #return void
*/
public function reset_count()
{
$this->valid_count(0);
$this->valid_count(0, TRUE);
}
/**
* Checks whether user has been promoted after having given enough valid responses.
*
* #param integer valid response count threshold
* #return boolean
*/
public function promoted($threshold = NULL)
{
// Promotion has been disabled
if (self::$config['promote'] === FALSE)
return FALSE;
// Use the config threshold
if ($threshold === NULL)
{
$threshold = self::$config['promote'];
}
// Compare the valid response count to the threshold
return ($this->valid_count() >= $threshold);
}
/**
* Returns or outputs the Captcha challenge.
*
* #param boolean TRUE to output html, e.g. <img src="#" />
* #return mixed html string or void
*/
public function render($html = TRUE)
{
return $this->driver->render($html);
}
/**
* Magically outputs the Captcha challenge.
*
* #return mixed
*/
public function __toString()
{
return $this->render();
}
} // End Captcha Class
From the little details provided I think your site uses the I18n library from Kohana for internationalization of the site. The other option is your site use Kohana messages for showing form errors.
The Validation class uses the __() function internally when generation validation errors. I think you don't have a message for key valid specified in APPPATH/messages/contact/captcha.php.
You should try to investigate more on how the form is processed and whether some validation errors are being generated. There might be an error in the captcha validation and despite it seem there is no error, there might be and just the error message could not be shown.
class Contact_Controller extends Main_Controller
{
function __construct()
{
parent::__construct();
}
public function index()
{
$this->template->header->this_page = 'contact';
$this->template->content = new View('contact');
$this->template->header->page_title .= Kohana::lang('ui_main.contact').Kohana::config('settings.title_delimiter');
// Setup and initialize form field names
$form = array (
'contact_name' => '',
'contact_email' => '',
'contact_phone' => '',
'contact_subject' => '',
'contact_message' => '',
'captcha' => ''
);
// Copy the form as errors, so the errors will be stored with keys
// corresponding to the form field names
$captcha = Captcha::factory();
$errors = $form;
$form_error = FALSE;
$form_sent = FALSE;
// Check, has the form been submitted, if so, setup validation
if ($_POST)
{
// Instantiate Validation, use $post, so we don't overwrite $_POST fields with our own things
$post = Validation::factory($_POST);
// Add some filters
$post->pre_filter('trim', TRUE);
// Add some rules, the input field, followed by a list of checks, carried out in order
$post->add_rules('contact_name', 'required', 'length[3,100]');
$post->add_rules('contact_email', 'required','email', 'length[4,100]');
$post->add_rules('contact_subject', 'required', 'length[3,100]');
$post->add_rules('contact_message', 'required');
$post->add_rules('captcha', 'required', 'Captcha::valid');
// Test to see if things passed the rule checks
if ($post->validate())
{
// Yes! everything is valid - Send email
$site_email = Kohana::config('settings.site_email');
$message = Kohana::lang('ui_admin.sender').": " . $post->contact_name . "\n";
$message .= Kohana::lang('ui_admin.email').": " . $post->contact_email . "\n";
$message .= Kohana::lang('ui_admin.phone').": " . $post->contact_phone . "\n\n";
$message .= Kohana::lang('ui_admin.message').": \n" . $post->contact_message . "\n\n\n";
$message .= "~~~~~~~~~~~~~~~~~~~~~~\n";
$message .= Kohana::lang('ui_admin.sent_from_website'). url::base();
// Send Admin Message
email::send( $site_email, $post->contact_email, $post->contact_subject, $message, FALSE );
$form_sent = TRUE;
}
// No! We have validation errors, we need to show the form again, with the errors
else
{
// repopulate the form fields
$form = arr::overwrite($form, $post->as_array());
// populate the error fields, if any
$errors = arr::overwrite($errors, $post->errors('contact'));
$form_error = TRUE;
}
}
$this->template->content->form = $form;
$this->template->content->errors = $errors;
$this->template->content->form_error = $form_error;
$this->template->content->form_sent = $form_sent;
$this->template->content->captcha = $captcha;
// Rebuild Header Block
$this->template->header->header_block = $this->themes->header_block();
$this->template->footer->footer_block = $this->themes->footer_block();
}
}
My problem has been solved!
My Email server was not set up properly. I resolved the postfix issue and now the comments form is working as it should,
thanks to all who answered,
Rob

Resources