I can't found a documentation about how to generate a link with the CSRF token, like in Symfony 1.4:
link_to(__('Delete'), url_for('ntw-delete', $network), array('confirm' => 'Are you sure?', 'method' => 'delete'))
UPDATED: I created a twig extension for that. Maybe it will help to someone
src/UmbrellaWeb/Bundle/ExtraTwigBundle/Twig/LinkExtension.php
<?php
namespace UmbrellaWeb\Bundle\ExtraTwigBundle\Twig;
use Twig_Extension;
use Twig_Function_Method;
use Twig_Environment;
use Symfony\Component\Form\Extension\Csrf\CsrfProvider\CsrfProviderInterface;
class LinkExtension extends Twig_Extension
{
protected $csrfProvider;
public function __construct(CsrfProviderInterface $csrfProvider)
{
$this->csrfProvider = $csrfProvider;
}
public function getFunctions()
{
return array(
'link_to' => new Twig_Function_Method($this, 'linkToFunction', array(
'is_safe' => array('html')
))
);
}
/**
* Build a link with anchor
*
* #param string $path
* #param string $title
* #param array $options Available options:
* string 'confirm' - Text for the popup
* string 'method' - HTTP Method: post, delete, put
* string 'csrfIntention' - CSRF intention. If empty then no CSRF. Not used for GET requests
* string 'csrfField' - CSRF field name. _token by default
* bool 'escape' - escape title, TRUE by default
*/
public function linkToFunction($path,$title,array $options = array())
{
$default = array(
'csrf_intention' => '',
'csrf_field' => '_token',
'escape' => TRUE
);
$options = array_merge($default,$options);
$ecape = $options['escape'];
unset($options['escape']);
$return = '<a href="%s"%s>%s</a>';
$return = sprintf($return,
htmlspecialchars($path),
$this->_tagOptions($this->_options2javascript($options)),
($ecape)?htmlspecialchars($title):$title
);
return $return;
}
function _options2javascript($options)
{
// confirm
$confirm = isset($options['confirm']) ? $options['confirm'] : '';
unset($options['confirm']);
// method
$method = isset($options['method']) ? $options['method'] : false;
unset($options['method']);
// CSRF Intention
$csrfIntention = isset($options['csrf_intention']) ? $options['csrf_intention'] : false;
unset($options['csrf_intention']);
// CSRF field name
$csrfField = isset($options['csrf_field']) ? $options['csrf_field'] : false;
unset($options['csrf_field']);
$onclick = isset($options['onclick']) ? $options['onclick'] : '';
if ($confirm && $method)
{
$options['onclick'] = $onclick . 'if (' . $this->_confirmJsFunction($confirm) . ') { ' . $this->_methodJsFunction($method,$csrfIntention,$csrfField) . ' };return false;';
} else
if ($confirm)
{
if ($onclick)
{
$options['onclick'] = 'if (' . $this->_confirmJsFunction($confirm) . ') { return ' . $onclick . '} else return false;';
} else
{
$options['onclick'] = 'return ' . $this->_confirmJsFunction($confirm) . ';';
}
} else
if ($method)
{
$options['onclick'] = $onclick . $this->_methodJsFunction($method,$csrfIntention,$csrfField) . 'return false;';
}
return $options;
}
function _confirmJsFunction($confirm)
{
return "confirm('".$this->_escapeJs($confirm)."')";
}
/**
* Escape carrier returns and single and double quotes for Javascript segments.
*/
function _escapeJs($javascript = '')
{
$javascript = preg_replace('/\r\n|\n|\r/', "\\n", $javascript);
$javascript = preg_replace('/(["\'])/', '\\\\\1', $javascript);
return $javascript;
}
function _methodJsFunction($method,$csrfIntention,$csrfField)
{
$function = "var f = document.createElement('form'); f.style.display = 'none'; this.parentNode.appendChild(f); f.method = 'post'; f.action = this.href;";
//put, delete HTTP methods
if ('post' != strtolower($method))
{
$function .= "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); ";
$function .= sprintf("m.setAttribute('name', '_method'); m.setAttribute('value', '%s'); f.appendChild(m);", strtolower($method));
}
// CSRF protection
if ($csrfIntention)
{
/**
* #todo isCsrfEnabled() - check a global config
*/
if (TRUE)
{
$function .= "var m = document.createElement('input'); m.setAttribute('type', 'hidden'); ";
$function .= sprintf("m.setAttribute('name', '%s'); m.setAttribute('value', '%s'); f.appendChild(m);", $csrfField, $this->csrfProvider->generateCsrfToken($csrfIntention));
}
}
$function .= "f.submit();";
return $function;
}
function _tagOptions(array $options = array())
{
$html = '';
foreach ($options as $key => $value)
{
$html .= ' ' . $key . '="' . htmlspecialchars($value) . '"';
}
return $html;
}
public function getName()
{
return 'umbrellaweb_link';
}
}
services.xml
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<service id="umbrellaweb.twig.link_extension" class="UmbrellaWeb\Bundle\ExtraTwigBundle\Twig\LinkExtension">
<tag name="twig.extension" />
<argument type="service" id="form.csrf_provider" />
</service>
</services>
</container>
Now in twig you can use:
{{ link_to(path('jk_aa_admin_delete',{'id' : admin.id}),'<img src="del_icon.png"/>',
{'escape':false,'method':'delete','csrf_intention':'delete-admin',
'confirm':'Are you sure?'}) }}
I controller:
//check CSRF token
if (FALSE === $this->get('form.csrf_provider')->isCsrfTokenValid('delete-admin', $request->get('_token')))
{
throw new AccessDeniedHttpException('Invalid CSRF token.');
}
Solution from Symfony 4, from documentation here http://symfony.com/doc/current/security/csrf.html:
Twig template:
<a href="{{ path("remove", { "id" : 1, "csrf_token" : csrf_token('remove') }) }}">
{{ "remove" | trans }}
</a>
Controller:
/**
* #Route("/remove/{id}", name="dashboard_alert_remove")
*
* #ParamConverter("alert", class="App:Alert")
*
* #param Alert $alert
*/
public function doRemoveAction(Alert $alert, Request $request)
{
$submittedToken = $request->get('csrf_token');
if ($this->isCsrfTokenValid('remove', $submittedToken))
{
// Do the deletion stuff
}
}
I had same problem. First I generated token in the Controller and passed to a twig file
$intentions = 'unknown';
$csrfToken = $this->container->get('form.csrf_provider')->generateCsrfToken($intentions);
return array('csrfToken'=>$csrfToken);
than from the twig file you can access the token as
var token = '{{csrfToken}}'
Related
I'm using Parsedown to parse HTML from the database to my site. With Parsedown, you can't really add target="_blank" to the links.
So what I'm trying to do is to add target="_blank" to external links. I've found this function in Parsedown.php:
protected function inlineLink($Excerpt)
{
$Element = array(
'name' => 'a',
'handler' => 'line',
'text' => null,
'attributes' => array(
'href' => null,
'title' => null,
),
);
$extent = 0;
$remainder = $Excerpt['text'];
if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
{
$Element['text'] = $matches[1];
$extent += strlen($matches[0]);
$remainder = substr($remainder, $extent);
}
else
{
return;
}
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
{
$Element['attributes']['href'] = $matches[1];
if (isset($matches[2]))
{
$Element['attributes']['title'] = substr($matches[2], 1, - 1);
}
$extent += strlen($matches[0]);
}
else
{
if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
{
$definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
$definition = strtolower($definition);
$extent += strlen($matches[0]);
}
else
{
$definition = strtolower($Element['text']);
}
if ( ! isset($this->DefinitionData['Reference'][$definition]))
{
return;
}
$Definition = $this->DefinitionData['Reference'][$definition];
$Element['attributes']['href'] = $Definition['url'];
$Element['attributes']['title'] = $Definition['title'];
}
$Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']);
return array(
'extent' => $extent,
'element' => $Element,
);
}
Now, what I've tried is this (added a comment of what I changed):
protected function inlineLink($Excerpt)
{
$Element = array(
'name' => 'a',
'handler' => 'line',
'text' => null,
'attributes' => array(
'href' => null,
'target' => null, // added this
'title' => null,
),
);
$extent = 0;
$remainder = $Excerpt['text'];
if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches))
{
$Element['text'] = $matches[1];
$extent += strlen($matches[0]);
$remainder = substr($remainder, $extent);
}
else
{
return;
}
if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*"|\'[^\']*\'))?\s*[)]/', $remainder, $matches))
{
$Element['attributes']['href'] = $matches[1];
if (isset($matches[2]))
{
$Element['attributes']['title'] = substr($matches[2], 1, - 1);
}
$extent += strlen($matches[0]);
}
else
{
if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches))
{
$definition = strlen($matches[1]) ? $matches[1] : $Element['text'];
$definition = strtolower($definition);
$extent += strlen($matches[0]);
}
else
{
$definition = strtolower($Element['text']);
}
if ( ! isset($this->DefinitionData['Reference'][$definition]))
{
return;
}
$Definition = $this->DefinitionData['Reference'][$definition];
$Element['attributes']['href'] = $Definition['url'];
if (strpos($Definition['url'], 'example.com') !== false) { // added this aswell, checking if its our own URL
$Element['attributes']['target'] = '_blank';
}
$Element['attributes']['title'] = $Definition['title'];
}
$Element['attributes']['href'] = str_replace(array('&', '<'), array('&', '<'), $Element['attributes']['href']);
return array(
'extent' => $extent,
'element' => $Element,
);
}
Any suggestions to accomplish this?
Ran into this issue today. I wanted to have all links from a different host open up in a new target automatically. Unfortunately, the accepted answer recommends editing the Parsedown class file, which is a bad idea imo.
I created a new PHP class which extends Parsedown, and created an override for the element method. Here is the whole class:
class ParsedownExtended extends Parsedown
{
protected function element(array $Element)
{
if ($this->safeMode) {
$Element = $this->sanitiseElement($Element);
}
$markup = '<' . $Element['name'];
if (isset($Element['name']) && $Element['name'] == 'a') {
$server_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
$href_host = isset($Element['attributes']['href']) ? parse_url($Element['attributes']['href'], PHP_URL_HOST) : null;
if ($server_host != $href_host) {
$Element['attributes']['target'] = '_blank';
}
}
if (isset($Element['attributes'])) {
foreach ($Element['attributes'] as $name => $value) {
if ($value === null) {
continue;
}
$markup .= ' ' . $name . '="' . self::escape($value) . '"';
}
}
if (isset($Element['text'])) {
$markup .= '>';
if (!isset($Element['nonNestables'])) {
$Element['nonNestables'] = array();
}
if (isset($Element['handler'])) {
$markup .= $this->{$Element['handler']}($Element['text'], $Element['nonNestables']);
}
else {
$markup .= self::escape($Element['text'], true);
}
$markup .= '</' . $Element['name'] . '>';
}
else {
$markup .= ' />';
}
return $markup;
}
}
Here is where the magic happens:
if (isset($Element['name']) && $Element['name'] == 'a') {
$server_host = isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : null;
$href_host = isset($Element['attributes']['href']) ? parse_url($Element['attributes']['href'], PHP_URL_HOST) : null;
if ($server_host != $href_host) {
$Element['attributes']['target'] = '_blank';
}
}
Now I simply use ParsedownExtended instead of Parsedown when parsing content, e.g.:
$parsedown = new ParsedownExtended();
return $parsedown->text($this->body);
Hope this helps someone.
Such issue already exists on GitHub. Please see this comment.
My extension can automatically set rel="nofollow" and target="_blank"
attributes to a link when it is detected as an external link. You can
also set those attributes manually through the attribute block:
[text](http://example.com) {rel="nofollow" target="_blank"}
Automatic rel="nofollow" Attribute on External Links
// custom external link attributes
$parser->links_external_attr = array(
'rel' => 'nofollow',
'target' => '_blank'
);
If you want to make changes in Parsedown class without using the parsedown-extra-plugin extension, you can do as follows:
1) In \Parsedown::element method after the first line $markup = '<'.$Element['name']; add this line $Element = $this->additionalProcessElement($Element);
2) Add new method to Parsedown class:
protected function additionalProcessElement($Element) { }
3) Extend Parsedown class and save it as MyParsedown.php file:
<?php
namespace myapps;
require_once __DIR__.'/Parsedown.php';
/**
* Class MyParsedown
* #package app
*/
class MyParsedown extends \Parsedown
{
/**
* #param array $Element
* #return array
*/
protected function additionalProcessElement($Element)
{
if ($Element['name'] == 'a' && $this->isExternalUrl($Element['attributes']['href'])) {
$Element['attributes']['target'] = '_blank';
}
return $Element;
}
/**
* Modification of the funciton from answer to the question "How To Check Whether A URL Is External URL or Internal URL With PHP?"
* #param string $url
* #param null $internalHostName
* #see https://stackoverflow.com/a/22964930/7663972
* #return bool
*/
protected function isExternalUrl($url, $internalHostName = null) {
$components = parse_url($url);
$internalHostName = ($internalHostName == null) ? $_SERVER['HTTP_HOST'] : $internalHostName;
// we will treat url like '/relative.php' as relative
if (empty($components['host'])) {
return false;
}
// url host looks exactly like the local host
if (strcasecmp($components['host'], $internalHostName) === 0) {
return false;
}
$isNotSubdomain = strrpos(strtolower($components['host']), '.'.$internalHostName) !== strlen($components['host']) - strlen('.'.$internalHostName);
return $isNotSubdomain;
}
}
4) Create test.php file and run it:
require_once __DIR__.'/MyParsedown.php';
$parsedown = new \myapps\MyParsedown();
$text = 'External link to [example.com](http://example.com/abc)';
echo $parsedown->text($text);
This HTML code will be displayed on the browser page (if your host is not example.com, of course):
<p>External link to example.com</p>
Just like kjdion84 I'd also extend the Parsedown class. I suggest to not copy and change the element method but overwrite inlineLink; it's less work and more future proof if the base code changes.
Heads up: the urlIsExternal method is by no means complete (host check is missing).
class ParsedownExtended extends Parsedown
{
protected function inlineLink($Excerpt)
{
$link = parent::inlineLink($Excerpt);
if ($this->urlIsExternal($link['element']['attributes']['href'])) {
$link['element']['attributes'] += [
'target' => '_blank',
'rel' => 'nofollow',
];
}
return $link;
}
protected function urlIsExternal($url)
{
$scheme = parse_url($url, PHP_URL_SCHEME);
$host = parse_url($url, PHP_URL_HOST);
if (!$scheme || !$host) {
return false;
}
if (strpos(strtolower($scheme), 'http') !== 0) {
return false;
}
// #TODO check the host
return true;
}
}
This will work.
<?php
declare(strict_types=1);
namespace YourNamespace;
class ParsedownExt extends \Parsedown
{
// Add target to links
protected function element(array $Element)
{
if (strcasecmp($Element['name'], 'a')===0)
$Element['attributes']['target'] = '_blank';
return parent::element($Element);
}
}
Just integrated Google Recaptcha. I am using this form as a POST to a secure checkout for a secure transaction site ( API ).
My challenge is, I have the form action =
"action="https://securepayments.cardconnect.com/hpp/payment/"
And event if I use :
if (isset($_POST['submit'])) {
to validate the :: recaptcha :: it still just goes straight to the form action URL without verifying the recaptcha.
Here is more code:
$secret = '-- XX --';
$response = $_POST['g-recaptcha-response'];
$remoteip = $_SERVER['REMOTE_ADDR'];
$url = file_get_contents("https://www.google.com/recaptcha/api/siteverify?secret=$secret&response=$response&remoteip=$remoteip");
$res = json_decode($unpass, TRUE);
$result = json_decode($url, TRUE);
if ($result['success'] == 1) {
echo $_POST['name'];
echo $_POST['companyname'];
}
else { echo 'you are a robot'; }
}
The form action URL passes POST / hidden variables to connect via MID/Password to authenticate itself with the API. I can't figure out how to integrate a solution to use recaptcha and then do the form action. Any help would be awesome!
function isValid()
{
try {
$url = 'https://www.google.com/recaptcha/api/siteverify';
$data = ['secret' => 'secret',
'response' => $_POST['g-recaptcha-response'],
'remoteip' => $_SERVER['REMOTE_ADDR']];
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data)
]
];
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
return json_decode($result)->success;
}
catch (Exception $e) {
return null;
}
}
if($_POST && isValid())
{
//do stuff
}
Why you don't use the Recaptcha Library?
<?php if (!defined('BASEPATH')) exit('No direct script access allowed');
class Recaptcha
{
/*
* This is a PHP library that handles calling reCAPTCHA.
* - Documentation and latest version
* http://recaptcha.net/plugins/php/
* - Get a reCAPTCHA API Key
* https://www.google.com/recaptcha/admin/create
* - Discussion group
* http://groups.google.com/group/recaptcha
*
* Copyright (c) 2007 reCAPTCHA -- http://recaptcha.net
* AUTHORS:
* Mike Crawford
* Ben Maurer
*
* CONTRIBUTION:
* Codeigniter version - 23.08.2012 by Christer Nordbø, Norway.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*
* UPDATE HISTORY:
* 28.08.2013 - Made recaptcha_check_answer() function bit simpler on default
* fields as html is generated by recaptcha_get_html() function.
* Updated by - Puneet Kalra (https://github.com/puneetkay)
*/
/**
* The reCAPTCHA server URL's
*/
const RECAPTCHA_API_SERVER = "http://www.google.com/recaptcha/api";
const RECAPTCHA_API_SECURE_SERVER = "https://www.google.com/recaptcha/api";
const RECAPTCHA_VERIFY_SERVER = "www.google.com";
protected $is_valid;
protected $error;
//Remember to obtain the Public and Private key # https://www.google.com/recaptcha/admin/create
protected $public_key = "YOUR PUBLIC KEY";
protected $privkey = "YOUR PRIVATE KEY";
protected $options = array();
function __construct()
{
log_message('debug', "RECAPTCHA Class Initialized.");
$this->_ci =& get_instance();
//Load the CI Config file for recaptcha
$this->_ci->load->config('recaptcha');
//load in the values from the config file.
$this->public_key = $this->_ci->config->item('public_key');
$this->privkey = $this->_ci->config->item('private_key');
$this->options = $this->_ci->config->item('recaptcha_options');
//Lets do some basic error handling to see if the configuration is A-OK.
$temp_error_msg = '';
if ($this->public_key === 'YOUR PUBLIC KEY') {
$temp_error_msg .= 'You need to set your public key in the config file <br />';
}
if ($this->privkey === 'YOUR PRIVATE KEY') {
$temp_error_msg .= 'You need to set your private key in the config file <br />';
}
if ($temp_error_msg != '') {
show_error($temp_error_msg);
}
}
/**
* Encodes the given data into a query string format
* #param $data - array of string elements to be encoded
* #return string - encoded request
*/
function recaptcha_qsencode($data)
{
$req = "";
foreach ($data as $key => $value)
$req .= $key . '=' . urlencode(stripslashes($value)) . '&';
// Cut the last '&'
$req = substr($req, 0, strlen($req) - 1);
return $req;
}
/**
* Submits an HTTP POST to a reCAPTCHA server
* #param string $host
* #param string $path
* #param array $data
* #param int port
* #return array response
*/
function recaptcha_http_post($host, $path, $data, $port = 80)
{
$req = $this->recaptcha_qsencode($data);
$http_request = "POST $path HTTP/1.0\r\n";
$http_request .= "Host: $host\r\n";
$http_request .= "Content-Type: application/x-www-form-urlencoded;\r\n";
$http_request .= "Content-Length: " . strlen($req) . "\r\n";
$http_request .= "User-Agent: reCAPTCHA/PHP\r\n";
$http_request .= "\r\n";
$http_request .= $req;
$response = '';
if (false == ($fs = #fsockopen($host, $port, $errno, $errstr, 10))) {
die ('Could not open socket');
}
fwrite($fs, $http_request);
while (!feof($fs))
$response .= fgets($fs, 1160); // One TCP-IP packet
fclose($fs);
$response = explode("\r\n\r\n", $response, 2);
return $response;
}
function recaptcha_get_html($error = null, $use_ssl = false)
{
if ($this->public_key == null || $this->public_key == '') {
die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
}
if ($use_ssl) {
$server = self::RECAPTCHA_API_SECURE_SERVER;
} else {
$server = self::RECAPTCHA_API_SERVER;
}
$errorpart = "";
if ($error) {
$errorpart = "&error=" . $error;
}
$options = "";
foreach ($this->options as $key => $value) {
$options .= $key . ':"' . $value . '", ';
}
return '<script type="text/javascript"> var RecaptchaOptions = { ' . $options . ' }; </script>
<script type="text/javascript" src="' . $server . '/challenge?k=' . $this->public_key . $errorpart . '"></script>
<noscript>
<iframe src="' . $server . '/noscript?k=' . $this->public_key . $errorpart . '" height="300" width="500" frameborder="0"></iframe><br/>
<textarea name="recaptcha_challenge_field" rows="3" cols="40"></textarea>
<input type="hidden" name="recaptcha_response_field" value="manual_challenge"/>
</noscript>';
}
/**
* Calls an HTTP POST function to verify if the user's guess was correct
* #param string $remoteip
* #param string $challenge
* #param string $response
* #param array $extra_params an array of extra variables to post to the server
* #return ReCaptchaResponse
*/
function recaptcha_check_answer($remoteip = null, $challenge = null, $response = null, $extra_params = array())
{
if ($this->privkey == null || $this->privkey == '') {
die ("To use reCAPTCHA you must get an API key from <a href='https://www.google.com/recaptcha/admin/create'>https://www.google.com/recaptcha/admin/create</a>");
}
$remoteip = ($remoteip == null) ? $_SERVER['REMOTE_ADDR'] : $remoteip;
$challenge = ($challenge == null) ? $this->_ci->input->post('recaptcha_challenge_field') : $challenge;
$response = ($response == null) ? $this->_ci->input->post('recaptcha_response_field') : $response;
if ($remoteip == null || $remoteip == '') {
die ("For security reasons, you must pass the remote ip to reCAPTCHA");
}
//discard spam submissions
if ($challenge == null || strlen($challenge) == 0 || $response == null || strlen($response) == 0) {
$this->is_valid = false;
$this->error = 'incorrect-captcha-sol';
}
$response = $this->recaptcha_http_post(self::RECAPTCHA_VERIFY_SERVER, "/recaptcha/api/verify",
array(
'privatekey' => $this->privkey,
'remoteip' => $remoteip,
'challenge' => $challenge,
'response' => $response
) + $extra_params
);
$answers = explode("\n", $response [1]);
if (trim($answers [0]) == 'true') {
$this->is_valid = true;
} else {
$this->is_valid = false;
$this->error = $answers [1];
}
}
/**
* gets a URL where the user can sign up for reCAPTCHA. If your application
* has a configuration page where you enter a key, you should provide a link
* using this function.
* #param string $domain The domain where the page is hosted
* #param string $appname The name of your application
*/
function recaptcha_get_signup_url($domain = null, $appname = 'Codeigniter')
{
return "https://www.google.com/recaptcha/admin/create?" . $this->recaptcha_qsencode(array('domains' => $domain, 'app' => $appname));
}
function recaptcha_aes_pad($val)
{
$block_size = 16;
$numpad = $block_size - (strlen($val) % $block_size);
return str_pad($val, strlen($val) + $numpad, chr($numpad));
}
/* Mailhide related code */
function recaptcha_aes_encrypt($val, $ky)
{
if (!function_exists("mcrypt_encrypt")) {
die ("To use reCAPTCHA Mailhide, you need to have the mcrypt php module installed.");
}
$mode = MCRYPT_MODE_CBC;
$enc = MCRYPT_RIJNDAEL_128;
$val = $this->recaptcha_aes_pad($val);
return mcrypt_encrypt($enc, $ky, $val, $mode, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0");
}
function recaptcha_mailhide_urlbase64($x)
{
return strtr(base64_encode($x), '+/', '-_');
}
/* gets the reCAPTCHA Mailhide url for a given email, public key and private key */
function recaptcha_mailhide_url($email)
{
if ($this->public_key == '' || $this->public_key == null || $this->privkey == "" || $this->privkey == null) {
die ("To use reCAPTCHA Mailhide, you have to sign up for a public and private key, " .
"you can do so at <a href='http://www.google.com/recaptcha/mailhide/apikey'>http://www.google.com/recaptcha/mailhide/apikey</a>");
}
$ky = pack('H*', $this->privkey);
$cryptmail = $this->recaptcha_aes_encrypt($email, $ky);
return "http://www.google.com/recaptcha/mailhide/d?k=" . $this->public_key . "&c=" . $this->recaptcha_mailhide_urlbase64($cryptmail);
}
/**
* gets the parts of the email to expose to the user.
* eg, given johndoe#example,com return ["john", "example.com"].
* the email is then displayed as john...#example.com
*/
function recaptcha_mailhide_email_parts($email)
{
$arr = preg_split("/#/", $email);
if (strlen($arr[0]) <= 4) {
$arr[0] = substr($arr[0], 0, 1);
} else if (strlen($arr[0]) <= 6) {
$arr[0] = substr($arr[0], 0, 3);
} else {
$arr[0] = substr($arr[0], 0, 4);
}
return $arr;
}
/**
* Gets html to display an email address given a public an private key.
* to get a key, go to:
*
* http://www.google.com/recaptcha/mailhide/apikey
*/
function recaptcha_mailhide_html($email)
{
$emailparts = $this->recaptcha_mailhide_email_parts($email);
$url = $this->recaptcha_mailhide_url($this->public_key, $this->privkey, $email);
return htmlentities($emailparts[0]) . "<a href='" . htmlentities($url) .
"' onclick=\"window.open('" . htmlentities($url) . "', '', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=0,width=500,height=300'); return false;\" title=\"Reveal this e-mail address\">...</a>#" . htmlentities($emailparts [1]);
}
function checkIfIsValid()
{
if ($this->getIsValid()) {
return $this->getIsValid();
} else {
return array($this->getIsValid(), $this->getError());
}
}
function getIsValid()
{
return $this->is_valid;
}
function getError()
{
return $this->error;
}
}
Use it:
$this->load->library('recaptcha');
$this->recaptcha->recaptcha_check_answer();
if (!$this->recaptcha->getIsValid()) {
$this->session->set_flashdata('error', 'Código Captcha incorrecto');
redirect(base_url() . 'add');
}
unset($post['recaptcha_challenge_field']);
unset($post['recaptcha_response_field']);
I am trying to create an API using CakePHP that allows searching. For example:
http://localhost:8765/users/index/?username=admin
Which should return users with usernames equal to 'admin':
users: [
{
id: 3,
username: "admin",
image: "",
firstName: "Jeremy",
lastName: "Quick",
userTypeId: 1,
email: "jrquick#test.com",
groupId: 2
}
]
So far, I have been able to accomplish this with a custom get() in the AppController which checks the $_GET and $_POST array for fields on the model. But the function is getting more and more complicated and verging on hackiness as I add more functionality (range search, collection search, and child table filtering). Is there a better, more CakePHP friendly way of accomplishing this? Whether through pure cakephp or a plugin?
I think you want to use the Cakephp Search plugin. It has good documentation and uses a PRG method similar to what you are currently using. It will function just fine through an API. Here's a link to that plugin: github.com/FriendsOfCake/search
If You want to create API, You should create a MiddleWare at first, which will filter tokens, keys etc. to make Your API more protected.
Also, You should use Plugins and RESTful Routes, which will be very helpful.
To create plugin:
bin/cake bake plugin Api
Create Model:
bin/cake bake model Users
For example, You want to have UsersController in Api plugin:
<?php
namespace Api\Controller;
/* This controller will be extending like parent */
use Api\Controller\AppController;
use Api\Model\Table\UsersTable;
/**
* Class UsersController
* #package Api\Controller
* #property UsersTable $Users
*
*/
class UsersController extends AppController{
public function initialize(){
parent::initialize();
$this->loadModel('Api.Users');
}
public function getUser($field ='username', $username = false){
return $this->_jsonResponse(
[
'users' => $this->Users->findBy{ucfirst($field)}($username)
];
)
}
public function _jsonResponse($data, $code = 200){
$this->response->type('json');
$this->response->statusCode($code);
$this->response->body(
json_encode((array)$data)
);
return $this->response;
}
}
Route will be descripbed in plugins/config/routes.php. You need to create Route Map for API in /api path:
function (RouteBuilder $routes) {
$routes->resources('Users', [
'map' => [
'get-user' => [
'action' => 'getUser',
'method' => 'GET' /* Can be also as array ['GET', 'PUT', 'DELETE'] */
]
]
]);
$routes->fallbacks('DashedRoute');
}
If You have frequent calls, You should use Cache that calls and save them for some amount of time. For example - 10 minutes. Cache can be configured in config/app.php. You should create separate Cache prefix and use it in this way:
<?php
use Cake\Cache\Cache;
$data = [];
Cache::write('some_key', $data, 'prefix')
dump(Cache::read('some_key', 'prefix'));
It's just examples. If You will face some problems - just tell in comments :)
Also, use Migrations and Seeds instead dumping sql files
If You want to filter data from Middleware - You should have Event as argument, that will contain request data ($_POST) and request query($_GET) variables that You will be able to easily handle with.
From controllers You need to use $this->request->data to get POST data array or $this->request->query to get GET data array.
I haven't found an answer that seems to work exactly how I am wanting, so here is my current get command. It does allow searching by fields, join tables, greater/less than, in array, and like.
If anyone has recommendations to improve I will update my answer.
public function get() {
$response = new Response();
$model = $this->loadModel();
$fields = $this->getFields();
$joins = $this->getJoins();
$order = $this->getOrder();
$params = $this->getParams();
$limit = $this->getLimit();
$offset = $this->getOffset();
$query = $model->find('all', ['fields' => $fields]);
if (!is_null($joins)) {
$query->contain($joins);
}
if (sizeof($params['equals']) > 0) {
foreach ($params['equals'] as $equalsKey=>$equalsValue) {
$query->andWhere([$equalsKey => $equalsValue]);
}
}
if (sizeof($params['or']) > 0) {
foreach ($params['or'] as $orKey=>$orValue) {
$query->orWhere([$orKey => $orValue]);
}
}
if (!is_null($order)) {
$query->order([$order]);
}
if (!is_null($limit)) {
$query->limit($limit);
if (!is_null($offset)) {
$query->offset($offset);
}
}
$response->addMessage($model->table(), $query->toArray());
$response->respond($this);
}
private function getFields() {
$fields = [];
if (array_key_exists('fields', $_GET)) {
$fields = explode(',', $_GET['fields']);
}
return $fields;
}
private function getLimit() {
$limit = null;
if (array_key_exists('limit', $_GET)) {
$limit = $_GET['limit'];
}
return $limit;
}
private function getJoins() {
$joins = null;
if (array_key_exists('joins', $_GET)) {
$joins = explode(',', $_GET['joins']);
}
return $joins;
}
private function getOffset() {
$offset = null;
if (array_key_exists('offset', $_GET)) {
$offset = $_GET['limit'];
}
return $offset;
}
private function getOrder() {
$results = [];
if (array_key_exists('order', $_GET)) {
$orders = explode(',', $_GET['order']);
foreach ($orders as $order) {
$sign = substr($order, 0, 1);
$direction = 'ASC';
if (in_array($sign, ['+', '-'])) {
if ($sign === '-') {
$direction = 'DESC';
}
$order = substr($order, 1);
}
$result = $order;
if (strpos($result, '.') === false) {
$result = $this->loadModel()->alias() . '.' . $order;
}
$result = $result . ' ' . $direction;
$results[] = $result;
}
}
return (sizeof($results) == 0) ? null : implode(',', $results);
}
private function getParams() {
$params = [
'equals' => [],
'or' => []
];
$parentModel = $this->loadModel();
$array = array_merge($_GET, $_POST);
foreach ($array as $field=>$value) {
$comparisonType = 'equals';
$operator = substr($field, strlen($field) - 1);
if (in_array($operator, ['!', '>', '<'])) {
$field = substr($field, 0, strlen($field) - 1);
$operator .= '=';
} else if (in_array($operator, ['|'])) {
$field = substr($field, 0, strlen($field) - 1);
$comparisonType = 'or';
$operator = '=';
} else if (in_array($operator, ['%'])) {
$field = substr($field, 0, strlen($field) - 1);
$operator = 'LIKE';
$value = '%'.$value.'%';
} else {
$operator = '=';
}
if ($value == 'null') {
$operator = (strpos($operator, '!') === false) ? 'IS' : 'IS NOT';
$value = null;
}
$field = str_replace('_', '.', $field);
if (strpos($field, '.') === false) {
$alias = $parentModel->alias();
} else {
$fieldExplosion = explode('.', $field);
$alias = $fieldExplosion[0];
$field = $fieldExplosion[1];
}
$model = null;
if ($parentModel->alias() !== $alias) {
$association = $parentModel->associations()->get($alias);
if (!is_null($association)) {
$model = $this->loadModel($association->className());
}
} else {
$model = $parentModel;
}
if (!is_null($model)) {
if ($model->hasField(rtrim($field, 's')) && !$model->hasField($field)) {
$field = rtrim($field, 's');
$value = '(' . $value . ')';
$operator = ' IN';
}
if ($model->hasField($field)) {
$params[$comparisonType][$alias.'.'.$field . ' ' . $operator] = $value;
}
}
}
return $params;
}
I'm using Laravel 4 for a project, but got an issue. I'm not sure, what i'm doing wrong.
Details:
Form is posted to the controller's save function.
When validation fails, i'm redirecting to the create function
After redirect (using Redirect::to(somewhere)->withErrors($validator)->withInput()):
Validation errors are being displayed correctly (if any)
Input::old() is empty (it should contain previously submitted data)
Create function in controller
public function create()
{
$this->scripts[] = 'various js path here';
return View::make('admin.modules.events.create', array(
// Loading various scripts specified in this function
'scripts' => $this->scripts,
));
}
In the view:
...
{{ Form::bsInput('event_name', 'Event title', 'event title goes here', $error = (($errors->has('event_name')) ? $errors->get('event_name') : false), $type = 'text', Input::old('event_name')) }}
...
Note: bsInput is a wrapper around Form::Input() to create bootstrap controls together with labels
Controller:
public function save()
{
if (Input::has('submitEventSave'))
{
$event = Mihirevent::find(Input::get(event_id));
$event_add = false;
}
else
{
$event = new Mihirevent();
$event_add = true;
}
if ($event === false)
{
// doing something else
}
else
{
$event->event_name = Input::get('event_name');
$event->event_slug = Input::get('event_slug');
$event->event_description = Input::get('event_description');
$event->event_location_text = Input::get('event_location_text');
$event->event_location_data = Input::get('event_location_data');
$event->event_status = Input::get('event_status');
$event->featured_image = Input::get('featured_image');
$event->event_date_from = Input::get('event_date_from');
$event->event_date_until = Input::get('event_date_until');
$validation_rules = $event_add === true?$event->rules:$event->update_rules;
$inputs = array(
'event_name' => $event->event_name,
'event_slug' => $event->event_slug,
'event_location_text' => $event->event_location_text,
);
$validator = Validator::make($inputs, $validation_rules);
if ($validator->fails())
{
Input::flash();
if ($event_add === true)
{
return Redirect::to('admin/event/create')
->withErrors($validator)->withInput();
}
else
{
return Redirect::to('admin/event/edit/'.$event->event_id)
->withErrors($validator)->withInput();
}
}
// save
MihirEvent::save();
// redirect to list
return Redirect::route('adminEvent');
}
}
Update:
bsInput macro:
Form::macro('bsInput', function($name, $text, $placeholder = null, $error = false, $type = 'text', $default = null, $class=null)
{
$label = Form::label($name, $text, array('class' => 'control-label'));
$input = Form::input($type, $name, $default, array('placeholder' => $placeholder, 'class' => 'form-control'.($class?' '.$class:'')));
$error_messages = false;
if($error)
{
$error_messages = '<ol>';
foreach ($error as $value) {
$error_messages .= '<li>'.$value.'</li>';
}
$error_messages .= '</ol>';
}
$html = '<div class="form-group'.(($error) ? ' has-error' : '').'">';
$html .= $label;
$html .= $input;
$html .= (($error_messages) ? '<div class="alert alert-danger">'.$error_messages.'</div>' : '');
$html .= '</div>';
return $html;
});
Looking at the Laravel 4 source:
/**
* Flash an array of input to the session.
*
* #param array $input
* #return \Illuminate\Http\RedirectResponse
*/
public function withInput(array $input = null)
{
$input = $input ?: $this->request->input();
$this->session->flashInput($input);
return $this;
}
It looks like if you don't pass an array of Input with ->withInput it tries to pull it from the original request. Try modifying the line like so:
if ($event_add === true)
{
return Redirect::to('admin/event/create')
->withErrors($validator)->withInput(Input::all());
}
else
{
return Redirect::to('admin/event/edit/'.$event->event_id)
->withErrors($validator)->withInput(Input::all());
}
This should hopefully force it to pass the array of input values, instead of relying on the
'$this->request->input()'
still existing in the session.
You are doing Input::flash() and then withInput(), which effectively does Input::flash() twice, probably invalidating the flashed input. Try only doing one of the two.
Also, MihirEvent::save(); is wrong, you want to do $event->save();.
Finally i found the issue: There was a leading space in the routes.php file, before the <?php opening tag. (was working on a team and someone else added that space).
I am trying to configure CKEditor but I get the following in my source, it seems that the helper is not being sent any of the $data from my index function, My helper is located application/helpers
This is my code:
Helper:
<?php
if(!defined('BASEPATH')) exit('No direct script access allowed');
/*
* CKEditor helper for CodeIgniter
*
* #author Samuel Sanchez <samuel.sanchez.work#gmail.com> - http://kromack.com/
* #package CodeIgniter
* #license http://creativecommons.org/licenses/by-nc-sa/3.0/us/
* #tutorial http://kromack.com/developpement-php/codeigniter/ckeditor-helper-for-codeigniter/
* #see http://codeigniter.com/forums/viewthread/127374/
* #version 2010-08-28
*
*/
/**
* This function adds once the CKEditor's config vars
* #author Samuel Sanchez
* #access private
* #param array $data (default: array())
* #return string
*/
function cke_initialize($data = array()) {
$return = '';
if(!defined('CI_CKEDITOR_HELPER_LOADED')) {
define('CI_CKEDITOR_HELPER_LOADED', TRUE);
$return = '<script type="text/javascript" src="'.base_url(). $data['path'] . '/ckeditor.js"></script>';
$return .= "<script type=\"text/javascript\">CKEDITOR_BASEPATH = '" . base_url() . $data['path'] . "/';</script>";
}
return $return;
}
/**
* This function create JavaScript instances of CKEditor
* #author Samuel Sanchez
* #access private
* #param array $data (default: array())
* #return string
*/
function cke_create_instance($data = array()) {
$return = "<script type=\"text/javascript\">
CKEDITOR.replace('" . $data['id'] . "', {";
//Adding config values
if(isset($data['config'])) {
foreach($data['config'] as $k=>$v) {
// Support for extra config parameters
if (is_array($v)) {
$return .= $k . " : [";
$return .= config_data($v);
$return .= "]";
}
else {
$return .= $k . " : '" . $v . "'";
}
if($k !== end(array_keys($data['config']))) {
$return .= ",";
}
}
}
$return .= '});</script>';
return $return;
}
/**
* This function displays an instance of CKEditor inside a view
* #author Samuel Sanchez
* #access public
* #param array $data (default: array())
* #return string
*/
function display_ckeditor($data = array())
{
// Initialization
$return = cke_initialize($data);
// Creating a Ckeditor instance
$return .= cke_create_instance($data);
// Adding styles values
if(isset($data['styles'])) {
$return .= "<script type=\"text/javascript\">CKEDITOR.addStylesSet( 'my_styles_" . $data['id'] . "', [";
foreach($data['styles'] as $k=>$v) {
$return .= "{ name : '" . $k . "', element : '" . $v['element'] . "', styles : { ";
if(isset($v['styles'])) {
foreach($v['styles'] as $k2=>$v2) {
$return .= "'" . $k2 . "' : '" . $v2 . "'";
if($k2 !== end(array_keys($v['styles']))) {
$return .= ",";
}
}
}
$return .= '} }';
if($k !== end(array_keys($data['styles']))) {
$return .= ',';
}
}
$return .= ']);';
$return .= "CKEDITOR.instances['" . $data['id'] . "'].config.stylesCombo_stylesSet = 'my_styles_" . $data['id'] . "';
</script>";
}
return $return;
}
/**
* config_data function.
* This function look for extra config data
*
* #author ronan
* #link http://kromack.com/developpement-php/codeigniter/ckeditor-helper-for-codeigniter/comment-page-5/#comment-545
* #access public
* #param array $data. (default: array())
* #return String
*/
function config_data($data = array())
{
$return = '';
foreach ($data as $key)
{
if (is_array($key)) {
$return .= "[";
foreach ($key as $string) {
$return .= "'" . $string . "'";
if ($string != end(array_values($key))) $return .= ",";
}
$return .= "]";
}
else {
$return .= "'".$key."'";
}
if ($key != end(array_values($data))) $return .= ",";
}
return $return;
}
**.htaccess:**
# Customized error messages.
ErrorDocument 404 /index.php
# Set the default handler.
DirectoryIndex index.php
# Various rewrite rules.
<IfModule mod_rewrite.c>
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond $1 !^(index\.php|css|js|images|files|scripts|robots\.txt)
RewriteRule ^(.*)$ index.php/$1 [L,QSA]
</IfModule>
Source
<script type="text/javascript" src="http://house.dev.local//ckeditor.js"></script><script type="text/javascript">CKEDITOR_BASEPATH = 'http://house.dev.local//';</script><script type="text/javascript">
View
if ( ! defined('BASEPATH')) exit('No direct script access allowed');
class Editpage extends CI_Controller {
function __construct(){
parent::__construct();
}
function index($id){
if(!$this->session->userdata('logged_in'))redirect('admin/home');
$this->load->helper('ckeditor');
//Ckeditor's configuration
$this->data['ckeditor'] = array(
//ID of the textarea that will be replaced
'id' => 'content',
'path' => 'includes/js/ckedit',
//Optionnal values
'config' => array(
'toolbar' => "Full", //Using the Full toolbar
'width' => "550px", //Setting a custom width
'height' => '100px', //Setting a custom height
),
//Replacing styles from the "Styles tool"
'styles' => array(
//Creating a new style named "style 1"
'style 1' => array (
'name' => 'Blue Title',
'element' => 'h2',
'styles' => array(
'color' => 'Blue',
'font-weight' => 'bold'
)
),
//Creating a new style named "style 2"
'style 2' => array (
'name' => 'Red Title',
'element' => 'h2',
'styles' => array(
'color' => 'Red',
'font-weight' => 'bold',
'text-decoration' => 'underline'
)
)
)
);
if ($this->input->post('submit')){
#The User has submitted updates, lets begin!
#Set The validation Rules
$this->form_validation->set_rules('content', 'Content', 'trim|required|xss_clean');
#if the form_validation rules fail then load the login page with the errors. Otherwise continue validating the user/pass
if ($this->form_validation->run() == FALSE){
$data['cms_pages'] = $this->navigation_model->getCMSPages($id);
#connect to getCMSCotent and set the page info equal to the $data['page'] where the row is equal to the passed $id from the URL.
$data['page'] = $this->page_model->getCMSContent($id);
$data['content'] = $this->load->view('admin/editpage', $data, TRUE);
$this->load->view('admintemplate', $data);
}
#Form Validation passed, so lets continue updating.
#lets set some variables.
$content = $this->input->post('content', TRUE);
#Now if updatePage fails to update hte database then show "there was a problem", you could echo the db error itself
if($this->page_model->updatePage($id, $content)) {
$data['cms_pages'] = $this->navigation_model->getCMSPages($id);
#connect to getCMSContent and set the page info equal to the $data['page'] where the row is equal to the passed $id from the URL.
$data['page'] = $this->page_model->getCMSContent($id);
$data['success'] = TRUE;
$data['content'] = $this->load->view('admin/editpage', $data, TRUE);
$this->load->view('admintemplate', $data);
}//END if updatePage
}else{
$data['cms_pages'] = $this->navigation_model->getCMSPages($id);
#connect to getCMSCotent and set the page info equal to the $data['page'] where the row is equal to the passed $id from the URL.
$data['page'] = $this->page_model->getCMSContent($id);
$data['content'] = $this->load->view('admin/editpage', $data, TRUE);
$this->load->view('admintemplate', $data);
}//END if post submitted
} //END function index()
}
You do know that you can jst embed CK Editor with JS to a textarea, and not much around with all this.
http://ckeditor.com/demo
Hows you exactly how.. 3 second job.
One issue may be the double slash in your JS paths;
CKEDITOR_BASEPATH = 'http://house.dev.local//';
Also, is .htaccess blocking access to your CKEditor files ?
Where is your helper functions? what does it do ?
If that is the issue, please post it.
Also, your own helpers should NOT going in to 'system/helpers', they go in to your 'application/helpers', system helpers are for core helpers only.