Je voudrais prolonger ma composante d'apprentissage que j'écris. Pour cette raison, je crée un petit système de billetterie qui récupère les messages provenant d'IMAP et enregistre les pièces jointes si elles ont un type de fichier spécifique (uniquement les formats png, jpg, ...).
Lors de la sauvegarde des pièces jointes, le nom du fichier est haché puis enregistré.
Bien sûr, j'aimerais également étendre ma vue de ticket: dans ma vue de ticket, tous les messages sont affectés à ce ticket (modèles utilisés: ticket, messages).
Maintenant, j'aimerais afficher des pièces jointes téléchargeables pour chaque message de ce ticket contenant une ou plusieurs pièces jointes. Cliquez sur "télécharger la pièce jointe" pour lancer le processus de téléchargement.
J'ai essayé d'étendre messages-modèle avec jointure gauche mais cela ne fonctionne pas très bien.
Ceci est mon code (modèle):
<?php
defined('_JEXEC') or die;
class BestiaModelMessages extends JModelList
{
/**
* __construct function.
*
* @access public
* @param array $config (default: array())
* @return void
*/
public function __construct($config = array())
{
if (empty($config['filter_fields']))
{
$config['filter_fields'] = array(
'id', 'a.id',
'ticketid' , 'a.ticketid',
'from', 'a.from',
'to', 'a.to',
'date', 'a.date',
'content', 'a.content',
'title', 'a.title'
);
}
parent::__construct($config);
}
/**
* populateState function.
*
* @access protected
* @param mixed $ordering (default: null)
* @param mixed $direction (default: null)
* @return void
*/
protected function populateState($ordering = null, $direction = null)
{
$published = $this->getUserStateFromRequest($this->context.'.filter.state', 'filter_state', '', 'string');
$this->setState('filter.state', $published);
parent::populateState('a.id', 'desc');
}
/**
* getListQuery function.
*
* @access protected
* @return void
*/
protected function getListQuery()
{
$db = $this->getDbo();
$query = $db->getQuery(true);
$query->select(
$this->getState(
'list.select',
'a.id, a.ticketid, a.from, a.to, a.date, a.title, a.content'));
$query->from($db->quoteName('#__bestia_tickets_messages').' AS a');
// Filter by search in title
$search = $this->getState('filter.search');
if (!empty($search))
{
if (stripos($search, 'id:') === 0)
{
$query->where('a.id = ' . (int) substr($search, 3));
}
else
{
$search = $db->quote('%' . $db->escape($search, true) . '%');
$query->where('(a.content LIKE ' . $search.' OR a.id LIKE ' . $search . 'OR a.from LIKE ' . $search . 'OR a.to LIKE ' . $search . 'OR a.date LIKE ' . $search .' OR a.title LIKE ' . $search . ')');
}
}
$query->select('GROUP_CONCAT(cast(concat(tma.filename,\', \',tma.filepath)AS char) SEPARATOR \', \') AS attachments')
->join('LEFT', '#__bestia_tickets_messages_attachments as tma ON a.id = tma.messageid');
$model =& $this->getInstance('Ticket', 'BestiaModel');
$item = $model->getItem();
$ticketid = $item->ticketid;
$query->where('a.ticketid = '.$db->quote($ticketid));
$query->order('a.date DESC');
$query->group('a.id');
// var_dump((string)$query);
return $query;
} // ./protected function getListQuery
} // ./class BestiaModelMessages
Le résultat obtenu ressemble à ceci:
object(stdClass)#375 (8) {
["id"]=> string(3) "725"
["ticketid"]=> string(11) "123456789"
["from"]=> string(16) "[email protected]"
["to"]=> string(27) "[email protected]"
["date"]=> string(19) "2015-08-03 14:47:27"
["title"]=> string(17) "Ticket mit Anhang"
["content"]=> string(775) "Ticket mit Anhang…"
["attachments"]=> string(217) "Test.png, asdaadasasgsdafasdasdasgasfasf, Bildschirmfoto 2015-07-14 um 17.10.43.png, 52d0833774bc94698e4101c021deebecf5724a4c65d1efc477948180c072c61778b6c0174040cdef6d94d8669251f0a6646c5caf6e1966b9796625979973b2a4.png"
}
Maintenant, j'aimerais associer le nom de fichier et le chemin du fichier pour l'utiliser comme ceci:
foreach($message->attachments as $attachment)
{
$attachment->filename;
$attachment->filepath;
}
Comment pourrais-je résoudre ce problème?
Je ne vois pas où, dans votre code, vous exportez l'objet que vous avez mentionné. Normalement, la requête générée par getListQuery devrait aboutir à un tableau. Je suppose que cet objet est un exemple de celui qui pourrait être contenu dans le tableau?
Si oui, alors je remplacerais votre groupe concat comme suit
$query->select('GROUP_CONCAT(cast(concat(tma.filename,\'|\',tma.filepath)AS char) SEPARATOR \',\') AS attachments')
->join('LEFT', '#__bestia_tickets_messages_attachments as tma ON a.id = tma.messageid');
cela vous donnerait quelque chose comme
["attachments"]=> string(217) "Test.png|asdaadasasgsdafasdasdasgasfasf, Bildschirmfoto 2015-07-14 um 17.10.43.png|52d0833774bc94698e4101c021deebecf5724a4c65d1efc477948180c072c61778b6c0174040cdef6d94d8669251f0a6646c5caf6e1966b9796625979973b2a4.png"
Alors dites que vous avez ailleurs:
$query = $this->getListQuery();
$rows = $db->setQuery($query)->loadObjectList();
vous pouvez maintenant parcourir ces lignes comme suit:
foreach ($rows as &$row)
{
$attachments = explode(',' $row->attachments);
$res = array();
foreach ($attachments as $attachment)
{
$attachment = explode('|', $attachment);
$obj = new stdClass;
$obj->filename = $attachment[0];
$obj->filepath = $attachment[1];
$res[] = $obj;
}
$row->attachments = $res;
}
Pour plus de sécurité, je remplacerais également les deux séparateurs de concat '|' et ',' avec une chaîne dont vous êtes sûr qu'il ne sera pas présent dans le nom de fichier/chemin. E.g in fabrik nous utilisons quelque chose comme '//..//'
En passant, je ne suis pas sûr de savoir pourquoi vous chargez le modèle de tickets avec getListQuery aussi bien qu'il semble ne pas être utilisé?
Une autre méthode consisterait à vider le concat de groupe et à le faire en deux requêtes distinctes: Disons que vous avez la liste des tickets sous forme de $ tickets (un tableau d'objets).
use Joomla\Utilities\ArrayHelper;
$ids = ArrayHelper::getColumn($tickets, 'id');
$ids = ArrayHelper::toInteger($ids);
if (!empty($ids))
{
$query = $db->getQuery(true);
$query->select('filename, filepath')
->from('#__bestia_tickets_messages_attachments')
->where('ticket_id IN (' . implode(',', $ids) . ')');
$db->setQuery($query);
$attachments = $db->loadObjectList('ticket_id');
}
foreach ($tickets as &$ticket)
{
$ticket->attachments = ArrayHelper::getValue($attachments, $ticket->id, array());
}
Cela peut être légèrement plus lent, mais est plus lisible.
GROUP_CONCAT est un tueur de performance. Sans oublier que dans votre requête, il est possible que les lignes soient jointes avant d'être filtrées. Alors va gaspiller beaucoup de mémoire et de temps CPU sur de plus grands ensembles de données.
Je retirerais ceci:
$query->select('GROUP_CONCAT(cast(concat(tma.filename,\', \',tma.filepath)AS char) SEPARATOR \', \') AS attachments')
->join('LEFT', '#__bestia_tickets_messages_attachments as tma ON a.id = tma.messageid');
Créer une fonction pour lier les pièces jointes:
protected function bindAttachments(Array &$items) {
// Get messages ids
$ids = array();
foreach( $items AS $item ) {
$ids[] = (int)$item->id;
}
// Get attachments
$query = $this->_db->getQuery(true);
$query->select('*');
$query->from('#__bestia_tickets_messages_attachments');
$query->where('messageid IN('.implode(',', $ids).')');
$query->order('messageid ASC');
$this->_db->setQuery($query);
$attachments = $this->_db->loadObjectList();
// Create attachments map by ID
$attachments_map = array();
foreach( $attachments AS $attachment ) {
if( !isset($attachments_map[$attachment->messageid]) ) {
$attachments_map[$attachment->messageid] = array();
}
$attachments_map[$attachment->messageid][] = $attachment;
}
// Attach attachments to items array
foreach( $items AS &$item ) {
if( isset($attachments_map[$item->id]) ) {
$item->attachments = $attachments_map[$item->id];
} else {
$item->attachments = array();
}
}
}
Et maintenant, remplacez votre fonction getItems
comme ceci:
public function getItems() {
$items = parent::getItems();
$this->bindAttachments($items);
return $items;
}