Yii Url Manager change integer parameter to equivalent string - php

Im trying to make url parameter string to it's integer equivalent
I want user/admins to route on index.php?r=user/admin&lvl=2, user/employees on index.php?r=user/admin&lvl=3 etc but it seems that it is not possible in yii?
I made the following rule but it's not working
'rules' => array(
'user/admins' => 'user/admin/<lvl:2>',
'user/employees' => 'user/admin/<lvl:3>'
);
I think this how I made it on CodeIgniter:
$route['user/admins'] = "user/admin/lvl/2";
$route['user/employees'] = "user/admin/lvl/3";
but I'm not sure in yii

I don't think that you can do that with the default rules provided by CUrlManager. But it's possible by using custom rules. For more information about their implementation see here.
In this case you should insert the following line in rules array in your config file:
array('class' => 'application.components.CustomRule'),
And you CustomRule file should look like:
class CustomRule extends CBaseUrlRule {
public function createUrl($manager, $route, $params, $ampersand) {
if ($route === 'user/admin') {
if ($params['lvl'] == 2) {
return 'user/admin';
} else if ($params['lvl'] == 3) {
return 'user/employees';
}
}
return FALSE;
}
public function parseUrl($manager, $request, $pathInfo, $rawPathInfo) {
if ('user/admins' == $pathInfo) {
$_GET['lvl'] = 2;
return 'user/admin';
} else if ('user/employees' == $pathInfo) {
$_GET['lvl'] = 3;
return 'user/admin';
}
return FALSE;
}
}

Variables are simply mapped after the action definition and separated with a slash:
'rules' => array(
'user/admins' => 'user/admin/lvl/2',
'user/employees' => 'user/admin/lvl/3'
);
You can now acces url user/admins which will be routed to user controller and action admin with a $_GET variable "lvl" and value 2. Note that the value then will be a string instead of an integer, but you can easily cast it.

Related

CodeIgniter 4 - Validation Custom Rule Function Quandry

In my CI4 learning, I have started by trying to simulate user sign in functionality. I have a Controller, two Views (not shown here, but really simply pages- one a pretty much just single form, and the other one a “blank” success HTML page), a set of custom rules in the Validation.php file, and a CustomRule.php file with the first of the methods that will implement all my custom rules (which, ultimately, I’d like to have all set in the Validation.php file). For lack of a better idea, I’ve stuck the CustomRules.php file in the app\Config\ folder.
Here is my problem:
For the life of me, I can’t figure out how to get the Validation service to pass additional parameters (from the form) to my custom rules function called ‘user_validated’. The CI4 documentation describes what the custom function needs to cater for when accepting additional parameters, but not how to trigger the Validation service to pass these additional parameters to one’s custom function… so although ‘user_validated’ is called, only ‘user_email_offered’ is ever passed as in as a string- nothing else goes in, from what I can tell. How do I get around this?
I have tried inserting < $validation->setRuleGroup('user_signin'); > before the call to validate, but found that I could move the setting of the rule group into the call to validate, using: $validationResult = $this->validate('user_signin'), which seemed to do the same, and which doesn't seem to work without the rule-group as a parameter (?). This still doesn't seem to be what triggers the additional data to be passed to the custom rule's method.
Extracts from my hack are appended below.
I’d be very grateful one of you knowledgeable folk could please point me in the right direction.
In app\Controllers\SignupTest.php:
<?php
namespace App\Controllers;
use CodeIgniter\Controller;
class SignupTest extends BaseController
{
public function index() { // redirection from the default to signup(), signin(), ...
return $this->signup();
}
public function signup() {
helper(['form']);
$validation = \Config\Services::validation();
if ($this->request->getPost()) { // still TBD: any different to using $this->request->getGetPost() ?
$validationResult = $this->validate('user_signin'); // set the rules to use: 'user_signin', 'user_signup'
if (!$validationResult) {
$validationErrors = $validation->getErrors();
return view('SignupTestView', $validationErrors); // redisplay simple html form view with list of validation errors
} else {
return view('SignupTestViewSuccess'); // display view to show success
}
} else {
return view('SignupTestView'); // initial display, in the event of there being no POST data
}
}
}
In \app\Config\CustomRules.php:
<?php
namespace Config;
use App\Models\UserModel;
//--------------------------------------------------------------------
// Custom Rule Functions
//--------------------------------------------------------------------
class CustomRules
{
public function user_validated(string $str, string $fields = NULL, array $data = NULL, string &$error = NULL) : bool{
$user_email_offered = $str;
$user_password_offered = ''; // to be extracted using $fields = explode(',', $fields), but $fields is never provided in the call to this user_validated method
if (($user_email_offered !== NULL) && ($user_password_offered !== NULL)) {
$usermodel = new UserModel(); // intended to create a UserEntity to permit connectivity to the database
$user_found = $usermodel->find($user_email_offered); // we're going to assume that user_email is unique (which is a rule configured in the database table)
if ($user_found === NULL) { // check if user exists before doing the more involved checks in the else-if section below, which may throw exceptions if there's nothing to compare (?)
...
}
}
In \app\Config\Validation.php:
?php
namespace Config;
class Validation
{
//--------------------------------------------------------------------
// Setup
//--------------------------------------------------------------------
/**
* Stores the classes that contain the
* rules that are available.
*
* #var array
*/
public $ruleSets = [
\CodeIgniter\Validation\Rules::class,
\CodeIgniter\Validation\FormatRules::class,
\CodeIgniter\Validation\FileRules::class,
\CodeIgniter\Validation\CreditCardRules::class,
\Config\CustomRules::class,
];
/**
* Specifies the views that are used to display the
* errors.
*
* #var array
*/
public $templates = [
'list' => 'CodeIgniter\Validation\Views\list',
'single' => 'CodeIgniter\Validation\Views\single',
];
//--------------------------------------------------------------------
// Custom Rules
//--------------------------------------------------------------------
/* configurable limits for validation rules array below*/
const user_email_min_lenth = 9;
const user_email_max_lenth = 50;
const user_password_min_lenth = 6;
const user_password_max_lenth = 25;
public $user_signin = [
'user_email' => [
'label' => 'e-mail address',
'rules' => 'trim|required|valid_email|user_validated', // user_validated is custom rule, that will have a custom error message
'errors' => [
'required' => 'You must provide an {field}',
'valid_email' => 'Please enter a valid {field}',
]
],
'user_password' => [
'label' => 'password',
'rules' => 'trim|required',
'errors' => [
'required' => 'Enter a {field} to sign in',
'user_password_check' => 'No such user/{field} combination found',
]
Calling custom rule with parameters should be exactly the same as calling CI4's regular rules. Let's get for example "required_without". You use it like in this example:
$validation->setRule('username', 'Username', 'required_without[id,email]');
And the function is declared as so:
public function required_without($str = null, string $fields, array $data): bool
{
$fields = explode(',', $fields);
//...
}
where $str - this is your main field, $fields - string, packing a comma-separated array.
As for Grouping rules, you do not need to group rules to be able to use custom rules with parameters.
If you have only 2 fields to test against you can go a bit cheaper, which will not be perfect but still works:
Function:
public function myrule(string $mainfield, string $fieldtotestwith): bool
{
//doing stuff
}
Validating rule:
$validation->setRule('somemainfield', 'Something', 'myrule[somesecondfield]');

How can I get the list of required parameters of a yii 2.0 action as an array?

When a user visits a particular url in my yii 2.0 application without required parameters, I want to present a form to collect the required missing parameters.
for this purpose, I need the names of missing parameters, e.g. I have a function
public function actionBlast ($bomb, $building) {
}
I expect the results as an array like this
$args = [0=>'bomb', 1=>'building'];
I tried func_get_args() but it returns null, and the undocumented ReflectionFunctionAbstract::getParameters ( void ) etc. Any other way out?
I think the best way to achieve what you want is to override the default ErrorAction.
Inside your controllers directory, create:
controllers
actions
ErrorAction.php
In ErrorAction.php, add:
<?php
namespace frontend\controllers\actions;
use Yii;
use yii\web\ErrorAction as DefaultErrorAction;
class ErrorAction extends DefaultErrorAction
{
public function run()
{
$missing_msg = 'Missing required parameters:';
$exception = Yii::$app->getErrorHandler()->exception;
if (substr($exception->getMessage(), 0, strlen($missing_msg)) === $missing_msg) {
$parameters = explode(',', substr($exception->getMessage(), strlen($missing_msg)));
return $this->controller->render('missing_params_form' ?: $this->id, [
'parameters' => $parameters,
]);
}
return parent::run();
}
}
In your controller add:
public function actions()
{
return [
'error' => [
'class' => 'frontend\controllers\actions\ErrorAction',
],
];
}
and create a view "missing_params_form.php" in your controller `s view directory, where you can generate your form fields.
I believe this to be your best option, though you may need to update it in case a Yii update changes the error message.

Rewriting route depending of parameter value Yii

I have several rules in Yii that allows me to rewrite some routes, where every will be pass to the action as a get parameter.
'<department>' => 'products/index',
'<department>/<category>' => 'products/index',
I want to explicitly write a rule that depending of the parameter value will change the url to whatever I want
example, right now I have an URL like this
www.mysite.com/Books+%26+Pencils which was rewritten because of this rule '<department>' => 'products/index', which is ok
I want to change that URL to www.mysite.com/books-pencils , if anyone know how to write a rule that compares the value of the deparment attribute and then rewrites it to whatever I want.
THanks
You can use a custom class to handle you special requests.
I have used sth like this, to get my custom URLs out of a database:
'urlManager'=>array(
'rules'=>array(
array(
'class' => 'application.components.UrlRule',
),
),
),
Then you create your custo class similar to this:
<?php
Yii::import("CBaseRule");
class UrlRule extends CBaseUrlRule
{
public function createUrl($manager,$route,$params,$ampersand)
{
// check for my special case of URL route, if not found, then return the unchaged route
preg_match("/^(.+)\/(.+)$/", $route, $r);
if(!is_array($r) or !isset($r[1]) or !isset($r[2])) {
return $route;
}
// handle your own route request, and create your url
$url = 'my-own-url/some-thing';
// check for any params, which i also want to add
$urlParams = $manager->createPathInfo($params,"=","&");
$return = trim($url,'/');
$return.= $urlParams ? "?" . $urlParams : "";
return $return;
}
public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
{
// handle my special url request
$controller = '....';
$action = '.....';
// return the controller/action that should be used
return lcfirst($controller)."/".$action;
}
}
I do not know if this was what you wanted, but at least in this class you can do everything you need with the URL requested.
If you would e.g. like to redirect a lot of similar URLs with a 301 Redirect to 1 URL, you could think of sth like this in the parseUrl function
// check my route and params, and if I need to redirect
$request->redirect('/your/new/url/?params=bla',true,'301');
First of all, if you want to change a URL, you should do a redirect (in this case 301). To implement this logic you can use custom URL rule class.
Url manager configuration:
'rules' => array(
// custom url rule class
array(
'class' => 'application.components.MyUrlRule',
),
)
MyUrlRule class:
class MyUrlRule extends CBaseUrlRule
{
public function createUrl($manager,$route,$params,$ampersand)
{
// Logic used to create url.
// If you do not create urls using Yii::app()->createUrl() in your app,
// you can leave it empty.
}
public function parseUrl($manager,$request,$pathInfo,$rawPathInfo)
{
// modify url
$pathInfoCleaned = strtolower(preg_replace('+%26+', '-', $pathInfo));
// redirect if needed
if ($pathInfo !== $pathInfoCleaned) {
$request->redirect($pathInfoCleaned, true, 301);
}
// parse params from url
$params = explode('/', $pathInfo);
if (isset($params[0])) {
$_GET['department'] = $params[0];
if (isset($params[1])) {
$_GET['category'] = $params[1];
}
}
return 'products/index';
}
}

Remove tags in field on form submit in cakephp

I want to perform strip_tags on a field called description before data is saved in the database during form submission. I thought of creating a custom rule and doing it over there:
'description' => array(
'stripTags' =>array(
'rule' => array('StripTags'),
'message' => ''
),
),
public function StripTags($user = array()) {
return !empty($user['description'])?strip_tags($user['description']):"";
}
However this doesn't work since cakephp expects true/false to be returned instead of an updated value. How should I do this?
Use the Model::beforeSave() callback, that's were all automatic pre-save data modification logic should go. It is invoked before save, but after validation.
Untested example:
public function beforeSave($options = array())
{
if(!parent::beforeSave($options))
{
return false;
}
if(!empty($this->data[$this->alias]['description']))
{
$this->data[$this->alias]['description'] = strip_tags($this->data[$this->alias]['description']);
}
return true;
}

Laravel validation on checkbox which named as array

I've some areas in my form something like:
<ul>
<li>
<input type="checkbox" name="post_categories[]" value="16">English First Main Category<br>
<ul>
<li><input type="checkbox" name="post_categories[]" value="17">English First Subcategory<br></li>
</ul>
</li>
</ul>
When I try to validate them as required fields or something else, Laravel did not validate rules. My rules something like below (In /application/models/posts.php):
public static $rules = array(
'post_title' => 'required',
'post_body' => 'required',
'content_language'=>'required|alpha',
'post_categories'=>'array_numeric',
'post_sequence_number'=>'numeric'
);
public static function validate($data){
return Validator::make($data, static::$rules);
}
In /application/library/validate.php I've a function that validates the array is numeric or not:
Class Validator extends Laravel\Validator {
public function validate_array_numeric($attribute, $value, $parameters){
$numeric_values = array_filter($value, create_function('$item', 'return (is_numeric($item));'));
return count($numeric_values) == count($value);
}
}
Rules works perfectly, except post_categories[]. I get the error:
Method [array_numeric] does not exist.
Cheers.
I had to solve the same problem. Here's what I did:
I created a custom class that extends the default Laravel\Validator class. I wanted to be able to tell the validator when I'm dealing with multiple values (like in your case). In my implementation this could be done by appending '_array' to every validation rule for a certain field name. What my class does is to check if the rule name has this suffix and if it does the value parameter (which is an array in this case) is broken down to its contained items and passed to the default validation functions in Laravel.
<?php
class Validator extends Laravel\Validator {
public function __call($method, $parameters)
{
if (substr($method, -6) === '_array')
{
$method = substr($method, 0, -6);
$values = $parameters[1];
$success = true;
foreach ($values as $value) {
$parameters[1] = $value;
$success &= call_user_func_array(array($this, $method), $parameters);
}
return $success;
}
else
{
return parent::__call($method, $parameters);
}
}
protected function getMessage($attribute, $rule)
{
if (substr($rule, -6) === '_array')
{
$rule = substr($rule, 0, -6);
}
return parent::getMessage($attribute, $rule);
}
}
As stated in the posts above you will have to make the following change so that your custom Validator class can be found by the Autoloader:
Then you need to remove the following line from
application/config/application.php file:
'Validator' => 'Laravel\Validator'
When this is done you'll be able to use all of Laravel's validation rules with the '_array' suffix. Here's an example:
public static $rules = array(
'post_categories'=>'required_array|alpha_array'
);
I don't know if this issue has been solved in Laravel 4. Maybe you can try it.
But what I'm doing right now is extending the validation class.
You can create a new library that extends the validation class.
To validate if all the items in the array has numeric values. This is in application/libraries:
class Validator extends Laravel\Validator {
public function validate_arraynumeric($attribute, $value, $parameters){
$numeric_values = array_filter($value, create_function('$item', 'return (is_numeric($item));'));
return count($numeric_values) == count($value);
}
}
To change the default error message when the validation fails. Go to application/language/en/validation.php. Just use the name of the function as the key for the new array item:
"arraynumeric" => "The :attribute contains non-numeric values",
update
Then you need to remove the following line from application/config/application.php file:
'Validator' => 'Laravel\\Validator'
To use the new validation:
public static $rules = array(
'post_categories'=>'array_numeric'
);
Now for the required checkbox. I assume you're just requiring one checkbox to be checked. You can just check in the function for the new validation the count of the checked checkboxes if it is at least 1.
You're doing something strange here.
Using post_categories[] as form names generates an array. This means you cannot validate it with 'post_categories[]'=>'required|numeric'. Instead you have to loop through the array and validate each field on it's own.

Categories