Codeigniter urls in multiple languages but handled by the same controller - multilingual

Let's say I would like to list products on a codeigniter project and support multiple languages, so that someone would select english and get the url example.com/products/5 where 5 is the page number, and someone else would select italian and go get example.com/prodotti/5 . Behind the scenes, I want to have a controller that gets passed a language parameter and a page number parameter to handle these requests. I have a small number of situations like this, and I was thinking about creating two controllers named products and prodotti and then calling a third controller from each one of them by giving it the parameters that it needs.
Is there a better way? Is this achievable with routes?

Here's how I've solved it:
application/config/routes.php
$route['products/(.*)'] = 'products/en/$1';
$route['prodotti/(.*)'] = 'products/it/$1';
application/controllers/products.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Products extends CI_Controller {
public function index($lang, $nr)
{
echo 'lang: ', $lang, ' nr: ',$nr;
}
// https://gist.github.com/1096571
function _remap($method)
{
$param_offset = 2;
if ( ! method_exists($this, $method))
{
$param_offset = 1;
$method = 'index';
}
$params = array_slice($this->uri->rsegment_array(), $param_offset);
call_user_func_array(array($this, $method), $params);
}
}

Related

How to not get some data based on records in another table with laravel eloquent?

Here's my Database Structure:
Products : ['id','name','image']
Request : ['id','marketer_id','distributor_id']
RequestItems : ['id','request_id','product_id','quantity']
Now this is just a short example of structure. what I'm trying to do is that, I have a page with request, which in this page I'm getting items in a request base on RequestItems table, and i want to add a button in my page to add product to this request, but i want to show products that are not exist in RequestItems.
I can make a condition to check before adding product to make sure user won't add 1 product 2 times in a request, but i also want to make it clear that user see products in add button which it's already exist in his request items.
I just need help for Query, I'm developing with Laravel & vue.js for SPA.
My Solution (But looking for better Solution) :
public function getRequestRepresentativeSideProducts(RepresentativesRequests $id, Request $request){
$products = DistributorProducts::where(
'distributor_id', $request->distributor_id
)
->latest()
->get();
$data = $id->items()->latest()->get();
$myArray = array();
for ($i = 0; $i < $data->count(); $i++)
{
$myArray[] = $data[$i]->product_id;
}
return $products->except($myArray);
}
Edit 01 : I've managed to get response with below query, but It's taking all data
$data = DistributorProducts::doesntHave('requestRepresentativeSideItems', 'and', function ($query){
$query->where('representative_request_id', '=', 1);
})->where('distributor_id', $request->distributor_id)
->latest()
->get();
$data = DistributorProducts::whereDoesntHave('requestRepresentativeSideItems', function (Builder $query) use ($id) {
$query->where('representative_request_id', 'like', $id->id);
})->where('distributor_id', $request->distributor_id)
->latest()
->get();
$data = DistributorProducts::whereDoesntHave('requestRepresentativeSideItems', function (Builder $query) use ($id) {
$query->where('representative_request_id', $id->id);
})->where('distributor_id', $request->distributor_id)
->latest()
->get();
I have 3 products for distributor_id which 2 of them are in request items, so my query should show 1 but will above query still I'm getting all products.
Edit 02: I've checked where from $query and used like too and changed request_id but didn't changed result at all. looks like it's just ignoring that part.
Presuming you have the id for the Request you wanted to filter out, the SQL query will be roughly looked like
SELECT * FROM `products` WHERE NOT EXISTS
(SELECT * FROM `request_items` where `products`.`id` = `product_id` and `request_id` = ? )
I had to make additional assumtion as you didnt provide the model, i'd just assume the following
class Product extends Model
{
public function requestItems()
{
return $this->hasMany(RequestItems::class);
}
}
class RequestItems extends Model
{
public function products()
{
return $this->belongsTo(Product::class);
}
}
Then we can use doesntHave query criteria to find products that dont have relationship with
the RequestItems for given request id (in this example its 1).
Products::doesntHave('requestItems', 'and',
function($query){ $query->where('request_id', '=', 1); }
);
Hope it helps. I havent tried it myself, but i believe it would works. You can use toSql
method to see its resulting SQL query.
Note: some insight on the differece of IN and EXISTS

Undefined variable in view

I am having an issue setting variables in the controller and showing it at the view.
my codes are as follow:
In my view (pages/anything.ctp):
<?php echo $anything; ?>
In my controller (pagesController.php):
public function anything() {
$a = "asdasdas";
$this->set('anything', $a);
}
I am new to Cake, and I've done quite a number of search in google and stack. Still no luck.
I'd be grateful if anybody could help, or if anyone already asked this question before please provide a link that would be best.
First read the following article Controller actions in CakePHP CookBook
When you use controller methods with requestAction(), you will often want to return data that isn’t a string. If you have controller methods that are used for normal web requests + requestAction, you should check the request type before returning:
class RecipesController extends AppController {
public function popular() {
$popular = $this->Recipe->popular();
if (!empty($this->request->params['requested'])) {
return $popular;
}
$this->set('popular', $popular);
}
}
The above controller action is an example of how a method can be used with requestAction() and normal requests. Returning array data to a non-requestAction request will cause errors and should be avoided. See the section on requestAction() for more tips on using requestAction()
Try this:
public function anything() {
$a = "asdasdas";
$this->set(compact('a'));
}
<?php echo $a; ?>

Difference in accessing variables in views

I've two controllers one is "Upload" which deals with images uploads and other is "Page" whid deals with the creation of pages of CMS now if in my "Upload" controller I load both the models i.e 'image_m' which deals with image upload and "page_m" which deals with the pages creation I've highlighted the relevant code my problem is if I access the variables in the view
$this->data['images'] = $this->image_m->get(); sent by this I can access in foreach loop as "$images->image_title, $images->image_path" etc
But the variable sent by this line ***$this->data['get_with_images'] = $this->page_m->get_no_parents();*** as $get_with_images->page_name, $get_with_images->page_id etc produces given error
A PHP Error was encountered
Severity: Notice
Message: Trying to get property of non-object
Filename: upload/index.php
Line Number: 20
what is the difference between these two access levels one for $image & other for $get_with_images because I can only access its values as $get_with_images
class Upload extends Admin_Controller {
public function __construct() {
parent::__construct();
***$this->load->model('image_m');
$this->load->model('page_m');***
}
public function index($id = NULL) {
//var_dump($this->data['images'] = $this->image_m->get_with_images());
//$this->data['images'] = $this->image_m->get_with_images();
***$this->data['images'] = $this->image_m->get();***
$this->data['subview'] = 'admin/upload/index';
if ($id) {
$this->data['image'] = $this->image_m->get($id);
count($this->data['image']) || $this->data['errors'][] = 'Page Could not be found';
}
$id == NULL || $this->data['image'] = $this->image_m->get($id);
/*this calls the page_m model function to load all the pages from pages table*/
***$this->data['get_with_images'] = $this->page_m->get_no_parents();***
You are not posting all your code so its hard to tell but is it because you used $this-> in the controller, but you haven't done the same thing in the view?
In this case i would recommend not using $this-> because its not necessary. Also its much better to check for errors etc when you call the model so do something like
if ( ! $data['images'] = $this->image_m->get($id) ) {
// Failure -- show an appropriate view for not getting any images
// am showing $data in case you have other values that are getting passed
$this->load->view( 'sadview', $data ); }
else {
// Success -- show a view to display images
$this->load->view( 'awesomeview', $data ); }
so we are saying if nothing came back - the ! is a negative - then show the failure view. Else $data['images'] came back, and it will be passed to the view. note i have not had to use $this-> for anything and it won't be needed in the view.
Would also suggest using separate methods - have one method to show all images and a separate method like returnimage($id) to show an image based on a specific validated $id.
====== Edit
You can access as many models as you want and pass that data to the View. You have a different issue - the problem is that you are waiting until the View to find out - and then it makes it more difficult to figure out what is wrong.
Look at this page and make sure you understand the differences between query results
http://ellislab.com/codeigniter/user-guide/database/results.html
When you have problems like this the first thing to do is make a simple view, and echo out directly from the model method that is giving you problems. Its probably something very simple but you are having to look through so much code that its difficult to discover.
The next thing is that for every method you write, you need to ask yourself 'what if it doesn't return anything?' and then deal with those conditions as part of your code. Always validate any input coming in to your methods (even links) and always have fallbacks for any method connecting to a database.
On your view do a var_dump($get_with_images) The error being given is that you are trying to use/access $get_with_images as an object but it is not an object.
or better yet on your controller do a
echo '<pre>';
var_dump($this->page_m->get_no_parents());
exit();
maybe your model is not returning anything or is returning something but the data is not an object , maybe an array of object that you still need to loop through in some cases.

Cakephp Plugin to generate sitemap

I'm using Cakephp 2.4.3 . I've read that "There are CakePHP plugins that are able to generate sitemaps for you. This way your sitemap.xml file will be created dynamically on demand and will always be up to date." . I've searched but all I find are from old cakephp version which is not useful as they only cause errors .
Is there still a good plugin for this?
Some plugins definitely exist:
https://github.com/sdevore/cakephp-sitemap-plugin
https://github.com/smarek/Croogo-Sitemap-2.0
https://github.com/webtechnick/CakePHP-Seo-Plugin
Are these the old, error-causing ones? As each CakePHP site can be radically different to the next, I'm not sure a one-size-fits-all solution will exist.
If you end up writing your own sitemap implementation, it'll depend mainly on whether your site has:
Lots of database-driven content with few controllers/actions (like a typical WordPress-style site)
Lots of controller/action driven content (more of a web application)
In the first case, you'd want to perform finds on your content, and inject the results into an xml template like this: http://bakery.cakephp.org/articles/masterkeedu/2008/08/26/automatically-generate-dynamic-sitemaps
For the second case, the following may help: a component I've used for development/testing, which lists all controllers and their methods:
<?php //File: app/Controller/Component/CtrlComponent.php
// Component rewritten for Cake2.x, original from : http://cakebaker.42dh.com/2006/07/21/how-to-list-all-controllers/
class CtrlComponent extends Component {
/**
* Return an array of Controllers and their methods.
* The function will exclude ApplicationController methods
* #return array
*/
public function get() {
$aCtrlClasses = App::objects('controller');
foreach ($aCtrlClasses as $controller) {
if ($controller != 'AppController') {
// Load the controller
App::import('Controller', str_replace('Controller', '', $controller));
// Load its methods / actions
$aMethods = get_class_methods($controller);
foreach ($aMethods as $idx => $method) {
if ($method{0} == '_') {
unset($aMethods[$idx]);
}
}
// Load the ApplicationController (if there is one)
App::import('Controller', 'AppController');
$parentActions = get_class_methods('AppController');
$controllers[$controller] = array_diff($aMethods, $parentActions);
}
}
return $controllers;
}
}
In reality, a full sitemap probably uses both methods, and you'll need to consider the difference between public and "private" areas of your site (excluding admin prefixes, for example)..

Need to make full names in cakePHP

If I have a person model with first_name and last_name, how do I create and display a full_name? I would like to display it at the top of my Edit and View views (i.e. "Edit Frank Luke") and other places. Simply dropping echoes to first_name and last_name isn't DRY.
I'm sorry if this is a very simple question, but nothing has yet worked.
Thank you,
Frank Luke
Edit for clarity: Okay, I have a function on the person model.
function full_name() {
return $this->Person->first_name . ' ' . $this->Person->last_name;
}
In the view, I call
echo $person['Person']['full_name']
This gives me a notice that I have an undefined index. What is the proper way to call the function from the view? Do I have to do it in the controller or elsewhere?
If what you are wanting is just to display a full name, and never need to do any database actions (comparisons, lookups), I think you should just concatenate your fields in the view.
This would be more aligned with the MVC design pattern. In your example you just want to view information in your database in a different way.
Since the action of concatenating is simple you probably don't save much code by placing it in a separate function. I think its easiest to do just in the view file.
If you want to do more fancy things ( ie Change the caps, return a link to the user ) I would recommend creating an element which you call with the Users data.
The arrays set by the save() method only return fields in the datbase, they do not call model functions. To properly use the function above (located in your model), you will need to add the following:
to the controller, in the $action method:
$this->set( 'fullname', $this->Person->full_name();
// must have $this-Person->id set, or redefine the method to include $person_id
in the view,
echo $fullname;
Basically, you need to use the controller to gather the data from the model, and assign it to the controller. It's the same process as you have before, where you assign the returned data from the find() call to the variable in the view, except youre getting the data from a different source.
There are multiple ways of doing this. One way is to use the afterFind-function in a model-class.
See: http://book.cakephp.org/view/681/afterFind.
BUT, this function does not handle nested data very well, instead, it doesn't handles it al all!
Therefore I use the afterfind-function in the app_model that walks through the resultset
function afterFind($results, $primary=false){
$name = isset($this->alias) ? $this->alias : $this->name;
// see if the model wants to attach attributes
if (method_exists($this, '_attachAttributes')){
// check if array is not multidimensional by checking the key 'id'
if (isset($results['id'])) {
$results = $this->_attachAttributes($results);
} else {
// we want each row to have an array of required attributes
for ($i = 0; $i < sizeof($results); $i++) {
// check if this is a model, or if it is an array of models
if (isset($results[$i][$name]) ){
// this is the model we want, see if it's a single or array
if (isset($results[$i][$name][0]) ){
// run on every model
for ($j = 0; $j < sizeof($results[$i][$name]); $j++) {
$results[$i][$name][$j] = $this->_attachAttributes($results[$i][$name][$j]);
}
} else {
$results[$i][$name] = $this->_attachAttributes($results[$i][$name]);
}
} else {
if (isset($results[$i]['id'])) {
$results[$i] = $this->_attachAttributes($results[$i]);
}
}
}
}
}
return $results;
}
And then I add a _attachAttributes-function in the model-class, for e.g. in your Person.php
function _attachAttributes($data) {
if (isset($data['first_name']) && isset($data['last_name'])) {
$data['full_name'] = sprintf("%s %s %s", $data['first_name'], $data['last_name']);
}
return $data;
}
This method can handle nested modelData, for e.g. Person hasMany Posts then this method can also attachAttributes inside the Post-model.
This method also keeps in mind that the linked models with other names than the className are fixed, because of the use of the alias and not only the name (which is the className).
You must use afterFind callback for it.
You would probably need to take the two fields that are returned from your database and concatenate them into one string variable that can then be displayed.
http://old.nabble.com/Problems-with-CONCAT-function-td22640199.html
http://teknoid.wordpress.com/2008/09/29/dealing-with-calculated-fields-in-cakephps-find/
Read the first one to find out how to use the 'fields' key i.e. find( 'all', array( 'fields' => array( )) to pass a CONCAT to the CakePHP query builder.
The second link shows you how to merge the numeric indexes that get returned when you use custom fields back into the appropriate location in the returned results.
This should of course be placed in a model function and called from there.

Resources