I am having some trouble, mostly from the lack of my knowledge on WordPress and I need some expert help. I am retrieving data from a table named wp_words in my WordPress database, and I am showing them in a table form, in a menu page in the WordPress administrator panel. I create a menu page called Lexicon Testing and the table is shown in that menu page. What I want to do is, when I click the edit button below each code (as shown in the picture), to be able to edit the rest of the lines fields on the same page. Like a quick edit function like in posts and pages. The way that I am retrieving data is shown n the code below. I would like some advice please and some guidance. Is it possible to do it like I want? Do I need to retrieve data another way to make it work like that? The way I used to retrieve data is on this page: https://www.sitepoint.com/using-wp_list_table-to-create-wordpress-admin-tables/
<?php
/*
Plugin Name: Testing v1
Description: Testing
Version: 1.0
*/
if (!class_exists('WP_List_Table')) {
require_once( ABSPATH . 'wp-admin/includes/class-wp-list-table.php' );
}
class Lexicon_words_List extends WP_List_Table {
/** Class constructor */
public function __construct() {
parent::__construct([
'singular' => __('Lexicon word', 'sp'), //singular name of the listed records
'plural' => __('Lexicon words', 'sp'), //plural name of the listed records
'ajax' => false //does this table support ajax?
]);
}
/**
* Retrieve customers data from the database
*
* #param int $per_page
* #param int $page_number
*
* #return mixed
*/
public static function get_lexicon_words($per_page = 5, $page_number = 1) {
global $wpdb;
$sql = "SELECT * FROM {$wpdb->prefix}lexicon_words";
if (!empty($_REQUEST['orderby'])) {
$sql .= ' ORDER BY ' . esc_sql($_REQUEST['orderby']);
$sql .= !empty($_REQUEST['order']) ? ' ' . esc_sql($_REQUEST['order']) : ' ASC';
}
$sql .= " LIMIT $per_page";
$sql .= ' OFFSET ' . ( $page_number - 1 ) * $per_page;
$result = $wpdb->get_results($sql, 'ARRAY_A');
return $result;
}
/**
* Delete a customer record.
*
* #param int $id customer ID
*/
public static function delete_lexicon_word($id) {
global $wpdb;
$wpdb->delete(
"{$wpdb->prefix}lexicon_words", array('id' => $id)
);
}
/**
* Edit a customer record.
*
* #param int $id customer ID
*/
public static function edit_lexicon_word($id) {
}
/**
* Returns the count of records in the database.
*
* #return null|string
*/
public static function record_count() {
global $wpdb;
$sql = "SELECT COUNT(*) FROM {$wpdb->prefix}lexicon_words";
return $wpdb->get_var($sql);
}
/** Text displayed when no customer data is available */
public function no_items() {
_e('No words avaliable.', 'sp');
}
/**
* Render a column when no column specific method exist.
*
* #param array $item
* #param string $column_name
*
* #return mixed
*/
public function column_default($item, $column_name) {
switch ($column_name) {
case 'code':
case 'text':
case 'phrase':
case 'context':
case 'level':
case 'lang':
return $item[$column_name];
default:
return print_r($item, true); //Show the whole array for troubleshooting purposes
}
}
/**
* Render the bulk edit checkbox
*
* #param array $item
*
* #return string
*/
function column_cb($item) {
return sprintf(
'<input type="checkbox" name="bulk-delete[]" value="%s" />', $item['id']
);
}
/**
* Method for code column
*
* #param array $item an array of DB data
*
* #return string
*/
function column_code($item) {
$delete_nonce = wp_create_nonce('sp_delete_lexicon_word');
$edit_nonce = wp_create_nonce('sp_edit_lexicon_word');
$title = '<strong>' . $item['code'] . '</strong>';
$actions = [
'edit' => sprintf('Edit', esc_attr($_REQUEST['page']), 'edit', absint($item['id']), $edit_nonce),
'delete' => sprintf('Delete', esc_attr($_REQUEST['page']), 'delete', absint($item['id']), $delete_nonce)
];
return $title . $this->row_actions($actions);
}
/**
* Associative array of columns
*
* #return array
*/
function get_columns() {
$columns = [
'cb' => '<input type="checkbox" />',
'code' => __('Code', 'sp'),
'text' => __('Text', 'sp'),
'phrase' => __('Phrase', 'sp'),
'context' => __('Context', 'sp'),
'level' => __('Level', 'sp'),
'column_6' => __('Column 6', 'sp'),
'column_7' => __('Column 7', 'sp'),
'column_8' => __('Column 8', 'sp'),
'column_9' => __('Column 9', 'sp'),
'column_10' => __('Column 10', 'sp'),
'column_11' => __('Column 11', 'sp'),
'column_12' => __('Column 12', 'sp'),
'column_13' => __('Column 13', 'sp'),
'column_14' => __('Column 14', 'sp'),
'column_15' => __('Column 15', 'sp'),
'column_16' => __('Column 16', 'sp'),
'lang' => __('Lang', 'sp')
];
return $columns;
}
/**
* Columns to make sortable.
*
* #return array
*/
public function get_sortable_columns() {
$sortable_columns = array(
'code' => array('code', true),
'text' => array('text', false)
);
return $sortable_columns;
}
/**
* Returns an associative array containing the bulk action
*
* #return array
*/
public function get_bulk_actions() {
$actions = [
'bulk-delete' => 'Delete'
];
return $actions;
}
/**
* Handles data query and filter, sorting, and pagination.
*/
public function prepare_items() {
$this->_column_headers = $this->get_column_info();
/** Process bulk action */
$this->process_bulk_action();
$per_page = $this->get_items_per_page('lexicon_words_per_page', 5);
$current_page = $this->get_pagenum();
$total_items = self::record_count();
$this->set_pagination_args([
'total_items' => $total_items, //WE have to calculate the total number of items
'per_page' => $per_page //WE have to determine how many items to show on a page
]);
$this->items = self::get_lexicon_words($per_page, $current_page);
}
public function process_bulk_action() {
//Detect when a bulk action is being triggered...
if ('delete' === $this->current_action()) {
// In our file that handles the request, verify the nonce.
$nonce = esc_attr($_REQUEST['_wpnonce']);
if (!wp_verify_nonce($nonce, 'sp_delete_lexicon_word')) {
die('Fatal Error');
} else {
self::delete_lexicon_word(absint($_GET['lexicon_word']));
// esc_url_raw() is used to prevent converting ampersand in url to "#038;"
// add_query_arg() return the current url
//$origUrl = esc_attr($_REQUEST['page']);
wp_redirect(esc_url_raw(add_query_arg(array('page' => 'lexicon_testing'), admin_url('admin.php'))));
exit;
}
}
if ('edit' === $this->current_action()) {
// In our file that handles the request, verify the nonce.
$nonce = esc_attr($_REQUEST['_wpnonce']);
if (!wp_verify_nonce($nonce, 'sp_edit_lexicon_word')) {
die('Fatal Error');
} else {
self::edit_lexicon_word(absint($_GET['lexicon_word']));
// esc_url_raw() is used to prevent converting ampersand in url to "#038;"
// add_query_arg() return the current url
//$origUrl = esc_attr($_REQUEST['page']);
wp_redirect(esc_url_raw(add_query_arg(array('page' => 'lexicon_testing'), admin_url('admin.php'))));
exit;
}
}
// If the delete bulk action is triggered
if (( isset($_POST['action']) && $_POST['action'] == 'bulk-delete' ) || ( isset($_POST['action2']) && $_POST['action2'] == 'bulk-delete' )
) {
$delete_ids = esc_sql($_POST['bulk-delete']);
// loop over the array of record IDs and delete them
foreach ($delete_ids as $id) {
self::delete_lexicon_word($id);
}
// esc_url_raw() is used to prevent converting ampersand in url to "#038;"
// add_query_arg() return the current url
wp_redirect(esc_url_raw(add_query_arg(array('page' => 'lexicon_testing'), admin_url('admin.php'))));
exit;
}
}
}
class SP_Plugin {
// class instance
static $instance;
// customer WP_List_Table object
public $lexicon_words_obj;
// class constructor
public function __construct() {
add_filter('set-screen-option', [__CLASS__, 'set_screen'], 10, 3);
add_action('admin_menu', [$this, 'plugin_menu']);
}
public static function set_screen($status, $option, $value) {
return $value;
}
public function plugin_menu() {
$hook = add_menu_page(
'Lexicon Testing', 'Lexicon Testing', 'manage_options', 'lexicon_testing', [$this, 'plugin_settings_page']
);
add_action("load-$hook", [$this, 'screen_option']);
}
/**
* Plugin settings page
*/
public function plugin_settings_page() {
?>
<div class="wrap">
<h2>Lexicon Database Management</h2>
<div id="poststuff">
<div id="post-body" class="metabox-holder columns-2">
<div id="post-body-content">
<div class="meta-box-sortables ui-sortable">
<form method="post">
<?php
$this->lexicon_words_obj->prepare_items();
$this->lexicon_words_obj->display();
?>
</form>
</div>
</div>
</div>
<br class="clear">
</div>
</div>
<?php
}
/**
* Screen options
*/
public function screen_option() {
$option = 'per_page';
$args = [
'label' => 'Words',
'default' => 5,
'option' => 'lexicon_words_per_page'
];
add_screen_option($option, $args);
$this->lexicon_words_obj = new Lexicon_words_List();
}
/** Singleton instance */
public static function get_instance() {
if (!isset(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
function edit_word_metabox() {
add_meta_box('editword_metabox', '$title', 'editword_metabox_callback', 'lexicon_testing');
}
function editword_metabox_callback() {
}
}
add_action('plugins_loaded', function () {
SP_Plugin::get_instance();
});
Related
We have created a REST API PHP setup which is designed to compare the Tables of one database with another and if the user doesn't exist in one, create it in the Application. Also if there is a change to a user i.e. Email address, string etc then update it.
We've had a script created which using php works fantastically on our test server, it creates a user when required and updates them when required.
When we move this over to a different server which has the same database structure, same tables, same root username and password etc, it isn't recognising the existing users. It thinks it has xx,xxx users to create and then fails as 'user with that username already exists'.
Does any one have any ideas why this maybe the case? Below is the main php file which then has a config file, a db.php, a common.php and then a GpsGateapi.php
require_once('rest_api_includes/Config.php');
require_once(Config::API_INCLUDES_DIR . '/DB.php');
require_once(Config::API_INCLUDES_DIR . '/Gpsgate_API.php');
require_once(Config::API_INCLUDES_DIR . '/Common.php');
class API_Sync
{
/** #var int application id */
private $application_id;
/** #var int user type id */
private $user_type_id;
/** #var Gpsgate_API instance */
private $api;
/** #var DB instance */
private $db;
/** #var string<show|log> Show or log errors */
private $errors_report_type;
/** #var string<show|log|none> Show or log actions */
private $actions_report_type;
/**
* Sets variables, connects to database and REST API
*
* #throws \Exception
*/
public function __construct()
{
$this->application_id = Config::API_APPLIATION_ID;
$this->user_type_id = Config::API_USER_TYPE_ID;
$this->errors_report_type = Config::API_SHOW_OUTPUT ? 'show' : 'log';
$this->actions_report_type = Config::API_SHOW_OUTPUT ? 'show' : (Config::API_ACTIONS_LOGGING ? 'log' : 'none');
}
/**
* Main method that controlls all the process
*
* #throws \Exception
*/
public function run()
{
$this->prepare();
$gpsgate_users_result = $this->api->getUsers();
if ($gpsgate_users_result->status !== 200) {
throw new Exception(implode('; ', $gpsgate_users_result->body));
}
$gpsgate_users = $gpsgate_users_result->body;
$db_users = $this->db->get(DB::TBL_PERSONS);
$res = $this->compare($gpsgate_users, $db_users);
$this->updateUsers($res['update']);
$this->createUsers($res['create']);
}
/**
* Create gsgate users data from database array
*
* #param array $users
*/
private function createUsers($users)
{
if (!empty($users)) {
$this->logOrShowAction("Begin to create users data.");
foreach ($users as $user) {
$res = $this->api->createUser([
'email' => $user['Email'],
'name' => $user['FirstName'],
'surname' => $user['LastName'],
'driverID' => $user['RFID'],
'username' => $user['UserName'],
'password' => Common::generatePassword(),
'userTypeId' => $this->user_type_id,
'description' => $user['Location'],
]);
if ($res->status !== 200) {
throw new Exception($res->body);
}
}
$this->logOrShowAction("Done.");
}
}
/**
* Update gsgate users data from database array
*
* #param array $users
*/
private function updateUsers($users)
{
if (!empty($users)) {
$this->logOrShowAction("Begin to update users data.");
foreach ($users as $user) {
$res = $this->api->updateUser([
'email' => $user['Email'],
'name' => $user['FirstName'],
'surname' => $user['LastName'],
'driverID' => $user['RFID'],
'description' => $user['Location'],
], $user['gpsgate_id']);
if ($res->status !== 200) {
throw new Exception($res->body);
}
}
$this->logOrShowAction("Done.");
}
}
/**
* Compare arrays and return list of users data to update/create
*/
private function compare($gpsgate_users, $db_users)
{
$this->logOrShowAction("Begin to compare users data.");
$gpsgate_user_key = 'username';
$db_user_key = 'UserName';
$gpsgate_users = Common::setIndexesByKey($gpsgate_user_key, $gpsgate_users);
$res = [
'update' => [],
'create' => [],
];
foreach ($db_users as $user) {
$user_key = $user[$db_user_key];
if (!empty($gpsgate_users[$user_key])) {
if ($this->userInfoVary($gpsgate_users[$user_key], $user)) {
$user['gpsgate_id'] = $gpsgate_users[$user_key]->id; // add gpsgate id
$res['update'][] = $user;
}
} else {
$res['create'][] = $user;
}
}
$this->logOrShowAction('Done');
$this->logOrShowAction('Need to create: ' . count($res['create']) . ' rows; to update: ' . count($res['update']) . ' rows.');
return $res;
}
/**
* Check is the information between db user and api user is different
*/
private function userInfoVary($gpsgate_user, $db_user)
{
return $db_user['Email'] != $gpsgate_user->email
|| $db_user['FirstName'] != $gpsgate_user->name
|| $db_user['LastName'] != $gpsgate_user->surname
|| $db_user['RFID'] != ($gpsgate_user->driverID ?? '')
|| $db_user['Location'] != $gpsgate_user->description;
}
/**
* Connects to database, REST API, creates folders for errors and actions if need
*/
private function prepare()
{
if ($this->errors_report_type == 'log') {
if (!file_exists(dirname(Config::API_LOG_FILE_ERRORS))) {
mkdir(dirname(Config::API_LOG_FILE_ERRORS));
}
if (!file_exists(Config::API_LOG_FILE_ERRORS)) {
file_put_contents(Config::API_LOG_FILE_ERRORS, '');
}
}
if ($this->actions_report_type == 'log') {
if (!file_exists(dirname(Config::API_LOG_FILE_ACTIONS))) {
mkdir(dirname(Config::API_LOG_FILE_ACTIONS));
}
if (!file_exists(Config::API_LOG_FILE_ACTIONS)) {
file_put_contents(Config::API_LOG_FILE_ACTIONS, '');
}
}
$this->logOrShowAction('Trying to connect to database and GPSGATE REST API.');
$this->api = new Gpsgate_API(Config::API_APPLIATION_ID, Config::API_USER_TYPE_ID);
$this->db = DB::instance();
$this->logOrShowAction('Done.');
}
/**
* Logs error message in file or output in browser
*
* #param string $msg
*/
public function logOrShowError($msg)
{
$msg = "<span style='color: red; font-weight: 600;'>Error: " . $msg . "</span>";
$this->writeOrEchoMessage($msg, Config::API_LOG_FILE_ERRORS, $this->errors_report_type);
}
/**
* Logs action message in file or output in browser
*
* #param string $msg
*/
public function logOrShowAction($msg)
{
$this->writeOrEchoMessage($msg, Config::API_LOG_FILE_ACTIONS, $this->actions_report_type);
}
private function writeOrEchoMessage($msg, $file, $report_type)
{
if ($report_type == 'none') {
return ;
}
$msg = '[' . date('Y-m-d H:i:s') . '] ' . $msg;
if ($report_type == 'show') {
echo $msg . '<br>';
} else {
$h = fopen($file, 'a+');
fwrite($h, strip_tags($msg) . PHP_EOL);
fclose($h);
}
}
}
$sync = new API_Sync();
try {
$sync->run();
} catch (\Exception $e) {
$sync->logOrShowError($e->getMessage());
}
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 am trying to add extra fields in magento product review.
1. Good thing about product
2. Bad thing about product
I follow this link. I have override core files into my local files.
Here my code:
template/review/form.phtml
<li>
<label for="goodthings" class="required"><?php echo $this->__('Good Things') ?></label>
<div class="input-box">
<textarea name="goodthings" id="goodthings" cols="5" rows="3" ><?php echo $this->escapeHtml($data->getGoodthings()) ?></textarea>
</div>
</li>
<li>
<label for="badthings" class="required"><?php echo $this->__('Bad Things') ?></label>
<div class="input-box">
<textarea name="badthings" id="badthings" cols="5" rows="3" ><?php echo $this->escapeHtml($data->getBadthings()) ?></textarea>
</div></li>
and
/local/Mage/Review/Model/Resource/Review.php
$detail = array(
'title' => $object->getTitle(),
'detail' => $object->getDetail(),
'nickname' => $object->getNickname(),
'goodthing' => $object->getGoodthings(),
'badthing' => $object->getBadthings(),
);
Database query:
ALTER TABLE `review_detail` ADD `goodthing` TEXT NOT NULL COMMENT 'review goodthing' AFTER `nickname` ,
ADD `badthing` TEXT NOT NULL COMMENT 'review badthing' AFTER `goodthing` ;
/local/Mage/Review/controllers/ProductController.php
class Mage_Review_ProductController extends Mage_Core_Controller_Front_Action
{
/**
* Action list where need check enabled cookie
*
* #var array
*/
protected $_cookieCheckActions = array('post');
public function preDispatch()
{
parent::preDispatch();
$allowGuest = Mage::helper('review')->getIsGuestAllowToWrite();
if (!$this->getRequest()->isDispatched()) {
return;
}
$action = strtolower($this->getRequest()->getActionName());
if (!$allowGuest && $action == 'post' && $this->getRequest()->isPost()) {
if (!Mage::getSingleton('customer/session')->isLoggedIn()) {
$this->setFlag('', self::FLAG_NO_DISPATCH, true);
Mage::getSingleton('customer/session')->setBeforeAuthUrl(Mage::getUrl('*/*/*', array('_current' => true)));
Mage::getSingleton('review/session')->setFormData($this->getRequest()->getPost())
->setRedirectUrl($this->_getRefererUrl());
$this->_redirectUrl(Mage::helper('customer')->getLoginUrl());
}
}
return $this;
}
/**
* Initialize and check product
*
* #return Mage_Catalog_Model_Product
*/
protected function _initProduct()
{
Mage::dispatchEvent('review_controller_product_init_before', array('controller_action'=>$this));
$categoryId = (int) $this->getRequest()->getParam('category', false);
$productId = (int) $this->getRequest()->getParam('id');
$product = $this->_loadProduct($productId);
if (!$product) {
return false;
}
if ($categoryId) {
$category = Mage::getModel('catalog/category')->load($categoryId);
Mage::register('current_category', $category);
}
try {
Mage::dispatchEvent('review_controller_product_init', array('product'=>$product));
Mage::dispatchEvent('review_controller_product_init_after', array(
'product' => $product,
'controller_action' => $this
));
} catch (Mage_Core_Exception $e) {
Mage::logException($e);
return false;
}
return $product;
}
/**
* Load product model with data by passed id.
* Return false if product was not loaded or has incorrect status.
*
* #param int $productId
* #return bool|Mage_Catalog_Model_Product
*/
protected function _loadProduct($productId)
{
if (!$productId) {
return false;
}
$product = Mage::getModel('catalog/product')
->setStoreId(Mage::app()->getStore()->getId())
->load($productId);
/* #var $product Mage_Catalog_Model_Product */
if (!$product->getId() || !$product->isVisibleInCatalog() || !$product->isVisibleInSiteVisibility()) {
return false;
}
Mage::register('current_product', $product);
Mage::register('product', $product);
return $product;
}
/**
* Load review model with data by passed id.
* Return false if review was not loaded or review is not approved.
*
* #param int $productId
* #return bool|Mage_Review_Model_Review
*/
protected function _loadReview($reviewId)
{
if (!$reviewId) {
return false;
}
$review = Mage::getModel('review/review')->load($reviewId);
/* #var $review Mage_Review_Model_Review */
if (!$review->getId() || !$review->isApproved() || !$review->isAvailableOnStore(Mage::app()->getStore())) {
return false;
}
Mage::register('current_review', $review);
return $review;
}
/**
* Submit new review action
*
*/
public function postAction()
{
if (!$this->_validateFormKey()) {
// returns to the product item page
$this->_redirectReferer();
return;
}
if ($data = Mage::getSingleton('review/session')->getFormData(true)) {
$rating = array();
if (isset($data['ratings']) && is_array($data['ratings'])) {
$rating = $data['ratings'];
}
} else {
$data = $this->getRequest()->getPost();
$rating = $this->getRequest()->getParam('ratings', array());
}
if (($product = $this->_initProduct()) && !empty($data)) {
$session = Mage::getSingleton('core/session');
/* #var $session Mage_Core_Model_Session */
$review = Mage::getModel('review/review')->setData($this->_cropReviewData($data));
/* #var $review Mage_Review_Model_Review */
$validate = $review->validate();
if ($validate === true) {
try {
$review->setEntityId($review->getEntityIdByCode(Mage_Review_Model_Review::ENTITY_PRODUCT_CODE))
->setEntityPkValue($product->getId())
->setStatusId(Mage_Review_Model_Review::STATUS_PENDING)
->setCustomerId(Mage::getSingleton('customer/session')->getCustomerId())
->setStoreId(Mage::app()->getStore()->getId())
->setStores(array(Mage::app()->getStore()->getId()))
->save();
foreach ($rating as $ratingId => $optionId) {
Mage::getModel('rating/rating')
->setRatingId($ratingId)
->setReviewId($review->getId())
->setCustomerId(Mage::getSingleton('customer/session')->getCustomerId())
->addOptionVote($optionId, $product->getId());
}
$review->aggregate();
$session->addSuccess($this->__('Your review has been accepted for moderation.'));
}
catch (Exception $e) {
$session->setFormData($data);
$session->addError($this->__('Unable to post the review.'));
}
}
else {
$session->setFormData($data);
if (is_array($validate)) {
foreach ($validate as $errorMessage) {
$session->addError($errorMessage);
}
}
else {
$session->addError($this->__('Unable to post the review.'));
}
}
}
if ($redirectUrl = Mage::getSingleton('review/session')->getRedirectUrl(true)) {
$this->_redirectUrl($redirectUrl);
return;
}
$this->_redirectReferer();
}
/**
* Show list of product's reviews
*
*/
public function listAction()
{
if ($product = $this->_initProduct()) {
Mage::register('productId', $product->getId());
$design = Mage::getSingleton('catalog/design');
$settings = $design->getDesignSettings($product);
if ($settings->getCustomDesign()) {
$design->applyCustomDesign($settings->getCustomDesign());
}
$this->_initProductLayout($product);
// update breadcrumbs
if ($breadcrumbsBlock = $this->getLayout()->getBlock('breadcrumbs')) {
$breadcrumbsBlock->addCrumb('product', array(
'label' => $product->getName(),
'link' => $product->getProductUrl(),
'readonly' => true,
));
$breadcrumbsBlock->addCrumb('reviews', array('label' => Mage::helper('review')->__('Product Reviews')));
}
$this->renderLayout();
} elseif (!$this->getResponse()->isRedirect()) {
$this->_forward('noRoute');
}
}
/**
* Show details of one review
*
*/
public function viewAction()
{
$review = $this->_loadReview((int) $this->getRequest()->getParam('id'));
if (!$review) {
$this->_forward('noroute');
return;
}
$product = $this->_loadProduct($review->getEntityPkValue());
if (!$product) {
$this->_forward('noroute');
return;
}
$this->loadLayout();
$this->_initLayoutMessages('review/session');
$this->_initLayoutMessages('catalog/session');
$this->renderLayout();
}
/**
* Load specific layout handles by product type id
*
*/
protected function _initProductLayout($product)
{
$update = $this->getLayout()->getUpdate();
$update->addHandle('default');
$this->addActionLayoutHandles();
$update->addHandle('PRODUCT_TYPE_'.$product->getTypeId());
if ($product->getPageLayout()) {
$this->getLayout()->helper('page/layout')
->applyHandle($product->getPageLayout());
}
$this->loadLayoutUpdates();
if ($product->getPageLayout()) {
$this->getLayout()->helper('page/layout')
->applyTemplate($product->getPageLayout());
}
$update->addUpdate($product->getCustomLayoutUpdate());
$this->generateLayoutXml()->generateLayoutBlocks();
}
/**
* Crops POST values
* #param array $reviewData
* #return array
*/
protected function _cropReviewData(array $reviewData)
{
$croppedValues = array();
$allowedKeys = array_fill_keys(array('detail', 'title', 'nickname','goodthings','badthings'), true);
foreach ($reviewData as $key => $value) {
if (isset($allowedKeys[$key])) {
$croppedValues[$key] = $value;
}
}
return $croppedValues;
}
}
form after submit not saving value of custom fields but default field are saving to database, i have tried to debug with Mage::log(); and i am getting value for all fields in my controller on submit but not getting in Model and no log print in my Model.
I was facing the same issue, but was able to solve it. Below are the steps:
1) my phtml file: added a custom field 'Email'
<li>
<label for="email_field" class="required"><?php echo $this->__('Email Address') ?> :</label>
<div class="input-box">
<input type="text" name="email" id="email_field" value="<?php echo $email; ?>" class="form-control validate-email" placeholder="Enter your Email" onfocus="this.placeholder = ''" onblur="this.placeholder = 'Enter your Email'" />
</div>
</li>
2) Created sql script to add new field to review_detail table
$installer = $this;
$installer->startSetup();
$installer->getConnection()
->addColumn(
$installer->getTable('review/review_detail'),
'customer_email',array(
'type' => Varien_Db_Ddl_Table::TYPE_TEXT,
'nullable' => false,
'length' => 255,
'after' => 'detail',
'comment' => 'Customer Email'
)
);
$installer->endSetup();
3) Override Mage/Review/controllers/ProductController.php and edit method _cropReviewData
protected function _cropReviewData(array $reviewData)
{
$croppedValues = array();
$allowedKeys = array_fill_keys(array('detail', 'title', 'nickname','customer_email'), true);
foreach ($reviewData as $key => $value) {
if (isset($allowedKeys[$key])) {
$croppedValues[$key] = $value;
}
}
return $croppedValues;
}
we have added our custom field customer_email to array
3) Override Mage_Review_Model_Resource_Review.php and edit method _afterSave and add custom field to $detail array
$detail = array(
'title' => $object->getTitle(),
'detail' => $object->getDetail(),
'nickname' => $object->getNickname(),
'customer_email' => $object->getCustomerEmail(),
);
After doing the above changes, the value for custom field will get saved to DB
Please try this and if not resolved let us know code of controller for save reviews
I want to integrate elasticsearch in my laravel project.
I have installed using following line :
Run command on terminal :
composer require shift31/laravel-elasticsearch:~1.0
Then i have created elasticsearch.php in app/config/ and added following code.
<?php
use Monolog\Logger;
return array(
'hosts' => array(
'your.elasticsearch.server:9200' // what should be my host ?
),
'logPath' => 'path/to/your/elasticsearch/log',
'logLevel' => Logger::INFO
);
My first question : What should i write in place of host name
Right now my project is running on local server with localhost:8000.
I have added Shift31\LaravelElasticsearch\ElasticsearchServiceProvider in app/config/app.php for enable the 'Es' facade.
Above all things done. Now in which file i should add the code of elasticsearch to add, update, delete and search the records.
I have product table I need to add product records in elasticsearch, when update product, records should be update.
I have no idea of the further process. Please guide me I have searched on google but no any example help me.
Create the following helper classes in their respective paths:
App\Traits\ElasticSearchEventTrait.php
<?php
Namespace App\Traits;
trait ElasticSearchEventTrait {
public $esRemoveDefault = array('created_at','updated_at','deleted_at');
public static function boot()
{
parent::boot();
static::bootElasticSearchEvent();
}
public static function bootElasticSearchEvent()
{
static::created(function ($model) {
if(isset($model->esEnabled) && $model->esEnabled === true)
{
$model->esCreate();
}
});
static::updated(function ($model) {
if(isset($model->esEnabled) && $model->esEnabled === true)
{
$model->esUpdate();
}
});
static::deleted(function ($model) {
if(isset($model->esEnabled) && $model->esEnabled === true)
{
$model->esUpdate();
}
});
}
private function esCreate()
{
//esContext is false for polymorphic relations with no elasticsearch indexing
if(isset($this->esMain) && $this->esMain === true && $this->esContext !== false)
{
\Queue::push('ElasticSearchHelper#indexTask',array('id'=>$this->esGetId(),'class'=>get_class($this),'context'=>$this->esGetContext(),'info-context'=>$this->esGetInfoContext(),'excludes'=>$this->esGetRemove()));
}
else
{
$this->esUpdate();
}
}
private function esUpdate()
{
//esContext is false for polymorphic relations with no elasticsearch indexing
if($this->esContext !== false)
{
\Queue::push('ElasticSearchHelper#updateTask',array('id'=>$this->esGetId(),'class'=>get_class($this),'context'=>$this->esGetContext(),'info-context'=>$this->esGetInfoContext(),'excludes'=>$this->esGetRemove()));
}
}
/*
* Get Id of Model
*/
public function esGetId()
{
if(isset($this->esId))
{
return $this->esId;
}
else
{
return $this->id;
}
}
public function esGetInfoContext()
{
if(isset($this->esInfoContext))
{
return $this->esInfoContext;
}
else
{
throw new \RuntimeException("esInfoContext attribute or esGetInfoContext() is not set in class '".get_class($this)."'");
}
}
/*
* Name of main context of model
*/
public function esGetContext()
{
if(isset($this->esContext))
{
return $this->esContext;
}
else
{
throw new \RuntimeException("esContext attribute or esGetContext() method must be set in class '".get_class($this)."'");
}
}
/*
* All attributes that needs to be removed from model
*/
public function esGetRemove()
{
if(isset($this->esRemove))
{
return array_unique(array_merge($this->esRemoveDefault,$this->esRemove));
}
else
{
return $this->esRemoveDefault;
}
}
/*
* Extends Illuminate Collection to provide additional array functions
*/
public function newCollection(array $models = Array())
{
return new Core\Collection($models);
}
/**
* Return a timestamp as DateTime object.
*
* #param mixed $value
* #return \Carbon\Carbon
*/
public function asEsDateTime($value)
{
// If this value is an integer, we will assume it is a UNIX timestamp's value
// and format a Carbon object from this timestamp. This allows flexibility
// when defining your date fields as they might be UNIX timestamps here.
if (is_numeric($value))
{
return \Carbon::createFromTimestamp($value);
}
// If the value is in simply year, month, day format, we will instantiate the
// Carbon instances from that format. Again, this provides for simple date
// fields on the database, while still supporting Carbonized conversion.
elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $value))
{
return \Carbon::createFromFormat('Y-m-d', $value)->startOfDay();
}
// Finally, we will just assume this date is in the format used by default on
// the database connection and use that format to create the Carbon object
// that is returned back out to the developers after we convert it here.
elseif ( ! $value instanceof DateTime)
{
$format = $this->getEsDateFormat();
return \Carbon::createFromFormat($format, $value);
}
return \Carbon::instance($value);
}
/**
* Get the format for database stored dates.
*
* #return string
*/
private function getEsDateFormat()
{
return $this->getConnection()->getQueryGrammar()->getDateFormat();
}
/*
* Converts model to a suitable format for ElasticSearch
*/
public function getEsSaveFormat()
{
$obj = clone $this;
//Go through ES Accessors
\ElasticSearchHelper::esAccessor($obj);
$dates = $this->getDates();
//Convert to array, then change Date to appropriate Elasticsearch format.
//Why? Because eloquent's date accessors is playing me.
$dataArray = $obj->attributesToArray();
//Remove all Excludes
foreach($this->esGetRemove() as $ex)
{
if(array_key_exists($ex,$dataArray))
{
unset($dataArray[$ex]);
}
}
if(!empty($dates))
{
foreach($dates as $d)
{
if(isset($dataArray[$d]) && $dataArray[$d] !== "" )
{
//Trigger Eloquent Getter which will provide a Carbon instance
$dataArray[$d] = $this->{$d}->toIso8601String();
}
}
}
return $dataArray;
}
}
App\Services\ElasticServiceHelper.php
<?php
/**
* Description of ElasticSearchHelper: Helps with Indexing/Updating with Elastic Search Server (https://www.elastic.co)
*
* #author kpudaruth
*/
Namespace App\Services;
class ElasticSearchHelper {
/*
* Laravel Queue - Index Task
* #param array $job
* #param array $data
*/
public function indexTask($job,$data)
{
if(\Config::get('website.elasticsearch') === true)
{
if(isset($data['context']))
{
$this->indexEs($data);
}
else
{
\Log::error('ElasticSearchHelper: No context set for the following dataset: '.json_encode($data));
}
}
$job->delete();
}
/*
* Laravel Queue - Update Task
* #param array $job
* #param array $data
*/
public function updateTask($job,$data)
{
if(\Config::get('website.elasticsearch') === true)
{
if(isset($data['context']))
{
$this->updateEs($data);
}
else
{
\Log::error('ElasticSearchHelper: No context set for the following dataset: '.json_encode($data));
}
}
$job->delete();
}
/*
* Index Elastic Search Document
* #param array $data
*/
public function indexEs($data)
{
$params = array();
$params['index'] = \App::environment();
$params['type'] = $data['context'];
$model = new $data['class'];
$form = $model::find($data['id']);
if($form)
{
$params['id'] = $form->id;
if($form->timestamps)
{
$params['timestamp'] = $form->updated_at->toIso8601String();
}
$params['body'][$data['context']] = $this->saveFormat($form);
\Es::index($params);
}
}
/*
* Update Elastic Search
* #param array $data
*/
public function updateEs($data)
{
$params = array();
$params['index'] = \App::environment();
$params['type'] = $data['context'];
$model = new $data['class'];
$form = $model::withTrashed()->find($data['id']);
if(count($form))
{
/*
* Main form is being updated
*/
if($data['info-context'] === $data['context'])
{
$params['id'] = $data['id'];
$params['body']['doc'][$data['info-context']] = $this->saveFormat($form);
}
else
{
//Form is child, we get parent
$parent = $form->esGetParent();
if(count($parent))
{
//Id is always that of parent
$params['id'] = $parent->id;
//fetch all children, given that we cannot save per children basis
$children = $parent->{$data['info-context']}()->get();
if(count($children))
{
//Get data in a format that can be saved by Elastic Search
$params['body']['doc'][$data['info-context']] = $this->saveFormat($children);
}
else
{
//Empty it is
$params['body']['doc'][$data['info-context']] = array();
}
}
else
{
\Log::error("Parent not found for {$data['context']} - {$data['class']}, Id: {$data['id']}");
return false;
}
}
//Check if Parent Exists
try
{
$result = \Es::get([
'id' => $params['id'],
'index' => $params['index'],
'type' => $data['context']
]);
} catch (\Exception $ex) {
if($ex instanceof \Elasticsearch\Common\Exceptions\Missing404Exception || $ex instanceof \Guzzle\Http\Exception\ClientErrorResponseException)
{
//if not, we set it
if (isset($parent) && $parent)
{
$this->indexEs([
'context' => $data['context'],
'class' => get_class($parent),
'id' => $parent->id,
]);
}
else
{
\Log::error('Unexpected error in updating elasticsearch records, parent not set with message: '.$ex->getMessage());
return false;
}
}
else
{
\Log::error('Unexpected error in updating elasticsearch records: '.$ex->getMessage());
return false;
}
}
\Es::update($params);
}
}
/*
* Iterate through all Es accessors of the model.
* #param \Illuminate\Database\Eloquent\Model $object
*/
public function esAccessor(&$object)
{
if(is_object($object))
{
$attributes = $object->getAttributes();
foreach($attributes as $name => $value)
{
$esMutator = 'get' . studly_case($name) . 'EsAttribute';
if (method_exists($object, $esMutator)) {
$object->{$name} = $object->$esMutator($object->{$name});
}
}
}
else
{
throw New \RuntimeException("Expected type object");
}
}
/*
* Iterates over a collection applying the getEsSaveFormat function
* #param mixed $object
*
* #return array
*/
public function saveFormat($object)
{
if($object instanceof \Illuminate\Database\Eloquent\Model)
{
return $object->getEsSaveFormat();
}
else
{
return array_map(function($value)
{
return $value->getEsSaveFormat();
}, $object->all());
}
}
}
A couple of gotchas from the above helper classes:
The default ElasticSearch index is set to the name of the App's Environment
The ..task() functions are meant for the old laravel 4.2 queue format. I've yet to port those to laravel 5.x. Same goes for the Queue::push commands.
Example
ElasticSearch Mapping:
[
'automobile' => [
"dynamic" => "strict",
'properties' => [
'automobile' => [
'properties' => [
'id' => [
'type' => 'long',
'index' => 'not_analyzed'
],
'manufacturer_name' => [
'type' => 'string',
],
'manufactured_on' => [
'type' => 'date'
]
]
],
'car' => [
'properties' => [
'id' => [
'type' => 'long',
'index' => 'not_analyzed'
],
'name' => [
'type' => 'string',
],
'model_id' => [
'type' => 'string'
]
]
],
"car-model" => [
'properties' => [
'id' => [
'type' => 'long',
'index' => 'not_analyzed'
],
'description' => [
'type' => 'string',
],
'name' => [
'type' => 'string'
]
]
]
]
]
]
Top level document is called 'automobile'. Underneath it, you have 'automobile', 'car' & 'car-model'. Consider 'car' & 'car-model' as relations to the automobile. They are known as sub documents on elasticsearch. (See: https://www.elastic.co/guide/en/elasticsearch/guide/current/document.html)
Model: App\Models\Car.php
namespace App\Models;
class Car extends \Eloquent {
use \Illuminate\Database\Eloquent\SoftDeletingTrait;
use \App\Traits\ElasticSearchEventTrait;
protected $table = 'car';
protected $fillable = [
'name',
'serie',
'model_id',
'automobile_id'
];
protected $dates = [
'deleted_at'
];
/* Elastic Search */
//Indexing Enabled
public $esEnabled = true;
//Context for Indexing - Top Level name in the mapping
public $esContext = "automobile";
//Info Context - Secondary level name in the mapping.
public $esInfoContext = "car";
//The following fields will not be saved in elasticsearch.
public $esRemove = ['automobile_id'];
//Fetches parent relation of car, so that we can retrieve its id for saving in the appropriate elasticsearch record
public function esGetParent()
{
return $this->automobile;
}
/*
* Event Observers
*/
public static function boot() {
parent:: boot();
//Attach events to model on start
static::bootElasticSearchEvent();
}
/*
* ElasticSearch Accessor
*
* Sometimes you might wish to format the data before storing it in elasticsearch,
* The accessor name is in the format of: get + attribute's name camel case + EsAttribute
* The $val parameter will always be the value of the attribute that is being accessed.
*
* #param mixed $val
*/
/*
* Elasticsearch Accessor: Model Id
*
* Get the model name and save it
*
* #param int $model_id
* #return string
*/
public function getModelIdEsAttribute($model_id) {
//Fetch model from table
$model = \App\Models\CarModel::find($model_id);
if($model) {
//Return name of model if found
return $model->name;
} else {
return '';
}
}
/*
* Automobile Relationship: Belongs To
*/
public function automobile()
{
return $this->belongsTo('\App\Models\Automobile','automobile_id');
}
}
Example of Search Query:
/**
* Get search results
*
* #param string $search (Search string)
*
*/
public function getAll($search)
{
$params = array();
$params['index'] = App::environment();
//Declare your mapping names in the array which you wish to search on.
$params['type'] = array('automobile');
/*
* Build Query String
*/
//Exact match is favored instead of fuzzy ones
$params['body']['query']['bool']['should'][0]['match']['name']['query'] = $search;
$params['body']['query']['bool']['should'][0]['match']['name']['operator'] = "and";
$params['body']['query']['bool']['should'][0]['match']['name']['boost'] = 2;
$params['body']['query']['bool']['should'][1]['fuzzy_like_this']['like_text'] = $search;
$params['body']['query']['bool']['should'][1]['fuzzy_like_this']['fuzziness'] = 0.5;
$params['body']['query']['bool']['should'][1]['fuzzy_like_this']['prefix_length'] = 2;
$params['body']['query']['bool']['minimum_should_match'] = 1;
//Highlight matches
$params['body']['highlight']['fields']['*'] = new \stdClass();
$params['body']['highlight']['pre_tags'] = array('<b>');
$params['body']['highlight']['post_tags'] = array('</b>');
//Exclude laravel timestamps
$params['body']['_source']['exclude'] = array( "*.created_at","*.updated_at","*.deleted_at");
/*
* Poll search server until we have some results
*/
$from_offset = 0;
$result = array();
//Loop through all the search results
do
{
try
{
$params['body']['from'] = $from_offset;
$params['body']['size'] = 5;
$queryResponse = \Es::search($params);
//Custom function to process the result
//Since we will receive a bunch of arrays, we need to reformat the data and display it properly.
$result = $this->processSearchResult($queryResponse);
$from_offset+= 5;
}
catch (\Exception $e)
{
\Log::error($e->getMessage());
return Response::make("An error occured with the search server.",500);
}
}
while (count($result) === 0 && $queryResponse['hits']['total'] > 0);
echo json_encode($result);
}
/*
* Format search results as necessary
* #param array $queryResponse
*/
private function processSearchResult(array $queryResponse)
{
$result = array();
//Check if we have results in the array
if($queryResponse['hits']['total'] > 0 && $queryResponse['timed_out'] === false)
{
//Loop through each result
foreach($queryResponse['hits']['hits'] as $line)
{
//Elasticsearch will highlight the relevant sections in your query in an array. The below creates a readable format with · as delimiter.
$highlight = "";
if(isset($line['highlight']))
{
foreach($line['highlight'] as $k=>$v)
{
foreach($v as $val)
{
$highlight[] = str_replace("_"," ",implode(" - ",explode(".",$k)))." : ".$val;
}
}
$highlight = implode(" · ",$highlight);
}
//Check the mapping type
switch($line['_type'])
{
case "automobile":
$result[] = array('icon'=>'fa-automobile',
'title'=> 'Automobile',
'id' => $line['_id'],
//name to be displayed on my search result page
'value'=>$line['_source'][$line['_type']]['name']." (Code: ".$line['_id'].")",
//Using a helper to generate the url. Build your own class.
'url'=>\App\Helpers\URLGenerator::generate($line['_type'],$line['_id']),
//And the highlights as formatted above.
'highlight'=>$highlight);
break;
}
}
}
return $result;
}
Hi I am using a SAutoComplete (extends CAutoComplete) and need to do some work when a value is selected from the list.
i am using it like this.
this->widget('application.components.SAutoComplete', array('width'=>200,
'model'=>$cssAtapsClient, 'parseData'=>true, 'matchContains'=>true,
'attribute'=>'suburb_id', 'data'=>$postCode, 'ddindicator'=>true,
'max'=>50,
'multipleSeparator'=>false,
'options' => array(
'select' => new CJavaScriptExpression('function(e, ui) { alert("hi"); }')
),
)); ?>
i am wondering why there is no select option like available in jquery UI auto completed?
example of a select is as below.
$("#auto_cp").autocomplete({
minLength: 3,
//source
source: function(req, add) {
$.getJSON("friends.php?callback=?", req, function(data) {
var suggestions = [];
$.each(data, function(i, val) {
suggestions.push({
label: val.name,
zzz: val.zzz
});
});
add(suggestions);
});
},
//select
select: function(e, ui) {
alert(ui.item.zzz);
}
});
EDIT 2
http://www.yiiframework.com/doc/api/1.1/CAutoComplete
code is like this,
<?php
class SAutoComplete extends CAutoComplete
{
public $ddindicator;
/**
*
* #var boolean whether to parse the data assumes data uses an assoc array
* array(value => array(name, value, ...), ...). Only works with a model present
*/
public $parseData;
/**
* #var boolean whether to raise the change event
*/
public $raiseChangeEvent = false;
/**
* Initializes the widget.
* This method registers all needed client scripts and renders
* the autocomplete input.
*/
public function init()
{
if ( !$this->max )
$this->max = 50000;
if ( $this->ddindicator )
$this->alternateInit();
else
parent::init();
}
public function alternateInit()
{
list($name,$id)=$this->resolveNameID();
$this->htmlOptions['id'] = $id.'_input';
$this->minChars = 0;
echo CHtml::openTag('div', array('class'=>'ac-input-dd'));
echo CHtml::openTag('div', array('class'=>'ac-input-btn'));
echo CHtml::closeTag('div');
if($this->hasModel())
{
$htmlOpt = array();
if ( $this->parseData )
{
$menu = $this->data;
$key = $this->attribute;
//Change if attribute is apart of a array. eg attribute[0]
$pos1 = stripos($key, '[');
$pos2 = stripos($key, ']');
if($pos1!==false && $pos2!==false)
{
$key = str_replace (substr($key,$pos1,$pos2 - $pos1 + 1),'',$key);
$htmlOpt['value'] = isset($this->model->$key) ? $this->model->$key : '';
}
$this->value = isset($menu[$this->model->$key][0]) ? $menu[$this->model->$key][0] : '';
$this->data = is_array($menu) ? array_values($menu) : array('Error in data.');
}
echo CHtml::activeHiddenField($this->model, $this->attribute, array_merge(array('id'=>$id, 'name'=>$name), $htmlOpt));
echo CHtml::textField('', $this->value, $this->htmlOptions);
}
else
{
echo CHtml::hiddenField($name, $this->value, array('id'=>$id));
echo CHtml::textField($name.'_input',$this->value,$this->htmlOptions);
}
echo CHtml::closeTag('div');
$this->methodChain = $this->methodChain.'.result(function(evt, data, formatted) { $("#'.
$id.'").val(data ? data[1] : "")'.($this->raiseChangeEvent?'.change()':'').'; })'.
'.parent().find(".ac-input-btn").mousedown(function(){'.
'jQuery(this).parent().find(".ac_input").toggleResults();})'.
'.mouseup(function(){jQuery(this).parent().find(".ac_input").focus();});';
$this->registerClientScript();
}
public static function registerScript()
{
$cs = Yii::app()->getClientScript();
$cs->registerCoreScript('jquery');
$cs->registerCoreScript('bgiframe');
TK::registerScriptFile('autocomplete');
$cs->registerCssFile($cs->getCoreScriptUrl().'/autocomplete/jquery.autocomplete.css');
}
/**
* Registers the needed CSS and JavaScript.
* #since 1.0.1
*/
public function registerClientScript()
{
// can cut this down once YII releases a fix for defect #38
if ( Yii::app()->request->isAjaxRequest || $this->ddindicator )
{
$id=$this->htmlOptions['id'];
$acOptions=$this->getClientOptions();
$options=$acOptions===array()?'{}' : CJavaScript::encode($acOptions);
$cs=Yii::app()->getClientScript();
if($this->data!==null)
$data=CJavaScript::encode($this->data);
else
{
$url=CHtml::normalizeUrl($this->url);
$data='"'.$url.'"';
}
if ( Yii::app()->request->isAjaxRequest )
{
echo '<script type="text/javascript">jQuery(document).ready('.
'function() {jQuery("#'.$id.'").autocomplete('.$data.','.$options.')'.
$this->methodChain.';});</script>';
}
else
{
SAutoComplete::registerScript();
$cs->registerScript('Yii.CAutoComplete#'.$id,"jQuery(\"#{$id}\").autocomplete($data,{$options}){$this->methodChain};");
}
}
else
parent::registerClientScript();
}
}
You can pass any option that the JUI autocomplete widget supports by including an options array:
$this->widget('zii.widgets.jui.CJuiAutoComplete', array(
// your other settings here
'options' => array(
'select' => new CJavaScriptExpression('function(e, ui) { alert("hi"); }')
),
));
If the options you want to pass include JavaScript code then you also have to wrap that inside a CJavaScriptExpression as above.
Try to add this,
'methodChain'=>".result(function(event,item){ urFunction(); })",
Have a nice day!
So at last it will look like,
$this->widget('application.components.SAutoComplete', array('width'=>200,
'model'=>$cssAtapsClient, 'parseData'=>true, 'matchContains'=>true,
'attribute'=>'suburb_id', 'data'=>$postCode, 'ddindicator'=>true,
'max'=>50,
'methodChain'=>".result(function(event,item){ urFucntion(); })",
));
I'm using CAutoComplete as the followin code:
In post/_form.php
<?php $this->widget('CAutoComplete', array(
'model' => $model,
'attribute' => 'tags',
'url' => array('suggestTags'),
'multiple' => true,
'htmlOptions' => array(
'size' => 50,
'class' => 'span11'
),
)); ?>
In PostController.php
Add one more action call: suggestTags
/**
* Suggests tags based on the current user input.
* This is called via AJAX when the user is entering the tags input.
*/
public function actionSuggestTags()
{
if(isset($_GET['q']) && ($keyword=trim($_GET['q']))!=='')
{
$tags=Tag::model()->suggestTags($keyword);
if($tags!==array())
echo implode("\n",$tags);
}
}
In Post.php model
add private $_oldTags; to the top of class (under class name).
add these functions:
/**
* Normalizes the user-entered tags.
*/
public function normalizeTags($attribute, $params)
{
$this->tags = Post::array2string(array_unique(Post::string2array($this->tags)));
}
public static function string2array($tags)
{
return preg_split('/\s*,\s*/', trim($tags), -1, PREG_SPLIT_NO_EMPTY);
}
public static function array2string($tags)
{
return implode(', ', $tags);
}
See more Yii tutorials here