I am making quite a large online points/purchasing system in PHP and just have a fundamental question.
All the relevant stuff is on a single PHP page within the site, with "includes" from other parts of the site such as shopping cart, points review, products etc..., but throughout the page there are stages where the user clicks a form submit button to pass values via $_POST.
As there is a main page for all this stuff, I have a part at the top of the page where it takes all the POST values and makes decisions based upon them, like so:
if($_POST['add']) {
$product_id = $_POST['add'];
}
if($_POST['remove']) {
$rid = $_POST['id'];
$cart->del_item($rid);
}
if($_POST['empty']){
$cart->empty_cart();
}
if($_POST['purchase']) {
foreach($cart->get_contents() as $item) {
$sql="INSERT INTO wp_scloyalty_orders VALUES (".$user_id.", ".$item['id'].")";
$result=mysql_query($sql);
}
$cart->empty_cart();
unset($_SESSION['cart']);
}
if($_POST['add']) {
query_posts('post_type=prizes&showposts=-1&p='.$product_id.'');
while (have_posts()) : the_post();
$my_meta = get_post_meta($post->ID,'_my_meta',TRUE);
if($calctotalnew > $my_meta['pointsvalue']){
$cart->add_item(get_the_id(), 1, $my_meta['pointsvalue'], get_the_title());
} else {
echo 'You do not have sufficient points to redeem this product...';
}
endwhile;
wp_reset_query();
}
So my question is... is this really a good way to organize a system, having the form actions go to the same page that the form is on, and have a load of IF statements to decide what to do with the POST values?
Thanks! :)
it's generally best to capture separate POST calls (grouped by type) in separate actions. I usually go as follows:
page 1 has a form, which will submit to eg. product.php?action=add. In product.php you can route the 'add' action to the function add_product() (or whatever). Then when the product is added, just header the user back to the main page (or whatever page you'd like). This immediately tackles the problem with refresh-posts (user refreshing the page which will send the same data again).
following mvc imagine you have a controller Product which handles all the product actions. The skeleton could look like this (assuming function action_x will be executed when yoursite.com/product/x is requested):
class Product_Controller {
function action_show() {
}
function action_update() {
}
function action_delete() {
}
}
if your framework supports a default action of some sort you could route your actions:
function action_default() {
if(method_exists(array($this, 'action_'. $_POST['action']))) {
return call_user_method('action_'. $_POST['action'], $this);
}
}
ofcourse the same can be achieved without controller classes;
if(function_exists('action_'. $_POST['action'])) {
call_user_func('action_'. $_POST['action']);
}
function action_show() { }
...
and to illustrate the discussion in the comments;
function action_update() {
// do some update logic, query an UPDATE to mysql etc.
if($result) {
// optionally save a success message
Message::add('Your record has been updated');
header('Location: main_page.php'); // or another intelligent redirect function
} else {
Message::add('Sorry, something went wrong');
header('Location: error_page.php'); // or also main_page
}
}
This will also keep your code cleaner, as updating/adding/deleting stuff is radically different from showing stuff, this will prevent you from mixing up stuff. You could even call the show function from within the update function if you want to skip the redirect.
But in the end it's a matter of choice, led by pragmatism or your framework ;)
I hope this'll explain everything a bit, don't hesitate to ask for clarification
if you want to separate the logic from the interface then you can simple create new file and put the all logical and database related code in that file and include OR require that file in the view file
like
view.php interface file and
logic.php is your logic file then
first line in view.php is
require_once(logic.php');
and all the logic is in this files
simple MVC
Well, it seems I have to explain.
It absolutely does not matter how much IF statements you have in the POST handler. Your current design is okay, and there is no reason to ask nor change it.
The only thing that you may wish to add to your design is a front controller, which will take both entity (cart) and action("add") and call add() method of $cart class. these methods you may store one under another in the class source.
Though it is quite huge improvement, requiring great rethinking of the whole site architecture. So, you may stick with your current one.
As for your other question, how to display errors, here is an answer: php redirection not working
Related
I am Working on making the menu for our content management software using php and we are having this small issue. Since we want everything to eventually be called in chunks, were breaking certain page items into chunks and loading them via functions through an included file. Since this is hard to explain, I will post some example code of what i mean below.
This is the file page.php (removed needless html code).
This is the page the user is on:
<?php
define("CURRENT_PAGE", "page.php");
include_once("data/main.inc.php");
?><html>
Content loads here.
<? desktopMenu(); ?>
</html>
Okay and here's the function for desktopMenu() from main.inc.php:
function desktopMenu() {
// Query to get the top level navigation links with no parents
$query = mysql_query("SELECT * FROM menu WHERE p_id = '0'");
if(mysql_num_rows($query) > 0) {
while($result = mysql_fetch_array($query)) {
extract($result);
if($isparent == "1") {
// Just check if they have children items
$sub_menu_query = mysql_query("SELECT * FROM menu WHERE p_id = '$id'");
if(mysql_num_rows($sub_menu_query) > 0) {
// CODE TO SHOW THE MENU ITEM AND ITS SUBS
}
} else {
// CODE TO SHOW REGULAR MENU ITEMS
// WANT TO INSERT class='active' if the CURRENT_PAGE is this value..
echo "<li><a href='#'>link</a></li>";
}
} else {
echo "<li><a href='javascript:void(0);'>Error Loading Menu</a></li>";
}
}
I am wondering how I can get the CURRENT_PAGE on the included script so I can load the class="active" onto the correct page. I am already using the following:
$config = include('config.inc.php');
$GLOBALS = $config;
on the top of main.inc.php, above this menu function so I could set global variables and include my $config['database'] variables for calling the SQL database within a function (doesn't work otherwise).
How can I check the current_page variable so I can set it active in the menu? I have tried a few different things but nothing is showing the way we expect it to. Thanks guy.
First of all I would recommend looking at MVC architecture when building your apps. I believe the use of GLOBALS is frowned upon.
To answer your question:
Since you are defining a constant define("CURRENT_PAGE", "page.php"); then this will be globally available within the scope of the function desktopMenu()
so you may use something like:
$className = (isset(CURRENT_PAGE) && CURRENT_PAGE=='xxxxx')?'class="active"':'';
echo "<li>link</li>";
xxxx string is most likely a field output from you database as the page name which will match the defined constant.
$className = (isset(CURRENT_PAGE) && CURRENT_PAGE==$result['page_name'])?'class="active"':'';
This is the basic form and you will most likely need additional conditions for the 'active' menu switch mapping to different pages.
I've tried to answer your question with an example although the structure you have used run the app is not the recommended way to develop.
I would look at the way modern frameworks are structured (Laravel, Zend, Symphony...) and utilise these.
I would also try and automate the page mapping (e.g. look at the URL and pull out the page from a rewrite which matches to the menu in your database)
best of luck
There are multiple options. Including static functions, global variables and passing the variable or object into the function.
The consensus for various reasons is to pass the variable into the function
$myVar = new Object\Or\Data();
function myFunction($myVar) {
//do stuff with $myVar
}
//then call the function
myFunction($myVar);
There are lots of answers to this question on stackOverflow, so have a deeper search. Here is an example
I found the solution to my problem and thought I would share here. I first set the call on the page.php to use desktopMenu(CURRENT_PAGE); and then on my main.inc.php I added this line
$thispage = CURRENT_PAGE;
function desktopMenu($thispage) {
//REST OF FUNCTION
}
And I set a table variable on each menu item called menu-group, so I can define the current menu group for a menu item and have the appropriate menu item highlighted when you're on that page or one of it's sub pages.
Thanks so much for the answers guys!
I have a TON of AJAX requests that are submitted to my functions.php file using "actions". Then in my functions.php file, I have many code blocks like the one below.
When I look at the developer tools, my functions.php file is being called many times as well (one for each AJAX POST). I'm thinking about using Amazon S3 when I go live and they charge a fee based upon how many POST requests are sent. The other concern I have is that I'm not sure that the way I've coded my functions.php file with all these blocks of code and associated AJAX requests is the best way to do it. Could I put these code blocks in a function or class and somehow combine the AJAX POSTS request into only a few POST requests?
if(isset($_POST['action']) && ($_POST['action'] == 'delete_account')) {
require("config.php");
require("database.php");
$deleteAccount = $_POST['account'];
try {
$results = $db->prepare("DELETE FROM account WHERE account_id_PK = ?");
$results->bindValue(1, $deleteAccount);
$results->execute();
} catch(Exception $e) {
echo "ERROR: Data could not be removed from the database. " . $e;
exit;
}
echo $deleteAccount;
Can you do something like...
$.AJAX({
data: {action= "delete_account", ... action="add_account"...}
});
obviously the bit above is not my actual ajax code, just illustrating my question as best as possible.
You're on the right track yes. However you need to make a couple changes.
First off replace = with : in your Javascript object.
Then you need to make sure you are using unique key names. So either do action1 and action2, or better yet create an array of actions like this:
data: { actions : ["delete_account", "add_account"] }
Then server-side realize that you have an array $_POST['actions'].
So you could do something like this:
if(is_array($_POST['actions']) && in_array("delete_account", $_POST['actions'])) {
// process the delete
Side note: regarding S3 pricing, you do realize that they charge you one penny for two thousand POST requests right? I'm all for making code concise. But if it really complicates things for you, you may rethink this. S3 pricing for POST requests is ridiculously cheap.
I have a relatively simple class which deletes a post:
function delete_post($postid, $reason){
//Stuff to delete post
$this->delete_response = 'Thanks, your course has been removed.';
}
This function is called at the top of a page with a form on. If the form is submitted, the same page checks the POST[] and carries out the function, like so:
if(!empty($_POST['removecourse'])){
$courseManager->delete_post($_POST['courseid'], $_POST['cancel-reason']);
echo $courseManager->delete_response;
};
So my problem is... when I refresh the page, the message keeps displaying. I know this is because I am re-submitting the form, and because there is no such P/R/G pattern going on, but as i am new to OOP, im wondering if im doing this the right way, or if anyone could suggest a way similar to PRG or something?
Add an if that test if somthing changed, like mysql_affected_rows
function delete_post($postid, $reason)
{
//Stuff to delete post
if(mysql_affected_rows())
{
$this->delete_response = 'Thanks, your course has been removed.';
}
}
I currently have a page defined which displays some data in rows. At the end of each row, there is a view which shows a total which is extracted from mysql.
$r->add('View_PointsLeft', 'pleft', 'pointsleft')
->setPoints($row['points_left'])
->setBacklog($row['backlog_ref'])
->setID($row['id'].'-points-left');
The view is defined with a template like this
<!-- points left -->
<div class='target points_left'>
<div class='sticky green'>
<div class='story'><?$backlog?></div>
<div id='<?$name?>' class='big_points big_point_margin'><?$pointsleft?></div>
</div>
</div>
<!-- end 0000-points-left -->
The data to populate the view is selected using a sql in the page which is looped through and the /lib/view/pointsleft.php code has set methods which are passed parameters from the page and update the fields in the template.
class View_PointsLeft extends View_Sticky {
function init(){
parent::init();
}
function setPoints($points){
$this->template->set('pointsleft',$points);
return $this;
}
function setBacklog($backlog){
$this->template->set('backlog',$backlog);
return $this;
}
function defaultTemplate(){
return array('view/scrumwall/pointsleft');
}
}
I want to update the database when something is changed on the page and also update the total view (to decrement the counter).
First, I'm wondering if i have approached this the wrong way (should each view should be self contained) - should i just pass the id field to the view, attach the relevant model to the view inside lib/view/pointsleft.php and call the set fields using the model values ?
Secondly, If i change it that way, does it then make it easier to update the view with a particular id when the database value is changed using ajax and if so , how do i do this ?
Thirdly - if i want to also trigger an update into the database based on an action on the client side javascript, where would i put this code e.g. in the non atk4 version of my code, i had a script called using $.post("update.php") which would update mysql. Where would i put such a script in ATK4 ?
Thanks in advance.
Update after answer from Romans
Man, ATK4 rocks ! - it does more than i expected and i was busy creating functions inside the view to populate each field name, so now having redone it using addModel,
the call from the page looks like this
$r->add('View_PointsLeft', 'pleft', 'pointsleft')
->loadData($row['id']);
the templates/view looks like this
<div id='<?$name?>' class='target points_left'>
<div class='sticky green'>
<div class='story'><?$backlog_ref?></div>
<div class='big_points big_point_margin'><?$points_left?></div>
</div>
</div>
and the lib/view code looks like this
<?php
class View_PointsLeft extends View_Sticky {
function loadData($id){
$this->setModel('Story')->loadData($id);
}
function init(){
parent::init();
}
function defaultTemplate(){
return array('view/scrumwall/pointsleft');
}
}
Update after code example from Romans
After following the code example Romans provided, i now add the URL call using jquery selectors at the bottom of my page code and do some jiggery pokery to get the task and status from the id fields (not sure about using HTML5 only stufff using data-id so just set the normal id and extract from that). Previously the drop code was in my own univ.js script but i dont have access to the php variables from there so i move it into the page
$p->js(true)->_selector('.movable')->draggable();
$p->js(true)->_selector('.target')->droppable(array(
'drop'=>$this->js(null,'function(event,ui){'.
' target=$(this).attr("id").split("-");'.
' zone=target[2];'.
' sticky=$(ui.draggable).attr("id").split("-");'.
' task=sticky[1];'.
' switch (zone) {'.
' case "verify": newStatus="V";'.
' break;'.
' case "in": newStatus="P";'.
' break;'.
' case "to": newStatus="I";'.
' break;'.
' case "done": newStatus="D";'.
' break;'.
'}
$.univ().ajaxec({ 0:"'.$this->api->getDestinationURL().'",'.
'task: task, status: newStatus }); } ')
));
and i have a if block which looks like this in the page. I add Model_Task and load the values based on the GET parameter so i then also have more information including the story it relates to so i can also update the points if the status is now done.
if($_GET['task'] && $_GET['status'])
{
$new_status=$_GET['status'];
$task_id=$_GET['task'];
$t=$p->add('Model_Task')->loadData($task_id);
$old_status=$t->get('status');
$task_points=$t->get('points');
if ($new_status<>$old_status & ($new_status=='D' | $old_status=='D'))
{
$s=$p->add('Model_Story')->loadData($t->get('story_id'));
if ($old_status='D')
{
$s->set('points_left',$s->get('points_left')+$task_points);
} else {
$s->set('points_left',$s->get('points_left')-$task_points);
}
$s->update();
$story=$t->get('story_id');
}
$t->set('status',$new_status);
$t->update();
}
i can then calculate the new number of points and update the story with points left and update the task with the new_status by setting the model values and using update().
If i now move one of the draggables, it works but opens a new window showing again the whole page and reporting
Error in AJAXec response: SyntaxError: syntax error
I think opening the extra window is because of the error but the error is something to do with the response having all the html for the whole page. I dont actually want any reload from the ajax call unless the status is a particular one.
Also the last thing i need to do is only reload one view on the page for the particular story that was updated.
I've tried by creating an array and adding the short variables to it like this when the page is first loaded
$this->pl_arr[$row['id']]=$r->add('View_PointsLeft', 'pleft', 'pointsleft')
->loadData($row['id']);
and then in the if block while processing the GET, to recall it
$pleft=$this->pl_arr[$story];
$pleft->js()->reload()->execute();
but it fails with an error
Error in AJAXec response: SyntaxError: missing ; before statement
Fatal error: Call to a member function js() on a non-object in C:\wamp\www\paperless\page\scrumwall.php on line 247
Final update
The last error is caused because i didnt use for the id in the outer div of the whole view i wanted to update. Once i changed this it is no longer null.
So the first time the page is loaded, i store all the view names in an associative array in a loop as i put them on the page
$st = $p->add('Model_Story');
$result = $st->getRows();
foreach ($result as $row) {
if (is_array($row)) {
$r=$p->add('View_Scrumwall_StoryRow')
->setWorkspace('ws-'.$row['id']);
... other code here ...
$points_left[$row['id']]=$r->add('View_PointsLeft', null, 'pointsleft')
->loadData($row['id']);
}
and then have the if GET block like this
if($_GET['task'] && $_GET['status'])
{
$new_status=$_GET['status'];
$task_id=$_GET['task'];
$t=$p->add('Model_Task')->loadData($task_id);
$old_status=$t->get('status');
$task_points=$t->get('points');
if ($new_status<>$old_status && ($new_status=='D' || $old_status=='D'))
{
$s=$p->add('Model_Story')->loadData($t->get('story_id'));
if ($new_status=='D')
{
$s->set('points_left',$s->get('points_left')-$task_points);
} else {
$s->set('points_left',$s->get('points_left')+$task_points);
}
$s->update();
$story=$t->get('story_id');
//reload the points left sticky note for the story of the task
$js[]=$points_left[$story]->js()->reload();
}
$t->set('status',$new_status);
$t->update();
$js[]=$this->js()->reload();
$this->js(null,$js)->execute();
}
Note that if I only want to update one view on the page, i can just call that chaing that object with reload and execute e.g.
$pl->js()->reload()->execute
but if i want to update several views on the page, i need to put them in an array (here called js[]) and then call execute like this - you can also see an example of this in Roman's codepad example.
$js[]=$points_left[$story]->js()->reload();
$js[]=$this->js()->reload();
$this->js(null,$js)->execute();
Problem solved with ATK4 :)
Ok, for a cleaner answer, I've put together a sample:
http://codepad.agiletoolkit.org/dragaction.html
Probably example here answers the question better.
In your case, since you are working with models, it should be easier to set this up. For the performance I decided to use 2 Listers, but in theory you can also have each person and task as a view.
I'm storing associations in session (through memorize) in your case you would store them in database.
Your structure seem to be OK. If you use setModel() on it which would have "pointsleft" and "backlog" fields, those would be automatically filled in.
I don't see how setID is defined though, but you could extend setModel, call parent and then execute that too.
Another thing I noticed, is in your template the most top-level div should have id=''. This gives your view unique selector which js() uses by default.
The .post function you are looking for is univ()->ajaxec(). It sends data to the server, receives javascript and executes it, hence the name. It behaves similarly to the form.
$mybutton->js('click')->ajaxec($this->getDestinationURL(null,array('a'=>'b'));
if($_GET['a']){
// update database
$r->getElement('plfat')->js()->reload()->execute();
}
Usually to make your code universal, you can drop this above code inside the view, but instead of 'a' you should better use the name of the object, like this. This eliminates the need for a separate page handling update:
$this->mybutton->js('click')->ajaxec($this->getDestinationURL(null,
array($this->name=>'reload'));
if($_GET[$this->name]){
// update database
$this->js()->reload()->execute();
}
Update
To clarify the sequence of how it's executed:
The page is rendered into HTML sent to your browser.
Along with the page Javascript chains are sent. All of them which define 1st argument to js, such as js(true), js('click'). in my code i have js('click') so it's sent to browser.
User performs the action such as clicking on a button. This triggers ajaxec() function
ajaxec function performs AJAX request to the page with the arguments you specify there.
PHP again is executed, but this time it goes inside if() branch. A js() without argument is created and ->execute() sends javascript to the browser.
browser receives output of the js()...->execute() and evaluates it. In our case it contains reload() for some other element.
atk4_loader widget is used to perform reload of other part of the page it sends AJAX request to server
PHP is executed with cut_object argument. It re-initializes original page, but renders only one object selectively. Output for that object is sent back to the frontend.
PHP also re-generates JS chains like in #2 but only relevant to that object
Frontend's atk4_loader receives the code, replaces HTML of the element and re-evaluates the javascript.
back to #3
It sounds like a lot of actions. In reality that's 2 requests per click and you can eliminate one if you do reload right away. Note that you can also pass arguments to reload() which you can then fetch from "get". I don't fully understand what triggers the action in your original script, perhaps we can find this out in https://chat.stackoverflow.com/rooms/2966/agile-toolkit-atk4 ?
I have a Drupal website and I want to show different welcome pages, depending on what my users enter as profile fields. I can't use the global $user variable, because users are not automatically logged in (They have to very their email address before they can log in).
Where can I add code to set the redirect?
I've tried with $form['#redirect'] and $form_state['redirect'] in the form validator, but that didn't work.
You can use logintobogan for inspiration:
#implementation of hook_user
mymodule_user($op) {
if ($op == 'login') {
$_REQUEST['destination'] = '/user/will/be/redirected/here'
}
}
The important part is to make sure, that by the time the final drupal_goto() is called in user.module, you have set your $_REQUEST['destination'].
A few things to note:
Logintoboggan has a lot of code to deal with all sorts of edge-cases, such as redirecting out/to https. You can ignore these, if your case is simple.
Your module must be called after user.module and probably after other modules implementing hook_user, for they might change this global too. Very ugly, but the way this works in Drupal.
Do not -ever- issue drupal_goto() in any hook. Especially not hook_user, or hook_form_alter. drupal_goto will prohibit other hooks from being called; breaking functionality at the least, but often corrupting your database.
Do not issue drupal_goto() in form_alter callbacks such as "_submit", this might break many other modules and might even corrupt your database.
Similar to Berke's answer, but it seems like you just want this to be a one time thing. For that, you can check for the $account->access property to check their last login. If it is 0, then they are logging in for the first time.
This should work fine for email or no email validation.
<?php
/**
* Implements hook_user().
*/
function mymodule_user($op, &$edit, &$account, $category = NULL) {
switch ($op) {
case 'login':
// execute this if they have never accessed the site before
if ($account->access == 0) {
// run conditional logic based on profile fields
// to set destination here
$_REQUEST['destination'] = 'path/to/welcome-page';
}
break;
}
}
?>
I suggest you use the Login Destionation module or you can use the Rules module redirect action which is maybe to robust for your purpose.
Just in case you don't want to write your own custom module :-)