Skip Navigation

[Resolved] Split: Managing Ad packages on a Classifieds site

This support ticket is created 6 years, 9 months ago. There's a good chance that you are reading advice that it now obsolete.

This is the technical support forum for Toolset - a suite of plugins for developing WordPress sites without writing PHP.

Everyone can read this forum, but only Toolset clients can post in it. Toolset support works 6 days per week, 19 hours per day.

Sun Mon Tue Wed Thu Fri Sat
- 7:00 – 14:00 7:00 – 14:00 7:00 – 14:00 7:00 – 14:00 7:00 – 14:00 -
- 15:00 – 16:00 15:00 – 16:00 15:00 – 16:00 15:00 – 16:00 15:00 – 16:00 -

Supporter timezone: Europe/London (GMT+00:00)

This topic contains 17 replies, has 2 voices.

Last updated by Nigel 6 years, 5 months ago.

Assisted by: Nigel.

Author
Posts
#615943

I'm building a classified ad site and I structure my membership levels like the one below to my users:

Buy Package 1 and you can post 2 ads
Buy Package 2 and you can post 5 ads
Buy Package 3 and you can post 8 ads

I would allow my members to create as many ads as as they wish as long as they're unpublished. But when they click the Publish button on one of their ads, I want my program to check if they still have enough ad allowance left to publish the ad.

So my question is: How can I find out how many *published* ads a member currently has? Please note that ads can be created using multiple, different custom post types, so it needs to count all of them.

Also, is the *cred_before_save_data* hook the correct one to use for this?

Do you think you could show me the codes on how to get the number of user's currently published ads and how to make it work in the *cred_before_save_data* hook?

Thank you very much.

chris

#616217

Hello,

If I may add to my original post above, I'd also like to know how to display a message to the member telling them to upgrade their package if they don't have any ad allowance left anymore to post a new ad when they hit the Publish button?

So to summarize, the custom script for the Publish button will do the following:
1. Find out if the member has enough ad allowance left (= total ad allowance - number of published ads)
2. If yes, let them publish the ad
3. If not, display a message telling them to upgrade their package

Thank you.

#616291

Nigel
Supporter

Languages: English (English ) Spanish (Español )

Timezone: Europe/London (GMT+00:00)

Hi Chris

Are you using our Classifieds reference site as a starting point for this?

It uses some custom code to manage ad packages, allowing users to buy a package with a certain number of ads and reducing the available number of ads as the user publishes them.

That code—which is used because you cannot do this using Toolset alone—is added via the dedicated plugin Toolset Classifieds.

If you don't already have it from installing the Classifieds reference site you can download it by going to your downloads page (wp-types.com/account/downloads), switching to the Reference sites tab, and then expanding the list of plugins used for the Classifieds site.

That plugin loads just a single PHP file. I suggest you study that to see how the ad packages are managed so that you can implement the same and modify it as required.

If you get stuck on a particular bit then let me know.

#617217

Hello Nigel,

Thanks for the information.

I checked out Toolset Classifieds and found the codes responsible for decreasing user's available ad credits after publishing an ad. This is a snipet:

                 //Retrieve available ad credits
                $user_available_ad_credits = $this->classifieds_return_available_ad_credits($user_id);
                //Reduce credit by one count after posting one ad
                $updated_ad_credits = $user_available_ad_credits - 1;
                //Update back the credits
                $success_updating_credits = update_user_meta($user_id, 'wpcf-user-total-available-ad-credits', $updated_ad_credits);

But I'm not sure.

In Toolset Classifieds, it looks like you need to create a separate counter to keep track how many ad credits the user has left. When he publishes an ad, decrease the counter. When he unpublishes an ad, increase the counter. If the ad has to be unpublished because it gets flagged by other users or admin, increase the counter. If he upgrades his ad package to get 5 more ads, increase the counter by 5. If he downgrades his ad package to get 3 fewer ads, downgrade the counter by 3. If he somehow clicks the Publish button of the same ad twice due to an error on the system, the counter would also get decreased twice in error. And so on. So you need to be extra careful on where and how to implement the increase/decrease functions of the counter. If you miss one, the counter will be wrong.

I think my idea is a bit simpler. When user tries to publish an ad, the program would:

1) Get the total number of ad credits available for the package the user is subscribing to
2) Get the number of the user's currently published ads
3) Subtract Number 2 from Number 1 above, and you get the user's currently available ad credits

And my codes would need to be implemented only on the Publish button(s). In Toolset Classfieds, its counter codes have to be implemented in many different places to cover all possible scenarios stated above. If you miss one, the counter will be wrong.

What do you think? Do you see any issue with my method that I perhaps missed? I'm not so familiar with how WordPress or Toolset works.

Here I decided to try to write my own codes:

// global variable for my post types
$k__g_ad_post_types_sql = "('my_post_type_1', 'my_post_type_2)"; /// for IN operator in sql
$k__g_ad_post_types_array = array('my_post_type_1', 'my_post_type_2'); // for in_array() in php

// check if member has an active subscription
function k__does_member_have_active_subscription($current_member_id){
	global $wpdb;
	return true; // temporary dummy data - not done yet - waiting for membership plugin to be installed
}

// get the total number of ad credits available for the package the user is subscribing to
function k__num_member_package_total_ad_credits($current_member_id){
	global $wpdb;
	return 3; // temporary dummy data - not done yet - waiting for membership plugin to be installed
}

// get the number of member's currently published ads
function k__get_num_member_published_ads($current_member_id){
	global $wpdb, $k__g_ad_post_types_sql;
	$wp_post_table_name = $wpdb->posts;
	$num_member_published_ads = $wpdb->get_var( "SELECT COUNT(*) FROM $wp_post_table_name WHERE post_author = $current_member_id AND post_status = 'publish' AND post_type IN $k__g_ad_post_types_sql" );
	return $num_member_published_ads;
}

// does member have enough ad credits to publish another ad?
function k__does_member_have_enough_ad_credits($current_member_id){
	$num_member_package_total_ad_credits = k__num_member_package_total_ad_credits($current_member_id); 
	$num_member_published_ads = k__get_num_member_published_ads($current_member_id); 
		
	if ( ($num_member_package_total_ad_credits - $num_member_published_ads) > 0){
		return true; // member has enough ad credits, let him publish the ad
	} else {
		return false; // member doesn't have enough ad credits left, don't let him publish the ad
	}
}

// assign function to publish button(s)
function k__can_member_publish_ad($form_data){
	global $k__g_ad_post_types_array;
	
	/// when member clicks a Publish button
	if ( $_POST['form_submit_1'] == 'Publish' ) {   // is it always 'form_submit_1'?

		// if post type is one of my custom ad post types
		if ( in_array($form_data['post_type'], $k__g_ad_post_types_array ) ) {  
			$current_member_id = get_current_user_id();
			$message_to_display = '';
				
			if ( k__does_member_have_active_subscription($current_member_id) === false ) {
				// member has no active subscription
				$message_to_display = 'Please subscribe to one of our packages.';
				// need more codes here
					
			} else if ( k__does_member_have_enough_ad_credits($current_member_id) === false ){
				// member doesn't have enough ad credits

				$message_to_display = 'You don\'t have enough ad credits. Please upgrade.';
					
				//$form_data['post_status'] = 'draft';              // didn't work
				//$_POST['form_submit_1'] == 'Save & Preview';             // didn't work
				// return;         // didn't work
				// exit();          // it worked but it stopped all the scripts - page went blank

				// what to write here? Above codes didn't work
				// how to prevent post status 'publish' from being saved? It should remain 'draft'
				// how to display a message telling member to upgrade their package?
			}
		}
	}
}

add_action('cred_before_save_data', 'k__can_member_publish_ad', 10, 1);

The script above seemed to work well, until the part when I added it to the 'cred_before_save_data' hook. It didn't work. This is where I'm stuck.

I tried using $form_data['post_status'] = 'draft'; or $_POST['form_submit_1'] == 'Save & Preview'; or return;, but it still let me publish the ad even though I didn't have enough ad credits.

So if I may ask questions... sorry I'm still learning WordPress and Toolset...

1. How can I prevent post status 'publish' from being saved? It should remain 'draft'

2. How to display a message telling user to upgrade their package?

3. Is the name of the form's submit button always 'form_submit_1'?

Thank you very much, Nigel!

#617487

Nigel
Supporter

Languages: English (English ) Spanish (Español )

Timezone: Europe/London (GMT+00:00)

Hi Chris

I'll refrain from commenting on the solution employed in the Classifieds reference site because it significantly predates my arrival here and I only know it superficially.

Your solution sounds simple and less vulnerable to unexpected problems, though you don't seem to have explicitly provided for displaying to a user the number of available ad credits they have outstanding, for example (trivial given what you have above).

Can I just confirm the workflow you envisage?

It looks like you intend for your users to be able to submit the form with an ad, and then check whether they have a valid subscription and if they have enough credits when processing the form.

Do you not want to, for example, display your messages about subscriptions or credits instead of the form if the user is unable to publish ads? (There is nothing in the form submission that tells you this, it is something you test based simply on who is viewing the page to submit an ad, no?)

Note that when you create a CRED form, which has an ID that can be used to verify what form is submitted when using the API hooks, the form settings specify what the status of the post submitted will be. If the settings say the status will be draft it will be draft, if they say it will be published, it will be published—unless you add code after the form is submitted where you change this. Normally you would use the cred_save_data hook, by when the post will have been saved with your chosen status, and then you would use wp_update_post (https://codex.wordpress.org/Function_Reference/wp_update_post) to update whatever details of the post you require, including its status.

To display messages that would involve using the server-side form validation available with the cred_form_validate hook (https://toolset.com/documentation/programmer-reference/cred-api/#cred_form_validate).

But clarify what you want with the workflow and I'll help with the solution.

#617613
after_clicking_save_n_preview_on_create_ad_form.png
after_clicking_publish_on_preview_page.png

Hello Nigel,

Thanks for your feedback. It's always useful.

Here's how I envision the workflow for a new member:

1. On our site, there will be 4 main menu options: Post an Ad, Pricing, Sign Up and Sign In

2. A visitor comes to our site, looks around and decides he wants to post an ad

3. He can clicks either the Post an Ad or Sign Up menu option. If he clicks Post an Ad, he will be directed to a page asking him to sign up

[Can WordPress/Toolset allow an unlogged-in visitor to create and preview a post and only ask him to sign up when he wants to save the post? This would be an ideal workflow. The signing up process to any site is often seen as a turn off by visitors. If possible, I'd like to push this process to be after my visitors create their ad and want to publish it.]

4. After signing up, he will be presented with 2 options:
- Purchase one of our subscription packages
- Create an ad for free*

* I want my members to be able to create and preview their ad (or ads) without having to purchase a subscription package first. Only when they want to publish it, are they required to have an active subscription package.

Same thing for the existing members with a subscription package. I want them to be able to create and preview a new ad regardless they have enough ad credits or not. Only when they want to publish it, do we check if they have enough ad credits or not.

5. When the member clicks on Create an Ad, he will be presented with a page containing the ad submission form.

6. After he's done filling out the form, he will click the "Save & Preview" button at the bottom. The ad will be saved and he will be taken to the preview page

7. On the preview page, there will be a Publish button. He will click this button to publish his ad. This is the point when I'd like to have a check on whether he has a valid subscription and enough ad credits

I hope I explained this well. Please let me know if you have any questions.

In your message you said the following:

-----------------------------
. . . . the form settings specify what the status of the post submitted will be. If the settings say the status will be draft it will be draft, if they say it will be published, it will be published—unless you add code after the form is submitted where you change this.
-----------------------------

[Issue 1]

[Method 1]
"... unless you add code after the form is submitted where you change this." This is what I'm trying to do, but I don't know how. What codes do I need to write to change the post status of the submitted post from 'publish' to 'draft' before the form data is saved in the database?

[Method 2]
Another method I can think of is to completely cancel the form submission. So when a member clicks on the Publish button but he is not allowed to publish an ad, the codes assigned to the 'cred_before_save_data' hook would somehow exit the script execution on the form before it saves the form data to the database, so it doesn't change the post status from 'draft' to 'publish'. But I don't know how to do it, either. I tried to use exit(). It did exit the script before executing the saving data, but it also exited from all other scripts, resulting in a blank Web page.

I also looked into wp_update_post() for the cred_save_data hook before, but this method requires another database operation. The program saves the data into the database without any changes and wp_update_post() will go into the database (again) and change a value. It seems redundant to me. It would be nice if we could change the data before it's saved into the database, not after. Or, don't save the form data at all as described in my Method 2 above.

[Issue 2]

As for displaying a message to user, thanks for letting me know about the cred_form_validate hook. But this looks like it's for validating a form's individual input fields. My issue was more about displaying a message when user is not allowed to publish an ad, not about his entering an invalid entry in the submit form.

To give you an idea what I'm looking for, I'm attaching screenshots of the top part of my ad preview page. The first green box is created with the [cred-form-message] shortcode. The second green box, I created myself to provide a more customized message. What I was trying to figure out was how to display a message in one of these green boxes when a member is not allowed to publish an ad after he clicks the Publish button. I think the answer might depend on how we solve the first issue above.

Thank you very much.

#617979

Nigel
Supporter

Languages: English (English ) Spanish (Español )

Timezone: Europe/London (GMT+00:00)

Hi Chris

Sorry the queue was particularly busy today and I didn't get time to review this fully, I will in the morning.

Just let me quickly note the following:

3. Guest users (i.e. not logged in) can publish content with CRED forms (with status determined by the form settings), but they can't be the author of those posts because they are not registered and so don't have a user id. You would need some system to hook them up with the content they previously "published" when they finally sign up.

#618121

Hi Nigel,

Thanks for your reply.

I just thought about this, maybe I should just follow the "traditional" way of doing this if it's simpler to do.

In other words, instead of making the Publish button as the gatekeeper for the user's ability to post an ad and writing complicated coding, maybe I should just restrict the page where the ad submission form is on. So only members with an active subscription and enough ad credits would be able to access this page and consequently post an ad. That way I wouldn't need to add any action/hook coding to the Publish button. Would that be easier?

I'm planning to use MemberPress as my membership plugin, so I just emailed them to see if I could restrict my ad submission page only to members with an active subscription and enough ad credits.

My next question is on View. Only ads from members with currently an active subscription should be displayed on the frontend, but I didn't see in View's filter any operand that I could use to select such ads to display. I think you told me I would need a separate support ticket for a different subject. Let me do that.

Thank you very much.

#618264

Nigel
Supporter

Languages: English (English ) Spanish (Español )

Timezone: Europe/London (GMT+00:00)

Hi Chris

Before we give up on your preferred workflow, let me just reply to a few points you raised.

How to change the post status before the CRED form writes to the database using the cred_before_save_data hook. You can't. The parameters available in the hook don't provide for such a thing. That is why I said to use the cred_save_data hook and to change the post status immediately after the post has been published.

To interrupt the flow and prevent a form submission being completed you can really only use the cred_form_validate hook, and add an error to an individual field. You could include a field, which is hidden, purely for the purpose of sending back a custom message to the form about why it failed to submit.

Regarding your preferred workflow, apart from the problem of unregistered users publishing content, I think you can have your users submit ads with a CRED form which sets the status as draft or pending review and which then displays the submitted post (i.e. a preview, which the author of the post will be able to see but others will not).

Your preview could include a Publish button at the bottom or a message saying that the user did not have a description or did not have sufficient credits.

You can use the wpv-conditional shortcode to selectively display content, and you can register custom PHP functions to use as conditions. https://toolset.com/documentation/user-guides/conditional-html-output-in-views/

You should be able to either display the button or the message according to what is returned by your custom function that determines whether they currently have the capability or not. And if the post is already published you could display an unpublish button instead.

Those buttons would connect to CRED edit forms that would change the status of the post to published (or back to draft or preview).

So I think you can still do what you want if you are able to fit the pieces together.

#618539

Hi Nigel,

Thanks for your perseverance! I appreciate it. ?

Yes, I've been using the conditional output. After user clicks my 'Save & Preview' button, he will see the 'Publish' button on the preview page. After he clicks the 'Publish' button to publish his ad, the preview page will reload and replace the 'Publish' button with the 'Unpublish' button. It works nicely.

But using custom functions in a conditional output is new to me. Thanks for letting me know. So what I'm trying to do is to display a 'You can't publish ad' message on the preview page and hide the Publish button from user if they don't have an active subscription or enough ad credits.

But when I tried to use custom functions in conditional outputs, I realized I would need to call my custom functions repeatedly. As the custom functions made a query to database, I thought doing it repeatedly wasn't a good idea.

So I looked around on your site and found that I could store a variable in a shortcode. So this is what I did:

	// check if member has an active subscription
	function k__does_member_have_active_subscription(){
		global $wpdb, $k__g_current_member_id;
		return true; // temporary dummy data 
	}
	add_shortcode('k__does_member_have_active_subscription', 'k__does_member_have_active_subscription');

	// does member have enough ad credits to publish another ad?
	function k__does_member_have_enough_ad_credits(){
		$num_member_package_total_ad_credits = k__num_member_package_total_ad_credits(); // number of total ad credits available from the package member is subscribing to
		$num_member_published_ads = k__get_num_member_published_ads(); // number of member's currently published ads
		
		if ( ($num_member_package_total_ad_credits - $num_member_published_ads) > 0){
			return true; // member has enough ad credits, let him publish the ad
		} else {
			return false; // member doesn't have enough ad credits left, don't let him publish the ad
		}
	}
	add_shortcode('k__does_member_have_enough_ad_credits', 'k__does_member_have_enough_ad_credits');

To test the shortcodes, I added them to one of my Visual Editor cells and it worked:

[k__does_member_have_active_subscription]|||[k__does_member_have_enough_ad_credits]

It shows the following on the web page:

1|||1

However, when I put them as conditions shown below, they didn't work:

  [wpv-conditional if=" ('[k__does_member_have_active_subscription]' eq '1' ) AND ('[k__does_member_have_enough_ad_credits]' eq '1' ) AND ( '[wpv-post-status]' eq 'draft') " ]
	<div class="col-sm-12 alert alert-success">
      	To publish your ad, please click the Publish button.
	</div>
  [/wpv-conditional]

The conditions above seemed to keep returning false. As a result, the message "To publish your ad, please click the Publish button" was never displayed even though I knew the conditions were all met.

Did I do it wrong? Is there a restriction that you cannot use your custom shortcodes as conditions?

Thank you very much.

#618672

Nigel
Supporter

Languages: English (English ) Spanish (Español )

Timezone: Europe/London (GMT+00:00)

Hi Chris

This might be as simple as you must register the custom shortcodes if you are going to use them inside conditional shortcodes at Toolset > Settings > Front-end Content.

#618862

Hi Nigel,

Yes, it works!! Thanks!!

I have a couple more questions, though, if I may.

Question 1

function my_function(){
     return false;
}
add_shortcode('my_shortcode', 'my_function');

When I have the value of one of my shortcodes set to boolean false, the shortcode will display nothing (empty) instead of 'false' or 0 (zero) on the Web page. Is this the correct behavior?

But if I set the value to 0 (zero), it will display 0 (zero). I wonder if I should use 0 (zero) instead of boolean false as the return value.

For boolean true, the shortcode will display 1.

Question 2

At the top of my ad preview page, I added the [cred-form-message] shortcode to display a message after the form is submitted.

Can the shortcode show an error message?

Currently, I set up my preview page assuming there's no error with the form submission. So after clicking the Publish button, for example, the user will see my custom message under the shortcode above saying 'Congratulations! Your ad has been published!'

But what if there is an error and the shortcode above is displaying it, but my custom message still says 'Congratulations'? I guess what I want to do is, if there's any error, my custom message shouldn't be displayed. But how can I tell programmatically if there's an error?

Thank you very much.

#618939

Nigel
Supporter

Languages: English (English ) Spanish (Español )

Timezone: Europe/London (GMT+00:00)

Hi Chris

Q1: WordPress shortcodes are intended to return text, not booleans. I'm not sure what to expect if you return a boolean, but note when using custom functions inside conditional shortcodes that you should compare to 1 for true and 0 for false.

Q2: What error are you anticipating? Validation errors would prevent the form submission from completing and so the success message would not be displayed. If you use the cred_validate_form hook the error messages returned would appear there.

#621591

Hello Nigel,

Thanks for your responses.

I used the cred_form_validate hook in the Publish button on the preview page and it worked!! If member has enough ad credits, it will let them publish the ad. If not, it will intercept the submission and display an error message telling them to upgrade.

This is what I did in case it's useful for other Toolset readers:

1. Create a dummy input field for the custom post type and name it "Ad Credits Insufficient"
2. Add this dummy field in the post form containing the Publish button
3. Add a CSS to hide this dummy field. I set position:absolute and top:-10000px
4. Add this hook filter script:

function can_member_publish_ad($error_fields, $form_data){
	if ( $form_data['id'] == 123 ) {
		if (any_ad_credits_left() === false) {
			list($fields,$errors) = $error_fields;
			$errors['wpcf-ad-credits-insufficient'] = 'You don\'t have ad credit left to post an ad. Please upgrade to get more ad credits.';
			return array($fields,$errors);
		}
	}
	return $error_fields;
}
add_filter('cred_form_validate','can_member_publish_ad',10,2);

The procedure above seems to work well on the preview page (single post page), though I was wondering why the custom function above was executed twice every time I clicked the Publish button.

When trying to use the same Publish button above on my "My Ads" page (member's page listing all their ads, including the published and unpublished ones), however, I ran into a problem:

Let's say I had 5 ads listed on my "My Ads" page and each had either a Publish or Unpublish button on it depending on its current status. If I clicked Publish on one of the ads but I didn't have enough ad credits, the error message would be displayed on *all* 5 ads instead of just the one I tried to publish. And all submit buttons became disabled. Do you know if there's a way to get around this?

If I had enough ad credits and the ad's status was changed to Publish successfully, the success message would be displayed only on the ad I'd just published, not on other ads, which was correct. So it seems the issue occurred only when it tried to display the error message created by my custom code above.

When I enabled AJAX for the Publish button/form submission, however, the error message was displayed correctly, which was only on the ad affected. But, since this is AJAX, the rest of the screen was not updated. As a result, for the ad I'd just published, its status on the screen was still displayed as "Unpublished." For this reason, I currently disabled AJAX.

Thank you very much.

#622000

Nigel
Supporter

Languages: English (English ) Spanish (Español )

Timezone: Europe/London (GMT+00:00)

Hi Chris

I set up a simple test locally and I could reproduce this problem, the CRED form validation messages being displayed on all occurrences of the form.

I don't have a suggestion as to how to avoid it right now, I am escalating this to our developers so that they can identify the cause can come up with a fix.

I'll report back when I have some news.