Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 111
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
CargoRecurringEvent
0.00% covered (danger)
0.00%
0 / 111
0.00% covered (danger)
0.00%
0 / 2
1892
0.00% covered (danger)
0.00%
0 / 1
 getJD
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 run
0.00% covered (danger)
0.00%
0 / 110
0.00% covered (danger)
0.00%
0 / 1
1806
1<?php
2/**
3 * Class for the #recurring_event function.
4 *
5 * @author Yaron Koren
6 * @ingroup Cargo
7 */
8
9class CargoRecurringEvent {
10
11    /**
12     * Gets the "Julian calendar days" of the specified date.
13     *
14     * @param string[] $date
15     * @return int
16     */
17    public static function getJD( $date ) {
18        return gregorianToJD( $date['month'], $date['day'], $date['year'] );
19    }
20
21    /**
22     * Handles the #recurring_event parser function - prints out a
23     * string that is a delimited list of recurring events.
24     *
25     * @param Parser $parser Unused
26     * @return string
27     */
28    public static function run( $parser ) {
29        global $wgCargoRecurringEventMaxInstances;
30
31        // Code based in large part on the code for Semantic
32        // MediaWiki's #set_recurring_event function.
33        $params = func_get_args();
34        array_shift( $params ); // we already know the $parser...
35
36        $allDateStrings = [];
37        $startDate = $endDate = $unit = $period = $weekNum = null;
38        $timeString = null;
39        $delimiter = '; '; // default
40        $includedDates = [];
41        $excludedDatesJD = [];
42
43        foreach ( $params as $param ) {
44            $parts = explode( '=', $param, 2 );
45
46            if ( count( $parts ) != 2 ) {
47                continue;
48            }
49            $key = trim( $parts[0] );
50            $value = trim( $parts[1] );
51
52            if ( $key == 'start' ) {
53                $startDate = date_parse( $value );
54                // We can assume that the time of day for
55                // all automatically-generated dates will
56                // be the same as that of the start date (if
57                // it was set at all).
58                $curHour = $startDate['hour'];
59                $curMinute = $startDate['minute'];
60                if ( $curHour !== false && $curMinute !== false ) {
61                    $timeString = ' ' . str_pad( $curHour, 2, '0', STR_PAD_LEFT ) . ':'
62                        . str_pad( $curMinute, 2, '0', STR_PAD_LEFT );
63                }
64            } elseif ( $key == 'end' ) {
65                $endDate = date_parse( $value );
66            } elseif ( $key == 'unit' ) {
67                $unit = $value;
68            } elseif ( $key == 'period' ) {
69                $period = $value;
70            } elseif ( $key == 'week number' ) {
71                $weekNum = $value;
72            } elseif ( $key == 'include' ) {
73                $includedDates = explode( ';', $value );
74            } elseif ( $key == 'exclude' ) {
75                $excludedDates = explode( ';', $value );
76                foreach ( $excludedDates as $dateStr ) {
77                    $excludedDatesJD[] = self::getJD( date_parse( $dateStr ) );
78                }
79            } elseif ( $key == 'delimiter' ) {
80                $delimiter = $value;
81            }
82        }
83
84        if ( $startDate === null ) {
85            return CargoUtils::formatError( 'Start date must be specified.' );
86        }
87        if ( $unit === null ) {
88            return CargoUtils::formatError( 'Unit must be specified.' );
89        }
90
91        // If the period is null, or outside of normal bounds,
92        // set it to 1.
93        if ( $period === null ) {
94            $period = 1;
95        }
96
97        // Handle 'week number', but only if it's of unit 'month'.
98        if ( $unit == 'month' && $weekNum !== null ) {
99            $unit = 'dayofweekinmonth';
100            if ( $weekNum < -4 || $weekNum > 5 || $weekNum == 0 ) {
101                $weekNum = null;
102            }
103        }
104
105        if ( $unit == 'dayofweekinmonth' && $weekNum === null ) {
106            $weekNum = ceil( $startDate['day'] / 7 );
107        }
108
109        // Get the Julian day value for both the start and
110        // end date.
111        $endDateJD = self::getJD( $endDate );
112        $curDate = $startDate;
113        $curDateJD = self::getJD( $curDate );
114
115        $instanceNum = 0;
116        do {
117            $instanceNum++;
118            $excludeDate = ( in_array( $curDateJD, $excludedDatesJD ) );
119            if ( !$excludeDate ) {
120                $dateStr = $curDate['year'] . '-' . $curDate['month'] . '-' . $curDate['day'] . $timeString;
121                $allDateStrings[] = $dateStr;
122            }
123
124            // Now get the next date.
125            // Handling is different depending on whether it's
126            // month/year or week/day since the latter is a set
127            // number of days while the former isn't.
128            if ( $unit === 'year' || $unit == 'month' ) {
129                $curYear = $curDate['year'];
130                $curMonth = $curDate['month'];
131                $curDay = $startDate['day'];
132
133                if ( $unit == 'year' ) {
134                    $curYear += $period;
135                    $displayMonth = $curMonth;
136                } else { // $unit === 'month'
137                    $curMonth += $period;
138                    $curYear += (int)( ( $curMonth - 1 ) / 12 );
139                    $curMonth %= 12;
140                    $displayMonth = ( $curMonth == 0 ) ? 12 : $curMonth;
141                }
142
143                // If the date is February 29, and this isn't
144                // a leap year, change it to February 28.
145                if ( $curMonth == 2 && $curDay == 29 ) {
146                    if ( !date( 'L', strtotime( "$curYear-1-1" ) ) ) {
147                        $curDay = 28;
148                    }
149                }
150
151                $dateStr = "$curYear-$displayMonth-$curDay" . $timeString;
152                $curDate = date_parse( $dateStr );
153                $allDateStrings = array_merge( $allDateStrings, $includedDates );
154                $curDateJD = self::getJD( $curDate );
155            } elseif ( $unit == 'dayofweekinmonth' ) {
156                // e.g., "3rd Monday of every month"
157                $prevMonth = $curDate['month'];
158                $prevYear = $curDate['year'];
159
160                $newMonth = ( $prevMonth + $period ) % 12;
161                if ( $newMonth == 0 ) {
162                    $newMonth = 12;
163                }
164
165                $newYear = $prevYear + floor( ( $prevMonth + $period - 1 ) / 12 );
166                $curDateJD += ( 28 * $period ) - 7;
167
168                // We're sometime before the actual date now -
169                // keep incrementing by a week, until we get there.
170                do {
171                    $curDateJD += 7;
172                    $curDate = date_parse( JDToGregorian( $curDateJD ) );
173                    $rightMonth = ( $curDate['month'] == $newMonth );
174
175                    if ( $weekNum < 0 ) {
176                        $nextWeekJD = $curDateJD;
177
178                        do {
179                            $nextWeekJD += 7;
180                            // FIXME: This method doesn't exist!
181                            $nextWeekDate = self::getJulianDayTimeValue( $nextWeekJD );
182                            $rightWeek = ( $nextWeekDate['month'] != $newMonth ) ||
183                                ( $nextWeekDate['year'] != $newYear );
184                        } while ( !$rightWeek );
185
186                        $curDateJD = $nextWeekJD + ( 7 * $weekNum );
187                        // FIXME: This method doesn't exist!
188                        $curDate = self::getJulianDayTimeValue( $curDateJD );
189                    } else {
190                        $curWeekNum = ceil( $curDate['day'] / 7 );
191                        $rightWeek = ( $curWeekNum == $weekNum );
192
193                        if ( $weekNum == 5 && ( $curDate['month'] % 12 == ( $newMonth + 1 ) % 12 ) ) {
194                            $curDateJD -= 7;
195                            // FIXME: This method doesn't exist!
196                            $curDate = self::getJulianDayTimeValue( $curDateJD );
197                            $rightMonth = $rightWeek = true;
198                        }
199                    }
200                } while ( !$rightMonth || !$rightWeek );
201            } else { // $unit == 'day' or 'week'
202                // Assume 'day' if it's none of the above.
203                $curDateJD += ( $unit === 'week' ) ? 7 * $period : $period;
204                $curDate = date_parse( JDToGregorian( $curDateJD ) );
205            }
206
207            // Should we stop?
208            $reachedEndDate = ( $instanceNum > $wgCargoRecurringEventMaxInstances ||
209                ( $endDate !== null && ( $curDateJD > $endDateJD ) ) );
210        } while ( !$reachedEndDate );
211
212        // Add in the 'include' dates as well.
213        $allDateStrings = array_filter( array_merge( $allDateStrings, $includedDates ) );
214
215        return implode( $delimiter, $allDateStrings );
216    }
217
218}