Joomla 3.1 JSON handling

1.4k views Asked by At

Basically I have an issue that stems from Joomla 3.1's JSON handling, although because I cannot isolate it I am making this question for direction on it. This will hopefully serve to isolate if this is a bug I should report or something in my application.

Joomla 3.1 seems to have a plugin, or some sort of JSON handling in either the default $model->save() or $table->save(). This can lead to unwanted additions to JSON fields when saving any kind of page. So far looking through the code for them I have been unable to isolate it (witch makes me think plugin, though I only have default enabled.)

So here is the question:

Does Joomla do any JSON handling of form inputs? If so can it be disabled?

To clarify on my personal problem, I have a skeleton component save feature (saves a few minor values to the database, one column is a JSON field). It uses JModelAdmin and through it JTablefor handling the saving of the content. However every item in the JSON array (it is an array of objects) gets duplicated every time it saves. Considering it is a string in a hidden form when it is saved, somewhere in the process it is being parsed into a php array and duplicated by Joomla. Since there is no custom code in the actual saving process it is likely that Joomla has some need to edit JSON values elsewhere and my component triggered it.

here is the form xml if this has anything to do with it, the values hidden form field is what "should" contain the json, it works until you save. That is when every index in the JSON array duplicates.

<?xml version="1.0" encoding="utf-8"?>
<form>
    <fieldset>
        <field name="id"  type="text" class="readonly" label="JGLOBAL_FIELD_ID_LABEL"
            description ="JGLOBAL_FIELD_ID_DESC" size="10" default="0"
            readonly="true" />

        <field name="name" type="text" label="COM_HELLO_COMPANY_FIELD_NAME_LABEL"
            description="COM_HELLO_COMPANY_FIELD_NAME_DESC" class="input-xlarge" size="30"
            required="true" labelclass="control-label" />

        <field name="state" type="list" label="JSTATUS"
            description="JFIELD_PUBLISHED_DESC" class="span12 small"
            filter="intval" size="1" default="1"
        >
            <option value="1">
                JPUBLISHED</option>
            <option value="0">
                JUNPUBLISHED</option>
            <option value="2">
                JARCHIVED</option>
            <option value="-2">
                JTRASHED</option>
        </field>

        <field
            name="buttonspacer"
            description="JGLOBAL_ACTION_PERMISSIONS_DESCRIPTION"
            type="spacer" />

        <field name="created" type="calendar" label="COM_HELLO_FIELD_CREATED_LABEL"
            description="COM_CONTENT_FIELD_CREATED_DESC" class="inputbox" size="22"
            format="%Y-%m-%d %H:%M:%S" filter="user_utc" labelclass="control-label" />

        <field name="created_by" type="user"
            label="COM_HELLO_FIELD_CREATED_BY_LABEL" description="COM_HELLO_FIELD_CREATED_BY_DESC" labelclass="control-label" />

        <field name="modified" type="calendar" class="readonly"
            label="JGLOBAL_FIELD_MODIFIED_LABEL" description="COM_HELLO_FIELD_MODIFIED_DESC"
            size="22" readonly="true" format="%Y-%m-%d %H:%M:%S" filter="user_utc" labelclass="control-label" />

        <field name="modified_by" type="user"
            label="JGLOBAL_FIELD_MODIFIED_BY_LABEL"
            class="readonly"
            readonly="true"
            filter="unset"
            labelclass="control-label"
         />

        <field name="checked_out" type="hidden" filter="unset" />

        <field name="values" type="hidden" />

        <field name="checked_out_time" type="hidden" filter="unset" />

        <field name="publish_up" type="calendar"
            label="COM_HELLO_FIELD_PUBLISH_UP_LABEL" description="COM_HELLO_FIELD_PUBLISH_UP_DESC"
            class="inputbox" format="%Y-%m-%d %H:%M:%S" size="22"
            filter="user_utc" labelclass="control-label" />

        <field name="publish_down" type="calendar"
            label="COM_HELLO_FIELD_PUBLISH_DOWN_LABEL" description="COM_HELLO_FIELD_PUBLISH_DOWN_DESC"
            class="inputbox" format="%Y-%m-%d %H:%M:%S" size="22"
            filter="user_utc" labelclass="control-label" />

        <field name="assesments" type="text" label="COM_HELLO_COMPANY_FIELD_ASSESEMENTS_LABEL"
            description="COM_HELLO_COMPANY_FIELD_ASSESEMENTS_DESC" class="readonly" size="6"
            readonly="true" filter="unset" />

    </fieldset>
</form>

Here is my edit.php for the page. It uses JSON to update the values field, the same concept works perfect on the front end of the site and only the administrator side has an issue with it.

<?php
defined('_JEXEC') or die;

JHtml::_('behavior.formvalidation');
JHtml::_('behavior.keepalive');
//JHtml::_('formbehavior.chosen', 'select');

$app = JFactory::getApplication();
$input = $app->input;
if(!empty($this->item->values)){
    $values = json_decode($this->item->values);
}else{
    $values = array();
}
function setupSelect($selected = 0,$savedValues){
    $selectOptions = '';
    foreach($savedValues as $value){
        $selectOptions .= '<option value="'.$value->id.'"'.($value->id==$selected?' selected="selected"':'').'>'.$value->name.'</option>';
    }
    return $selectOptions;
}
?>
<script type="text/javascript">
    Joomla.submitbutton = function(task){
        if(task == 'company.cancel' || document.formvalidator.isValid(document.id('item-form'))){
            Joomla.submitform(task, document.getElementById('item-form'));
        }
    }
</script>
<form action="<?php echo JRoute::_('index.php?option=com_hello&view=company&layout=edit&id='.(int) $this->item->id); ?>" method="post" name="adminForm" id="item-form" class="form-validate">

    <?php echo JLayoutHelper::render('joomla.edit.item_title', $this); ?>

    <div class="row-fluid">
        <!-- Begin Content -->
        <div class="span10 form-horizontal">
            <?php echo JHtml::_('bootstrap.startTabSet', 'myTab', array('active' => 'general')); ?>

                <?php echo JHtml::_('bootstrap.addTab', 'myTab', 'general', JText::_('COM_HELLO_VALUE_DETAILS', true)); ?>
                    <fieldset class="adminform">
                        <div class="control-group form-inline">
                            <?php echo $this->form->getLabel('name'); ?> <?php echo $this->form->getInput('name'); ?>
                        </div>
                        <div>Core Values:</div>
                        <div class="control-group form-inline">
                            <div id="core-values" class="accordion">
                                <?php
                                if(count($values)):
                                    foreach($values as $value):
                                        $id = uniqid();
                                        ?>
                                        <div class="core-value row-fluid">
                                            <div class="accordion-group core-value span9" style="padding:0;">
                                                <div class="accordion-heading">
                                                    <a class="accordion-toggle" data-toggle="collapse" data-parent="#core-values" href="#collapse<?php echo $id; ?>">
                                                        <select class="value-select">
                                                            <option value="0"><?php echo JText::_('COM_HELLO_CORE_VALUES_SELECT_NONE'); ?></option>
                                                            <?php echo setupSelect($value->id,$this->values); ?>
                                                        </select>
                                                        <span class="pull-right" style="height:28px;line-height:28px;">
                                                            <span class="icon-plus main"></span>
                                                        </span>
                                                    </a>
                                                    <span class="clearfix"></span>
                                                </div>
                                                <div id="collapse<?php echo $id; ?>" class="accordion-body collapse">
                                                    <div class="accordion-inner">
                                                        <input type="text" class="outcome-input input-block-level" placeholder="Outcome Statement" value="<?php echo $value->outcome; ?>">
                                                        <ul class="behaviors">
                                                            <?php
                                                            $num = 6;
                                                            for($i=0;$i<$num;$i++): 
                                                            ?>
                                                            <li><input type="text" class="behaviors-input input-block-level" placeholder="Behavior" value="<?php echo empty($value->behaviors[$i])?'':$value->behaviors[$i]; ?>"></li>
                                                            <?php endfor; ?>
                                                            <?php if(count($value->behaviors)>6): 
                                                                for($i=6;$i<count($value->behaviors);$i++): 
                                                            ?>
                                                            <li><input type="text" class="behaviors-input input-block-level" placeholder="Behavior" value="<?php echo empty($value->behaviors[$i])?'':$value->behaviors[$i]; ?>"></li>
                                                            <?php endfor; 
                                                            endif; ?>
                                                        </ul>
                                                        <div class="clearfix"></div>
                                                        <button type="button" class="btn new-value2 pull-right"><span class="icon-plus no-stop"></span></button>
                                                        <div class="clearfix"></div>
                                                    </div>
                                                </div>
                                            </div>
                                            <div class="span3" style="line-height:44px;">
                                                <button type="button" class="btn rm-value"><span class="icon-minus"></span></button>
                                            </div>
                                        </div>
                                        <?php
                                    endforeach;
                                endif;
                                $id = uniqid();
                                ?>
                                <div class="core-value row-fluid">
                                    <div class="accordion-group span9" style="padding:0;">
                                        <div class="accordion-heading">
                                            <a class="accordion-toggle" data-toggle="collapse" data-parent="#core-values" href="#collapse<?php echo $id; ?>">
                                                <select class="value-select">
                                                    <option value="0"><?php echo JText::_('COM_HELLO_CORE_VALUES_SELECT_NONE'); ?></option>
                                                    <?php echo setupSelect(0,$this->values); ?>
                                                </select>
                                                <span class="pull-right" style="height:28px;line-height:28px;">
                                                    <span class="icon-plus main"></span>
                                                </span>
                                            </a>
                                            <span class="clearfix"></span>
                                        </div>
                                        <div id="collapse<?php echo $id; ?>" class="accordion-body collapse">
                                            <div class="accordion-inner">
                                                <input type="text" class="outcome-input input-block-level" placeholder="Outcome Statement">
                                                <ul class="behaviors">
                                                    <li><input type="text" class="behaviors-input input-block-level" placeholder="Behavior"></li>
                                                    <li><input type="text" class="behaviors-input input-block-level" placeholder="Behavior"></li>
                                                    <li><input type="text" class="behaviors-input input-block-level" placeholder="Behavior"></li>
                                                    <li><input type="text" class="behaviors-input input-block-level" placeholder="Behavior"></li>
                                                    <li><input type="text" class="behaviors-input input-block-level" placeholder="Behavior"></li>
                                                    <li><input type="text" class="behaviors-input input-block-level" placeholder="Behavior"></li>
                                                </ul>
                                                <div class="clearfix"></div>
                                                <button type="button" class="btn new-value2 pull-right"><span class="icon-plus no-stop"></span></button>
                                                <div class="clearfix"></div>
                                            </div>
                                        </div>
                                    </div>
                                    <div class="span3" style="line-height:44px;">
                                        <button type="button" class="btn rm-value"><span class="icon-minus"></span></button>
                                        <button type="button" class="btn new-value"><span class="icon-plus"></span></button>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <?php echo $this->form->getInput('values'); ?>
                    </fieldset>
                <?php echo JHtml::_('bootstrap.endTab'); ?>
                <?php echo JHtml::_('bootstrap.addTab', 'myTab', 'publishing', JText::_('COM_HELLO_FIELDSET_PUBLISHING', true)); ?>
                    <div class="row-fluid">
                        <div class="span6">
                            <div class="control-group">
                                <div class="control-label">
                                    <?php echo $this->form->getLabel('id'); ?>
                                </div>
                                <div class="controls">
                                    <?php echo $this->form->getInput('id'); ?>
                                </div>
                            </div>
                            <div class="control-group">
                                <?php echo $this->form->getLabel('created_by'); ?>
                                <div class="controls">
                                    <?php echo $this->form->getInput('created_by'); ?>
                                </div>
                            </div>
                            <div class="control-group">
                                <?php echo $this->form->getLabel('created'); ?>
                                <div class="controls">
                                    <?php echo $this->form->getInput('created'); ?>
                                </div>
                            </div>
                        </div>
                        <div class="span6">
                            <div class="control-group">
                                <?php echo $this->form->getLabel('publish_up'); ?>
                                <div class="controls">
                                    <?php echo $this->form->getInput('publish_up'); ?>
                                </div>
                            </div>
                            <div class="control-group">
                                <?php echo $this->form->getLabel('publish_down'); ?>
                                <div class="controls">
                                    <?php echo $this->form->getInput('publish_down'); ?>
                                </div>
                            </div>
                            <?php if ($this->item->modified_by) : ?>
                                <div class="control-group">
                                    <?php echo $this->form->getLabel('modified_by'); ?>
                                    <div class="controls">
                                        <?php echo $this->form->getInput('modified_by'); ?>
                                    </div>
                                </div>
                                <div class="control-group">
                                    <?php echo $this->form->getLabel('modified'); ?>
                                    <div class="controls">
                                        <?php echo $this->form->getInput('modified'); ?>
                                    </div>
                                </div>
                            <?php endif; ?>

                            <?php if ($this->item->assesments) : ?>
                                <div class="control-group">
                                    <div class="control-label">
                                        <?php echo $this->form->getLabel('assesments'); ?>
                                    </div>
                                    <div class="controls">
                                        <?php echo $this->form->getInput('assesments'); ?>
                                    </div>
                                </div>
                            <?php endif; ?>
                        </div>
                    </div>
                <?php echo JHtml::_('bootstrap.endTab'); ?>
            <?php echo JHtml::_('bootstrap.endTabSet'); ?>
            <input type="hidden" name="task" value="" />
            <input type="hidden" name="return" value="<?php echo $input->getCmd('return');?>" />
            <?php echo JHtml::_('form.token'); ?>
        </div>
        <!-- End Content -->
        <!-- Begin Sidebar -->
            <?php echo JLayoutHelper::render('joomla.edit.details', $this); ?>
        <!-- End Sidebar -->
    </div>
</form>
<script>
var template = jQuery('.core-value:last').clone();
function setupButton(){
    jQuery('.new-value').on('click',function(){
        var clone = template.clone();
        clone.find('.accordion-body').attr('id','collapse'+new Date().getTime());
        clone.find('.accordion-toggle').attr('href','#collapse'+new Date().getTime());
        jQuery(this).parent().parent().after(clone);
        jQuery(this).remove();
        setupButton();
        setupRm();
    });
}
function setupRm(){
    jQuery('.rm-value').each(function(){
        jQuery(this).off('click').on('click',function(){
            var root = jQuery(this).parent().parent();
            if(jQuery('.core-value').length==1){
                root.replaceWith(template.clone());
                setupButton();
                setupRm();
            }else{
                root.remove();
            }
        });
    });
}
function parseBehaviors(root){
    var beh = [];
    root.find('li').each(function(){
        if(jQuery(this).find('.behaviors-input').val()){
            beh.push(jQuery(this).find('.behaviors-input').val());
        }
    });
    return beh;
}

jQuery(document).ready(function(){
    jQuery('.new-value2').on('click',function(){
        var root = jQuery(this).parent();
        root.find('.behaviors').append('<li><input type="text" class="behaviors-input input-block-level" placeholder="Behavior"></li>');
    });
    setupButton();
    setupRm();
    jQuery('.value-select').on('click',function(event){
        event.stopPropagation();
    });
    jQuery('#core-values').sortable();
    jQuery('#core-values').on('shown',function(event){
        jQuery(event.target).parent().find('.icon-plus.main').removeClass('icon-plus').addClass('icon-minus');
    }).on('hidden',function(event){
        jQuery(event.target).parent().find('.icon-minus.main').removeClass('icon-minus').addClass('icon-plus');
    });

    var setRefresh = setInterval(function(){
        var arr = [];
        jQuery('.core-value').each(function(){
            var obj = {};
            if(jQuery(this).find('.value-select option:selected').val()!=0){
                obj.id=jQuery(this).find('.value-select option:selected').val();
                obj.name=jQuery.trim(jQuery(this).find('.value-select option:selected').text());
                obj.outcome=jQuery(this).find('.outcome-input').val();
                obj.behaviors=parseBehaviors(jQuery(this).find('.behaviors'));
                arr.push(obj);
            }
        });
        var parse = JSON.stringify(arr);
        jQuery('#jform_values').val(parse);
    },200);
});
</script>

The entire thing functions as a very dynamic and compressed custom form input. So that being said I must have the JSON served as a string and since there is really no point to me doing my own handling of it I don't bother with doing any save overrides in the model/table as the default functions for joomla "should" work. My next step is to override JTable/JModelAdmin almost completely to make sure I get this problem dealt with (although there is still a chance part of my code is where this comes from, though after hunting for hours already I have pretty much given up hope on that).

I have also added this to the Joomla Issue Tracker

Tracker

In there I have included a couple screenshots. Although it links to the same thing, also posted this as an issue on the cms repo.

GitHub

Either way if no answer gets found I will figure this out one way or another, I am thankful for anyone who can assist as this is a critical error in this component, not necessarily Joomla, but if it truly is a bug with 3.1 I can imagine I will be the first of many to run into this.

0

There are 0 answers