i want to test a controller function getStructuredChartData which takes $chartData as a parameter
function getStructuredChartData($chartData = null) {
$structuredChartData = array();
$structuredChartData['Create'] = array();
$structuredChartData['Create']['type'] = 'column';
$structuredChartData['Create']['exportingEnabled'] = TRUE;
if($chartData == null) {
$structuredChartData['null'] = TRUE;
} else {
$structuredChartData['ChartParams'] = array();
$structuredChartData['ChartParams']['renderTo'] = 'columnwrapper';
....
....
....
}
}
and to test this the code i have written in testcase controller is as follows
public function testTrendsReportWithAjaxRequest() {
$chartData = array();
$from_date = new DateTime("2014-07-01");
$to_date = new DateTime("2014-07-31");
$chartData['StartDate'] = $from_date;
$chartData['EndDate'] = $to_date;
$chartData['View'] = "Daily";
$chartData['Data'][(int) 0]['Project']['name'] = 'Test Project #1';
$chartData['Data'][(int) 0][(int) 0] = array('reviewed' => '1', 'modified' => '2014-07-16');
debug($chartData);
// Invoke the index action.
$result = $this->testAction(
'/reports/getStructuredChartData',
array('data' => $chartData)
);
debug($result);
$this->assertNotEmpty($result);
}
now my concern is that how to pass $chartData to controller function in testCase.
Currently in Controller function $chartData occurs as NULL and the if condition
if($chartData == null) {
$structuredChartData['null'] = TRUE;
}
gets executed. moreover i would like else condition
else {
$structuredChartData['ChartParams'] = array();
$structuredChartData['ChartParams']['renderTo'] = 'columnwrapper';
....
....
....
}
to be executed.
From CakePHP testing documentation
By supplying the data key, the request made to the controller will be POST. By default all requests will be POST requests. You can simulate a GET request by setting the method key:
You have to add get as method:
$result = $this->testAction(
'/reports/getStructuredChartData',
array('data' => $chartData, 'method' => 'get')
);
If you have your routes properly configured you can just pass the full url:
$result = $this->testAction(
'/reports/getStructuredChartData/test'
);
Again from CakePHP docs
// routes.php
Router::connect(
'/reports/getStructuredData/:chartData',
array('controller' => 'reports', 'action' => 'getStructuredData'),
array(
'pass' => array('chartData'),
)
);
To pass $chartData as argument in TestCaseController what i did is ::
$this->controller = new ReportsController();
$result = $this->controller->getStructuredChartData($chartData);
Related
Using sessions we can achieve this, but need this without sessions or cookies.
<?php
class Employees extends CI_Controller
{
public function __construct()
{
parent::__construct();
}
public function auth() {
$adminEmail = $this->input->post('adminEmail');
$adminPassword = $this->input->post('adminPassword');
if ($adminEmail != "" && $adminPassword != "") {
$query = $this->db->query("select * from admin_tbl where email= '$adminEmail' and password = '$adminPassword'");
//if user exist
if ($query->num_rows() <= 0) {
$response = array();
$jwtoken = "";
$this->session->set_flashdata("invalid", "Wrong email or password");
$response = array(
'status' => 'invalid',
'message' => $_SESSION['invalid'],
'token' => $jwtoken,
);
//used to send finalized values
$this->output
->set_content_type('application/json')
->set_output(json_encode($response));
return $jwtoken; //return value
} else {
// $this->session->set_userdata('adminEmail', $adminEmail);
$response = array();
$jwt = new JWT();
$data = array(
'adminEmail' => $adminEmail,
'iat' => time()
);
$jwtoken = $jwt->encode($data, jwtSecretKey, 'HS256');
// I want to pass $jwtoken's variable to all the functions in a controller
$this->session->set_flashdata("login", "Scucessfully login!");
// if (isset($_SESSION['adminEmail'])) {
if ($jwtoken != "") {
$response = array(
'status' => 'valid',
'message' => $_SESSION['login'],
'token' => $jwtoken
);
}
$abc = $jwtoken;
//used to send finalized values
$this->output
->set_content_type('application/json')
->set_output(json_encode($response));
return $jwtoken; //return value
}
}
}
public function addNew()
{
$response = array();
$this->auth(); // this value is always null returned by auth() method
}
}
?>
This is more of a OOP programming basics question. If you want to re-use a variable in another function of the same controller object, you have to set the variable globally for the Employees class and then set/get its value in your functions by using $this->yourVariableName. But the set value of the object instance can only be reused in that instance only. Which means that after the auth() function, another function should be called subsequently to "access" the $this->yourVariableName.
Another way is to pass the $jwtoken as a parameter to a function.
But the following code answers your question "How to pass calculated/final value of one function to other functions in a controller of Codeigniter application", if it doesn't, then your question should be corrected I guess.
Edit:
Ow ok, first the auth() function is being called, then you would like to pass the $jwtoken value to another function, am I right? Well once a function is finished executing, the variable "disappears" if not passed to another function. If you would like to process the $jwtoken value immediately within the auth() function, then the answer is to pass the $jwtoken value to another function from within the auth() function:
<?php
class Employees extends CI_Controller
{
public function __construct() {
parent::__construct();
}
public function auth() {
$adminEmail = $this->input->post('adminEmail');
$adminPassword = $this->input->post('adminPassword');
if ($adminEmail != "" && $adminPassword != "") {
$query = $this->db->query("select * from admin_tbl where email= '$adminEmail' and password = '$adminPassword'");
//if user exist
if ($query->num_rows() <= 0) {
$response = array();
$jwtoken = "";
$this->session->set_flashdata("invalid", "Wrong email or password");
$response = array(
'status' => 'invalid',
'message' => $_SESSION['invalid'],
'token' => $jwtoken,
);
//used to send finalized values
$this->output
->set_content_type('application/json')
->set_output(json_encode($response));
return $jwtoken; //return value
} else {
// $this->session->set_userdata('adminEmail', $adminEmail);
$response = array();
$jwt = new JWT();
$data = array(
'adminEmail' => $adminEmail,
'iat' => time()
);
$jwtoken = $jwt->encode($data, jwtSecretKey, 'HS256');
// I want to pass $jwtoken's variable to all the functions in a controller
// this is one way you can pass the value to another function, depending on what you want to do, you can also place a condition and continue only if the return value of the following function is respected:
$this->addNew($jwtoken);
// What is the addNew() supposed to do?
$this->session->set_flashdata("login", "Scucessfully login!");
// if (isset($_SESSION['adminEmail'])) {
if ($jwtoken != "") {
$response = array(
'status' => 'valid',
'message' => $_SESSION['login'],
'token' => $jwtoken
);
}
$abc = $jwtoken;
//used to send finalized values
$this->output
->set_content_type('application/json')
->set_output(json_encode($response));
return $jwtoken; //return value
}
}
}
public function addNew($jwtoken = "default_value_if_not_set") {
echo $jwtoken;
}
}
Since you are creating an API, I assume the API is a REST api and stateless, so there is no interference of sessions and cookies.
I assume your process works like this:
User does a login request from the app to the api and the api returns a token when the credentials check is valid
The token is stored in the app (in a local database for example) and used for other requests
So the only thing you need to do is (I assume you have a route to addNew):
public function addNew() {
$token = $this->input->get('token');
$loginData = $this->validateToken($token);
//... add new process
}
And from your app you need to pass the token with the request to the api.
How do you validate the token?
To obtain the data you have set in the token, you have to decode the token:
/**
* throws SignatureInvalidException
*/
function validateToken($token)
{
$jwt = new JWT();
return $jwt->decode($token, jwtSecretKey, 'HS256');
}
Code improvement
Avoid using sessions and cookies
Since your api is stateless, you have to avoid settings cookies or sessions. So in your controller you can remove the flash data helper:
public function auth() {
$adminEmail = $this->input->post('adminEmail');
$adminPassword = $this->input->post('adminPassword');
if ($adminEmail != "" && $adminPassword != "") {
$query = $this->db->query("select * from admin_tbl where email= '$adminEmail' and password = '$adminPassword'");
//if user exist
if ($query->num_rows() <= 0) {
$response = array();
$jwtoken = "";
# REMOVE THIS LINE
# $this->session->set_flashdata("invalid", "Wrong email or password");
$response = array(
'status' => 'invalid',
'message' => "Wrong email or password", //CHANGE THIS LINE
'token' => $jwtoken,
);
//used to send finalized values
$this->output
->set_content_type('application/json')
->set_output(json_encode($response));
return $jwtoken; //return value
} else {
// $this->session->set_userdata('adminEmail', $adminEmail);
$response = array();
$jwt = new JWT();
$data = array(
'adminEmail' => $adminEmail,
'iat' => time()
);
$jwtoken = $jwt->encode($data, jwtSecretKey, 'HS256');
// I want to pass $jwtoken's variable to all the functions in a controller
# REMOVE THIS LINE
# $this->session->set_flashdata("login", "Scucessfully login!");
// if (isset($_SESSION['adminEmail'])) {
if ($jwtoken != "") {
$response = array(
'status' => 'valid',
'message' => "Scucessfully login!", //CHANGE THIS LINE
'token' => $jwtoken
);
}
$abc = $jwtoken;
//used to send finalized values
$this->output
->set_content_type('application/json')
->set_output(json_encode($response));
return $jwtoken; //return value
}
}
}
Return the output response instead of $jwtoken
In your response you have already set the the token, so you can simply return the response:
return $this->output
->set_content_type('application/json')
->set_output(json_encode($response));
Your query is vulnerable to sql injections
Use escape method around you variables or bind the params:
$sql = "select * from admin_tbl where email=? and password = ?";
$query = $this->db->query($sql, array($adminEmail, $adminPassword));
I'm trying to save relations in third table 'Relations'. Here's code:
Controller save action:
$relations = $_POST['VideoCaptions']['countries'];
$model->attachBehavior('ManyToManyRelationBehavior', array(
'class' => 'ManyToManyRelationBehavior',
'modelNameRelation' => 'Relations',
'firstField' => 'video_captions',
'secondField' => 'video_countries',
'relationList' => $relations,
));
ManyToManyRelationBehavior class afterSave action:
if (is_array($this->relationList)){
$model_ = $this->modelNameRelation;
$model_::model()->deleteAll("first_field = :firstField AND first_field_value = :firstFieldValue AND second_field = :secondField", array(
":firstField" => $this->firstField,
":firstFieldValue" => $this->owner->id,
":secondField" => $this->secondField
));
foreach ($this->relationList as $value){
$model_ = new $this->modelNameRelation;
$model_->first_field = $this->firstField;
$model_->first_field_value = $this->owner->id;
$model_->second_field = $this->secondField;
$model_->second_field_value = intval($value);
if (!$model_->save()) return false;
}
}
return true;
var_dump($model_) returns that model exists, but $model->save() doesn't save any data in table 'Relations'. I can't understand why. Can anyone help?
What are the validations you set up for this "modelNameRelation"? You should try the insert() method instead of save() and check whether it will work or not? Save first validates then call insert() or update() method.
SOLVED:
foreach ($this->relationList as $value){
$model = new $this->modelNameRelation;
$model->first_field = $this->firstField;
$model->first_field_value = $this->owner->id;
$model->second_field = $this->secondField;
$model->second_field_value = intval($value);
Yii::app()->db->createCommand()->insert($model->tableName(), $model->attributes);
}
I am looking for a way to access and change the DATABASE_CONFIG variables, based on user input. Using CakePHP I created a custom datasource, based on the one provided in the docs, to access an external API. The API returns a JSON string containing the 12 most recent objects. I need to be able to change the page number in the API request to get the next 12 results, as well as accept a free text query entered by the user.
app/Config/Database.php
class DATABASE_CONFIG {
public $behance = array(
'datasource' => 'BehanceDatasource',
'api_key' => '123456789',
'page' => '1',
'text_query' => 'foo'
);
}
app/Model/Datasource/BehanceDataSource.php
App::uses('HttpSocket', 'Network/Http');
class BehanceDatasource extends DataSource {
public $description = 'Beehance datasource';
public $config = array(
'api_key' => '',
'page' => '',
'text_query' => ''
);
public function __construct($config) {
parent::__construct($config);
$this->Http = new HttpSocket();
}
public function listSources($data = null) {
return null;
}
public function describe($model) {
return $this->_schema;
}
public function calculate(Model $model, $func, $params = array()) {
return 'COUNT';
}
public function read(Model $model, $queryData = array(), $recursive = null) {
if ($queryData['fields'] === 'COUNT') {
return array(array(array('count' => 1)));
}
$queryData['conditions']['api_key'] = $this->config['api_key'];
$queryData['conditions']['page'] = $this->config['page'];
$queryData['conditions']['page'] = $this->config['text_query'];
$json = $this->Http->get('http://www.behance.net/v2/projects', $queryData['conditions']);
$res = json_decode($json, true);
if (is_null($res)) {
$error = json_last_error();
throw new CakeException($error);
}
return array($model->alias => $res);
}
}
Is there anyway to access and change the $behance array, or is there another way to go about accessing an external API with cakePHP that I am totally missing?
Other modules in the application are updating, besides this one.
Here, I am using a model mapper in attempts to update a row set, as found in http://framework.zend.com/manual/en/learning.quickstart.create-model.html
public function SomeAction()
{
$mapper = new Application_Model_SomeMapper();
$model = new Application_Model_SomeModel(); //getters and setters
// action body
$request = $this->getRequest();
$data = $this->_request->getParams();
$someId = $data['someid'];
$get = $mapper->find($someId, new Application_Model_SomeModel, true); //find the row by id, and return array
/*
instantiating a form object and adding "submit"
*/
$form = new Module_Form_FormName();
$form->setAction("/module/controller/action/params/$someId");
$form->setMethod('post');
$form->setName('some_edit');
$submit = $form->createElement('button', 'submit');
$submit->setAttrib('ignore',true);
$submit->setLabel('Edit Something');
$form->addElement($submit);
if ($this->_request->isPost())
{
if($form->isValid($request->getPost()))
{
$data = $this->_request->getPost();
if(empty($data['some_id' ]))
{
$data['tier_models_id'] = NULL;
}
unset($data['submit']);
$setters = $model->setId($data['id'])
->setField1($data['field_1']);
if ($mapper->save($someId, $setters))
{
$this->_redirect("/index/");
}
}
}
$form->populate($tier);
$this->view->form = $get;
}
Here is an example of the save mapper function, except I've included an additional $id parameter
public function save(Application_Model_Guestbook $guestbook)
{
$data = array(
'email' => $guestbook->getEmail(),
'comment' => $guestbook->getComment(),
'created' => date('Y-m-d H:i:s'),
);
if (null === ($id = $guestbook->getId())) {
unset($data['id']);
$this->getDbTable()->insert($data);
} else {
$this->getDbTable()->update($data, array('id = ?' => $id)); //not happening, although the 'id' is passed as a param
}
}
Is there something missing?
Try this instead
$where = $this->getDbTable()->getAdapter()->quoteInto('id = ?', $id);
$this->getDbTable()->update($data, $where);
I'm trying to write a route for an level N category depth. So an usual category URL would look like this:
http://website/my-category/my-subcategory/my-subcategory-level3/my-subcategory-level4
It has an unknown depth and my route has to match all possible levels. I made a route for this, but I can't get all the params from my controller.
$routeCategory = new Zend_Controller_Router_Route_Regex(
'(([a-z0-9-]+)/?){1,}',
array(
'module' => 'default',
'controller' => 'index',
'action' => 'index'
),
array( 1 => 'path'),
'%s'
);
$router->addRoute('category', $routeCategory);
I can't seem to find a way to send the route matched params to the controller. If you have a better solution, I'm open to suggestions!
I found a solution that I think fits my needs. I'll post it here for people who will end up in the same thing I got into.
Problem:
need custom route for level N categories like category/subcategory/subsubcategory/...
custom route for N categories + object like category/subcategory/../page.html
preserve Zend Framework's default routing (for other modules, admin for example)
URL assembling with URL helper
Solution:
create custom route class (I used Zend_Controller_Router_Route_Regex as a starting point so I can benefit from the assemble() method)
Actual code:
<?php
class App_Controller_Router_Route_Category extends Zend_Controller_Router_Route_Regex
{
public function match($path, $partial = false)
{
if (!$partial) {
$path = trim(urldecode($path), '/');
}
$values = explode('/', $path);
$res = (count($values) > 0) ? 1 : 0;
if ($res === 0) {
return false;
}
/**
* Check if first param is an actual module
* If it's a module, let the default routing take place
*/
$modules = array();
$frontController = Zend_Controller_Front::getInstance();
foreach ($frontController->getControllerDirectory() as $module => $path) {
array_push($modules, $module);
}
if(in_array($values[0], $modules)) {
return false;
}
if ($partial) {
$this->setMatchedPath($values[0]);
}
$myValues = array();
$myValues['cmsCategory'] = array();
// array_filter_key()? Why isn't this in a standard PHP function set yet? :)
foreach ($values as $i => $value) {
if (!is_int($i)) {
unset($values[$i]);
} else {
if(preg_match('/.html/', $value)) {
$myValues['cmsObject'] = $value;
} else {
array_push($myValues['cmsCategory'], $value);
}
}
}
$values = $myValues;
$this->_values = $values;
$values = $this->_getMappedValues($values);
$defaults = $this->_getMappedValues($this->_defaults, false, true);
$return = $values + $defaults;
return $return;
}
public function assemble($data = array(), $reset = false, $encode = false, $partial = false)
{
if ($this->_reverse === null) {
require_once 'Zend/Controller/Router/Exception.php';
throw new Zend_Controller_Router_Exception('Cannot assemble. Reversed route is not specified.');
}
$defaultValuesMapped = $this->_getMappedValues($this->_defaults, true, false);
$matchedValuesMapped = $this->_getMappedValues($this->_values, true, false);
$dataValuesMapped = $this->_getMappedValues($data, true, false);
// handle resets, if so requested (By null value) to do so
if (($resetKeys = array_search(null, $dataValuesMapped, true)) !== false) {
foreach ((array) $resetKeys as $resetKey) {
if (isset($matchedValuesMapped[$resetKey])) {
unset($matchedValuesMapped[$resetKey]);
unset($dataValuesMapped[$resetKey]);
}
}
}
// merge all the data together, first defaults, then values matched, then supplied
$mergedData = $defaultValuesMapped;
$mergedData = $this->_arrayMergeNumericKeys($mergedData, $matchedValuesMapped);
$mergedData = $this->_arrayMergeNumericKeys($mergedData, $dataValuesMapped);
/**
* Default Zend_Controller_Router_Route_Regex foreach insufficient
* I need to urlencode values if I bump into an array
*/
if ($encode) {
foreach ($mergedData as $key => &$value) {
if(is_array($value)) {
foreach($value as $myKey => &$myValue) {
$myValue = urlencode($myValue);
}
} else {
$value = urlencode($value);
}
}
}
ksort($mergedData);
$reverse = array();
for($i = 0; $i < count($mergedData['cmsCategory']); $i++) {
array_push($reverse, "%s");
}
if(!empty($mergedData['cmsObject'])) {
array_push($reverse, "%s");
$mergedData['cmsCategory'][] = $mergedData['cmsObject'];
}
$reverse = implode("/", $reverse);
$return = #vsprintf($reverse, $mergedData['cmsCategory']);
if ($return === false) {
require_once 'Zend/Controller/Router/Exception.php';
throw new Zend_Controller_Router_Exception('Cannot assemble. Too few arguments?');
}
return $return;
}
}
Usage:
Route:
$routeCategory = new App_Controller_Router_Route_Category(
'',
array(
'module' => 'default',
'controller' => 'index',
'action' => 'index'
),
array(),
'%s'
);
$router->addRoute('category', $routeCategory);
URL Helper:
echo "<br>Url: " . $this->_helper->url->url(array(
'module' => 'default',
'controller' => 'index',
'action' => 'index',
'cmsCategory' => array(
'first-category',
'subcategory',
'subsubcategory')
), 'category');
Sample output in controller with getAllParams()
["cmsCategory"]=>
array(3) {
[0]=>
string(15) "first-category"
[1]=>
string(16) "subcategory"
[2]=>
string(17) "subsubcategory"
}
["cmsObject"]=>
string(15) "my-page.html"
["module"]=>
string(7) "default"
["controller"]=>
string(5) "index"
["action"]=>
string(5) "index"
Note the cmsObject is set only when the URL contains something like category/subcategory/subsubcategory/my-page.html
I've done it without routes... I've routed only the first parameter and then route the others getting all the params inside the controller
Route:
resources.router.routes.catalog-display.route = /catalog/item/:id
resources.router.routes.catalog-display.defaults.module = catalog
resources.router.routes.catalog-display.defaults.controller = item
resources.router.routes.catalog-display.defaults.action = display
as example:
I use this for the catalog, then into the itemController into the displayAction I check for $this->getRequest()->getParams(), the point is that you can (but I think that you know it) read all the params passed in the way key/value, as example: "site.com/catalog/item/15/kind/hat/color/red/size/M" will produce an array as: $params['controller'=>'catalog','action'=>'display','id'=>'15','kind'=>'hat','color'=>'red','size'=>'M'];
For anyone stumbling across this question using Zend Framework 2 or Zend Framework 3, there is a regex route type which can (and probably should) be used for the OP's route in which there is an unknown number of parameters dependent on the number of child categories. To use, add the following line to the top of your router config:
use Zend\Router\Http\Regex;
Then, you can use a route such as the following to match an unknown number of categories:
'categories' => [
'type' => Regex::class,
'options' => [
'regex' => '/categories(?<sequence>(/[\w\-]+)+)',
'defaults' => [
'controller' => ApplicationController\Categories::class,
'action' => 'view',
],
'spec' => '%sequence',
],
],
The above route will match the following routes:
/categories/parent-cat
/categories/parent-cat/child-cat
/categories/parent-cat/sub-child-cat
/categories/parent-cat/sub-sub-child-cat
/categories/parent-cat-2
/categories/parent-cat-2/child-cat
... and so on. The sequence of categories is passed to the controller in the sequence parameter. You can process this parameter as desired in your controller.