Cannot get FlexiGrid for CodeIgniter to work - php

I'm trying to use FlexiGrid plugin with CodeIgniter, but when the page displays the grid shows, but there are no records, and there is a little message saying that you should wait while it's processing, which never goes away...
I'm using one of my own databases, so I modified parts of the code to use fields in my database (id, username, email).
Flexigrid controller:
<?php
class Flexigrid extends CI_Controller
{
/* function Flexigrid ()
{
parent::Controller();
$this->load->helper('flexigrid');
}
*/
function __construct()
{
parent::__construct();
$this->load->helper('flexigrid');
}
function index()
{
//ver lib
/*
* 0 - display name
* 1 - width
* 2 - sortable
* 3 - align
* 4 - searchable (2 -> yes and default, 1 -> yes, 0 -> no.)
*/
$colModel['id'] = array('id', 40, TRUE, 'center', 2);
$colModel['username'] = array('username', 40, TRUE, 'center', 0);
$colModel['email'] = array('email', 180, TRUE, 'left', 1);
/*
* Aditional Parameters
*/
$gridParams = array(
'width' => 'auto',
'height' => 400,
'rp' => 15,
'rpOptions' => '[10,15,20,25,40]',
'pagestat' => 'Displaying: {from} to {to} of {total} items.',
'blockOpacity' => 0.5,
'title' => 'Hello',
'showTableToggleBtn' => true
);
/*
* 0 - display name
* 1 - bclass
* 2 - onpress
*/
$buttons[] = array('Delete', 'delete', 'test');
$buttons[] = array('separator');
$buttons[] = array('Select All', 'add', 'test');
$buttons[] = array('DeSelect All', 'delete', 'test');
$buttons[] = array('separator');
//Build js
//View helpers/flexigrid_helper.php for more information about the params on this function
$grid_js = build_grid_js('flex1', site_url("/ajax"), $colModel, 'id', 'asc', $gridParams, $buttons);
$data['js_grid'] = $grid_js;
$data['version'] = "0.36";
$data['download_file'] = "Flexigrid_CI_v0.36.rar";
$this->load->view('flexigrid', $data);
}
function example()
{
$data['version'] = "0.36";
$data['download_file'] = "Flexigrid_CI_v0.36.rar";
$this->load->view('example', $data);
}
}
?>
Flexigrid view (only changes in head to correct paths to css and js):
<head>
<title>Flexigrid Implemented in CodeIgniter</title>
<link href="<?=$this->config->item('base_url');?>assets/flexigrid/css/style.css" rel="stylesheet" type="text/css"/>
<link href="<?=$this->config->item('base_url');?>assets/flexigrid/css/flexigrid.css" rel="stylesheet"
type="text/css"/>
<script type="text/javascript" src="<?=base_url()?>assets/scripts/jquery-1.5.1.min.js"></script>
<script type="text/javascript"
src="<?=$this->config->item('base_url');?>assets/flexigrid/js/jquery.pack.js"></script>
<script type="text/javascript"
src="<?=$this->config->item('base_url');?>assets/flexigrid/js/flexigrid.pack.js"></script>
</head>
ajax_model:
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
/**
* Eye View Design CMS module Ajax Model
*
* PHP version 5
*
* #category CodeIgniter
* #package EVD CMS
* #author Frederico Carvalho
* #copyright 2008 Mentes 100Limites
* #version 0.1
*/
class Ajax_model extends CI_Model
{
/**
* Instanciar o CI
*/
/* public function Ajax_model()
{
parent::Model();
$this->CI =& get_instance();
}*/
function __construct()
{
parent::__construct();
$this->CI =& get_instance();
}
public function get_countries()
{
//Select table name
$table_name = "users";
//Build contents query
$this->db->select('id,username,email')->from($table_name);
$this->CI->flexigrid->build_query();
//Get contents
$return['records'] = $this->db->get();
//Build count query
$this->db->select('count(id) as record_count')->from($table_name);
$this->CI->flexigrid->build_query(FALSE);
$record_count = $this->db->get();
$row = $record_count->row();
//Get Record Count
$return['record_count'] = $row->record_count;
//Return all
return $return;
}
/**
* Remove country
* #param int country id
* #return boolean
*/
public function delete_country($country_id)
{
$delete_country = $this->db->query('DELETE FROM country WHERE id=' . $country_id);
return TRUE;
}
}
?>
Ajax controller (had to use the non-JSon extension code, so the other part is commented out, according to the instructions on the FlexiGrid web site. Also, I was a bit puzzled when modifying the $recorde_item array, because the example had id twice at the beginning. I thought this must be a mistake, but tried adding a second id row too, didn't help either):
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Ajax extends CI_Controller
{
/* function Ajax ()
{
parent::Controller();
$this->load->model('ajax_model');
$this->load->library('flexigrid');
}*/
function __construct()
{
parent::__construct();
$this->load->model('ajax_model');
$this->load->library('flexigrid');
}
function index()
{
//List of all fields that can be sortable. This is Optional.
//This prevents that a user sorts by a column that we dont want him to access, or that doesnt exist, preventing errors.
$valid_fields = array('id', 'username', 'email');
$this->flexigrid->validate_post('id', 'asc', $valid_fields);
//Get "countries"
$records = $this->ajax_model->get_countries();
//Init json build
if ($this->flexigrid->init_json_build($records['record_count'])) {
//Add records
foreach ($records['records']->result() as $row)
{
$record_item = array($row->id,
$row->username,
$row->email
);
$this->flexigrid->json_add_item($record_item);
}
//Last item added, close up.
$this->flexigrid->json_add_item();
}
//Print please
$this->output->set_header($this->config->item('json_header'));
$this->output->set_output($this->flexigrid->json_build);
/*$this->output->set_header($this->config->item('json_header'));*/
/*
* Json build WITH json_encode. If you do not have this function please read
* http://flexigrid.eyeviewdesign.com/index.php/flexigrid/example#s3 to know how to use the alternative
*/
/* foreach ($records['records']->result() as $row)
{
$record_items[] = array($row->id,
$row->id,
$row->iso,
$row->name,
'<span style=\'color:#ff4400\'>' . addslashes($row->printable_name) . '</span>',
$row->iso3,
$row->numcode,
'<a href=\'#\'><img border=\'0\' src=\'' . $this->config->item('base_url') . 'public/images/close.png\'></a> '
);
}
//Print please
$this->output->set_output($this->flexigrid->json_build($records['record_count'], $record_items));*/
}
//Delete Country
function deletec()
{
$countries_ids_post_array = split(",", $this->input->post('items'));
foreach ($countries_ids_post_array as $index => $country_id)
if (is_numeric($country_id) && $country_id > 1)
$this->ajax_model->delete_country($country_id);
$error = "Selected countries (id's: " . $this->input->post('items') . ") deleted with success";
$this->output->set_header($this->config->item('ajax_header'));
$this->output->set_output($error);
}
}
?>
Well, that should be it. I also had to change the "extends Controller" etc to "extends CI_Controller" since the code in the example seems to be for an older version of CodeIgniter.
But again, it doesn't work. I only get an empty grid. The database table does definitely have the fields I mentioned. I can't find any typos myself at least. It's also my default database in CodeIgniter, and I have no trouble connecting to it in other cases. And it is autoloaded, so I'm guessing that should work automatically, right? I shouldn't have to connect to the database manually here, since it uses the $db variable...
Any ideas then why it isn't working?

I Implemented Flexigrid with Codeigniter 2.1.0. You can download the code from my blog. Flexigrid in Codeigniter 2.1.0 and you can check the tutorial at Flexigrid Tutorial

Not sure if you still need this help or not, and i'm not sure i'm the best to teach as i'm still new to PHP and don't know the most "secure" practices, however, the following is what I was able to do with flexgrid and may help future users, or spark a more "secure" idea from a more advanced user. Without further adieu, my solution:
In the javasript grabbing the and using the json is EASY, check it:
keep in mind i tried to keep this very generic & removed all possible personal values ... it think
var flexset = {
url: 'https://www.yourSite.com/index.php/yourController/yourDBFunc',
dataType: 'json',
colModel : [
{display: 'A Column', name: 'a_col', width: 80, sortable: true, align: 'center'}, // where name=the name of the column in the database
{display: 'Another Column', name: 'another_col', width: 110, sortable: false, align: 'center'}
],
buttons : [ {name: "aButton", onpress: someFunction} ],
searchitems : [
{display: 'A Column (some more info if you want)', name: 'a_col', isdefault: true}, // where name=the name of the column in the database
{display: 'Quantity', name: 'charge'}
],
sortname: "a_col", // the name of the column in the database
sortorder: "desc", // desc or asc
usepager: true,
title: 'Some grid title',
useRp: true,
rp: 20,
showTableToggleBtn: true,
resizable: true,
width: "100%",
height: "400px",
singleSelect: true,
onSuccess: function() {
// do some work each time grid is loaded/refreshed, for instance, manipulate certain cells to have specific bgcolor based on content
console.log('onSuccess:\t\t', this, gridData);
console.log('Your inner table:\t', gridData.bDiv);
// you can get specific cells by searching from an attribute called "abbr" on each cell,
// it will = the same "name" you used when setting up the column that cell belongs too
console.log($(gridData.bDiv).find("td[abbr=transactionType]"));
}
}
$("#yourGrid").flexigrid(flexset);
In php, it gets a little harder as i use 2 functions, one public and one private. The private one actually makes the DB query while the public one is accessed by flexigrid to get the resulting data as json_encoded.
The Public Function
public function getJSONData() {
// to ensure its being pulled from a logged user,
// i always set at least one "where" var by hand,
// in this case, i am going to set the where to
// look for all rows containing this user's id,
// so i quickly grab the session var for the current id
$userID = $this->session->userdata('userID');
// should easily be greater than false in this case unless maybe it's id is 0?
// in any case, this is not the exact var i check, but same exact concept and my stuff works
if ($userID) {
// Return JSON data
$data = array();
// first i check post for a page number and go ahead and set the page
// this will be usful later in setting up the page query
$data['page'] = floatval(($this->input->post('page', TRUE))) ? floatval($this->input->post('page', TRUE)) : 1;
$value = array(
'select' => '*',
'where' => array( 'ownerID' => $userID ), // here you see my preset where clause, not absolutly necesary but useful, with some slight manipulation, you could easily redo this how you like
'orderBy' => array( 'a_col' => 'desc' ) // here i set a default orderby in case it's not provided by the post
);
// Get all other POSSIBLE posted data and save it to $value for passing to private func
if ($this->input->post('qtype', TRUE) && $this->input->post('query', TRUE)) $value['where'][$this->input->post('qtype', TRUE)] = $this->input->post('query', TRUE);
if ($this->input->post('sortname', TRUE) && $this->input->post('sortorder', TRUE)) $value['orderBy'] = array( $this->input->post('sortname', TRUE) => $this->input->post('sortorder', TRUE) );
if ($this->input->post('rp', TRUE)) $value['limit'] = array( $this->input->post('rp', TRUE) => (($data['page'] - 1) * floatval($this->input->post('rp', TRUE))) );
// call private function to get the desired data from db
$results = $this->getDBData($value);
// set return data's total count
$data['total'] = $results["total"]["absolute"];
// now we clearly define our "ROWS" for the return data to display in our final grid output
$data['rows'] = array();
foreach($results["rows"] as $k => $v) {
$data['rows'][] = array(
"id" => $v["id"],
"cell" => array( // keep in mind the order of these result here need match the order of the cols you set in the flexset options
$v["a_col"],
$v["another_col"]
)
);
};
// finally, json encode the data to be returned
$data = json_encode($data);
echo($data); // and echo the data to our ajax caller
};
}
The Private Funtion should be pretty self-explanitory
private function getDBData($val) {
$data = array( 'options'=>array(), 'rows'=>array(), 'total'=>array( 'absolute'=>0, 'offset'=>0 ) );
if (array_key_exists("from", $val)) {
$select = (array_key_exists("select", $val)) ? $val["select"] : "*";
$from = $val["from"]; // cannot be changed
$where = array(); // (col => value)
$orderBy = array(); // (name => direction)
$limit = array(); // "totalLimit" or (start => offset)
$limitType = "array";
$total = 0;
if (array_key_exists("where", $val)) if (gettype($val["where"]) === "array") $where = $val["where"];
if (array_key_exists("orderBy", $val)) if (gettype($val["orderBy"]) === "array") $orderBy = $val["orderBy"];
if (array_key_exists("limit", $val)) {
if (gettype($val["limit"]) === "array" || gettype($val["limit"]) === "integer" || gettype($val["limit"]) === "string") {
$limit = $val["limit"];
$limitType = gettype($val["limit"]);
};
};
$options = array( 'select'=>$select, 'from'=>$from, 'where'=>$where, 'orderBy'=>$orderBy, 'limit'=>$limit, 'limitType'=>$limitType );
$this->db->select($select);
$this->db->from($from);
if (count($where) > 0) $this->db->where($where);
if (count($orderBy) > 0) {
foreach ($orderBy as $k => $v) {
$this->db->order_by($k, $v);
};
};
if (gettype($limit) === "array") {
foreach ($limit as $k => $v) {
$this->db->limit($k, $v);
};
}
elseif (gettype($limit) === "integer" || gettype($limit) === "string") {
$this->db->limit($limit);
};
$records = $this->db->get();
$results = $records->result_array();
if (count($where) > 0) {
$total = $this->db->get_where($from, $where)->num_rows;
}
else {
$total = $this->db->get($from)->num_rows;
};
$data["options"] = $options;
$data["rows"] = $results;
$data["total"]["absolute"] = $total;
$data["total"]["offset"] = $records->num_rows;
};
return($data);
}

Related

PHP Correct way of stopping to load the view if not logged in

I have a website in PHP and a log in page. which is loaded when the class is construct if the session "Caisse_login" is not created. I have seen a bug which i have fixed with exit(), but I wanted to know if there is any other way of doing it.
here is the beginning of my class:
class caisses extends controller{
public function __Construct(){
$this->caisseModel = $this->model('caisse');
if(!isset($_SESSION['Caisse_login']) OR $_SESSION['Caisse_login']==FALSE){
$this->login();
exit();
}
}
everytime an url is entered it will call the associated method. so if i use the following URL:
caisselive/en/caisses/showTable/3
it will therefore create the class above caisses, and the following method:
public function showTable($tableId = 0){
// control if there is at least one table
if(!$this->caisseModel->countTable()){
flash('Transaction', 'No any table have been created', 'alert alert-warning');
redirect('caisses/transaction');
}else if($tableId==0){
$data = [
'table' => $this->caisseModel->getAllTable(),
];
$this->view('caisses/tableShow', $data);
}else{
// control if the ID provided is an existing table
if(!$this->caisseModel->getTempTablebyID($tableId)){
flash('Transaction', 'The table does not exist', 'alert alert-warning');
redirect('caisses/showTable');
}else{
//Display the table
$totalPrice = 0;
$productsQuery = $this->caisseModel->getTempTableProducts($tableId);
$tempTable = $this->caisseModel->getTempTablebyID($tableId);
$i=0;
$k=0;
$totalPricePaid=0;
$productsPaid = null;
foreach($productsQuery as $products_1){
// control if it has already been paid
if($products_1->paid==0){
for($j=0;$j<$products_1->qty;$j++){
$products[$i]['productName'] = $products_1->product_name;
$products[$i]['productID'] = $products_1->id_product;
$products[$i]['transactionId'] = $products_1->id;
$products[$i]['rebate'] = $products_1->percentage;
$products[$i]['rebateID'] = $products_1->id_rebate;
$products[$i]['rebateName'] = $products_1->name;
$products[$i]['qty'] = 1;
$products[$i]['price'] = $products_1->price_s;
$products[$i]['priceTotInt'] = number_format($products_1->price_s * 1 * (1-($products_1->percentage/100)), 2);
$totalPrice = $totalPrice + $products[$i]['priceTotInt'] ;
$i++;
}
//create the list of what it has already have been paid
}else{
$productsPaid[$k]['productName'] = $products_1->product_name;
$productsPaid[$k]['productID'] = $products_1->id_product;
$productsPaid[$k]['transactionId'] = $products_1->id;
$productsPaid[$k]['rebate'] = $products_1->percentage;
$productsPaid[$k]['rebateID'] = $products_1->id_rebate;
$productsPaid[$k]['rebateName'] = $products_1->name;
$productsPaid[$k]['qty'] = $products_1->qty;
$productsPaid[$k]['price'] = $products_1->price_s;
$productsPaid[$k]['priceTotInt'] = number_format($products_1->price_s * $products_1->qty * (1-($products_1->percentage/100)), 2);
$totalPricePaid = $totalPricePaid + $productsPaid[$k]['priceTotInt'] ;
$k++;
}
}
$data = [
'products' => $products,
'productsPaid' => $productsPaid,
'dateTable' => $tempTable->date,
'nameTable' => $tempTable->nameTable,
'tableId' => $tableId,
'rebate' => $this->caisseModel->getAllRebate(),
'totalPrice' => number_format($totalPrice, 2),
'totalPricePaid' => number_format($totalPricePaid, 2)
];
$this->view('caisses/tableShowDetail', $data);
}
}
}
what happens if the person is not loged in, it will load the 2 views, as explained I have kinda fixed it with the exit() when the class is constructed, but I am wondering if there is not a better way of doing it ?
There are different options. If you use a framework, you could use a method to redirect this request to another controller. Another option is to set a boolean to indicate that this user is not logged in and you process this parameter in your controller.
The easiest solution you have already implemented, just an exit, but this could cause some issues if you need to execute something on the end of every operation in a controller.

Manage files and directories in browser

I'm using a PHP based CMS called Couch (CouchCMS).
The interface is pretty simple. It's easy to edit page content and that's basically it.
I'm now trying to add functionality. I want to add a tab called "files". This tab when activated is going to display all my files. For example it will show me my "images" folder and all the other files located in my website's folder.
Is it possible to somehow with PHP show all my directories on my web page and from there add and remove them?
Try using this function: http://php.net/manual/en/function.readdir.php
But I think you can use jQuery + PHP libraries that do this for you:
https://www.sitepoint.com/10-jquery-file-manager-plugins/
This is a php code used for the Silverstripe cms, don't know if it might work on your side.
<?php
/**
* AssetAdmin is the 'file store' section of the CMS.
* It provides an interface for manipulating the File and Folder objects in the system.
*
* #package cms
* #subpackage assets
*/
class AssetAdmin extends LeftAndMain implements PermissionProvider{
private static $url_segment = 'assets';
private static $url_rule = '/$Action/$ID';
private static $menu_title = 'Files';
private static $tree_class = 'Folder';
/**
* Amount of results showing on a single page.
*
* #config
* #var int
*/
private static $page_length = 15;
/**
* #config
* #see Upload->allowedMaxFileSize
* #var int
*/
private static $allowed_max_file_size;
private static $allowed_actions = array(
'addfolder',
'delete',
'AddForm',
'DeleteItemsForm',
'SearchForm',
'getsubtree',
'movemarked',
'removefile',
'savefile',
'deleteUnusedThumbnails' => 'ADMIN',
'doSync',
'filter',
);
/**
* Return fake-ID "root" if no ID is found (needed to upload files into the root-folder)
*/
public function currentPageID() {
if(is_numeric($this->request->requestVar('ID'))) {
return $this->request->requestVar('ID');
} elseif (is_numeric($this->urlParams['ID'])) {
return $this->urlParams['ID'];
} elseif(Session::get("{$this->class}.currentPage")) {
return Session::get("{$this->class}.currentPage");
} else {
return 0;
}
}
/**
* Set up the controller, in particular, re-sync the File database with the assets folder./
*/
public function init() {
parent::init();
// Create base folder if it doesnt exist already
if(!file_exists(ASSETS_PATH)) Filesystem::makeFolder(ASSETS_PATH);
Requirements::javascript(CMS_DIR . "/javascript/AssetAdmin.js");
Requirements::javascript(CMS_DIR . '/javascript/CMSMain.GridField.js');
Requirements::add_i18n_javascript(CMS_DIR . '/javascript/lang', false, true);
Requirements::css(CMS_DIR . "/css/screen.css");
$frameworkDir = FRAMEWORK_DIR;
Requirements::customScript(<<<JS
_TREE_ICONS = {};
_TREE_ICONS['Folder'] = {
fileIcon: '$frameworkDir/javascript/tree/images/page-closedfolder.gif',
openFolderIcon: '$frameworkDir/javascript/tree/images/page-openfolder.gif',
closedFolderIcon: '$frameworkDir/javascript/tree/images/page-closedfolder.gif'
};
JS
);
CMSBatchActionHandler::register('delete', 'AssetAdmin_DeleteBatchAction', 'Folder');
}
/**
* Returns the files and subfolders contained in the currently selected folder,
* defaulting to the root node. Doubles as search results, if any search parameters
* are set through {#link SearchForm()}.
*
* #return SS_List
*/
public function getList() {
$folder = $this->currentPage();
$context = $this->getSearchContext();
// Overwrite name filter to search both Name and Title attributes
$context->removeFilterByName('Name');
$params = $this->request->requestVar('q');
$list = $context->getResults($params);
// Don't filter list when a detail view is requested,
// to avoid edge cases where the filtered list wouldn't contain the requested
// record due to faulty session state (current folder not always encoded in URL, see #7408).
if(!$folder->ID
&& $this->request->requestVar('ID') === null
&& ($this->request->param('ID') == 'field')
) {
return $list;
}
// Re-add previously removed "Name" filter as combined filter
// TODO Replace with composite SearchFilter once that API exists
if(!empty($params['Name'])) {
$list = $list->filterAny(array(
'Name:PartialMatch' => $params['Name'],
'Title:PartialMatch' => $params['Name']
));
}
// Always show folders at the top
$list = $list->sort('(CASE WHEN "File"."ClassName" = \'Folder\' THEN 0 ELSE 1 END), "Name"');
// If a search is conducted, check for the "current folder" limitation.
// Otherwise limit by the current folder as denoted by the URL.
if(empty($params) || !empty($params['CurrentFolderOnly'])) {
$list = $list->filter('ParentID', $folder->ID);
}
// Category filter
if(!empty($params['AppCategory'])
&& !empty(File::config()->app_categories[$params['AppCategory']])
) {
$exts = File::config()->app_categories[$params['AppCategory']];
$list = $list->filter('Name:PartialMatch', $exts);
}
// Date filter
if(!empty($params['CreatedFrom'])) {
$fromDate = new DateField(null, null, $params['CreatedFrom']);
$list = $list->filter("Created:GreaterThanOrEqual", $fromDate->dataValue().' 00:00:00');
}
if(!empty($params['CreatedTo'])) {
$toDate = new DateField(null, null, $params['CreatedTo']);
$list = $list->filter("Created:LessThanOrEqual", $toDate->dataValue().' 23:59:59');
}
return $list;
}
public function getEditForm($id = null, $fields = null) {
$form = parent::getEditForm($id, $fields);
$folder = ($id && is_numeric($id)) ? DataObject::get_by_id('Folder', $id, false) : $this->currentPage();
$fields = $form->Fields();
$title = ($folder && $folder->exists()) ? $folder->Title : _t('AssetAdmin.FILES', 'Files');
$fields->push(new HiddenField('ID', false, $folder ? $folder->ID : null));
// File listing
$gridFieldConfig = GridFieldConfig::create()->addComponents(
new GridFieldToolbarHeader(),
new GridFieldSortableHeader(),
new GridFieldFilterHeader(),
new GridFieldDataColumns(),
new GridFieldPaginator(self::config()->page_length),
new GridFieldEditButton(),
new GridFieldDeleteAction(),
new GridFieldDetailForm(),
GridFieldLevelup::create($folder->ID)->setLinkSpec('admin/assets/show/%d')
);
$gridField = GridField::create('File', $title, $this->getList(), $gridFieldConfig);
$columns = $gridField->getConfig()->getComponentByType('GridFieldDataColumns');
$columns->setDisplayFields(array(
'StripThumbnail' => '',
// 'Parent.FileName' => 'Folder',
'Title' => _t('File.Name'),
'Created' => _t('AssetAdmin.CREATED', 'Date'),
'Size' => _t('AssetAdmin.SIZE', 'Size'),
));
$columns->setFieldCasting(array(
'Created' => 'Date->Nice'
));
$gridField->setAttribute(
'data-url-folder-template',
Controller::join_links($this->Link('show'), '%s')
);
if($folder->canCreate()) {
$uploadBtn = new LiteralField(
'UploadButton',
sprintf(
'<a class="ss-ui-button ss-ui-action-constructive cms-panel-link" data-pjax-target="Content" data-icon="drive-upload" href="%s">%s</a>',
Controller::join_links(singleton('CMSFileAddController')->Link(), '?ID=' . $folder->ID),
_t('Folder.UploadFilesButton', 'Upload')
)
);
} else {
$uploadBtn = null;
}
if(!$folder->hasMethod('canAddChildren') || ($folder->hasMethod('canAddChildren') && $folder->canAddChildren())) {
// TODO Will most likely be replaced by GridField logic
$addFolderBtn = new LiteralField(
'AddFolderButton',
sprintf(
'<a class="ss-ui-button ss-ui-action-constructive cms-add-folder-link" data-icon="add" data-url="%s" href="%s">%s</a>',
Controller::join_links($this->Link('AddForm'), '?' . http_build_query(array(
'action_doAdd' => 1,
'ParentID' => $folder->ID,
'SecurityID' => $form->getSecurityToken()->getValue()
))),
Controller::join_links($this->Link('addfolder'), '?ParentID=' . $folder->ID),
_t('Folder.AddFolderButton', 'Add folder')
)
);
} else {
$addFolderBtn = '';
}
if($folder->canEdit()) {
$syncButton = new LiteralField(
'SyncButton',
sprintf(
'<a class="ss-ui-button ss-ui-action ui-button-text-icon-primary ss-ui-button-ajax" data-icon="arrow-circle-double" title="%s" href="%s">%s</a>',
_t('AssetAdmin.FILESYSTEMSYNCTITLE', 'Update the CMS database entries of files on the filesystem. Useful when new files have been uploaded outside of the CMS, e.g. through FTP.'),
$this->Link('doSync'),
_t('AssetAdmin.FILESYSTEMSYNC','Sync files')
)
);
} else {
$syncButton = null;
}
// Move existing fields to a "details" tab, unless they've already been tabbed out through extensions.
// Required to keep Folder->getCMSFields() simple and reuseable,
// without any dependencies into AssetAdmin (e.g. useful for "add folder" views).
if(!$fields->hasTabset()) {
$tabs = new TabSet('Root',
$tabList = new Tab('ListView', _t('AssetAdmin.ListView', 'List View')),
$tabTree = new Tab('TreeView', _t('AssetAdmin.TreeView', 'Tree View'))
);
$tabList->addExtraClass("content-listview cms-tabset-icon list");
$tabTree->addExtraClass("content-treeview cms-tabset-icon tree");
if($fields->Count() && $folder->exists()) {
$tabs->push($tabDetails = new Tab('DetailsView', _t('AssetAdmin.DetailsView', 'Details')));
$tabDetails->addExtraClass("content-galleryview cms-tabset-icon edit");
foreach($fields as $field) {
$fields->removeByName($field->getName());
$tabDetails->push($field);
}
}
$fields->push($tabs);
}
// we only add buttons if they're available. User might not have permission and therefore
// the button shouldn't be available. Adding empty values into a ComposteField breaks template rendering.
$actionButtonsComposite = CompositeField::create()->addExtraClass('cms-actions-row');
if($uploadBtn) $actionButtonsComposite->push($uploadBtn);
if($addFolderBtn) $actionButtonsComposite->push($addFolderBtn);
if($syncButton) $actionButtonsComposite->push($syncButton);
// List view
$fields->addFieldsToTab('Root.ListView', array(
$actionsComposite = CompositeField::create(
$actionButtonsComposite
)->addExtraClass('cms-content-toolbar field'),
$gridField
));
$treeField = new LiteralField('Tree', '');
// Tree view
$fields->addFieldsToTab('Root.TreeView', array(
clone $actionsComposite,
// TODO Replace with lazy loading on client to avoid performance hit of rendering potentially unused views
new LiteralField(
'Tree',
FormField::create_tag(
'div',
array(
'class' => 'cms-tree',
'data-url-tree' => $this->Link('getsubtree'),
'data-url-savetreenode' => $this->Link('savetreenode')
),
$this->SiteTreeAsUL()
)
)
));
// Move actions to "details" tab (they don't make sense on list/tree view)
$actions = $form->Actions();
$saveBtn = $actions->fieldByName('action_save');
$deleteBtn = $actions->fieldByName('action_delete');
$actions->removeByName('action_save');
$actions->removeByName('action_delete');
if(($saveBtn || $deleteBtn) && $fields->fieldByName('Root.DetailsView')) {
$fields->addFieldToTab(
'Root.DetailsView',
CompositeField::create($saveBtn,$deleteBtn)->addExtraClass('Actions')
);
}
$fields->setForm($form);
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
// TODO Can't merge $FormAttributes in template at the moment
$form->addExtraClass('cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses());
$form->setAttribute('data-pjax-fragment', 'CurrentForm');
$form->Fields()->findOrMakeTab('Root')->setTemplate('CMSTabSet');
$this->extend('updateEditForm', $form);
return $form;
}
public function addfolder($request) {
$obj = $this->customise(array(
'EditForm' => $this->AddForm()
));
if($request->isAjax()) {
// Rendering is handled by template, which will call EditForm() eventually
$content = $obj->renderWith($this->getTemplatesWithSuffix('_Content'));
} else {
$content = $obj->renderWith($this->getViewer('show'));
}
return $content;
}
public function delete($data, $form) {
$className = $this->stat('tree_class');
$record = DataObject::get_by_id($className, Convert::raw2sql($data['ID']));
if($record && !$record->canDelete()) return Security::permissionFailure();
if(!$record || !$record->ID) throw new HTTPResponse_Exception("Bad record ID #" . (int)$data['ID'], 404);
$parentID = $record->ParentID;
$record->delete();
$this->setCurrentPageID(null);
$this->response->addHeader('X-Status', rawurlencode(_t('LeftAndMain.DELETED', 'Deleted.')));
$this->response->addHeader('X-Pjax', 'Content');
return $this->redirect(Controller::join_links($this->Link('show'), $parentID ? $parentID : 0));
}
/**
* Get the search context
*
* #return SearchContext
*/
public function getSearchContext() {
$context = singleton('File')->getDefaultSearchContext();
// Namespace fields, for easier detection if a search is present
foreach($context->getFields() as $field) $field->setName(sprintf('q[%s]', $field->getName()));
foreach($context->getFilters() as $filter) $filter->setFullName(sprintf('q[%s]', $filter->getFullName()));
// Customize fields
$context->addField(
new HeaderField('q[Date]', _t('CMSSearch.FILTERDATEHEADING', 'Date'), 4)
);
$context->addField(
DateField::create(
'q[CreatedFrom]',
_t('CMSSearch.FILTERDATEFROM', 'From')
)->setConfig('showcalendar', true)
);
$context->addField(
DateField::create(
'q[CreatedTo]',
_t('CMSSearch.FILTERDATETO', 'To')
)->setConfig('showcalendar', true)
);
$appCategories = array(
'image' => _t('AssetAdmin.AppCategoryImage', 'Image'),
'audio' => _t('AssetAdmin.AppCategoryAudio', 'Audio'),
'mov' => _t('AssetAdmin.AppCategoryVideo', 'Video'),
'flash' => _t('AssetAdmin.AppCategoryFlash', 'Flash', 'The fileformat'),
'zip' => _t('AssetAdmin.AppCategoryArchive', 'Archive', 'A collection of files'),
'doc' => _t('AssetAdmin.AppCategoryDocument', 'Document')
);
$context->addField(
$typeDropdown = new DropdownField(
'q[AppCategory]',
_t('AssetAdmin.Filetype', 'File type'),
$appCategories
)
);
$typeDropdown->setEmptyString(' ');
$context->addField(
new CheckboxField('q[CurrentFolderOnly]', _t('AssetAdmin.CurrentFolderOnly', 'Limit to current folder?'))
);
$context->getFields()->removeByName('q[Title]');
return $context;
}
/**
* Returns a form for filtering of files and assets gridfield.
* Result filtering takes place in {#link getList()}.
*
* #return Form
* #see AssetAdmin.js
*/
public function SearchForm() {
$folder = $this->currentPage();
$context = $this->getSearchContext();
$fields = $context->getSearchFields();
$actions = new FieldList(
FormAction::create('doSearch', _t('CMSMain_left_ss.APPLY_FILTER', 'Apply Filter'))
->addExtraClass('ss-ui-action-constructive'),
Object::create('ResetFormAction', 'clear', _t('CMSMain_left_ss.RESET', 'Reset'))
);
$form = new Form($this, 'filter', $fields, $actions);
$form->setFormMethod('GET');
$form->setFormAction(Controller::join_links($this->Link('show'), $folder->ID));
$form->addExtraClass('cms-search-form');
$form->loadDataFrom($this->request->getVars());
$form->disableSecurityToken();
// This have to match data-name attribute on the gridfield so that the javascript selectors work
$form->setAttribute('data-gridfield', 'File');
return $form;
}
public function AddForm() {
$folder = singleton('Folder');
$form = CMSForm::create(
$this,
'AddForm',
new FieldList(
new TextField("Name", _t('File.Name')),
new HiddenField('ParentID', false, $this->request->getVar('ParentID'))
),
new FieldList(
FormAction::create('doAdd', _t('AssetAdmin_left_ss.GO','Go'))
->addExtraClass('ss-ui-action-constructive')->setAttribute('data-icon', 'accept')
->setTitle(_t('AssetAdmin.ActionAdd', 'Add folder'))
)
)->setHTMLID('Form_AddForm');
$form->setResponseNegotiator($this->getResponseNegotiator());
$form->setTemplate($this->getTemplatesWithSuffix('_EditForm'));
// TODO Can't merge $FormAttributes in template at the moment
$form->addExtraClass('add-form cms-add-form cms-edit-form cms-panel-padded center ' . $this->BaseCSSClasses());
return $form;
}
/**
* Add a new group and return its details suitable for ajax.
*
* #todo Move logic into Folder class, and use LeftAndMain->doAdd() default implementation.
*/
public function doAdd($data, $form) {
$class = $this->stat('tree_class');
// check create permissions
if(!singleton($class)->canCreate()) return Security::permissionFailure($this);
// check addchildren permissions
if(
singleton($class)->hasExtension('Hierarchy')
&& isset($data['ParentID'])
&& is_numeric($data['ParentID'])
&& $data['ParentID']
) {
$parentRecord = DataObject::get_by_id($class, $data['ParentID']);
if(
$parentRecord->hasMethod('canAddChildren')
&& !$parentRecord->canAddChildren()
) return Security::permissionFailure($this);
} else {
$parentRecord = null;
}
$parent = (isset($data['ParentID']) && is_numeric($data['ParentID'])) ? (int)$data['ParentID'] : 0;
$name = (isset($data['Name'])) ? basename($data['Name']) : _t('AssetAdmin.NEWFOLDER',"NewFolder");
if(!$parentRecord || !$parentRecord->ID) $parent = 0;
// Get the folder to be created
if($parentRecord && $parentRecord->ID) $filename = $parentRecord->FullPath . $name;
else $filename = ASSETS_PATH . '/' . $name;
// Actually create
if(!file_exists(ASSETS_PATH)) {
mkdir(ASSETS_PATH);
}
$record = new Folder();
$record->ParentID = $parent;
$record->Name = $record->Title = basename($filename);
// Ensure uniqueness
$i = 2;
$baseFilename = substr($record->Filename, 0, -1) . '-';
while(file_exists($record->FullPath)) {
$record->Filename = $baseFilename . $i . '/';
$i++;
}
$record->Name = $record->Title = basename($record->Filename);
$record->write();
mkdir($record->FullPath);
chmod($record->FullPath, Filesystem::config()->file_create_mask);
if($parentRecord) {
return $this->redirect(Controller::join_links($this->Link('show'), $parentRecord->ID));
} else {
return $this->redirect($this->Link());
}
}
/**
* Custom currentPage() method to handle opening the 'root' folder
*/
public function currentPage() {
$id = $this->currentPageID();
if($id && is_numeric($id) && $id > 0) {
$folder = DataObject::get_by_id('Folder', $id);
if($folder && $folder->exists()) {
return $folder;
}
}
$this->setCurrentPageID(null);
return new Folder();
}
public function getSiteTreeFor($className, $rootID = null, $childrenMethod = null, $numChildrenMethod = null, $filterFunction = null, $minNodeCount = 30) {
if (!$childrenMethod) $childrenMethod = 'ChildFolders';
if (!$numChildrenMethod) $numChildrenMethod = 'numChildFolders';
return parent::getSiteTreeFor($className, $rootID, $childrenMethod, $numChildrenMethod, $filterFunction, $minNodeCount);
}
public function getCMSTreeTitle() {
return Director::absoluteBaseURL() . "assets";
}
public function SiteTreeAsUL() {
return $this->getSiteTreeFor($this->stat('tree_class'), null, 'ChildFolders', 'numChildFolders');
}
//------------------------------------------------------------------------------------------//
// Data saving handlers
/**
* Can be queried with an ajax request to trigger the filesystem sync. It returns a FormResponse status message
* to display in the CMS
*/
public function doSync() {
$message = Filesystem::sync();
$this->response->addHeader('X-Status', rawurlencode($message));
return;
}
/**
* #################################
* Garbage collection.
* #################################
*/
/**
* Removes all unused thumbnails from the file store
* and returns the status of the process to the user.
*/
public function deleteunusedthumbnails($request) {
// Protect against CSRF on destructive action
if(!SecurityToken::inst()->checkRequest($request)) return $this->httpError(400);
$count = 0;
$thumbnails = $this->getUnusedThumbnails();
if($thumbnails) {
foreach($thumbnails as $thumbnail) {
unlink(ASSETS_PATH . "/" . $thumbnail);
$count++;
}
}
$message = _t(
'AssetAdmin.THUMBSDELETED',
'{count} unused thumbnails have been deleted',
array('count' => $count)
);
$this->response->addHeader('X-Status', rawurlencode($message));
return;
}
/**
* Creates array containg all unused thumbnails.
*
* Array is created in three steps:
* 1. Scan assets folder and retrieve all thumbnails
* 2. Scan all HTMLField in system and retrieve thumbnails from them.
* 3. Count difference between two sets (array_diff)
*
* #return array
*/
private function getUnusedThumbnails() {
$allThumbnails = array();
$usedThumbnails = array();
$dirIterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(ASSETS_PATH));
$classes = ClassInfo::subclassesFor('SiteTree');
if($dirIterator) {
foreach($dirIterator as $file) {
if($file->isFile()) {
if(strpos($file->getPathname(), '_resampled') !== false) {
$pathInfo = pathinfo($file->getPathname());
if(in_array(strtolower($pathInfo['extension']), array('jpeg', 'jpg', 'jpe', 'png', 'gif'))) {
$path = str_replace('\\','/', $file->getPathname());
$allThumbnails[] = substr($path, strpos($path, '/assets/') + 8);
}
}
}
}
}
if($classes) {
foreach($classes as $className) {
$SNG_class = singleton($className);
$objects = DataObject::get($className);
if($objects !== NULL) {
foreach($objects as $object) {
foreach($SNG_class->db() as $fieldName => $fieldType) {
if($fieldType == 'HTMLText') {
$url1 = HTTP::findByTagAndAttribute($object->$fieldName,array('img' => 'src'));
if($url1 != NULL) {
$usedThumbnails[] = substr($url1[0], strpos($url1[0], '/assets/') + 8);
}
if($object->latestPublished > 0) {
$object = Versioned::get_latest_version($className, $object->ID);
$url2 = HTTP::findByTagAndAttribute($object->$fieldName, array('img' => 'src'));
if($url2 != NULL) {
$usedThumbnails[] = substr($url2[0], strpos($url2[0], '/assets/') + 8);
}
}
}
}
}
}
}
}
return array_diff($allThumbnails, $usedThumbnails);
}
/**
* #param bool $unlinked
* #return ArrayList
*/
public function Breadcrumbs($unlinked = false) {
$items = parent::Breadcrumbs($unlinked);
// The root element should explicitly point to the root node.
// Uses session state for current record otherwise.
$items[0]->Link = Controller::join_links(singleton('AssetAdmin')->Link('show'), 0);
// If a search is in progress, don't show the path
if($this->request->requestVar('q')) {
$items = $items->limit(1);
$items->push(new ArrayData(array(
'Title' => _t('LeftAndMain.SearchResults', 'Search Results'),
'Link' => Controller::join_links($this->Link(), '?' . http_build_query(array('q' => $this->request->requestVar('q'))))
)));
}
// If we're adding a folder, note that in breadcrumbs as well
if($this->request->param('Action') == 'addfolder') {
$items->push(new ArrayData(array(
'Title' => _t('Folder.AddFolderButton', 'Add folder'),
'Link' => false
)));
}
return $items;
}
public function providePermissions() {
$title = _t("AssetAdmin.MENUTITLE", LeftAndMain::menu_title_for_class($this->class));
return array(
"CMS_ACCESS_AssetAdmin" => array(
'name' => _t('CMSMain.ACCESS', "Access to '{title}' section", array('title' => $title)),
'category' => _t('Permission.CMS_ACCESS_CATEGORY', 'CMS Access')
)
);
}
}
/**
* Delete multiple {#link Folder} records (and the associated filesystem nodes).
* Usually used through the {#link AssetAdmin} interface.
*
* #package cms
* #subpackage batchactions
*/
class AssetAdmin_DeleteBatchAction extends CMSBatchAction {
public function getActionTitle() {
// _t('AssetAdmin_left_ss.SELECTTODEL','Select the folders that you want to delete and then click the button below')
return _t('AssetAdmin_DeleteBatchAction.TITLE', 'Delete folders');
}
public function run(SS_List $records) {
$status = array(
'modified'=>array(),
'deleted'=>array()
);
foreach($records as $record) {
$id = $record->ID;
// Perform the action
if($record->canDelete()) $record->delete();
$status['deleted'][$id] = array();
$record->destroy();
unset($record);
}
return Convert::raw2json($status);
}
}

Laravel 5 - Clean code, where to keep business logic (controller example)

Below example of 'store' method of my controller Admin/MoviesController. It already seems quite big, and 'update' method will be even bigger.
The algoritm is:
Validate request data in CreateMovieRequest and create new movie
with all fillable fields.
Upload poster
Fill and save all important, but not required fields (Meta title, Meta Description..)
Then 4 blocks of code with parsing and attaching to movie of Genres, Actors, Directors, Countries.
Request of IMDB's rating using third-party API
My questions:
Should I just move all this code to Model and divide it into smaller methods like: removeGenres($id), addGenres(Request $request), ...
Are there some best practices? I'm talking not about MVC, but Laravel's features. At the moment to keep some logic behind the scene I'm using only Request for validation.
public function store(CreateMovieRequest $request) {
$movie = Movies::create($request->except('poster'));
/* Uploading poster */
if ($request->hasFile('poster')) {
$poster = \Image::make($request->file('poster'));
$poster->fit(250, 360, function ($constraint) {
$constraint->upsize();
});
$path = storage_path() . '/images/movies/'.$movie->id.'/';
if(! \File::exists($path)) {
\File::makeDirectory($path);
}
$filename = time() . '.' . $request->file('poster')->getClientOriginalExtension();
$poster->save($path . $filename);
$movie->poster = $filename;
}
/* If 'Meta Title' is empty, then fill it with the name of the movie */
if ( empty($movie->seo_title) ) {
$movie->seo_title = $movie->title;
}
/* If 'Meta Description' is empty, then fill it with the description of the movie */
if ( empty($movie->seo_description) ) {
$movie->seo_description = $movie->description;
}
// Apply all changes
$movie->save();
/* Parsing comma separated string of genres
* and attaching them to movie */
if (!empty($request->input('genres'))) {
$genres = explode(',', $request->input('genres'));
foreach($genres as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$genre = Genre::where('name', $name)->first();
/* If such genre doesn't exists in 'genres' table
* then we create a new one */
if ( empty($genre) ) {
$genre = new Genre();
$genre->fill(['name' => $name])->save();
}
$movie->genres()->attach($genre->id);
}
}
/* Parsing comma separated string of countries
* and attaching them to movie */
if (!empty($request->input('countries'))) {
$countries = explode(',', $request->input('countries'));
foreach($countries as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$country = Country::where('name', $name)->first();
if ( empty($country) ) {
$country = new Country();
$country->fill(['name' => $name])->save();
}
$movie->countries()->attach($country->id);
}
}
/* Parsing comma separated string of directors
* and attaching them to movie */
if (!empty($request->input('directors'))) {
$directors = explode(',', $request->input('directors'));
foreach($directors as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
// Actors and Directors stored in the same table 'actors'
$director = Actor::where('fullname', trim($name))->first();
if ( empty($director) ) {
$director = new Actor();
$director->fill(['fullname' => $name])->save();
}
// Save this relation to 'movie_director' table
$movie->directors()->attach($director->id);
}
}
/* Parsing comma separated string of actors
* and attaching them to movie */
if (!empty($request->input('actors'))) {
$actors = explode(',', $request->input('actors'));
foreach($actors as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$actor = Actor::where('fullname', $name)->first();
if ( empty($actor) ) {
$actor = new Actor();
$actor->fill(['fullname' => $name])->save();
}
// Save this relation to 'movie_actor' table
$movie->actors()->attach($actor->id);
}
}
// Updating IMDB and Kinopoisk ratings
if (!empty($movie->kinopoisk_id)) {
$content = Curl::get('http://rating.kinopoisk.ru/'.$movie->kinopoisk_id.'.xml');
$xml = new \SimpleXMLElement($content[0]->getContent());
$movie->rating_kinopoisk = (double) $xml->kp_rating;
$movie->rating_imdb = (double) $xml->imdb_rating;
$movie->num_votes_kinopoisk = (int) $xml->kp_rating['num_vote'];
$movie->num_votes_imdb = (int) $xml->imdb_rating['num_vote'];
$movie->save();
}
return redirect('/admin/movies');
}
You need to think on how you could re-utilize the code if you need to use it in another classes or project modules. For starting, you could do something like this:
Movie model, can improved in order to:
Manage the way on how the attributes are setted
Create nice functions in functions include/manage the data of relationships
Take a look how the Movie implements the functions:
class Movie{
public function __construct(){
//If 'Meta Title' is empty, then fill it with the name of the movie
$this->seo_title = empty($movie->seo_title)
? $movie->title
: $otherValue;
//If 'Meta Description' is empty,
//then fill it with the description of the movie
$movie->seo_description = empty($movie->seo_description)
? $movie->description
: $anotherValue;
$this->updateKinopoisk();
}
/*
* Parsing comma separated string of countries and attaching them to movie
*/
public function attachCountries($countries){
foreach($countries as $item) {
$name = mb_strtolower(trim($item), 'UTF-8');
$country = Country::where('name', $name)->first();
if ( empty($country) ) {
$country = new Country();
$country->fill(['name' => $name])->save();
}
$movie->countries()->attach($country->id);
}
}
/*
* Update Kinopoisk information
*/
public function updateKinopoisk(){}
/*
* Directors
*/
public function attachDirectors($directors){ ... }
/*
* Actores
*/
public function attachActors($actors){ ... }
/*
* Genders
*/
public function attachActors($actors){ ... }
}
Poster, you may considere using a service provider (I will show this example because I do not know your Poster model
looks like):
public class PosterManager{
public static function upload($file, $movie){
$poster = \Image::make($file);
$poster->fit(250, 360, function ($constraint) {
$constraint->upsize();
});
$path = config('app.images') . $movie->id.'/';
if(! \File::exists($path)) {
\File::makeDirectory($path);
}
$filename = time() . '.' . $file->getClientOriginalExtension();
$poster->save($path . $filename);
return $poster;
}
}
Config file
Try using config files to store relevant application constanst/data, for example, to store movie images path:
'images' => storage_path() . '/images/movies/';
Now, you are able to call $path = config('app.images'); globally. If you need to change the path only setting the config file is necessary.
Controllers as injected class.
Finally, the controller is used as a class where you only need to inject code:
public function store(CreateMovieRequest $request) {
$movie = Movies::create($request->except('poster'));
/* Uploading poster */
if ($request->hasFile('poster')) {
$file = $request->file('poster');
$poster = \PosterManager::upload($file, $movie);
$movie->poster = $poster->filename;
}
if (!empty($request->input('genres'))) {
$genres = explode(',', $request->input('genres'));
$movie->attachGenders($genders);
}
// movie->attachDirectors();
// movie->attachCountries();
// Apply all changes
$movie->save();
return redirect('/admin/movies');
}

Magento admin grid column formatting - renderer?

I want the sales_order_create grid to show both the price and special price in the same column, and I've done so by adding:
->addAttributeToSelect('special_price')
To the _prepareCollection() function, and then adding:
$this->addColumn('special_price', array(
'header' => Mage::helper('sales')->__('Special Price'),
'sortable' => false,
'index' => array('price', 'special_price'),
'type' => 'concat',
'separator' => ' -- ',
'width' => '140px',
));
To the _prepareColumns() function.
This works! The result is a new column which, as an example, displays:
79.9800 -- 34.9900
How do I format this so it's in a currency format? £xx.xx
Also, is it possible to style it? So it looks like this: £79.98 (£34.99)
If it's not possible to style, just in currency format would be great.
I think it's something to do with the renderer but I'm new to Magento so would need it explaining in a basic way if thats ok.
Thanks
I've managed to get this working, my code is horrible, but it may help anyone who stumbles across this in the future.
I looked at the price column and saw:
'renderer' => 'adminhtml/sales_order_create_search_grid_renderer_price',
After navigating to app/code/core/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer I noticed a concat renderer.
I copied Concat.php from this directory and created a local version, I named it Special.php to avoid any conflicts:
/app/code/local/Mage/Adminhtml/Block/Widget/Grid/Column/Renderer/Special.php
I then added this renderer to my special_price column:
'renderer' => 'adminhtml/sales_order_create_search_grid_renderer_special',
My Special.php code is as follows (warning: this code is pretty horrible, but it works so I'm happy):
class Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Special
extends Mage_Adminhtml_Block_Widget_Grid_Column_Renderer_Abstract
{
/**
* Renders grid column
*
* #param Varien_Object $row
* #return string
*/
public function render(Varien_Object $row)
{
$dataArr = array();
foreach ($this->getColumn()->getIndex() as $index) {
if ($data = $row->getData($index)) {
//$dataArr[] = $data;
$dataArr[] = number_format((float)$data, 2, '.', '');
}
}
$data = join($this->getColumn()->getSeparator(), $dataArr);
// TODO run column type renderer
$price = '';
$special = '';
if (strlen($dataArr[0]) > 0) {
if (strlen($dataArr[1]) > 0) {
$price = '<span style="text-decoration:line-through">£' . $dataArr[0] . '</span>';
$special = ' £' . $dataArr[1];
}
else {
$price = '£' . $dataArr[0];
$special = '';
}
}
return $price . $special;
}
}
The result is if there is no price (grouped product) the entry is blank, if there is no special the entry is the RRP and if there is a special the entry is RRP special price
The code I'm sure can be improved but it works

custom status button in joomla component

The Joomla com_content has small toggle button for the article's status "publish" to publish or unpublish the articles. So, I want to have same type of toggle button in my component also to approve or disapprove users.
Now, I want some advice from experts on how to go about. I have gone through com_content but I don't really understand that how should i begin. I can't understand com_content approach and code because I am not coding in line with Joomla 2.5.
How should I start with this ?
i made it work on my own. let me share the experience for those who will need it in future. my table field or database field is approved and its value is 0 initially (which means the record is unapproved by the admin)
in my layout/default page i have the code as below for the toggle button:
<?php
$k = 0;
for ($i=0, $n=count( $this->items ); $i < $n; $i++)
{
$row = &$this->items[$i];
..................
..................
?>
..................
<td align="center">
<?php echo JHtml::_('job.approve', $row->approved, $i); ?>
</td>
Note that i've $row->approved which is my field from db. then i've job.approve for which i have created a job.php file and placed in helpers directory. the code for job.php is:
<?php
// no direct access
defined('_JEXEC') or die;
/**
* #package Joomla.Administrator
* #subpackage com_content
*/
abstract class JHtmlJob
{
/**
* #param int $value The state value
* #param int $i
*/
static function approve($value = 0, $i)
{
// Array of image, task, title, action
$states = array(
0 => array('disabled.png', 'approve', 'Unapproved', 'Toggle to approve'),
1 => array('tick.png', 'unapprove', 'Approved', 'Toggle to unapprove'),
);
$state = JArrayHelper::getValue($states, (int) $value, $states[1]);
$html = JHtml::_('image', 'admin/'.$state[0], JText::_($state[2]), NULL, true);
//if ($canChange) {
$html = '<a href="#" onclick="return listItemTask(\'cb'.$i.'\',\''.$state[1].'\')" title="'.JText::_($state[3]).'">'
. $html.'</a>';
//}
return $html;
}
}
Then i've registered two tasks in controller as approve and unapprove along with approve function:
public function __construct($config = array())
{
parent::__construct($config);
$this->registerTask('unapprove', 'approve');
}
/**
* Method to toggle the featured setting of a list of articles.
*
* #return void
* #since 1.6
*/
function approve()
{
// Initialise variables.
$user = JFactory::getUser();
$ids = JRequest::getVar('cid', array(), '', 'array');
$values = array('approve' => 1, 'unapprove' => 0);
$task = $this->getTask();
$value = JArrayHelper::getValue($values, $task, 0, 'int');
if (empty($ids)) {
JError::raiseWarning(500, JText::_('JERROR_NO_ITEMS_SELECTED'));
}
else {
// Get the model.
$model = $this->getModel('jobs');
// Publish the items.
if (!$model->approve($ids, $value)) {
JError::raiseWarning(500, $model->getError());
}
}
$redirectTo = JRoute::_('index.php?option='.JRequest::getVar('option'));
$this->setRedirect($redirectTo);
}
Thereafter, i've added the following function in model to update the value to 0 or 1.
function approve($cid, $publish) {
if (count( $cid ))
{
JArrayHelper::toInteger($cid);
$cids = implode( ',', $cid );
$query = 'UPDATE #__tbljobs'
. ' SET approved = '.(int) $publish
. ' WHERE id IN ( '.$cids.' )';
$this->_db->setQuery( $query );
if (!$this->_db->query()) {
$this->setError($this->_db->getErrorMsg());
return false;
}
}
return true;
}
Please don't forget to include the job.php file in your view/view.html.php file as below:
<?php
defined('_JEXEC') or die('Restricted Access');
jimport('joomla.application.component.view');
require_once JPATH_COMPONENT .'/helpers/job.php';
Class JobsViewListJobs extends JView
{
And remember i am not using JForm nor my code is in joomla 1.7 style. But i am following the MVC architecture. So, i am not sure if my method will work for people who are coding in joomla 1.7 and above style.
You can use this to create publish button Read more-
JHtml::_('jgrid.published', $item->state, $i, 'articles.', $canChange);
Or this html-
<?php if($item->approve){?>
<td class="center">
<a class="jgrid hasTip" href="javascript:void(0);" onclick="return listItemTask('cb<?php echo $i?>','items.disapprove')" title=""><span class="state publish"><span class="text">Disapprove</span></span></a>
</td>
<?php }else{?>
<td class="center">
<a class="jgrid hasTip" href="javascript:void(0);" onclick="return listItemTask('cb<?php echo $i?>','items.approve')" title=""><span class="state unpublish"><span class="text">Approve</span></span></a>
</td>
<?php }?>
In items.approve and items.disapprove ,items is controller and approve and
disapprove is task of items controller.`
In your controller add these function-
public function __construct($config = array()){
parent::__construct($config);
$this->registerTask('unapproved', 'approved');
}
function approved() {
$ids = JRequest::getVar('cid', array(), '', 'array');
JArrayHelper::toInteger($ids );
$cids = implode( ',', $ids);
$values = array('approved' => 1, 'unapproved' => 0);
$task = $this->getTask();
$value = JArrayHelper::getValue($values, $task, 0, 'int');
$db =& JFactory::getDBO();
$query = 'UPDATE #__tbljobs' . ' SET approved = '.(int) $value . ' WHERE id IN ( '.$cids.' )';
$db->setQuery($query);
$result = $db->query();
$redirectTo = JRoute::_('index.php?option='.JRequest::getVar('option').'&task=display');
$this->setRedirect($redirectTo);
}
Read this - Joomla 2.5 extend jgrid.published column in custom component
Hope this will help.

Categories