I am looking for input/help on how to do this. Might be some PHP/cake developers could provide some good solutions here. Cakephp 2.3 something :)
Problem; How to put shortcodes in wysiwyg editor (example: [slideshow=1]slideshow here[/slideshow]) and render an element (in this case, loading and displaying the slideshow with ID=1).
ShortcodeHelper.php
App::import('Helper', 'Html', 'Router');
class ShortcodeHelper extends AppHelper {
public $shortcodes = array(
'slideshow' => '/(\[slideshow=)(.+?)(\])(.+?)(\[\/slideshow\])/'
);
public $returncodes = array(
//'slideshow' => $this->render('/elements/slideshow', array('id'=>'\\2'))
'slideshow' => '<strong rel="\\2">\\4</strong>'
);
public function render($content, $render=null) {
$shortcodes = $this->shortcodes;
$returncodes = $this->returncodes;
if(isset($render)) {
$temp_shortcodes = array();
$temp_returncodes = array();
foreach ($render as $key => $value) {
$temp_shortcodes[$key] = $shortcodes[$value];
$temp_returncodes[$key] = $returncodes[$value];
}
$returncodes = $temp_returncodes;
$shortcodes = $temp_shortcodes;
}
$return = preg_replace($shortcodes, $returncodes, $content);
return $this->output($return);
}
}
view.ctp (call render function from helper, and run the page-content trough it):
<?php echo $this->Shortcode->render($page['Page']['body']); ?>
Thanks. You are awesome!! :)
-Tom
You need to turn the short code string into a method call, parse it.
Your helper will need to be able to detect them and then break them up. Your code needs to be mapped somehow to a callback.
// [slideshow=1]slideshow here[/slideshow]
$this->requestAction(array('controller' => 'slideshows', 'action' => 'view', $id);
For example.
I think the best way here would be to just always map the first arg, the "function call" to an element instead and pass all other args to the element. This way you can do there whatever you want and request the data or just simply display HTML only.
I would put the mapping of short codes into something like Configure::write('ShortCodes', $shortCodeArray); this way plugins could even register their callback mapping by simply adding them to that array.
array(
'slideshow' => array('controller' => 'slideshows', 'action' => 'view')
);
You'll have to merge that with args from the parsed short code.
Why requestAction()? You should not violate the MVC pattern, for this reason you'll have to request the data via requestAction().
Related
In my routes.php file, I've put this into Router::scope('/', function ($routes) {
$routes->connect(
'/:controller/:id',
['action' => 'view'],
['id' => '[0-9]+']
);
However it does not seem to work? The URLs still go to players/view/1
I'm not sure what I'm doing wrong
From the book: book.cakephp.org/3.0/en/development/routing.html#route-elements (just below the code you may have cut-and-pasted):
CakePHP does not automatically produce lowercased and dashed URLs when using the :controller parameter. If you need this, the above example could be rewritten like so:
$routes->connect(
'/:controller/:id',
['action' => 'view'],
['id' => '[0-9]+', 'routeClass' => 'DashedRoute']
);
...and then a little further down:
Once this route has been defined, requesting /apples/5 would call the view() method of the ApplesController. Inside the view() method, you would need to access the passed ID at $this->request->params['id'].
So in my (for example) ApplesController's view() method, I added the following at the very top:
if ($id == null) {
$id = $this->request->params['id'];
}
...and now apples/view/5 and apples/5 both work the same way.
I have 4 different types of file to download image/doc/pdf/xls. I want to download the file once I clicked on file link.
//controller
public function sendFile($id) {
$file = $this->Attachment->getFile($id);
$this->response->file($file['path']);
// Return response object to prevent controller from trying to render
// a view
return $this->response;
}
//view
<?php echo $file->name; ?>
No need to return the response - see this.
Simply use
public function send_file($id = null) {
...
$this->autoRender = false;
$this->response->file($file['path']);
}
In your view you need to link to this controller action, though (if you think about it):
$this->Html->link('Download', array(
'controller' => 'controller_name', 'action' => 'send_file', $id
));
Also note the conventions I corrected for you - the docs tell you that as well.
just to complete and add some notes on Mark's answer
Simply use
public function send_file($file_name = null) {
//local machine to the file
//e.g. c:\wamp\www\project\...
$path_local = 'path to the file'.$file_name;
//live path to the file
//e.g. \var\...
$path_live = 'path to the file'.$file_name;
$this->autoRender = false;
//if you trying to test it from localhost set the localhost path
//other wise live path
$this->response->file($path_live or $path_local, array('download' => true));
}
In your view you need to link to this controller action, though (if you think about it):
$this->Html->link('Download', array(
'controller' => 'controller_name', 'action' => 'send_file', $file_name
));
Also note the conventions I corrected for you - the docs tell you that as well.
I'm working with CakePHP and trying to understand the best ways to make my application consistent and logical.
Now I'm trying to working with Model data validation and handling validation errors in the view, I have a doubt on how should I do if I like to insert some link inside the returned error, for example for a forgotten password.
Is it good to use (if it's possibile) HtmlHelper inside the Model to return consistent links inside my application, or should I think about another way?
<?php
App::import('Helper', 'Html');
class User extends AppModel {
var $name = 'User';
var $validate = array (
'email' => array (
'checkEmail' => array (
'rule' => array('email', true),
'message' => 'Email not valid message.'
),
'checkUnique' => array (
'rule' => 'isUnique',
'message' => 'This email is allready in the db, if you forgot the password, '.(string)$this->Html->link('click here', array('controller' => 'users', 'action' => 'password-recover')).'.'
)
)
// the rest of the code...
This doesn't work because it seems I can't chain the message string with HTML string.
Does exist e smartest way to do that, or should I simply insert the html string without the HtmlHelper?
If you really want HTML in your validation messages CakePHP provides a way to do this, no breaking Cake, no writing a lot of code.
In your $validation just use whatever HTML you would like to have presented to the user.
In your view when you create your FormHelper::input($fieldName, array $options) pass the following array to $options:
$options = array('error' => array(
'attributes' => array('escape' => false)
))
See this page to learn more about the $options['error'] ...options.
Alternatively, if you want all inputs with no HTML escaping you can pass $options['inputDefaults'] when you create the form.
this is a difficult topic because
you might need to break MVC
validation is as in your case usually in $validate and cannot contain dynamic stuff
for 1)
you can also use Router::url() with manual HTML
you can use BBcode or pseudo-markup and translate this into real links in the view/element of the flashmessage
for 2)
use __construct() and $this->validate to use dynamic elements if needed
In PHP, properties of a class (such as $validate) have to be initialized with constant values.
<?php
class User extends AppModel {
public $validate = array(
'email' => array(
'checkUnique' => array(
'rule' => array('isUnique'),
'message' => 'This email address has already been claimed, possibly by you. If this is your email address, use the reset password facility to regain access to your account'
),
),
);
public function beforeValidate($options = array()) {
$this->validate['email']['checkUnique']['message'] = String::insert(
$this->validate['email']['checkUnique']['message'],
array('link' => Router::url(array('action' => 'password-recover')))
);
return true;
}
You are making it hard on yourself. The helpers are not accessible in the model and controller. And for good reason: the M and C shouldn't be concerned with the V.
There are ways to do exactly as you want (but involves considerably more code). Since you ask for the smartest way: What's wrong with just echo the reset password link in the view, after the login form? Just echo 'Forgot your password? '.$this->Html->link('Click here', array('controller' => 'users', 'action' => 'password-recover'));
I don't agree on breaking the MVC logic. I also tried all the array('escape' => false) possible ways (in Form->input, in Form->error and even in the model) and none of them worked with me! (cakephp 2.0)
"Anh Pham" answer is the easiest and simplest way. In addition to that, I returned empty error message from model validation ('errorMessage' => false ; doesn't work in cakePhp 2.0).
Because I wanted to pass a variable to the view to build the link there (MVC), in the controller I check if the field is invalidated:
$invlaidFields = array_keys($this->Model->validationErrors();
if ( in_array('myField', $invalidFields) ){
...
}
In the view, I check if the field was invalidated, I then echo my error message giving it class error-message so it looks the same as the rest error messages.
if ($this->Form->('myFields')) { ... echo '<span class="error-message">error message'. $this->Html->link(...).'</span>'; }
Hope it helps somebody out there.
P.S. It's always a good practice to mention what cakePHP version you are using...
To cakephp2 you can use the following:
//model validation
'company' => array('notempty' => array('rule' => array('notempty'),'message' => "select one company o send email to contact",),)
//front
<?php if ($this->Form->isFieldError('Register.company')): ?>
<span class="text-danger"><?php echo $this->Form->error('Register.company', null, array('escape'=>false)); ?></span>
<?php endif; ?>
I have set up the RSS Helper (with CakePHP 1.3.4) and all is working - I can access my feeds by /news/feed.rss - as exampled at http://book.cakephp.org/view/1461/Creating-an-RSS-feed-with-the-RssHelper
But I want to be able to do this conditionally, in sudo, something like:
if (!empty($var)) {
switch ($var) {
case one :
$xml = $this->method->find('$var conditions...');
... use RSS Helper to serve results as XML.
case two :
$xml = $this->method->find('other $var conditions...');
... use RSS Helper to serve results as XML.
}
}
Can I use the RSS Helper in this circumstance? What calls/syntax do I use?
There are 2 ways to do this.
Basically you can pass a variable like this:
http://yourserver.com/news/feed.rss?recent=20
and then in the controller you can access this variable with
$this->params['url']['recent']; //20
Or you can add a line in your Router file like this:
Router::connect('/feed-:recent/*', array('plugin'=>false, 'controller' => 'news', 'action' => 'feed'), array('recent'=>'[0-9]+'));
This way your url will look like:
http://yourserver.com/news/feed-20.rss
and finally I believe that url like this will work as well:
http://yourserver.com/news/feed.rss/recent:20
You can pass params to the function the same as any other controller call.
IE
public function rss( $limit = 20, $topic = null ){
if( !$topic ){
$xml = $this->Article->find( 'all', array( 'limit' => $limit ));
...
} else {
$xml = $this->Article->find( 'all', array( 'limit' => $limit, 'conditions' => array( 'Article.topic' => $topic )));
}
...
}
}
?>
Then you can access the rss feed with: http://domain/articles/rss.rss
If you need to pass parameters: http://domain/articles/rss/100.rss (fetch all with 100 results)
Or like this: http://domain/articles/rss/10/obama.rss (fetch all with the topic "obama" and return 10 items.
The RequestHandler allows you to use a url like /posts/index.rss and it automatically loads the RSS Helper and sends the output to /views/posts/rss/index.ctp, and uses layout at /views/layouts/rss/default.ctp.
But if you want to use the RSS Helper on it's own you need to:
1) include it the controller
var $helpers = array('Rss');
2) in your controller action you need to specify where the output goes, so
$this->render('/posts/rss/index','/rss/default');
In this case I also specified which layout to use in the 2nd argument. The first arg - the index.ctp file location is relative to your views/ directory. The second arg - the layout is relative to your views/layouts/ directory.
So I have a method in my posts_controller that I use to identify which feed is wanted (through a passed var) and subsequently finds the posts and sends them to the correct views and layouts:
function rss(){
if (!empty($this->params['pass'])){
$ops=array(
'conditions'=>'where feedname_id=' . $this->params['pass'][0],
'order' => 'Post.created DESC',
'limit' => 10
);
$this->set('posts', $this->Post->find('all',$ops));
$this->render('/posts/rss/index','/rss/default');
} else {
$this->redirect(array('controller'=>'feednames','action'=>'index'));
}
}
There might be better ways to code this - if so please let me know.
What's the cleanest way to add a prefix to every URL in CakePHP, like a language parameter?
http://example.com/en/controller/action
http://example.com/ru/admin/controller/action
It needs to work with "real" prefixes like admin, and ideally the bare URL /controller/action could be redirected to /DEFAULT-LANGUAGE/controller/action.
It's working in a retro-fitted application for me now, but it was kind of a hack, and I need to include the language parameter by hand in most links, which is not good.
So the question is twofold:
What's the best way to structure Routes, so the language parameter is implicitly included by default without having to be specified for each newly defined Route?
Router::connect('/:controller/:action/*', ...) should implicitly include the prefix.
The parameter should be available in $this->params['lang'] or somewhere similar to be evaluated in AppController::beforeFilter().
How to get Router::url() to automatically include the prefix in the URL, if not explicitly specified?
Router::url(array('controller' => 'foo', 'action' => 'bar')) should return /en/foo/bar
Since Controller::redirect(), Form::create() or even Router::url() directly need to have the same behavior, overriding every single function is not really an option. Html::image() for instance should produce a prefix-less URL though.
The following methods seem to call Router::url.
Controller::redirect
Controller::flash
Dispatcher::__extractParams via Object::requestAction
Helper::url
JsHelper::load_
JsHelper::redirect_
View::uuid, but only for a hash generation
Out of those it seems the Controller and Helper methods would need to be overridden, I could live without the JsHelper. My idea would be to write a general function in AppController or maybe just in bootstrap.php to handle the parameter insertion. The overridden Controller and Helper methods would use this function, as would I if I wanted to manually call Router::url. Would this be sufficient?
This is essentially all the code I implemented to solve this problem in the end (at least I think that's all ;-)):
/config/bootstrap.php
define('DEFAULT_LANGUAGE', 'jpn');
if (!function_exists('router_url_language')) {
function router_url_language($url) {
if ($lang = Configure::read('Config.language')) {
if (is_array($url)) {
if (!isset($url['language'])) {
$url['language'] = $lang;
}
if ($url['language'] == DEFAULT_LANGUAGE) {
unset($url['language']);
}
} else if ($url == '/' && $lang !== DEFAULT_LANGUAGE) {
$url.= $lang;
}
}
return $url;
}
}
/config/core.php
Configure::write('Config.language', 'jpn');
/app_helper.php
class AppHelper extends Helper {
public function url($url = null, $full = false) {
return parent::url(router_url_language($url), $full);
}
}
/app_controller.php
class AppController extends Controller {
public function beforeFilter() {
if (isset($this->params['language'])) {
Configure::write('Config.language', $this->params['language']);
}
}
public function redirect($url, $status = null, $exit = true) {
parent::redirect(router_url_language($url), $status, $exit);
}
public function flash($message, $url, $pause = 1) {
parent::flash($message, router_url_language($url), $pause);
}
}
/config/routes.php
Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
Router::connect('/pages/*', array('controller' => 'pages', 'action' => 'display'));
Router::connect('/:language/', array('controller' => 'pages', 'action' => 'display', 'home'), array('language' => '[a-z]{3}'));
Router::connect('/:language/pages/*', array('controller' => 'pages', 'action' => 'display'), array('language' => '[a-z]{3}'));
Router::connect('/:language/:controller/:action/*', array(), array('language' => '[a-z]{3}'));
This allows default URLs like /controller/action to use the default language (JPN in my case), and URLs like /eng/controller/action to use an alternative language. This logic can be changed pretty easily in the router_url_language() function.
For this to work I also need to define two routes for each route, one containing the /:language/ parameter and one without. At least I couldn't figure out how to do it another way.
rchavik from IRC suggested this link: CakePHP URL based language switching for i18n and l10n internationalization and localization
In general, it seems that overriding Helper::url might be the solution.
An easier way might be to store the chosen language in a cookie and then not have to rewrite all the URLs. You could also potentially detect the user's browser language automatically.
However, search engines would be unlikely to pickup the various languages and you'd also lose the language if someone tried to share the link.
But love the full solution you posted, very comprehensive, thanks. :-)