Input box that list values for a select box - php

Can someone help me find a way when i type a text in an Input text (eg: cars ), and the name of cars (bmw, Chevrolet, etc..) is listed in a select box.
do some one have an idea or an example?

If you are familiar with jQuery and Ajax, this can be performed very simply. You watch the input of the text box and fire off an Ajax request once it reaches a stage you are happy with. The return of the Ajax query is them used to populate a drop down box.
Below is an example of this in practice. You'll need to either have jquery on your server, or reference a copy held within a CDN (such as Google). I've had to edit it a bit in order to protect the innocent, but with a couple of more lines it'll work fine.
The only item you'll need to figure out yourself, it by what means should Ajax be called and therefore your drop down list be populated. I've used it after so many characters, once a text pattern has been recognised, and as well as a hoover off event. The choice is yours.
JAVASCRIPT
<script language="JavaScript" src="./js.jquery.inc.js"></script>
<script language="JavaScript" type="text/javascript">
// This is the pure ajax call, needs some way of being called.
$.ajax({
url: "ajaxCode.php",
cache: false,
type: "POST",
data: ({carID : $("#CARS").val()}),
dataType: "xml",
success: function(data){
populateCars(data);
},
error: function(data){
// Something in here to denote an error.
alert("Error no cars retrieved.");
}
});
function populateCars(data)
{
$(data).find("node").each(function()
{
var carStr = $(this).find("String").text()
var carKey = $(this).find("Key").text()
$("<option value='"+carKey+"'>"+carStr+"</option>").appendTo("#CARSLOOKUP");
});
}
</script>
HTML CODE
<input type="text" id="CARS" value="" >
<select id="CARSLOOKUP">
</select>
AJAXCODE.PHP CODE
<?php
// Post code will come in as a POST variable!
$carCode = $_POST['carID'];
// Need to build up an array of cars to return, based upon the example below.
// Preferably based upon the input given within the car code variable.
foreach($carList as $cars)
{
$returnArray[] = array("Key" => "Vectra", "String" => "Vauxhall Vectra");
}
// Output the headers and data required.
header('Cache-Control: no-cache, must-revalidate');
header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
header('Content-type: application/xml');
// This simply converts the array and returns formatted XML data (functions listed below).
$xml = new array2xml('cars');
$xml->createNode($returnArray);
echo $xml;
exit(0);
class array2xml extends DomDocument
{
public $nodeName;
private $xpath;
private $root;
private $node_name;
/**
* Constructor, duh
*
* Set up the DOM environment
*
* #param string $root The name of the root node
* #param string $nod_name The name numeric keys are called
*
*/
public function __construct($root='root', $node_name='node')
{
parent::__construct();
/*** set the encoding ***/
$this->encoding = "ISO-8859-1";
/*** format the output ***/
$this->formatOutput = true;
/*** set the node names ***/
$this->node_name = $node_name;
/*** create the root element ***/
$this->root = $this->appendChild($this->createElement( $root ));
$this->xpath = new DomXPath($this);
}
/*
* creates the XML representation of the array
*
* #access public
* #param array $arr The array to convert
* #aparam string $node The name given to child nodes when recursing
*
*/
public function createNode( $arr, $node = null)
{
if (is_null($node))
{
$node = $this->root;
}
foreach($arr as $element => $value)
{
$element = is_numeric( $element ) ? $this->node_name : $element;
$child = $this->createElement($element, (is_array($value) ? null : $value));
$node->appendChild($child);
if (is_array($value))
{
self::createNode($value, $child);
}
}
}
/*
* Return the generated XML as a string
*
* #access public
* #return string
*
*/
public function __toString()
{
return $this->saveXML();
}
/*
* array2xml::query() - perform an XPath query on the XML representation of the array
* #param str $query - query to perform
* #return mixed
*/
public function query($query)
{
return $this->xpath->evaluate($query);
}
} // end of class
?>

jQuery UI has an autocomplete module:
http://jqueryui.com/demos/autocomplete/

Related

How can I Embed the Reports Filter in Google Data Studio?

This is a scenario that I am trying to achieve: I created a report in Google Data Studio and embedded to my website. I activated "any one with link can view" option so that this report will be visible to my website users.
But I need to show my website users different data depending on their user ids. Basically showing a filtered data according to user id. But the challenge that I have here is, these users do not have a google account. Due to this, using a community connector and passing an email id in the config function will not work for me.
I tried the bookmarking option where you embed a link and change a parameter for different users. It will show correctly in embed code. But once user clicks or opens in Google Data Studio, then this filter goes off.
Has anyone implemented a work around for this scenario? Showing different data right from the webapplication in which the graph is embedded.
Showing different data according to user id is so critical for our use case that without it, data studio with its all functions cannot be used.
In that case, we need to create a custom data source provider.
Step 1: We need to write a custom data source to https://script.google.com/. First, edit the Application.json. Here we have to update the manifest JSON and Code.gs script.
For more information https://github.com/googledatastudio/community-connectors/tree/master/JSON-connect/src
appsscript.json
{
"dataStudio": {
"name": "JSON Connect",
"logoUrl": "https://raw.githubusercontent.com/googledatastudio/community-connectors/master/JSON-connect/assets/logo.png",
"company": "Lingewoud",
"companyUrl": "https://lingewoud.nl/",
"addonUrl": "https://github.com/googledatastudio/community-connectors/blob/master/JSON-connect/README.md",
"supportUrl": "https://github.com/googledatastudio/community-connectors/issues/new?title=JSON%20Connect%20Connector%20Issue",
"shortDescription": "Connect to JSON data by URL",
"description": "Free JSON connector. Fetch data from any JSON data source by URL. Use caching to speed up rendering.",
"sources": ["CUSTOM_JSON"],
"authType": ["NONE"],
"feeType": ["FREE"]
}
}
main.js
// Copyright notice
//
// (c) 2019 Gabriël Ramaker <gabriel#lingewoud.nl>, Lingewoud
//
// All rights reserved
//
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// This copyright notice MUST APPEAR in all copies of the script!
/* eslint-disable prefer-rest-params */
/* eslint-disable prefer-spread */
/**
* Throws and logs script exceptions.
*
* #param {String} message The exception message
*/
function sendUserError(message) {
var cc = DataStudioApp.createCommunityConnector();
cc.newUserError()
.setText(message)
.throwException();
}
/**
* function `getAuthType()`
*
* #returns {Object} `AuthType` used by the connector.
*/
function getAuthType() {
return {type: 'NONE'};
}
/**
* function `isAdminUser()`
*
* #returns {Boolean} Currently just returns false. Should return true if the current authenticated user at the time
* of function execution is an admin user of the connector.
*/
function isAdminUser() {
return false;
}
/**
* Returns the user configurable options for the connector.
*
* Required function for Community Connector.
*
* #param {Object} request Config request parameters.
* #returns {Object} Connector configuration to be displayed to the user.
*/
function getConfig(request) {
var cc = DataStudioApp.createCommunityConnector();
var config = cc.getConfig();
var option1 = config
.newOptionBuilder()
.setLabel('Text')
.setValue('text');
var option2 = config
.newOptionBuilder()
.setLabel('Inline')
.setValue('inline');
config
.newInfo()
.setId('instructions')
.setText('Fill out the form to connect to a JSON data source.');
config
.newTextInput()
.setId('url')
.setName('Enter the URL of a JSON data source')
.setHelpText('e.g. https://my-url.org/json')
.setPlaceholder('https://my-url.org/json');
config
.newCheckbox()
.setId('cache')
.setName('Cache response')
.setHelpText('Usefull with big datasets. Response is cached for 10 minutes')
.setAllowOverride(true);
config
.newSelectSingle()
.setId('nestedData')
.setName('Nested data')
.setHelpText('How to import nested data, as text or inline.')
.setAllowOverride(true)
.addOption(option1)
.addOption(option2);
config.setDateRangeRequired(false);
return config.build();
}
/**
* Gets UrlFetch response and parses JSON
*
* #param {string} url The URL to get the data from
* #returns {Object} The response object
*/
function fetchJSON(url) {
try {
var response = UrlFetchApp.fetch(url);
} catch (e) {
sendUserError('"' + url + '" returned an error:' + e);
}
try {
var content = JSON.parse(response);
} catch (e) {
sendUserError('Invalid JSON format. ' + e);
}
return content;
}
/**
* Gets cached response. If the response has not been cached, make
* the fetchJSON call, then cache and return the response.
*
* #param {string} url The URL to get the data from
* #returns {Object} The response object
*/
function getCachedData(url) {
var cacheExpTime = 600;
var cache = CacheService.getUserCache();
var cacheKey = url.replace(/[^a-zA-Z0-9]+/g, '');
var cacheKeyString = cache.get(cacheKey + '.keys');
var cacheKeys = cacheKeyString !== null ? cacheKeyString.split(',') : [];
var cacheData = {};
var content = [];
if (cacheKeyString !== null && cacheKeys.length > 0) {
cacheData = cache.getAll(cacheKeys);
for (var key in cacheKeys) {
if (cacheData[cacheKeys[key]] != undefined) {
content.push(JSON.parse(cacheData[cacheKeys[key]]));
}
}
} else {
content = fetchJSON(url);
for (var key in content) {
cacheData[cacheKey + '.' + key] = JSON.stringify(content[key]);
}
cache.putAll(cacheData);
cache.put(cacheKey + '.keys', Object.keys(cacheData), cacheExpTime);
}
return content;
}
/**
* Fetches data. Either by calling getCachedData or fetchJSON, depending on the cache configuration parameter.
*
* #param {String} url The URL to get the data from
* #param {Boolean} cache Parameter to determine whether the request should be cached
* #returns {Object} The response object
*/
function fetchData(url, cache) {
if (!url || !url.match(/^https?:\/\/.+$/g)) {
sendUserError('"' + url + '" is not a valid url.');
}
try {
var content = cache ? getCachedData(url) : fetchJSON(url);
} catch (e) {
sendUserError(
'Your request could not be cached. The rows of your dataset probably exceed the 100KB cache limit.'
);
}
if (!content) sendUserError('"' + url + '" returned no content.');
return content;
}
/**
* Matches the field value to a semantic
*
* #param {Mixed} value The field value
* #param {Object} types The list of types
* #return {string} The semantic type
*/
function getSemanticType(value, types) {
if (!isNaN(parseFloat(value)) && isFinite(value)) {
return types.NUMBER;
} else if (value === true || value === false) {
return types.BOOLEAN;
} else if (typeof value != 'object' && value != null) {
if (
value.match(
new RegExp(
/[-a-zA-Z0-9#:%_\+.~#?&//=]{2,256}\.[a-z]{2,4}\b(\/[-a-zA-Z0-9#:%_\+.~#?&//=]*)?/gi
)
)
) {
return types.URL;
} else if (!isNaN(Date.parse(value))) {
return types.YEAR_MONTH_DAY_HOUR;
}
}
return types.TEXT;
}
/**
* Creates the fields
*
* #param {Object} fields The list of fields
* #param {Object} types The list of types
* #param {String} key The key value of the current element
* #param {Mixed} value The value of the current element
*/
function createField(fields, types, key, value) {
var semanticType = getSemanticType(value, types);
var field =
semanticType == types.NUMBER ? fields.newMetric() : fields.newDimension();
field.setType(semanticType);
field.setId(key.replace(/\s/g, '_').toLowerCase());
field.setName(key);
}
/**
* Handles keys for recursive fields
*
* #param {String} currentKey The key value of the current element
* #param {Mixed} key The key value of the parent element
* #returns {String} if true
*/
function getElementKey(key, currentKey) {
if (currentKey == '' || currentKey == null) {
return;
}
if (key != null) {
return key + '.' + currentKey.replace('.', '_');
}
return currentKey.replace('.', '_');
}
/**
* Extracts the objects recursive fields and adds it to fields
*
* #param {Object} fields The list of fields
* #param {Object} types The list of types
* #param {String} key The key value of the current element
* #param {Mixed} value The value of the current element
* #param {boolean} isInline if true
*/
function createFields(fields, types, key, value, isInline) {
if (typeof value === 'object' && !Array.isArray(value) && value !== null) {
Object.keys(value).forEach(function(currentKey) {
var elementKey = getElementKey(key, currentKey);
if (isInline && value[currentKey] != null) {
createFields(fields, types, elementKey, value[currentKey], isInline);
} else {
createField(fields, types, currentKey, value);
}
});
} else if (key !== null) {
createField(fields, types, key, value);
}
}
/**
* Parses first line of content to determine the data schema
*
* #param {Object} request getSchema/getData request parameter.
* #param {Object} content The content object
* #return {Object} An object with the connector configuration
*/
function getFields(request, content) {
var cc = DataStudioApp.createCommunityConnector();
var fields = cc.getFields();
var types = cc.FieldType;
var aggregations = cc.AggregationType;
var isInline = request.configParams.nestedData === 'inline';
if (!Array.isArray(content)) content = [content];
if (typeof content[0] !== 'object' || content[0] === null) {
sendUserError('Invalid JSON format');
}
try {
createFields(fields, types, null, content[0], isInline);
} catch (e) {
sendUserError('Unable to identify the data format of one of your fields.');
}
return fields;
}
/**
* Returns the schema for the given request.
*
* #param {Object} request Schema request parameters.
* #returns {Object} Schema for the given request.
*/
function getSchema(request) {
var content = fetchData(request.configParams.url, request.configParams.cache);
var fields = getFields(request, content).build();
return {schema: fields};
}
/**
* Performs a deep merge of objects and returns new object. Does not modify
* objects (immutable) and merges arrays via concatenation.
* Thanks to jhildenbiddle https://stackoverflow.com/users/4903063/jhildenbiddle
* https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge
*
* #param Objects to merge
* #returns {object} New object with merged key/values
*/
function mergeDeep() {
var objects = Array.prototype.slice.call(arguments);
return objects.reduce(function(prev, obj) {
Object.keys(obj).forEach(function(key) {
var pVal = prev[key];
var oVal = obj[key];
if (Array.isArray(pVal) && Array.isArray(oVal)) {
prev[key] = pVal.concat.apply(pVal, toConsumableArray(oVal));
} else if (pVal === Object(pVal) && oVal === Object(oVal)) {
prev[key] = mergeDeep(pVal, oVal);
} else {
prev[key] = oVal;
}
});
return prev;
}, {});
}
/**
* Converts date strings to YYYYMMDDHH:mm:ss
*
* #param {String} val Date string
* #returns {String} Converted date string
*/
function convertDate(val) {
var date = new Date(val);
return (
date.getUTCFullYear() +
('0' + (date.getUTCMonth() + 1)).slice(-2) +
('0' + date.getUTCDate()).slice(-2) +
('0' + date.getUTCHours()).slice(-2)
);
}
/**
* Validates the row values. Only numbers, boolean, date and strings are allowed
*
* #param {Field} field The field declaration
* #param {Mixed} val The value to validate
* #returns {Mixed} Either a string or number
*/
function validateValue(field, val) {
if (field.getType() == 'YEAR_MONTH_DAY_HOUR') {
val = convertDate(val);
}
switch (typeof val) {
case 'string':
case 'number':
case 'boolean':
return val;
case 'object':
return JSON.stringify(val);
}
return '';
}
/**
* Returns the (nested) values for requested columns
*
* #param {Object} valuePaths Field name. If nested; field name and parent field name
* #param {Object} row Current content row
* #returns {Mixed} The field values for the columns
*/
function getColumnValue(valuePaths, row) {
for (var index in valuePaths) {
var currentPath = valuePaths[index];
if (row[currentPath] === null) {
return '';
}
if (row[currentPath] !== undefined) {
row = row[currentPath];
continue;
}
var keys = Object.keys(row);
for (var index_keys in keys) {
var key = keys[index_keys].replace(/\s/g, '_').toLowerCase();
if (key == currentPath) {
row = row[keys[index_keys]];
break;
}
}
}
return row;
}
/**
* Returns an object containing only the requested columns
*
* #param {Object} content The content object
* #param {Object} requestedFields Fields requested in the getData request.
* #returns {Object} An object only containing the requested columns.
*/
function getColumns(content, requestedFields) {
if (!Array.isArray(content)) content = [content];
return content.map(function(row) {
var rowValues = [];
requestedFields.asArray().forEach(function(field) {
var valuePaths = field.getId().split('.');
var fieldValue = row === null ? '' : getColumnValue(valuePaths, row);
rowValues.push(validateValue(field, fieldValue));
});
return {values: rowValues};
});
}
/**
* Returns the tabular data for the given request.
*
* #param {Object} request Data request parameters.
* #returns {Object} Contains the schema and data for the given request.
*/
function getData(request) {
var content = fetchData(request.configParams.url, request.configParams.cache);
var fields = getFields(request, content);
var requestedFieldIds = request.fields.map(function(field) {
return field.name;
});
var requestedFields = fields.forIds(requestedFieldIds);
return {
schema: requestedFields.build(),
rows: getColumns(content, requestedFields)
};
}
Step 2: Deploy your code. After deployment, you get “Deployment id” and direct URL of your Data Source.
Step 3: Need to provide your source JSON URL and allow parameters to override by URL. Allow parameters for our need for data according to department wise and connect to the data source.
https://drive.google.com/file/d/1ys4RCPyWYC8wP31jWtCJcZpqt6SIKnMC/view?usp=sharing
So by using a custom connector, we can show data according to the role.

How to see Data passed by Ajax to PHP and what PHP does with the Data - Dropdown Menu Option

I want to update my database without refreshing the page when selecting an option from my drop down menu. So I will use Ajax.
The problem is that without refreshing the page and without seeing the PHP code running, I cannot see if there are any errors in my code in order to fix them.
Is there any way to see the data that I pass when selecting an option from my drop down to my PHP file, and see any errors the PHP file reports when processing that data?
I have tried to check the console of my browser but it doesn't display anything.
My drop down:
echo "<select id='vacationId' name='vacationId' style='background:lightblue'>
<option selected='selected' value='' style='background:lightblue'></option>
<option value='" . $vacationIdP . "' style='background:lightblue'>P</option>
<option value='" . $vacationIdO . "' style='background:lightblue'>O</option>
<option value='" . $vacationIdA . "' style='background:lightblue'>A</option>
<option value='" . $vacationIdR . "' style='background:lightblue'>R</option>
</select>";
Ajax code to pass the option Value to a PHP file:
$('#vacationId').change(function(){
var option = $('#vacationId').val();
console.log(option);
$.ajax({
type: 'GET',
url: 'saveVV.php',
data:
{
option:option // Does this pass the value of the option ?
// Can I access this in my PHP file with $vacationId = $_GET['option'];
}
}).done(function(resp){
if(resp == 200) {
console.log('Success!');
}else if(resp == 0){
console.log('Failed..');
}
});
});
What I want is to pass the Value of the selected option to my PHP file and then do some processing to it in PHP.
And I want to see that I pass the correct info to my PHP file.
And I would like to see the PHP code running with that info and maybe displaying some errors.
You can use this wrapper I wrote
https://github.com/ArtisticPhoenix/MISC/blob/master/AjaxWrapper/AjaxWrapper.php
class AjaxWrapper{
/**
* Development mode
*
* This is the least secure mode, but the one that
* diplays the most information.
*
* #var string
*/
const ENV_DEVELOPMENT = 'development';
/**
*
* #var string
*/
const ENV_PRODUCTION = 'production';
/**
*
* #var string
*/
protected static $environment;
/**
*
* #param string $env
*/
public static function setEnviroment($env){
if(!defined(__CLASS__.'::ENV_'.strtoupper($env))){
throw new Exception('Unknown enviroment please use one of the '.__CLASS__.'::ENV_* constants instead.');
}
static::$environment = $env;
}
/**
*
* #param closure $callback - a callback with your code in it
* #param number $options - json_encode arg 2
* #param number $depth - json_encode arg 3
* #throws Exception
*
* #example
*
*
*/
public static function respond(Closure $callback, $options=0, $depth=32){
$response = ['userdata' => [
'debug' => false,
'error' => false
]];
ob_start();
try{
if(!is_callable($callback)){
//I have better exception in mine, this is just more portable
throw new Exception('Callback is not callable');
}
$callback($response);
}catch(\Exception $e){
//example 'Exception[code:401]'
$response['error'] = get_class($e).'[code:'.$e->getCode().']';
if(static::$environment == ENV_DEVELOPMENT){
//prevents leaking data in production
$response['error'] .= ' '.$e->getMessage();
$response['error'] .= PHP_EOL.$e->getTraceAsString();
}
}
$debug = '';
for($i=0; $i < ob_get_level(); $i++){
//clear any nested output buffers
$debug .= ob_get_clean();
}
if(static::environment == static::ENV_DEVELOPMENT){
//prevents leaking data in production
$response['debug'] = $debug;
}
header('Content-Type: application/json');
echo static::jsonEncode($response, $options, $depth);
}
/**
* common Json wrapper to catch json encode errors
*
* #param array $response
* #param number $options
* #param number $depth
* #return string
*/
public static function jsonEncode(array $response, $options=0, $depth=32){
$json = json_encode($response, $options, $depth);
if(JSON_ERROR_NONE !== json_last_error()){
//debug is not passed in this case, because you cannot be sure that, that was not what caused the error.
//Such as non-valid UTF-8 in the debug string, depth limit, etc...
$json = json_encode(['userdata' => [
'debug' => false,
'error' => json_last_error_msg()
]],$options);
}
return $json;
}
}
For example
AjaxWrapper::setEnviroment(AjaxWrapper::ENV_DEVELOPMENT);
AjaxWrapper::respond(function(&$result){
echo "foo";
//..other code
$response['success'] = true;
});
Then when you do console.log it will have whatever was output within the callback as data.debug etc..
Hope it helps.
It uses a combination of ob_* output buffering and exception trapping try/catch to catch any output and wrap it up in an item data.debug or data.error if the environment is set correctly.
Then when you do ajax stuff ... function(data) { console.log(data); } it will be included.
data.debug = "foo"
And so on.
You need to add the dataType parameter, and the data parameter in the ajax call is a javascript object of key and value. So the key should be in single/double quotes mentioned in the code below:
$.ajax({
type: 'GET',
url: 'saveVV.php',
dataType: 'json',
data: { 'option':option } // In php you can access like $_GET['option']
}).done(function(resp){
if(resp == 200) {
console.log('Success!');
}else if(resp == 0){
console.log('Failed..');
}
});

HTML Form: Input type hidden value is visible and changable - Recording Processed State

I am working on an application that I need to post a secret variable. I wrote this code.
<form target="_blank" action="/validate/lista.php" method="POST">
<input type="hidden" name="evento" value="<?php echo $pname ?>" />
<button class="btn btn-block btn-md btn-outline-success">Lista</button>
</form>
My problem is that if the user inspect the element with chrome or whatever, he can see the value and change it before POST.
I could use SESSION but every user has a different session ID and this way I would need to POST the session ID (because they are separete applications), which I think is not secure. Or is it ok?
How can I prevent this? I am new to programming...
Thank you
Maintain HTML Form State safely ('Conversation' Tracking)
Keep track of the 'state' of an HTML Form as it is processed by the client and the server.
The typical 'conversation' is:
Send a new form to the client, often for a specific user who has to login.
The client enters data and returns it.
It is validated and may be sent out again.
The data changes are applied.
the client is informed of the result.
It sounds simple. Alas, we need to keep track of the 'state' of the form during the 'conversation'.
We need to record the state in a hidden field. This can open us up to various 'failure modes'.
This answer is one method of reliably keeping track of the 'conversations'.
Including people being 'malicious'. It happens. ;-/
This is a data change form so we don't want it applied to the wrong person.
There are various requirements:
Sensible ones:
prevent a form being processed twice
Ask a user to confirm the data if the form is too old
Malicious ones:
Changing the form to appear to be from a different user
Using an old copy of the form
Changing other hidden data to corrupt the user data
Now, we cannot prevent the client changing the hidden data, or storing it to replay later. etc.
What to do?
We need to ensure that if it is changed then we can detect that it is tampered with and tell the user about it. We do nothing.
If they send us an old stored valid copy then we can detect that as well.
Is there a simple way of doing this? Oh yes! :)
Answers:
Give each form a unique id: makes it easy to determine if we have already seen it.
Give each form a timestamp of when it was first created.
we can then decide the max age we allow to use it.
If it is too old then we just copy the entered data to a new form and ask the user to confirm it. see Captcha :)
When we process the form we store the form id.
The first check before processing a form is to see if we have already processed it
Identifying 'tampering'?
We encrypt it with AES! :) Only the server needs to know the password so there are no client issues.
If it is changed then the decrypt will fail and we just issue a new form to the user with the data input on it. :)
Is it a lot of code? Not really. And it makes forms processing safe.
One advantage is that has the protection for the CSRF attack built in so no separate code needed.
Program Code (FormState Class)
<?php
/**
* every 'data edit' form has one of these - without exeception.
*
* This ensures that the form I sent out came from me.
*
* It has:
* 1) A unique #id
* 2) A date time stamp and a lifetime
*
* Can be automatically generated and checked.
*/
class FormState {
const MAX_FORM_AGE = 600; // seconds
const ENC_PASSWORD = '327136823981d9e57652bba2acfdb1f2';
const ENC_IV = 'f9928260b550dbb2eecb6e10fcf630ba';
protected $state = array();
public function __construct($prevState = '')
{
if (!empty($prevState)) {
$this->reloadState($prevState); // will not be valid if fails
return;
}
$this->setNewForm();
}
/**
* Generate a new unique id and timestanp
*
* #param $name - optional name for the form
*/
public function setNewForm($name = '')
{
$this->state = array();
$this->state['formid'] = sha1(uniqid(true)); // each form has a unique id
$this->state['when'] = time();
if (!empty($name)) {
$this->setAttribute('name', $name);
}
}
/**
* retrieve attribute value
*
* #param $name attribute name to use
* #param $default value to return if attribute does not exist
*
* #return string / number
*/
public function getAttribute($name, $default = null)
{
if (isset($this->state[$name])) {
return $this->state[$name];
} else {
return $default;
}
}
/**
* store attribute value
*
* #param $name attribute name to use
* #param $value value to save
*/
public function setAttribute($name, $value)
{
$this->state[$name] = $value;
}
/**
* get the array
*/
public function getAllAttributes()
{
return $this->state;
}
/**
* the unique form id
*
* #return hex string
*/
public function getFormId()
{
return $this->getAttribute('formid');
}
/**
* Age of the form in seconds
* #return int seconds
*/
public function getAge()
{
if ($this->isValid()) {
return time() - $this->state['when'];
}
return 0;
}
/**
* check the age of the form
*
*#param $ageSeconds is age older than the supplied age
*/
public function isOutOfDate($ageSeconds = self::MAX_FORM_AGE)
{
return $this->getAge() >= $ageSeconds;
}
/**
* was a valid string passed when restoring it
* #return boolean
*/
public function isValid()
{
return is_array($this->state) && !empty($this->state);
}
/** -----------------------------------------------------------------------
* Encode as string - these are encrypted to ensure they are not tampered with
*/
public function asString()
{
$serialized = serialize($this->state);
$encrypted = $this->encrypt_decrypt('encrypt', $serialized);
$result = base64_encode($encrypted);
return $result;
}
/**
* Restore the saved attributes - it must be a valid string
*
* #Param $prevState
* #return array Attributes
*/
public function fromString($prevState)
{
$encrypted = #base64_decode($prevState);
if ($encrypted === false) {
return false;
}
$serialized = $this->encrypt_decrypt('decrypt', $encrypted);
if ($serialized === false) {
return false;
}
$object = #unserialize($serialized);
if ($object === false) {
return false;
}
if (!is_array($object)) {
throw new \Exception(__METHOD__ .' failed to return object: '. $object, 500);
}
return $object;
}
public function __toString()
{
return $this->asString();
}
/**
* Restore the previous state of the form
* will not be valid if not a valid string
*
* #param $prevState an encoded serialized array
* #return bool isValid or not
*/
public function reloadState($prevState)
{
$this->state = array();
$state = $this->fromString($prevState);
if ($state !== false) {
$this->state = $state;
}
return $this->isValid();
}
/**
* simple method to encrypt or decrypt a plain text string
* initialization vector(IV) has to be the same when encrypting and decrypting
*
* #param string $action: can be 'encrypt' or 'decrypt'
* #param string $string: string to encrypt or decrypt
*
* #return string
*/
public function encrypt_decrypt($action, $string)
{
$output = false;
$encrypt_method = "AES-256-CBC";
$secret_key = self::ENC_PASSWORD;
// iv - encrypt method AES-256-CBC expects 16 bytes - else you will get a warning
$secret_iv_len = openssl_cipher_iv_length($encrypt_method);
$secret_iv = substr(self::ENC_IV, 0, $secret_iv_len);
if ( $action == 'encrypt' ) {
$output = openssl_encrypt($string, $encrypt_method, $secret_key, OPENSSL_RAW_DATA, $secret_iv);
} else if( $action == 'decrypt' ) {
$output = openssl_decrypt($string, $encrypt_method, $secret_key, OPENSSL_RAW_DATA, $secret_iv);
}
if ($output === false) {
// throw new \Exception($action .' failed: '. $string, 500);
}
return $output;
}
}
Example Code
Full Example Application Source Code (Q49924789)
Website Using the supplied Source Code
FormState source code
Do we have an existing form?
$isExistingForm = !empty($_POST['formState']);
$selectedAction = 'start-NewForm'; // default action
if ($isExistingForm) { // restore state
$selectedAction = $_POST['selectedAction'];
$formState = new \FormState($_POST['formState']); // it may be invalid
if (!$formState->isValid() && $selectedAction !== 'start-NewForm') {
$selectedAction = "formState-isWrong"; // force user to start a new form
}
} else {
$_POST = array(); // yes, $_POST is just another PHP array
$formState = new \FormState();
}
Start New Form
$formState = new \FormState();
$_POST = array();
$displayMsg = "New formstate created. FormId: ". $formState->getFormId();
Store UserId (Database Id) in the FormState
$formState->setAttribute('userId' $userId);
Check a form being to old?
$secsToBeOutOfDate = 3;
if ($formState->isOutOfDate($secsToBeOutOfDate)) {
$errorMsg = 'Out Of Date Age: '. $secsToBeOutOfDate .'secs'
.', ActualAge: '. $formState->getAge();
}
Reload State from the form hidden field.
$formState = new \FormState('this is rubbish!!');
$errorMsg = "formState: isValid(): ". ($formState->isValid() ? 'True' : 'False');
Check if a form has already been processed.
if (isset($_SESSION['processedForms'][$formState->getFormId()])) {
$errorMsg = 'This form has already been processed. (' . $formState->getFormId() .')';
break;
}
$_SESSION['processedForms'][$formState->getFormId()] = true;
$displayMsg = "Form processed and added to list.";

Custom attributes on Yii CHtml::checkboxList

Is it possible to create custom HTML attributes on CHtml::checkboxList?
For example, I want to generate an input like this, adding the custom attribute "data-input-x":
<input class="customClass" id="Model_inputX_0" value="1" name="Model[relationX][]" type="checkbox" data-input-x="3">
I already tried using the code bellow, but it not worked:
echo $form->checkboxList($model, 'relationX', $dataList, array('class'=>'checkboxFase refeicaoFaseComum', 'data-input-x'=>3));
If you run your code and inspect element it you will see values the values created by Yii, the difference. Echos under a foreach loop will work nicely..
You can extend CHtml like that:
In folder "components" you create a new file named MyCHtml. In there create the class MyCHtml and copy the core code of framework for checkBoxList (https://github.com/yiisoft/yii/blob/1.1.16/framework/web/helpers/CHtml.php#L1123).
class MyCHtml extends CHtml {
//Final method is provided below
}
Then you add the parameter $extraAttributes=array() after $htmlOptions=array().
The trick is to add those attributes and their values at $htmlOptions array of each input.
If all your configurations are correct and you have access to your componenets as normal, you can call the new checkBoxList function like this:
<?php
//Values can be created dynamically or statically depending on situation
//Each value corresponds to each checkbox value that you want to contain the extra attribute
$extraAttributes = array(
'data-input-x'=>array(
6=>'k',
11=>'a',
7=>'b'),
'data-input-y'=>array(
6=>'c',
2=>'d'),
);
echo MyCHtml::checkboxList(($name, $select, $data, $htmlOptions, $extraAttributes);
?>
The whole class is the following:
<?php
class MyCHtml extends CHtml
{
/**
* Generates a list box.
* ...
* #param array $extraAttributes extra HTML attributes corresponding on each checkbox
* ...
*/
public static function checkBoxList($name,$select,$data,$htmlOptions=array(), $extraAttributes=array())
{
$template=isset($htmlOptions['template'])?$htmlOptions['template']:'{input} {label}';
$separator=isset($htmlOptions['separator'])?$htmlOptions['separator']:self::tag('br');
$container=isset($htmlOptions['container'])?$htmlOptions['container']:'span';
unset($htmlOptions['template'],$htmlOptions['separator'],$htmlOptions['container']);
if(substr($name,-2)!=='[]')
$name.='[]';
if(isset($htmlOptions['checkAll']))
{
$checkAllLabel=$htmlOptions['checkAll'];
$checkAllLast=isset($htmlOptions['checkAllLast']) && $htmlOptions['checkAllLast'];
}
unset($htmlOptions['checkAll'],$htmlOptions['checkAllLast']);
$labelOptions=isset($htmlOptions['labelOptions'])?$htmlOptions['labelOptions']:array();
unset($htmlOptions['labelOptions']);
$items=array();
$baseID=isset($htmlOptions['baseID']) ? $htmlOptions['baseID'] : self::getIdByName($name);
unset($htmlOptions['baseID']);
$id=0;
$checkAll=true;
foreach($data as $value=>$labelTitle)
{
$checked=!is_array($select) && !strcmp($value,$select) || is_array($select) && in_array($value,$select);
$checkAll=$checkAll && $checked;
$htmlOptions['value']=$value;
$htmlOptions['id']=$baseID.'_'.$id++;
//********This does the trick
foreach($extraAttributes as $attributesKey => $attributesValue) {
$found = false;
foreach($attributesValue as $subAttributesKey => $subAttributesValue) {
if ($value === $subAttributesKey) {
$htmlOptions[$attributesKey] = $subAttributesValue;
$found = true;
break;
}
}
if (!$found) {
$htmlOptions[$attributesKey] = '';
}
}
//********All the rest is the same with core method
$option=self::checkBox($name,$checked,$htmlOptions);
$beginLabel=self::openTag('label',$labelOptions);
$label=self::label($labelTitle,$htmlOptions['id'],$labelOptions);
$endLabel=self::closeTag('label');
$items[]=strtr($template,array(
'{input}'=>$option,
'{beginLabel}'=>$beginLabel,
'{label}'=>$label,
'{labelTitle}'=>$labelTitle,
'{endLabel}'=>$endLabel,
));
}
if(isset($checkAllLabel))
{
$htmlOptions['value']=1;
$htmlOptions['id']=$id=$baseID.'_all';
$option=self::checkBox($id,$checkAll,$htmlOptions);
$beginLabel=self::openTag('label',$labelOptions);
$label=self::label($checkAllLabel,$id,$labelOptions);
$endLabel=self::closeTag('label');
$item=strtr($template,array(
'{input}'=>$option,
'{beginLabel}'=>$beginLabel,
'{label}'=>$label,
'{labelTitle}'=>$checkAllLabel,
'{endLabel}'=>$endLabel,
));
if($checkAllLast)
$items[]=$item;
else
array_unshift($items,$item);
$name=strtr($name,array('['=>'\\[',']'=>'\\]'));
$js=<<<EOD
jQuery('#$id').click(function() {
jQuery("input[name='$name']").prop('checked', this.checked);
});
jQuery("input[name='$name']").click(function() {
jQuery('#$id').prop('checked', !jQuery("input[name='$name']:not(:checked)").length);
});
jQuery('#$id').prop('checked', !jQuery("input[name='$name']:not(:checked)").length);
EOD;
$cs=Yii::app()->getClientScript();
$cs->registerCoreScript('jquery');
$cs->registerScript($id,$js);
}
if(empty($container))
return implode($separator,$items);
else
return self::tag($container,array('id'=>$baseID),implode($separator,$items));
}
public static function activeCheckBoxList($model,$attribute,$data,$htmlOptions=array())
{
self::resolveNameID($model,$attribute,$htmlOptions);
$selection=self::resolveValue($model,$attribute);
if($model->hasErrors($attribute))
self::addErrorCss($htmlOptions);
$name=$htmlOptions['name'];
unset($htmlOptions['name']);
if(array_key_exists('uncheckValue',$htmlOptions))
{
$uncheck=$htmlOptions['uncheckValue'];
unset($htmlOptions['uncheckValue']);
}
else
$uncheck='';
$hiddenOptions=isset($htmlOptions['id']) ? array('id'=>self::ID_PREFIX.$htmlOptions['id']) : array('id'=>false);
$hidden=$uncheck!==null ? self::hiddenField($name,$uncheck,$hiddenOptions) : '';
return $hidden . self::checkBoxList($name,$selection,$data,$htmlOptions);
}
/**
* Generates a push Html button that can submit the current form in POST method.
* #param string $label the button label
* #param mixed $url the URL for the AJAX request. If empty, it is assumed to be the current URL. See {#link normalizeUrl} for more details.
* #param array $ajaxOptions AJAX options (see {#link ajax})
* #param array $htmlOptions additional HTML attributes. Besides normal HTML attributes, a few special
* attributes are also recognized (see {#link clientChange} and {#link tag} for more details.)
* #return string the generated button
*/
public static function ajaxSubmitHtmlButton($label,$url,$ajaxOptions=array(),$htmlOptions=array())
{
$ajaxOptions['type']='POST';
$htmlOptions['type']='submit';
return self::ajaxHtmlButton($label,$url,$ajaxOptions,$htmlOptions);
}
}

cakephp tcpd error: Some data has already been output to browser, can't send PDF file on remote server

I'm getting the following warning when try to view list_of_holidays.pdf from the remote server:
Warning (2): Cannot modify header information - headers already sent by (output started
at /home/aquinto1/app/views/helpers/flash.php:155) [APP/vendors/tcpdf/tcpdf.php, line 8541]
TCPDF ERROR: Some data has already been output to browser, can't send PDF file
line 155 is the last line in flash.php ie the closing tag for php (?>). Before that it is the code to embedSWF. I don't see anything wrong with that.
However, it is displaying fine on the local server.
I've checked for whitespaces and yet the error is still there.
i'm already using ob_clean before the output.
can someone tell me on what i'm doing wrong. FYI i'm using cakephp with tcpdf.
The following is flash.php
class FlashHelper extends AppHelper {
var $helpers = array('Javascript');
/**
* Used for remembering options from init() to each renderSwf
*
* #var array
*/
var $options = array(
'width' => 100,
'height' => 100
);
/**
* Used by renderSwf to set a flash version requirement
*
* #var string
*/
var $defaultVersionRequirement = '9.0.0';
/**
* Used by renderSwf to only call init if it hasnt been done, either
* manually or automatically by a former renderSwf()
*
* #var boolean
*/
var $initialized = false;
/**
* Optional initializing for setting default parameters and also includes the
* swf library. Should be called once, but if using several groups of flashes,
* MAY be called several times, once before each group.
*
* #example echo $flash->init();
* #example $flash->init(array('width'=>200,'height'=>100);
* #return mixed String if it was not able to add the script to the view, true if it was
*/
function init($options = array()) {
if (!empty($options)) {
$this->options = am($this->options, $options);
}
$this->initialized = true;
$view =& ClassRegistry::getObject('view');
if (is_object($view)) {
$view->addScript($this->Javascript->link('swfobject'));
return true;
} else {
return $this->Javascript->link('swfobject');
}
}
/**
* Wrapper for the SwfObject::embedSWF method in the vendor. This method will write a javascript code
* block that calls that javascript method. If given a dom id as fourth parameter the flash will
* replace that dom object. If false is given, a div will be placed at the point in the
* page that this method is echo'ed. The last parameter is mainly used for sending in extra settings to
* the embedding code, like parameters and attributes. It may also send in flashvars to the flash.
*
* For doucumentation on what options can be sent, look here:
* http://code.google.com/p/swfobject/wiki/documentation
*
* #example echo $flash->renderSwf('counter.swf'); // size set with init();
* #example echo $flash->renderSwf('flash/ad.swf',100,20);
* #example echo $flash->renderSwf('swf/banner.swf',800,200,'banner_ad',array('params'=>array('wmode'=>'opaque')));
* #param string $swfFile Filename (with paths relative to webroot)
* #param int $width if null, will use width set by FlashHelper::init()
* #param int $height if null, will use height set by FlashHelper::init()
* #param mixed $divDomId false or string : dom id
* #param array $options array('flashvars'=>array(),'params'=>array('wmode'=>'opaque'),'attributes'=>array());
* See SwfObject documentation for valid options
* #return string
*/
function renderSwf($swfFile, $width = null, $height = null, $divDomId = false, $options = array()) {
$options = am ($this->options, $options);
if (is_null($width)) {
$width = $options['width'];
}
if (is_null($height)) {
$height = $options['height'];
}
$ret = '';
if (!$this->initialized) {
$init = $this->init($options);
if (is_string($init)) {
$ret = $init;
}
$this->initialized = TRUE;
}
$flashvars = '{}';
$params = '{wmode : "opaque"}';
$attributes = '{}';
if (isset($options['flashvars'])) {
$flashvars = $this->Javascript->object($options['flashvars']);
}
if (isset($options['params'])) {
$params = $this->Javascript->object($options['params']);
}
if (isset($options['attributes'])) {
$attributes = $this->Javascript->object($options['attributes']);
}
if ($divDomId === false) {
$divDomId = uniqid('c_');
$ret .= '<div id="'.$divDomId.'"></div>';
}
if (isset($options['version'])) {
$version = $options['version'];
} else {
$version = $this->defaultVersionRequirement;
}
if (isset($options['install'])) {
$install = $options['install'];
} else {
$install = '';
}
$swfLocation = $this->webroot.$swfFile;
$ret .= $this->Javascript->codeBlock(
'swfobject.embedSWF
("'.$swfLocation.'", "'.$divDomId.'", "'.$width.'", "'.$height.'", "'.$version.'",
"'.$install.'", '.$flashvars.', '.$params.', '.$attributes.');');
return $ret;
}
}
?>
Simply do what the error tells you: Check app/views/helpers/flash.php line 155 and see what it is outputting there and fix it. There must be some code that outputs something.
Could it be one of the return statements?
if (is_object($view)) {
$view->addScript($this->Javascript->link('swfobject'));
return true;
} else {
return $this->Javascript->link('swfobject');
}
$ret .= $this->Javascript->codeBlock(
'swfobject.embedSWF
("'.$swfLocation.'", "'.$divDomId.'", "'.$width.'", "'.$height.'", "'.$version.'",
"'.$install.'", '.$flashvars.', '.$params.', '.$attributes.');');
return $ret;
}
What other code is on the page calling flash?

Categories