CrowdedText.com - Writing Contest Site powered by Drupal
CrowdedText.com is a site where businesses and other customers can create writing contests for their creative and content needs. Writers then compete in these contests, uploading their entries, and getting paid if selected as a winner.
In order to create a contest, the customer fills out a multi-step form, selecting contest type, filling out requirements and prize to award, selecting any upgrades, and finally checking out and paying the corresponding fees. The customer can manage their contests by voting on entries, sorting entries, and sending private messages to writers. They can award an entry at any time.
CrowdedText.com was built in Drupal 6. The entire process was managed and implemented by PropDrop.
Contributed Modules Used:
One of the reasons I chose Drupal to implement this site, as opposed to doing it from scratch, was because of the immense community that develops and maintains quality contributions. Through some of my previous experience, I knew that existing modules could provide more than 90% of the functionality I needed. My main job was to provide the glue.
CCK – I had two key content types: contests and entries. The contest type has 16 extra fields, which is to be expected as this content type is the main driver of the site. Some fields were for more traditional uses, like the prize amount and end date of the contest. But many fields are used as simply boolean type variables. For example, there is a field that tracks whether an email has been sent regarding this contest, one that is set when the contest has been upgraded, and one that is set when there are new entries since the last time the Contest Holder had logged in.
I debated creating my own tables to keep track of the information I needed, but I realized I would be reinventing the wheel, losing out on CCK's built-in validation and Views integration. And the Views integration, especially, saved weeks of my time when I was building the necessary lists of content. Without CCK, writing these queries would have been a pain, especially on the administration side of things, which is covered under the Views section.
An entry has a node refernce type to reference the contest, a field that is set if the entry is a winning entry, and a field that contains the user id of the Contest Holder. I provided the last one because, while a contest is still running, only a Conest Holder should be able to view entries. Putting the Contest Holder id in a cck field when an entry is created prevents extra database calls every time someone tries to view an entry.
Views – This was my first extensive experience with Views 2 and I absolutely loved the improvements from Views 1. The easy templating alone was manna from heaven.
Since the contest listing would need to look different based on the upgrades it did or didn't have, I could just include the CCK upgrade fields, and then exclude them from display. That way the Views template had access to the values and I could change the CSS class of the row or the title text based on them. Very simple to do.
I also wanted this site to be easy and effortless to manage. This is another spot where many of the boolean CCK fields come into play. I created administration only views, and I can filter based on those values and get a glance of what is going on with the site very easily. I can also see all users that have won contests, yet have not received a payment for winnings. Without views, these queries would have been tedious torture.
The other key place Views and it's templating abilities came into play were listing the entries of a contest. But it would look different depending on if the contest holder was looking at the page or if it was someone else. To complicate things, there also needed to be a third version for when the contest was over.
This was accomplished by creating three different types of views. For the contest holder, one had a clickable fivestar widget and a clickable entry name. The other once had a static fivestar widget and just the entry name without a link. And the third, for after a contest ended, had a linked entry name, but still had a static fivestar widget.
In the template file for contests, I did a simple check on the current user and the contest time, and then embedded the appropriate view. The flexibility really is endless with embedded views.
Ubercart – Using a full cart solution just for the payment gateway functionality may seem like overkill at first. However, this goes back to why I didn't want to do the whole site from scratch. The Ubercart developers have writtin a robust payment solution that is maintained and supported, it just works and I'm familiar with the API from previous projects. It's also modular, so I didn't need to enable a lot of what is included in the module.
Using Ubercart also allows for easy expansion if I decide to add functionality in the future. This has already paid off even before I launched. I added the ability for Contest Holders to extend their contest for three additional days. If I had done the payment gateway from scratch, I would have had to hack my original code again and go through rounds and rounds of retesting everything. With Ubercart, I just had to write one line of code using the Ubercart API.
Ubercart also made it simple for an anonymous user to begin creating a contest, and after they logged in or registered for the very last step of the form, their cart would be transferred automatically to the logged in session. All I had to do was redirect to the checkout page.
Userpoints and VotingAPI – A holistic way to list the Top Writers on the site was critical to encourage quality writers to contribute. But I didn't want the list to be determined solely on the number of contests won, as I felt it would be too rigid. There would be many quality entries that wouldn't necessarily win a contest. So I decided that every entry that got a vote of 3 stars or more would also improve the ranking of a writer.
To accomplish this, all I had to do was call the hook_votingapi_insert, make sure the value of the vote was high enough, and then use the Userpoints API to add the point within the hook. Similarly, if the vote was cancled, I needed to subtract the point, so an implementation of hook_votingapi_delete was also necessary.
Other contributed modules I used and am thankful for:
- Fivestar
- Content Profile
- Privatemsg
- Imagecache
- Imagecache Profile Pictures
- Automatic Nodetitles
- Token
- Views Display Tabs
- Secure Pages
Custom Modules:
I made use of three custom modules. In retrospect, these should have been broken down even further based on functionality, but three is a manageable number, and none of them exceed 800 lines of code. Plus, they should be easy to port to Drupal 7 if and when I decide to make the switch.
One module takes care of the contest creation multi-step form, and calling the necessary Ubercart API calls to add the correct nodes to the cart and then redirect immediately to the checkout page.
One module handled all functionality dealing with entries and awarding them as winners.
The last module simply implemented hook_cron and handles all of the logic for email reminders. There are custom email reminders written for when a contest ends, when an entry is selected as a winner, when there are new entries that have been added to the contest since the Contest Holder last logged in, and when the Contest Holder has not left enough feedback.
All in all, I ended up having just one custom table that kept track of all awarded entries.
Customized Profiles:
Advanced functionality on the user profiles was a must so people could manage their contests. But I also wanted it to aid in conversion. When a user creates a contest at first, it is set to "unpublished". Only after checkout will the contest then be "published" and listed on the site.
So on the profile, a list of Pending Contests is displayed, with a button to "Launch this Contest". That way if they went away, but came back, they didn't have to refill all the contest information in again. This is done with a simple embedded view in the profile template.
They also get exactly one email reminder per pending contest that exists. But only one to keep down the annoyance factor. I hope this will increase conversions in the long run.
Challenges:
1.Hidden, Read Only Fields – Fields that I didn't want users to be able to edit in Contests and Entries were easily hidden by surrounding them with a div that had a style of "display: none". However, anyone with a tool like firebug could easily manipulate the data. So for each form that had fields I wanted to be uneditable, I added an extra validate function. Every validate function has access to the $form variable, which has a copy of the node being edited. So the validate function simply set the form variables back to their original values before the form is processed, with no extra calls to the database.
2.Keeping Track of Payments – The main part of managing the site will be distributing payments to winning writers. But how to know how much and also track when they had been paid previously? I certainly didn't want to do a complicated query. So I took the simple route. Whenever a writer has a winning entry, the prize amount is added to their "funds" record. A view was created that displays all users with a positive balance. Using Views Bulk Operations, I can set these "fund" records to zero after they are paid.
For the Future:
The main hurdle for the future will be scalability. As the site grows, we will begin utlizing a CDN and implement more aggressive server-side caching. For the moment, however, Page Caching and CSS aggregation handles the load admirably.
Drupal version: Drupal 6.x