I have created a module to add an extra text for each product and that can be edited from the backoffice and its text saved in the database so that when it is updated it remains saved, etc.
The question is that said form does not appear in any of the installed Hooks, any suggestions? Thanks!
My php code:
<?php
if (!defined('_PS_VERSION_')) {
exit;
}
class MyModuleText extends Module
{
protected $config_form = false;
public function __construct()
{
$this->name = 'mymoduletext';
$this->tab = 'administration';
$this->version = '1.0.0';
$this->author = 'Jordi Comes';
$this->need_instance = 0;
/**
* Set $this->bootstrap to true if your module is compliant with bootstrap (PrestaShop 1.6)
*/
$this->bootstrap = true;
parent::__construct();
$this->displayName = $this->l('My_Module_Text');
$this->description = $this->l('Crea un texto editable desde el backoffice para cualquier producto de forma independiente');
$this->ps_versions_compliancy = array('min' => '1.6', 'max' => _PS_VERSION_);
}
/**
* Don't forget to create update methods if needed:
* http://doc.prestashop.com/display/PS16/Enabling+the+Auto-Update
*/
public function install()
{
include(dirname(__FILE__).'/sql/install.php');
// Register hooks
$this->registerHook('displayProductTabContent');
$this->registerHook('actionProductUpdate');
$this->registerHook('displayProductAdditionalInfo');
$this->registerHook('header');
return parent::install()
&& Configuration::updateValue('MYMODULETEXT_MODULE_NAME', 'producttextmodule');
}
public function uninstall()
{
include(dirname(__FILE__).'/sql/uninstall.php');
// Unregister hooks
$this->unregisterHook('actionProductUpdate');
$this->unregisterHook('displayProductAdditionalInfo');
$this->unregisterHook('header');
return parent::uninstall()
&& Configuration::deleteByName('MYMODULETEXT_MODULE_NAME');
}
public function hookDisplayProductTabContent($params)
{
$productText = $this->getProductText((int)$params['product']['id_product']);
$this->smarty->assign(array(
'productText' => $productText,
));
return $this->context->smarty->fetch($this->local_path.'../producttabcontent.tpl');
}
public function hookActionProductUpdate($params)
{
// Save custom text for product
if (Tools::isSubmit('submitProductTextModule')) {
$id_product = (int)$params['id_product'];
$text = pSQL(Tools::getValue('mymoduletext'));
if (isset($text)) {
$sql = 'REPLACE INTO `'._DB_PREFIX_.'mymoduletext` (`id_product`, `text`)
VALUES ('.$id_product.', "'.$text.'")';
Db::getInstance()->execute($sql);
}
}
}
public function hookDisplayProductAdditionalInfo($params)
{
// Get custom text for product
$id_product = (int)$params['product']['id_product'];
$sql = 'SELECT `text` FROM `'._DB_PREFIX_.'mymoduletext` WHERE `id_product` = '.(int)$id_product;
$text = Db::getInstance()->getValue($sql);
if ($text === false) {
return '';
}
// Display custom text in product information block
$this->context->smarty->assign(array(
'mymoduletext' => $text,
));
return $this->context->smarty->fetch($this->local_path.'../producttextmodule.tpl');
}
public function hookHeader()
{
// Include CSS and JS files
$this->context->controller->addCSS($this->_path.'views/css/producttextmodule.css');
$this->context->controller->addJS($this->_path.'views/js/producttextmodule.js');
}
public function getContent()
{
// Handle form submission
$output = '';
if (Tools::isSubmit('submitProductTextModule')) {
$my_module_name = strval(Tools::getValue('MYMODULETEXT_MODULE_NAME'));
if (!$my_module_name
|| empty($my_module_name)
|| !Validate::isGenericName($my_module_name)
) {
$output .= $this->displayError($this->l('Invalid Configuration value'));
} else {
Configuration::updateValue('MYMODULETEXT_MODULE_NAME', $my_module_name);
$output .= $this->displayConfirmation($this->l('Settings updated'));
}
}
// Display form
return $output.$this->displayForm();
}
public function displayForm()
{
// Get default language
$default_lang = (int)Configuration::get('PS_LANG_DEFAULT');
// Init Fields form array
$fields_form[0]['form'] = array(
'legend' => array(
'title' => $this->l('Settings'),
),
'input' => array(
array(
'type' => 'text',
'label' => $this->l('Configuration value'),
'name' => 'MYMODULETEXT_MODULE_NAME_MODULE_NAME',
'size' => 20,
'required' => true
)
),
'submit' => array(
'title' => $this->l('Save'),
'class' => 'btn btn-default pull-right'
)
);
$helper = new HelperForm();
// Module, token and currentIndex
$helper->module = $this;
$helper->name_controller = $this->name;
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name;
// Language
$languages = Language::getLanguages(false);
$helper->default_form_language = $default_lang;
$helper->allow_employee_form_lang = $default_lang;
$helper->languages = $languages;
// Title and toolbar
$helper->title = $this->displayName;
$helper->show_toolbar = true;
$helper->toolbar_scroll = true;
$helper->submit_action = 'submitProductTextModule';
$helper->toolbar_btn = array(
'save' =>
array(
'desc' => $this->l('Save'),
'href' => AdminController::$currentIndex.'&configure='.$this->name.'&save'.$this->name.
'&token='.Tools::getAdminTokenLite('AdminModules'),
),
'back' => array(
'href' => AdminController::$currentIndex.'&token='.Tools::getAdminTokenLite('AdminModules'),
'desc' => $this->l('Back to list')
)
);
// Load current value
$helper->fields_value['MYMODULETEXT_MODULE_NAME'] = Configuration::get('MYMODULETEXT_MODULE_NAME');
return $helper->generateForm($fields_form);
}
}
?>
<div class="product-text-module">
<form action="{$link->getAdminLink('AdminProducts')}&id_product={$product.id_product}&updateproduct" method="post">
<textarea name="mymoduletext" id="mymoduletext">{$productText}</textarea>
<button type="submit" name="submitProductTextModule" class="btn btn-default">{l s='Save'}</button>
</form>
</div>
{literal}
<style>
.product-text-module {
padding: 20px;
background-color: #f5f5f5;
border-radius: 5px;
margin-bottom: 20px;
}
</style>
{/literal}
<div class="product-text-module">
<p>{$mymoduletext}</p>
</div>
{literal}
<style>
.product-text-module {
padding: 20px;
background-color: #f5f5f5;
border-radius: 5px;
margin-bottom: 20px;
}
</style>
{/literal}
All sugestions accepted
If your module installed correctly in the hook,
Try this:
$this->fetch('module:producttextmodule/producttextmodule.tpl');
Related
so I set up a ticketing system called osticket for our users, unfortunately they do not have an automated workflow feature. Basically what I would like for the ticketing system to do is create an automated child task from a parent ticket. So if someone puts in a software request, then a parent request is created for support team and an approval child task is automatically created and assigned to management. I have no idea where to begin since I do not have a thorough programming background. If someone can point me to the direction of where I can find information about something similar or just provide a guide, that would be great!
Below is the current configured task.php file
<?php
/*********************************************************************
class.task.php
**********************************************************************/
include_once INCLUDE_DIR.'class.role.php';
class TaskModel extends VerySimpleModel {
static $meta = array(
'table' => TASK_TABLE,
'pk' => array('id'),
'joins' => array(
'dept' => array(
'constraint' => array('dept_id' => 'Dept.id'),
),
'lock' => array(
'constraint' => array('lock_id' => 'Lock.lock_id'),
'null' => true,
),
'staff' => array(
'constraint' => array('staff_id' => 'Staff.staff_id'),
'null' => true,
),
'team' => array(
'constraint' => array('team_id' => 'Team.team_id'),
'null' => true,
),
'thread' => array(
'constraint' => array(
'id' => 'TaskThread.object_id',
"'A'" => 'TaskThread.object_type',
),
'list' => false,
'null' => false,
),
'cdata' => array(
'constraint' => array('id' => 'TaskCData.task_id'),
"class.task.php" 1826 lines, 57620 characters
If I can provide any additional information, then please let me know.
Edit
Managed to find this php file, towards the bottom it mentions something about "Create Task", I'm guessing this is where I'll have the ability to set up an automated feature?
<?php
/*********************************************************************
class.thread_actions.php
Actions for thread entries. This serves as a simple repository for
drop-down actions which can be triggered on the ticket-view page for an
object's thread.
Jared Hancock <jared#osticket.com>
Peter Rotich <peter#osticket.com>
Copyright (c) 2006-2014 osTicket
http://www.osticket.com
Released under the GNU General Public License WITHOUT ANY WARRANTY.
See LICENSE.TXT for details.
vim: expandtab sw=4 ts=4 sts=4:
**********************************************************************/
include_once(INCLUDE_DIR.'class.thread.php');
class TEA_ShowEmailRecipients extends ThreadEntryAction {
static $id = 'emailrecipients';
static $name = /* trans */ 'View Email Recipients';
static $icon = 'group';
function isVisible() {
global $thisstaff;
if ($this->entry->getEmailHeader())
return ($thisstaff && $this->entry->getEmailHeader());
elseif ($this->entry->recipients)
return $this->entry->recipients;
}
function getJsStub() {
return sprintf("$.dialog('%s');",
$this->getAjaxUrl()
);
}
function trigger() {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET' && $this->entry->recipients:
return $this->getRecipients();
case 'GET':
return $this->trigger__get();
}
}
private function trigger__get() {
$hdr = Mail_parse::splitHeaders(
$this->entry->getEmailHeader(), true);
$recipients = array();
foreach (array('To', 'TO', 'Cc', 'CC') as $k) {
if (isset($hdr[$k]) && $hdr[$k] &&
($addresses=Mail_Parse::parseAddressList($hdr[$k]))) {
foreach ($addresses as $addr) {
$email = sprintf('%s#%s', $addr->mailbox, $addr->host);
$name = $addr->personal ?: '';
$recipients[$k][] = sprintf('%s<%s>',
(($name && strcasecmp($name, $email))? "$name ": ''),
$email);
}
}
}
include STAFFINC_DIR . 'templates/thread-email-recipients.tmpl.php';
}
private function getRecipients() {
$recipients = json_decode($this->entry->recipients, true);
include STAFFINC_DIR . 'templates/thread-email-recipients.tmpl.php';
}
}
ThreadEntry::registerAction(/* trans */ 'E-Mail', 'TEA_ShowEmailRecipients');
class TEA_ShowEmailHeaders extends ThreadEntryAction {
static $id = 'view_headers';
static $name = /* trans */ 'View Email Headers';
static $icon = 'envelope';
function isVisible() {
global $thisstaff;
if (!$this->entry->getEmailHeader())
return false;
return $thisstaff && $thisstaff->isAdmin();
}
function getJsStub() {
return sprintf("$.dialog('%s');",
$this->getAjaxUrl()
);
}
function trigger() {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
return $this->trigger__get();
}
}
private function trigger__get() {
$headers = $this->entry->getEmailHeader();
include STAFFINC_DIR . 'templates/thread-email-headers.tmpl.php';
}
}
ThreadEntry::registerAction(/* trans */ 'E-Mail', 'TEA_ShowEmailHeaders');
class TEA_EditThreadEntry extends ThreadEntryAction {
static $id = 'edit';
static $name = /* trans */ 'Edit';
static $icon = 'pencil';
function isVisible() {
// Can't edit system posts
return ($this->entry->staff_id || $this->entry->user_id)
&& $this->entry->type != 'R' && $this->isEnabled();
}
function isEnabled() {
global $thisstaff;
$T = $this->entry->getThread()->getObject();
// You can edit your own posts or posts by your department members
// if your a manager, or everyone's if your an admin
return $thisstaff && (
$thisstaff->getId() == $this->entry->staff_id
|| ($T instanceof Ticket
&& $T->getDept()->getManagerId() == $thisstaff->getId()
)
|| ($T instanceof Ticket
&& ($role = $thisstaff->getRole($T->getDeptId(), $T->isAssigned($thisstaff)))
&& $role->hasPerm(ThreadEntry::PERM_EDIT)
)
|| ($T instanceof Task
&& $T->getDept()->getManagerId() == $thisstaff->getId()
)
|| ($T instanceof Task
&& ($role = $thisstaff->getRole($T->getDeptId(), $T->isAssigned($thisstaff)))
&& $role->hasPerm(ThreadEntry::PERM_EDIT)
)
);
}
function getJsStub() {
return sprintf(<<<JS
var url = '%s';
$.dialog(url, [201], function(xhr, resp) {
var json = JSON.parse(resp);
if (!json || !json.thread_id)
return;
$('#thread-entry-'+json.thread_id)
.attr('id', 'thread-entry-' + json.new_id)
.html(json.entry)
.find('.thread-body')
.delay(500)
.effect('highlight');
}, {size:'large'});
JS
, $this->getAjaxUrl());
}
function trigger() {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
return $this->trigger__get();
case 'POST':
return $this->trigger__post();
}
}
protected function trigger__get() {
global $cfg, $thisstaff;
$poster = $this->entry->getStaff();
include STAFFINC_DIR . 'templates/thread-entry-edit.tmpl.php';
}
function updateEntry($guard=false) {
global $thisstaff;
$old = $this->entry;
$new = ThreadEntryBody::fromFormattedText($_POST['body'], $old->format);
if ($new->getClean() == $old->getBody())
// No update was performed
return $old;
$entry = ThreadEntry::create(array(
// Copy most information from the old entry
'poster' => $old->poster,
'userId' => $old->user_id,
'staffId' => $old->staff_id,
'type' => $old->type,
'threadId' => $old->thread_id,
'recipients' => $old->recipients,
// Connect the new entry to be a child of the previous
'pid' => $old->id,
// Add in new stuff
'title' => Format::htmlchars($_POST['title']),
'body' => $new,
'ip_address' => $_SERVER['REMOTE_ADDR'],
));
if (!$entry)
return false;
// Move the attachments to the new entry
$old->attachments->filter(array(
'inline' => false,
))->update(array(
'object_id' => $entry->id
));
// Note, anything that points to the $old entry as PID should remain
// that way for email header lookups and such to remain consistent
if ($old->flags & ThreadEntry::FLAG_EDITED
// If editing another person's edit, make a new entry
and ($old->editor == $thisstaff->getId() && $old->editor_type == 'S')
and !($old->flags & ThreadEntry::FLAG_GUARDED)
) {
// Replace previous edit --------------------------
$original = $old->getParent();
// Link the new entry to the old id
$entry->pid = $old->pid;
// Drop the previous edit, and base this edit off the original
$old->delete();
$old = $original;
}
// Mark the new entry as edited (but not hidden nor guarded)
$entry->flags = ($old->flags & ~(ThreadEntry::FLAG_HIDDEN | ThreadEntry::FLAG_GUARDED))
| ThreadEntry::FLAG_EDITED;
// Guard against deletes on future edit if requested. This is done
// if an email was triggered by the last edit. In such a case, it
// should not be replaced by a subsequent edit.
if ($guard)
$entry->flags |= ThreadEntry::FLAG_GUARDED;
// Log the editor
$entry->editor = $thisstaff->getId();
$entry->editor_type = 'S';
// Sort in the same place in the thread
$entry->created = $old->created;
$entry->updated = SqlFunction::NOW();
$entry->save(true);
// Hide the old entry from the object thread
$old->flags |= ThreadEntry::FLAG_HIDDEN;
$old->save();
return $entry;
}
protected function trigger__post() {
global $thisstaff;
if (!($entry = $this->updateEntry()))
return $this->trigger__get();
ob_start();
include STAFFINC_DIR . 'templates/thread-entry.tmpl.php';
$content = ob_get_clean();
Http::response('201', JsonDataEncoder::encode(array(
'thread_id' => $this->entry->id, # This is the old id!
'new_id' => $entry->id,
'entry' => $content,
)));
}
}
ThreadEntry::registerAction(/* trans */ 'Manage', 'TEA_EditThreadEntry');
class TEA_OrigThreadEntry extends ThreadEntryAction {
static $id = 'previous';
static $name = /* trans */ 'View History';
static $icon = 'copy';
function isVisible() {
// Can't edit system posts
return $this->entry->flags & ThreadEntry::FLAG_EDITED;
}
function getJsStub() {
return sprintf("$.dialog('%s');",
$this->getAjaxUrl()
);
}
function trigger() {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
return $this->trigger__get();
}
}
private function trigger__get() {
global $thisstaff;
if (!$this->entry->getParent())
Http::response(404, 'No history for this entry');
$entry = $this->entry;
include STAFFINC_DIR . 'templates/thread-entry-view.tmpl.php';
}
}
ThreadEntry::registerAction(/* trans */ 'Manage', 'TEA_OrigThreadEntry');
class TEA_EditAndResendThreadEntry extends TEA_EditThreadEntry {
static $id = 'edit_resend';
static $name = /* trans */ 'Edit and Resend';
static $icon = 'reply-all';
function isVisible() {
// Can only resend replies
return $this->entry->staff_id && $this->entry->type == 'R'
&& $this->isEnabled();
}
protected function trigger__post() {
$resend = #$_POST['commit'] == 'resend';
if (!($entry = $this->updateEntry($resend)))
return $this->trigger__get();
if ($resend)
$this->resend($entry);
ob_start();
include STAFFINC_DIR . 'templates/thread-entry.tmpl.php';
$content = ob_get_clean();
Http::response('201', JsonDataEncoder::encode(array(
'thread_id' => $this->entry->id, # This is the old id!
'new_id' => $entry->id,
'entry' => $content,
)));
}
function resend($response) {
global $cfg, $thisstaff;
if (!($object = $response->getThread()->getObject()))
return false;
$vars = $_POST;
$dept = $object->getDept();
$poster = $response->getStaff();
if ($thisstaff && $vars['signature'] == 'mine')
$signature = $thisstaff->getSignature();
elseif ($poster && $vars['signature'] == 'theirs')
$signature = $poster->getSignature();
elseif ($vars['signature'] == 'dept' && $dept && $dept->isPublic())
$signature = $dept->getSignature();
else
$signature = '';
$variables = array(
'response' => $response,
'signature' => $signature,
'staff' => $response->getStaff(),
'poster' => $response->getStaff());
$options = array('thread' => $response);
// Resend response to collabs
if (($object instanceof Ticket)
&& ($email=$dept->getEmail())
&& ($tpl = $dept->getTemplate())
&& ($msg=$tpl->getReplyMsgTemplate())) {
$recipients = json_decode($response->recipients, true);
$msg = $object->replaceVars($msg->asArray(),
$variables + array('recipient' => $object->getOwner()));
$attachments = $cfg->emailAttachments()
? $response->getAttachments() : array();
$email->send($object->getOwner(), $msg['subj'], $msg['body'],
$attachments, $options, $recipients);
}
// TODO: Add an option to the dialog
if ($object instanceof Task)
$object->notifyCollaborators($response, array('signature' => $signature));
// Log an event that the item was resent
$object->logEvent('resent', array('entry' => $response->id));
$type = array('type' => 'resent');
Signal::send('object.edited', $object, $type);
// Flag the entry as resent
$response->flags |= ThreadEntry::FLAG_RESENT;
$response->save();
}
}
ThreadEntry::registerAction(/* trans */ 'Manage', 'TEA_EditAndResendThreadEntry');
class TEA_ResendThreadEntry extends TEA_EditAndResendThreadEntry {
static $id = 'resend';
static $name = /* trans */ 'Resend';
static $icon = 'reply-all';
function isVisible() {
// Can only resend replies
return $this->entry->staff_id && $this->entry->type == 'R'
&& !parent::isEnabled();
}
function isEnabled() {
return true;
}
protected function trigger__get() {
global $cfg, $thisstaff;
$poster = $this->entry->getStaff();
include STAFFINC_DIR . 'templates/thread-entry-resend.tmpl.php';
}
protected function trigger__post() {
$resend = #$_POST['commit'] == 'resend';
if (#$_POST['commit'] == 'resend')
$this->resend($this->entry);
Http::response('201', 'Okee dokey');
}
}
ThreadEntry::registerAction(/* trans */ 'Manage', 'TEA_ResendThreadEntry');
/* Create a new ticket from thread entry as description */
class TEA_CreateTicket extends ThreadEntryAction {
static $id = 'create_ticket';
static $name = /* trans */ 'Create Ticket';
static $icon = 'plus';
function isVisible() {
global $thisstaff;
return $thisstaff && $thisstaff->hasPerm(Ticket::PERM_CREATE, false);
}
function getJsStub() {
return sprintf(<<<JS
window.location.href = '%s';
JS
, $this->getCreateTicketUrl()
);
}
function trigger() {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
return $this->trigger__get();
}
}
private function trigger__get() {
Http::redirect($this->getCreateTicketUrl());
}
private function getCreateTicketUrl() {
return sprintf('tickets.php?a=open&tid=%d', $this->entry->getId());
}
}
ThreadEntry::registerAction(/* trans */ 'Manage', 'TEA_CreateTicket');
class TEA_CreateTask extends ThreadEntryAction {
static $id = 'create_task';
static $name = /* trans */ 'Create Task';
static $icon = 'plus';
function isVisible() {
global $thisstaff;
return $thisstaff && $thisstaff->hasPerm(Task::PERM_CREATE, false);
}
function getJsStub() {
return sprintf(<<<JS
var url = '%s';
var redirect = $(this).data('redirect');
$.dialog(url, [201], function(xhr, resp) {
if (!!redirect)
$.pjax({url: redirect, container: '#pjax-container'});
else
$.pjax({url: '%s.php?id=%d#tasks', container: '#pjax-container'});
});
JS
, $this->getAjaxUrl(),
$this->entry->getThread()->getObjectType() == 'T' ? 'tickets' : 'tasks',
$this->entry->getThread()->getObjectId()
);
}
function trigger() {
switch ($_SERVER['REQUEST_METHOD']) {
case 'GET':
return $this->trigger__get();
case 'POST':
return $this->trigger__post();
}
}
private function trigger__get() {
$vars = array(
'description' => Format::htmlchars($this->entry->getBody()));
if ($_SESSION[':form-data'])
unset($_SESSION[':form-data']);
$_SESSION[':form-data']['tid'] = $this->entry->getThread()->getObJectId();
$_SESSION[':form-data']['eid'] = $this->entry->getId();
$_SESSION[':form-data']['timestamp'] = $this->entry->getCreateDate();
$_SESSION[':form-data']['type'] = $this->entry->getThread()->object_type;
if (($f= TaskForm::getInstance()->getField('description'))) {
$k = 'attach:'.$f->getId();
unset($_SESSION[':form-data'][$k]);
foreach ($this->entry->getAttachments() as $a)
if (!$a->inline && $a->file) {
$_SESSION[':form-data'][$k][$a->file->getId()] = $a->getFilename();
$_SESSION[':uploadedFiles'][$a->file->getId()] = $a->getFilename();
}
}
if ($this->entry->getThread()->getObjectType() == 'T')
return $this->getTicketsAPI()->addTask($this->getObjectId(), $vars);
else
return $this->getTasksAPI()->add($this->getObjectId(), $vars);
}
private function trigger__post() {
if ($this->entry->getThread()->getObjectType() == 'T')
return $this->getTicketsAPI()->addTask($this->getObjectId());
else
return $this->getTasksAPI()->add($this->getObjectId());
}
}
ThreadEntry::registerAction(/* trans */ 'Manage', 'TEA_CreateTask');
Firstly, I tried all the questions & answers related to this topic. Additionally and I tried related questions and try to solve it but no success. So please read my question thoroughly.
1) i want to create a custom controller on custom module in prestashop without tab.and get browser url Link.
2) how to create a controller url link with tocken on twig file.
i have successfully created module and installed in my PS.
i create a controller [Checkstatus.php]
file path module/mymodule/contollers/admin/Checkstatus.php
<?php
class CheckstatusController extends ModuleAdminController {
public function __construct()
{
$this->page_name = 'checkstatus'; // page_name and body id
echo "sfg";
parent::__construct();
}
public function init()
{
parenrt::init();
}
public function demoAction()
{
return $this->render('#Modules/your-module/templates/admin/demo.html.twig');
}
}
my Custom module
<?php
if (!defined('_PS_VERSION_')) {
exit;
}
class MyModule extends PaymentModule
{
public function __construct()
{
$this->name = 'MyCustomModule';
$this->tab = 'payments XYZ';
$this->version = '1.0';
$this->author = 'XYZ Technologies';
$this->bootstrap = true;
$this->displayName = 'XYZ';
$this->description = 'XYZ.';
$this->confirmUninstall = 'Are you sure you want to uninstall XYZ module?';
$this->ps_versions_compliancy = array('min' => '1.7.0', 'max' => _PS_VERSION_);
$this->allow_countries = array('CH', 'LI', 'AT', 'DE');
$this->allow_currencies = array('CHF', 'EUR');
parent::__construct();
}
/**
* Install this module and register the following Hooks:
*
* #return bool
*/
public function install()
{
if (Shop::isFeatureActive()) {
Shop::setContext(Shop::CONTEXT_ALL);
}
Db::getInstance()->execute('
CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'MyCustomModule` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`customer_id` int(255) NOT NULL,
`MyCustomModule` int(255) DEFAULT NULL,
`lastcheck_date` date,
`add_date` date,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;
');
return parent::install() && $this->registerHook('Statusbtnoncustomerview');
}
/**
* Uninstall this module and remove it from all hooks
*
* #return bool
*/
public function uninstall()
{
return parent::uninstall() && $this->uninstallDb() && $this->unregisterHook('Statusbtnoncustomerview');
}
public function uninstallDb()
{
return Db::getInstance()->execute('DROP TABLE IF EXISTS '._DB_PREFIX_.'MyCustomModule');
}
public function hookStatusbtnoncustomerview()
{
/**
* Verify if this module is enabled
*/
if (!$this->active) {
return;
}
return $this->fetch('module:MyCustomModule/views/templates/hook/personal_information.html.twig');
}
/**
* Returns a string containing the HTML necessary to
* generate a configuration screen on the admin
*
* #return string
*/
public function getContent()
{
$output = null;
if (Tools::isSubmit('submit'.$this->name)) {
// get configuration fields value
$MyCustomModule_Account_Data = strval(Tools::getValue('MyCustomModule_Account_Data'));
$credit_Checkbox = strval(Tools::getValue('credit_Checkbox_1'));
$interval_Month = strval(Tools::getValue('Interval_Month'));
if (
!$MyCustomModule_Account_Data ||
empty($MyCustomModule_Account_Data) ||
!Validate::isGenericName($MyCustomModule_Account_Data)
) {
$output .= $this->displayError($this->l('Please Enter MyCustomModule Account Data.'));
} else{
// Update configuration fields value
Configuration::updateValue('MyCustomModule_Account_Data', $MyCustomModule_Account_Data);
Configuration::updateValue('credit_Checkbox_1', $credit_Checkbox);
Configuration::updateValue('Interval_Month', $interval_Month);
// Display message after successfully submit value
$output .= $this->displayConfirmation($this->l('Settings updated'));
}
}
return $output.$this->displayForm();
}
/**
* Display a form
*
* #param array $params
* #return form html using helper form
*/
public function displayForm()
{
// Get default language
$defaultLang = (int)Configuration::get('PS_LANG_DEFAULT');
$credit_Checkbox = [
[
'id'=>1,
'name'=>'',
'val' => 1
]
];
// Init Fields form array
$fieldsForm[0]['form'] = [
'legend' => [
'title' => $this->l('Configuration'),
],
'input' => [
[
'type' => 'text',
'label' => $this->l('MyCustomModule Account Data'),
'name' => 'MyCustomModule_Account_Data',
'required' => true
],
[
'type'=>'checkbox',
'label'=> $this->l('credit'),
'name'=>'credit_Checkbox',
'values'=>[
'query'=>$credit_Checkbox,
'id'=>'id',
'name'=>'name'
]
],
[
'type' => 'html',
'html_content' => '<input type="number" min="0" step="1" value="'.Configuration::get('Interval_Month').'" name="Interval_Month">',
'label' => $this->l('interval Month'),
'name' => 'Interval_Month',
'size' => 20
],
],
'submit' => [
'title' => $this->l('Save'),
'class' => 'btn btn-default pull-right'
]
];
$helper = new HelperForm();
// Module, token and currentIndex
$helper->module = $this;
$helper->name_controller = $this->name;
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->currentIndex = AdminController::$currentIndex.'&configure='.$this->name;
// Language
$helper->default_form_language = $defaultLang;
$helper->allow_employee_form_lang = $defaultLang;
// Title and toolbar
$helper->title = $this->displayName;
$helper->show_toolbar = true; // false -> remove toolbar
$helper->toolbar_scroll = true; // yes - > Toolbar is always visible on the top of the screen.
$helper->submit_action = 'submit'.$this->name;
$helper->toolbar_btn = [
'save' => [
'desc' => $this->l('Save'),
'href' => AdminController::$currentIndex.'&configure='.$this->name.'&save'.$this->name.
'&token='.Tools::getAdminTokenLite('AdminModules'),
],
'back' => [
'href' => AdminController::$currentIndex.'&token='.Tools::getAdminTokenLite('AdminModules'),
'desc' => $this->l('Back to list')
]
];
// Load current value
$helper->fields_value['MyCustomModule_Account_Data'] = Configuration::get('MyCustomModule_Account_Data');
$helper->fields_value['credit_Checkbox_1'] = Configuration::get('credit_Checkbox_1');
$helper->fields_value['Interval_Month'] = Configuration::get('Interval_Month');
return $helper->generateForm($fieldsForm);
}
}
i trying this url : http://localhost/prestashop/admin482vzxnel/index.php?module=mymodule&controller=checkstatus
geting error :
Page not found
The controller checkstatus is missing or invalid.
Thanks
To include a Class in a module you can use php require. Once included in module main file you can create an instance of you class where you want in your module.
To assign variable that you can use on template there is $this->context->smarty->assig() function.
The code Below is just an example of how include and use a Class in a custom module Controller.
Even the edit in the hookStatusbtnoncustomerview() is an example of how retrieve the module link and the link on his method, so take the code below such as it is, only an example
require_once (dirname(__FILE__)).'contollers/admin/Checkstatus.php';
class MyModule extends PaymentModule
{
public function __construct()
{
$this->name = 'MyCustomModule';
$this->tab = 'payments XYZ';
$this->version = '1.0';
$this->author = 'XYZ Technologies';
$this->bootstrap = true;
$this->displayName = 'XYZ';
$this->description = 'XYZ.';
$this->confirmUninstall = 'Are you sure you want to uninstall XYZ module?';
$this->ps_versions_compliancy = array('min' => '1.7.0', 'max' => _PS_VERSION_);
$this->allow_countries = array('CH', 'LI', 'AT', 'DE');
$this->allow_currencies = array('CHF', 'EUR');
$this->checkController = null;
parent::__construct();
}
private function _useCheckstatus(){
$this->checkController = new CheckstatusController();
}
public function MyMethod (){
$this->_useCheckstatus();
$this->checkController->demoAction();
}
public function hookStatusbtnoncustomerview()
{
/**
* Verify if this module is enabled
*/
if (!$this->active) {
return;
}
$this->context->smarty->assign(
array(
'my_module_name' => Configuration::get('MyCustomModule'),
'my_module_link' => $this->context->link->getModuleLink('MyCustomModule', 'mymethod'),
'token' => Tools::getToken(false)
)
);
return $this->fetch('module:MyCustomModule/views/templates/hook/personal_information.html.twig');
}
.........
it's not easy to investigate how prestashop works, so my tip is: look at the code from other modules, and take example from them.
Instead if you need to override a Core Prestashop Controller in your module you have to put the override file in /mymodule/override/controllers/admin/ModuleAdminController.php. installing/resetting the module will put this file within root/override/controllers/admin/ folder and the override becomes active. just remember to clear PS cache in "advanced settings->performance"
I want to add a range slider on configuration page of my PrestaShop module. I've tried to do this using HelperForm class, but I just can't do this, if I write other type, for example 'textarea' or 'checkbox', it works fine, even with not really standard input types like 'color', but 'range' doesn't work
<?php
if (!defined('_PS_VERSION_'))
exit;
class icropper extends Module
{
public function __construct()
{
$this->name = 'icropper';
$this->tab = 'front_office_features';
$this->version = '1.0';
$this->author = 'AppDev';
$this->need_instance = 1;
$this->ps_versions_compliancy = array('min' => '1.5', 'max' => _PS_VERSION_);
parent::__construct();
$this->displayName = $this->l('icropper');
$this->description = $this->l('Module for Cropping Images');
$this->confirmUninstall = $this->l('Are you sure you want to uninstall?');
if (!Configuration::get('MYMODULE_NAME'))
$this->warning = $this->l('No name provided');
}
public function install()
{
$filename = _PS_ROOT_DIR_.'/override/cropp.php';
$ext = get_loaded_extensions();
foreach($ext as $i)
{
if($i == "imagick") {
$imgck = $i;
break;
}
}
if (!parent::install()) {
return false;
} elseif (!$imgck) {
$this->context->controller->errors[] = $this->l('In your server does not installed Imagick library');
return false;
} elseif(file_exists($filename)) {
$this->context->controller->errors[] = $this->l('File that override cropping
already exist, please delete it and replace file by yourself');
return false;
}else {
//copy(__DIR__ . '/override/list_footer.tpl', _PS_ROOT_DIR_ . '/override/helpers/admin/templates/list');
return true;
}
}
public function uninstall()
{
if (!parent::uninstall())
return false;
return true;
}
public function getContent()
{
return $this->DisplayForm();
}
public function displayForm(){
$fields_formm[0] = array(
'form' => array(
'legend' => array(
'title' => $this->l('Header'),
'icon' => 'icon-file-text'
),
'input' => array(
array(
'type' => '',
'name'=> 'vania',
'min'=>0,
'max'=>100,
'step'=>1
),
'submit' => array(
'title' => $this->l('Generate')
)
)
)
);
$helper = new HelperForm();
$helper->show_toolbar = false;
$helper->table = $this->table;
$lang = new Language((int)Configuration::get('PS_LANG_DEFAULT'));
$helper->default_form_language = 1;
$this->fields_formm = array();
$helper->submit_action = 'submitform';
return $helper->generateForm(array($fields_formm[0]));
}
}
?>
You have to extends the view of the helper form. I will try to guide you :).
First, your module had to be hooked on this hook 'displayBackOfficeHeader':
public function install(){
[...]
$this->registerHook('backOfficeHeader');
[...]
}
So edit your code to add this line of code.
Second step, add the function for the hook, and load, query and jquery ui for the slider
public function hookBackOfficeHeader($params){
if ( Tools::getValue('module_name') == $this->name OR Tools::getValue('configure') == $this->name ) {
$this->context->controller->addJquery();
$this->context->controller->addJqueryUI('ui.slider');
}
}
Third step, add a 'new' type to your input in the fields_form array, like rangeslider, and I'll suggest you to use this corrected lines of codes:
public function displayForm(){
$fields_form = array(
'form' => array(
'legend' => array(
'title' => $this->l('Header'),
'icon' => 'icon-file-text'
),
'input' => array(
array(
'type' => 'rangeslider',
'name'=> 'vania',
'label' => $this->l('Select range'),
'min'=>0,
'max'=>100,
'step'=>1
),
),
'submit' => array(
'title' => $this->l('Generate')
)
)
);
$helper = new HelperForm();
$helper->show_toolbar = false;
$lang = new Language((int)Configuration::get('PS_LANG_DEFAULT'));
$helper->module = $this;
$helper->default_form_language = $this->context->language->id;
$helper->currentIndex = $this->context->link->getAdminLink('AdminModules', false)
. '&configure=' . $this->name . '&tab_module=' . $this->tab . '&module_name=' . $this->name;
$helper->token = Tools::getAdminTokenLite('AdminModules');
$helper->submit_action = 'submitform';
return $helper->generateForm(array($fields_form));
}
Fourth step, add a file name form.tpl in this directory:
icropper/views/templates/admin/_configure/helpers/form/form.tpl
with this content:
{extends file="helpers/form/form.tpl"}
{block name="field"}
{if $input.type == 'rangeslider'}
<div class="col-lg-9">
<div id="slider-range"></div>
<p>
<label for="amount">Price range:</label>
<input type="text" id="amount" readonly style="border:0; color:#f6931f; font-weight:bold;">
</p>
</div>
<script type="text/javascript">
{literal}
$( function() {
$( "#slider-range" ).slider({
range: true,
min: {/literal}{$input.min|intval}{literal},
max: {/literal}{$input.max|intval}{literal},
step: {/literal}{$input.step|intval}{literal},
slide: function( event, ui ) {
$( "#amount" ).val( "$" + ui.values[ 0 ] + " - $" + ui.values[ 1 ] );
}
});
$( "#amount" ).val( "$" + $( "#slider-range" ).slider( "values", 0 ) +
" - $" + $( "#slider-range" ).slider( "values", 1 ) );
});
{/literal}
</script>
{else}
{$smarty.block.parent}
{/if}
{/block}
Here you are, this is the way to add your range slider to the form (or other input types ), by the way, in this case I have merged smarty and javascript code for quickness, but if we want to respect the prestashop mvc we have to made a different js files with slider initialization, too long to explain XD.
Cheers ;).
Tell me if I've missed something :).
I was following this book to install the ZendSearh on the application, I did exactly as it's written and I'm getting a Fatal error: Class 'ZendSearch\Lucene\Lucene' not found in /var/www/CommunicationApp/module/Users/src/Users/Controller/SearchController.php on line 107
<?php
namespace Users\Controller;
use Zend\Mvc\Controller\AbstractActionController;
use Zend\View\Model\ViewModel;
use Zend\Http\Headers;
use Zend\Authentication\AuthenticationService;
use Zend\Authentication\Adapter\DbTable as DbTableAuthAdapter;
use Users\Form\RegisterForm;
use Users\Form\RegisterFilter;
use Users\Model\User;
use Users\Model\UserTable;
use Users\Model\Upload;
use Users\Model\ImageUpload;
use Users\Model\ImageUploadTable;
use ZendSearch\Lucene;
use ZendSearch\Lucene\Document;
use ZendSearch\Lucene\Index;
class SearchController extends AbstractActionController
{
protected $storage;
protected $authservice;
public function getAuthService()
{
if (! $this->authservice) {
$this->authservice = $this->getServiceLocator()->get('AuthService');
}
return $this->authservice;
}
public function getIndexLocation()
{
// Fetch Configuration from Module Config
$config = $this->getServiceLocator()->get('config');
if ($config instanceof Traversable) {
$config = ArrayUtils::iteratorToArray($config);
}
if (!empty($config['module_config']['search_index'])) {
return $config['module_config']['search_index'];
} else {
return FALSE;
}
}
public function getFileUploadLocation()
{
// Fetch Configuration from Module Config
$config = $this->getServiceLocator()->get('config');
if ($config instanceof Traversable) {
$config = ArrayUtils::iteratorToArray($config);
}
if (!empty($config['module_config']['upload_location'])) {
return $config['module_config']['upload_location'];
} else {
return FALSE;
}
}
public function indexAction()
{
$request = $this->getRequest();
if ($request->isPost()) {
$queryText = $request->getPost()->get('query');
$searchIndexLocation = $this->getIndexLocation();
$index = Lucene\Lucene::open($searchIndexLocation);
$searchResults = $index->find($queryText);
}
// prepare search form
$form = new \Zend\Form\Form();
$form->add(array(
'name' => 'query',
'attributes' => array(
'type' => 'text',
'id' => 'queryText',
'required' => 'required'
),
'options' => array(
'label' => 'Search String',
),
));
$form->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Search',
'style' => "margin-bottom: 8px; height: 27px;"
),
));
$viewModel = new ViewModel(array('form' => $form, 'searchResults' => $searchResults));
return $viewModel;
}
public function generateIndexAction()
{
$searchIndexLocation = $this->getIndexLocation();
$index = Lucene\Lucene::create($searchIndexLocation); // line 107
$userTable = $this->getServiceLocator()->get('UserTable');
$uploadTable = $this->getServiceLocator()->get('UploadTable');
$allUploads = $uploadTable->fetchAll();
foreach($allUploads as $fileUpload) {
//
$uploadOwner = $userTable->getUser($fileUpload->user_id);
// id field
$fileUploadId= Document\Field::unIndexed('upload_id', $fileUpload->id);
// label field
$label = Document\Field::Text('label', $fileUpload->label);
// owner field
$owner = Document\Field::Text('owner', $uploadOwner->name);
if (substr_compare($fileUpload->filename, ".xlsx", strlen($fileUpload->filename)-strlen(".xlsx"), strlen(".xlsx")) === 0) {
// index excel sheet
$uploadPath = $this->getFileUploadLocation();
$indexDoc = Lucene\Document\Xlsx::loadXlsxFile($uploadPath ."/" . $fileUpload->filename);
} else if (substr_compare($fileUpload->filename, ".docx", strlen($fileUpload->filename)-strlen(".docx"), strlen(".docx")) === 0) {
// index word doc
$uploadPath = $this->getFileUploadLocation();
$indexDoc = Lucene\Document\Docx::loadDocxFile($uploadPath ."/" . $fileUpload->filename);
} else {
$indexDoc = new Lucene\Document();
}
$indexDoc->addField($label);
$indexDoc->addField($owner);
$indexDoc->addField($fileUploadId);
$index->addDocument($indexDoc);
}
$index->commit();
}
}
It has its own repository on github.
https://github.com/zendframework/ZendSearch
You have to load it via composer or just download and put it under vendor folder.
I made a product site at the end of last year using DataObjects as Pages - Part 2 Silverstripe, the site is live now, and I need to implement a site search function for the site.
I implemented a search function like Tutorial 4 - Site Search however this doesn't work with the product search since each product is a dataobject rather than a page.
Can anyone shed some light on how I can make the site search work for the products?
I know there is a tutorial 3 DataObject as Pages I tried it but it messed up all my existing products as well as some additional existing functions for the products. Someone suggested http://silverstripe.org/all-other-modules/show/6641?start=24 but unsuccessful so far.
Any help on how to do the search function for the products is appreciated.
Thanks.
Here is my Product.php code
<?php
class Product extends DataObject
{
static $db = array(
'Title' => 'Varchar(255)',
'Description' => 'HTMLText',
'Price' => 'Decimal(6,2)',
'URLSegment' => 'Varchar(255)'
);
//Set our defaults
static $defaults = array(
'Title' => 'New Product',
'URLSegment' => 'new-product'
);
static $has_one = array(
'Image' => 'Image',
'PDF' => 'File'
);
//Relate to the category pages
static $belongs_many_many = array(
'Categories' => 'CategoryPage'
);
//Fields to show in ModelAdmin table
static $summary_fields = array(
'Title' => 'Title',
'URLSegment' => 'URLSegment',
'Price' => 'Price (£)'
);
//Add an SQL index for the URLSegment
static $indexes = array(
"URLSegment" => true
);
//Fields to search in ModelAdmin
static $searchable_fields = array (
'Title',
'URLSegment',
'Description',
'Categories.ID' => array(
'title' => 'Category'
)
);
function getCMSFields()
{
$fields = parent::getCMSFields();
//Main Tab
$fields->addFieldToTab("Root.Main", new TextField('Title', 'Title'));
$fields->addFieldToTab("Root.Main", new TextField('URLSegment', 'URL Segment'));
$fields->addFieldToTab("Root.Main", new NumericField('Price'));
$fields->addFieldToTab("Root.Main", new HTMLEditorField('Description'));
//added below for the ordering
$Categories = DataObject::get('CategoryPage');
$map = $Categories->map('ID', 'CheckboxSummary');
asort($map);
$fields->addFieldToTab("Root.Categories", new CheckboxsetField('Categories', 'Categories', $map));
//Images
$fields->addFieldToTab("Root.Images", new ImageField('Image', 'Image', Null, Null, Null, 'Uploads/category_banners'));
$fields->addFieldToTab("Root.Files", new FileIFrameField('PDF'));
return $fields;
}
//Set URLSegment to be unique on write
function onBeforeWrite()
{
// If there is no URLSegment set, generate one from Title
if((!$this->URLSegment || $this->URLSegment == 'new-product') && $this->Title != 'New Product')
{
$this->URLSegment = SiteTree::generateURLSegment($this->Title);
}
else if($this->isChanged('URLSegment'))
{
// Make sure the URLSegment is valid for use in a URL
$segment = preg_replace('/[^A-Za-z0-9]+/','-',$this->URLSegment);
$segment = preg_replace('/-+/','-',$segment);
// If after sanitising there is no URLSegment, give it a reasonable default
if(!$segment) {
$segment = "product-$this->ID";
}
$this->URLSegment = $segment;
}
// Ensure that this object has a non-conflicting URLSegment value.
$count = 2;
while($this->LookForExistingURLSegment($this->URLSegment))
{
$this->URLSegment = preg_replace('/-[0-9]+$/', null, $this->URLSegment) . '-' . $count;
$count++;
}
parent::onBeforeWrite();
}
//Test whether the URLSegment exists already on another Product
function LookForExistingURLSegment($URLSegment)
{
return (DataObject::get_one('Product', "URLSegment = '" . $URLSegment ."' AND ID != " . $this->ID));
}
//Generate the link for this product
function Link()
{
//if we are on a category page return that
if(Director::CurrentPage()->ClassName == 'CategoryPage')
{
$Category = Director::CurrentPage();
}
//Otherwise just grab the first category this product is in
else
{
$Category = $this->Categories()->First();
}
//Check we have a category then return the link
if($Category)
{
return $Category->absoluteLink() . 'show/' . $this->URLSegment;
}
}
//Return the Title as a menu title
public function MenuTitle()
{
return $this->Title;
}
function canView() {
return true;
}
public function LinkingMode()
{
//Check that we have a controller to work with and that it is a StaffPage
if(Controller::CurrentPage() && Controller::CurrentPage()->ClassName == 'CategoryPage')
{
//check that the action is 'show' and that we have a StaffMember to work with
if(Controller::CurrentPage()->getAction() == 'show' && $Product = Controller::CurrentPage()->getCurrentProduct())
{
//If the current StaffMember is the same as this return 'current' class
return ($Product->ID == $this->ID) ? 'current' : 'link';
}
}
}
}
and here is my CategoryPage.php
<?php
class CategoryPage extends Page
{
static $has_one = array(
'CategoryBanner' => 'Image',
'Photo' => 'Image'
);
static $many_many = array(
'Products' => 'Product'
);
static $allowed_children = array(
'none' => 'none'
);
function getCMSFields()
{
$fields = parent::getCMSFields();
$fields->addFieldToTab("Root.Content.Images", new ImageField('Photo'));
//Banner Images
$fields->addFieldToTab("Root.Content.Banner", new ImageField('CategoryBanner', 'Banner', Null, Null, Null, 'Uploads/category_banners'));
return $fields;
}
//important for sidebar showing, this is sitetree stuff - relationship between categories and products 20012012
public function onBeforeDelete()
{
$CurrentVal = $this->get_enforce_strict_hierarchy();
$this->set_enforce_strict_hierarchy(false);
parent::onBeforeDelete();
$this->set_enforce_strict_hierarchy($CurrentVal);
}
public function Children(){
return $this->Products();
}
//added this on 03022011 for the parent page to show on Categories in admin
function CheckboxSummary(){
return $this->Parent()->Title . ' - ' . $this->Title;
}
}
class CategoryPage_Controller extends Page_Controller
{
static $allowed_actions = array(
'show'
);
public function init()
{
parent::init();
Requirements::css('themes/tutorial/css/products.css');
//added this to make the gallery js work 10012012
Requirements::set_write_js_to_body(false);
Requirements::javascript("mysite/javascript/jquery-1.4.2.min.js"); Requirements::javascript("mysite/javascript/jquery.cycle.lite.min.js");
Requirements::javascript("mysite/javascript/toggle_menu.js");
}
//Return the list of products for this category
public function getProductsList()
{
return $this->Products(Null, 'Price ASC');
}
//Get's the current product from the URL, if any
public function getCurrentProduct()
{
$Params = $this->getURLParams();
$URLSegment = Convert::raw2sql($Params['ID']);
if($URLSegment && $Product = DataObject::get_one('Product', "URLSegment = '" . $URLSegment . "'"))
{
return $Product;
}
}
//Shows the Product detail page
function show()
{
//Get the Product
if($Product = $this->getCurrentProduct())
{
$Data = array(
'Product' => $Product,
'MetaTitle' => $Product->Title
);
//return our $Data array to use, rendering with the ProductPage.ss template
return $this->customise($Data)->renderWith(array('ProductPage', 'Page'));
}
else //Product not found
{
return $this->httpError(404, 'Sorry that product could not be found');
}
}
//Generate out custom breadcrumbs
public function Breadcrumbs() {
//Get the default breadcrumbs
$Breadcrumbs = parent::Breadcrumbs();
if($Product = $this->getCurrentProduct())
{
//Explode them into their individual parts
$Parts = explode(SiteTree::$breadcrumbs_delimiter, $Breadcrumbs);
//Count the parts
$NumOfParts = count($Parts);
//Change the last item to a link instead of just text
$Parts[$NumOfParts-1] = ('' . $Parts[$NumOfParts-1] . '');
//Add our extra piece on the end
$Parts[$NumOfParts] = $Product->Title;
//Return the imploded array
$Breadcrumbs = implode(SiteTree::$breadcrumbs_delimiter, $Parts);
}
return $Breadcrumbs;
}
}
If you are doing any serious search stuff, the built-in search functionality (based on MySQL MyISAM) is not ideal. I'd suggest to use Solr or Sphinx, integrated into SilverStripe with https://github.com/silverstripe/silverstripe-sphinx or https://github.com/nyeholt/silverstripe-solr (I'd start off with the first one). This will also index DAOs.