I have developed a full website with CakePHP framework and we'd like to make a very light version of the website for mobile devices (mainly iPhone/iPad).
Is there a way to use the existing website with a new sub domain (for instance mobile.mywebsite.com) which will render specific views? I would like to avoid copying and simplifying the current one to match the new one requirements. I do not want to have to "re-develop" a new CakePHP website and do the changes twice every time I need to change a controller action.
I've done this using a quick addition to the beforeFilter() in my app_controller.php file.
function beforeFilter() {
if ($this->RequestHandler->isMobile()) {
$this->is_mobile = true;
$this->set('is_mobile', true );
$this->autoRender = false;
}
}
This uses the CakePHP RequestHandler to sense if it's a mobile device visiting my site. It sets a property and view variable to allow actions an views to adjust themselves based on the new layout. Also turns off the autoRender because we'll take care of that in an afterFilter.
In the afterFilter() it looks for and uses a mobile view file if one exists. Mobile versions are stored in a 'mobile' folder inside the controller's view folder and are named exactly the same as the normal non-mobile versions. (ie. add.ctp becomes mobile/add.ctp)
function afterFilter() {
// if in mobile mode, check for a valid view and use it
if (isset($this->is_mobile) && $this->is_mobile) {
$view_file = new File( VIEWS . $this->name . DS . 'mobile/' . $this->action . '.ctp' );
$this->render($this->action, 'mobile', ($view_file->exists()?'mobile/':'').$this->action);
}
}
Dan's answer worked for me. However, I used file_exists instead of the File constructor and added the ability to use mobile layouts. The before filter was the same, but the afterFilter looked like this:
function afterFilter() {
// if in mobile mode, check for a valid view and use it
if (isset($this->is_mobile) && $this->is_mobile) {
$view_file = file_exists( VIEWS . $this->name . DS . 'mobile/' . $this->action . '.ctp' );
$layout_file = file_exists( LAYOUTS . 'mobile/' . $this->layout . '.ctp' );
$this->render($this->action, ($layout_file?'mobile/':'').$this->layout, ($view_file?'mobile/':'').$this->action);
}
}
You can use Theme feature in CakePHP 2.x for mobile layout.
Simply do:
if($this->RequestHandler->isMobile())
$this->theme = 'mobile';
I found this better, as you can share View file on mobile and desktop theme easily.
I modified this technique for a CakePHP 2.1 app. Here is my beforeFilter():
public function beforeFilter() {
if ($this->request->isMobile()){
$this->is_mobile = true;
$this->set('is_mobile', true );
$this->autoRender = false;
}
}
And here is my afterFilter():
function afterFilter() {
// if in mobile mode, check for a valid view and use it
if (isset($this->is_mobile) && $this->is_mobile) {
$view_file = file_exists( 'Views' . $this->name . DS . 'mobile/' . $this->action . '.ctp' );
$layout_file = file_exists( 'Layouts' . 'mobile/' . $this->layout . '.ctp' );
if($view_file || $layout_file){
$this->render($this->action, ($layout_file?'mobile/':'').$this->layout, ($view_file?'mobile/':'').$this->action);
}
}
}
This helps account for deprecated verbiage and constants in CakePHP 2.
The simple solution is to create a new 'mobile' layout with respective stylesheet(s) and turn it on in AppController:
public $components = array('RequestHandler');
public function beforeRender() {
parent::beforeRender();
if ($this->RequestHandler->isMobile()) {
$this->layout = 'mobile';
}
}
It is important to do that in beforeRender() in case if you change $this->layout in your controllers' methods.
CakePHP v2.2.1 Solution (+ Cookies to persist mobile/desktop/other layout)
This solution is based on answers by #Dan Berlyoung, #deewilcox and #Chris K.
Parts of those answers failed to work (for me) in CakePHP 2.2.1.
I also extended the solution to support "forcing" a mobile/desktop/other layout from the frontend - useful for debugging and for users that don't want to be stuck on a "mobile" themed layout.
/app/Controller/AppController.php
class AppController extends Controller {
public $components = array('Cookie');
public $is_mobile = false;
public $layouts = array('desktop', 'mobile');
// executed before every action in the controller
function beforeFilter()
{
// Using "rijndael" encryption because the default "cipher" type of encryption fails to decrypt when PHP has the Suhosin patch installed.
// See: http://cakephp.lighthouseapp.com/projects/42648/tickets/471-securitycipher-function-cannot-decrypt
$this->Cookie->type('rijndael');
// When using "rijndael" encryption the "key" value must be longer than 32 bytes.
$this->Cookie->key = 'qSI242342432qs*&sXOw!adre#34SasdadAWQEAv!#*(XSL#$%)asGb$#11~_+!##HKis~#^'; // When using rijndael encryption this value must be longer than 32 bytes.
// Flag whether the layout is being "forced" i.e overwritten/controlled by the user (true or false)
$forceLayout = $this->Cookie->read('Options.forceLayout');
// Identify the layout the user wishes to "force" (mobile or desktop)
$forcedLayout = $this->Cookie->read('Options.forcedLayout');
// Check URL paramaters for ?forcedLayout=desktop or ?forcedLayout=mobile and persist this decision in a COOKIE
if( isset($this->params->query['forcedLayout']) && in_array($this->params->query['forcedLayout'], $this->layouts) )
{
$forceLayout = true;
$forcedLayout = $this->params->query['forcedLayout'];
$this->Cookie->write('Options.forceLayout', $forceLayout);
$this->Cookie->write('Options.forcedLayout', $forcedLayout);
}
// We use CakePHP's built in "mobile" User-Agent detection (a pretty basic list of UA's see: /lib/Cake/Network/CakeRequest.php)
// Note: For more robust detection consider using "Mobile Detect" (https://github.com/serbanghita/Mobile-Detect) or WURL (http://wurfl.sourceforge.net/)
if( ( $forceLayout && $forcedLayout == 'mobile' ) || ( !$forceLayout && $this->request->is('mobile') ) ) {
$this->is_mobile = true;
$this->autoRender = false; // take care of rendering in the afterFilter()
}
$this->set('is_mobile', $this->is_mobile);
}
// executed after all controller logic, including the view render.
function afterFilter() {
// if in mobile mode, check for a vaild layout and/or view and use it
if( $this->is_mobile ) {
$has_mobile_view_file = file_exists( ROOT . DS . APP_DIR . DS . 'View' . DS . $this->name . DS . 'mobile' . DS . $this->action . '.ctp' );
$has_mobile_layout_file = file_exists( ROOT . DS . APP_DIR . DS . 'View' . DS . 'Layouts' . DS . 'mobile' . DS . $this->layout . '.ctp' );
$view_file = ( $has_mobile_view_file ? 'mobile' . DS : '' ) . $this->action;
$layout_file = ( $has_mobile_layout_file ? 'mobile' . DS : '' ) . $this->layout;
$this->render( $view_file, $layout_file );
}
}
}
/app/View/Elements/default_footer.ctp
<ul>
<?php
$paramsQuery = $this->params->query;
if(!is_array($paramsQuery))
{
$paramsQuery = array();
}
$paramsQuery['url'] = ( isset($paramsQuery['url']) ) ? $paramsQuery['url'] : '';
$url = $paramsQuery['url'];
unset($paramsQuery['url']);
$params = $paramsQuery;
$mobile_url = '/' . $url . '?' . http_build_query( array_merge( $params, array( 'forcedLayout' => 'mobile' ) ) );
$desktop_url = '/' . $url . '?' . http_build_query( array_merge( $params, array( 'forcedLayout' => 'desktop' ) ) );
?>
<?php if($is_mobile): ?>
<li><?= $this->Html->link('Desktop Site', $desktop_url, array('target' => '', 'class' => '')) ?></li>
<?php else: ?>
<li><?= $this->Html->link('Mobile Site', $mobile_url, array('target' => '', 'class' => '')) ?></li>
<?php endif; ?>
</ul>
/app/View/Layouts/default.ctp
<h1>Desktop Site Layout</h1>
<?= $this->fetch('content') ?>
/app/View/Layouts/mobile/default.ctp
<h1>Mobile Site Layout</h1>
<?= $this->fetch('content') ?>
/app/View/Pages/home.ctp
<h2>Home - on Desktop</h2>
<?= $this->element('default_footer') ?>
/app/View/Pages/mobile/home.ctp
<h2>Home - on Mobile</h2>
<?= $this->element('default_footer') ?>
Usage
Use the default_footer links to change layout - or these direct urls
http://example.com/pages/home?forcedLayout=desktop
http://example.com/pages/home?forcedLayout=mobile
A session COOKIE persists the option you've chosen... e.g. try setting to "mobile" and then visit a url without the forcedLayout= param.
http://example.com/pages/home
The default_footer links persist existing params (except for the "fragment" #gohere)
http://example.com/pages/home/a/b/c:d?this=that&foo=bar#gohere
Desktop Site url is:
http://example.com/pages/home/a/b/c:d?this=that&foo=bar&forcedLayout=desktop
For more robust device User-Agent detection consider using the Mobile Detect PHP Library ... you could then target Tablets, and even specific devise OS versions.... Oh what fun! ^_^
The solution I went with was a lightweight modification based on a few of the answers here, for CakePHP 2.5.5. The handling is all done in the beforeRender (note that beforeRender is only run on controller actions that will actually render a page, so this saves overhead as opposed to beforeFilter/afterFilter for private methods):
$mobile = $this->request->is('mobile');
$this->set('mobile',$mobile);
//Check if an alternate mobile view and/or layout exists for this request.
if($mobile){
if(file_exists(APP.'View'.DS.$this->name.DS.'mobile'.DS.$this->view.'.ctp')){
//Render this action on its mobile view.
$this->view = 'mobile'.DS.$this->view;
}
if(file_exists(APP.'View'.DS.'Layouts'.DS.'mobile'.DS.$this->layout.'.ctp' )){
//Render this action on its mobile layout.
$this->layout = 'mobile'.DS.$this->layout;
}
}
The $mobile variable can be used on any view if you have small tweaks to make, otherwise you can optionally replace any view with View/{controller}/mobile/same_file_name.ctp or layout with View/Layouts/mobile/same_file_name.ctp to have a separate page structure entirely.
Note that this uses $this->view and $this->layout and then modifies them, instead of using $this->action and $this->render(view,layout), because your view won't always match your action (same view, multiple actions, for example, break using $this->action), and this solution prevents needing to worry about when $this->render() will be forced, and allows it to happen naturally.
Yes, you can re use all of your domain and controllers, have a look to Tera-WURLF
And even better, you don't need a subdomain for the mobile version.
Related
I have followed the cakephp documentation for 2.0 to create a restFUL. I am not sure if I have it right.
If I was to just put the URL into the browser should I see the xml called back. I am just trying to test it but all I see is the standard view and not the xml view. I just want a quick test to see if I have it right.
The URL
http://www.mydomain.com/members/123.xml
The controller is Members and the method I am calling is view
Here is my code:
routes.php
Router::mapResources('members');
Router::parseExtensions('xml', 'json');
MembersController.php
public function view($id = null) {
if (!$this->Member->exists($id)) {
throw new NotFoundException(__('Invalid member'));
}
$options = array('conditions' => array('Member.' . $this->Member->primaryKey => $id));
$members = $this->Member->find('first', $options);
$this->set(array(
'member' => $members,
'_serialize' => array('member')
));
}
app/view/members/xml/view.ctp
echo $xml->serialize($member)
Have you the RequestHandler in your components array? If not put it in there.
See this page in the CakePHP book.
You don't need any view, CakePHP handles it automatically. Delete folder app/view/members/ with all files inside.
hi all I'm trying to create an invoicing system however when i goto the related url i get a blank page and header just being the url. I've followed the following tutorial [http://bakery.cakephp.org/articles/kalileo/2010/06/08/creating-pdf-files-with-cakephp-and-tcpdf]
here is my viewPdf function in the invoices controller
function viewPdf($id = null)
{
if (!$id)
{
$this->Session->setFlash('Sorry, there was no property ID submitted.');
$this->redirect(array('action'=>'index_admin'), null, true);
}
Configure::write('debug',0); // Otherwise we cannot use this method while developing
$id = intval($id);
$property = $this->__view($id); // here the data is pulled from the database and set for the view
if (empty($property))
{
$this->Session->setFlash('Sorry, there is no property with the submitted ID.');
$this->redirect(array('action'=>'index'), null, true);
}
$this->layout = 'pdf'; //this will use the pdf.ctp layout
$this->render();
}
//End of Controller
}
and here is my viewPdf view
<?php
App::import('Vendor','xtcpdf');
$tcpdf = new XTCPDF();
$textfont = 'freesans'; // looks better, finer, and more condensed than 'dejavusans'
$fpdf->xheadertext = 'YOUR ORGANIZATION';
$tcpdf->SetAuthor("KBS Homes & Properties at http://kbs-properties.com");
$tcpdf->SetAutoPageBreak( false );
$tcpdf->setHeaderFont(array($textfont,'',40));
$tcpdf->xheadercolor = array(150,0,0);
$tcpdf->xheadertext = 'KBS Homes & Properties';
$tcpdf->xfootertext = 'Copyright © %d KBS Homes & Properties. All rights reserved.';
// add a page (required with recent versions of tcpdf)
$tcpdf->AddPage();
// Now you position and print your page content
// example:
$tcpdf->SetTextColor(0, 0, 0);
$tcpdf->SetFont($textfont,'B',20);
$tcpdf->Cell(0,14, "Hello World", 0,1,'L');
// ...
// etc.
// see the TCPDF examples
echo $tcpdf->Output('filename.pdf', 'D');
?>
to my knowledge it should be creating a pdf file that has the words 'hello world' written in it.
after following advice about that being outdated, I used the link to github and have this in my controller now
public function view($id = null) {
$this->set('title_for_layout', 'Invoices');
$this->set('stylesheet_used', 'homestyle');
$this->set('image_used', 'eBOXLogoHome.png');
$this->layout='home_layout';
$this->Invoice->id = $id;
if (!$this->Invoice->exists()) {
throw new NotFoundException(__('Invalid invoice'));
}
$this->pdfConfig = array(
'orientation' => 'potrait',
'filename' => 'Invoice_' . $id
);
$this->set('invoice', $this->Invoice->read(null, $id));
}
}
and my view.ctp
<html>
<head></head>
<title></title>
<body>
<?php $this->pdfConfig = array('engine' => 'CakePdf.WkHtmlToPdf') ?>
<?php echo $invoice; ?>
</body>
</html>
it prints out Array but does not render it as a pdf file
here is a link to the github pagw
https://github.com/ceeram/CakePdf
Use this plugin, setup is on github page ;)
https://github.com/ceeram/CakePdf
this is an obsolete tutorial, this is a tutorial for Cakephp 1.2 and you are using 2.1.
It's much better the code from the link of GitHub that Matus Edko gave you.
By the way, is always better to show all the error and notifications when you are developing and testing, by setting up de "debug" to 3.
Configure::write('debug',3);
Is it possible to theme a plugin's view? I have a mobile theme for mobile devices and would like to use different view files for a plugins app/views as well. I have tried app/views/themed/THEME/plugin/... and /app/plugins/PLUGIN/views/themed/THEME/...none seem to work. Thanks in advance.
Copy content of your theme to:
app/views/themed/THEMENAME/plugins/PLUGINNAME/
Create a ThemePluginsView class on app/libs/view/theme_plugins.php
// app/libs/view/theme_plugins.php
if (!class_exists('ThemeView'))
App::import('Core', 'view/theme');
class ThemePluginsView extends ThemeView {
function __construct(&$controller, $register = true) {
parent::__construct($controller, $register);
}
function _paths($plugin = null, $cached = true) {
$paths = parent::_paths($plugin, $cached);
if (!is_string($plugin) || empty($plugin) || empty($this->theme))
return $paths;
// add app/plugins/PLUGIN/views/themed/THEME path
$dirPath = APP . 'plugins' . DS . $plugin . DS . 'views' . DS . 'themed' . DS . $this->theme . DS;
if (is_dir($dirPath))
$paths = array_merge(array($dirPath), $paths);
return $paths;
}
}
Then call it on your app_controller on beforeFilter() or usual controller on beforeFilter() like:
function beforeFilter() {
if (!class_exists('ThemePluginsView'))
App::import('Core', 'view/theme_plugins');
$this->view = 'ThemePlugins';
$this->theme = 'THEME_NAME';
}
Hope it helps
Cakephp 2.x supports this without having to make any code changes:
If you (can) convert to using cakephp 2.x then yes you can (automatically). The view paths for a theme 'mytheme' and a plugin 'Permissions' would be:
Array
(
[0] => /var/vhosts/Project/htdocs/app/View/Themed/Mytheme/Plugin/Permissions/
[1] => /var/vhosts/Project/htdocs/app/View/Themed/Mytheme/
[2] => /var/vhosts/Project/htdocs/app/View/Plugin/Permissions/
[3] => /var/vhosts/Project/htdocs/app/Plugin/Permissions/View/
[4] => /var/vhosts/Project/htdocs/app/View/
[5] => /var/vhosts/Project/htdocs/lib/Cake/View/
[6] => /var/vhosts/Project/htdocs/lib/Cake/Console/Templates/skel/View/
)
So if you had Users/index.ctp in the plugin and wanted to override it you would edit:
/var/vhosts/Project/htdocs/app/View/Themed/Mytheme/Plugin/Permissions/Users/index.ctp
for the themed version OR:
/var/vhosts/Project/htdocs/app/View/Plugin/Permissions/Users/index.ctp
for the non themed version
Since CakePHP 1.3, the Ajax helper has been deprecated, one cannot user $ajax->Autocomplete..
I use Cake 2.0, and wish to implement dynamic autocomplete( dynamic table, dynamic field) it seems impossible with current tutorials available.
What is closest alternative to $ajax->autoComplete in cakephp 2.0?
One method I've used is to create an Autocomplete Controller (or better yet build it as a plugin), with a method similar to this:
class AutocompleteController extends AppController {
public function fetch($model, $field, $query) {
$this->loadModel($model);
$results = $this->$model->find('all', array(
'conditions'=>array(
$model . "." . $field . " LIKE '%" . $query . "%'"
)
));
$this->set(compact('results');
}
}
/Views/Autocomplete/fetch.ctp:
<?php echo json_encode($results); ?>
To fetch the data, you would use the following URL in your javascript:
/autocomplete/fetch/name_of_your_model/name_of_your_field/string_to_look_for
e.g.
/autocomplete/fetch/User/name/rich
Edit:
Another alternative is to create an autocomplete method in app controller:
public function autocomplete($field, $query) {
$model = $this->{$this->$modelClass}->alias;
$results = $this->$model->find('all', array(
'conditions'=>array(
$model . "." . $field . " LIKE '%" . $query . "%'"
)
));
$this->set(compact('results');
}
And is called with a url like this:
/users/autocomplete/name/rich
which will return all users with an email address LIKE '%rich%'
I have a controller which has 3 functions. I wish to show 3 different views and layouts in each function depending on whether the user comes from mobile, website or facebook. I am passing in already where the user is coming from.
I am unsure how I would then show a particular view and layout for each. Here is some code that I started to do to change the layout. I have the views in a folder called res.
function availability() {
if ($_REQUEST['from'] == 'facebook') {
$this->layout = 'facebook';
print_r ('face');
}elseif ($_REQUEST['from'] == 'website'){
$this->layout = 'website';
print_r ('web');
}elseif ($_REQUEST['from'] == 'mobile'){
$this->layout = 'mobile';
print_r ('mobile');
};
}
Use $this->render() to change the view.
$this->layout = 'facebook';
$this->render( 'res/facebook' );
You could also put all the views for different layouts to their own folders and set the viewpath so that you don't have to choose the views manually in each function:
function beforeFilter() {
parent::beforeFilter();
$this->viewPath = $_REQUEST[ 'from' ];
}
Now the view for action "availability" for the Facebook layout is fetched from facebook/availability.ctp.