(* COCANWIKI - a wiki written in Objective CAML. * Written by Richard W.M. Jones . * Copyright (C) 2004 Merjis Ltd. * $Id: cocanwiki_ext_calendar.ml,v 1.2 2004/10/07 18:56:53 rich Exp $ * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. *) open Apache open Registry open Cgi open Printf open ExtList open GregorianDate open Cocanwiki open Cocanwiki_template open Cocanwiki_strings open Cocanwiki_date let day_template = _get_template "calendar_day.html" let month_template = _get_template "calendar_month.html" let year_template = _get_template "calendar_year.html" let year_1m_template = _get_template "calendar_year_1m.html" let rec range a b = if a <= b then a :: range (a+1) b else [] let extension (dbh : Dbi.connection) hostid url = (* Validate a date in the form "yyyy[/mm[/dd]]". Returns a (yyyy, mm, dd) * tuple with missing fields set to 0. If the string doesn't parse or the * date isn't valid, then raises Not_found. *) let valid_date str = if String.length str = 4 && isdigit str.[0] && isdigit str.[1] && isdigit str.[2] && isdigit str.[3] then ( let yyyy = int_of_string (String.sub str 0 4) in (yyyy, 0, 0) ) else if String.length str = 7 && isdigit str.[0] && isdigit str.[1] && isdigit str.[2] && isdigit str.[3] && str.[4] = '/' && isdigit str.[5] && isdigit str.[6] then ( let yyyy = int_of_string (String.sub str 0 4) in let mm = int_of_string (String.sub str 5 2) in if mm >= 1 && mm <= 12 then (yyyy, mm, 0) else raise Not_found ) else if String.length str = 10 && isdigit str.[0] && isdigit str.[1] && isdigit str.[2] && isdigit str.[3] && str.[4] = '/' && isdigit str.[5] && isdigit str.[6] && str.[7] = '/' && isdigit str.[8] && isdigit str.[9] then ( let yyyy = int_of_string (String.sub str 0 4) in let mm = int_of_string (String.sub str 5 2) in let dd = int_of_string (String.sub str 8 2) in let date = (yyyy, mm, dd) in if GregorianDate.check_date date then date else raise Not_found ) else raise Not_found in (* From the links table, find all links to this page, or sub-calendar pages. * This query overselects. We then filter the real pages in OCaml. *) let sth = dbh#prepare_cached "select li.from_url, p.title, li.to_url from links li, pages p where li.hostid = ? and li.to_url like ? and li.hostid = p.hostid and li.from_url = p.url" in sth#execute [`Int hostid; `String (url ^ "%")]; let pages = let results = sth#map (function [`String from_url; `String title; `String to_url] -> from_url, title, to_url | _ -> assert false) in List.filter_map (fun (from_url, title, to_url) -> try let date = valid_date to_url in Some (date, (title, from_url)) with Not_found -> None) results in let pages = List.sort pages in (* Validate the date in the URL itself. *) let date = try Some (valid_date url) with Not_found -> None in match date with | None -> (* Not a valid date. *) "

" ^ url ^ " is not an actual date.

" | Some (yyyy, 0, 0) -> (* Year view. *) let template = year_template in template#set "yyyy" (string_of_int yyyy); template#set "prev_yyyy" (string_of_int (yyyy - 1)); template#set "next_yyyy" (string_of_int (yyyy + 1)); (* Return true if there are any events on a particular day. *) let has_events date = List.exists (fun (d, _) -> date = d) pages in (* Generate each month template separately ... * Wow, finally found a place I can use a for loop. *) for mm = 1 to 12 do let str = let template = year_1m_template in template#set "yyyy" (string_of_int yyyy); template#set "mm" (sprintf "%02d" mm); template#set "month_name" (long_month mm); let dow = GregorianDate.day_of_week (yyyy, mm, 1) in let dow = if dow = 7 then 0 else dow in let max_dd = GregorianDate.days_in_month yyyy mm in let dd = ref (1-dow) in let rows = ref [] in for r = 0 to 5 do (* up to 5 rows ... *) let cols = ref [] in for c = 0 to 6 do (* 7 columns, Sunday - Saturday *) let is_day = !dd >= 1 && !dd <= max_dd in let clasz = if is_day then ( let date = yyyy, mm, !dd in let is_weekend = GregorianDate.day_of_week date >= 6 in let events = has_events date in (if is_weekend then "cal_year_1m_weekend " else "") ^ (if events then "cal_year_1m_events" else "") ) else "cal_year_1m_empty" in let col = [ "is_day", Template.VarConditional is_day; "dd", Template.VarString (sprintf "%02d" !dd); "class", Template.VarString clasz ] in cols := col :: !cols; incr dd done; rows := [ "cols", Template.VarTable (List.rev !cols) ] :: !rows; cols := [] done; template#table "rows" (List.rev !rows); template#to_string in template#set ("month" ^ string_of_int mm) str done; (* Annual events. *) let events = List.filter (function ((_, 0, 0), _) -> true | _ -> false) pages in let table = List.map (fun (_, (title, page)) -> [ "title", Template.VarString title; "page", Template.VarString page ]) events in template#table "events" table; template#to_string | Some (yyyy, mm, 0) -> (* Month view. *) let template = month_template in template#set "month_name" (long_month mm); template#set "yyyy" (string_of_int yyyy); template#set "mm" (sprintf "%02d" mm); let prev_yyyy, prev_mm = if mm = 1 then yyyy - 1, 12 else yyyy, mm - 1 in let next_yyyy, next_mm = if mm = 12 then yyyy + 1, 1 else yyyy, mm + 1 in template#set "prev_yyyy" (string_of_int prev_yyyy); template#set "prev_mm" (sprintf "%02d" prev_mm); template#set "next_yyyy" (string_of_int next_yyyy); template#set "next_mm" (sprintf "%02d" next_mm); (* Get all monthly events and all daily events. *) let monthly_events, daily_events = List.partition (function ((_, _, 0), _) -> true | _ -> false) pages in (* Table of monthly events. *) let table = List.map (fun (_, (title, page)) -> [ "title", Template.VarString title; "page", Template.VarString page ]) monthly_events in template#table "monthly_events" table; (* How many days in this month? *) let max_dd = GregorianDate.days_in_month yyyy mm in let days = range 1 max_dd in let table = List.map (fun dd -> let events = List.filter (fun ((_, _, d), _) -> d = dd) daily_events in let table = List.map (fun (_, (title, page)) -> [ "title", Template.VarString title; "page", Template.VarString page ]) events in let is_weekend = GregorianDate.day_of_week (yyyy, mm, dd) >= 6 in [ "dd", Template.VarString (sprintf "%02d" dd); "is_weekend", Template.VarConditional is_weekend; "events", Template.VarTable table ]) days in template#table "days" table; template#to_string | Some (yyyy, mm, dd) -> (* Single day view. *) let template = day_template in failwith "not impl"; template#to_string (* Register the extension. *) let () = extensions := ("calendar", extension) :: !extensions