Repeet: A Plugin for Expression Engine

Contents:

Introduction

About the Plugin

Repeet is a plugin for Expression Engine. It was written by Brent C. Wilson. Portions of the code are borrowed from or based on the Expression Engine code base. That code is used with permission from pMachine, Inc.

Repeet allows the user to simulate repeating events. When used in a template, Repeet tags display EE weblog entries as though they repeat at an interval set by the author. The repeating effect is only simulated; the EE database is not modified in any way.

Repeet is free software. No warranties are offered or implied. Use at your own risk. Technical support is not provided. However, the author does want the plugin to be useful for the EE community. If you run into a problem, post on the EE forums and/or contact the author directly.

General Instructions

Repeet works with normal weblog entries. By default it looks at three things: the entry's entry date, its expiration date, and a repeat interval specified by using a custom field (described below). Repeet will then display the entry as often as necessary according to those parameters, beginning from the entry date and ending on or by the expiration date, as appropriate. Repeet looks not only at the entry and expiration dates, but also their times. The times specify what time the entry begins and ends. Simple enough, right?

Repeet also includes the powerful abilities to specify dates using two other important custom fields (described below). The first is an "include dates" field. The user can enter custom event dates in this field for those events that don't necessarily occur at regular intervals, and therefore cannot use the repeat interval field. By default the entry will occur at the entry's normal start and end times (i.e. the times set in the entry date and expiration date), but those times, too, can be customized. Furthermore, the user can specify date ranges in this field, giving the user all sorts of flexibility.

The last custom field is an "exclude dates" field. This field can be used to "exclude" certain dates. For example, say an entry is set to repeat at regular, two week intervals. But this year that means the entry will fall on Christmas Day, and you don't want that to happen. No problem! Just enter the date you want to exclude, and your entry won't show up on that day. In this example you would enter "12/25/2006" (or maybe 2006-12-25, if you prefer).

Questions, Comments, Feature Requests, Bug Reports, &c.

For any of the above, .

About the Author

Brent C. Wilson is a shy guy. Or maybe he's just lazy. Either way, this is all he has to say about himself.

Download

Download the most current version of the plugin: Repeet 1.0.7. Install pi.repeet.php in your plugins folder, and lang.repeet.php in your language folder (system/language/english/ for most of you).

This software is made available for free to the EE community. If you would like to make a donation to support the development of this plugin and other Expression Engine plugins, extensions, and modules, you may do so. I sincerely appreciate your support, whether it come via a donation, a kind word, or even just letting me know how the plugin was useful to you.

Requirements

Expression Engine version

Repeet has been verified to work on Expression Engine 1.3.2 -- 1.6.1. Functionality on other versions of Expression Engine cannot be guaranteed. If you discover that Repeet does work on other versions of Expression Engine, please contact the author of this plugin.

Events With No End

By default Repeet uses an entry's entry date and expiration date as the boundaries within which it displays your repeating entry. But what if you want to have an entry with no defined ending date? This might be useful if making an office events calendar that includes monthly staff meetings, for example.

Unfortunately, achieving this behavior is a little trickier than it seems at first. It would be nice if we could just not include an expiration date for entries that should continue indefinitely. But (returning to our office events calendar example), how would we define the entry's ending time? There are dozens of potential solutions, but all of the solutions come with problems depending on how a user is utilizing the plugin.

The solution we went with is this: to get an entry to repeat indefinitely, simply set its expiration date equal to a date and time greater than or equal to a specified length of time from the entry date. That was confusing, so here it is in English: if the entry date is today, set the expiration date to a date at least ten years in the future. If the entry date (as shown in the control panel) is 2006-04-07 07:30 PM, make the expiration date at least 2016-04-07 07:30 PM. It's as easy as swapping a 1 for a 0.

Why ten years? It just seemed like a good number. (The thought process was actually a little more involved than that, but I don't want to bore you.) If you don't like it, you can change it in a couple ways. One way to change it involves setting the indefinite_difference parameter in the exp:weblog:get_ids, exp:repeet:list_dates, and/or exp:repeet:calendar tags. The other way is more of a hack, and involves changing the value of the $ind_diff variable in the plugin itself. The value is set in seconds, with a default value of 315360000, or approximately ten years (60*60*24*365*10). If you wanted to change the value to one year, for example, you could use 31536000 (60*60*24*365*1).

Optional Custom Fields

Repeet can use three custom fields to be created in the field group associated with the weblog(s) in which entries will be repeated. Each custom field can be named according to your preferences. The formatting for each must be set to "None".

Custom field: repeat interval

The repeat interval custom field will tell Repeet at what interval a given weblog entry should be repeated. The field type should be "Drop-down List" or "Text Field", and the formatting must be set to "None". If you choose a drop-down list you can pre-set repeat intervals that users can utilize. If you use a text field, you will need to type in a value for each entry, using the formats listed below.

A number of select options can be specified as values for the drop-down list. The values you choose relate to how often a particular event will repeat. The values you may choose from include:

In addition, the values "daily", "weekly", "monthly", and "yearly" or "annually" correspond to "1 day", "1 week", "1 month", and "1 year", respectively.

For example, your drop-down list might have values like these:

Obviously the options you include will vary depending on your own unique needs. For example, you may only need "2 weeks" and "1 month" in your list.

Custom field: include dates

The include dates custom field is the second custom field that may be created in the field group associated with the weblog(s) in which entries will be repeated. This custom field can be named according to your preferences. The formatting must be set to "None". The field type should be "Textarea".

The include dates custom field allows you to specify custom dates and times at which an entry will appear. For example, some events just don't happen on a regular schedule, so setting a standard repeat interval won't do you any good. You can enter specific dates and times for an entry into this field. You may enter multiple dates and times by placing each on its own line.

The format in which you enter your dates and times is important. If Repeet doesn't understand what you've entered, it may ignore what you've entered or it may behave oddly. Fortunately, you can enter your dates and times in several ways:

As you can see, you have quite a bit of flexibility. If you enter a single date, the default start and end times for that entry will be used (from the entry's entry date and expiration date, respectively). If you enter a single date and time, the default end time for that entry will be used (from the entry's expiration date). You may also specify ranges by separating the start and end dates with " -- " (SPACE DASH DASH SPACE).

Custom field: exclude dates

The exclude dates custom field is the third custom field that may be created in the field group associated with the weblog(s) in which entries will be repeated. This custom field can be named according to your preferences. The formatting must be set to "None". The field type should be "Textarea".

The exclude dates custom field allows you to exclude certain dates on which an entry otherwise would have occurred. This is useful if, for example, an event occurs weekly except on certain holidays. Just as with the include dates custom field, the format in which you enter your dates and times is important. If Repeet doesn't understand what you've entered, it may ignore what you've entered or it may behave oddly. Use the same type of formatting to exclude a date that you would use if you were including it as described above.

Language file

The language file can be used to translate system-readable date intervals into text more appropriate for your wants or needs (i.e. translate "3 months" to "quarterly"). Let's say your setup requires only three repeat intervals: none, 1 week, and 10 days. But you don't want such boring text to appear in the drop-down menu on your publish page (or in your stand-alone entry form). The values you want to display are: "This entry does not repeat", "Repeat weekly", and "Repeat every ten days". In the lang file you would type:

'This entry does not repeat' =>
"none",

'Repeat weekly' => 
"1 week",

'Repeat every ten days' =>
"10 days",

Several translations are already included in the language file. Feel free to use the existing values, or to add your own using the same format.

Listing Events

One of the uses of the Repeet plugin is to display items in a list organized by the date on which those items occur. To do so, your code will look something like this:

    {exp:repeet:parse date_header_interval="day"}
      {repeet:date_header}%M %d, %Y{/repeet:date_header}
      {repeet:no_results}Oops, looks like there aren't any events in the time range you specified.{/repeet:date_header}
      
      {exp:repeet:get_ids weblog="events" parse="inward"}
  
        {exp:weblog:entries weblog="events" entry_id="{repeet:entry_ids}" dynamic="off" show_expired="yes" show_future_entries="yes"}
        {repeet:item}
          {repeet:entry_date}{entry_date format="%Y-%m-%d %H:%i"}{/repeet:entry_date}
          {repeet:expiration_date}{expiration_date format="%Y-%m-%d %H:%i"}{/repeet:expiration_date}
          {repeet:interval}{repeat_interval}{/repeet:interval}
          {repeet:include_dates}{include_dates}{/repeet:include_dates}
          {repeet:exclude_dates}{exclude_dates}{/repeet:exclude_dates}
          {repeet:display}
            <h2><a href="{url_title_path="events/details"}" title="{title}">{title}</a></h2>
            {summary}
            <p><a href="{url_title_path="events/details"}" title="Get more information about {title}">Details</a>...</p>
          {/repeet:display}
        {/repeet:item}
        {/exp:weblog:entries}

      {/exp:repeet:get_ids}

    {/exp:repeet:parse}
	

What the heck does all that mean? Good question! Let's break it down.

First, you probably noticed that there are two different Repeet tags: {exp:repeet:parse} and, inside of that, {exp:repeet:get_ids}. We need two tags because we need to process our data in two waves. exp:repeet:parse runs last, so we'll talk about it last. First let's talk about exp:repeet:get_ids.

exp:repeet:get_ids has to run first. It tells our exp:weblog:entries tag which entries to pull from the database. Why do we need a special tag to do that? Because the exp:weblog:entries tag only pulls entries from the database based on their entry date. If the date or date range we're interested in does not encompass an item's entry date, that item won't be pulled from the database. exp:repeet:get_ids gets around that limitation by "seeding" the exp:weblog:entries tag with the entry IDs it should use. That's what the {repeet:entry_ids} variable is for in the example above. The variable must be placed in the entry_id parameter.

In order to get exp:repeet:get_ids to run first, we need to use parse="inward" as one of the parameters.

Next, the exp:weblog:entries tag runs. It uses the entry IDs given to it by exp:repeet:get_ids via the {repeet:entry_ids} variable. The show_expired and show_future_entries parameters should be set to "yes", and the dynamic parameter should be set to "off".

Items can be displayed using standard exp:weblog:entries tags and variables, but they need to be displayed in a certain way. First, the content of the exp:weblog:entries tag should be enclosed in {repeet:item}{/repeet:item} tags, as shown above. Then you need to set several variables that will be used by exp:repeet:parse when it runs later. You should leave them pretty much as they are shown above. The only three things you need to change are names of the {repeat_interval}, {include_dates}, and {exclude_dates} variables. Change them to match the names of the custom fields you created earlier.

The information you actually want to display -- the stuff you would normally display using plain-old exp:weblog:entries tags -- should go between {repeet:display}{/repeet:display} tags, as shown above. Information between those tags will be repeated for each occurrence of each event.

Finally, the exp:repeet:parse tag does its magic. Data between the {repeet:date_header}{/repeet:date_header} tags should include PHP date codes formatted however you would like the dates to appear. Use the date_header_interval parameter to determine how often the date header will display. (Values include hour, day, week, month, and year.) The date will be displayed once for each item or group of items that fall within the desired date interval.

These tags can either have a date range specified (using begin and end; see below) in their parameters, or they will accept dates in the URL, such as blah.com/index.php/events/2006/, blah.com/index.php/events/2006/01/, or blah.com/index.php/events/2006/01/31/

Parameters

date_header_interval (exp:repeet:parse only)

One of minute, hour, day, week, month, year. Default is day. Note: If week is specified, items will be grouped by week, Monday through Sunday.

begin (exp:repeet:get_ids only)

Optional. A date in an acceptable input format. If not specified, the date in the URL will be used, or if no URL date is present, today's date will be used.

end (exp:repeet:get_ids only)

Optional. A date in an acceptable input format. If not specified, the date in the URL will be used, or if no URL date is present, today's date will be used.

weblog (exp:repeet:get_ids only)

Required. Must be the same as the weblog(s) specified in the exp:weblog:entries form.

status (exp:repeet:get_ids only)

Optional. If specified, must be the same as the status specified in the exp:weblog:entries form.

dynamic="off" (exp:repeet:get_ids only)

Optional. If included, causes Repeet to ignore values in the URL.

indefinite_difference (exp:repeet:get_ids only)

Optional. See above.

Variables

{repeet:entry_ids}

Required. Must be placed in the entry_id parameter of the exp:weblog:entries tag (i.e. entry_id="{repeet:entry_ids}").

{repeet:date_header}%M %d, %Y{/repeet:date_header} (exp:repeet:parse only)

Required. Place just below the opening exp:repeet:parse tag. Specify the date header to be used, using PHP date variables (see EE documentation).

{repeet:item}{/repeet:item}

Required. Place within the exp:weblog:entries tags. Must contain the following required and optional variables: repeet:entry_id, repeet:expiration_date, repeet:interval, repeet:include_dates, repeet:exclude_dates, repeet:display.

{repeet:entry_date}{/repeet:entry_date}

Required. Must look exactly like: {repeet:entry_date}{entry_date format="%Y-%m-%d %H:%i"}{/repeet:entry_date}. Must be placed inside the repeet:item tags.

{repeet:expiration_date}{/repeet:expiration_date}

Required. Must look exactly like: {repeet:expiration_date}{expiration_date format="%Y-%m-%d %H:%i"}{/repeet:expiration_date}. Must be placed inside the repeet:item tags.

{repeet:interval}{/repeet:interval}

Required. Should look like: {repeet:interval}{repeat_interval}{/repeet:interval}. Replace {repeat_interval} with the name of your custom repeat interval field. Must be placed inside the repeet:item tags.

{repeet:include_dates}{/repeet:include_dates}

Required. Should look like: {repeet:include_dates}{include_dates}{/repeet:include_dates}. Replace {include_dates} with the name of your custom repeat interval field. Must be placed inside the repeet:item tags.

{repeet:exclude_dates}{/repeet:exclude_dates}

Required. Should look like: {repeet:exclude_dates}{exclude_dates}{/repeet:exclude_dates}. Replace {exclude_dates} with the name of your custom repeat interval field. Must be placed inside the repeet:item tags.

{repeet:display}{/repeet:display}

Required. Contains the data that will be displayed (and repeated, if necessary) for each entry. You can put pretty much anything you like -- well, anything you would normally put between exp:weblog:entries tags -- between these tags.

{repeet:start_time format=""}

Optional. Used to display the start time of each occurrence of the event. More useful than entry_date if the same event has different start times on different days.

{repeet:end_time format=""}

Optional. Used to display the end time of each occurrence of the event. More useful than expiration_date if the same event has different end times on different days.

Creating a Calendar

Repeet can create a calendar very similar to the type of calendar mentioned in the EE documentation. The difference: with Repeet, your calendar will display your repeating events.

The code for a mini-calendar, based on the one described in the EE documentation, might look something like this:

{exp:repeet:get_ids weblog="events" parse="inward" calendar="yes"}
  {exp:repeet:calendar weblog="events" interval_field="repeat_interval" include_field="include_dates" exclude_field="exclude_dates"  switch="calendarToday|calendarCell" entry_id="{repeet:entry_ids}" show_future_entries="yes" show_expired="yes"}
    <table class="calendarBG" summary="Mini Events Calendar">
      <tr class="calendarHeader">
        <th colspan="7"><a href="http://yoursite.com/events/{date format="%Y/%m/"}" title="View all events in {date format="%F %Y"}">{date format="%F %Y"}</a></th>
      </tr>
      <tr>
        {calendar_heading}
        <td class="calendarDayHeading">{lang:weekday_abrev}</td>
        {/calendar_heading}
      </tr>
      {calendar_rows }
      {row_start}<tr class="calendarRow">{/row_start}
        {if entries}
        <td class='{switch}' align='center'><a href="{day_path=events/index}">{day_number}</a></td>
        {/if}
        {if not_entries}
        <td class='{switch}' align='center'>{day_number}</td>
        {/if}
        {if blank}
        <td class='calendarBlank'> </td>
        {/if}
      {row_end}</tr>{/row_end}
    {/calendar_rows}
    <tr class="calendarMonthLinks">
       <td colspan="3">
         <div class="calendarPrevMonth"><a href="http://yoursite.com/events/{previous_date format="%Y/%m/"}"><< {previous_date format="%M %Y"}</a></div>
       </td>
       <td></td>
       <td colspan="3">
         <div class="calendarNextMonth"><a href="http://yoursite.com/events/{next_date format="%Y/%m/"}">{next_date format="%M %Y"} >></a></div>
       </td>
     </tr>
   </table>
  {/exp:repeet:calendar}
{/exp:repeet:get_ids}
	

Let's break this down. On the outside we've got an exp:repeet:get_ids tag, much like we saw above. It contains weblog= and parse= parameters, the same as above, plus a few new parameters. One is calendar="yes". That just tells the plugin that the data to be returned is going to be used in a calendar. Others are interval_field, include_field, and exclude_field.

On the inside we see an exp:repeet:calendar tag. It is very similar to the weblog module's exp:weblog:calendar tag. In fact, it uses much of the same code. (Thanks, pMachine!) Notice that we need to specify show_future_entries="yes" and show_expired="yes". Nothing surprising there.

Otherwise, use the same parameters and variables as with the exp:weblog:calendar tag.

Parameters

begin (exp:repeet:get_ids only)

Optional. A date in an acceptable input format. The calendar is monthly, so the day you specify will be ignored. If this parameter is not specified, the date in the URL will be used, or if no URL date is present, today's date will be used. If only a year is specified in the URL, January's calendar will be displayed.

weblog

Required in both tags. Must be the same in both tags.

indefinite_difference

Optional. See above. If used, must be set to the same value in both tags.

interval_field (exp:repeet:calendar only)

Optional. Must match the name of your custom repeat interval field. If you are creating a calendar from two or more weblogs (i.e. you have specified weblog="events|holidays") then you must list an equal number of fields in this parameter (i.e. interval_field="events_interval|holidays_interval").

include_field (exp:repeet:calendar only)

Optional. Must match the name of your custom include field. If you are creating a calendar from two or more weblogs (i.e. you have specified weblog="events|holidays") then you must list an equal number of fields in this parameter (i.e. include_field="events_include_dates|holidays_include_dates").

interval_value (exp:repeet:calendar only)

Optional. Use only if you do not use the include_field parameter. This parameter is used to hard code a particular repeat interval value (i.e. "daily"). If both this parameter and interval_field are absent, Repeet will default to a value of "none".

exclude_field (exp:repeet:calendar only)

Optional. Must match the name of your custom exclude field. If you are creating a calendar from two or more weblogs (i.e. you have specified weblog="events|holidays") then you must list an equal number of fields in this parameter (i.e. exclude_field="events_exclude_dates|holidays_exclude_dates").

status

Optional. If specified, it should be the same in both tags.

Variables

{entry_ids}

Required. Must be placed in the entry_id parameter of the exp:repeet:calendar tag (i.e. entry_id="{entry_ids}").

{count}

Displays the number of entries that occur on a given day.

Listing a Single Event's Occurrences

Repeet also includes a tag, exp:repeet:list_dates, that allows you to list the occurrences of an entry within a specified time span. For example, it can list all the occurrences over the next month, occurrences from one month ago to one month from now, or occurrences last year.

The exp:repeet:list_dates tag should be used inside an exp:weblog:entries tag. You might use something like this:

{exp:weblog:entries weblog="events"}

{title}
{body}

{exp:repeet:list_dates from="today" to="1 year" entry_date="{entry_date format="%Y-%m-%d %H:%i"}" expiration_date="{expiration_date format="%Y-%m-%d %H:%i"}" max_list="25" interval="{repeat_interval}"}
This event occurs on the following dates over the next year:
{repeet:include_dates}{include_dates}{/repeet:include_dates}
{repeet:exclude_dates}{exclude_dates}{/repeet:exclude_dates}
{repeet:display} <li> <a href="http://yoursite.com/events/{repeet:start_time format="%Y/%m/%d/"}" title="View other events happening on {repeet:start_time format="%F %j, %Y"}"> {repeet:start_time format="%l, %F %d, %Y %g:%i %A"}{repeet:end_time format=" - %g:%i %A"} </a> </li> {/repeet:display} {/exp:repeet:list_dates} {/exp:weblog:entries}

Paramters

from

The date beginning with which the plugin will start displaying dates. Can be in YYYY-MM-DD format, or in an easy-to-use text format (see below). If the text format is used, date_style must be set to "text". If not specified, defaults to today.

Text dates are relative to today. Sample text dates:

The word "ago" can also be appended to refer to dates in the past. For example, "1 week" means one week from today, while "1 week ago" means, well, one week ago. See these date input formats for more information about acceptable text dates.

to

The date until which the plugin will display dates. Can be in YYYY-MM-DD format, or in an easy-to-use text format (see above). If the text format is used, date_style must be set to "text". If not specified, defaults to today.

If the value of this parameter is greater than the value of the from parameter, dates will be displayed in ascending order. If the value of this parameter is less than the value of the from parameter, dates will be displayed in descending order. For example, if you were to specify from="today" and to="1 month", dates would be displayed in order from today to one month from today. If, on the other hand, you had from="1 month" and to="today", the dates would be displayed beginning one month from today, and ending today.

entry_date

Required. The date on which the item starts. Use entry_date="{entry_date format="%Y-%m-%d %H:%i"}".

expiration_date

Required. The date on which the item ends. Use expiration_date="{expiration_date format="%Y-%m-%d %H:%i"}".

interval

Required. The interval (i.e. "daily", "monthly") by which the dates should be listed. Unless all the items have the same repeat interval, use the {custom_field} variable that corresponds with the custom field you use to hold the repeat interval data for the specified weblog.

max_list

Optional. The maximum number of dates to list. Defaults to 100.

indefinite_difference

Optional. See above.

offset

Optional. Allows you to offset the list of dates by a specific number of dates. For example, offset="1" would begin with the second item on the list.

exclude_entry_date

Optional. Will exclude the entry date from the list of events. Use like exclude_entry_date="yes".

Variables

{repeet:include_dates}{/repeet:include_dates}

Required. Should look like: {repeet:include_dates}{include_dates}{/repeet:include_dates}. Replace {include_dates} with the name of your custom repeat interval field. Must be placed inside the repeet:list_dates tags, but outside the repeet:display tags.

{repeet:exclude_dates}{/repeet:exclude_dates}

Required. Should look like: {repeet:exclude_dates}{exclude_dates}{/repeet:exclude_dates}. Replace {exclude_dates} with the name of your custom repeat interval field. Must be placed inside the repeet:list_dates tags, but outside the repeet:display tags.

{repeet:display}{/repeet:display}

Required. Contains the data that will be displayed for each occurrence of the entry. You can put pretty much anything you like -- well, anything you would normally put between exp:weblog:entries tags -- between these tags.

{repeet:start_time format=""}

Optional. Used to display the start time of each occurrence of the event. More useful than entry_date if the same event has different start times on different days.

{repeet:end_time format=""}

Optional. Used to display the end time of each occurrence of the event. More useful than expiration_date if the same event has different end times on different days.

Examples

The code for each example can be found at the bottom of each example page.

Events Listed by Month

Events on a Specific Day

Events Listed by Week

Mini Event Calendar

Large Event Calendar

List of Upcoming Occurrences of an Event

To-Do List

Change Log

1.0.7

1.0.6

1.0.5

1.0.4

1.0.3

1.0.2

1.0.1

1.0

0.9.3

0.9.2.1

0.9.2

0.9.1.1

0.9.1