Magento 2 adminhtml multiselect and showing selected options after save - multi-select

How do I get the Magento2 Adminhtml Form with a Multiselect to select the saved options?
Here is my Adminhtml Form Class for reference.
namespace RussellAlbin\Blog\Block\Adminhtml\Post\Edit;
/**
* Adminhtml blog post edit form
*/
class Form extends \Magento\Backend\Block\Widget\Form\Generic
{
/**
* #var \RussellAlbin\Blog\Model\Category\Source\ListCategories
*/
protected $_categories;
/**
* #var \RussellAlbin\Blog\Model\Postcategory
*/
protected $_postcategory;
/**
* #var \Magento\Store\Model\System\Store
*/
protected $_systemStore;
/**
* #param \Magento\Backend\Block\Template\Context $context
* #param \Magento\Framework\Registry $registry
* #param \Magento\Framework\Data\FormFactory $formFactory
* #param \Magento\Store\Model\System\Store $systemStore
* #param \RussellAlbin\Blog\Model\Category\Source\ListCategories $categories
* #param \RussellAlbin\Blog\Model\Postcategory $postcategory
* #param array $data
*/
public function __construct(
\Magento\Backend\Block\Template\Context $context,
\Magento\Framework\Registry $registry,
\Magento\Framework\Data\FormFactory $formFactory,
\Magento\Store\Model\System\Store $systemStore,
\RussellAlbin\Blog\Model\Category\Source\ListCategories $categories,
\RussellAlbin\Blog\Model\Postcategory $postcategory,
array $data = []
) {
$this->_categories = $categories;
$this->_systemStore = $systemStore;
$this->_postcategory = $postcategory;
parent::__construct($context, $registry, $formFactory, $data);
}
/**
* Init form
*
* #return void
*/
protected function _construct()
{
parent::_construct();
$this->setId('blog_post_form');
$this->setTitle(__('Blog Post Information'));
}
/**
* Prepare form
*
* #return $this
*/
protected function _prepareForm()
{
/** #var \RussellAlbin\Blog\Model\Post $model */
$model = $this->_coreRegistry->registry('blog_post');
/** #var \Magento\Framework\Data\Form $form */
$form = $this->_formFactory->create(
['data' => ['id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post']]
);
$form->setHtmlIdPrefix('post_');
$fieldset = $form->addFieldset(
'base_fieldset',
['legend' => __('General Information'), 'class' => 'fieldset-wide']
);
if ($model->getPostId()) {
$fieldset->addField('post_id', 'hidden', ['name' => 'post_id']);
}
// Gather our existing categories
$currentCategories = $this->_getExistingCategories( $model );
// Get all the categories that in the database
$allCategories = $this->_categories->toOptionArray();
$field = $fieldset->addField(
'blog_categories',
'multiselect',
[
'label' => __('Categories'),
'required' => true,
'name' => 'blog_categories',
'values' => $allCategories,
'value' => $currentCategories
]
);
$form->setValues($model->getData());
$form->setUseContainer(true);
$this->setForm($form);
return parent::_prepareForm();
}
/**
* #param $model
* #return array
*/
private function _getExistingCategories( $model )
{
// Get our collection
$existingCategories = $this->_postcategory->getCollection()
->addFieldToSelect('category_id')
->addFieldToFilter('post_id', $model->getId());
// Setup our placeholder for the array of categories needed to set back on the value of the multiselect
$itemList = array();
foreach($existingCategories as $_item)
{
$itemList[] = $_item['category_id'];
}
return $itemList;
}
}
I looked back at some work I have done in Magento 1.x and I the only way I got this to work before was using javascript and the setAfterElementHtml to set the options that matched. I remember hating this method of getting it accomplished because it seemed like a work around.

I found the issue.
I did not have the values saved on the model.
// This is what shows it as selected on reload
$model->setData('blog_categories', $categories);
$fieldset->addField(
'blog_categories',
'multiselect',
[
'name' => 'blog_categories[]',
'label' => __('Categories'),
'title' => __('Categories'),
'required' => true,
'values' => $optionArray,
'disabled' => false
]
);

Related

DebugKit Error: CakePHP get() mystery empty elements in associations

EDIT: This is after upgrading from CakePHP 4.0 to 4.3
With DebugKit turned on, CakePHP is throwing the following error message:
Argument 1 passed to Cake\ORM\Entity::get() must be of the type string, bool given, called in /Users/thomasbelknap/one-vision/vendor/cakephp/cakephp/src/Datasource/EntityTrait.php on line 607
This appears to be as a result of an empty, numerically-indexed element in the return array of a database query. Let me explain:
In my system, a User has many Estimates (and also Carts and Orders, but more on that). These two clauses in the User and Estimate table files bare this out:
$this->belongsTo('Users', [
'foreignKey' => 'user_id',
]);
::snip::
$this->hasMany('Estimates', [
'foreignKey' => 'user_id',
'dependent' => true,
]);
I am querying this information on a "Dashboard" page using the following query structure:
$user = $this->Users->get($this->getRequest()->getSession()->read('Auth.id'), [
'contain' => [
'UserGroups',
'Carts' => function ($q) {
return $q->limit(5);
},
'Orders' => function ($q) {
return $q->limit(5);
},
'Orders.OrderStatuses',
'Estimates' => function ($q) {
return $q->limit(5);
},
'Estimates.EstimateStatuses',
],
]);
I am not doing any further transformation of the data. There are no Collection functions being employed here. Nevertheless, in the estimates returned, there is always a mystery, numerically-indexed and empty array element:
[1] => Visualize\Model\Entity\Estimate Object
(
[id] => 30
[user_id] => 1
[estimate_status_id] => 5
[estimate_po] => lksjdf
[estimate_total] =>
[name] => Full throttle
[instore_date] => Cake\I18n\FrozenDate Object
(
[date] => 2022-12-31 00:00:00.000000
[timezone_type] => 3
[timezone] => EST
)
[notes] => Let's do this.
[created] => Cake\I18n\FrozenTime Object
(
[date] => 2022-04-19 13:20:39.000000
[timezone_type] => 3
[timezone] => EST
)
[modified] => Cake\I18n\FrozenTime Object
(
[date] => 2022-04-19 13:21:26.000000
[timezone_type] => 3
[timezone] => EST
)
[1] =>
[location_info] => Array
(
)
[[new]] =>
[[accessible]] => Array
(
[user_id] => 1
[estimate_status_id] => 1
[estimate_po] => 1
[estimate_total] => 1
[name] => 1
[instore_date] => 1
[notes] => 1
[created] => 1
[modified] => 1
[user] => 1
[line_items] => 1
[orders] => 1
[estimate_options] => 1
)
[[dirty]] => Array
(
)
[[original]] => Array
(
)
[[virtual]] => Array
(
[item_count] => 1
[box_count] => 1
[deliverable_count] => 1
[0] => location_info
[estimate_total] => 1
)
[[hasErrors]] =>
[[errors]] => Array
(
)
[[invalid]] => Array
(
)
[[repository]] => Estimates
)
This only affects the Estimates part of the query! The other two elements do not have this mystery index. Here's what CakePHP ultimately uses as it's query, which again, seems fine:
SELECT
Estimates.id AS Estimates__id,
Estimates.user_id AS Estimates__user_id,
Estimates.estimate_status_id AS Estimates__estimate_status_id,
Estimates.estimate_po AS Estimates__estimate_po,
Estimates.estimate_total AS Estimates__estimate_total,
Estimates.name AS Estimates__name,
Estimates.instore_date AS Estimates__instore_date,
Estimates.notes AS Estimates__notes,
Estimates.created AS Estimates__created,
Estimates.modified AS Estimates__modified,
EstimateStatuses.id AS EstimateStatuses__id,
EstimateStatuses.name AS EstimateStatuses__name,
EstimateStatuses.description AS EstimateStatuses__description,
EstimateStatuses.created AS EstimateStatuses__created,
EstimateStatuses.modified AS EstimateStatuses__modified
FROM
estimates Estimates
LEFT JOIN estimate_statuses EstimateStatuses ON EstimateStatuses.id = Estimates.estimate_status_id
WHERE
Estimates.user_id in (1)
ORDER BY
Estimates.modified DESC
LIMIT
5
For obvious reasons, I really need the DebugKit to work, but I have no idea where this mystery element is coming from. Everything seems right, to me?
Edit: Adding the definition of my estimate entity:
<?php
declare(strict_types=1);
namespace Visualize\Model\Entity;
use Cake\ORM\Entity;
/**
* Estimate Entity
*
* #property int $id
* #property int $user_id
* #property int|null $estimate_status_id
* #property string|null $estimate_po
* #property float|null $estimate_total
* #property string $name
* #property \Cake\I18n\FrozenDate $instore_date
* #property string $notes
* #property \Cake\I18n\FrozenTime $created
* #property \Cake\I18n\FrozenTime $modified
*
* #property \Visualize\Model\Entity\User $user
* #property \Visualize\Model\Entity\LineItem[] $line_items
* #property \Visualize\Model\Entity\Order[] $orders
* #property \Visualize\Model\Entity\EstimateOption[] $estimate_options
* #property float $item_count
* #property int $box_count
* #property array|\Cake\Collection\CollectionInterface|null $by_location
* #property int $deliverable_count
* #property array $location_info
* #property \Visualize\Model\Entity\EstimateStatus|null $estimate_status
* #property array|\Cake\Collection\CollectionInterface|null $by_deliverable
*/
class Estimate extends Entity
{
/**
* Fields that can be mass assigned using newEntity() or patchEntity().
*
* Note that when '*' is set to true, this allows all unspecified fields to
* be mass assigned. For security purposes, it is advised to set '*' to false
* (or remove it), and explicitly make individual fields accessible as needed.
*
* #var array
*/
protected $_accessible = [
'user_id' => true,
'estimate_status_id' => true,
'estimate_po' => true,
'estimate_total' => true,
'name' => true,
'instore_date' => true,
'notes' => true,
'created' => true,
'modified' => true,
'user' => true,
'line_items' => true,
'orders' => true,
'estimate_options' => true,
];
/**
* #var array
*/
protected $_virtual = [
'item_count' => true,
'box_count' => true,
'deliverable_count' => true,
'location_info' => true,
'estimate_total' => true,
];
/**
* Returns a zero-filled version of the cannonical ID
*
* #param int $id The autoincremental ID
* #return string
*/
protected function _getId($id): string
{
return sprintf('%05d', $id);
}
/**
* Returns the sum of quantities of all line items.
*
* #return float
*/
protected function _getItemCount(): float
{
$count = 0;
if (!empty($this->line_items)) {
$collection = new \Cake\Collection\Collection($this->line_items);
$count = $collection->sumOf('quantity');
}
return (float)$count;
}
/**
* Returns the count of locations in the estimate
*
* #return int
*/
protected function _getBoxCount(): int
{
$count = 0;
if (!empty($this->line_items)) {
$collection = new \Cake\Collection\Collection($this->line_items);
$collection = $collection->combine('location_id', 'id');
$count = $collection->count();
}
return $count;
}
/**
* Returns the total number of deliverable types in the order.
*
* #return int
*/
protected function _getDeliverableCount(): int
{
$count = 0;
if (!empty($this->line_items)) {
$collection = new \Cake\Collection\Collection($this->line_items);
$collection = $collection->combine('deliverable_id', 'id');
$count = $collection->count();
}
return $count;
}
/**
* Returns the list of locations for this estimate
*
* #return array
*/
protected function _getLocationInfo(): array
{
$info = [];
if (!empty($this->line_items)) {
$collection = new \Cake\Collection\Collection($this->line_items);
$info = $collection->combine(
'location_id',
function ($entity) {
return $entity->location;
}
);
return $info->toArray();
}
return $info;
}
/**
* Returns either the name or the id of the given estimate
*
* #return string
*/
protected function _getName(): string
{
return $this->name ?? (string)$this->id;
}
public const FIELD_ID = 'id';
public const FIELD_USER_ID = 'user_id';
public const FIELD_ESTIMATE_STATUS_ID = 'estimate_status_id';
public const FIELD_ESTIMATE_PO = 'estimate_po';
public const FIELD_ESTIMATE_TOTAL = 'estimate_total';
public const FIELD_NAME = 'name';
public const FIELD_INSTORE_DATE = 'instore_date';
public const FIELD_NOTES = 'notes';
public const FIELD_CREATED = 'created';
public const FIELD_MODIFIED = 'modified';
public const FIELD_USER = 'user';
public const FIELD_LINE_ITEMS = 'line_items';
public const FIELD_ORDERS = 'orders';
public const FIELD_ESTIMATE_OPTIONS = 'estimate_options';
public const FIELD_ITEM_COUNT = 'item_count';
public const FIELD_BOX_COUNT = 'box_count';
public const FIELD_BY_LOCATION = 'by_location';
public const FIELD_DELIVERABLE_COUNT = 'deliverable_count';
public const FIELD_LOCATION_INFO = 'location_info';
public const FIELD_ESTIMATE_STATUS = 'estimate_status';
public const FIELD_BY_DELIVERABLE = 'by_deliverable';
}
Take a close look at the dump of the entity, that field is apparently configured as an exposed virtual field, and looking at the other exposed fields, they seem to be configured incorrectly, as that property should hold a flat array of strings, not string indexed entries like 'item_count' => 1, as the value declares the field name, hence why you see a field named 1.
You are also misinterpreting the output, location_info does not belong to the 1 field, that's actually two different fields, 1 which holds something empty-ish that PHP doesn't print, like null or false, and location_info which holds an empty array.
Use debug() instead of pr()/print_r() to get a better structured dump which also properly shows empty data. And then inspect your Estimate entity class (or wherever things may be configured on the fly) and fix up the virtual fields.

Symfony 2 override entity field property

I want to override the entity field property. I need to get data from another database table (mapped by id). It should be a combination of "artikelnummer" and a field called "name" from another database table.
$builder->add('schlauch', 'entity', array(
'class' => 'SchlauchBundle:Artikelspezifikation',
'property' => 'artikelnummer',
'attr' => array(
'class' => 'extended-select'
),
'data_class' => null
));
The field "artikelnummer" outputs something like "12345" but I need to add the name (from another database table called "schlauch"), so it should look like "12345 Articlename". I tried a query in the entity file, but I dont't want to manipulate the output everywhere.
Is it possible to use a query for property and override it?
You can simple solve that by adding new getter to you entity:
class Artikelspezifikation
{
//…
/**
* #var Schlauch
*
* #ORM\ManyToOne(targetEntity="Schlauch", inversedBy="artikelspezifikations")
*/
private $schlauch;
//…
/**
* Get display name
*
* #return string
*/
public function getDisplayName()
{
return $this->artikelnummer . ' ' . $this->schlauch->getArtikelName();
}
//…
/**
* Set schlauch
*
* #param \SchlauchBundle\Entity\Schlauch $schlauch
*
* #return Artikelspezifikation
*/
public function setCategory(\SchlauchBundle\Entity\Schlauch $schlauch = null)
{
$this->schlauch = $schlauch;
return $this;
}
/**
* Get schlauch
*
* #return \SchlauchBundle\Entity\Schlauch
*/
public function getCategory()
{
return $this->schlauch;
}
}
And in your form class just change property:
$builder->add('schlauch', 'entity', array(
'class' => 'SchlauchBundle:Artikelspezifikation',
'property' => 'displayName',
'attr' => array(
'class' => 'extended-select'
),
'data_class' => null
));

Tried to use TinyMce in cakephp but it is not working

I tried to use the TinyMce plugin in cakephp but the editor is not loading but the helpers are. Even after extracting the files to the plugins folder and adding the codes in bootstrap.php file its not working.
Helper:
<?php
App::uses('AppHelper', 'View/Helper');
class TinymceHelper extends AppHelper {
// Take advantage of other helpers
public $helpers = array('Js', 'Html', 'Form');
// Check if the tiny_mce.js file has been added or not
public $_script = false;
/**
* Adds the tiny_mce.js file and constructs the options
*
* #param string $fieldName Name of a field, like this "Modelname.fieldname"
* #param array $tinyoptions Array of TinyMCE attributes for this textarea
* #return string JavaScript code to initialise the TinyMCE area
*/
function _build($fieldName, $tinyoptions = array()){
if(!$this->_script){
// We don't want to add this every time, it's only needed once
$this->_script = true;
$this->Html->script('tiny_mce/tiny_mce', array('inline' => false));
}
// Ties the options to the field
$tinyoptions['mode'] = 'exact';
$tinyoptions['elements'] = $this->domId($fieldName);
// List the keys having a function
$value_arr = array();
$replace_keys = array();
foreach($tinyoptions as $key => &$value){
// Checks if the value starts with 'function ('
if(strpos($value, 'function(') === 0){
$value_arr[] = $value;
$value = '%' . $key . '%';
$replace_keys[] = '"' . $value . '"';
}
}
// Encode the array in json
$json = $this->Js->object($tinyoptions);
// Replace the functions
$json = str_replace($replace_keys, $value_arr, $json);
$this->Html->scriptStart(array('inline' => false));
echo 'tinyMCE.init(' . $json . ');';
$this->Html->scriptEnd();
}
/**
* Creates a TinyMCE textarea.
*
* #param string $fieldName Name of a field, like this "Modelname.fieldname"
* #param array $options Array of HTML attributes.
* #param array $tinyoptions Array of TinyMCE attributes for this textarea
* #param string $preset
* #return string An HTML textarea element with TinyMCE
*/
function textarea($fieldName, $options = array(), $tinyoptions = array(), $preset = null){
// If a preset is defined
if(!empty($preset)){
$preset_options = $this->preset($preset);
// If $preset_options && $tinyoptions are an array
if(is_array($preset_options) && is_array($tinyoptions)){
$tinyoptions = array_merge($preset_options, $tinyoptions);
}else{
$tinyoptions = $preset_options;
}
}
return $this->Form->textarea($fieldName, $options) . $this->_build($fieldName, $tinyoptions);
}
/**
* Creates a TinyMCE textarea.
*
* #param string $fieldName Name of a field, like this "Modelname.fieldname"
* #param array $options Array of HTML attributes.
* #param array $tinyoptions Array of TinyMCE attributes for this textarea
* #return string An HTML textarea element with TinyMCE
*/
function input($fieldName, $options = array(), $tinyoptions = array(), $preset = null){
// If a preset is defined
if(!empty($preset)){
$preset_options = $this->preset($preset);
// If $preset_options && $tinyoptions are an array
if(is_array($preset_options) && is_array($tinyoptions)){
$tinyoptions = array_merge($preset_options, $tinyoptions);
}else{
$tinyoptions = $preset_options;
}
}
$options['type'] = 'textarea';
return $this->Form->input($fieldName, $options) . $this->_build($fieldName, $tinyoptions);
}
/**
* Creates a preset for TinyOptions
*
* #param string $name
* #return array
*/
private function preset($name){
// Full Feature
if($name == 'full'){
return array(
'theme' => 'advanced',
'plugins' => 'safari,pagebreak,style,layer,table,save,advhr,advimage,advlink,emotions,iespell,inlinepopups,insertdatetime,preview,media,searchreplace,print,contextmenu,paste,directionality,fullscreen,noneditable,visualchars,nonbreaking,xhtmlxtras,template',
'theme_advanced_buttons1' => 'save,newdocument,|,bold,italic,underline,strikethrough,|,justifyleft,justifycenter,justifyright,justifyfull,styleselect,formatselect,fontselect,fontsizeselect',
'theme_advanced_buttons2' => 'cut,copy,paste,pastetext,pasteword,|,search,replace,|,bullist,numlist,|,outdent,indent,blockquote,|,undo,redo,|,link,unlink,anchor,image,cleanup,help,code,|,insertdate,inserttime,preview,|,forecolor,backcolor',
'theme_advanced_buttons3' => 'tablecontrols,|,hr,removeformat,visualaid,|,sub,sup,|,charmap,emotions,iespell,media,advhr,|,print,|,ltr,rtl,|,fullscreen',
'theme_advanced_buttons4' => 'insertlayer,moveforward,movebackward,absolute,|,styleprops,|,cite,abbr,acronym,del,ins,attribs,|,visualchars,nonbreaking,template,pagebreak',
'theme_advanced_toolbar_location' => 'top',
'theme_advanced_toolbar_align' => 'left',
'theme_advanced_statusbar_location' => 'bottom',
'theme_advanced_resizing' => true,
'theme_advanced_resize_horizontal' => false,
'convert_fonts_to_spans' => true,
'file_browser_callback' => 'ckfinder_for_tiny_mce'
);
}
// Basic
if($name == 'basic'){
return array(
'theme' => 'advanced',
'plugins' => 'safari,advlink,paste',
'theme_advanced_buttons1' => 'code,|,copy,pastetext,|,bold,italic,underline,|,link,unlink,|,bullist,numlist',
'theme_advanced_buttons2' => '',
'theme_advanced_buttons3' => '',
'theme_advanced_toolbar_location' => 'top',
'theme_advanced_toolbar_align' => 'center',
'theme_advanced_statusbar_location' => 'none',
'theme_advanced_resizing' => false,
'theme_advanced_resize_horizontal' => false,
'convert_fonts_to_spans' => false
);
}
// Simple
if($name == 'simple'){
return array(
'theme' => 'simple',
);
}
// BBCode
if($name == 'bbcode'){
return array(
'theme' => 'advanced',
'plugins' => 'bbcode',
'theme_advanced_buttons1' => 'bold,italic,underline,undo,redo,link,unlink,image,forecolor,styleselect,removeformat,cleanup,code',
'theme_advanced_buttons2' => '',
'theme_advanced_buttons3' => '',
'theme_advanced_toolbar_location' => 'top',
'theme_advanced_toolbar_align' => 'left',
'theme_advanced_styles' => 'Code=codeStyle;Quote=quoteStyle',
'theme_advanced_statusbar_location' => 'bottom',
'theme_advanced_resizing' => true,
'theme_advanced_resize_horizontal' => false,
'entity_encoding' => 'raw',
'add_unload_trigger' => false,
'remove_linebreaks' => false,
'inline_styles' => false
);
}
return null;
}
}
I used the following codes in my view file:
<?php
echo $this->Html->script('/TinyMCE/js/tiny_mce/tiny_mce.js', array(
'inline' => false)); ?>
<script type="text/javascript">
tinyMCE.init({
theme : "advanced",
mode : "textareas",
convert_urls : false,
theme_advanced_buttons1 : "bold,italic,underline,blockquote,separator,strikethrough,justifyleft,justifycenter,justifyright, justifyfull,bullist,numlist,undo,redo,link,unlink",
theme_advanced_buttons2: "",
theme_advanced_buttons3: "",
theme_advanced_buttons4: "",
theme_advanced_toolbar_location : "top",
theme_advanced_toolbar_align : "left"
});
</script>
I added this code in my controller:
public $helpers = array('Tinymce');
I have solved it... I just inserted it manually and the problem was in setting the path to the js file.

ZF2 Dafault Database Session Storage

I am busy writing a DB Session module so I can quickly install it between multiple applications. The module will be loaded from the autoloader as the first module to be started. What I'm trying to accomplish is to change the default session container / session handler to be the default session handler for all modules and it should also be database hosted sessions. I've been strugling with zf2 session handler for quite a while now and the errors in the logs make 0 sense. So here what I have so far. A basic module with Module.php containing...
namespace DBSession;
use Zend\Mvc\ModuleRouteListener;
class Module {
public function onBootstrap($e) {
$e->getApplication()->getServiceManager()->get('translator');
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
$config = $e->getApplication()->getServiceManager()->get('Config');
$controller = $e->getTarget();
$controller->config = $config;
new \DBSession\Storage\DBStorage();
}
public function getConfig() {
return include __DIR__ . '/config/module.config.php';
}
public function getAutoloaderConfig() {
return array(
'Zend\Loader\ClassMapAutoloader' => array(
__DIR__ . '/autoload_classmap.php',
),
'Zend\Loader\StandardAutoloader' => array(
'namespaces' => array(
__NAMESPACE__ => __DIR__ . '/src/' . __NAMESPACE__,
),
),
);
}
}
And the actual class that initiates the DB session handler.
namespace DBSession\Storage;
use Zend\Session\SaveHandler\DbTableGateway;
use Zend\Session\SaveHandler\DbTableGatewayOptions;
use Zend\Db\Adapter\Adapter;
use Zend\Session\SessionManager;
use Zend\Session\Container;
class DBStorage {
public function __construct() {
$dbAdapter = new Adapter(array(
'driver' => 'Mysqli',
'host' => 'localhost',
'dbname' => 'zf2_session',
'username' => 'zf2',
'password' => 'testme',
'options' => array(
'buffer_results' => true,
),
));
$tableGateway = new \Zend\Db\TableGateway\TableGateway('session', $dbAdapter);
$gwOpts = new DbTableGatewayOptions();
$gwOpts->setDataColumn('data');
$gwOpts->setIdColumn('id');
$gwOpts->setLifetimeColumn('lifetime');
$gwOpts->setModifiedColumn('modified');
$gwOpts->setNameColumn('name');
$saveHandler = new DbTableGateway($tableGateway, $gwOpts);
$sessionManager = new SessionManager();
$sessionManager->setSaveHandler($saveHandler);
return Container::setDefaultManager($sessionManager);
}
}
When trying to create a session I see the following in the logs which I have 0 clue how to fix. This is starting to make me hate magic...
[29-Nov-2012 20:47:28 UTC] PHP Fatal error: Uncaught exception 'Zend\Db\Sql\Exception\InvalidArgumentException' with message 'Not a valid magic property for this object' in /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php:764
Stack trace:
#0 /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php(163): Zend\Db\Sql\Select->__get('tableReadOnly')
#1 /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php(146): Zend\Db\Sql\Select->from('session')
#2 /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Sql.php(65): Zend\Db\Sql\Select->__construct('session')
#3 /document_root/vendor/zendframework/zendframework/library/Zend/Db/TableGateway/AbstractTableGateway.php(191): Zend\Db\Sql\Sql->select()
#4 /document_root/vendor/zendframework/zendframework/library/Zend/Session/SaveHandler/DbTableGateway.php(134): Zend\Db\TableGateway\AbstractTableGateway->select(Array)
#5 [internal function]: Zend\Session\SaveHandler\DbTableGateway->write(' in /document_root/vendor/zendframework/zendframework/library/Zend/Db/Sql/Select.php on line 764
update :
zend framework >= 2.2, this issue is no more .
Old answer :
I have faced same issue on storing session into database , I have written initDbSession function inside basic module class \module\Application\Module.php
class Module
{
public function onBootstrap(MvcEvent $e)
{
$e->getApplication()->getServiceManager()->get('translator');
$eventManager = $e->getApplication()->getEventManager();
$moduleRouteListener = new ModuleRouteListener();
$moduleRouteListener->attach($eventManager);
// session start from here
$this->initDbSession( $e );
}
/**
* Store session into database
*
* #param type $e
*/
private function initDbSession( MvcEvent $e )
{
// grab the config array
$serviceManager = $e->getApplication()->getServiceManager();
$config = $serviceManager->get('config');
$dbAdapter = $serviceManager->get('Zend\Db\Adapter\Adapter');
/* some how this not works for me
$sessionOptions = new \Zend\Session\SaveHandler\DbTableGatewayOptions( null );
$sessionTableGateway = new \Zend\Db\TableGateway\TableGateway('session', $dbAdapter);
$saveHandler = new \Zend\Session\SaveHandler\DbTableGateway($sessionTableGateway, $sessionOptions);
*/
/* I written my own save handler , I am using mysql as database */
$saveHandler = new \My\Session\SaveHandler\Mysql( $config['db'] );
$sessionConfig = new \Zend\Session\Config\SessionConfig();
$sessionConfig->setOptions($config['session']);
// pass the saveHandler to the sessionManager and start the session
$sessionManager = new \Zend\Session\SessionManager( $sessionConfig , NULL, $saveHandler );
$sessionManager->start();
\Zend\Session\Container::setDefaultManager($sessionManager);
}
// other function goes here ...
Here my config file , which is located in \config\autoload\global.php
return array(
'db' => array(
'driver' => 'Pdo',
'dsn' => 'mysql:dbname=zf2;host=localhost',
'driver_options' => array(
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
'buffer_results' => true
),
'username' => 'root',
'password' => '',
'host' => 'localhost',
'dbname' => 'zf2',
),
'session' => array(
'remember_me_seconds' => 2419200,
'use_cookies' => true,
'cookie_httponly' => true,
'cookie_lifetime' => 2419200,
'gc_maxlifetime' => 2419200,
),
'service_manager' => array(
'factories' => array(
'Zend\Db\Adapter\Adapter' => 'Zend\Db\Adapter\AdapterServiceFactory'
)
)
);
Mysql Table structure is
CREATE TABLE `session` (
`id` CHAR(32) NOT NULL DEFAULT '',
`name` VARCHAR(255) NOT NULL,
`modified` INT(11) NULL DEFAULT NULL,
`lifetime` INT(11) NULL DEFAULT NULL,
`data` TEXT NULL,
PRIMARY KEY (`id`)
)COLLATE='utf8_general_ci' ENGINE=InnoDB;
Custom Mysql save handler class is given below . I have written this class, because new Zend\Session\SaveHandler\DbTableGateway
not working on my server . My custom Mysql session save handler class written on \library\My\Session\SaveHandler\Mysql.php
<?php
namespace My\Session\SaveHandler;
use Zend\Session\SaveHandler\SaveHandlerInterface;
/**
* Description of Mysql
*
* #author rab
*/
class Mysql implements SaveHandlerInterface
{
/**
* Session Save Path
*
* #var string
*/
protected $sessionSavePath;
/**
* Session Name
*
* #var string
*/
protected $sessionName;
/**
* Lifetime
* #var int
*/
protected $lifetime;
/**
* Constructor
*
*/
public function __construct( $dbConfig )
{
$this->dbconn = mysql_connect(
$dbConfig['host'],
$dbConfig['username'],
$dbConfig['password']
);
if ( $this->dbconn ) {
return mysql_select_db($dbConfig['dbname'], $this->dbconn);
}
}
/**
* Open the session
*
* #return bool
*/
public function open( $savePath, $name )
{
$this->sessionSavePath = $savePath;
$this->sessionName = $name;
$this->lifetime = ini_get('session.gc_maxlifetime');
return true;
}
/**
* Close the session
*
* #return bool
*/
public function close()
{
return mysql_close($this->dbconn);
}
/**
* Read the session
*
* #param int session id
* #return string string of the sessoin
*/
public function read($id)
{
$id = mysql_real_escape_string($id);
$sql = "SELECT `data` FROM `session` " .
"WHERE id = '$id'";
if ( $result = mysql_query($sql, $this->dbconn)) {
if ( mysql_num_rows($result) ) {
$record = mysql_fetch_assoc($result);
return $record['data'];
}
}
return '';
}
/**
* Write the session
*
* #param int session id
* #param string data of the session
*/
public function write($id, $data )
{
$data = (string) $data ;
$dbdata = array(
'modified' => time(),
'data' => mysql_real_escape_string( $data ) ,
);
$selectSql = "SELECT * FROM session
WHERE id = '$id' AND name = '{$this->sessionName}' ";
$rs = mysql_query( $selectSql, $this->dbconn );
if ( $rs = mysql_query( $selectSql , $this->dbconn)) {
if ( mysql_num_rows($rs) ) {
$updateSql = "UPDATE `session` SET
`modified`= '".$dbdata['modified'] . "' ,
`data`= '".$dbdata['data']. "'
WHERE id= '$id' AND name = '{$this->sessionName}' ";
mysql_query( $updateSql , $this->dbconn );
return true;
}
}
$dbdata['lifetime'] = $this->lifetime;
$dbdata['id'] = $id;
$dbdata['name'] = $this->sessionName;
$insertSql = "INSERT INTO session (". implode(',' , array_keys($dbdata)) .")"
."VALUES ('" . implode("','" , array_values( $dbdata )). "')";
return mysql_query( $insertSql, $this->dbconn);
}
/**
* Destoroy the session
*
* #param int session id
* #return bool
*/
public function destroy($id)
{
$sql = sprintf("DELETE FROM `session` WHERE `id` = '%s'", $id);
return mysql_query($sql, $this->dbconn);
}
/**
* Garbage Collector
*
* #param int life time (sec.)
* #return bool
*/
public function gc( $maxlifetime )
{
$sql = sprintf("DELETE FROM `session` WHERE `modified` < '%s'",
mysql_real_escape_string(time() - $maxlifetime)
);
return mysql_query($sql, $this->dbconn);
}
}
Which saves my session values into db table . For more information you can check http://php.net/manual/en/function.session-set-save-handler.php
After all of the fixes that have recently gone into ZF2 my original solution is now working without modification however, I have added a new composer module for anyone that wants to use it, it reduces the need to do it all manually.
Installation details are in the readme file on: https://github.com/Nitecon/DBSessionStorage
Enjoy and let me know if you have any issues.
Here's another similar solution:
How to use cookie in Zend Framework 2?
make sure you change this line:
Session::setDefaultManager($sessionManager);
With this one:
Container::setDefaultManager($sessionManager);
Using:
use Zend\Session\Container;

PaypalIpnConfig gives me a fatal error

I have this code in my C:\xampp\htdocs\wifidrivescanportal\app\Plugin\PaypalIpn\View\Helper\PaypalHelper.php:
<?php
/** Paypal Helper part of the PayPal IPN plugin.
*
* #author Nick Baker
* #link http://www.webtechnick.com
* #license MIT
*/
class PaypalHelper extends AppHelper {
var $helpers = array('Html','Form');
/**
* Setup the config based on the paypal_ipn_config in /Plugins/PaypalIpn/Config/paypal_ipn_config.php
*/
function __construct(View $View, $settings = array()){
//debug(APP_PATH);
//if(App::import(array('type' => 'File', 'name' => 'PaypalIpn.PaypalIpnConfig', 'file' => APP_PATH . DS . 'Config' . DS . 'paypal_ipn_config.php'))) {
//if(App::import(array('type' => 'File', 'name' => 'PaypalIpn.PaypalIpnConfig', 'file' => '/PaypalIpn/Config/paypal_ipn_config.php'))) {
if(App::import(array('type' => 'File', 'name' => 'PaypalIpn.PaypalIpnConfig', 'file' => APP.'Plugin'.DS.'paypal_ipn'.DS.'libs'))) {
$this->config =& new PaypalIpnConfig();
}
else {
$this->config =& new PaypalIpnConfig();
}
parent::__construct($View, $settings);
}
/**
* function button will create a complete form button to Pay Now, Donate, Add to Cart, or Subscribe using the paypal service.
* Configuration for the button is in /config/paypal_ip_config.php
*
* for this to work the option 'item_name' and 'amount' must be set in the array options or default config options.
*
* Example:
* $paypal->button('Pay Now', array('amount' => '12.00', 'item_name' => 'test item'));
* $paypal->button('Subscribe', array('type' => 'subscribe', 'amount' => '60.00', 'term' => 'month', 'period' => '2'));
* $paypal->button('Donate', array('type' => 'donate', 'amount' => '60.00'));
* $paypal->button('Add To Cart', array('type' => 'addtocart', 'amount' => '15.00'));
* $paypal->button('View Cart', array('type' => 'viewcart'));
* $paypal->button('Unsubscribe', array('type' => 'unsubscribe'));
* $paypal->button('Checkout', array(
* 'type' => 'cart',
* 'items' => array(
* array('item_name' => 'Item 1', 'amount' => '120', 'quantity' => 2, 'item_number' => '1234'),
* array('item_name' => 'Item 2', 'amount' => '50'),
* array('item_name' => 'Item 3', 'amount' => '80', 'quantity' => 3),
* )
* ));
* Test Example:
* $paypal->button('Pay Now', array('test' => true, 'amount' => '12.00', 'item_name' => 'test item'));
*
* #access public
* #param String $title takes the title of the paypal button (default "Pay Now" or "Subscribe" depending on option['type'])
* #param Array $options takes an options array defaults to (configuration in /config/paypal_ipn_config.php)
*
* helper_options:
* test: true|false switches default settings in /config/paypal_ipn_config.php between settings and testSettings
* type: 'paynow', 'addtocart', 'donate', 'unsubscribe', 'cart', or 'subscribe' (default 'paynow')
*
* You may pass in api name value pairs to be passed directly to the paypal form link. Refer to paypal.com for a complete list.
* some paypal API examples:
* amount: float value
* notify_url: string url
* item_name: string name of product.
* etc...
*/
function button($title = null, $options = array()){
if(is_array($title)){
$options = $title;
$title = isset($options['label']) ? $options['label'] : null;
}
$defaults = (isset($options['test']) && $options['test']) ? $this->config->testSettings : $this->config->settings;
$options = array_merge($defaults, $options);
$options['type'] = (isset($options['type'])) ? $options['type'] : "paynow";
switch($options['type']){
case 'subscribe': //Subscribe
$options['cmd'] = '_xclick-subscriptions';
$default_title = 'Subscribe';
$options['no_note'] = 1;
$options['no_shipping'] = 1;
$options['src'] = 1;
$options['sra'] = 1;
$options = $this->__subscriptionOptions($options);
break;
case 'addtocart': //Add To Cart
$options['cmd'] = '_cart';
$options['add'] = '1';
$default_title = 'Add To Cart';
break;
case 'viewcart': //View Cart
$options['cmd'] = '_cart';
$options['display'] = '1';
$default_title = 'View Cart';
break;
case 'donate': //Doante
$options['cmd'] = '_donations';
$default_title = 'Donate';
break;
case 'unsubscribe': //Unsubscribe
$options['cmd'] = '_subscr-find';
$options['alias'] = $options['business'];
$default_title = 'Unsubscribe';
break;
case 'cart': //upload cart
$options['cmd'] = '_cart';
$options['upload'] = 1;
$default_title = 'Checkout';
$options = $this->__uploadCartOptions($options);
break;
default: //Pay Now
$options['cmd'] = '_xclick';
$default_title = 'Pay Now';
break;
}
$title = (empty($title)) ? $default_title : $title;
$retval = "<form action='{$options['server']}/cgi-bin/webscr' method='post'><div class='paypal-form'>";
unset($options['server']);
foreach($options as $name => $value){
$retval .= $this->__hiddenNameValue($name, $value);
}
$retval .= $this->__submitButton($title);
return $retval;
}
/**
* __hiddenNameValue constructs the name value pair in a hidden input html tag
* #access private
* #param String name is the name of the hidden html element.
* #param String value is the value of the hidden html element.
* #access private
* #return Html form button and close form
*/
function __hiddenNameValue($name, $value){
return "<input type='hidden' name='$name' value='$value' />";
}
/**
* __submitButton constructs the submit button from the provided text
* #param String text | text is the label of the submit button. Can use plain text or image url.
* #access private
* #return Html form button and close form
*/
function __submitButton($text){
return "</div>" . $this->Form->end(array('label' => $text));
}
/**
* __subscriptionOptions conversts human readable subscription terms
* into paypal terms if need be
* #access private
* #param array options | human readable options into paypal API options
* INT period //paypal api period of term, 2, 3, 1
* String term //paypal API term //month, year, day, week
* Float amount //paypal API amount to charge for perioud of term.
* #return array options
*/
function __subscriptionOptions($options = array()){
//Period... every 1, 2, 3, etc.. Term
if(isset($options['period'])){
$options['p3'] = $options['period'];
unset($options['period']);
}
//Mount billed
if(isset($options['amount'])){
$options['a3'] = $options['amount'];
unset($options['amount']);
}
//Terms, Month(s), Day(s), Week(s), Year(s)
if(isset($options['term'])){
switch($options['term']){
case 'month': $options['t3'] = 'M'; break;
case 'year': $options['t3'] = 'Y'; break;
case 'day': $options['t3'] = 'D'; break;
case 'week': $options['t3'] = 'W'; break;
default: $options['t3'] = $options['term'];
}
unset($options['term']);
}
return $options;
}
/**
* __uploadCartOptions converts an array of items into paypal friendly name/value pairs
* #access private
* #param array of options that will be returned with proper paypal friendly name/value pairs for items
* #return array options
*/
function __uploadCartOptions($options = array()){
if(isset($options['items']) && is_array($options['items'])){
$count = 1;
foreach($options['items'] as $item){
foreach($item as $key => $value){
$options[$key.'_'.$count] = $value;
}
$count++;
}
unset($options['items']);
}
return $options;
}
}
?>
but i am getting this error in my browswer:
Fatal error: Class 'PaypalIpnConfig' not found in C:\xampp\htdocs\wifidrivescanportal\app\Plugin\PaypalIpn\View\Helper\PaypalHelper.php on line 23
line 23 is actually this line:
$this->config = new PaypalIpnConfig();
i dunno what's going on. even google cant provide me the answer.

Resources