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).
Related
I want to call my function but when I call it I have a problem with curly Brackets at the end of my code and i have this error Error SYMFONY ( {} ) in my Controller.
I have no idea where to put them for my code to work. I have this problem when I add my function that allows me to retrieve the
history of the action. The mentioned function goes as this:
$this->logHistory->addHistoryConnection($project->getId(), $user->getId(), 'Delete Local Suf', $sf_code);
Function Supp Suf
/**
* #Route("/creation/suf/supp", name="suf_supp")
*/
public function suf(
Request $request,
ShapesRepository $shapesRepository
) {
$params = $this->requestStack->getSession();
$projet = $params->get('projet');
$modules = $params->get('modules');
$fonctionnalites = $params->get('fonctionnalites');
$user = $this->getUser()->getUserEntity();
$manager = $this->graceManager;
$mapManager = $this->mapManager;
$countElements = $mapManager->getCount();
$shapes = $shapesRepository->findBy(array('projet' => $projet->getId()));
$adresseWeb = $this->getParameter('adresse_web');
$carto = $params->get('paramCarto');
$centrage = $params->get('centrage');
$cableColor = $params->get('cableColor');
$sf_code = '';
if ($request->get('suf') != '') {
$sf_code = $request->get('suf');
}
$suf = $manager->getSuf($sf_code);
$success = '';
$error = '';
$warning = '';
if ($request->query->get('success')) {
$success = $request->query->get('success');
} elseif ($request->query->get('error')) {
$error = $request->query->get('error');
} elseif ($request->query->get('warning')) {
$warning = $request->query->get('warning');
}
if ($request->isMethod('POST')) {
if ($request->request->get('sf_code') != '') {
$sf_code = $request->request->get('sf_code');
}
if ($request->get('val') != '') {
$val = $request->get('val');
}
$dir = $this->getparameter('client_directory');
$dossier = str_replace(' ', '_', $projet->getProjet());
$dir = $dir . $dossier . '/documents/';
$cable = $val[0];
$chem = $val[1];
$t_suf = $this->graceCreator->supprimeSuf($sf_code, $cable, $chem);
if ($t_suf[0][0] == '00000') {
$this->logHistorique->addHistoryConnection($projet->getId(), $user->getId(), 'Suppression Suf Local', $sf_code);
// $creator->delDirObjet( $st_code, $dir );
$data = new JsonResponse(array("success" => "create!"));
return $data;
} else {
$data = new JsonResponse(array("error" => "Error : " . $t_suf));
return $data;
}
return $this->render('Modifications/supSuf.html.twig', array(
'user' => $user,
'paramCarto' => $carto,
'cableColor' => $cableColor,
'suf' => $suf,
'adresseWeb' => $adresseWeb,
'centrage' => $centrage,
'shapes' => $shapes,
'projet' => $projet,
'modules' => $modules,
'fonctionnalites' => $fonctionnalites,
'countElements' => $countElements
));
}
}
Your only return statement is inside of an if condition. If the code does not pass the condition, it has nothing to return. The code must return something in all possible cases. If you are not still used to these practices, an IDE might guide you until it becomes a routine. PHPStorm is my personal preference.
BTW, I recommend you to switch from the array() syntax to the more globally accepted [] although you must be in PHP 5.4 or higher.
I am using slim image cropper.
I have the following.
<div class="slim" data-service="async.php" data-ratio="3:2" data-size="600,400" data-row="">
<!-- <input type="hidden" id="rowID" name="rowID"> -->
<input type="file"/>
</div>
I added the data-row=" " and then in the jQuery I added the following.
$('.slim').attr('data-row', rowID);
That part is working fine, but now how do I retrieve the value of the data-row on the async.php page?
Here is ASYNC.php
<?php
// Uncomment if you want to allow posts from other domains
// header('Access-Control-Allow-Origin: *');
require_once('slim.php');
foreach(Slim::getImages() as $imagess) {
echo $imagess['meta']->data-row;
}
// Get posted data, if something is wrong, exit
try {
$images = Slim::getImages();
}
catch (Exception $e) {
// Possible solutions
// ----------
// Make sure you're running PHP version 5.6 or higher
Slim::outputJSON(array(
'status' => SlimStatus::FAILURE,
'message' => 'Unknown'
));
return;
}
// No image found under the supplied input name
if ($images === false) {
// Possible solutions
// ----------
// Make sure the name of the file input is "slim[]" or you have passed your custom
// name to the getImages method above like this -> Slim::getImages("myFieldName")
Slim::outputJSON(array(
'status' => SlimStatus::FAILURE,
'message' => 'No data posted'
));
return;
}
// Should always be one image (when posting async), so we'll use the first on in the array (if available)
$image = array_shift($images);
// Something was posted but no images were found
if (!isset($image)) {
// Possible solutions
// ----------
// Make sure you're running PHP version 5.6 or higher
Slim::outputJSON(array(
'status' => SlimStatus::FAILURE,
'message' => 'No images found'
));
return;
}
// If image found but no output or input data present
if (!isset($image['output']['data']) && !isset($image['input']['data'])) {
// Possible solutions
// ----------
// If you've set the data-post attribute make sure it contains the "output" value -> data-post="actions,output"
// If you want to use the input data and have set the data-post attribute to include "input", replace the 'output' String above with 'input'
Slim::outputJSON(array(
'status' => SlimStatus::FAILURE,
'message' => 'No image data'
));
return;
}
// if we've received output data save as file
if (isset($image['output']['data'])) {
// get the name of the file
$name = $image['output']['name'];
// get the crop data for the output image
$data = $image['output']['data'];
// If you want to store the file in another directory pass the directory name as the third parameter.
// $output = Slim::saveFile($data, $name, 'my-directory/');
// If you want to prevent Slim from adding a unique id to the file name add false as the fourth parameter.
// $output = Slim::saveFile($data, $name, 'tmp/', false);
// Default call for saving the output data
$output = Slim::saveFile($data, $name, 'images/modules/listings');
}
// if we've received input data (do the same as above but for input data)
if (isset($image['input']['data'])) {
// get the name of the file
$name = $image['input']['name'];
// get the crop data for the output image
$data = $image['input']['data'];
// If you want to store the file in another directory pass the directory name as the third parameter.
// $input = Slim::saveFile($data, $name, 'my-directory/');
// If you want to prevent Slim from adding a unique id to the file name add false as the fourth parameter.
// $input = Slim::saveFile($data, $name, 'tmp/', false);
// Default call for saving the input data
$input = Slim::saveFile($data, $name, 'images/modules/listings');
}
//
// Build response to client
//
$response = array(
'status' => SlimStatus::SUCCESS
);
if (isset($output) && isset($input)) {
$response['output'] = array(
'file' => $output['name'],
'path' => $output['path']
);
$response['input'] = array(
'file' => $input['name'],
'path' => $input['path']
);
}
else {
$response['file'] = isset($output) ? $output['name'] : $input['name'];
$response['path'] = isset($output) ? $output['path'] : $input['path'];
}
// Return results as JSON String
Slim::outputJSON($response);
Here is the SLIM.php
<?php
abstract class SlimStatus {
const FAILURE = 'failure';
const SUCCESS = 'success';
}
class Slim {
public static function getImages($inputName = 'slim') {
$values = Slim::getPostData($inputName);
// test for errors
if ($values === false) {
return false;
}
// determine if contains multiple input values, if is singular, put in array
$data = array();
if (!is_array($values)) {
$values = array($values);
}
// handle all posted fields
foreach ($values as $value) {
$inputValue = Slim::parseInput($value);
if ($inputValue) {
array_push($data, $inputValue);
}
}
// return the data collected from the fields
return $data;
}
// $value should be in JSON format
private static function parseInput($value) {
// if no json received, exit, don't handle empty input values.
if (empty($value)) {return null;}
// If magic quotes enabled
if (get_magic_quotes_gpc()) {
$value = stripslashes($value);
}
// The data is posted as a JSON String so to be used it needs to be deserialized first
$data = json_decode($value);
// shortcut
$input = null;
$actions = null;
$output = null;
$meta = null;
if (isset ($data->input)) {
$inputData = null;
if (isset($data->input->image)) {
$inputData = Slim::getBase64Data($data->input->image);
}
else if (isset($data->input->field)) {
$filename = $_FILES[$data->input->field]['tmp_name'];
if ($filename) {
$inputData = file_get_contents($filename);
}
}
$input = array(
'data' => $inputData,
'name' => $data->input->name,
'type' => $data->input->type,
'size' => $data->input->size,
'width' => $data->input->width,
'height' => $data->input->height,
);
}
if (isset($data->output)) {
$outputDate = null;
if (isset($data->output->image)) {
$outputData = Slim::getBase64Data($data->output->image);
}
else if (isset ($data->output->field)) {
$filename = $_FILES[$data->output->field]['tmp_name'];
if ($filename) {
$outputData = file_get_contents($filename);
}
}
$output = array(
'data' => $outputData,
'name' => $data->output->name,
'type' => $data->output->type,
'width' => $data->output->width,
'height' => $data->output->height
);
}
if (isset($data->actions)) {
$actions = array(
'crop' => $data->actions->crop ? array(
'x' => $data->actions->crop->x,
'y' => $data->actions->crop->y,
'width' => $data->actions->crop->width,
'height' => $data->actions->crop->height,
'type' => $data->actions->crop->type
) : null,
'size' => $data->actions->size ? array(
'width' => $data->actions->size->width,
'height' => $data->actions->size->height
) : null,
'rotation' => $data->actions->rotation,
'filters' => $data->actions->filters ? array(
'sharpen' => $data->actions->filters->sharpen
) : null
);
}
if (isset($data->meta)) {
$meta = $data->meta;
}
// We've sanitized the base64data and will now return the clean file object
return array(
'input' => $input,
'output' => $output,
'actions' => $actions,
'meta' => $meta
);
}
// $path should have trailing slash
public static function saveFile($data, $name, $path = 'tmp/', $uid = true) {
// Add trailing slash if omitted
if (substr($path, -1) !== '/') {
$path .= '/';
}
// Test if directory already exists
if(!is_dir($path)){
mkdir($path, 0755, true);
}
// Sanitize characters in file name
$name = Slim::sanitizeFileName($name);
// Let's put a unique id in front of the filename so we don't accidentally overwrite other files
if ($uid) {
$name = uniqid() . '_' . $name;
}
// Add name to path, we need the full path including the name to save the file
$path = $path . $name;
// store the file
Slim::save($data, $path);
// return the files new name and location
return array(
'name' => $name,
'path' => $path
);
}
/**
* Get data from remote URL
* #param $url
* #return string
*/
public static function fetchURL($url, $maxFileSize) {
if (!ini_get('allow_url_fopen')) {
return null;
}
$content = null;
try {
$content = #file_get_contents($url, false, null, 0, $maxFileSize);
} catch(Exception $e) {
return false;
}
return $content;
}
public static function outputJSON($data) {
header('Content-Type: application/json');
echo json_encode($data);
}
/**
* http://stackoverflow.com/a/2021729
* Remove anything which isn't a word, whitespace, number
* or any of the following characters -_~,;[]().
* If you don't need to handle multi-byte characters
* you can use preg_replace rather than mb_ereg_replace
* #param $str
* #return string
*/
public static function sanitizeFileName($str) {
// Basic clean up
$str = preg_replace('([^\w\s\d\-_~,;\[\]\(\).])', '', $str);
// Remove any runs of periods
$str = preg_replace('([\.]{2,})', '', $str);
return $str;
}
/**
* Gets the posted data from the POST or FILES object. If was using Slim to upload it will be in POST (as posted with hidden field) if not enhanced with Slim it'll be in FILES.
* #param $inputName
* #return array|bool
*/
private static function getPostData($inputName) {
$values = array();
if (isset($_POST[$inputName])) {
$values = $_POST[$inputName];
}
else if (isset($_FILES[$inputName])) {
// Slim was not used to upload this file
return false;
}
return $values;
}
/**
* Saves the data to a given location
* #param $data
* #param $path
* #return bool
*/
private static function save($data, $path) {
if (!file_put_contents($path, $data)) {
return false;
}
return true;
}
/**
* Strips the "data:image..." part of the base64 data string so PHP can save the string as a file
* #param $data
* #return string
*/
private static function getBase64Data($data) {
return base64_decode(preg_replace('#^data:image/\w+;base64,#i', '', $data));
}
}
That is all the pages includes, I am not sure where to even add this so I can retrieve this rowID, is it under the async, or the slim. Sorry for the confusion. Hopefully adding these pages helped clear some stuff up.
Your HTML can stay the same.
<div class="slim" data-service="async.php" data-ratio="3:2" data-size="600,400">
<input type="file" />
</div>
Append data-meta-data-row to contain the rowID as well in your JS.
(['data-meta-data-row', 'data-row']).forEach( (key) => {
$('.slim').attr(key, rowID);
});
On the server side, you can then use the meta key which holds an object of properties like so:
foreach(Slim::getImages() as $image)
echo $image['meta']->data-row;
So after lots of hacking. I figured it out with the help of the slim frameworks developer.
Per the documentation, there is a function called willSave, this can be triggered and will update whenever is called on the server.
The meta data for slim is only called on page load, that is why it was never reading when the variable was updated via my custom function.
My fix was the following:
After opening the modal, I created the will save function. I passed the variable from the function that opens the modal, to the save function.
Code below for reference and big shout out to the developer for the initial help.
html
<div id="slim" class="slim" data-service="async.php" data-ratio="3:2" data-size="600,400" data-will-save="saveRow">
<input type="file"/>
</div>
JS
function addThumb(rowID) {
//$('.slim').attr('data-meta-data-row', rowID);
window.row = rowID;
$("#imageManager").modal('show');
}
function saveRow(data, ready) {
data.meta.row = window.row;
ready(data);
}
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);
}
}
Why is that form validation still return an error even though the input file is not empty
Here's my controller together with my callback function
public function add_post()
{
$validation = array (
array(
'field' => 'post_title',
'label' => 'Post title',
'rules' => 'trim|required|alpha_numeric_spaces'
),
array(
'field' => 'post_desc',
'label' => 'Post Description',
'rules' => 'trim|required|alpha_numeric_spaces'
),
array(
'field' => 'post_content',
'label' => 'Post content',
'rules' => 'trim|required'
),
array(
'field' => 'headerimage',
'label' => 'File',
'rules' => 'required|callback_file_check'
)
);
$this->form_validation->set_rules($validation);
if($this->form_validation->run()===FALSE)
{
$info['errors'] = validation_errors();
$info['success'] = false;
}
else
{
$this->save_image();
$datetime = $this->get_time();
$data = array(
"post_title" => $this->input->post('post_title'),
"post_content" => $this->input->post('post_content'),
"post_image" => $this->uploadFileName,
"post_created" => $datetime
);
$this->Blog_model->add_post($data);
$info['success'] = true;
$info['message'] = "Successfully added blog post";
}
$this->output->set_content_type('application/json')->set_output(json_encode($info));
}
Here's the form mark-up
<?php echo form_open_multipart('#',array("class"=>"form-horizontal","id"=>"blogform")); ?>
<div class="row">
<div class="col-md-6">
<input type="text" placeholder="Enter your post title" name="post_title" class="form-control">
</div>
<div class="col-md-6 form-group">
<label for="file" class="control-label col-md-4">Select header image:</label>
<div class="col-md-8">
<input type="file" id="header" name="headerimage" accept="image/*" />
</div>
</div>
</div>
<input type="text" placeholder="Enter your post description" name="post_desc" class="form-control">
<label class="control-label text-muted">Post Body:</label>
<div>
<textarea id="post_content"></textarea>
</div>
<button class="btn btn-default pull-right" type="button" id="save-post"><i class="fa fa-save"></i> Save Post</button>
<div class="clearfix"></div>
<?php echo form_close(); ?>
I call the add_post controller function through AJAX.
$(document).on('click','#save-post',function(){
$post_content = $('#post_content').summernote('code');
$.ajax({
url:site_url('Blog/add_post'),
data: $('#blogform').serialize() + "&post_content=" + $post_content,
type: "POST",
dataType: 'json',
encode: true,
success: function(data){
if(!data.success){
if(data.errors){
$('#blog-message').html(data.errors).addClass('alert alert-danger');
}
}else{
alert(data.message);
}
}
});
});
I don't understand why validation is not working properly or I think the controller doesn't get the input file.
File upload data is not stored in the $_POST array, so cannot be validated using CodeIgniter's form_validation library. File uploads are available to PHP using the $_FILES array.
if (empty($_FILES['headerimage']['name']))
{
$this->form_validation->set_rules('headerimage', 'file', 'required');
// OR
$this->form_validation->set_rules('headerimage', 'file', 'trim|required');
}
OR you can use below system/application/libraries/MY_form_validation.php
Example :
$this->form_validation->set_rules(
// Field Name
$file_field_name ,
// Label
"YOUR FILE LAEBL",
// Rules
"file_required|file_min_size[10KB]|file_max_size[500KB]|file_allowed_type[jpg,jpeg]|file_image_mindim[50,50]|file_image_maxdim[400,300]"
);
MY_form_validation.php
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
/*
* Rules supported:
* file_required
* file_allowed_type[type]
* file_disallowed_type[type]
* file_size_min[size]
* file_size_max[size]
* file_image_mindim[x,y]
* file_image_maxdim[x,y]
*/
class MY_Form_validation extends CI_Form_validation {
function __construct()
{
parent::CI_Form_validation();
}
function set_rules($field, $label = '', $rules = '')
{
if(count($_POST)===0 AND count($_FILES) > 0)//it will prevent the form_validation from working
{
//add a dummy $_POST
$_POST['DUMMY_ITEM'] = '';
parent::set_rules($field,$label,$rules);
unset($_POST['DUMMY_ITEM']);
}
else
{
//we are safe just run as is
parent::set_rules($field,$label,$rules);
}
}
function run($group='')
{
$rc = FALSE;
log_message('DEBUG','called MY_form_validation:run()');
if(count($_POST)===0 AND count($_FILES)>0)//does it have a file only form?
{
//add a dummy $_POST
$_POST['DUMMY_ITEM'] = '';
$rc = parent::run($group);
unset($_POST['DUMMY_ITEM']);
}
else
{
//we are safe just run as is
$rc = parent::run($group);
}
return $rc;
}
function file_upload_error_message($error_code)
{
switch ($error_code)
{
case UPLOAD_ERR_INI_SIZE:
return 'The uploaded file exceeds the upload_max_filesize directive in php.ini';
case UPLOAD_ERR_FORM_SIZE:
return 'The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form';
case UPLOAD_ERR_PARTIAL:
return 'The uploaded file was only partially uploaded';
case UPLOAD_ERR_NO_FILE:
return 'No file was uploaded';
case UPLOAD_ERR_NO_TMP_DIR:
return 'Missing a temporary folder';
case UPLOAD_ERR_CANT_WRITE:
return 'Failed to write file to disk';
case UPLOAD_ERR_EXTENSION:
return 'File upload stopped by extension';
default:
return 'Unknown upload error';
}
}
function _execute($row, $rules, $postdata = NULL, $cycles = 0)
{
log_message('DEBUG','called MY_form_validation::_execute ' . $row['field']);
//changed based on
//http://codeigniter.com/forums/viewthread/123816/P10/#619868
if(isset($_FILES[$row['field']]))
{// it is a file so process as a file
log_message('DEBUG','processing as a file');
$postdata = $_FILES[$row['field']];
//before doing anything check for errors
if($postdata['error'] !== UPLOAD_ERR_OK)
{
$this->_error_array[$row['field']] = $this->file_upload_error_message($postdata['error']);
return FALSE;
}
$_in_array = FALSE;
// If the field is blank, but NOT required, no further tests are necessary
$callback = FALSE;
if ( ! in_array('file_required', $rules) AND $postdata['size']==0)
{
// Before we bail out, does the rule contain a callback?
if (preg_match("/(callback_\w+)/", implode(' ', $rules), $match))
{
$callback = TRUE;
$rules = (array('1' => $match[1]));
}
else
{
return;
}
}
foreach($rules as $rule)
{
/// COPIED FROM the original class
// Is the rule a callback?
$callback = FALSE;
if (substr($rule, 0, 9) == 'callback_')
{
$rule = substr($rule, 9);
$callback = TRUE;
}
// Strip the parameter (if exists) from the rule
// Rules can contain a parameter: max_length[5]
$param = FALSE;
if (preg_match("/(.*?)\[(.*?)\]/", $rule, $match))
{
$rule = $match[1];
$param = $match[2];
}
// Call the function that corresponds to the rule
if ($callback === TRUE)
{
if ( ! method_exists($this->CI, $rule))
{
continue;
}
// Run the function and grab the result
$result = $this->CI->$rule($postdata, $param);
// Re-assign the result to the master data array
if ($_in_array == TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
}
// If the field isn't required and we just processed a callback we'll move on...
if ( ! in_array('file_required', $rules, TRUE) AND $result !== FALSE)
{
return;
}
}
else
{
if ( ! method_exists($this, $rule))
{
// If our own wrapper function doesn't exist we see if a native PHP function does.
// Users can use any native PHP function call that has one param.
if (function_exists($rule))
{
$result = $rule($postdata);
if ($_in_array == TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
}
}
continue;
}
$result = $this->$rule($postdata, $param);
if ($_in_array == TRUE)
{
$this->_field_data[$row['field']]['postdata'][$cycles] = (is_bool($result)) ? $postdata : $result;
}
else
{
$this->_field_data[$row['field']]['postdata'] = (is_bool($result)) ? $postdata : $result;
}
}
//this line needs testing !!!!!!!!!!!!! not sure if it will work
//it basically puts back the tested values back into $_FILES
//$_FILES[$row['field']] = $this->_field_data[$row['field']]['postdata'];
// Did the rule test negatively? If so, grab the error.
if ($result === FALSE)
{
if ( ! isset($this->_error_messages[$rule]))
{
if (FALSE === ($line = $this->CI->lang->line($rule)))
{
$line = 'Unable to access an error message corresponding to your field name.';
}
}
else
{
$line = $this->_error_messages[$rule];
}
// Is the parameter we are inserting into the error message the name
// of another field? If so we need to grab its "field label"
if (isset($this->_field_data[$param]) AND isset($this->_field_data[$param]['label']))
{
$param = $this->_field_data[$param]['label'];
}
// Build the error message
$message = sprintf($line, $this->_translate_fieldname($row['label']), $param);
// Save the error message
$this->_field_data[$row['field']]['error'] = $message;
if ( ! isset($this->_error_array[$row['field']]))
{
$this->_error_array[$row['field']] = $message;
}
return;
}
}
}
else
{
log_message('DEBUG','Called parent _execute');
parent::_execute($row, $rules, $postdata,$cycles);
}
}
/**
* Future function. To return error message of choice.
* It will use $msg if it cannot find one in the lang files
*
* #param string $msg the error message
*/
function set_error($msg)
{
$CI =& get_instance();
$CI->lang->load('upload');
return ($CI->lang->line($msg) == FALSE) ? $msg : $CI->lang->line($msg);
}
/**
* tests to see if a required file is uploaded
*
* #param mixed $file
*/
function file_required($file)
{
if($file['size']===0)
{
$this->set_message('file_required','Uploading a file for %s is required.');
return FALSE;
}
return TRUE;
}
/**
* tests to see if a file is within expected file size limit
*
* #param mixed $file
* #param mixed $max_size
*/
function file_size_max($file,$max_size)
{
$max_size_bit = $this->let_to_bit($max_size);
if($file['size']>$max_size_bit)
{
$this->set_message('file_size_max',"%s is too big. (max allowed is $max_size)");
return FALSE;
}
return true;
}
/**
* tests to see if a file is bigger than minimum size
*
* #param mixed $file
* #param mixed $min_size
*/
function file_size_min($file,$min_size)
{
$max_size_bit = $this->let_to_bit($max_size);
if($file['size']<$min_size_bit)
{
$this->set_message('file_size_min',"%s is too small. (Min allowed is $max_size)");
return FALSE;
}
return true;
}
/**
* tests the file extension for valid file types
*
* #param mixed $file
* #param mixed $type
*/
function file_allowed_type($file,$type)
{
//is type of format a,b,c,d? -> convert to array
$exts = explode(',',$type);
//is $type array? run self recursively
if(count($exts)>1)
{
foreach($exts as $v)
{
$rc = $this->file_allowed_type($file,$v);
if($rc===TRUE)
{
return TRUE;
}
}
}
//is type a group type? image, application, word_document, code, zip .... -> load proper array
$ext_groups = array();
$ext_groups['image'] = array('jpg','jpeg','gif','png');
$ext_groups['application'] = array('exe','dll','so','cgi');
$ext_groups['php_code'] = array('php','php4','php5','inc','phtml');
$ext_groups['word_document'] = array('rtf','doc','docx');
$ext_groups['compressed'] = array('zip','gzip','tar','gz');
if(array_key_exists($exts[0],$ext_groups))
{
$exts = $ext_groups[$exts[0]];
}
//get file ext
$file_ext = strtolower(strrchr($file['name'],'.'));
$file_ext = substr($file_ext,1);
if(!in_array($file_ext,$exts))
{
$this->set_message('file_allowed_type',"%s should be $type.");
return false;
}
else
{
return TRUE;
}
}
function file_disallowed_type($file,$type)
{
$rc = $this->file_allowed_type($file,$type);
if(!$rc)
{
$this->set_message('file_disallowed_type',"%s cannot be $type.");
}
return $rc;
}
//http://codeigniter.com/forums/viewthread/123816/P20/
/**
* given an string in format of ###AA converts to number of bits it is assignin
*
* #param string $sValue
* #return integer number of bits
*/
function let_to_bit($sValue)
{
// Split value from name
if(!preg_match('/([0-9]+)([ptgmkb]{1,2}|)/ui',$sValue,$aMatches))
{ // Invalid input
return FALSE;
}
if(empty($aMatches[2]))
{ // No name -> Enter default value
$aMatches[2] = 'KB';
}
if(strlen($aMatches[2]) == 1)
{ // Shorted name -> full name
$aMatches[2] .= 'B';
}
$iBit = (substr($aMatches[2], -1) == 'B') ? 1024 : 1000;
// Calculate bits:
switch(strtoupper(substr($aMatches[2],0,1)))
{
case 'P':
$aMatches[1] *= $iBit;
case 'T':
$aMatches[1] *= $iBit;
case 'G':
$aMatches[1] *= $iBit;
case 'M':
$aMatches[1] *= $iBit;
case 'K':
$aMatches[1] *= $iBit;
break;
}
// Return the value in bits
return $aMatches[1];
}
/**
* returns false if image is bigger than the dimensions given
*
* #param mixed $file
* #param array $dim
*/
function file_image_maxdim($file,$dim)
{
log_message('debug','MY_form_validation:file_image_maxdim ' . $dim);
$dim = explode(',',$dim);
if(count($dim)!==2)
{
//bad size given
$this->set_message('file_image_maxdim','%s has invalid rule expected similar to 150,300 .');
return FALSE;
}
log_message('debug','MY_form_validation:file_image_maxdim ' . $dim[0] . ' ' . $dim[1]);
//get image size
$d = $this->get_image_dimension($file['tmp_name']);
log_message('debug',$d[0] . ' ' . $d[1]);
if(!$d)
{
$this->set_message('file_image_maxdim','%s dimensions was not detected.');
return FALSE;
}
if($d[0] < $dim[0] && $d[1] < $dim[1])
{
return TRUE;
}
$this->set_message('file_image_maxdim','%s image size is too big.');
return FALSE;
}
/**
* returns false is the image is smaller than given dimension
*
* #param mixed $file
* #param array $dim
*/
function file_image_mindim($file,$dim)
{
$dim = explode(',',$dim);
if(count($dim)!==2)
{
//bad size given
$this->set_message('file_image_mindim','%s has invalid rule expected similar to 150,300 .');
return FALSE;
}
//get image size
$d = $this->get_image_dimension($file['tmp_name']);
if(!$d)
{
$this->set_message('file_image_mindim','%s dimensions was not detected.');
return FALSE;
}
log_message('debug',$d[0] . ' ' . $d[1]);
if($d[0] > $dim[0] && $d[1] > $dim[1])
{
return TRUE;
}
$this->set_message('file_image_mindim','%s image size is too big.');
return FALSE;
}
/**
* attempts to determine the image dimension
*
* #param mixed $file_name path to the image file
* #return array
*/
function get_image_dimension($file_name)
{
log_message('debug',$file_name);
if (function_exists('getimagesize'))
{
$D = #getimagesize($file_name);
return $D;
}
return FALSE;
}
}
/* End of file MY_form_validation.php */
/* Location: ./system/application/libraries/MY_form_validation.php */
For required file validation you can set validation as follow:
if (empty($_FILES['headerimage']['name']))
{
$this->form_validation->set_rules('headerimage', 'File', 'required');
}
Remove:
array(
'field' => 'headerimage',
'label' => 'File',
'rules' => 'required|callback_file_check'
)
I've built a View helper that will build a checkbox label and input. The issue I'm having is on populate the checkbox value is ignored.
My form element looks like:
require_once 'Zend/Form/Element/Xhtml.php';
class Core_Form_Element_Other extends Zend_Form_Element_Xhtml
{
public $helper = 'formOther';
public function isValid ($value, $context = null)
{
return parent::isValid($value, $context);
}
public function getValue()
{
return parent::getValue();
}
}
The view helper is:
class Core_View_Helper_FormOther extends Zend_View_Helper_FormElement
{
public function formOther($name, $value = null, $attribs = null)
{
$labelStyle = '';
$label = '';
$checkboxAttrib = array('checked' => 1,'unchecked' => 0);
$textAttrib = array('size' => 10);
foreach ($attribs as $k => $v)
{
$a = str_replace(array('text-', 'checkbox-'), '', $k);
if (strpos($k, 'text-') !== false)
{
$textAttrib[$a] = $v;
}
if (strpos($k, 'checkbox-') !== false)
{
$checkboxAttrib[$a] = $v;
}
}
$textValue = '';
$checkboxValue = $checkboxAttrib['unchecked'];
if (!empty($value))
{
$checkboxValue = $checkboxAttrib['checked'];
$textValue = $value;
}
if (isset($attribs['checkboxLabel']))
{
$label = $attribs['checkboxLabel'];
}
if (isset($attribs['checkboxLabelStyle']))
{
$labelStyle = $attribs['checkboxLabelStyle'];
}
return $this->view->formCheckbox($name.'_check', $checkboxValue, null, $checkboxAttrib)
. ' <label style="'. $labelStyle .'">'. $label .'</label> '
. $this->view->formText($name, $textValue, $textAttrib);
}
}
and Finally it's called with:
$other = $this->createElement('Other', 'otherElem')
->setAttrib('checkboxLabel', 'Other')
->setAttrib('checkbox-checked', 'yes')
->setAttrib('checkbox-unChecked', 'no')
->setAttrib('checkboxLabelStyle', 'font-weight:normal;padding-right:0px;')
->setAttrib('text-size', 20)
->setDecorators(array(
'ViewHelper',
array('Errors', array('class' => 'error small', 'placement' => Zend_Form_Decorator_Abstract::PREPEND)),
array('htmlTag', array('tag' => 'div', 'class' => 'span-8 last'))
));
$this->_telements[] = $other;
What exactly do you mean by 'on populate'? If your value is missing upon submission of the form when the checkbox is not checked, that is default html behavior.
At any point do you set the checkbox itself with an attribute 'checked="checked"'? Value of a checkbox and checked-ness are different things.
In my opinion, using custom decorators is the most-wrongest way to style zend forms. Output default html from zend, make your form labels pretty with jQuery. Zend forms are good for a lot of things, but style and arrangement is not one of them.