Media creation via php in Shopware 6 - shopware

i'm struggling get a media import via PHP for Shopware 6 to work.
This is my service:
<?php declare(strict_types=1);
namespace My\Namespace\Service;
use Shopware\Core\Content\Media\File\MediaFile;
use Shopware\Core\Content\Media\MediaService;
use Shopware\Core\Framework\Context;
class ImageImport
{
/**
* #var MediaService
*/
protected $mediaService;
/**
* ImageImport constructor.
* #param MediaService $mediaService
*/
public function __construct(MediaService $mediaService)
{
$this->mediaService = $mediaService;
}
public function addImageToProductMedia($imageUrl, Context $context)
{
$mediaId = NULL;
$context->disableCache(function (Context $context) use ($imageUrl, &$mediaId): void {
$filePathParts = explode('/', $imageUrl);
$fileName = array_pop($filePathParts);
$fileNameParts = explode('.', $fileName);
$actualFileName = $fileNameParts[0];
$fileExtension = $fileNameParts[1];
if ($actualFileName && $fileExtension) {
$tempFile = tempnam(sys_get_temp_dir(), 'image-import');
file_put_contents($tempFile, file_get_contents($imageUrl));
$fileSize = filesize($tempFile);
$mimeType = mime_content_type($tempFile);
$mediaFile = new MediaFile($tempFile, $mimeType, $fileExtension, $fileSize);
$mediaId = $this->mediaService->saveMediaFile($mediaFile, $actualFileName, $context, 'product');
}
});
return $mediaId;
}
}
A entry in the table media with the correct media_folder_association is created. And as far as i can see there are no differences to other medias uploaded via backend (except private is 1 and user_id is NULL).
But in the backend the media entries are broken, seems like it can not load the actual image file (i've tried to set private to true to see it in the media section, same happens when adding the media to a product via php, but i guess the problem is before any assignment to products).
Image in backend media
Has anybody a suggestion whats wrong here?
Thanks
Phil
===== SOLUTION ======
Here is the updated and working service:
<?php declare(strict_types=1);
namespace My\Namespace\Service;
use Shopware\Core\Content\Media\File\FileSaver;
use Shopware\Core\Content\Media\File\MediaFile;
use Shopware\Core\Content\Media\MediaService;
use Shopware\Core\Framework\Context;
class ImageImport
{
/**
* #var MediaService
*/
protected $mediaService;
/**
* #var FileSaver
*/
private $fileSaver;
/**
* ImageImport constructor.
* #param MediaService $mediaService
* #param FileSaver $fileSaver
*/
public function __construct(MediaService $mediaService, FileSaver $fileSaver)
{
$this->mediaService = $mediaService;
$this->fileSaver = $fileSaver;
}
public function addImageToProductMedia($imageUrl, Context $context)
{
$mediaId = NULL;
$context->disableCache(function (Context $context) use ($imageUrl, &$mediaId): void {
$filePathParts = explode('/', $imageUrl);
$fileName = array_pop($filePathParts);
$fileNameParts = explode('.', $fileName);
$actualFileName = $fileNameParts[0];
$fileExtension = $fileNameParts[1];
if ($actualFileName && $fileExtension) {
$tempFile = tempnam(sys_get_temp_dir(), 'image-import');
file_put_contents($tempFile, file_get_contents($imageUrl));
$fileSize = filesize($tempFile);
$mimeType = mime_content_type($tempFile);
$mediaFile = new MediaFile($tempFile, $mimeType, $fileExtension, $fileSize);
$mediaId = $this->mediaService->createMediaInFolder('product', $context, false);
$this->fileSaver->persistFileToMedia(
$mediaFile,
$actualFileName,
$mediaId,
$context
);
}
});
return $mediaId;
}
}

In order to import files to Shopware 6 theres two steps which are necessary:
You have to create a media file object (MediaDefinition / media table). Take a look at the MediaConverter
Create a new entry in the SwagMigrationMediaFileDefinition (swag_migration_media_file table).
Each entry in the swag_migration_media_file table of the associated migration run will get processed by an implementation of MediaFileProcessorInterface.
To add a file to the table you can do something like this in your Converter class (this example is from the MediaConverter):
abstract class MediaConverter extends ShopwareConverter
{
public function convert(
array $data,
Context $context,
MigrationContextInterface $migrationContext
): ConvertStruct {
$this->generateChecksum($data);
$this->context = $context;
$this->locale = $data['_locale'];
unset($data['_locale']);
$connection = $migrationContext->getConnection();
$this->connectionId = '';
if ($connection !== null) {
$this->connectionId = $connection->getId();
}
$converted = [];
$this->mainMapping = $this->mappingService->getOrCreateMapping(
$this->connectionId,
DefaultEntities::MEDIA,
$data['id'],
$context,
$this->checksum
);
$converted['id'] = $this->mainMapping['entityUuid'];
if (!isset($data['name'])) {
$data['name'] = $converted['id'];
}
$this->mediaFileService->saveMediaFile(
[
'runId' => $migrationContext->getRunUuid(),
'entity' => MediaDataSet::getEntity(), // important to distinguish between private and public files
'uri' => $data['uri'] ?? $data['path'],
'fileName' => $data['name'], // uri or path to the file (because of the different implementations of the gateways)
'fileSize' => (int) $data['file_size'],
'mediaId' => $converted['id'], // uuid of the media object in Shopware 6
]
);
unset($data['uri'], $data['file_size']);
$this->getMediaTranslation($converted, $data);
$this->convertValue($converted, 'title', $data, 'name');
$this->convertValue($converted, 'alt', $data, 'description');
$albumMapping = $this->mappingService->getMapping(
$this->connectionId,
DefaultEntities::MEDIA_FOLDER,
$data['albumID'],
$this->context
);
if ($albumMapping !== null) {
$converted['mediaFolderId'] = $albumMapping['entityUuid'];
$this->mappingIds[] = $albumMapping['id'];
}
unset(
$data['id'],
$data['albumID'],
// Legacy data which don't need a mapping or there is no equivalent field
$data['path'],
$data['type'],
$data['extension'],
$data['file_size'],
$data['width'],
$data['height'],
$data['userID'],
$data['created']
);
$returnData = $data;
if (empty($returnData)) {
$returnData = null;
}
$this->updateMainMapping($migrationContext, $context);
// The MediaWriter will write this Shopware 6 media object
return new ConvertStruct($converted, $returnData, $this->mainMapping['id']);
}
}
swag_migration_media_files are processed by the right processor service. This service is different for documents and normal media, but it still is gateway dependent
=== DIFFERENT APPROACH (Shyim suggestion) ===
Take a look at this (taken from Shopwaredowntown's Github repository):
public function upload(UploadedFile $file, string $folder, string $type, Context $context): string
{
$this->checkValidFile($file);
$this->validator->validate($file, $type);
$mediaFile = new MediaFile($file->getPathname(), $file->getMimeType(), $file->getClientOriginalExtension(), $file->getSize());
$mediaId = $this->mediaService->createMediaInFolder($folder, $context, false);
try {
$this->fileSaver->persistFileToMedia(
$mediaFile,
pathinfo($file->getFilename(), PATHINFO_FILENAME),
$mediaId,
$context
);
} catch (MediaNotFoundException $e) {
throw new UploadException($e->getMessage());
}
return $mediaId;
}
src/Portal/Hacks/StorefrontMediaUploader.php:49
public function upload(UploadedFile $file, string $folder, string $type, Context $context): string

Related

error in codeigniter 4 You must set the database table to be used with your query

i am working codeigniter 4. i am trying to fetch data from database by creating method in Model but am getting error
You must set the database table to be used with your query
even I have mentioned the table name in query builder. i dun know why this is happening ? following is my code
public function Login($values)
{
$db = \Config\Database::connect();
$result= $db->table('tbl_adminuser')
->where(['username',$values['username']])
->where(['password',$values['password']])
->get()
->getResult();
print_r($result);
}
There are two types of getting data from the database.
Using the Model file
Load db in the controller file
Check both the below types to get data from the database.
1. Using the Model file
First, create a User model and mention the name of the database table.
Here is the sample of model file:
<?php
namespace App\Models;
use CodeIgniter\Model;
class UserMasterModel extends Model
{
protected $table = 'user_master';
}
Then create a controller file and load the model using use App\Models\UserMasterModel;.
You should following the below sample of the controller file there is mention load the model and get the data using a model file.
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
use App\Models\UserMasterModel;
public function __construct()
{
$this->db = \Config\Database::connect();
}
public function Login($values){
$UserMasterModel = new UserMasterModel();
$result = $UserMasterModel->where('username',$values['username'])
->where('password',$values['password'])
->findAll();
print_r($result);
}
2. Load db in controller file
When using \Config\Database::connect() the controller file looks like below:
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
public function __construct()
{
$this->db = \Config\Database::connect();
$this->user_master = $this->db->table('user_master');
}
public function Login($values){
$this->user_master->select('*');
$this->user_master->where('username',$values['username']);
$this->user_master->where('password',$values['password']);
$result = $this->user_master->get()->getResult();
print_r($result);
}
create model for users
<?php namespace Myth\Auth\Models;
use CodeIgniter\Model;
use Myth\Auth\Authorization\GroupModel;
use Myth\Auth\Entities\User;
class UserModel extends Model
{
protected $table = 'users';
protected $primaryKey = 'id';
protected $returnType = User::class;
protected $useSoftDeletes = false;
protected $allowedFields = [
'email', 'username', 'password_hash', 'reset_hash', 'reset_at', 'reset_expires', 'activate_hash',
'status', 'status_message', 'active', 'force_pass_reset', 'permissions',
'first_name',
'last_name',
'image',
'address',
'phone',
'email',
'gender',
'country',
'city',
'created_at',
'updated_at',
'deleted_at',
];
protected $useTimestamps = false;
protected $validationRules = [
'email' => 'if_exist|required|valid_email|is_unique[users.email,id,{id}]',
'phone' => 'if_exist|required|is_unique[users.phone,id,{id}]',
'username' => 'if_exist|required|alpha_numeric_punct|min_length[3]|max_length[30]|is_unique[users.username,id,{id}]',
'password_hash' => 'if_exist|required',
];
protected $validationMessages = [];
protected $skipValidation = false;
protected $afterInsert = ['addToGroup'];
/**
* The id of a group to assign.
* Set internally by withGroup.
*
* #var int|null
*/
protected $assignGroup;
/**
* Logs a password reset attempt for posterity sake.
*
* #param string $email
* #param string|null $token
* #param string|null $ipAddress
* #param string|null $userAgent
*/
public function logResetAttempt(string $email, string $token = null, string $ipAddress = null, string $userAgent = null)
{
$this->db->table('auth_reset_attempts')->insert([
'email' => $email,
'ip_address' => $ipAddress,
'user_agent' => $userAgent,
'token' => $token,
'created_at' => date('Y-m-d H:i:s')
]);
}
/**
* Logs an activation attempt for posterity sake.
*
* #param string|null $token
* #param string|null $ipAddress
* #param string|null $userAgent
*/
public function logActivationAttempt(string $token = null, string $ipAddress = null, string $userAgent = null)
{
$this->db->table('auth_activation_attempts')->insert([
'ip_address' => $ipAddress,
'user_agent' => $userAgent,
'token' => $token,
'created_at' => date('Y-m-d H:i:s')
]);
}
/**
* Sets the group to assign any users created.
*
* #param string $groupName
*
* #return $this
*/
public function withGroup(string $groupName)
{
$group = $this->db->table('auth_groups')->where('name', $groupName)->get()->getFirstRow();
$this->assignGroup = $group->id;
return $this;
}
/**
* Clears the group to assign to newly created users.
*
* #return $this
*/
public function clearGroup()
{
$this->assignGroup = null;
return $this;
}
/**
* If a default role is assigned in Config\Auth, will
* add this user to that group. Will do nothing
* if the group cannot be found.
*
* #param mixed $data
*
* #return mixed
*/
protected function addToGroup($data)
{
if (is_numeric($this->assignGroup)) {
$groupModel = model(GroupModel::class);
$groupModel->addUserToGroup($data['id'], $this->assignGroup);
}
return $data;
}
}
too use model
$model = new usermodel();
$model->asObject()->where('active', '1')->findAll();

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.

Plug-in for Smart Search on Joomla: no results

I'm writing a plug-in for my component. For this component I have table "#__radiocatalog_item" with columns id, name, description, and I need to lookup at column name. For this, I wrote this plugin:
<?php
defined('JPATH_BASE') or die;
require_once JPATH_ADMINISTRATOR.'/components/com_finder/helpers/indexer/adapter.php';
class PlgFinderRadioitem extends FinderIndexerAdapter
{
protected $context = 'Radioitem';
protected $extension = 'com_radiocatalog';
protected $layout = 'item';
protected $type_title = 'item';
protected $table = '#__radiocatalog_item';
protected $state_field = 'parent';
protected $autoloadLanguage = true;
protected function setup()
{
return true;
}
public function onFinderDelete($context, $table)
{
if ($context == 'com_radiocatalog.item')
{
$id = $table->id;
}
elseif ($context == 'com_finder.index')
{
$id = $table->id;
}
else
{
return true;
}
return $this->remove($id);
}
public function onFinderChangeState($context, $pks, $value)
{
if ($context == 'com_radiocatalog.item')
{
$this->itemStateChange($pks, $value);
}
if ($context == 'com_plugins.plugin' && $value === 0)
{
$this->pluginDisable($pks);
}
}
protected function index(FinderIndexerResult $item, $format = 'html')
{
if (JComponentHelper::isEnabled($this->extension) == false)
{
return;
}
$item->url = $this->getURL($item->id, 'com_radiocatalog&layout=item', $this->layout);
$item->route = 'index.php?option=com_radiocatalog&view=item&layout=item&id='.$item->id;
$item->addTaxonomy('Type', 'Radioitems');
$item->addTaxonomy('Language', $item->language);
$this->indexer->index($item);
}
protected function getListQuery($sql = null)
{
$db = JFactory::getDbo();
$sql = $sql instanceof JDatabaseQuery ? $sql : $db->getQuery(true);
$sql->select('a.id as id, a.name as title, a.description as description');
$sql->from('#__radiocatalog_item AS a');
return $sql;
}
protected function getStateQuery()
{
$sql = $this->db->getQuery(true);
$sql->select($this->db->quoteName('a.id'));
$sql->select($this->db->quoteName('a.name').' as title');
$sql->from($this->db->quoteName('#__radiocatalog_item') . ' AS a');
return $sql;
}
}
?>
After full indexing, search on the site does not work.
I was struggling with the same problem. So I enabled Joomla debugging {Global Configuration / System / Debug System = true} and tried to search for a term "myterm" using public site SmartSearch module. Then I checked the performed SQL queries. First, the term was found:
SELECT t.term, t.term_id
FROM j_finder_terms AS t
WHERE t.term = 'myterm'
AND t.phrase = 0
with ID=653 (used later):
SELECT l.link_id,m.weight AS ordering
FROM `j_finder_links` AS l
INNER JOIN `j_finder_links_terms2` AS m
ON m.link_id = l.link_id
WHERE l.access IN (1,1)
AND l.state = 1
AND (l.publish_start_date = '0000-00-00 00:00:00' OR l.publish_end_date <= '2014-01-04 17:34:00')
AND (l.publish_end_date = '0000-00-00 00:00:00' OR l.publish_end_date >= '2014-01-04 17:34:00')
AND m.term_id IN (653)
But this query didn't return any result, because j_finder_links.access and j_finder_links.state values were set to 0 instead of 1.
So my suggest you to check the queries and if you have the same problem, try to change your query from getStateQuery() method or select "1 AS access, 1 AS state" in the getListQuery() query and leave the $state_field variable unset.
I'm sorry for a vague explanation, I don't know much about how the SmartSearch work, I'm just trying to make it work somehow with my component.

Acceptable to save results from Spotify search API?

Reading the TOS for the Spotify web API, developers are not allowed to aggregate data from the API in creation of databases. I don't know if what I'm trying to accomplish counts as "aggregation."
I have a website that allows users to make suggestions for songs to be played at a wedding. I let them put in song name, artist, and album names so that the DJ can readily find the music. All this is user supplied. The songs are then approved by the bride/groom and voted on by other guests to generate a playlist for the DJ will know what music will be popular at the event.
What I want to provide is a way for the user to use that information and have the ability to search through the top few search results on Spotify, select the right track, and associate the Spotify track with their suggestion. This let's other guests hear the song they are suggesting if they're not familiar with it and allows the administrators to allow or disallow the song depending on the tastes of the bride/groom.
In order to avoid API calls exceeding the rate limit, I'd like to be able to store the Spotify URI returned from the search results with the user supplied song information so that I can generate a play button on the site for the songs suggested.
Does this count as aggregation, or would this be allowed under the current TOS for the web search API?
What you're doing sounds just fine.
The TOS section you're asking about is to prevent people making automated tools that scrape the Spotify catalogue without user interaction. If you're writing a "normal" application and caching data from the Spotify APIs as a result of a user actually doing something like searching, browsing, etc etc you have no problems.
Source: I work at Spotify.
I use this one:
<?php
namespace App\Services;
use DB;
use Exception;
use App\Genre;
use App\Album;
use App\Artist;
use Illuminate\Support\Str;
class ArtistSaver {
/**
* Save artist to database and return it.
*
* #param array $data
* #return Artist
*/
public function save($data)
{
$artist = Artist::whereName($data['mainInfo']['name'])->first();
if ( ! $artist) {
$artist = Artist::create($data['mainInfo']);
} else {
$artist->fill($data['mainInfo'])->save();
}
$this->saveAlbums($data, $artist);
if (isset($data['albums'])) {
$this->saveTracks($data['albums'], $artist);
}
if (isset($data['similar'])) {
$this->saveSimilar($data['similar'], $artist);
}
if (isset($data['genres']) && ! empty($data['genres'])) {
$this->saveGenres($data['genres'], $artist);
}
return $artist;
}
/**
* Save and attach artist genres.
*
* #param array $genres
* #param Artist $artist
*/
public function saveGenres($genres, $artist) {
$existing = Genre::whereIn('name', $genres)->get();
$ids = [];
foreach($genres as $genre) {
$dbGenre = $existing->filter(function($item) use($genre) { return $item->name === $genre; })->first();
//genre doesn't exist in db yet, so we need to insert it
if ( ! $dbGenre) {
try {
$dbGenre = Genre::create(['name' => $genre]);
} catch(Exception $e) {
continue;
}
}
$ids[] = $dbGenre->id;
}
//attach genres to artist
$artist->genres()->sync($ids, false);
}
/**
* Save artists similar artists to database.
*
* #param $similar
* #param $artist
* #return void
*/
public function saveSimilar($similar, $artist)
{
$names = array_map(function($item) { return $item['name']; }, $similar);
//insert similar artists that don't exist in db yet
$this->saveOrUpdate($similar, array_flatten($similar), 'artists');
//get ids in database for artist we just inserted
$ids = Artist::whereIn('name', $names)->lists('id');
//attach ids to given artist
$artist->similar()->sync($ids);
}
/**
* Save artist albums to database.
*
* #param array $data
* #param Artist|null $artist
* $param int|null
* #return void
*/
public function saveAlbums($data, $artist = null, $albumId = null)
{
if (isset($data['albums']) && count($data['albums'])) {
$b = $this->prepareAlbumBindings($data['albums'], $artist, $albumId);
$this->saveOrUpdate($b['values'], $b['bindings'], 'albums');
}
}
/**
* Save albums tracks to database.
*
* #param array $albums
* #param Artist|null $artist
* #param Album|null $trackAlbum
* #return void
*/
public function saveTracks($albums, $artist, $tracksAlbum = null)
{
if ( ! $albums || ! count($albums)) return;
$tracks = [];
foreach($albums as $album) {
if ( ! isset($album['tracks']) || empty($album['tracks'])) continue;
if ($tracksAlbum) {
$id = $tracksAlbum['id'];
} else {
$id = $this->getIdFromAlbumsArray($album['name'], $artist['albums']);
}
foreach($album['tracks'] as $track) {
$track['album_id'] = $id;
$tracks[] = $track;
}
}
if ( ! empty($tracks)) {
$this->saveOrUpdate($tracks, array_flatten($tracks), 'tracks');
}
}
private function getIdFromAlbumsArray($name, $albums) {
$id = false;
foreach($albums as $album) {
if ($name === $album['name']) {
$id = $album['id']; break;
}
}
if ( ! $id) {
foreach($albums as $album) {
if (Str::slug($name) == Str::slug($album['name'])) {
$id = $album['id']; break;
}
}
}
return $id;
}
/**
* Unset tracks key from album arrays and flatten them into single array.
*
* #param array $albums
* #param Artist|null $artist
* #param int|null $albumId
* #return array
*/
private function prepareAlbumBindings($albums, $artist = null, $albumId = null)
{
$flat = [];
foreach($albums as $k => $album) {
if (isset($albums[$k]['tracks'])) unset($albums[$k]['tracks']);
if ( ! isset($albums[$k]['artist_id']) || ! $albums[$k]['artist_id']) {
$albums[$k]['artist_id'] = $artist ? $artist->id : 0;
}
//can't insert null into auto incrementing id because
//mysql will increment the id instead of keeping the old one
if ($albumId) {
$albums[$k]['id'] = $albumId;
}
foreach($albums[$k] as $name => $data) {
if ($name !== 'tracks') {
$flat[] = $data;
}
}
}
return ['values' => $albums, 'bindings' => $flat];
}
/**
* Compiles insert on duplicate update query for multiple inserts.
*
* #param array $values
* #param array $bindings
* #param string $table
*
* #return void
*/
public function saveOrUpdate(array $values, $bindings, $table)
{
if (empty($values)) return;
$first = head($values);
//count how many inserts we need to make
$amount = count($values);
//count in how many columns we're inserting
$columns = array_fill(0, count($first), '?');
$columns = '(' . implode(', ', $columns) . ') ';
//make placeholders for the amount of inserts we're doing
$placeholders = array_fill(0, $amount, $columns);
$placeholders = implode(',', $placeholders);
$updates = [];
//construct update part of the query if we're trying to insert duplicates
foreach ($first as $column => $value) {
$updates[] = "$column = COALESCE(values($column), $column)";
}
$prefixed = DB::getTablePrefix() ? DB::getTablePrefix().$table : $table;
$query = "INSERT INTO {$prefixed} " . '(' . implode(',' , array_keys($first)) . ')' . ' VALUES ' . $placeholders .
'ON DUPLICATE KEY UPDATE ' . implode(', ', $updates);
DB::statement($query, $bindings);
}
}

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