Joomla 3.x Override button functions in custom component - php

I'm trying to override the buttons in my joomla component.
I've managed fine with the save function by adding code into the controller.
public function save($key = null, $urlVar = null)
{
$uri = JUri::getInstance();
$requestData = JRequest::getVar('jform', array(), 'post', 'array');
$user =& JFactory::getUser();
$userId = $user->get( 'id' );
$idToUse = $requestData['id'];
I want to do the same thing with save and close and save and new but I can't figure out how to do it. I thought that I needed to go to the view.html and look at the custom functions:
JToolBarHelper::title(JText::_('COM_SHOPPER_TITLE_SELECTOR'), 'selector.png');
// If not checked out, can save the item.
if (!$checkedOut && ($canDo->get('core.edit') || ($canDo->get('core.create'))))
{
JToolBarHelper::apply('selector.apply', 'JTOOLBAR_APPLY');
JToolBarHelper::save('selector.save', 'JTOOLBAR_SAVE');
}
if (!$checkedOut && ($canDo->get('core.create')))
{
JToolBarHelper::custom('selector.save2new', 'save-new.png', 'save-new_f2.png', 'JTOOLBAR_SAVE_AND_NEW', false);
}
So I thought I just needed to add a function save2new() but I can't figure out how to make it work.
Any thoughts on where I'm going wrong would be great.

I guess you are looking for registerTask.
If you want to call save method for save2new task than you need to call registerTask inside constructor __constructor
$this->registerTask('save2new', 'save');
https://docs.joomla.org/API17:JController::registerTask

Related

Dynamic global array in codeigniter

I want a global array that I can access through controller functions, they can either add or delete any item with particular key. How do I do this? I have made my custom controller 'globals.php' and added it on autoload library.
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
$notification_array = array();
$config['notification'] = $notification_array;
?>
following function on controller should add new item to my array
function add_data(){
array_unshift($this->config->item('notification'), "sample-data");
}
after add_data adds to the global array, whenever following function is called from client, it should give the updated array to the client.
function send_json()
{
header('content-type: application/json');
$target = $this->config->item('notification');
echo json_encode($target);
}
But my client always gets empty array. How can I make this happen? Please help.
Hi take advantage of OOP, like this
// put MY_Controller.php under core directory
class MY_Controller extends CI_Controller{
public $global_array = array('key1'=>'Value one','key2'=>'Value2'):
public function __construct() {
parent::__construct();
}
}
//page controller
class Page extends MY_Controller{
public function __construct() {
parent::__construct();
}
function send_json()
{
header('content-type: application/json');
$target = $this->global_array['key1'];
echo json_encode($target);
}
}
One solution I came up is to use session, its easy to use and its "fast" you need to do some benchmarking.
As I commented on both answers above/below there is no way you get same data in different controllers just because with each request everything is "reset", and to get to different controller you need to at least reload tha page. (note, even AJAX call makes new request)
Note that sessions are limited by size, you have a limit of 4kb (CodeIgniter stores session as Cookie) but wait, there is way around, store them in DB (to allow this go to config file and turn it on $config['sess_use_database'] = TRUE; + create table you will find more here)
Well lets get to the answer itself, as I understand you tried extending all your controllers if no do it and place some code in that core/MY_Controller.php file
as follows:
private function _initJSONSession() { //this function should be run in MY_Controller construct() after succesful login, $this->_initJSONSession(); //ignore return values
$json_session_data = $this->session->userdata('json');
if (empty($json_session_data )) {
$json_session_data['json'] = array(); //your default array if no session json exists,
//you can also have an array inside if you like
$this->session->set_userdata($ses_data);
return TRUE; //returns TRUE so you know session is created
}
return FALSE; //returns FALSE so you know session is already created
}
you also need these few functions they are self explainatory, all of them are public so you are free to use them in any controller that is extended by MY_Controller.php, like this
$this->_existsSession('json');
public function _existsSession( $session_name ) {
$ses_data = $this->session->userdata( $session_name );
if (empty( $ses_data )) return FALSE;
return TRUE;
}
public function _clearSession($session_name) {
$this->session->unset_userdata($session_name);
}
public function _loadSession($session_name) {
return (($this->_existsSession( $session_name )) ? $this->session->userdata($session_name) : FALSE );
}
the most interesting function is _loadSession(), its kind of self explainatory it took me a while to fully understand session itself, well in a few words you need to get (load) data that are in session already, do something with it ([CRUD] like add new data, or delete some) and than put back (REWRITE) all data in the same session.
Lets go to the example:
keep in mind that session is like 2d array (I work with 4+5d arrays myself)
$session['session_name'] = 'value';
$session['json'] = array('id' => '1', 'name' => 'asok', 'some_array' => array('array_in_array' => array()), 'etcetera' => '...');
so to write new (rewrite) thing in session you use
{
$session_name = 'json';
$session_data[$session_name] = $this->_loadSession($session_name);
//manipulate with array as you wish here, keep in mind that your variable is
$session_data[$session_name]['id'] = '2'; // also keep in mind all session variables are (string) type even (boolean) TRUE translates to '1'
//or create new index
$session_data[$session_name]['new_index'] = FALSE; // this retypes to (string) '0'
//now put session in place
$this->session->set_userdata($session_data);
}
if you like to use your own function add_data() you need to do this
well you need to pass some data to it first add_data($arr = array(), $data = ''){}
eg: array_unshift( $arr, $data );
{
//your default array that is set to _initJSONSession() is just pure empty array();
$session_name = 'json';
$session_data[$session_name] = $this->_loadSession( $session_name );
// to demonstrate I use native PHP function instead of yours add_data()
array_unshift( $session_data[$session_name], 'sample-data' );
$this->session->set_userdata( $session_data );
unset( $session_data );
}
That is it.
You can add a "global" array per controller.
At the top of your controller:
public $notification_array = array();
Then to access it inside of different functions you would use:
$this->notification_array;
So if you want to add items to it, you could do:
$this->notification_array['notification'] = "Something";
$this->notification_array['another'] = "Something Else";

WordPress delete_user hook when delete multiple users

I'm using delete_user hook to make some action (call another function) before the user is deleted.
This is part of my code:
function delete_user( $user_id )
{
include('sso_functions.php');
global $wpdb;
$user_obj = get_userdata( $user_id );
$email = $user_obj->user_email;
$login = array_values(login($email));
$login_err = $login[1];
$cookie = $login[0];
if($login_err == 0)
{
//....
}
else
{
//...
}
}
add_action( 'delete_user', 'delete_user' );
Login() function is declared in sso_settings.php file.
If I try to delete only one user is working good.
But if I try to delete 2 users - login() function is called and first user is deleted from Wordpress, but after that I get a php error that function login() is redeclared.
If I use include_once('sso_functions.php') instead of include('sso_function.php'). I don't receive the error and users are deleted from Wordpress but function Login() is not called for second user.
Any idea how can I solve this?
Thanks!
The wordrpess shows correct error message. Instead of using include('sso_functions.php'); line try using it
if(!function_exists('login')){
include('sso_functions.php');
}
I am not sure but i think delete_user is already a function. Just try replacing the name from delete_user to something else. Something like
add_action( 'delete_user', 'wp_delete_user' );
And also dont forget to change the name of the function. Its always better if you use the your plugin name as a prefix. Same goes with the function login, add a prefix to it, so it core functions are not mixed with custom created functions.
$array = array(1,2,3,4); //array of user that need to delete
foreach($array as $userid)
{
delete_user( $userid );
}

zf2 forms and object binding, without clearing non-passed values

I've read through the tutorials/reference of the Form-Component in Zend-Framework 2 and maybe I missed it somehow, so I'm asking here.
I've got an object called Node and bound it to a form. I'm using the Zend\Stdlib\Hydrator\ArraySerializable-Standard-Hydrator. So my Node-object has got the two methods of exchangeArray() and getArrayCopy() like this:
class Node
{
public function exchangeArray($data)
{
// Standard-Felder
$this->node_id = (isset($data['node_id'])) ? $data['node_id'] : null;
$this->node_name = (isset($data['node_name'])) ? $data['node_name'] : null;
$this->node_body = (isset($data['node_body'])) ? $data['node_body'] : null;
$this->node_date = (isset($data['node_date'])) ? $data['node_date'] : null;
$this->node_image = (isset($data['node_image'])) ? $data['node_image'] : null;
$this->node_public = (isset($data['node_public'])) ? $data['node_public'] : null;
$this->node_type = (isset($data['node_type'])) ? $data['node_type']:null;
$this->node_route = (isset($data['node_route'])) ? $data['node_route']:null;
}
public function getArrayCopy()
{
return get_object_vars($this);
}
}
In my Controller I've got an editAction(). There I want to modify the values of this Node-object. So I am using the bind-method of my form. My form has only fields to modify the node_name and the node_body-property. After validating the form and dumping the Node-object after submission of the form the node_name and node_body-properties now contain the values from the submitted form. However all other fields are empty now, even if they contained initial values before.
class AdminController extends AbstractActionController
{
public function editAction()
{
// ... more stuff here (getting Node, etc)
// Get Form
$form = $this->_getForm(); // return a \Zend\Form instance
$form->bind($node); // This is the Node-Object; It contains values for every property
if(true === $this->request->isPost())
{
$data = $this->request->getPost();
$form->setData($data);
// Check if form is valid
if(true === $form->isValid())
{
// Dumping here....
// Here the Node-object only contains values for node_name and node_body all other properties are empty
echo'<pre>';print_r($node);echo'</pre>';exit;
}
}
// View
return array(
'form' => $form,
'node' => $node,
'nodetype' => $nodetype
);
}
}
I want to only overwrite the values which are coming from the form (node_name and node_body) not the other ones. They should remain untouched.
I think a possible solution would be to give the other properties as hidden fields into the form, however I don't wanna do this.
Is there any possibility to not overwrite values which are not present within the form?
I rechecked the code of \Zend\Form and I gotta be honest I just guessed how I can fix my issue.
The only thing I changed is the Hydrator. It seems that the Zend\Stdlib\Hydrator\ArraySerializable is not intended for my case. Since my Node-Object is an object and not an Array I checked the other available hydrators. I've found the Zend\Stdlib\Hydrator\ObjectProperty-hydrator. It works perfectly. Only fields which are available within the form are populated within the bound object. This is exactly what I need. It seems like the ArraySerializable-hydrator resets the object-properties, because it calls the exchangeArray-method of the bound object (Node). And in this method I'm setting the non-given fields to null (see code in my question). Another way would propably be to change the exchangeArray-method, so that it only sets values if they are not available yet.
So the solution in the code is simple:
$form = $this->_getForm();
$form->setHydrator(new \Zend\Stdlib\Hydrator\ObjectProperty()); // Change default hydrator
There is a bug in the class form.php, the filters are not initialized in the bindvalues method just add the line $filter->setData($this->data);
it should look like this after including the line
public function bindValues(array $values = array())
{
if (!is_object($this->object)) {
return;
}
if (!$this->hasValidated() && !empty($values)) {
$this->setData($values);
if (!$this->isValid()) {
return;
}
} elseif (!$this->isValid) {
return;
}
$filter = $this->getInputFilter();
$filter->setData($this->data); //added to fix binding empty data
switch ($this->bindAs) {
case FormInterface::VALUES_RAW:
$data = $filter->getRawValues();
break;
case FormInterface::VALUES_NORMALIZED:
default:
$data = $filter->getValues();
break;
}
$data = $this->prepareBindData($data, $this->data);
// If there is a base fieldset, only hydrate beginning from the base fieldset
if ($this->baseFieldset !== null) {
$data = $data[$this->baseFieldset->getName()];
$this->object = $this->baseFieldset->bindValues($data);
} else {
$this->object = parent::bindValues($data);
}
}
to be precious it is line no 282 in my zf2.0.6 library
this would fix your problem, this happen only for binded object situation
I ran into the same problem, but the solution of Raj is not the right way. This is not a bug as for today the code remains still similar without the 'fix' of Raj, adding the line:
$filter->setData($this->data);
The main problem here is when you bind an object to the form, the inputfilter is not stored inside the Form object. But called every time from the binded object.
public function getInputFilter()
...
$this->object->getInputFilter();
...
}
My problem was that I created every time a new InputFilter object when the function getInputFilter was called. So I corrected this to be something like below:
protected $filter;
...
public function getInputFilter {
if (!isset($this->filter)) {
$this->filter = new InputFilter();
...
}
return $this->filter;
}
I ran into the same issue today but the fix Raj suggested did not work. I am using the latest version of ZF2 (as of this writing) so I am not totally surprised that it didn't work.
Changing to another Hydrator was not possible as my properties are held in an array. Both the ObjectProperty and ClassMethods hydrators rely on your properties actually being declared (ObjectProperty uses object_get_vars and ClassMethods uses property_exists). I didn't want to create my own Hydrator (lazy!).
Instead I stuck with the ArraySerializable hydrator and altered my exchangeArray() method slightly.
Originally I had:
public function exchangeArray(array $data)
{
$newData = [];
foreach($data as $property=>$value)
{
if($this->has($property))
{
$newData[$property] = $value;
}
}
$this->data = $newData;
}
This works fine most of the time, but as you can see it blows away any existing data in $this->data.
I tweaked it as follows:
public function exchangeArray(array $data)
{
$newData = [];
foreach($data as $property=>$value)
{
if($this->has($property))
{
$newData[$property] = $value;
}
}
//$this->data = $newData; I changed this line...
//to...
$this->data = array_merge($this->data, $newData);
}
This preserves any existing keys in $this->data if they are missing from the new data coming in. The only downside to this approach is I can no longer use exchangeArray() to overwrite everything held in $this->data. In my project this approach is a one-off so it is not a big problem. Besides, a new replaceAllData() or overwrite() method is probably preferred in any case, if for no other reason than being obvious what it does.

Is there a way to add a group of elements to a form?

I'm wondering if there was a way to add a group of elements to a zend form as if they were one element, I guess much like a subform, but it seems the functionality of a subform may be too much...
Here's my use-case. I've created a class that handles multi-page forms. I want to be able to write logic to change the buttons at the bottom of the form based on the page of the form I'm on.
I originally thought that Zend-Form-DisplayGroup would fix my problem, but you have to add the items to the form first and then add them to the display group and can't pass a display group through a function with attached elements. I would like to have a function that would be something like
public function setSubmitButtonGroup($submitButtonGroupElements)
{
/*Code to set button group*/
}
The idea of using an array of elements just hit me right now as opposed to something else and add logic to add that array of elements to the form on render... but does anyone have any "better" ideas or done this before?
BTW, if anyone is wondering... I'm loosely basing my initial design off of this section: Zend Framework Advance Form Usage.
Not sure I understand your problem correctly but this how I do some things.
In a Zend_Form object you can add elements as a group with `addElements($elements) in an array. For the Submit button etc. I have a class where I get the $elements array from and then I simply pop it in. I also add a displayGroup but separately and simply to control where the buttons are. Because a form is an object you can do simple things like the following but I always add a reference to show my intent.
update: shuffled the button manipulation
function addButtons(&$form,$nextName = null) {
$buttons = $this->getButtons(); // this will be an array with your buttons
// make sure you have the element names in your buttons arrays
if ( is_string($nextName) ) {
$buttons['nextButton']->setLabel($nextName);
} elseif ( is_bool($nextName) && false === $nextName ) {
unset($buttons['nextButton'];
}
// repeat for other buttons
$form->addElements($buttons);
$elementNames = array_keys($buttons);
$form->addDisplayGroup($elementNames,'buttonGroup',array('legend'=>'Click some buttons'));
}
$this->addButtons($form,'Finish');
You could make yourself a factory that receive three params, your form element, the current controller and the current action. Then in that factory, you could call a builder based on the controller/action combination and you pass your form.
In your builder you add 1, 2 or 3 buttons based on the corresponding controller/action requirement which are stored in diffrent components. Once it is done, you return your form to the factory and the factory return the form.
My_Form // your Zend_Form object
My_Form_Factory // Your new factory (see below)
My_Form_Factory_Builder_Controller_Action // One of your builder (see below)
My_Form_Factory_Component // Extends the corresponding Zend_Form_Elements
// basic factory that can be called like My_Factory::factory($form, $controller, $action)
class My_Form_Factory {
static public function factory($form, $controller, $action)
$builderClass = "My_Form_Factory_Builder_" . $controller . '_' . $action;
$builder = new $builderClass($form);
return $builder->buildForm();
}
// Basic builder
class My_Form_Factory_Builder_Controller_Action
{
protected $_form;
protected $_previousComponent ;
protected $_nextComponent ;
protected $_cancelComponent ;
public function __construct($form)
{
$this->_form = $form;
$this->_previousComponent = new My_Form_Factory_Component_Previous();
$this->_nextComponent = new My_Form_Factory_Component_Next();
$this->_cancelComponent = new My_Form_Factory_Component_Cancel();
}
public function buildForm()
{
$this->_form->addElement($previousCompnent);
$this->_form->addElement($nextComponent);
$this->_form->addElement($cancelComponent);
return $this->_form;
}
}
If you want to automatize the instanciation you could initialize all the different compoments you might require in an abstract class and in the method buildForm() only add the elements you need for that current interface. (I would rather repeat the code in each builder than rely on this kind of "magic" but it a viable method to do it).
So the complexity of my problem comes with knowing what page of the multipage form. Using an array and the above mentioned addElements() helped.
Simple Answer
The answer to my problem was an array that could be manipulated after the form was "built" so to speak but before it was rendered so that I could add to the form using addElements().
Long Answer
To get the whole picture, imagine each time you hit the next or previous button, you are traversing through an array of subforms. In this case one would need a function to handle the button rendering. I ended up using a case statment, though it's not the best implementation in the world (not reusable in the parent class Form_MultiPage), but it worked:
in my extention of my mulipage form class I have
public function setSubmitControls()
{
$previous = new Zend_Form_Element_Submit('previous',array(
'label'=>'previous',
'required'=>false,
'ignore'=>false,
'order'=>9000
));
$cancel = new Zend_Form_Element_Submit('cancel',array(
'label'=>'Cancel',
'required'=>false,
'ignore'=>false,
'order'=>9003
));
$next = new Zend_Form_Element_Submit('next',array(
'label'=>'Next',
'required'=>false,
'ignore'=>false,
'order'=>9002
));
$finished = new Zend_Form_Element_submit('finish',array(
'label'=>'Finish',
'required'=>false,
'ignore'=>false,
'order'=>9004
));
$submitControls = array();
echo var_dump($this->getCurrentSubForm()->getName());
switch($this->getCurrentSubForm()->getName())
{
case 'billInfo':
$submitControls = array(
$next,
$cancel
);
break;
case 'payerInfo':
$submitControls = array(
$previous,
$next,
$cancel
);
break;
//So on for other subforms
}
$this->setSubmitButtonGroup($submitControls);
}
In my parent class, Form_Multipage, I have
public function setSubmitButtonGroup(array $elements)
{
$this->_submitButton = $elements;
}
And
public function addSubmitButtonGroupToSubForm(Zend_Form_SubForm $subForm)
{
$subForm->addElements($this->_submitButton);
return $subForm;
}
Which is called when I render the "page" of the form with this function
public function prepareSubForm($spec)
{
if (is_string($spec)) {
$subForm = $this->{$spec};
} elseif ($spec instanceof Zend_Form_SubForm) {
$subForm = $spec;
} else {
throw new Exception('Invalid argument passed to ' .
__FUNCTION__ . '()');
}
$subform = $this->setSubFormDecorators($subForm);
$subform = $this->addSubmitButtonGroupToSubForm($subForm);
$subform = $this->addSubFormActions($subForm);
$subform->setMethod($this->getMethod());
return $subForm;
}

Which is the event listener after doSave() in Symfony?

I've been looking at this event-listeners page http://www.doctrine-project.org/documentation/manual/1_1/pl/event-listeners and I'm not sure which is the listener I have to use to make a change after the doSave() method in the BaseModelForm.class.php.
// PlaceForm.class.php
protected function doSave ( $con = null )
{
...
parent::doSave($con);
....
// Only for new forms, insert place into the tree
if($this->object->level == null){
$parent = Place::getPlace($this->getValue('parent'), Language::getLang());
...
$node = $this->object->getNode();
$method = ($node->isValidNode() ? 'move' : 'insert') . 'AsFirstChildOf';
$node->$method($parent); //calls $this->object->save internally
}
return;
}
What I want to do is to make a custom slug with the ancestors' name of that new place. So if I inserting "San Francisco", the slug would be "usa-california-san-francisco"
public function postXXXXXX($event)
{
...
$event->getInvoker()->slug = $slug;
}
The problem is that I'm inserting a new object with no reference to its parent. After it's saved, I insert it to the tree. So I can't change the slug until then.
I think a Transaction listener could work, but I'm use there is a better way I'm not seeing right now.
thanks!
You are looking at the wrong piece of code. As stated by benlumley, you should manage your slug directly in the model, not in the form. To achieve what you want (a recursive slug) is quite easy using doctrine's Sluggable behavior. You need to implement a getUniqueSlug() into your model so that it gets called by the behavior (it's automatic) and handle your slug specifities in there:
public function getUniqueSlug()
{
$slug = '';
$parent = $this->getParent();
if ($parent->exists())
{
$slug = $this->getParent()->getUniqueSlug().'-';
}
return $slug.$this->getName();
}
What we do here is basically traverse all the ancestors of the current object and append the slugs on the go (replace the getParent() by whatever method you use to retrieve an object's parent.
Firstly, I'd put this into the model rather than the form - that way if the object is ever edited/updated the behaviour would still happen.
In the form though, I'd use updateObject:
function updateObject($values = array()) {
parent::updateObject($values);
// do your stuff
}
In the model (looks like you are using doctrine ...) I'd put this into the postSave() method. As I say, I think its better there than the form.
I had the same problems, and the Doctrine_Record::postInsert(Doctrine_Event $event) method did not work for me. Indeed the node aren't hydrated yet.
I had to overwrite the sfFormObject::doSave method like this:
protected function doSave($con = null)
{
$is_new = $this->isNew();
parent::doSave($con);
$this->doSaveNestedSet($con);
$service = $this->getObject();
if( $is_new and ! $service->getClientId() and $parent = $service->getParent())
{
$service->setClient($parent->getClient());
$service->save();
}
}

Categories