SECURITY TOKEN in first Prestashop 1.7 module - php

I have tryed now for a few day´s to create a module.
Its going to be a easy one, that add some variable to different table, and show them in a list when you select from a dropdown.
Im only still in admin, the front do i create later,
I follow a tutorials that was easy to follow.
First copy the sql to the db.
CREATE TABLE pasta (
`id` INT NOT NULL AUTO_INCREMENT,
`sku` VARCHAR(255) NOT NULL,
`name` VARCHAR(255) NOT NULL,
`description` TEXT,
`id_pasta_category` INT NOT NULL,
`created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE = InnoDB;
Then i copy the ObjectModel class /override/classes/fc_pasta/Pasta.php
<?php
class Pasta extends ObjectModel {
public $id; // fields are mandatory for create/update
public $sku;
public $name;
public $created;
public $category;
public $id_pasta_category;
public static $definition = [
'table' => 'pasta',
'primary' => 'id',
'fields' => [
'sku' => ['type' => self::TYPE_STRING, 'validate' => 'isAnything', 'required'=>true],
'name' => ['type' => self::TYPE_STRING, 'validate' => 'isAnything', 'required'=>true],
'description' => ['type' => self::TYPE_HTML, 'validate' => 'isAnything',],
'created' => ['type' => self::TYPE_DATE, 'validate' => 'isDateFormat'],
'id_pasta_category' => ['type'=>self::TYPE_INT, 'validate'=>'isUnsignedInt','required'=>true,],
],
];
}
And after that i copy the module /modules/fc_pasta/fc_pasta.php
<?php
if (!defined('_PS_VERSION_')) {exit;}
class Fc_Pasta extends Module {
public function __construct() {
$this->name = 'fc_pasta'; // must match folder & file name
$this->tab = 'administration';
$this->version = '1.0.0';
$this->author = 'Florian Courgey';
$this->bootstrap = true; // use Bootstrap CSS
parent::__construct();
$this->displayName = $this->l('PrestaShop Module by FC');
$this->description = $this->l('Improve your store by [...]');
$this->ps_versions_compliancy = ['min' => '1.7', 'max' => _PS_VERSION_];
// install Tab to register AdminController in the database
$tab = new Tab();
$tab->class_name = 'AdminPasta';
$tab->module = $this->name;
$tab->id_parent = (int)Tab::getIdFromClassName('DEFAULT');
$tab->icon = 'settings_applications';
$languages = Language::getLanguages();
foreach ($languages as $lang) {
$tab->name[$lang['id_lang']] = $this->l('FC Pasta Admin controller');
}
$tab->save();
}
}
And after that i create the AdminPastaController
<?php
require_once _PS_ROOT_DIR_.'/override/classes/fc_pasta/Pasta.php';
class AdminPastaController extends ModuleAdminController {
public function __construct(){
parent::__construct();
// Base
$this->bootstrap = true; // use Bootstrap CSS
$this->table = 'pasta'; // SQL table name, will be prefixed with _DB_PREFIX_
$this->identifier = 'id'; // SQL column to be used as primary key
$this->className = 'Pasta'; // PHP class name
$this->allow_export = true; // allow export in CSV, XLS..
// List records
$this->_defaultOrderBy = 'a.sku'; // the table alias is always `a`
$this->_defaultOrderWay = 'ASC';
$this->_select = 'a.name as `pastaName`, cl.name as `categoryName`';
$this->_join = '
LEFT JOIN `'._DB_PREFIX_.'category` cat ON (cat.id_category=a.id_pasta_category)
LEFT JOIN `'._DB_PREFIX_.'category_lang` cl ON (cat.id_category=cl.id_category and cat.id_shop_default=cl.id_shop)';
$this->fields_list = [
'id' => ['title' => 'ID','class' => 'fixed-width-xs'],
'sku' => ['title' => 'SKU'],
'pastaName' => ['title' => 'Name', 'filter_key'=>'a!name'], // filter_key mandatory because "name" is ambiguous for SQL
'categoryName' => ['title' => 'Category', 'filter_key'=>'cl!name'], // filter_key mandatory because JOIN
'created' => ['title' => 'Created','type'=>'datetime'],
];
// Read & update record
$this->addRowAction('details');
$this->addRowAction('edit');
$categories = Category::getCategories($this->context->language->id, $active=true, $order=false); // [0=>[id_category=>X,name=>Y]..]
$categories = [['id'=>1, 'display'=> 'abc'], ['id'=>2, 'display'=>'def']];
$this->fields_form = [
'legend' => [
'title' => 'Pasta',
'icon' => 'icon-list-ul'
],
'input' => [
['type'=>'html','html_content'=>'<div class="alert alert-info">Put here any info content</div>'],
['name'=>'id_xxx','label'=>'XXX','type'=>'select',
'options'=>[ 'query'=>$categories,
'id'=>'id', // use the key id as the <option> value
'name'=> 'display', // use the key display as the <option> title
]
],
['name'=>'name','type'=>'text','label'=>'Name','required'=>true],
['name'=>'description','type'=>'textarea','label'=>'Description',],
['name'=>'created','type'=>'datetime','label'=>'Created',],
['name'=>'id_pasta_category','label'=>'Category','type'=>'select','required'=>true,'class'=>'select2',
'options'=>[ 'query'=>$categories,
'id'=>'id_category', // use the key "id_category" as the <option> value
'name'=> 'name', // use the key "name" as the <option> title
]],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
]
];
}
protected function getFromClause() {
return str_replace(_DB_PREFIX_, '', parent::getFromClause());
}
}
Everything almost ok, but everytime i update the page it create a new menu all the time:
FC Pasta Admin controller, instead of 1 i have 25 now.
That I can´t find why.
And one more thing, everything i do in the module i get INVALID SECURITY TOKEN
Im really new to create modules to PS 1.7, but i really whant to try.

You are creating and saving a new backoffice tab in the module constructor,
so a new tab will be created everytime the module object is instantiated.
You'll need to move the tab creation login in the install() method of the module, so that your tab will be created only once during the very first module installation.

Related

Module lang not working in Prestashop 1.7.6

Just trying to create a new simple module with translation but the saving process is not working. The table 'ps_myoptions_lang' is updating with the field id_myoptions = 0 in each lang_id and nothing is saved in 'ps_myoptions'.
modules/myoptions/controllers/admin/AdminOptionsController.php
<?php
require_once(dirname(__FILE__) . './../../classes/Option.php');
class AdminOptionsController extends AdminController
{
public function __construct(){
parent::__construct();
$this->bootstrap = true; // use Bootstrap CSS
$this->table = 'myoptions'; // SQL table name, will be prefixed with _DB_PREFIX_
$this->lang = true;
$this->identifier = 'id_myoptions'; // SQL column to be used as primary key
$this->className = 'Option'; // PHP class name
$this->allow_export = true; // allow export in CSV, XLS..
$this->_defaultOrderBy = 'a.name'; // the table alias is always `a`
$this->_defaultOrderWay = 'DESC';
$this->fields_list = [
'id_myoptions' => ['title' => 'ID','class' => 'fixed-width-xs'],
'name' => ['title' => 'Name'],
];
$this->addRowAction('edit');
$this->addRowAction('details');
$this->fields_form = [
'legend' => [
'title' => 'Pasta',
'icon' => 'icon-list-ul'
],
'input' => [
['name'=>'name','type'=>'text', 'lang' => true, 'label'=>'Name','required'=>true],
],
'submit' => [
'title' => $this->trans('Save', [], 'Admin.Actions'),
]
];
}
}
modules/myoptions/classes/Options.php
<?php
class Option extends ObjectModel
{
public $id;
public $name;
public static $definition = [
'table' => 'myoptions',
'primary' => 'id_myoptions',
'multilang' => true,
'fields' => [
'name' => ['type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isAnything', 'required'=>true],
],
];
}
modules/myoptions/myoptions.php
if (!defined('_PS_VERSION_')) {
exit;
}
class Myoptions extends Module
{
protected $config_form = false;
public function __construct()
{
$this->name = 'myoptions';
$this->tab = 'administration';
$this->version = '1.0.0';
$this->author = 'abc';
$this->need_instance = 1;
/**
* 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 Options');
$this->description = $this->l('Add additional options');
$this->ps_versions_compliancy = array('min' => '1.7', '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()
{
Configuration::updateValue('MYOPTIONS_LIVE_MODE', false);
include(dirname(__FILE__).'/sql/install.php');
return parent::install() &&
$this->registerHook('header') &&
$this->registerHook('backOfficeHeader') &&
$this->installTabs();
}
public function uninstall()
{
Configuration::deleteByName('MYOPTIONS_LIVE_MODE');
include(dirname(__FILE__).'/sql/uninstall.php');
return parent::uninstall();
}
public function enable($force_all = false)
{
return parent::enable($force_all)
&& $this->installTabs()
;
}
public function disable($force_all = false)
{
return parent::disable($force_all)
&& $this->uninstallTabs()
;
}
/**
* Since PS 1.7.1
* #var array
*/
public $tabs = array(
array(
'name' => array(
'en' => 'Options', // Default value should be first
'fr' => 'Options',
),
'class_name' => 'AdminOptions',
'visible' => true,
'parent_class_name' => 'AdminParentThemes',
),
);
public function installTabs()
{
$moduleName = $this->name;
$this->addTab('AdminOptions', 'Options', $moduleName, 'AdminTools');
return true;
}
public function addTab($className, $tabName, $moduleName, $parentClassName)
{
$tab = new Tab();
$tab->active = 1;
$tab->class_name = $className;
$tab->name = array();
foreach (Language::getLanguages(true) as $lang) {
$tab->name[$lang['id_lang']] = $tabName;
}
$tab->id_parent = (int) Tab::getIdFromClassName($parentClassName);
$tab->module = $moduleName;
$tab->add();
return $tab;
}
}
Is there something that I'm doing wrong?
Thanks for your answer.
Prestashop 1.7.6
PHP 7.2.25
I think I found the problem :
$sqlCreate = "CREATE TABLE `" . _DB_PREFIX_ . "myoptions` (
`id_myoptions` int(11) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id_myoptions`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;";
$sqlCreateLang = "CREATE TABLE `" . _DB_PREFIX_ . "myoptions_lang` (
`id_myoptions` int(11) unsigned NOT NULL AUTO_INCREMENT,
`id_lang` int(11) NOT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id_myoptions`,`id_lang`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;";
Db::getInstance()->execute($sqlCreate) && Db::getInstance()->execute($sqlCreateLang);
I have the same field name in both tables. I then use it only in Lang Table and it seems to work now.

Create a custom resource webservice PrestaShop

I create a module for PrestaShop 1.6 where I create a table as following in mymodule/mymodule.php:
class Mymodule extends Module {
// Some code
public function installDb() {
return Db::getInstance()->execute("
CREATE TABLE IF NOT EXISTS `" . _DB_PREFIX_ . "mytable`(
`id_mdm` INT NOT NULL AUTO_INCREMENT,
`id_category` INT NOT NULL,
`service` INT NOT NULL,
`title` VARCHAR(300) NOT NULL default '',
`title_font_size` VARCHAR(128) NOT NULL default '',
`title_color` VARCHAR(128) NOT NULL default '',
`background_color` VARCHAR(128) NOT NULL default '',
`border_style` VARCHAR(128) NOT NULL default '',
`position` INT NOT NULL,
`count` INT NOT NULL,
PRIMARY KEY (`id_mdm`), UNIQUE (`id_category`)) ENGINE = InnoDB;");
}
// Some code
}
It works fine, my table is created. Then I override webservice in mymodule/override/classes/webservice/WebserviceRequest.php:
class WebserviceRequest extends WebserviceRequestCore {
public static function getResources() {
$resources = parent::getResources();
$resources['myresource'] = array(
'description' => '',
'class' => 'myresource'
);
ksort($resources);
return $resources;
}
}
I create a new class called myresource in mymodule/override/classes/Myresource.php:
class MyresourceCore extends ObjectModel {
public $id;
public $id_mdm;
public $id_category;
public $service;
public $title;
public $title_font_size;
public $title_color;
public $background_color;
public $border_style;
public $position;
public $count;
public static $definition = array(
'table' => 'mytable',
'primary' => 'id_mdm',
'fields' => array(
'id_category' => array('type' => self::TYPE_INT),
'service' => array('type' => self::TYPE_INT),
'title' => array('type' => self::TYPE_STRING),
'title_font_size' => array('type' => self::TYPE_STRING),
'title_color' => array('type' => self::TYPE_STRING),
'background_color' => array('type' => self::TYPE_STRING),
'border_style' => array('type' => self::TYPE_STRING),
'position' => array('type' => self::TYPE_INT),
'count' => array('type' => self::TYPE_INT)
)
);
protected $webserviceParameters = array();
}
In the Back office I generate a key for myresource, but when I test in my browser http://mydomain/api/myresource?ws_key=mykey, there is the following error:
Fatal error: Class 'myresource' not found in /path/mydomain/classes/webservice/WebserviceRequest.php on line 502
I don't know why PrestaShop doesn't detect it. Thank you in advance for your assistance.
In Prestashop 1.7, you can use this hook: addWebserviceResources
Example:
include_once dirname(__FILE__) . '/classes/Sample.php';
class myAPISample extends Module {
// ...
public function install() {
return parent::install() && $this->registerHook('addWebserviceResources');
}
// ...
public function hookAddWebserviceResources($params) {
return [ 'samples' => ['description' => 'My sample', 'class' => 'Sample' ] ];
}
//...
}
See also (in french) : https://www.h-hennes.fr/blog/2018/06/25/prestashop-ajouter-un-objet-dans-lapi/
If you check the PHP error logs, you will notice an error of the type Class not found. In this case it's class "MyResource" not found.
In order to solve this, you need to include your Model class in the constructor of the override method like this
class WebserviceRequest extends WebserviceRequestCore {
public function __construct()
{
include_once(_PS_MODULE_DIR_ . 'myresource' . DIRECTORY_SEPARATOR . 'classes' . DIRECTORY_SEPARATOR . 'MyResource.php');
}
public static function getResources()
{
$resources = parent::getResources();
$resources['myresource'] = array(
'description' => '',
'class' => 'myresource'
);
ksort($resources);
return $resources;
}
}
And you need to place the Model Class in /mymodule/classes/MyResource.php
Placing the Model Class in mymodule/override/classes/Myresource.php is not correct cause there is no Myresource class to override. This will give you an error when uninstalling the module - you will not be able to uninstall it
Finally I found an alternative solution without using the native PrestaShop webservice. I created a directory called webservice in mymodule/webservice/mymodule.php. This file will be used to post data to PrestaShop's website. Here is how I did it:
<?php
$currentDirectory = str_replace('modules/mymodule/webservice/', '',
dirname($_SERVER['SCRIPT_FILENAME']) . "/");
$sep = DIRECTORY_SEPARATOR;
require_once $currentDirectory . 'config' . $sep . 'config.inc.php';
require_once $currentDirectory . 'init.php';
$hostnameIp = $_SERVER['REMOTE_ADDR'];
if ($hostnameIp == 'AUTHORIZED_IP') {
if ($_SERVER['REQUEST_METHOD'] == 'POST') {
// Some code
http_response_code(200);
} else {
http_response_code(405);
}
} else {
http_response_code(403);
}
Then I just need to execute a POST request to myresource at the following url: http://mydomain/modules/mymodule/webservice/mymodule.php.
Be careful to do some validation for security, like IP address. If the validation is successful, then your do some treatment to insert or update data to mysql tables.

SilverStripe 3.4: How to add default records to db from model

Unable to locate in the SilverStripe Documentation how to have a DataObject Model inject a collection of default records on /dev/build
Anybody able to point me in the right direction
This is what I currently have, and obviously I would like to inject pre-configured options into this aptly named Configuration model for my Module.
class Configuration extends DataObject
{
private static $db = array(
'Option' => 'Varchar',
'Value' => 'Varchar'
);
private static $summary_fields = array(
'Option' => 'Option',
'Value' => 'Value',
);
}
Thanks in advance for any direction/pointers.
UPDATE
I was turned onto SiteConfig by #Barry below
However in following his practice, requireDefaultRecords() is not injecting defaults
Note: I have since revisited /dev/build?flush
class RMSConfiguration extends DataExtension
{
private static $db = array(
'username' => 'Varchar',
'password' => 'Varchar',
'agent_id' => 'Varchar(15)',
'client_id' => 'Varchar(15)',
'testMode' => 'Int(1)',
'timezone' => 'Varchar',
'apiUrl' => 'Varchar(255)'
);
public function updateCMSFields(FieldList $fields)
{
$fields->addFieldsToTab(
"Root.RMSConfig",
array(
TextField::create('username', 'RMS Username'),
TextField::create('password', 'RMS Password'),
TextField::create('agent_id', 'RMS Agent ID'),
TextField::create('client_id', 'RMS Client ID'),
TextField::create('apiUrl', 'API Url'),
CheckboxField::create("testMode", 'Toggle Test Mode'),
DropdownField::create("timezone", 'Timezone', static::$timezones)
)
);
}
public function requireDefaultRecords()
{
parent::requireDefaultRecords();
$arrOptions = array(
'timezone' => 'Australia/Sydney',
'apiUrl' => 'https://api.example.com.au/',
'testMode' => 0
);
foreach ($arrOptions as $strOption => $strValue) {
if (!$configuration = self::get()->filter('Option', $strOption)->first()) {
$configuration = self::create(array( 'Option' => $strOption ));
}
$configuration->Value = $strValue;
$configuration->write();
}
}
/**
* List of timezones supported by PHP >=5.3.x
*
* #var array
*/
public static $timezones = array(
"Africa/Abidjan",
"Africa/Accra",
"Africa/Addis_Ababa",
"Africa/Algiers",
...
...
"Zulu"
);
}
Using the function requireDefaultRecords in the DataObject - this is called during every dev/build.
Note: First check if the option exists to prevent duplicates as this will be called every time you dev build.
class Configuration extends DataObject {
private static $db = array(
'Option' => 'Varchar',
'Value' => 'Varchar'
);
private static $summary_fields = array(
'Option' => 'Option',
'Value' => 'Value',
);
function requireDefaultRecords() {
parent::requireDefaultRecords();
$arrOptions = array(
'Option1' => 'Value1',
'Option2' => 'Value2',
'Option3' => 'Value3',
);
foreach ($arrOptions as $strOption => $strValue) {
if (!$configuration = Configuration::get()->filter('Option',$strOption)->first())
$configuration = Configuration::create(array('Option' => $strOption));
$configuration->Value = $strValue;
$configuration->write();
}
}
}
One final comment is that there is a module for SiteConfig which is used by SilverStripe, most modules and where I would recommend you put configuration values like this instead.
If you do choose SiteConfig then please see the function populateDefaults and documentation for it's use, this is an example...
/**
* Sets the Date field to the current date.
*/
public function populateDefaults() {
$this->Date = date('Y-m-d');
parent::populateDefaults();
}
(if the above is used in an extensions it might need $this->owner->Date instead of $this->Date)
The above function isn't needed if all the values are static, instead it will read them just from this array (again within DataObject)
public static $defaults = array(
'Option1' => 'Value1',
'Option2' => 'Value2'
);
This works on any DataObject as well, but as SiteConfig manages one record and this populates that record once upon creation this is much more convenient for to use instead of requireDefaultRecords.

How to retrieve the model name within the table class in symfony 1.4?

I am inside a ModelTable and I need the Model name. For example: in case of EventTable I need to know the model it instantiates - Event.
Internally the following function already instantiates an correct Model:
class EventTable extends Doctrine_Table
{
public function findBySomething($something)
{
// Will return a Event
return $this->createQuery('s')->fetchOne();
}
}
What I would like to be able to do:
class EventTable extends Doctrine_Table
{
public function findBySomething($something)
{
$modelName = $this->getModelName();
echo "I will create a ".$modelName; // Will display Event
return $this->createQuery('s')->fetchOne();
}
}
How do I retrieve the model name from inside a table?
You have an array of options available for each Table (Doctrine/Table.php):
protected $_options = array(
'name' => null,
'tableName' => null,
'sequenceName' => null,
'inheritanceMap' => array(),
'enumMap' => array(),
'type' => null,
'charset' => null,
'collate' => null,
'treeImpl' => null,
'treeOptions' => array(),
'indexes' => array(),
'parents' => array(),
'joinedParents' => array(),
'queryParts' => array(),
'versioning' => null,
'subclasses' => array(),
);
So you can retrieve the model name using:
$this->getOption('name');

SQL join with Kohana ORM

I'm using Kohana 3.0.6 with ORM.
I have a model named "truck" and in his table there's a column with the id of his maker ("maker"). Then I have the "maker" model with the id and the name in his table.
I'm trying to do a simple LEFT-JOIN when I display the listing of the trucks so I can get directly the names of their maker.
Here is my "truck" model:
<?php defined('SYSPATH') or die('No direct access allowed.');
class Model_Truck extends ORM {
// Database settings
protected $_db = 'default';
protected $_table_name = 'trucks';
protected $_primary_key = 'id';
//Tried adding this but doesn't seems to work
protected $_has_one = array('maker' => array('model' => 'maker') );
// Table fields
protected $_table_columns = array(
'id' => array('data_type' => 'int', 'is_nullable' => FALSE),
'serial' => array('data_type' => 'string', 'is_nullable' => FALSE),
'maker' => array('data_type' => 'string', 'is_nullable' => FALSE),
'model' => array('data_type' => 'string', 'is_nullable' => FALSE),
'year' => array('data_type' => 'int', 'is_nullable' => FALSE),
);
}
As you can see, I'm using this line to add the "has_one", though I've also seen the "with()" call somewhere but couldn't make it work proprely (doc is a bit lacking, especially for version 3.x.x).
protected $_has_one = array('maker' => array('model' => 'maker') );
Here's the line I'm using in the view to output the maker name (something along those lines):
foreach ($trucks as $t) {
echo($t->maker->name);
}
Do you have the column named truck_id in the makers table?

Categories