How to update multiple fields? - php

I'm getting a number of errors, related to profile_address not being defined. Most of this current code came from a working update for 1 value, I've since tried to add in updating a second value and I would like to expand it to update another 10 values.
I think the issue is the variables are not being parsed correctly in my application.
I initially tried to add it to an array as seen in the model however this hasn't worked.
I think this is a fairly trivial problem and I'm sure it's a simple solution that I haven't thought of yet.
View:
<form action="<?php echo Config::get('URL'); ?>login/editUserProfile_action" method="post">
<label for="comment">Name</label>
<textarea class="form-control" rows="3" name="profile_name"></textarea>
<br>
<label for="comment">Address</label>
<textarea class="form-control" rows="3" name="profile_address"></textarea>
</form>
Controller:
public function editUserProfile()
{
Auth::checkAuthentication();
$this->View->render('login/editUserProfile');
}
/**
* Edit user profile (perform the real action after form has been submitted)
* Auth::checkAuthentication() makes sure that only logged in users can use this action and see this page
*/
// make this POST
public function editUserProfile_action()
{
Auth::checkAuthentication();
UserModel::editUserProfile(Request::post('profile_name', 'profile_address'));
Redirect::to('login/editUserProfile');
}
Model:
public static function editUserProfile($profile_name, $profile_address)
{
// write to database, if successful ...
if (UserModel::saveUserProfile(Session::get('user_id'), $profile_name, $profile_address)) {
Session::set(array('profile_name', $profile_name, 'profile_address', $profile_address));
Session::add('feedback_positive', Text::get('FEEDBACK_EMAIL_CHANGE_SUCCESSFUL'));
return true;
}
Session::add('feedback_negative', Text::get('FEEDBACK_UNKNOWN_ERROR'));
return false;
}
/**
* Writes new data to database
*
* #param $user_id int user id
*
* #return bool
*/
public static function saveUserProfile($user_id, $profile_name, $profile_address)
{
$database = DatabaseFactory::getFactory()->getConnection();
$query = $database->prepare("UPDATE users SET profile_name = :profile_name, profile_address = :profile_address WHERE user_id = :user_id LIMIT 1");
$query->execute(array(':profile_name' => $profile_name, ':profile_address' => $profile_address, ':user_id' => $user_id));
$count = $query->rowCount();
if ($count == 1) {
return true;
}
return false;
}

The problem is in the arrays, I have fixed the issue by doing the following:
public function editUserProfile_action()
{
Auth::checkAuthentication();
UserModel::editUserProfile(Request::post('profile_name'),
Request::post('profile_address'));
Redirect::to('login/editUserProfile');
}
This was fixed by doing the same thing in the model.

Related

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.";

Arrays to database

I have a php file(users.php) which I save the user info. Every time I update or add employee I need to open the file in text editor and make some changes. This is the sample lists of employees in $users array.
$users = array(
'001' => array('id'=>'001', 'name'=>'first lastname', 'dept'=>'Sales', 'position'=>'Lead Team', 'rate'=>'800', 'dayoff'=>'SUN'),
'002' => array('id'=>'002', 'name'=>'sec lastname', 'dept'=>'Sales', 'position'=>'Designer', 'rate'=>'800', 'dayoff'=>'SUN'),
'003' => array('id'=>'003', 'name'=>'david, sample', 'dept'=>'IT', 'position'=>'', 'rate'=>'220.83', 'dayoff'=>'SUN'),
'004' => array('id'=>'004', 'name'=>'Test, Johny', 'dept'=>'', 'position'=>'', 'rate'=>'600', 'dayoff'=>''),
'005' => array('id'=>'005', 'name'=>'Name, Last', 'dept'=>'IT', 'position'=>'Programmer', 'rate'=>'500', 'dayoff'=>'SUN')
);
When I compute their salary I grab all the details of employee($users array) from that file. This is my sample function.
function compute(){
global $users;
include('users.php');
//import list of users;
foreach($the_log as $k=>$v){
if($users[$k]){
//codes here
//show user data with computed salary
}
}
}
How can I make a simple database(like csv file or text file) not MySql or any open source database, so that I can add, edit and delete a user(with just a click) easily whenever I want. What I want to achieve here is to be able to make $users array editable. Is it possible?
Edit: When I use or save data in .csv file, How can I edit or delete a specific user/row?
Just because it's fun, I created an example of how you could do it.
Bare in mind, it's not tested so it might have some bugs but it shows how you could do it.
Since you got so much finished code, I'll leave that up to you to find the bugs. ;-) (However, if you find bugs, leave them as a comment and I'll update the answer).
Important note: Just like #deceze mentioned in his comment, this works well if you know that there won't be any simultaneous "connections" (several people working with the files at the same time) and that you always "open, do stuff, save" and not "open, do stuff, open in a new browser tab, do stuff, save in first tab, save in second tab". Otherwise, your first changes will be overwritten with your second changes and so on...
Class to manage users:
class Users
{
/**
* #var string
*/
protected $path;
/**
* #var array
*/
protected $users = [];
/**
* #param string $path Path to the user file (must be writeable)
*/
public function __construct($path)
{
$this->path = $path;
if (!is_file($this->path)) {
// The file doesn't exist yet, let's create it
file_put_contents($this->path, json_encode([]));
} else {
// It does exist. Load it.
$this->users = json_decode(file_get_contents($this->path), true);
}
}
/**
* Get all users
*
* #return array
*/
public function all()
{
return $this->users;
}
/**
* Get a specific user
*
* #param string|integer $userId The array index for that user
* #return array|null Returns null if user doesn't exist
*/
public function get($userId)
{
if (!array_key_exists($userId, $this->users)) {
// The key doesn't exist, return null
return null;
}
return $this->users[$userId];
}
/**
* Update or add a user
*
* #param string|integer $userId The array index for that user
* #param array $data The user info
* #return boolean
*/
public function save($userId, array $data)
{
$this->users[$userId] = $data;
$written = file_put_contents($this->path, json_encode($this->users));
return $written !== false;
}
}
How you would use it:
// When you have created the instance, use the same instance
// through out your whole application (only do: new Users() once).
// You could do this with some factory class.
$users = new Users('/path/to/users.json');
// List all users
foreach($users->all() as $userId => $row) {
echo $row['first_name'];
// ...
}
// Get user
$user = $users->get('001');
// Change user
$user['first_name'] = "Magnus";
// Save user (this is both update and add)
$users->save('001', $user);

Store forms in session and back button

I'm trying to achieve the following scenario:
1. user display the page addBook.php
2. user starts filling the form
3. but when he wants to select the book Author from the Author combo box, the Author is not yet created in the database so the user clicks a link to add a new Author
5. user is redirected to addAuthor.php
6. the user fill the form and when he submits it, he goes back to addBook.php with all the previous data already present and the new Author selected.
The things is: I have scenarios where there is more than one level of recursion. (Example: Add Book => Add Author => Add Country)
How can I do that?
At step #3, the link submit the form so that I can save it in session.
To handle recursion, I can use a Stack and push the current from on the Stack each time I click a link. And pop the last form of the Stack when the user completes the action correctly or click a cancel button.
My problem is:
How can I handle the back button of the browser?
If instead of clicking the "cancel" button, the user click on the back button, how could I kown that I need to pop the last element?
Do you known some common pattern to achieve that?
You must use javascript on the client and hook into the window unload event, serialize the form and send the answer to the server, which saves it in the session.
$(window).unload(function() {
$.ajax({
url : 'autosave.php',
data : $('#my_form').serialize()
});
});
on server
// autosave.php
$_SESSION['autosave_data'] = $_POST['autosave_data'];
// addbook.php
if (isset($_SESSION['autosave_data'])) {
// populate the fields
}
This is the solution I developed to answer my problem.
As the problem was not a client side problem but truly a server side one. Following the php classes I used in my project:
First the main class of the stack functionality. The inclusion need to be done before the session_start as the object will be stored in the session
class Stack {
private $stack;
private $currentPosition;
private $comeFromCancelledAction = false;
public function __construct() {
$this->clear();
}
/* ----------------------------------------------------- */
/* PUBLICS METHODS */
/* ----------------------------------------------------- */
/**
* Clear the stack history
*/
public function clear() {
$this->stack = array();
$this->currentPosition = -1;
}
/**
* get the current position of the stack
*/
public function getCurrentPosition() {
return $this->currentPosition;
}
/**
* Add a new element on the stack
* Increment the current position
*
* #param $url the url to add on the stack
* #param $data optionnal, the data that could be stored with this $url
*/
public function add($url, &$data = array()) {
if (count($this->stack) != $this->currentPosition) {
// the currentPosition is not the top of the stack
// need to slice the array to discard dirty urls
$this->stack = array_slice($this->stack, 0, $this->currentPosition+1);
}
$this->currentPosition++;
$this->stack[] = array('url' => $url, 'data' => $data, 'previousData' => null, 'linked_data' => null);
}
/**
* Add the stack position parameter in the URL and do a redirect
* Exit the current script.
*/
public function redirect() {
header('location:'.$this->addStackParam($this->getUrl($this->currentPosition)), 301);
exit;
}
/**
* get the URL of a given position
* return null if the position is not valid
*/
public function getUrl($position) {
if (isset($this->stack[$position])) {
return $this->stack[$position]['url'];
} else {
return null;
}
}
/**
* get the Data of a given position
* return a reference of the data
*/
public function &getData($position) {
if (isset($this->stack[$position])) {
return $this->stack[$position]['data'];
} else {
return null;
}
}
/**
* Update the context of the current position
*/
public function storeCurrentData(&$data) {
$this->stack[$this->currentPosition]['data'] = $data;
}
/**
* store some data that need to be fixed in sub flow
* (for example the id of the parent object)
*/
public function storeLinkedData($data) {
$this->stack[$this->currentPosition]['linked_data'] = $data;
}
/**
* Update the context of the current position
*/
public function storePreviousData(&$data) {
$this->stack[$this->currentPosition]['previousData'] = $data;
}
/**
* Compute all linked data for every positions before the current one and return an array
* containing all keys / values
* Should be called in sub flow to fixed some data.
*
* Example: if you have tree pages: dad.php, mum.php and child.php
* when creating a "child" object from a "dad", the dad_id should be fixed
* but when creating a "child" object from a "mum", the mum_id should be fixed and a combo for choosing a dad should be displayed
*/
public function getLinkedData() {
$totalLinkedData = array();
for($i = 0; $i < $this->currentPosition; $i++) {
$linkedData = $this->stack[$i]['linked_data'];
if ($linkedData != null && count($linkedData) > 0) {
foreach($linkedData as $key => $value) {
$totalLinkedData[$key] = $value;
}
}
}
return $totalLinkedData;
}
/**
* Main method of the Stack class.
* Should be called on each page before any output as this method should do redirects.
*
* #param $handler StackHandler object that will be called at each step of the stack process
* Let the caller to be notified when something appens.
* #return the data
*/
public function initialise(StackHandler $handler) {
if (!isset($_GET['stack']) || !ctype_digit($_GET['stack'])) {
// no stack info, acces the page directly
$this->clear();
$this->add($this->getCurrentUrl()); //add the ?stack=<position number>
$this->storeLinkedData($handler->getLinkedData());
$this->redirect(); //do a redirect to the same page
} else {
// $_GET['stack'] is set and is a number
$position = $_GET['stack'];
if ($this->currentPosition == $position) {
// ok the user stay on the same page
// or just comme from the redirection
if (!empty($_POST['action'])) {
// user submit a form and need to do an action
if ($_POST['action'] == 'cancel') {
$currentData = array_pop($this->stack);
$this->currentPosition--;
$handler->onCancel($currentData);
// redirect to the next page with ?stack=<current position + 1>
$this->redirect();
} else {
// store the action for future use
$this->stack[$this->currentPosition]['action'] = $_POST['action'];
$currentData = $this->getData($this->currentPosition);
list($currentData, $nextUrl) = $handler->onAction($currentData, $_POST['action']);
// store current form for future use
$this->storeCurrentData($currentData);
// add the new page on the stack
$this->add($nextUrl);
// redirect to the next page with ?stack=<current position + 1>
$this->redirect();
}
} else if (isset($this->stack[$this->currentPosition]['action'])) {
// no action, and an action exists for this position
$currentData = $this->getData($this->currentPosition);
$action = $this->stack[$this->currentPosition]['action'];
if ($this->comeFromCancelledAction) {
//we return from a cancelled action
$currentData = $handler->onReturningFromCancelledAction($action, $currentData);
$this->comeFromCancelledAction = false;
} else {
$previousData = $this->getPreviousData();
if ($previousData != null) {
//we return from a sucessful action
$currentData = $handler->onReturningFromSuccesAction($action, $currentData, $previousData);
$this->resetPreviousData();
}
}
$this->storeCurrentData( $currentData );
}
$currentData = $this->getData($this->currentPosition);
if ($currentData == null) {
$currentData = $handler->getInitialData();
$this->storeCurrentData( $currentData );
}
return $currentData;
} else if ($this->getUrl($position) == $this->getCurrentUrl()) {
// seems that the user pressed the back or next button of the browser
// set the current position
$this->currentPosition = $position;
return $this->getData($position);
} else {
// the position does not exist or the url is incorrect
// redirect to the last known position
$this->redirect();
}
}
}
/**
* call this method after completing an action and need to redirect to the previous page.
* If you need to give some data to the previous action, use $dataForPreviousAction
*/
public function finishAction($dataForPreviousAction = null) {
$pop = array_pop($this->stack);
$this->currentPosition--;
$this->storePreviousData($dataForPreviousAction);
$this->redirect();
}
/* ----------------------------------------------------- */
/* PRIVATE METHODS */
/* ----------------------------------------------------- */
/**
* get the previous data for the current position
* used when a sub flow finish an action to give some data to the parent flow
*/
private function &getPreviousData() {
if (isset($this->stack[$this->currentPosition])) {
return $this->stack[$this->currentPosition]['previousData'];
} else {
return null;
}
}
/**
* get the current url without the stack parameter
*
* Attention: this method calls "basename" on PHP_SELF do strip the folder structure
* and assume that every pages are in the same directory.
*
* The "stack" parameter is removed from the query string
*
* Example: for the page "http://myserver.com/path/to/a.php?id=1&stack=2"
* PHP_SELF will be: /path/to/a.php
* QUERY_STRING wille be: id=1&stack=2
* This method will return: "a.php?id=1"
*/
private function getCurrentUrl() {
$basename = basename($_SERVER['PHP_SELF']);
if ($_SERVER['QUERY_STRING'] != '') {
return $basename.$this->removeQueryStringKey('?'.$_SERVER['QUERY_STRING'], 'stack');
} else {
return $basename;
}
}
/**
* add the "stack" parameter in an url
*/
private function addStackParam($url) {
return $url . (strpos($url, '?') === false ? '?' : '&') . 'stack=' . $this->currentPosition;
}
/**
* Usefull private method to remove a key=value from a query string.
*/
private function removeQueryStringKey($url, $key) {
$url = preg_replace('/(?:&|(\?))'.$key.'=[^&]*(?(1)&|)?/i', "$1", $url);
return $url != '?' ? $url : '';
}
/**
* reset the previous data so that the data are not used twice
*/
private function resetPreviousData() {
$this->stack[$this->currentPosition]['previousData'] = null;
}
}
Then define the abstract StackHandler class
abstract class StackHandler {
/**
* return the initial data to store for this current page
*/
public function &getInitialData() {
return null;
}
/**
* return an array containing the key/values that need to be fixed in sub flows
*/
public function getLinkedData() {
return null;
}
/**
* user ask to go to a sub page
*/
public function onAction(&$currentData, $action) {
$currentData = $_POST;
$nextUrl = $_POST['action'];
return array($currentData, $nextUrl);
}
public function onCancel(&$currentData) {
}
public function onReturningFromCancelledAction($action, &$currentData) {
}
public function onReturningFromSuccesAction($action, &$currentData, $previousData) {
}
}
Then add the following lines at the top of your pages. Adapt the handler it to fit your needs.
// be sure that a stack object exist in the session
if (!isset($_SESSION['stack'])) {
$_SESSION['stack'] = new Stack();
}
$myDad = $_SESSION['stack']->initialise(new DadStackHandler());
class DadStackHandler extends StackHandler {
/**
* return the initial data to store for this current page
*/
public function &getInitialData() {
if(! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])){
// update
$myDad = new Dad($_GET['id_dad']);
} else {
// creation
$myDad = new Dad();
}
return $myDad;
}
/**
* return an array containing the key/values that need to be fixed in sub flows
*/
public function getLinkedData() {
$linkedData = array();
if (! empty($_GET['id_dad']) && ctype_digit($_GET['id_dad'])) {
$linkedData['id_dad'] = $_GET['id_dad'];
}
return $linkedData;
}
/**
* user ask to go to a sub page
*/
public function onAction(&$myDad, $action) {
//in order not to loose user inputs, save them in the current data
$myDad->name = $_POST['name'];
$nextUrl = null;
// find the next url based on the action name
if ($action == 'child') {
$nextUrl = 'child.php';
}
return array($myDad, $nextUrl);
}
public function onCancel(&$myDad) {
// probably nothing to do, leave the current data untouched
// or update current data
return $myDad;
}
public function onReturningFromCancelledAction($action, &$myDad) {
// probably nothing to do, leave the current data untouched
// called when returning from child.php
return $myDad;
}
public function onReturningFromSuccesAction($action, &$myDad, $newId) {
// update the id of the foreign field if needed
// or update the current data
// not a good example as in real life child should be a list and not a foreign key
// $myDad->childId = $newId;
$myDad->numberOfChildren++;
return $myDad;
}
}
...
if (user submit form and all input are correct) {
if ($myDad->save()) {
// the user finish an action, so we should redirect him to the previous one
if ($_SESSION['stack']->getCurrentPosition() > 0) {
$_SESSION['stack']->finishAction($myDad->idDad);
} else {
// default redirect, redirect to the same page in view more or redirect to a list page
}
}
}
I hope this could help others.

PHP: Error handling without throwing an exception

I'm sending multiple emails from a PHP application, and I want to inform the user of the emails that failed to be sent.
What is the most elegant way to do the error handling when
I don't want to throw an exception that terminates the sending of the remaining emails
The call goes through several method calls
What I want is to get the $notificationSucceeded back from Suggestion::notifyDeletionToAll() to SuggestionController somehow nicely from all notifications.
The depth of the call stack made me doubt if returning it through all the methods is the most elegant way, especially when I already have a return value from Suggestion::cancel().
Is there a better way?
Controller:
class SuggestionController {
function cancelSuggestion($suggestionId)
{
$suggestion = new Suggestion();
$suggestion->fetch($suggestionId);
$suggestionDeleted = $suggestion->cancel();
print json_encode(array(
'status' => 'ok',
'suggestionDeleted' => $suggestionDeleted,
));
}
}
Suggestion class:
class Suggestion {
/**
* Cancels membership of current user in the suggestion
*/
public function cancel()
{
$this->cancelMembership();
if (!$this->hasAcceptedMembers()) {
$this->deleteAndNotify();
return true;
}
return false;
}
/**
* Deletes the suggestion and notifies all the users in it
*/
private function deleteAndNotify()
{
$this->notifyDeletionToAll();
DB::inst()->query("DELETE FROM suggestions WHERE id = {$this->id}");
}
/**
* Notifies about the deletion of the suggestion to all members (users in the suggestion)
*/
private function notifyDeletionToAll()
{
$result = DB::inst()->query("SELECT user_id FROM suggestions_users
WHERE suggestion_id = {$this->id}");
while ($member_id = DB::inst()->fetchFirstField($result)) {
$member = new User();
$member->fetch($member_id);
$notificationSucceeded = $member->notifySuggestionDeleted($this);
}
}
}
I can't understand your question clearly. but i hope this will help you
$successfully_sent_arr = array();
$failure_notsent_arr = array();
if($mail->Send())
{
$successfully_sent_arr[] = $to_email;
//Once the last loop occurs, update this array in database
}
else
{
$failure_notsent_arr[] = $to_email;
//Once the last loop occurs, update this array in database
}

php oop programming: building a class

i am looking at building my first real class, i've played around with bits and bobs but its time to try it for real :)
what i am trying to do is have a form class which handles all my form submissions, checks the data entered and returns with either an error message or success message.
so here one of my forms, (i have 5 of these on 1 page)
<form action="include/mform.php" method="post" name="business_listing">
<input name="biz_name" type="text" value="Business or Place Name" />
<select name="biz_department">
<option value="">Business Sector</option>
<?php
$query = $user->database->query("SELECT * FROM tbl_sectors");
while($row=$user->database->fetchArray($query))
{
$id = $row['sectorID'];
$dept = $row['sectorName'];
echo "<option value='$id'>$dept</option>";
}?>
</select>
<input name="biz_address1" type="text" value="Street Name" />
<select name="job_location">
<option value="">Location</option>
<?php
$query = $user->database->query("SELECT * FROM tbl_places");
while($row=$user->database->fetchArray($query))
{
$id = $row['placeID'];
$dept = $row['placeName'];
echo "<option value='$id'>$dept</option>";
}?>
</select>
<input name="biz_phone" type="text" value="Contact Number" />
<input name="businessSubmit" type="submit" value="Submit" />
</form>
</div>
each of the form's action is set to include/mform.php which contains my class. within the class one of the first things it does is check to see which form was submitted and the idea is then to check the data that has been submitted and do whats necessary with it
my problem is that once my class knows which form was submitted what would be the best way to check the submitted data? should i create varibles within the function to get all the post data and take it from there or should i pass those in the actual function as parameters? , or does it matter?
here is my current class file which is a little bare atm
class Mform
{
private $values = array(); //Holds submitted form field values
private $errors = array(); //Holds submitted form error messages
private $num_errors; //The number of errors in submitted form
public function __construct()
{
if(isset($_POST['businessSubmit']))
{
$this->chkBusiness();
}
if(isset($_POST['jobSubmit']))
{
$this->chkJob();
}
if(isset($_POST['accommodationSubmit']))
{
$this->chkAccommodation();
}
if(isset($_POST['tradeSubmit']))
{
$this->chkTrade();
}
if(isset($_POST['eventSubmit']))
{
$this->chkEvent();
}
}
public function chkBusiness()
{
$field = "business";
}
public function chkJob()
{
return "job";
}
public function chkAccommodation()
{
return "accommodation";
}
public function chkTrade()
{
return "trade";
}
public function chkEvent()
{
return "event";
}
/**
* setValue - Records the value typed into the given
* form field by the user.
*/
public function setValue($field, $value)
{
$this->values[$field] = $value;
}
/**
* setError - Records new form error given the form
* field name and the error message attached to it.
*/
public function setError($field, $errmsg)
{
$this->errors[$field] = $errmsg;
$this->num_errors = count($this->errors);
}
/**
* value - Returns the value attached to the given
* field, if none exists, the empty string is returned.
*/
public function value($field)
{
if(array_key_exists($field,$this->values))
{
return htmlspecialchars(stripslashes($this->values[$field]));
}
else
{
return "";
}
}
/**
* error - Returns the error message attached to the
* given field, if none exists, the empty string is returned.
*/
public function error($field)
{
if(array_key_exists($field,$this->errors))
{
return "<font size=\"2\" color=\"#ff0000\">".$this->errors[$field]."</font>";
}
else
{
return "";
}
}
/* getErrorArray - Returns the array of error messages */
public function getErrorArray()
{
return $this->errors;
}
}
/* Initialize mform */
$mform = new Mform();
most of the individual functions just have return "word" as placeholder so i dont forget to do that function at a later date.
this is what i was thinking of doing for each of the individual form functions
public function chkBusiness()
{
$field = "business";
$name = $_POST['biz_name'];// all need to be sanitized!!
$dept = $_POST['biz_dept'];
$address = $_POST['biz_address'];
$location = $_POST['biz_location'];
$phone = $_POST['biz_phone'];
//start checking the input
if(!$name || strlen($name = trim($name)) == 0)
{
$this->mform->setError($field, "* Name not entered");
}
...
...
}
any help would be appreciated
Luke
I generate a class for every table in my database; life is too short to actually write that functionality for every database table!
Each field has it's own object containing it's value, type, maxlength etc. and the fields are also added to an array so I can do really cool things with them.
Each of the table classes extend a much bigger class that allows me to insert, update, delete and display as tables and forms... I can override any functions that are non-standard although that is very rare.
The performance overhead is about 0.001ms for iterating through about 25000 good sized records.
As an example, my code to produce a datatable of document records looks like this:
//Create object
$objDocument = new cls__CMS_Document();
//Set the fields you want to display in the datatable
$objDocument->objID->Active(true);
$objDocument->objDocument_Name->Active(true);
$objDocument->objAuthorID->Active(true);
$objDocument->objVersion->Active(true);
$objDocument->objDate_Created->Active(true);
$objDocument->objDate_Last_Edited->Active(true);
//Include a hyperlink from the ID field
$objDocument->objID->HyperLink('/drilldown.php');
$objDocument->objID->Post(true);
//Pass a field through a formatting function
$objDocument->objAuthorID->OutputFunction('getAuthorFromID');
$result .= $objDocument->displayTable($sqlConditions);
unset ($objDocument);
The best bit: every step of the way, the auto-complete works:) So you type $objDocument-> and all the methods and properties pop up including all the field objects so you never misspell them.
If I get enough interest (votes), I'll make the whole thing available on the net.
That should be food for thought though when making your own.

Categories