diff --git a/COPYING.txt b/COPYING.txt deleted file mode 100644 index dba13ed..0000000 --- a/COPYING.txt +++ /dev/null @@ -1,661 +0,0 @@ - GNU AFFERO GENERAL PUBLIC LICENSE - Version 3, 19 November 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU Affero General Public License is a free, copyleft license for -software and other kinds of works, specifically designed to ensure -cooperation with the community in the case of network server software. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -our General Public Licenses are intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - Developers that use our General Public Licenses protect your rights -with two steps: (1) assert copyright on the software, and (2) offer -you this License which gives you legal permission to copy, distribute -and/or modify the software. - - A secondary benefit of defending all users' freedom is that -improvements made in alternate versions of the program, if they -receive widespread use, become available for other developers to -incorporate. Many developers of free software are heartened and -encouraged by the resulting cooperation. However, in the case of -software used on network servers, this result may fail to come about. -The GNU General Public License permits making a modified version and -letting the public access it on a server without ever releasing its -source code to the public. - - The GNU Affero General Public License is designed specifically to -ensure that, in such cases, the modified source code becomes available -to the community. It requires the operator of a network server to -provide the source code of the modified version running there to the -users of that server. Therefore, public use of a modified version, on -a publicly accessible server, gives the public access to the source -code of the modified version. - - An older license, called the Affero General Public License and -published by Affero, was designed to accomplish similar goals. This is -a different license, not a version of the Affero GPL, but Affero has -released a new version of the Affero GPL which permits relicensing under -this license. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU Affero General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Remote Network Interaction; Use with the GNU General Public License. - - Notwithstanding any other provision of this License, if you modify the -Program, your modified version must prominently offer all users -interacting with it remotely through a computer network (if your version -supports such interaction) an opportunity to receive the Corresponding -Source of your version by providing access to the Corresponding Source -from a network server at no charge, through some standard or customary -means of facilitating copying of software. This Corresponding Source -shall include the Corresponding Source for any work covered by version 3 -of the GNU General Public License that is incorporated pursuant to the -following paragraph. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the work with which it is combined will remain governed by version -3 of the GNU General Public License. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU Affero General Public License from time to time. Such new versions -will be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU Affero General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU Affero General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU Affero General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as published by - the Free Software Foundation, either version 3 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 Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If your software can interact with users remotely through a computer -network, you should also make sure that it provides a way for users to -get its source. For example, if your program is a web application, its -interface could display a "Source" link that leads users to an archive -of the code. There are many ways you could offer source, and different -solutions will be better for different programs; see section 13 for the -specific requirements. - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU AGPL, see -. diff --git a/Docs.php b/Docs.php deleted file mode 100644 index 5835bec..0000000 --- a/Docs.php +++ /dev/null @@ -1,88 +0,0 @@ - - - - -

About

-

With this app you can subscribe to an iCalendar file which then gets regularly checked for updates. When new events in the calendar become available, it will create a facebook event in your name.

- -

Example: Google Calendar

-

Most calendars export to the iCalendar format, usually a file with the suffix .ics. For example, to get your Google Calendar URL: in Google Calendar go to Settings -> Calendars -> choose a calendar -> public ICAL.

- -

Pages

-

You can add this app to your facebook pages (fan-pages) to create events for them. Here is how: Go to the page of this app and click on the left on "Add to my Page". Add it to those of your pages you want, then close the popup. Now go to your page (fanpage) and edit it. Under Applications, you now should see "iCalendar to Event". Click there once again on Edit which should get you to the iCalendar to Event page with the page_id already filled in under Advanced Options.

- -

Groups

-

You can create events for a facebook group. Simply enter the group-id into the field when subscribing. To get your group-id, check the web-address of the grouppage for gid=XXX.

- -

Images

-

If some of the events in your iCalendar file have a special X-field that cointains an URL which points to an image file, you can enter the name of that field in the 'Picture' field in the advanced options of this App. For example if your ics file looks like the following -

-BEGIN:VEVENT
-DTSTART;VALUE=DATE:20060704
-DTEND;VALUE=DATE:20060705
-SUMMARY:Independence Day
-X-GOOGLE-CALENDAR-CONTENT-URL:http://www.google.com/logos/july4th06.gif
-END:VEVENT
-

-then you would write X-GOOGLE-CALENDAR-CONTENT-URL in the Picture field in the advanced options of a new subscription or click edit for an existing one. Or you can write ATTACH if you have attached an image by URL.

- -

If you're using Google Calendar you can do the following: -

    -
  1. in the iCalendar-to-Event app, in Advanced options (or Edit for an existing subscription), write 'ATTACH' (without the quotations marks) in the Picture field
  2. -
  3. in Google Calendar, click on the Labs icon in the upper right corner (the green potion)
  4. -
  5. enable the Event attachments feature.
  6. -
  7. now, when you create a new event you can add an attachement. choose an image
  8. -
-

- -

Subscriptions was deactivated

-

If the app always encounters an error when updating a subscription for an extended period of time, that subscription will be deactivated. This is displayed in a red box on the iCalendar-to-Event app subscriptions list (if you see nothing there then all your subscriptions are fine). You can try to reactivate it there which will only work if the error has been fixed. This is done as to not bother my server with checking lots of ics-files that don't work and aren't used anymore.

- -

Miscellaneous/FAQ

-
    -
  • Don't add a calendar with lots of event, remove it again, add it again etc. Facebook imposes certain limits and they don't like adding/removing lots of event too fast. When they decide you posted too much too quick the App will simply fail to create new events for you and usually there is a cryptic message in your error log.
  • -
  • If you change events in the ical file the facebook events will get updated but not the other way around.
  • -
  • New events are usually created every four hours or so.
  • -
  • If the App doesn't recognize your ics file you should make sure that the URL doesn't redirect. You can check for redirections here and if any are detected you should use the resolved URL.
  • -
  • If you see post of events on your wall although you have disabled 'posting to wall' in the advanced options in the iCalendar-to-Event App, it is probably Facebook's own 'Events' App that is posting to your wall and not my app. Please try the following:
    -
      -
    1. on the wall move your mouse over the post -> a cross (or X) should appear at the right
    2. -
    3. click on the cross -> a small menu appears
    4. -
    5. select "Remove publishing rights of Events"
    6. -
    -
  • -
  • When editing a subscription, and you selected Wall, even if you choose Update also existing events there will be no wall posts created for old events.
  • -
  • Internet Explorer might have some problems with this app. Get a decent browser like Firefox or Chrome.
  • -
- -
-

Future development

-
-

Planned Features

-

Here is a list of planned features and options that are not yet supported:

-
    -
  • support recurring events in ics files (there's a fundraiser for that)
  • -
  • associate a picture with a subscription which then will be added to every event created
  • -
  • your default RSVP: not attending
  • -
  • invite members of group/fans
  • -
- -
-

Contribute

-

This is free and open source software written in PHP. Feel free to contribute!

- -

Support

-

I'm working on this app in my spare time and running it on my private server. Please feel free to donate some money. Thank you!

-
- - - - -
diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..ba16a00 --- /dev/null +++ b/INSTALL @@ -0,0 +1,4 @@ +1) fill in your configuration and rename configTEMPLATE.inc.php to config.inc.php +2) run setup.sql on your SQL Database +3) set permissions to write for your web server for the directories 'tmp' and 'tmp/images' +4) make sure you're running PHP 5.3 or later on both the php command and in the web server \ No newline at end of file diff --git a/README b/README index 14dd04d..1769f6f 100644 --- a/README +++ b/README @@ -1,5 +1,5 @@ This is a Facebook Application you can use to subscribe to an iCalendar file which then gets regularly checked for updates. When new events in the calendar become available, it will create a facebook event in your name. -currently the app is online at http://www.facebook.com/apps/application.php?id=164414672850 +currently the app is online at https://www.facebook.com/pages/Calendar-to-Event/158110800961417 For any questions, please contact Mauro Bieg over facebook. \ No newline at end of file diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index b98b496..0000000 --- a/TODO.txt +++ /dev/null @@ -1,5 +0,0 @@ -* associate a picture with a subscription which then will be added to every event created (or by ATTACH property of VEVENT) -* support recurring events in ics files -* Your default RSVP: not attending. with Events.rsvp (requires extended-permission rsvp_event) -* invite members of group/fans with Events.invite -(http://wiki.developers.facebook.com/index.php/Communicating_with_Users_via_Email) \ No newline at end of file diff --git a/app_removed.php b/app_removed.php deleted file mode 100644 index d969e1c..0000000 --- a/app_removed.php +++ /dev/null @@ -1,38 +0,0 @@ -. -*/ - -mb_internal_encoding('UTF-8'); - -require_once 'config.php'; -require_once 'facebook/facebook.php'; - -$facebook = new Facebook($appapikey, $appsecret); -$user_id = $facebook->require_login(); - -//Connect to Database -$con = mysql_connect($host,$db_user,$db_password); -if (!$con) - die('Could not connect: '. mysql_error()); -mysql_query("SET NAMES 'utf8';", $con); -mysql_query("SET CHARACTER SET 'utf8';", $con); -mysql_select_db($database_name,$con); - -mysql_query("DELETE FROM users WHERE user_id = '$user_id'"); - -mysql_close($con); -?> \ No newline at end of file diff --git a/assets/scripts.js b/assets/scripts.js new file mode 100644 index 0000000..3748a17 --- /dev/null +++ b/assets/scripts.js @@ -0,0 +1,32 @@ +function activate(subId, subName) { + $('

The subscription '+subName+' is currently deactivated. Do you want to reactivate it?

' + + '

It might have been deactivated because there was an error for an extended period of time.

' + + '' + + '' + ).appendTo('.hoverDialog'); + showHoverDialog(); +} + +function deactivate(subId, subName) { + $('

The subscription '+subName+' is currently active. Do you want to deactivate it?

' + + '

Existing events will remain on facebook but no new events will be created.

' + + '' + + '' + ).appendTo('.hoverDialog'); + showHoverDialog(); +} + +function unsubscribe(subId, subName) { + $('

Are you sure you want to delete the subscription '+subName+' and all its events from facebook?

' + + '' + + '' + ).appendTo('.hoverDialog'); + showHoverDialog(); +} + +function showHoverDialog() { + $('.hoverDialog, .background').css('display', 'block'); +} +function hideHoverDialog() { + $('.hoverDialog, .background').empty().css('display', 'none'); +} \ No newline at end of file diff --git a/assets/styles.css b/assets/styles.css new file mode 100644 index 0000000..a914a76 --- /dev/null +++ b/assets/styles.css @@ -0,0 +1,161 @@ +html, body { + margin: 0; + padding: 0; + font-family: Helvetica, Arial, sans-serif; +} + +a, a:visited { + color: #3B5998; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} + +.container { + width: 760px; + margin: 1em auto; +} + +.successBox{ + border: 1px green solid; + padding: 1em; + margin: 1em 0; +} + +.red{ + color: red; + +} +.errorBox { + border: 1px red solid; + padding: 1em; + margin: 1em 0; +} +.successBox p, .errorBox p{ + margin-top: 0; +} + +.topBar { + background-color: #3B5998; + min-height: 50px; + margin-bottom: 20px; +} +.topBar a { + color: white; +} +.titleBar { + width: 770px; + margin: 0 auto; +} +#uberTitle { + float: right; + padding: 15px 0 0 0; + font-size: 20px; + font-weight: bold; +} +.topMenu { + float: left; + padding: 18px 8px 5px 0; + margin: 0; +} +.topMenu li { + list-style: none; + padding-right: 25px; + float: left; +} + +.donateBox { + float: right; + position: relative; + top: -20px; +} + +/* TABLES */ + +table { + width: 100%; + border-collapse: collapse; + text-align: left; +} +table th{ + padding-bottom: .5em; +} +table td{ + border-top: 1px solid lightgrey; + padding: .5em; +} + +/* ERROR LOG */ + +table .thLogId { + width: 100px; +} +table .thTime { + width: 100px; +} + + +h3 { + margin-top: 2.2em; +} + +/* SUB LIST */ + +.subTable { + margin-bottom: 2em; +} +.subTable .thName{ + width: 200px; +} +.subTable .thPage{ + width: 200px; +} + + + +.hoverDialog { + position: fixed; + top: 3em; + left: 30%; + width: 500px; + padding: 1em; + border: 1px solid black; + background-color: white; + z-index: 1000; +} +.background { + width: 100%; + height: 100%; + background-color: rgba(0,0,0,.75); + background-color: rgb(.1,.1,.1); + position: fixed; + top: 0; + left: 0; +} + +/* SUBSCRIBE PAGE */ + +.property { + border: 1px solid lightgrey; + padding: 1em; + margin: 1em 0; +} +.property .label { +} +.property input { + width: 50%; +} +.property input.checkbox { + width: 1em; +} +.property div.floatdiv input{ + float: left; + margin: 0.5em 1em 1em 0; +} +#iCalendarUrlInput { + width: 100%; +} +.submitButton { + margin-top: 1.5em; +} \ No newline at end of file diff --git a/classes/Calendar.php b/classes/Calendar.php deleted file mode 100644 index ecaf5a0..0000000 --- a/classes/Calendar.php +++ /dev/null @@ -1,304 +0,0 @@ -. -*/ - -class Calendar { - /////////////////////////////// - // PROPERTIES - /////////////////////////////// - - public $calendar_timezone; - public $newline_char; - public $iCalendar; //object of class vcalendar - public $sub_data; -//$sub_data is of form: -// array( -// "url" => $url, -// "user_id" => $user_id, -// "category" => $category, -// "subcategory" => $subcategory, -// "page_id" => $page_id, -// "picture" => $picture, -// "wall" => $wall, -// "image_field" => $image_field; -// ); - - private $session_key; - - /////////////////////////////// - //PUBLIC METHODS - /////////////////////////////// - - function __construct($sub_id, $sub_data = FALSE) { - //the optional parameter $sub_data is an array of the form specified above - - if ($sub_data === FALSE) { - //no sub_data supplied, fetch it from db - $result = mysql_query("SELECT * FROM subscriptions where sub_id='$sub_id'"); - if (mysql_num_rows($result) == 0) - throw new Exception("No subscription with sub_id=".$sub_id." found."); - $sub_data = mysql_fetch_assoc($result); - } - else { - //check supplied sub_data - $sub_data = $this->check_sub_data($sub_data); - } - - $this->sub_data = $sub_data; - } - - public function update($force = FALSE) { - //updates all events in this calendar and returns number of new events created - global $config; - - date_default_timezone_set('UTC'); - - $this->ensure_parse(); - $user_id = $this->sub_data['user_id']; - $url = $this->sub_data['url']; - $sub_id = $this->sub_data['sub_id']; - - //go figure timezone of calendar - $this->ensure_timezone(); - - //loop through the parsed icalendar and add all new events to db as well as create new fb-events - $numb_events = 0; - while( $event = $this->iCalendar->getComponent( 'vevent' )) { - $UID = $event->getProperty('UID'); - $UID = trim($UID); - $lm = $event->getProperty('LAST-MODIFIED'); - if ($lm) { - $lastupdated = $event->_date2timestamp($lm); - } - else { - $lastupdated = NULL; - } - - $sql_error = FALSE; - $result = mysql_query("select * from user$user_id where sub_id ='$sub_id' and binary UID = '$UID'") or $sql_error = TRUE; - if ($result && !($data = mysql_fetch_array($result)) && !$sql_error && !$force) { - //event doesn't exist yet, add it - - if($numb_events > $config['number_of_events_threshold']) { - //to not overstretch the facebook limits, add the other events later.. - ignore_user_abort(true); - - $numb_events = 0; - sleep($config['sleep_time']); - } - - $dts = $event->getProperty('DTSTART'); - if ($dts) { - $dtstart = $event->_date2timestamp($dts); - } - if ($dtstart > strtotime($config['old_event_threshold'])) { - //only add event if it is not older than a month - - $event_obj = new Event($event,$this); - //create facebook event - $event_id = $event_obj->post_to_fb(); - - if ($event_id > 0) { - //if successful - //get rid of all ' for mysql - $summary = mysql_real_escape_string($event->getProperty('SUMMARY')); - - //and save event to db - mysql_query("INSERT INTO user$user_id (event_id, UID, summary, lastupdated, sub_id) VALUES ('$event_id', '$UID', '$summary', '$lastupdated', '$sub_id')") or trigger_error(mysql_error()); - - $numb_events++; - } - - if ($this->sub_data['wall']) - $event_obj->post_to_wall(); - } - } - elseif($result && !$sql_error) { - //event already exists on fb or update is being forced - - if (isset($lastupdated) || $force) { - if ( ($lastupdated > $data['lastupdated']) || $force ) { - //event already exists, but has been changed - $event_id = $data['event_id']; - //new event - $event_obj = new Event($event,$this); - - // edit facebook event - try{ - $status = $event_obj->update_to_fb($event_id); - }catch(Exception $e) { - // or if($status != 1) - // throw new Exception("Could not update event " . $event_id); - } - - if ($status == 1) { - //if successfull - //get rid of all ' for mysql - $summary = mysql_real_escape_string($event->getProperty('SUMMARY')); - - mysql_query("UPDATE user$user_id SET summary='$summary', lastupdated='$lastupdated' WHERE event_id='$event_id'") or trigger_error(mysql_error()); - - $numb_events++; - } - } - } - } - } - - return $numb_events; - } - - public function insert_into_db() { - //inserts a new subscription into the db - - $user_id = $this->sub_data['user_id']; - $sub_name = $this->sub_data['sub_name']; - $url = $this->sub_data['url']; - $category = $this->sub_data['category']; - $subcategory = $this->sub_data['subcategory']; - $page_id = $this->sub_data['page_id']; - $wall = $this->sub_data['wall']; - $image_field = $this->sub_data['image_field']; - - mysql_query("INSERT INTO subscriptions (sub_name, user_id, url, category, subcategory, page_id, wall, image_field) VALUES ('$sub_name', '$user_id', '$url', '$category', '$subcategory', '$page_id', '$wall', '$image_field')") or trigger_error(mysql_error()); - //get sub_id from db - $query = mysql_query("select max(sub_id) from subscriptions"); - $this->sub_data['sub_id'] = mysql_result($query, 0); - } - - public function update_in_db() { - //updates an existing subscription in the db from this calendar object - - $user_id = $this->sub_data['user_id']; - $sub_name = $this->sub_data['sub_name']; - $url = $this->sub_data['url']; - $category = $this->sub_data['category']; - $subcategory = $this->sub_data['subcategory']; - $page_id = $this->sub_data['page_id']; - $wall = $this->sub_data['wall']; - $image_field = $this->sub_data['image_field']; - - $sub_id = $this->sub_data['sub_id']; - - //update db - mysql_query("UPDATE subscriptions SET sub_name='$sub_name', page_id='$page_id', category='$category', - subcategory='$subcategory', wall='$wall', image_field='$image_field' WHERE sub_id='$sub_id' AND user_id='$user_id'") or trigger_error(mysql_error()); - } - - public function get_session_key() { - //returns the session_key of the user of this calendar - - if (!isset($this->session_key)) { - $user_id = $this->sub_data['user_id']; - $result = mysql_query("SELECT session_key FROM users WHERE user_id='$user_id'") or trigger_error(mysql_error()); - if (mysql_num_rows($result) > 0) - $this->session_key = mysql_result($result, 0); - else { - throw new Exception("No session key found in database."); - } - } - - return $this->session_key; - } - - public function ensure_parse() { - //parses the file if not already done - if(!isset($this->iCalendar)) { - $this->parse(); - } - } - - public function ensure_timezone() { - //figures the timezone if not already done - if(! isset($this->calendar_timezone)) - $this->set_timezone(); - } - - /////////////////////////////// - //PRIVATE METHODS - /////////////////////////////// - - private function parse() { - // parses iCalendar/ics file - - $vcalendar = new vcalendar(); - $vcalendar->setConfig( "url", $this->sub_data['url'] ); - $vcalendar->setConfig( "newlinechar", "\r\n" ); - if ( FALSE === $vcalendar->parse()) { - //try other newline character - $vcalendar->setConfig( "newlinechar", "\n" ); - $this->newline_char = "n"; - if ( FALSE === $vcalendar->parse()) { - throw new Exception("Error when parsing file. Is this really a valid iCalendar file?"); - } - } - $this->iCalendar = $vcalendar; - } - - private function set_timezone() { - //sets $this->calendar_timezone - - $vtimezone = $this->iCalendar->getComponent('vtimezone'); - if ($vtimezone) { - $this->calendar_timezone = $vtimezone->getProperty("TZID"); - } - else { - $vtimezone = $this->iCalendar->getProperty( "X-WR-TIMEZONE" ); - if ($vtimezone) - $this->calendar_timezone = $vtimezone[1]; - } - } - - private function check_sub_data($sub_data) { - //checks whether the $sub_data entered by the user is valid - - //check subscription name - if (mb_strlen($sub_data['sub_name']) == 0) { - echo '{ "msg":"
You need to give your subscription a name.
"}'; - exit; - } - else { - //get rid of all ' for mysql - $sub_data['sub_name'] = str_replace("'","\'", $sub_data['sub_name']); - } - - //check category - if ($sub_data['category'] == "" || $sub_data['subcategory'] == "") { - echo '{ "msg":"
You need to specify a category and subcategory.
"}'; - exit; - } - - if ( isset($sub_data['wall']) ) - $sub_data['wall'] = TRUE; - else - $sub_data['wall'] = FALSE; - - //check url - $sub_data['url'] = trim($sub_data['url']); - $urlregex = '/^(https?|ftp):\/\/(\S*)\Z/'; - if (! preg_match($urlregex, $sub_data['url']) ) { - echo '{ "msg":"
URL doesn\'t have correct form. Please include http:// etc.
"}'; - exit; - } - - - return $sub_data; - } - - -} -?> diff --git a/classes/Controller.php b/classes/Controller.php new file mode 100644 index 0000000..a7514c0 --- /dev/null +++ b/classes/Controller.php @@ -0,0 +1,95 @@ +setImporter($calUrl); + } + + public function updateSub($subId){ + //fetches new events and publishes them to facebook + //regardless whether the subscription is active or not + + global $logger; + + $logger->setCurrentSubId($subId); + + $this->importSub($subId); + $this->publishSub($subId); + + $logger->unsetCurrentSubId(); + } + + public function importSub($subId){ + global $database; + + //download and parse iCal-file + $calUrl = $database->getCalUrl($subId); + $this->setImporter($calUrl, $subId); + + $moduleNames = array(); + $STH = $database->getModules($subId); + while ( $row = $STH->fetch() ) { + $moduleNames[] = $row->module; + } + $moduleNames[] = 'standardModule'; + + //TODO: select right modules + $this->importer->applyModules($moduleNames); + $this->importer->saveToDb(); + + $database->setSuccessfulImport($subId); + } + + public function publishSub($subId) { + global $database; + global $logger; + global $propagateExceptions; + + $this->publisher = new Publisher(); + + try{ + $this->publisher->publishSubscription($subId); + $database->setSuccessfulPublish($subId); + } catch(Exception $e){ + if ($propagateExceptions) + throw $e; + else + $logger->warning("Event could not be created on Facebook.", $e); + } + } + + + private function setImporter($calUrl, $subId = NULL) { + global $config; + global $database; + + $imageProperty = $database->getImageProperty($subId); + if ($imageProperty) { + $eventXProperties = array($imageProperty); + } else { + $eventXProperties = null; + } + + $this->importer = new iCalcreatorImporter(); //or qCalImporter() + + $this->importer->downloadAndParse( + $calUrl, + strtotime( $config['defaultReccurWindowOpen'] ), + strtotime( $config['defaultWindowClose'] ), + $subId, + $eventXProperties + ); + } +} + +?> diff --git a/classes/Database.php b/classes/Database.php new file mode 100644 index 0000000..bf555f3 --- /dev/null +++ b/classes/Database.php @@ -0,0 +1,498 @@ +DBH = new PDO("mysql:host=" . $config['dbHost'] . ";dbname=" . $config['dbName'], + $config['dbUser'], $config['dbPassword']); + $this->DBH->setAttribute( PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION ); + } catch(PDOException $e){ + $logger->error("Could not connect to database. " . $e->getMessage(), $e); + exit; + } + } + + function __destruct() { + //close connection + $DBH = null; + } + + + + + public function insertOrUpdateLog($level, $message, $debugInfo, $ip, $timestamp, $fbUserId, $subId, $ourEventId){ + $STH = $this->DBH->prepare(" + INSERT INTO log (level, message, debugInfo, ip, timestamp, fbUserId, subId, ourEventId, errorCount) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1) + + "); +/* ON DUPLICATE KEY UPDATE level=VALUES(level), message=VALUES(message), debugInfo=VALUES(debugInfo), + ip=VALUES(ip), timestamp=VALUES(timestamp), ourEventId=VALUES(ourEventId), errorCount=errorCount+1 + * + */ + $data = Array($level, $message, $debugInfo, $ip, $timestamp, $fbUserId, $subId, $ourEventId); + $STH->execute($data); + } + + public function cleanUpNewEvents() { + $this->DBH->query(" + DELETE FROM events WHERE state='new' + "); + } + + public function cleanUpLog(){ + global $config; + + $this->DBH->query(" + DELETE FROM log WHERE timestamp < " . strtotime($config['keepLogEntriesInDb']) + ); + } + + public function deactivateDeadSubs() { + global $config; + + $this->DBH->query(" + UPDATE subscriptions + SET active = 0 + WHERE lastSuccessfulPublishTimestamp < " . strtotime($config['deactivateSubsWithLastSuccessfullPublish']) + ); + } + + + public function getAccessToken($fbUserId) { + //returns the facebook access token as a string + + $STH = $this->DBH->prepare(' + SELECT fbAccessToken + FROM users + WHERE fbUserId = ? + '); + $STH->execute(array($fbUserId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + $row = $STH->fetch(); + + return $row->fbAccessToken; + } + + public function storeAccessToken($token, $fbUserId) { + //update or insert facebook access token + + $STH = $this->DBH->prepare(" + INSERT INTO users (fbUserId, fbAccessToken) + VALUES (?, ?) + ON DUPLICATE KEY UPDATE fbAccessToken = VALUES(fbAccessToken) + "); + + $data = Array($fbUserId, $token); + $STH->execute($data); + } + + public function selectUserIdAndAccessToken($subId) { + + $STH = $this->DBH->prepare(' + SELECT users.fbUserId, users.fbAccessToken + FROM users, subscriptions + WHERE users.fbUserId = subscriptions.fbUserId AND subscriptions.subId = ? + '); + $STH->execute(array($subId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + return $STH; + + } + + /* SUBSCRIPTIONS */ + + public function getCalUrl($subId) { + $STH = $this->DBH->prepare(' + SELECT calUrl + FROM subscriptions + WHERE subId = ? + '); + $STH->execute(array($subId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + + if ( $row = $STH->fetch() ) + return $row->calUrl; + else + throw new Exception("sub with id ".$subId." doesn't exist."); + } + + public function getImageProperty($subId) { + $STH = $this->DBH->prepare(' + SELECT imageProperty + FROM subscriptions + WHERE subId = ? + '); + $STH->execute(array($subId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + if( $row = $STH->fetch() ) + return $row->imageProperty; + else + return null; + } + + public function insertOrUpdateEvents($subscription) { + //takes an object of class Subscription and saves the fb* fields of its events to the db + $STH = $this->DBH->prepare(" + INSERT INTO events (calUID, recurrenceSetUID, startDate, subId, state, lastModifiedTimestamp, fbName, fbDescription, + fbStartTime, fbEndTime, fbLocation, fbPrivacy, imageFileUrl) + VALUES (:calUID, :recurrenceSetUID, :startDate, :subId, 'new', :lastModifiedTimestamp, + :fbName, :fbDescription, :fbStartTime, + :fbEndTime, :fbLocation, :fbPrivacy, :imageFileUrl) + ON DUPLICATE KEY UPDATE state = 'updated', lastModifiedTimestamp = :lastModifiedTimestamp, + recurrenceSetUID = :recurrenceSetUID, startDate = :startDate, fbName = :fbName, + fbDescription = :fbDescription, fbStartTime = :fbStartTime, + fbEndTime = :fbEndTime, fbLocation = :fbLocation, + fbPrivacy = :fbPrivacy, imageFileUrl = :imageFileUrl + "); + + foreach ($subscription->eventArray as &$e) { + $data = Array( + ':subId' => $subscription->getSubId(), + ':lastModifiedTimestamp' => $e['lastModifiedTimestamp'], + + ':fbName' => $e['fbName'], + ':fbDescription' => $e['fbDescription'], + ':fbStartTime' => $e['fbStartTime'], + ':fbEndTime' => $e['fbEndTime'], + ':fbLocation' => $e['fbLocation'], + ); + if( isset($e['fbPrivacy']) ) + $data[':fbPrivacy'] = $e['fbPrivacy']; + else + $data[':fbPrivacy'] = null; + + if( isset($e['imageFileUrl']) ) + $data[':imageFileUrl'] = $e['imageFileUrl']; + else + $data[':imageFileUrl'] = null; + + if ( isset($e['calUID']) ) { + $data[':calUID'] = $e['calUID']; + } else { + $data[':calUID'] = null; + } + + if ( isset($e['isPartOfRecurrenceSet']) && $e['isPartOfRecurrenceSet'] ) { + + $data[':recurrenceSetUID'] = $e['recurrenceSetUID']; + + if (isset($e['startDate'])) { + $data[':startDate'] = $e['startDate']; + } else { + $data[':startDate'] = null; + } + } else { + $data[':recurrenceSetUID'] = null; + $data[':startDate'] = null; + } + + $STH->execute($data); + } + } + + public function setSuccessfulImport($subId) { + //sets lastSuccessfulImportTimestamp in the database to now + + $timestamp = strtotime("now"); + + $STH = $this->DBH->prepare(" + UPDATE subscriptions + SET lastSuccessfulImportTimestamp = ? + WHERE subId = ? + "); + + $STH->execute( Array($timestamp, $subId) ); + } + + public function setSuccessfulPublish($subId) { + //sets lastSuccessfulPublishTimestamp in the database to now + + $timestamp = strtotime("now"); + + $STH = $this->DBH->prepare(" + UPDATE subscriptions + SET lastSuccessfulPublishTimestamp = ? + WHERE subId = ? + "); + + $STH->execute( Array($timestamp, $subId) ); + } + + public function hasSuccessfulImport($subId) { + //returns true if there ever has been a successful import, false otherwise + + $STH = $this->DBH->prepare(' + SELECT lastSuccessfulImportTimestamp + FROM subscriptions + WHERE subId = ? + '); + $STH->execute(array($subId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + $row = $STH->fetch(); + + if( isset($row->lastSuccessfulImportTimestamp) ) + return true; + else + return false; + } + + + /* EVENTS */ + + public function selectAllEvents($subId) { + + $STH = $this->DBH->prepare(' + SELECT * + FROM events + WHERE subId = ? + '); + $STH->execute(array($subId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + return $STH; + } + + public function selectNewEvents($subId) { + //selects events to be created or updated (plus the fbPageId) + + $STH = $this->DBH->prepare(" + SELECT events.*, subscriptions.fbPageId + FROM events, subscriptions + WHERE events.subId = ? AND (events.state = 'updated' OR events.state = 'new') AND subscriptions.subId = ? + "); + $STH->execute( array($subId, $subId) ); + + $STH->setFetchMode(PDO::FETCH_OBJ); + return $STH; + + } + + public function setEventUpdated($ourEventId, $fbEventId) { + + $STH = $this->DBH->prepare(" + UPDATE events + SET state = 'current', fbEventId = ? + WHERE ourEventId = ? + "); + + $STH->execute( array($fbEventId, $ourEventId) ); + } + + public function setEventDeleted($ourEventId) { + + $STH = $this->DBH->prepare(" + DELETE FROM events + WHERE ourEventId = ? + "); + + $STH->execute( array($ourEventId) ); + } + + public function deleteEvents($subId) { + $STH = $this->DBH->prepare(" + DELETE FROM events + WHERE subId = ? + "); + + $STH->execute( array($subId) ); + } + + public function notModified($lastModifiedTimestamp, $calUID, $subId) { + //returns true if the event has not been modified, i.e. + //if the $lastModifiedTimestamp is the same as in the db + //returns false if the event isn't found in the db or has been modified + + $STH = $this->DBH->prepare(' + SELECT lastModifiedTimestamp + FROM events + WHERE calUID = ? AND subId = ? + '); + $STH->execute(array($calUID, $subId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + $row = $STH->fetch(); + + if( isset($row->lastModifiedTimestamp) ) + $notModified = ( $row->lastModifiedTimestamp == $lastModifiedTimestamp ); + else + $notModified = false; + + return $notModified; + } + + public function selectAllActiveSubIds() { + + $STH = $this->DBH->query(' + SELECT subId + FROM subscriptions + WHERE active = 1 + '); + + $STH->setFetchMode(PDO::FETCH_OBJ); + return $STH; + } + + /* GUI */ + + public function selectSubscriptions($fbUserId) { + + $STH = $this->DBH->prepare(' + SELECT * + FROM subscriptions + WHERE fbUserId = ? + '); + $STH->execute(array($fbUserId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + return $STH; + } + + public function insertSubscription($subName, $fbUserId, $calUrl, $fbPageId, $imageProperty) { + //insert a new subscription and returns its subId + + $STH = $this->DBH->prepare(" + INSERT INTO subscriptions (subName, fbUserId, calUrl, fbPageId, imageProperty, active) + VALUES (?, ?, ?, ?, ?, 1) + "); + + if( strlen($imageProperty) < 4) + $imageProperty = null; + + $data = Array($subName, $fbUserId, $calUrl, $fbPageId, $imageProperty); + $STH->execute($data); + + return $this->DBH->lastInsertId(); + } + + public function selectErrorLog($subId) { + + $STH = $this->DBH->prepare(" + SELECT * + FROM log + WHERE subId = ? AND level = 'ERROR' + ORDER BY logId DESC + LIMIT 20 + "); + $STH->execute(array($subId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + return $STH; + } + + public function selectInfoLog($subId) { + //actually ERROR plus INFO log + + $STH = $this->DBH->prepare(" + SELECT * + FROM log + WHERE subId = ? AND level = 'INFO' + ORDER BY logId DESC + LIMIT 20 + "); + $STH->execute(array($subId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + return $STH; + } + + public function getSubscription($subId, $fbUserId) { + + $STH = $this->DBH->prepare(" + SELECT * + FROM subscriptions + WHERE subId = ? AND fbUserId = ? + "); + $STH->execute(array($subId, $fbUserId)); + + $STH->setFetchMode(PDO::FETCH_OBJ); + $row = $STH->fetch(); + + return $row; + } + + public function updateSubscriptionData($post, $fbUserId) { + + if ($post['imageProperty'] == '') + $post['imageProperty'] = null; + + $STH = $this->DBH->prepare(" + UPDATE subscriptions + SET subName = ?, imageProperty = ? + WHERE subId = ? AND fbUserId = ? + "); + + $data = Array($post['subName'], $post['imageProperty'], $post['subId'], $fbUserId); + $STH->execute($data); + } + + public function deleteSubscription($subId) { + + $STH = $this->DBH->prepare(" + DELETE FROM subscriptions + WHERE subId = ? + "); + + $STH->execute(array($subId)); + } + + public function deactivate($subId, $fbUserId) { + //fbUserId is required so people can only deactivate their own subs + + $STH = $this->DBH->prepare(" + UPDATE subscriptions + SET active = 0 + WHERE subId = ? AND fbUserId = ? + "); + + $data = Array($subId, $fbUserId); + $STH->execute($data); + } + + public function activate($subId, $fbUserId) { + //fbUserId is required so people can only reactivate their own subs + + $STH = $this->DBH->prepare(" + UPDATE subscriptions + SET active = 1 + WHERE subId = ? AND fbUserId = ? + "); + + $data = Array($subId, $fbUserId); + $STH->execute($data); + } + + public function getModules($subId) { + $STH = $this->DBH->prepare(" + SELECT module + FROM subscribedModules as s, modules as m + WHERE subId = ? AND s.module = m.moduleName + ORDER BY m.moduleOrder + "); + $STH->execute(array($subId)); + $STH->setFetchMode(PDO::FETCH_OBJ); + + return $STH; + } +} + +?> diff --git a/classes/Event.php b/classes/Event.php deleted file mode 100644 index b6be57a..0000000 --- a/classes/Event.php +++ /dev/null @@ -1,370 +0,0 @@ -. -*/ - -class Event { - // property declaration - public $calendar; - public $fbEvent; - private $vEvent; - private $event_id; - private $start_time; //start datetime as string in the TZ the user wants it to see (not pacific time or UTC) - private $image_file_path; - private $image_file_url; - - /////////////////////////////// - //PUBLIC METHODS - /////////////////////////////// - - function __construct($event, $calendar) { - $this->vEvent = $event; - $this->calendar = $calendar; - - //figure what image to use if any - $image_field = $this->calendar->sub_data['image_field']; - if (strlen($image_field) > 2) { - if ($image_field == "ATTACH"){ - //set image URL from ATTACH field - $this->image_file_url = $this->vEvent->getProperty('ATTACH'); - if (substr_count($this->image_file_url, "%3A%2F%2F") > 0){ - //decode percent-encoding - $this->image_file_url = rawurldecode($this->image_file_url); - } - } - else{ - //set image URL from X-property - $xprop = $this->vEvent->getProperty($image_field); - $this->image_file_url = $xprop[1]; - } - $this->get_image_file(); - } - elseif ($this->calendar->sub_data['picture']) { - //use generic subscription picture - $this->image_file_path = "pictures/".$this->calendar->sub_data['sub_id']; - $this->image_file_url = _HOST_URL . $this->image_file_path; - } - } - - function __destruct() { - //delete image in pictures/tmp - if ($this->image_file_path && !$this->calendar->sub_data['picture']) - unlink($this->image_file_path); - } - - public function post_to_fb() { - //creates a new event on facebook - - global $facebook; - $this->ensure_convert(); - - $user_id = $this->calendar->sub_data['user_id']; - $session_key = $this->calendar->get_session_key(); - $facebook->set_user($user_id, $session_key); - - //post array to facebook - if ($this->image_file_path) - $event_id = $facebook->api_client->events_create(json_encode($this->fbEvent), $this->image_file_path); - else - $event_id = $facebook->api_client->events_create(json_encode($this->fbEvent)); - - $this->event_id = $event_id; - return $event_id; - } - - - public function update_to_fb($eid) { - //updates an existing facebook event - - global $facebook; - $this->ensure_convert(); - - $user_id = $this->calendar->sub_data['user_id']; - $session_key = $this->calendar->get_session_key(); - $facebook->set_user($user_id, $session_key); - - //post array to facebook - try { - if ($this->image_file_path) - $status = $facebook->api_client->events_edit($eid, json_encode($this->fbEvent), $this->image_file_path); - else - $status = $facebook->api_client->events_edit($eid, json_encode($this->fbEvent)); - } - catch(Exception $e) { - //To avoid error "You are no longer able to change the name of this event." - $event_without_name = $this->fbEvent; - unset($event_without_name['name']); - if ($this->image_file_path) - $status = $facebook->api_client->events_edit($eid, json_encode($event_without_name), $this->image_file_path); - else - $status = $facebook->api_client->events_edit($eid, json_encode($event_without_name)); - } - - return $status; - } - - public function post_to_wall() { - //posts a small message and link to the event to the wall/stream of the user or page - //works only when post_to_fb has been called on the same object before (because of $this->event_id) - - global $facebook; - global $config; - - $user_id = $this->calendar->sub_data['user_id']; - $session_key = $this->calendar->get_session_key(); - $facebook->set_user($user_id, $session_key); - - $page_id = $this->calendar->sub_data['page_id']; - if ($page_id == 0) { - $page_id = NULL; - } - - //format start date/time - date_default_timezone_set('UTC'); - $user_details = $facebook->api_client->users_getInfo($user_id, 'locale'); - $user_zero = $user_details[0]; - $locale = $user_zero['locale']; - setlocale(LC_TIME, $locale); - $start_time = strftime("%A, %d. %B %Y %R", $this->start_time); - - $message = ''; - - //localization of wall texts (set language) - if ( isset($config['wall_text'][$locale]) ) - $caption = "{*actor*} ".$config['wall_text'][$locale]; - else - $caption = "{*actor*} ".$config['wall_text']["en_GB"]; - if ( isset($config['text_time'][$locale]) ) - $text_time = $config['text_time'][$locale]; - else - $text_time = $config['text_time']["en_GB"]; - - $attachment = array( - 'name' => $this->fbEvent['name'], - 'href' => 'http://www.facebook.com/event.php?eid=' . $this->event_id, - 'caption' => $caption, - 'properties' => array($text_time => utf8_encode($start_time)), - ); - - if ($this->image_file_path) { - //add picture - $attachment['media'] = array(array('type' => 'image', 'src' => $this->image_file_url, 'href' => 'http://www.facebook.com/event.php?eid=' . $this->event_id)); - } - $attachment = json_encode($attachment); - - //check whether $page_id is a page-ID or a group-ID - $is_group = $facebook->api_client->groups_get(NULL, $page_id); - - if ($is_group) - $facebook->api_client->stream_publish($message, $attachment, NULL, $page_id); - else - $facebook->api_client->stream_publish($message, $attachment, NULL, NULL, $page_id); - - } - - public function ensure_convert() { - if(!isset($this->fbEvent)) { - $this->convert(); - } - } - - -/////////////////////////////// -//PRIVATE METHODS -/////////////////////////////// - - private function convert() { - //converts vEvent to fbEvent - global $config; - - //category - $event['category'] = $this->calendar->sub_data['category']; - //subcategory - $event['subcategory'] = $this->calendar->sub_data['subcategory']; - //host - $event['host'] = $this->calendar->sub_data['user_id']; - //page_id - $event['page_id'] = $this->calendar->sub_data['page_id']; - if ($event['page_id'] == 0 || $event['page_id'] == '') - unset($event['page_id']); - - //location - $location_arr = $this->vEvent->getProperty('LOCATION', FALSE, TRUE); - $location = $location_arr['value']; - - //check for Quoted-Printable encoding - if ($location_arr['params']['ENCODING'] == "QUOTED-PRINTABLE") - $location = quoted_printable_decode($location); - if (!$location || $location == '') - $event['location'] = ' '; - else - $event['location'] = $location; - - //summary - $summary_arr = $this->vEvent->getProperty('SUMMARY', FALSE , TRUE); - $summary = $summary_arr['value']; - if (!$summary) - $event['name'] = "unknown name"; - else { - //check for Quoted-Printable encoding - if ($summary_arr['params']['ENCODING'] == "QUOTED-PRINTABLE") - $summary = quoted_printable_decode($summary); - - //facebook doesn't allow title names that are too long - $event['name'] = mb_substr($summary, 0, $config['max_length_title']); - } - - $weburl= $this->vEvent->getProperty('URL'); - - //description - $description_arr = $this->vEvent->getProperty('DESCRIPTION', FALSE, TRUE); - $description = $description_arr['value']; - - if ($description) { - //check for Quoted-Printable encoding - if ($description_arr['params']['ENCODING'] == "QUOTED-PRINTABLE") - $description = quoted_printable_decode($description); - - $description = str_replace("\\n", "\r\n", $description, $count); - if ($this->calendar->newline_char == "n") { - $description = str_replace("\\r", "", $description, $count); - } - $description = strip_tags($description); - } - if ($weburl) - $description .= "\r\n\r\n".$weburl; - if ($description) - $event['description'] = $description; - - //start_time - $event["start_time"] = $this->to_facebook_time("DTSTART"); - - //end_time - $event["end_time"] = $this->to_facebook_time("DTEND"); - - $this->fbEvent = $event; - } - - - private function to_facebook_time($key) { - //takes an ics-key (like DTSTART or DTEND) and outputs this in the time-format facebook currently uses in Events.create - /* - because facebook is stupid the time of the event displayed is the same for each user, regardless of his time zone. - */ - - - $dt = $this->vEvent->getProperty($key, FALSE, TRUE); - $time_arr = $dt["value"]; - - date_default_timezone_set('UTC'); - - - if (!$dt && strtoupper($key) == "DTEND") { - //if only DURATION specified calculate DTEND - - $duration_arr = $this->vEvent->getProperty('DURATION'); - if (!$duration_arr) - $duration_arr['hour'] = 2; - - $start_dt = $this->vEvent->getProperty('DTSTART', FALSE, TRUE); - $start_dt = $start_dt["value"]; - $start_time = $this->vEvent->_date2timestamp($start_dt); - if (isset($duration_arr['week'])) - $duration .= $duration_arr['week'] . "weeks "; - if (isset($duration_arr['day'])) - $duration .= $duration_arr['day'] . "days "; - if (isset($duration_arr['hour'])) - $duration .= $duration_arr['hour'] . "hours "; - if (isset($duration_arr['min'])) - $duration .= $duration_arr['min'] . "minutes "; - if (isset($duration_arr['sec'])) - $duration .= $duration_arr['sec'] . "seconds "; - - $time = strtotime($duration, $start_time); //end_time = start_time + duration - } - else { - $time = $this->vEvent->_date2timestamp($time_arr); - } - - - if(!isset($dt["params"]["TZID"])) { - //if no timezone specified check for calendar timezone - $calendar_tz = $this->calendar->calendar_timezone; - - if (isset($calendar_tz) && isset($time_arr["tz"]) && $time_arr["tz"] == "Z") { - // if calendar timezone is set and event is in UTC we assume that the user - // wants his event time displayed in the calendar timezone (presumably - // his local timezone) and not in UTC. Thus we calculate the offset. - $user_tz = new DateTimeZone($calendar_tz); - $datetime = new DateTime("@$time"); - $tz_offset = timezone_offset_get($user_tz, $datetime); - $time += $tz_offset; - } - } - - - //save start time for post_to_wall() - //$time is timestamp in UTC now - if ($key == "DTSTART") - $this->start_time = $time; - - date_default_timezone_set('UTC'); - - //adjust for newest facebook bug.. - //adds 7 or 8 hours (depending on whether it's daylight saving time over in sunny California) to the timestamp - $user_tz = new DateTimeZone('America/Los_Angeles'); - $datetime = new DateTime("@$time"); - $tz_offset = timezone_offset_get($user_tz, $datetime); - $time -= $tz_offset; - - return date("c", $time); //output in ISO 8601, e.g. 2004-02-12T15:19:21+00:00 - } - - private function get_image_file() { - //download image for specific event - - $image_url = $this->image_file_url; - - try { - $newfilename = "pictures/tmp/" . rand(100000, 10000000); - - $out = fopen($newfilename, 'wb'); - if ($out == FALSE) { - throw new Exception("Could not open location $newfilename."); - } - $ch = curl_init(); - curl_setopt($ch, CURLOPT_FILE, $out); - curl_setopt($ch, CURLOPT_HEADER, 0); - curl_setopt($ch, CURLOPT_URL, $image_url); - - curl_exec($ch); - - if (curl_error($ch)) - throw new Exception(curl_error($ch)); - - curl_close($ch); - - $this->image_file_path = $newfilename; - } - catch (Exception $e) { - echo $e->getMessage(); - - return NULL; - } - } - -} -?> diff --git a/classes/Importer.php b/classes/Importer.php new file mode 100644 index 0000000..49be036 --- /dev/null +++ b/classes/Importer.php @@ -0,0 +1,53 @@ +subscription with a new + * object and stores the data in its cal* fields. + * Only events whose DTSTART is between start- and endTimestamp are imported. + * Also, if certain modules need non-standard X-PROPERTIES of events they need to be + * specified as a string-array here to be imported. + */ + + public function applyModules($moduleNames){ + /* + * moduleNames is an array of strings. + * applies the modules to the subscription field in the order specified + */ + + foreach ($moduleNames as $moduleName) { + + require_once 'classes/modules/' . $moduleName . '.php'; + + //execute the function with the name stored in $module + $module = new $moduleName($this->subscription); + $module->apply(); + } + } + + public function saveToDb(){ + /* + * saves the subscription's fields to the database + */ + + global $database; + + $database->insertOrUpdateEvents($this->subscription); + } + +} + +?> diff --git a/classes/Logger.php b/classes/Logger.php new file mode 100644 index 0000000..5b09551 --- /dev/null +++ b/classes/Logger.php @@ -0,0 +1,152 @@ +currentFbUserId = $currentFbUserId; + } + + public function setCurrentSubId($currentSubId) { + $this->currentSubId = $currentSubId; + } + public function getCurrentSubId() { + return $this->currentSubId; + } + public function unsetCurrentSubId() { + $this->currentSubId = null; + } + + public function setCurrentOurEventId($ourEventId) { + $this->ourEventId = $ourEventId; + } + public function unsetCurrentOurEventId() { + $this->ourEventId = null; + } + + public function setLogToFile() { + $this->loggingTarget = "file"; + } + + public function setLogToDb($database) { + $this->database = $database; + $this->loggingTarget = "db"; + } + + /* LOGGING */ + + public function info($message) { + $this->log("INFO", $message); + } + + public function warning($message, $exception = "") { + $this->log("WARNING", $message, $exception); + } + + public function error($message, $exception = "") { + $this->log("ERROR", $message, $exception); + } + + public function debug($message){ + global $config; + if ($config['debugMode']) { + $this->log("DEBUG", $message); + } + } + + public function log($level, $message, $exception = "") { + global $config; + + $level = strtoupper($level); + + switch ( $config['logLevel'] ) { + case "OFF": + break; + case "ERROR": + if ($level == "ERROR") + $this->doLog($level, $message, $exception); + break; + case "WARN": + if ($level == "ERROR" || $level == "WARNING") + $this->doLog($level, $message, $exception); + break; + case "INFO": + $this->doLog($level, $message, $exception); + break; + } + } + + + /* PRIVATE */ + + private function doLog($level, $message, $exception) { + if ($this->loggingTarget == "db") { + $this->logToDb($level, $message, $exception); + } else { + $this->logToFile($level, $message); + } + } + + private function logToFile($level, $message) { + global $config; + + $currentIP = $_SERVER["REMOTE_ADDR"]; + $currentTime = strftime("%Y-%m-%d--%H:%M:%S"); + $logLine = $currentTime . " - " . $level . " - " . $currentIP . " - " . str_replace("\n", "", str_replace("\r", "", $message)); + + $logFile = $config['logFile']; + + // First check if there exists a log file + if (!file_exists($logFile)) { + touch($logFile); + } + + // Then check if the size of it is too big + if (filesize($logFile) > $config['logMaxFileSize']) { + //copy($current_logfile, $old_logfile); + unlink($logFile); + touch($logFile); + } + + // Now write line into the file + $fh = fopen($logFile, "a"); + fwrite($fh, trim($logLine) . "\n"); + fflush($fh); + fclose($fh); + } + + private function logToDb($level, $message, $exception = "") { + $currentIP = isset($_SERVER["REMOTE_ADDR"]) ? $_SERVER["REMOTE_ADDR"] : null; + + if ($exception == ""){ + $debugInfo = ""; + } else { + $message .= " - " . $exception->getMessage(); + $debugInfo = "File " . $exception->getFile() . ", Line " . $exception->getLine() . ", Trace " . $exception->getTraceAsString(); + } + + try { + $this->database->insertOrUpdateLog($level, $message, $debugInfo, $currentIP, time(), $this->currentFbUserId, $this->currentSubId, $this->ourEventId); + } catch(Exception $e) { + $this->logToFile($level, $message); + $this->logToFile("ERROR", "Could not log to db, have logged to file instead. ".$e->getMessage() ); + } + } + +} + +?> diff --git a/classes/Module.php b/classes/Module.php new file mode 100644 index 0000000..41ca5f0 --- /dev/null +++ b/classes/Module.php @@ -0,0 +1,21 @@ +sub = $sub; + } + + public abstract function apply(); + //this method is implemented by the child classes. + +} + +?> diff --git a/classes/Publisher.php b/classes/Publisher.php new file mode 100644 index 0000000..a491e30 --- /dev/null +++ b/classes/Publisher.php @@ -0,0 +1,111 @@ +selectUserIdAndAccessToken($subId); + $row = $STH->fetch(); + $fbUserId = $row->fbUserId; + $token = $row->fbAccessToken; + + + $STH = $database->selectNewEvents($subId); + + $thereIsAnotherRow = ( $row = $STH->fetch() ); + while ($thereIsAnotherRow) { + //create new events + + $logger->setCurrentOurEventId($row->ourEventId); + + $fbEventArray = array( + 'name' => $row->fbName, + 'description' => $row->fbDescription, + 'start_time' => $row->fbStartTime, + 'end_time' => $row->fbEndTime, + 'location' => $row->fbLocation, + 'privacy_type' => $row->fbPrivacy, //or 'privacy' ? + 'access_token' => $token, + ); + + if ($row->state == 'new') { + $action = "create"; + if( isset($row->fbPageId) && $row->fbPageId ) { + $fbEventArray['page_id'] = $row->fbPageId; + $page = $row->fbPageId . '/events'; + } else { + //post to profile + $page = '/me/events'; + } + } elseif ($row->state == 'updated') { + $action = "update"; + $page = '/' . $row->fbEventId; + } else { + throw new Exception("Event state is neither 'new' nor 'updated' but: '".$row->state."'"); + } + + if( isset($row->imageFileUrl) ) { + $file = tempnam('tmp/images/', $row->ourEventId.'_'); + if (!$file) + $logger->error('Could not create file in tmp/images.'); + $imageContent = file_get_contents($row->imageFileUrl, null, null, null, $config['maxImageFileLength']); + if ($imageContent) { + file_put_contents ($file, $imageContent); + $fbEventArray[basename($file)] = '@'.realpath($file); + } + } else { + $imageContent = false; + } + + try { + $response = $facebook->api($page, 'post', $fbEventArray); + + if ($response) { + if ( $action == "update") { + $fbEventId = $row->fbEventId; + } elseif ( $action == "create" && is_numeric($response['id']) ) { + $fbEventId = $response['id']; + } else { + throw new Exception("Response is not valid"); + } + $database->setEventUpdated($row->ourEventId, $fbEventId); + $logger->info("Event ".$action."d on facebook. fbEventId: " . $fbEventId); + } else { + throw new Exception("Response when trying to ".$action." event was negative."); + } + } catch (Exception $e) { + if ($action == "create" || $propagateExceptions) { + //if failed to create event don't bother trying the rest of the subscription + throw $e; + } else { + //if only an update failed go on with the other events of the subscription + $logger->warning("Could not update event on Facebook.", $e); + } + } + + if( isset($file) && file_exists($file) ) + unlink($file); + + $logger->unsetCurrentOurEventId(); + + $thereIsAnotherRow = ( $row = $STH->fetch() ); + if ($thereIsAnotherRow) + sleep($config['waitForNextEventPublish']); + } + + } + +} + +?> diff --git a/classes/SimpleImage.php b/classes/SimpleImage.php deleted file mode 100644 index eba37fe..0000000 --- a/classes/SimpleImage.php +++ /dev/null @@ -1,86 +0,0 @@ -image_type = $image_info[2]; - if( $this->image_type == IMAGETYPE_JPEG ) { - $this->image = imagecreatefromjpeg($filename); - } elseif( $this->image_type == IMAGETYPE_GIF ) { - $this->image = imagecreatefromgif($filename); - } elseif( $this->image_type == IMAGETYPE_PNG ) { - $this->image = imagecreatefrompng($filename); - } - } - function save($filename, $image_type=IMAGETYPE_JPEG, $compression=75, $permissions=null) { - if( $image_type == IMAGETYPE_JPEG ) { - imagejpeg($this->image,$filename,$compression); - } elseif( $image_type == IMAGETYPE_GIF ) { - imagegif($this->image,$filename); - } elseif( $image_type == IMAGETYPE_PNG ) { - imagepng($this->image,$filename); - } - if( $permissions != null) { - chmod($filename,$permissions); - } - } - function output($image_type=IMAGETYPE_JPEG) { - if( $image_type == IMAGETYPE_JPEG ) { - imagejpeg($this->image); - } elseif( $image_type == IMAGETYPE_GIF ) { - imagegif($this->image); - } elseif( $image_type == IMAGETYPE_PNG ) { - imagepng($this->image); - } - } - function getWidth() { - return imagesx($this->image); - } - function getHeight() { - return imagesy($this->image); - } - function resizeToHeight($height) { - $ratio = $height / $this->getHeight(); - $width = $this->getWidth() * $ratio; - $this->resize($width,$height); - } - function resizeToWidth($width) { - $ratio = $width / $this->getWidth(); - $height = $this->getheight() * $ratio; - $this->resize($width,$height); - } - function scale($scale) { - $width = $this->getWidth() * $scale/100; - $height = $this->getheight() * $scale/100; - $this->resize($width,$height); - } - function resize($width,$height) { - $new_image = imagecreatetruecolor($width, $height); - imagecopyresampled($new_image, $this->image, 0, 0, 0, 0, $width, $height, $this->getWidth(), $this->getHeight()); - $this->image = $new_image; - } -} -?> \ No newline at end of file diff --git a/classes/Subscription.php b/classes/Subscription.php new file mode 100644 index 0000000..b31b87d --- /dev/null +++ b/classes/Subscription.php @@ -0,0 +1,110 @@ + "", + calDTEND => "", + calDTStartTZID => "", + calDTEndTZID => "", + calUID => "", + calSUMMARY => "", + calDESCRIPTION => "", + calLOCATION => "", + calCLASS => "", + calRRULE => "", + + lastModifiedTimestamp => "", + isPartOfRecurrenceSet => "", + recurrenceSetUID => "", + startDate => "", //needed for identification of recurrence instance, ->format("Ymd") i.e. YYYYMMDD + + fbName => "", + fbDescription => "", + fbStartTime => "", + fbEndTime => "", + fbLocation => "", + fbPrivacy => "", + + imageFileUrl => "" + ); + */ + + public function getSubId() { + return $this->subId; + } + + public function setSubId($subId) { + $this->subId = $subId; + } + + public function getSubName() { + return $this->subName; + } + + public function setSubName($subName) { + $this->subName = $subName; + } + + public function getFinalTimezone() { + return $this->finalTimezone; + } + + public function setFinalTimezone($finalTimezone) { + $this->finalTimezone = $finalTimezone; + } + + public function getCalTZID() { + return $this->calTZID; + } + + public function setCalTZID($calTZID) { + $this->calTZID = $calTZID; + } + + public function getCalXWRTIMEZONE() { + return $this->calXWRTIMEZONE; + } + + public function setCalXWRTIMEZONE($calXWRTIMEZONE) { + $this->calXWRTIMEZONE = $calXWRTIMEZONE; + } + + public function getFbUserId() { + return $this->fbUserId; + } + + public function setFbUserId($fbUserId) { + $this->fbUserId = $fbUserId; + } + + public function getEventArray() { + return $this->eventArray; + } + + public function setEventArray($eventArray) { + $this->eventArray = $eventArray; + } + +} + +?> diff --git a/classes/iCalcreatorImporter.php b/classes/iCalcreatorImporter.php new file mode 100644 index 0000000..722892c --- /dev/null +++ b/classes/iCalcreatorImporter.php @@ -0,0 +1,186 @@ += $config['maxiCalFileLength']) + throw new Exception("File was too large, i.e. larger than " . floor($config['maxiCalFileLength'] / 1000) . " KB."); + + $vcalendar = new vcalendar(); + $vcalendar->setConfig("newlinechar", "\r\n"); + if (FALSE === $vcalendar->parse($rawdata)) { + $vcalendar->setConfig("newlinechar", "\n"); + if (FALSE === $vcalendar->parse($rawdata)) { + throw new Exception("Error when parsing iCalendar file."); + } + } + + //create subscription + $sub = new Subscription(); + + if (isset($subId)) + $sub->setSubId($subId); + + /* + * put calendar properties + */ + + $vtimezone = $vcalendar->getComponent('vtimezone'); + if ($vtimezone) { + $sub->setCalTZID($vtimezone->getProperty("TZID")); + } + + $vtimezone = $vcalendar->getProperty("X-WR-TIMEZONE"); + if ($vtimezone) + $sub->setCalXWRTIMEZONE($vtimezone[1]); + + + /* + * put events + */ + + $eventArray = array(); + while ($e = $vcalendar->getComponent('vevent')) { + + $event = array(); + + $dtstartValueParam = $e->getProperty('DTSTART', FALSE, TRUE); + $dtstart = $this->timeArrayToIso($dtstartValueParam['value']); + if ( !isset($dtstartValueParam['value']['hour'] ) ){ + $event['calDTStartTZID'] = "UTC"; + $event['calDTEndTZID'] = "UTC"; + } + + $dtendValueParam = $e->getProperty('DTEND', FALSE, TRUE); + if ($dtendValueParam) { + $dtend = $this->timeArrayToIso($dtendValueParam['value']); + } else { + //no DTEND in cal + $dtstartDateTime = new DateTime($dtstart); + $dtend = $dtstartDateTime->modify("+1 hour")->format("Y-m-d\TH:i:s"); + } + + $dtstartStamp = strtotime($dtstart); + + if ($dtstartStamp < $startTimestamp || $dtstartStamp > $endTimestamp) { + //if starttime of event isn't in window set, skip this event + continue; + } else { + $event['calDTSTART'] = $dtstart; + $event['calDTEND'] = $dtend; + } + + $event['calUID'] = $e->getProperty('UID'); + + $lastModified = $this->timeArrayToIso($e->getProperty('LAST-MODIFIED')); + $event['lastModifiedTimestamp'] = strtotime($lastModified); + + if (isset($subId) && $database->notModified($event['lastModifiedTimestamp'], $event['calUID'], $subId)) { + //if event has not been modified skip it + continue; + } + + $params = $dtstartValueParam['params']; + if ( isset($params['TZID']) ) { + $event['calDTStartTZID'] = $params['TZID']; + } + + $params = $dtendValueParam['params']; + if ( isset($params['TZID']) ) { + $event['calDTEndTZID'] = $params['TZID']; + } + + + $prop = $e->getProperty('SUMMARY'); + if ($prop) + $event['calSUMMARY'] = $prop; + else + $event['calSUMMARY'] = "Untitled"; + + $prop = $e->getProperty('DESCRIPTION'); + if ($prop) + $event['calDESCRIPTION'] = str_replace("\\n", "\r\n", $prop); + else + $event['calDESCRIPTION'] = ""; + + $prop = $e->getProperty('LOCATION'); + if ($prop) + $event['calLOCATION'] = $prop; + else + $event['calLOCATION'] = ""; + + $prop = $e->getProperty('CLASS'); + if ($prop) + $event['calCLASS'] = $prop; + + /* + * this unfortunately returns a confused array + $prop = $e->getProperty('RRULE'); + if ($prop) + $event['calRRULE'] = $prop; + * so we use our hacked-in stuff + */ + $prop = $e->unparsedRrule; + if ( isset($prop) ) + $event['calRRULE'] = $prop; + + //read in optional X-Properties + if (isset($eventXProperties)) { + foreach ($eventXProperties as $xprop) { + $prop = $e->getProperty($xprop); + if ($prop) { + if ($xprop == "ATTACH") { + if (substr_count($prop, "%3A%2F%2F") > 0) { + //decode percent-encoding + $event[$xprop] = rawurldecode($prop); + } else { + $event[$xprop] = $prop; + } + } else { + //is X-property + $event[$xprop] = $prop[1]; + } + } + } + } + + //push new event on array of events + array_push($eventArray, $event); + } + $sub->setEventArray($eventArray); + + //save subscription object in field + $this->subscription = $sub; + } + + private function timeArrayToIso($timeArr) { + //takes a iCalcreator date/time array and returns it as a ISO 8601 string + if (isset($timeArr['hour'])){ + $time = $timeArr['hour'] . ':' . $timeArr['min'] . ':' . $timeArr['sec']; + } else { + $time = "00:00:00"; + } + return $timeArr['year'] . '-' . $timeArr['month'] . '-' . $timeArr['day'] + . 'T' . $time; + ; + } + +} + +?> diff --git a/classes/modules/recurringEventsModule.php b/classes/modules/recurringEventsModule.php new file mode 100644 index 0000000..43a141b --- /dev/null +++ b/classes/modules/recurringEventsModule.php @@ -0,0 +1,126 @@ +sub; + $eventArray = &$sub->eventArray; + + $rrule = new recurrenceRuleParser(); + + //fill in recurring events + $tmpEventArray = array(); + foreach ($eventArray as &$e) { + if (isset($e['calRRULE'])){ + + try { + $ruleString = $e['calRRULE']; + $rrule->parseRule($ruleString, $e['calDTSTART'], $config['defaultReccurWindowOpen'], $config['defaultWindowClose']); + + $e['isPartOfRecurrenceSet'] = true; + $e['recurrenceSetUID'] = $e['calUID']; + + $startDateTime = new DateTime($e['calDTSTART']); + $endDateTime = new DateTime($e['calDTEND']); + $interval = $startDateTime->diff($endDateTime); + + foreach ($rrule->getStartTimesArray() as $startTime){ + + $newEvent = $e; + + //starttime + $newStartDateTime = new DateTime($startTime); + $newEvent['calDTSTART'] = $newStartDateTime->format("Y-m-d\TH:i:s"); + $newEvent['startDate'] = $newStartDateTime->format("Ymd"); + + //endtime + $newStartDateTime->add($interval); + $newEvent['calDTEND'] = $newStartDateTime->format("Y-m-d\TH:i:s"); + + $newEvent['recurrenceSetUID'] = $e['calUID']; + unset( $newEvent['calUID'] ); + + //append new recurring event to eventArray + $tmpEventArray[] = $newEvent; + } + } catch (Exception $ex) { + $logger->warning("Couldn't instantiate recurring event.", $ex); + } + } + } + $eventArray = array_merge($eventArray, $tmpEventArray); + + } + + /* + public function rulesArrayToString($arr) { + //parse the strange RRULE array that comes from the iCalcreator lib into the original string (like in the iCal standard) + + $out = ""; + + foreach($arr as $key => $value) { + + if ($key == "UNTIL") { + $out .= "UNTIL="; + $out .= $value['year']; + $out .= $value['month']; + $out .= $value['day']; + if ( isset($value['hour']) ) { + $out .= "T" . $value['hour']; + if ( isset($value['min']) ) + $out .= $value['min']; + else + $out .= "00"; + if ( isset($value['sec']) ) + $out .= $value['sec']; + else + $out .= "00"; + } + } else { + if (is_array($value)) { + $out .= $key . "="; + foreach ($value as $val) { + if (is_array($val)) { + foreach ($val as $v) { + if ( $key == "BYDAY") { + if (!is_numeric($v)) + $out .= $v . ","; + else + $out .= $v; + } else { + $out .= $v . ","; + } + } + } else { + $out .= $val . ","; + } + } + $out = substr($out, 0, strlen($out)-1); + } else { + $out .= $key . "=" . $value; + } + } + + $out .= ";"; + } + + $out = substr($out, 0, strlen($out)-1); + + return $out; + } + * + */ +} + +?> \ No newline at end of file diff --git a/classes/modules/recurringEventsModule/recurrenceRuleParser.php b/classes/modules/recurringEventsModule/recurrenceRuleParser.php new file mode 100644 index 0000000..7a0c773 --- /dev/null +++ b/classes/modules/recurringEventsModule/recurrenceRuleParser.php @@ -0,0 +1,515 @@ +outputFormat field. + */ + + //save input arguments to fields + $this->dtstartDateTime = new DateTime($dtstart); + $this->startDateTime = new DateTime($startTime); + $this->endDateTime = new DateTime($endTime); + + $this->startTimesArray = array(); + + if ( empty($rule) ) { + throw new Exception("RRULE is empty string"); + } + + //create $this->rulesArr like array('FREQ' => 'DAILY', 'INTERVAL' => '1') + $ruleParts = explode(";", $rule); + $this->rulesArr = array(); + foreach ($ruleParts as $rulePart) { + $explodedPart = explode("=", $rulePart, 2); + $this->rulesArr[ $explodedPart[0] ] = $explodedPart[1]; + } + $this->rulesArr = $this->rulesArr; + + //set Count + if ( isset($this->rulesArr['COUNT']) ) + $this->maxCount = $this->rulesArr['COUNT']; + else + $this->maxCount = PHP_INT_MAX; + + //set Until + $windowEndDateTime = $this->endDateTime; + if (isset($this->rulesArr['UNTIL'])) { + $until = new DateTime($this->rulesArr['UNTIL']); + if ($until > $windowEndDateTime) { + $until = $windowEndDateTime; + } + } + else { + $until = $windowEndDateTime; + } + $this->until = $until; + + //set default values + if ( !isset($this->rulesArr['INTERVAL']) ) + $this->rulesArr['INTERVAL'] = 1; + + //set date of original event + $this->recDate = clone $this->dtstartDateTime; + + if ( isset($this->rulesArr['FREQ']) ) { + $freq = $this->rulesArr['FREQ']; + } else + throw new Exception("No FREQ found in RRULE"); + + //iterate over the FREQ interval + while ($this->eventsCount < $this->maxCount && $this->recDate <= $until) { + + //check for some (the supported) combinations of BY***-rules + if ( isset($this->rulesArr['BYYEARDAY']) ) { + //if ( $freq != 'YEARLY' ) + // throw new Exception("BYYEARDAY must only be specified with FREQ=YEARLY"); + //$this->byYearDay(); + throw new Exception("BYYEARDAY not supported (yet?)"); + } + elseif ( isset($this->rulesArr['BYMONTHDAY']) ) { + if ($freq != 'MONTHLY') + throw new Exception("BYMONTHDAY only supported with FREQ=MONTHLY (yet?)"); + //if ( isset($this->rulesArr['BYMONTH']) ) { + // $this->byMonthDay_and_byMonth(); + //} + //else { + $this->byMonthDay(); + //} + } elseif( isset($this->rulesArr['BYMONTH']) ){ + if ( $freq == 'WEEKLY') { + throw new Exception("BYMONTH must not occure with FREQ=WEEKLY"); + } + + if ($freq == 'MONTHLY') { + $this->byDay_Monthly(); + } + elseif ( isset($this->rulesArr['BYDAY']) ){ + $this->byMonth_and_byDay(); + } + else { + $this->byMonth(); + } + } + elseif ( isset($this->rulesArr['BYWEEKNO']) ){ + throw new Exception("BYWEEKNO not supported (yet?)"); + + /* + if ( isset($this->rulesArr['BYDAY']) ){ + $this->byWeekNo_and_byDay(); + } + else { + $this->byWeekNo(); + } + */ + } + elseif ( isset($this->rulesArr['BYDAY']) ){ + if ($freq == 'WEEKLY') + $this->byDay_Weekly(); + elseif ($freq == 'MONTHLY') + $this->byDay_Monthly(); + elseif ($freq == 'YEARLY') { + if (isset($this->rulesArr['BYMONTH'])) + $this->byMonth_and_byDay(); + else + throw new Exception("BYDAY and FREQ=YEARLY only supported in combination with BYMONTH (yet?)"); + } + else + throw new Exception("BYDAY only supported with FREQ = WEEKLY, MONTHLY or YEARLY (yet?)"); + } + else { + $this->noByDefined(); + } + } + } + + public function setTimezone($tzid) { + $this->tzid = $tzid; + } + + public function getStartTimesArray(){ + return $this->startTimesArray; + } + + public function serializeStartTimesArray() { + $output = ""; + foreach($this->startTimesArray as $el) { + $output .= $el."_"; + } + return $output; + } + + + /* + * BY***-parsing functions + */ + + + private function byMonthDay() { + //case: BYMONTHDAY + //only supported FREQ in combination with this is MONTHLY + + + $daysArr = explode(",", $this->rulesArr['BYMONTHDAY']); + $date = clone $this->recDate; + + //fill up usual dates + foreach ($daysArr as $day) { + if (checkdate($date->format('m'), $day, $date->format('Y'))) { + $date->setDate($date->format('Y'), $date->format('m'), $day); + $this->appendToOutputArray($date); + } + } + + //increment by FREQ + $this->countUpByMonthInterval(); + } + + private function byMonth_and_byDay() { + //case: BYMONTH in combination with BYDAY + //only supported FREQ in combination with this is YEARLY + //between SU & SA + + //@TODO + throw new Exception("BYMONTH in combination with BYDAY with FREQ=YEARLY not supported yet"); + } + + private function byMonth() { + //case: BYMONTH + //only supported FREQ in combination with this is YEARLY + + + $monthArr = explode(",", $this->rulesArr['BYMONTH']); + + $date = clone $this->recDate; + $year = $date->format('Y'); + $day = $date->format('d'); + + foreach( $monthArr as $month) { + //$month is number from 1 to 12 + + if ( checkdate($month, $day, $year) ) { + $datetime = DateTime::createFromFormat("Y-m-d", $year.'-'.$month.'-'.$day); + $datetime->setTime($date->format('H'), $date->format('i'), $date->format('s')); + $this->appendToOutputArray( $datetime ); + } + } + + //increment by FREQ + $this->recDate->modify( "+" . $this->rulesArr['INTERVAL'] . " year"); + } + + private function byDay_Weekly() { + //case: BYDAY and FREQ=WEEKLY + + + if ( isset($this->rulesArr['WKST']) && $this->rulesArr['WKST'] != 'MO') + throw new Exception("WKST != MO not supported yet"); + + $daysArr = explode(",", $this->rulesArr['BYDAY']); + + $date = clone $this->recDate; + $weekdayInt = $this->weekdayToInt( $date->format('l') ); + + //set $date to monday of the week we are in + $date->modify("-".$weekdayInt." Days"); + + foreach ($daysArr as $day) { + $monday = clone $date; + + $monday->modify("+". $this->weekdayToInt($day) ." Days"); + + $this->appendToOutputArray($monday); + } + + //increment by FREQ + $this->recDate->modify( "+" . $this->rulesArr['INTERVAL'] . " week"); + } + + private function byDay_Monthly() { + //case: BYDAY and FREQ=MONTHLY + + + $daysArr = explode(",", $this->rulesArr['BYDAY']); + + $date = clone $this->recDate; + + //set to first day of the month + $date->setDate($date->format('Y'), $date->format('m'), 1); + + if ( isset($this->rulesArr['BYSETPOS']) ) { + //if BYSETPOS is set we ignore numeric values like "2SU" + $bySetPos = $this->rulesArr['BYSETPOS']; + + if ( count(explode(",", $bySetPos)) > 1) + throw new Exception("only one value in BYSETPOS supported (yet?)"); + + if ($bySetPos < 0) { + $sign = "-"; + $bySetPos = abs($bySetPos); + + $lastDayInMonth = cal_days_in_month(CAL_GREGORIAN, $date->format('m'), $date->format('Y')); + $date->setDate($date->format('Y'), $date->format('m'), $lastDayInMonth); + } else { + $sign = "+"; + } + + $count = 0; //counts the number of hits we had on the set weekdays in $daysArr + $month = $date->format('n'); //if we overshoot our month we stop the loop + + if (isset($this->rulesArr['BYMONTH'])) { + $byMonthRulesString = $this->rulesArr['BYMONTH']; + $monthsArr = explode(",", $byMonthRulesString); + $byMonthSet = true; + } else { + $monthsArr = array(); + $byMonthSet = false;; + } + + + $isInArr = in_array($month, $monthsArr); + if (!$byMonthSet || $isInArr) { + + while ( ($count < $bySetPos) && ($date->format('n') == $month) ) { + + $weekday = strtoupper( substr($date->format('l'), 0, 2) ); //e.g. "MO" + if ( in_array($weekday, $daysArr) ) { + $count++; + if ($count == $bySetPos) { + //if we hit something we append it to array + $this->appendToOutputArray($date); + break; + } + } + $date->modify($sign."1 day"); + } + } + } + else { + //if BYSETPOS is not set we look out for numeric values like "1MO" + + foreach ($daysArr as $day) { + if ( strlen($day) > 2 ) { + //weekday is preceeded by integer like "3TU" or "-1SU" + $number = substr($day, 0, strlen($day)-2); + $day = substr($day, -2); + + //e.g. new DateTime("sixth Tuesday of March 2012"); + $newDate = new DateTime( + $this->intToOrdinalString($number)." " + .$this->shortToLongWeekdayString($day) + .' of '.$date->format('M').' '.$date->format('Y') + ); + if ($newDate->format('M') == $date->format('M')) { + //if it didn't overshoot into next month -> set time and add it to array + $newDate->setTime($date->format('H'), $date->format('i'), $date->format('s')); + $this->appendToOutputArray($newDate); + } + } else { + //e.g. "TU" -> every Tuesday + throw new Exception("FREQ=MONTHLY in combination with BYDAY without preceeding numeric value not supported (yet?)"); + } + } + } + + //increment by FREQ + $this->recDate->setDate($this->recDate->format('Y'), $this->recDate->format('m'), 1); + $this->recDate->modify("+" . $this->rulesArr['INTERVAL'] . " month"); + } + + private function noByDefined() { + //case: no BY*** rule defined + + + $this->appendToOutputArray( $this->recDate ); + + //increment by FREQ + $freq = $this->rulesArr['FREQ']; + if ($freq == "MONTHLY") { + $this->countUpByMonthInterval(); + } else { + //count up recDate in interval set by FREQ and INTERVAL + switch ($freq) { + case "DAILY": + $this->recDate->modify( "+".$this->rulesArr['INTERVAL']." Day" ); + break; + default: //e.g. YEARLY -> YEAR + $this->recDate->modify( "+".$this->rulesArr['INTERVAL']." ".substr($freq, 0, -2) ); + } + } + } + + + + + + /* + * Helper functions + */ + + private function countUpByMonthInterval(){ + //count up recDate in interval set by MONTH + + $month = $this->recDate->format('m'); + $year = $this->recDate->format('Y'); + $originalDay = $this->recDate->format('d'); + + $date = clone $this->recDate; + $date->setDate($year, $month, 1); + + do { + $date->modify("+" . $this->rulesArr['INTERVAL'] . " months"); + } + while( ! checkdate($date->format('m'), $originalDay, $year) ); + + $this->recDate->setDate($date->format('Y'), $date->format('m'), $originalDay); + } + + private function appendToOutputArray($datetime) { + //takes a php DateTime object + + //if we were to append the object we had to use 'clone' as it would else be assigned by reference in php5 + //but ->format gives a string so that's okay + if ($datetime > $this->startDateTime && $datetime > $this->dtstartDateTime + && $this->eventsCount < $this->maxCount && $datetime <= $this->until) { + + $this->startTimesArray[] = $datetime->format($this->outputFormat); + $this->eventsCount++; + } + } + + + private function weekdayToInt($weekday) { + //takes string as 'SU' or 'Sunday' and returns 6 (range: 0-6) + switch ( strtoupper($weekday) ) { + case 'MO': + return 0; + break; + case 'TU': + return 1; + break; + case 'WE': + return 2; + break; + case 'TH': + return 3; + break; + case 'FR': + return 4; + break; + case 'SA': + return 5; + break; + case 'SU': + return 6; + break; + + case 'MONDAY': + return 0; + break; + case 'TUESDAY': + return 1; + break; + case 'WEDNESDAY': + return 2; + break; + case 'THURSDAY': + return 3; + break; + case 'FRIDAY': + return 4; + break; + case 'SATURDAY': + return 5; + break; + case 'SUNDAY': + return 6; + break; + default: + throw new Exception("Could not parse weekday: ".$weekday); + return -1; + } + } + + private function shortToLongWeekdayString($weekday) { + //takes e.g. 'MO' and returns 'Monday' + + switch ( strtoupper($weekday) ) { + case 'MO': + return "Monday"; + break; + case 'TU': + return "Tuesday"; + break; + case 'WE': + return "Wednesday"; + break; + case 'TH': + return "Thursday"; + break; + case 'FR': + return "Friday"; + break; + case 'SA': + return "Saturday"; + break; + case 'SU': + return "Sunday"; + break; + default: + throw new Exception("Could not parse weekday: ".$weekday); + } + } + + private function intToOrdinalString($int) { + //$int may be -1 or between 1 and 5 + + if ( !is_numeric($int) || $int < -1 || $int == 0 || $int > 5) + throw new Exception($int." is not in range of intToOrdinalString()"); + + if ($int == -1) + $string = "last"; + else { + $days[1] = 'first'; + $days[2] = 'second'; + $days[3] = 'third'; + $days[4] = 'fourth'; + $days[5] = 'fifth'; + + $string = $days[$int]; + } + return $string; + } +} \ No newline at end of file diff --git a/classes/modules/standardModule.php b/classes/modules/standardModule.php new file mode 100644 index 0000000..e4466c5 --- /dev/null +++ b/classes/modules/standardModule.php @@ -0,0 +1,112 @@ +sub; + + $sub->setFinalTimezone( $this->getTimezone() ); + + //delete events that are too old + foreach ($sub->eventArray as $key => $e) { + if ( strtotime($e['calDTSTART']) < strtotime($config['defaultWindowOpen']) ) { + unset($sub->eventArray[$key]); + } + } + + foreach ($sub->eventArray as &$e) { + + $e['fbName'] = mb_substr($e['calSUMMARY'], 0, $config['maxFbTitleLength']); + + $e['fbDescription'] = $e['calDESCRIPTION']; + + $e['fbLocation'] = $e['calLOCATION']; + + if( isset($e['calDTStartTZID']) ) + $e['fbStartTime'] = $this->toFbTime( $e['calDTSTART'], $e['calDTStartTZID'] ); + else + $e['fbStartTime'] = $this->toFbTime( $e['calDTSTART']); + + if( isset($e['calDTEndTZID']) ) + $e['fbEndTime'] = $this->toFbTime( $e['calDTEND'], $e['calDTEndTZID'] ); + else + $e['fbEndTime'] = $this->toFbTime( $e['calDTEND'] ); + + if ($e['fbStartTime'] == $e['fbEndTime']) { + //event must have a duration + $e['fbEndTime']++; + } + + + if ( isset($e['calCLASS']) && $e['calCLASS'] == 'PRIVATE') + $e['fbPrivacy'] = "CLOSED"; + elseif ( isset($e['calCLASS']) && $e['calCLASS'] == 'CONFIDENTIAL') + $e['fbPrivacy'] = "SECRET"; + else + $e['fbPrivacy'] = "OPEN"; + + $imageProperty = $database->getImageProperty( $sub->getSubId() ); + if ($imageProperty) { + $e['imageFileUrl'] = $e[ $imageProperty ]; + } + } + } + + + private function getTimezone() { + // sets finalTimezone to either calTZID or calXWRTIMEZONE + + if ( !is_null($this->sub->getCalTZID()) ) { + $tz = $this->sub->getCalTZID(); + } elseif ( !is_null($this->sub->getCalXWRTIMEZONE()) ) { + $tz = $this->sub->getCalXWRTIMEZONE(); + } + + if( !isset($tz) ) + $tz = 0; + + return $tz; + } + + + private function toFbTime($time, $eventTZ = null) { + /* + * because facebook is stupid the time of the event displayed is the same for each user, + * regardless of his time zone. + */ + + date_default_timezone_set('UTC'); + + $timestamp = strtotime($time); + + if ( !isset($eventTZ) && $this->sub->getFinalTimezone() ) { + //assume event time is in UTC and that the user + // wants his event time displayed in the calendar timezone (presumably + // his local timezone) and not in UTC. Thus we calculate the offset. + $calTz = new DateTimeZone($this->sub->getFinalTimezone()); + $datetime = new DateTime("@$timestamp"); + $tzOffset = timezone_offset_get($calTz, $datetime); + $timestamp += $tzOffset; + } + + //adjust for newest facebook bug.. + //adds 7 or 8 hours (depending on whether it's daylight saving time over in sunny California) to the timestamp + $californiaTz = new DateTimeZone('America/Los_Angeles'); + $datetime = new DateTime("@$timestamp"); + $tzOffset = timezone_offset_get($californiaTz, $datetime); + $timestamp -= $tzOffset; + + //return date("c", $timestamp); //output in ISO 8601, e.g. 2004-02-12T15:19:21+00:00 + return $timestamp; + } +} + +?> diff --git a/classes/qCalImporter.php b/classes/qCalImporter.php new file mode 100644 index 0000000..85bd868 --- /dev/null +++ b/classes/qCalImporter.php @@ -0,0 +1,139 @@ += $config['maxiCalFileLength']) + throw new Exception("File was too large, i.e. larger than ". floor($config['maxiCalFileLength']/1000) ." KB." ); + + if ($rawdata) { + $ical = $parser->parse($rawdata); + if (!$ical) + throw new Exception('Could not parse iCalendar file.'); + } else { + throw new Exception("Could not download file from ".$calUrl); + } + + //create subscription + $sub = new Subscription(); + + if ( isset($subId) ) + $sub->setSubId ($subId); + + /* + * put calendar properties + */ + + $prop = $ical->getProperty('X-WR-TIMEZONE'); + if( isset($prop[0]) ) + $sub->setCalXWRTIMEZONE($prop[0]->getValue()); + + $prop = $ical->getProperty('TZID'); + if( isset($prop[0]) ) + $sub->setCalTZID($prop[0]->getValue()); + + + /* + * put events + */ + + $children = $ical->getChildren(); + $events = $children['VEVENT']; + + $eventArray = array(); + foreach ($events as $e){ + $prop = $e->getProperty('DTSTART'); + $dtstart = $prop[0]->getValue(); + + $prop = $e->getProperty('DTEND'); + $dtend = $prop[0]->getValue(); + + $dtstartStamp = strtotime($dtstart); + + if ( $dtstartStamp < $startTimestamp || $dtstartStamp > $endTimestamp ) { + //if starttime of event isn't in window set, skip this event + continue; + } else { + $event = array(); + $event['calDTSTART'] = $dtstart; + $event['calDTEND'] = $dtend; + } + + $prop = $e->getProperty('UID'); + $event['calUID'] = $prop[0]->getValue(); + + $prop = $e->getProperty('LAST-MODIFIED'); + $lastModified = $prop[0]->getValue(); + $event['lastModifiedTimestamp'] = strtotime($lastModified); + + if ( isset($subId) && $database->notModified($event['lastModifiedTimestamp'], $event['calUID'], $subId) ) { + //if event has not been modified skip it + continue; + } + + //$prop = $e->getProperty('?'); + //$event['calDTStartTZID'] = $prop[0]->getValue(); + + //$prop = $e->getProperty('?'); + //$event['calDTEndTZID'] = $prop[0]->getValue(); + + $prop = $e->getProperty('SUMMARY'); + if ($prop[0]) + $event['calSUMMARY'] = $prop[0]->getValue(); + else + $event['calSUMMARY'] = "Untitled"; + + $prop = $e->getProperty('DESCRIPTION'); + if ($prop[0]) + $event['calDESCRIPTION'] = $prop[0]->getValue(); + else + $event['calDESCRIPTION'] = ""; + + $prop = $e->getProperty('LOCATION'); + if ($prop[0]) + $event['calLOCATION'] = $prop[0]->getValue(); + else + $event['calLOCATION'] = ""; + + $prop = $e->getProperty('CLASS'); + if ($prop[0]) + $event['calCLASS'] = $prop[0]->getValue(); + + //read in optional X-Properties + if ( isset($eventXProperties) ){ + foreach ($eventXProperties as $xprop) { + $prop = $e->getProperty($xprop); + if ($prop[0]) + $event[$xprop] = $prop[0]->getValue(); + } + } + + //push new event on array of events + array_push($eventArray, $event); + } + $sub->setEventArray($eventArray); + + //save subscription object in field + $this->subscription = $sub; + } + +} + +?> diff --git a/config.template.php b/config.template.php deleted file mode 100644 index 677ada5..0000000 --- a/config.template.php +++ /dev/null @@ -1,57 +0,0 @@ - "hat diese neue Veranstaltung erstellt. Kommst du auch?", - "en_GB" => "posted this new event. Are you coming too?", - "en_US" => "posted this new event. Are you coming too?", - "en_UD" => "posted this new event. Are you coming too?", - "ca_ES" => "ha publicat aquest nou esdeveniment. Veniu?" - ); - -$config['text_time'] = array( - "de_DE" => "Zeit", - "en_GB" => "Time", - "en_US" => "Time", - "en_UD" => "Time", - "ca_ES" => "Data" - ); - -?> \ No newline at end of file diff --git a/configTEMPLATE.inc.php b/configTEMPLATE.inc.php new file mode 100644 index 0000000..68f0a4d --- /dev/null +++ b/configTEMPLATE.inc.php @@ -0,0 +1,47 @@ + "CalendarToFacebook", + "dbHost" => "localhost", + "dbUser" => "", + "dbPassword" => "", + + "facebookAppId" => "", + "facebookSecret" => "", + + //e.g. /usr/bin/php + "phpCommand" => "php", + + "debugMode" => true, + "debugWithoutFacebook" => false, + + "logFile" => "log/log.txt", + "logMaxFileSize" => 100000, + "keepLogEntriesInDb" => "-2 days", + "logLevel" => "INFO", //possible values: (logs nothing) "OFF", "ERROR", "WARN", "INFO" (logs everything) + + "deactivateSubsWithLastSuccessfullPublish" => "-3 months", + + "maxiCalFileLength" => 1000000, + "maxImageFileLength" => 1500000, + + // events with DTSTART outside this window are ignored by default + "defaultWindowOpen" => "now", + "defaultWindowClose" => "+3 months", + "defaultReccurWindowOpen" => "-4 years", //recurring events won't be calculated if the DTSTART of the original event is earlier than set value + + //time in seconds to sleep before processing more events (fb limits) + "waitForNextEventPublish" => 10, + + //maximal length of event title, rest will be cropped (last time checked facebook didn't allow more than ~70) + "maxFbTitleLength" => 70, +); + +?> diff --git a/cron/cleanUpLog.php b/cron/cleanUpLog.php new file mode 100644 index 0000000..e6e2e3f --- /dev/null +++ b/cron/cleanUpLog.php @@ -0,0 +1,13 @@ +cleanUpLog(); +} catch (Exception $e) { + $logger->error("cleanUpLog.php failed", $e); +} + +?> diff --git a/cron/deactivateDeadSubs.php b/cron/deactivateDeadSubs.php new file mode 100644 index 0000000..afac0ce --- /dev/null +++ b/cron/deactivateDeadSubs.php @@ -0,0 +1,13 @@ +deactivateDeadSubs(); +} catch (Exception $e) { + $logger->error("deactivateDeadSubs.php failed", $e); +} + +?> diff --git a/cron/updateAllActiveSubs.php b/cron/updateAllActiveSubs.php new file mode 100644 index 0000000..87dbcaf --- /dev/null +++ b/cron/updateAllActiveSubs.php @@ -0,0 +1,19 @@ +selectAllActiveSubIds(); + +chdir("cron"); + +while ( $row = $subs->fetch() ) { + echo exec($config['phpCommand'] . ' updateSub.php ' . $row->subId); // . '>/dev/null 2>&1' +} + +//delete new events in db that couldn't be created on fb +$database->cleanUpNewEvents(); + +?> + diff --git a/cron/updateSub.php b/cron/updateSub.php new file mode 100644 index 0000000..db1d12f --- /dev/null +++ b/cron/updateSub.php @@ -0,0 +1,29 @@ +updateSub($subId); + +} catch (Exception $e) { + $logger->error("updateSub.php could not update subscription", $e); +} + +?> diff --git a/display_error_log.php b/display_error_log.php deleted file mode 100644 index a225c13..0000000 --- a/display_error_log.php +++ /dev/null @@ -1,73 +0,0 @@ -. -*/ - -mb_internal_encoding('UTF-8'); -date_default_timezone_set('UTC'); - -///////////////////////////////// -// CONFIGURATION -///////////////////////////////// -require_once 'config.php'; -require_once 'facebook/facebook.php'; - -$facebook = new Facebook($appapikey, $appsecret); -$user_id = $facebook->require_login(); - -//Connect to Database -$con = mysql_connect($host,$db_user,$db_password); -if (!$con) - die('Could not connect: '. mysql_error()); -mysql_query("SET NAMES 'utf8';", $con); -mysql_query("SET CHARACTER SET 'utf8';", $con); -mysql_select_db($database_name,$con); - -echo ''; -?> - -

iCalendar to Event – Help/Documentation

-
- - - - - -"; - echo ""; - echo ""; - echo ""; - echo ""; - echo ""; - } -?> - -

Subscription Name

sub_idGroup/PageLast Error logged
".$row['sub_name']."".$row['sub_id']."". $row['page_id']."". $row['error_log']."
- -

-

Common errors are:

-
  • -
      Permissions error. Facebook is blocking you from creating/editing events. This is usually temporarily and may have one of several reasons: You have added/modified too many events too fast, or you entered a page/group id and don't have permission to publish events on that page (that is you're not an administrator), or because facebook isn't functioning correctly.
    -
      Error when parsing file. Check whether your URL is still pointing to a correct iCalendar file.
    -
      Could not cancel event. Does the event still exist? Or maybe also a permission problem.
    -
      Session key. Have you given this app permission to publish events? In the worst case, try removing and re-adding this app.
    -
  • -
    - diff --git a/facebook/facebook.php b/facebook/facebook.php deleted file mode 100644 index 76696c1..0000000 --- a/facebook/facebook.php +++ /dev/null @@ -1,609 +0,0 @@ -api_key = $api_key; - $this->secret = $secret; - $this->generate_session_secret = $generate_session_secret; - $this->api_client = new FacebookRestClient($api_key, $secret, null); - $this->validate_fb_params(); - - // Set the default user id for methods that allow the caller to - // pass an explicit uid instead of using a session key. - $defaultUser = null; - if ($this->user) { - $defaultUser = $this->user; - } else if ($this->profile_user) { - $defaultUser = $this->profile_user; - } else if ($this->canvas_user) { - $defaultUser = $this->canvas_user; - } - - $this->api_client->set_user($defaultUser); - - - if (isset($this->fb_params['friends'])) { - $this->api_client->friends_list = - array_filter(explode(',', $this->fb_params['friends'])); - } - if (isset($this->fb_params['added'])) { - $this->api_client->added = $this->fb_params['added']; - } - if (isset($this->fb_params['canvas_user'])) { - $this->api_client->canvas_user = $this->fb_params['canvas_user']; - } - } - - /* - * Validates that the parameters passed in were sent from Facebook. It does so - * by validating that the signature matches one that could only be generated - * by using your application's secret key. - * - * Facebook-provided parameters will come from $_POST, $_GET, or $_COOKIE, - * in that order. $_POST and $_GET are always more up-to-date than cookies, - * so we prefer those if they are available. - * - * For nitty-gritty details of when each of these is used, check out - * http://wiki.developers.facebook.com/index.php/Verifying_The_Signature - */ - public function validate_fb_params() { - $this->fb_params = $this->get_valid_fb_params($_POST, 48 * 3600, 'fb_sig'); - - // note that with preload FQL, it's possible to receive POST params in - // addition to GET, so use a different prefix to differentiate them - if (!$this->fb_params) { - $fb_params = $this->get_valid_fb_params($_GET, 48 * 3600, 'fb_sig'); - $fb_post_params = $this->get_valid_fb_params($_POST, - 48 * 3600, // 48 hours - 'fb_post_sig'); - $this->fb_params = array_merge($fb_params, $fb_post_params); - } - - // Okay, something came in via POST or GET - if ($this->fb_params) { - $user = isset($this->fb_params['user']) ? - $this->fb_params['user'] : null; - $this->profile_user = isset($this->fb_params['profile_user']) ? - $this->fb_params['profile_user'] : null; - $this->canvas_user = isset($this->fb_params['canvas_user']) ? - $this->fb_params['canvas_user'] : null; - $this->base_domain = isset($this->fb_params['base_domain']) ? - $this->fb_params['base_domain'] : null; - $this->ext_perms = isset($this->fb_params['ext_perms']) ? - explode(',', $this->fb_params['ext_perms']) - : array(); - - if (isset($this->fb_params['session_key'])) { - $session_key = $this->fb_params['session_key']; - } else if (isset($this->fb_params['profile_session_key'])) { - $session_key = $this->fb_params['profile_session_key']; - } else { - $session_key = null; - } - $expires = isset($this->fb_params['expires']) ? - $this->fb_params['expires'] : null; - $this->set_user($user, - $session_key, - $expires); - } else if ($cookies = - $this->get_valid_fb_params($_COOKIE, null, $this->api_key)) { - // if no Facebook parameters were found in the GET or POST variables, - // then fall back to cookies, which may have cached user information - // Cookies are also used to receive session data via the Javascript API - $base_domain_cookie = 'base_domain_' . $this->api_key; - if (isset($_COOKIE[$base_domain_cookie])) { - $this->base_domain = $_COOKIE[$base_domain_cookie]; - } - - // use $api_key . '_' as a prefix for the cookies in case there are - // multiple facebook clients on the same domain. - $expires = isset($cookies['expires']) ? $cookies['expires'] : null; - $this->set_user($cookies['user'], - $cookies['session_key'], - $expires); - } - - return !empty($this->fb_params); - } - - // Store a temporary session secret for the current session - // for use with the JS client library - public function promote_session() { - try { - $session_secret = $this->api_client->auth_promoteSession(); - if (!$this->in_fb_canvas()) { - $this->set_cookies($this->user, $this->api_client->session_key, $this->session_expires, $session_secret); - } - return $session_secret; - } catch (FacebookRestClientException $e) { - // API_EC_PARAM means we don't have a logged in user, otherwise who - // knows what it means, so just throw it. - if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) { - throw $e; - } - } - } - - public function do_get_session($auth_token) { - try { - return $this->api_client->auth_getSession($auth_token, $this->generate_session_secret); - } catch (FacebookRestClientException $e) { - // API_EC_PARAM means we don't have a logged in user, otherwise who - // knows what it means, so just throw it. - if ($e->getCode() != FacebookAPIErrorCodes::API_EC_PARAM) { - throw $e; - } - } - } - - // Invalidate the session currently being used, and clear any state associated - // with it. Note that the user will still remain logged into Facebook. - public function expire_session() { - try { - if ($this->api_client->auth_expireSession()) { - $this->clear_cookie_state(); - return true; - } else { - return false; - } - } catch (Exception $e) { - $this->clear_cookie_state(); - } - } - - /** Logs the user out of all temporary application sessions as well as their - * Facebook session. Note this will only work if the user has a valid current - * session with the application. - * - * @param string $next URL to redirect to upon logging out - * - */ - public function logout($next) { - $logout_url = $this->get_logout_url($next); - - // Clear any stored state - $this->clear_cookie_state(); - - $this->redirect($logout_url); - } - - /** - * Clears any persistent state stored about the user, including - * cookies and information related to the current session in the - * client. - * - */ - public function clear_cookie_state() { - if (!$this->in_fb_canvas() && isset($_COOKIE[$this->api_key . '_user'])) { - $cookies = array('user', 'session_key', 'expires', 'ss'); - foreach ($cookies as $name) { - setcookie($this->api_key . '_' . $name, - false, - time() - 3600, - '', - $this->base_domain); - unset($_COOKIE[$this->api_key . '_' . $name]); - } - setcookie($this->api_key, false, time() - 3600, '', $this->base_domain); - unset($_COOKIE[$this->api_key]); - } - - // now, clear the rest of the stored state - $this->user = 0; - $this->api_client->session_key = 0; - } - - public function redirect($url) { - if ($this->in_fb_canvas()) { - echo ''; - } else if (preg_match('/^https?:\/\/([^\/]*\.)?facebook\.com(:\d+)?/i', $url)) { - // make sure facebook.com url's load in the full frame so that we don't - // get a frame within a frame. - echo ""; - } else { - header('Location: ' . $url); - } - exit; - } - - public function in_frame() { - return isset($this->fb_params['in_canvas']) - || isset($this->fb_params['in_iframe']); - } - public function in_fb_canvas() { - return isset($this->fb_params['in_canvas']); - } - - public function get_loggedin_user() { - return $this->user; - } - - public function get_canvas_user() { - return $this->canvas_user; - } - - public function get_profile_user() { - return $this->profile_user; - } - - public static function current_url() { - return 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } - - // require_add and require_install have been removed. - // see http://developer.facebook.com/news.php?blog=1&story=116 for more details - public function require_login($required_permissions = '') { - $user = $this->get_loggedin_user(); - $has_permissions = true; - - if ($required_permissions) { - $this->require_frame(); - $permissions = array_map('trim', explode(',', $required_permissions)); - foreach ($permissions as $permission) { - if (!in_array($permission, $this->ext_perms)) { - $has_permissions = false; - break; - } - } - } - - if ($user && $has_permissions) { - return $user; - } - - $this->redirect( - $this->get_login_url(self::current_url(), $this->in_frame(), - $required_permissions)); - } - - public function require_frame() { - if (!$this->in_frame()) { - $this->redirect($this->get_login_url(self::current_url(), true)); - } - } - - public static function get_facebook_url($subdomain='www') { - return 'http://' . $subdomain . '.facebook.com'; - } - - public function get_install_url($next=null) { - // this was renamed, keeping for compatibility's sake - return $this->get_add_url($next); - } - - public function get_add_url($next=null) { - $page = self::get_facebook_url().'/add.php'; - $params = array('api_key' => $this->api_key); - - if ($next) { - $params['next'] = $next; - } - - return $page . '?' . http_build_query($params); - } - - public function get_login_url($next, $canvas, $req_perms = '') { - $page = self::get_facebook_url().'/login.php'; - $params = array('api_key' => $this->api_key, - 'v' => '1.0', - 'req_perms' => $req_perms); - - if ($next) { - $params['next'] = $next; - } - if ($canvas) { - $params['canvas'] = '1'; - } - - return $page . '?' . http_build_query($params); - } - - public function get_logout_url($next) { - $page = self::get_facebook_url().'/logout.php'; - $params = array('app_key' => $this->api_key, - 'session_key' => $this->api_client->session_key); - - if ($next) { - $params['connect_next'] = 1; - $params['next'] = $next; - } - - return $page . '?' . http_build_query($params); - } - - public function set_user($user, $session_key, $expires=null, $session_secret=null) { - if (!$this->in_fb_canvas() && (!isset($_COOKIE[$this->api_key . '_user']) - || $_COOKIE[$this->api_key . '_user'] != $user)) { - $this->set_cookies($user, $session_key, $expires, $session_secret); - } - $this->user = $user; - $this->api_client->session_key = $session_key; - $this->session_expires = $expires; - } - - public function set_cookies($user, $session_key, $expires=null, $session_secret=null) { - $cookies = array(); - $cookies['user'] = $user; - $cookies['session_key'] = $session_key; - if ($expires != null) { - $cookies['expires'] = $expires; - } - if ($session_secret != null) { - $cookies['ss'] = $session_secret; - } - - foreach ($cookies as $name => $val) { - setcookie($this->api_key . '_' . $name, $val, (int)$expires, '', $this->base_domain); - $_COOKIE[$this->api_key . '_' . $name] = $val; - } - $sig = self::generate_sig($cookies, $this->secret); - setcookie($this->api_key, $sig, (int)$expires, '', $this->base_domain); - $_COOKIE[$this->api_key] = $sig; - - if ($this->base_domain != null) { - $base_domain_cookie = 'base_domain_' . $this->api_key; - setcookie($base_domain_cookie, $this->base_domain, (int)$expires, '', $this->base_domain); - $_COOKIE[$base_domain_cookie] = $this->base_domain; - } - } - - /** - * Tries to undo the badness of magic quotes as best we can - * @param string $val Should come directly from $_GET, $_POST, etc. - * @return string val without added slashes - */ - public static function no_magic_quotes($val) { - if (get_magic_quotes_gpc()) { - return stripslashes($val); - } else { - return $val; - } - } - - /* - * Get the signed parameters that were sent from Facebook. Validates the set - * of parameters against the included signature. - * - * Since Facebook sends data to your callback URL via unsecured means, the - * signature is the only way to make sure that the data actually came from - * Facebook. So if an app receives a request at the callback URL, it should - * always verify the signature that comes with against your own secret key. - * Otherwise, it's possible for someone to spoof a request by - * pretending to be someone else, i.e.: - * www.your-callback-url.com/?fb_user=10101 - * - * This is done automatically by verify_fb_params. - * - * @param assoc $params a full array of external parameters. - * presumed $_GET, $_POST, or $_COOKIE - * @param int $timeout number of seconds that the args are good for. - * Specifically good for forcing cookies to expire. - * @param string $namespace prefix string for the set of parameters we want - * to verify. i.e., fb_sig or fb_post_sig - * - * @return assoc the subset of parameters containing the given prefix, - * and also matching the signature associated with them. - * OR an empty array if the params do not validate - */ - public function get_valid_fb_params($params, $timeout=null, $namespace='fb_sig') { - $prefix = $namespace . '_'; - $prefix_len = strlen($prefix); - $fb_params = array(); - if (empty($params)) { - return array(); - } - - foreach ($params as $name => $val) { - // pull out only those parameters that match the prefix - // note that the signature itself ($params[$namespace]) is not in the list - if (strpos($name, $prefix) === 0) { - $fb_params[substr($name, $prefix_len)] = self::no_magic_quotes($val); - } - } - - // validate that the request hasn't expired. this is most likely - // for params that come from $_COOKIE - if ($timeout && (!isset($fb_params['time']) || time() - $fb_params['time'] > $timeout)) { - return array(); - } - - // validate that the params match the signature - $signature = isset($params[$namespace]) ? $params[$namespace] : null; - if (!$signature || (!$this->verify_signature($fb_params, $signature))) { - return array(); - } - return $fb_params; - } - - /** - * Validates the account that a user was trying to set up an - * independent account through Facebook Connect. - * - * @param user The user attempting to set up an independent account. - * @param hash The hash passed to the reclamation URL used. - * @return bool True if the user is the one that selected the - * reclamation link. - */ - public function verify_account_reclamation($user, $hash) { - return $hash == md5($user . $this->secret); - } - - /** - * Validates that a given set of parameters match their signature. - * Parameters all match a given input prefix, such as "fb_sig". - * - * @param $fb_params an array of all Facebook-sent parameters, - * not including the signature itself - * @param $expected_sig the expected result to check against - */ - public function verify_signature($fb_params, $expected_sig) { - return self::generate_sig($fb_params, $this->secret) == $expected_sig; - } - - /** - * Validate the given signed public session data structure with - * public key of the app that - * the session proof belongs to. - * - * @param $signed_data the session info that is passed by another app - * @param string $public_key Optional public key of the app. If this - * is not passed, function will make an API call to get it. - * return true if the session proof passed verification. - */ - public function verify_signed_public_session_data($signed_data, - $public_key = null) { - - // If public key is not already provided, we need to get it through API - if (!$public_key) { - $public_key = $this->api_client->auth_getAppPublicKey( - $signed_data['api_key']); - } - - // Create data to verify - $data_to_serialize = $signed_data; - unset($data_to_serialize['sig']); - $serialized_data = implode('_', $data_to_serialize); - - // Decode signature - $signature = base64_decode($signed_data['sig']); - $result = openssl_verify($serialized_data, $signature, $public_key, - OPENSSL_ALGO_SHA1); - return $result == 1; - } - - /* - * Generate a signature using the application secret key. - * - * The only two entities that know your secret key are you and Facebook, - * according to the Terms of Service. Since nobody else can generate - * the signature, you can rely on it to verify that the information - * came from Facebook. - * - * @param $params_array an array of all Facebook-sent parameters, - * NOT INCLUDING the signature itself - * @param $secret your app's secret key - * - * @return a hash to be checked against the signature provided by Facebook - */ - public static function generate_sig($params_array, $secret) { - $str = ''; - - ksort($params_array); - // Note: make sure that the signature parameter is not already included in - // $params_array. - foreach ($params_array as $k=>$v) { - $str .= "$k=$v"; - } - $str .= $secret; - - return md5($str); - } - - public function encode_validationError($summary, $message) { - return json_encode( - array('errorCode' => FACEBOOK_API_VALIDATION_ERROR, - 'errorTitle' => $summary, - 'errorMessage' => $message)); - } - - public function encode_multiFeedStory($feed, $next) { - return json_encode( - array('method' => 'multiFeedStory', - 'content' => - array('next' => $next, - 'feed' => $feed))); - } - - public function encode_feedStory($feed, $next) { - return json_encode( - array('method' => 'feedStory', - 'content' => - array('next' => $next, - 'feed' => $feed))); - } - - public function create_templatizedFeedStory($title_template, $title_data=array(), - $body_template='', $body_data = array(), $body_general=null, - $image_1=null, $image_1_link=null, - $image_2=null, $image_2_link=null, - $image_3=null, $image_3_link=null, - $image_4=null, $image_4_link=null) { - return array('title_template'=> $title_template, - 'title_data' => $title_data, - 'body_template'=> $body_template, - 'body_data' => $body_data, - 'body_general' => $body_general, - 'image_1' => $image_1, - 'image_1_link' => $image_1_link, - 'image_2' => $image_2, - 'image_2_link' => $image_2_link, - 'image_3' => $image_3, - 'image_3_link' => $image_3_link, - 'image_4' => $image_4, - 'image_4_link' => $image_4_link); - } - - -} - diff --git a/facebook/facebookapi_php5_restlib.php b/facebook/facebookapi_php5_restlib.php deleted file mode 100755 index e249a32..0000000 --- a/facebook/facebookapi_php5_restlib.php +++ /dev/null @@ -1,3702 +0,0 @@ -secret = $secret; - $this->session_key = $session_key; - $this->api_key = $api_key; - $this->batch_mode = FacebookRestClient::BATCH_MODE_DEFAULT; - $this->last_call_id = 0; - $this->call_as_apikey = ''; - $this->use_curl_if_available = true; - $this->server_addr = - Facebook::get_facebook_url('api') . '/restserver.php'; - $this->photo_server_addr = - Facebook::get_facebook_url('api-photo') . '/restserver.php'; - - if (!empty($GLOBALS['facebook_config']['debug'])) { - $this->cur_id = 0; - ?> - -user = $uid; - } - - - /** - * Switch to use the session secret instead of the app secret, - * for desktop and unsecured environment - */ - public function use_session_secret($session_secret) { - $this->secret = $session_secret; - $this->using_session_secret = true; - } - - /** - * Normally, if the cURL library/PHP extension is available, it is used for - * HTTP transactions. This allows that behavior to be overridden, falling - * back to a vanilla-PHP implementation even if cURL is installed. - * - * @param $use_curl_if_available bool whether or not to use cURL if available - */ - public function set_use_curl_if_available($use_curl_if_available) { - $this->use_curl_if_available = $use_curl_if_available; - } - - /** - * Start a batch operation. - */ - public function begin_batch() { - if ($this->pending_batch()) { - $code = FacebookAPIErrorCodes::API_EC_BATCH_ALREADY_STARTED; - $description = FacebookAPIErrorCodes::$api_error_descriptions[$code]; - throw new FacebookRestClientException($description, $code); - } - - $this->batch_queue = array(); - $this->pending_batch = true; - } - - /* - * End current batch operation - */ - public function end_batch() { - if (!$this->pending_batch()) { - $code = FacebookAPIErrorCodes::API_EC_BATCH_NOT_STARTED; - $description = FacebookAPIErrorCodes::$api_error_descriptions[$code]; - throw new FacebookRestClientException($description, $code); - } - - $this->pending_batch = false; - - $this->execute_server_side_batch(); - $this->batch_queue = null; - } - - /** - * are we currently queueing up calls for a batch? - */ - public function pending_batch() { - return $this->pending_batch; - } - - private function execute_server_side_batch() { - $item_count = count($this->batch_queue); - $method_feed = array(); - foreach ($this->batch_queue as $batch_item) { - $method = $batch_item['m']; - $params = $batch_item['p']; - list($get, $post) = $this->finalize_params($method, $params); - $method_feed[] = $this->create_url_string(array_merge($post, $get)); - } - - $serial_only = - ($this->batch_mode == FacebookRestClient::BATCH_MODE_SERIAL_ONLY); - - $params = array('method_feed' => json_encode($method_feed), - 'serial_only' => $serial_only, - 'format' => $this->format); - $result = $this->call_method('facebook.batch.run', $params); - - if (is_array($result) && isset($result['error_code'])) { - throw new FacebookRestClientException($result['error_msg'], - $result['error_code']); - } - - for ($i = 0; $i < $item_count; $i++) { - $batch_item = $this->batch_queue[$i]; - $batch_item['p']['format'] = $this->format; - $batch_item_result = $this->convert_result($result[$i], - $batch_item['m'], - $batch_item['p']); - - if (is_array($batch_item_result) && - isset($batch_item_result['error_code'])) { - throw new FacebookRestClientException($batch_item_result['error_msg'], - $batch_item_result['error_code']); - } - $batch_item['r'] = $batch_item_result; - } - } - - public function begin_permissions_mode($permissions_apikey) { - $this->call_as_apikey = $permissions_apikey; - } - - public function end_permissions_mode() { - $this->call_as_apikey = ''; - } - - - /* - * If a page is loaded via HTTPS, then all images and static - * resources need to be printed with HTTPS urls to avoid - * mixed content warnings. If your page loads with an HTTPS - * url, then call set_use_ssl_resources to retrieve the correct - * urls. - */ - public function set_use_ssl_resources($is_ssl = true) { - $this->use_ssl_resources = $is_ssl; - } - - /** - * Returns public information for an application (as shown in the application - * directory) by either application ID, API key, or canvas page name. - * - * @param int $application_id (Optional) app id - * @param string $application_api_key (Optional) api key - * @param string $application_canvas_name (Optional) canvas name - * - * Exactly one argument must be specified, otherwise it is an error. - * - * @return array An array of public information about the application. - */ - public function application_getPublicInfo($application_id=null, - $application_api_key=null, - $application_canvas_name=null) { - return $this->call_method('facebook.application.getPublicInfo', - array('application_id' => $application_id, - 'application_api_key' => $application_api_key, - 'application_canvas_name' => $application_canvas_name)); - } - - /** - * Creates an authentication token to be used as part of the desktop login - * flow. For more information, please see - * http://wiki.developers.facebook.com/index.php/Auth.createToken. - * - * @return string An authentication token. - */ - public function auth_createToken() { - return $this->call_method('facebook.auth.createToken'); - } - - /** - * Returns the session information available after current user logs in. - * - * @param string $auth_token the token returned by auth_createToken or - * passed back to your callback_url. - * @param bool $generate_session_secret whether the session returned should - * include a session secret - * @param string $host_url the connect site URL for which the session is - * being generated. This parameter is optional, unless - * you want Facebook to determine which of several base domains - * to choose from. If this third argument isn't provided but - * there are several base domains, the first base domain is - * chosen. - * - * @return array An assoc array containing session_key, uid - */ - public function auth_getSession($auth_token, - $generate_session_secret = false, - $host_url = null) { - if (!$this->pending_batch()) { - $result = $this->call_method( - 'facebook.auth.getSession', - array('auth_token' => $auth_token, - 'generate_session_secret' => $generate_session_secret, - 'host_url' => $host_url)); - $this->session_key = $result['session_key']; - - if (!empty($result['secret']) && !$generate_session_secret) { - // desktop apps have a special secret - $this->secret = $result['secret']; - } - - return $result; - } - } - - /** - * Generates a session-specific secret. This is for integration with - * client-side API calls, such as the JS library. - * - * @return array A session secret for the current promoted session - * - * @error API_EC_PARAM_SESSION_KEY - * API_EC_PARAM_UNKNOWN - */ - public function auth_promoteSession() { - return $this->call_method('facebook.auth.promoteSession'); - } - - /** - * Expires the session that is currently being used. If this call is - * successful, no further calls to the API (which require a session) can be - * made until a valid session is created. - * - * @return bool true if session expiration was successful, false otherwise - */ - public function auth_expireSession() { - return $this->call_method('facebook.auth.expireSession'); - } - - /** - * Revokes the given extended permission that the user granted at some - * prior time (for instance, offline_access or email). If no user is - * provided, it will be revoked for the user of the current session. - * - * @param string $perm The permission to revoke - * @param int $uid The user for whom to revoke the permission. - */ - public function auth_revokeExtendedPermission($perm, $uid=null) { - return $this->call_method('facebook.auth.revokeExtendedPermission', - array('perm' => $perm, 'uid' => $uid)); - } - - /** - * Revokes the user's agreement to the Facebook Terms of Service for your - * application. If you call this method for one of your users, you will no - * longer be able to make API requests on their behalf until they again - * authorize your application. Use with care. Note that if this method is - * called without a user parameter, then it will revoke access for the - * current session's user. - * - * @param int $uid (Optional) User to revoke - * - * @return bool true if revocation succeeds, false otherwise - */ - public function auth_revokeAuthorization($uid=null) { - return $this->call_method('facebook.auth.revokeAuthorization', - array('uid' => $uid)); - } - - /** - * Get public key that is needed to verify digital signature - * an app may pass to other apps. The public key is only used by - * other apps for verification purposes. - * @param string API key of an app - * @return string The public key for the app. - */ - public function auth_getAppPublicKey($target_app_key) { - return $this->call_method('facebook.auth.getAppPublicKey', - array('target_app_key' => $target_app_key)); - } - - /** - * Get a structure that can be passed to another app - * as proof of session. The other app can verify it using public - * key of this app. - * - * @return signed public session data structure. - */ - public function auth_getSignedPublicSessionData() { - return $this->call_method('facebook.auth.getSignedPublicSessionData', - array()); - } - - /** - * Returns the number of unconnected friends that exist in this application. - * This number is determined based on the accounts registered through - * connect.registerUsers() (see below). - */ - public function connect_getUnconnectedFriendsCount() { - return $this->call_method('facebook.connect.getUnconnectedFriendsCount', - array()); - } - - /** - * This method is used to create an association between an external user - * account and a Facebook user account, as per Facebook Connect. - * - * This method takes an array of account data, including a required email_hash - * and optional account data. For each connected account, if the user exists, - * the information is added to the set of the user's connected accounts. - * If the user has already authorized the site, the connected account is added - * in the confirmed state. If the user has not yet authorized the site, the - * connected account is added in the pending state. - * - * This is designed to help Facebook Connect recognize when two Facebook - * friends are both members of a external site, but perhaps are not aware of - * it. The Connect dialog (see fb:connect-form) is used when friends can be - * identified through these email hashes. See the following url for details: - * - * http://wiki.developers.facebook.com/index.php/Connect.registerUsers - * - * @param mixed $accounts A (JSON-encoded) array of arrays, where each array - * has three properties: - * 'email_hash' (req) - public email hash of account - * 'account_id' (opt) - remote account id; - * 'account_url' (opt) - url to remote account; - * - * @return array The list of email hashes for the successfully registered - * accounts. - */ - public function connect_registerUsers($accounts) { - return $this->call_method('facebook.connect.registerUsers', - array('accounts' => $accounts)); - } - - /** - * Unregisters a set of accounts registered using connect.registerUsers. - * - * @param array $email_hashes The (JSON-encoded) list of email hashes to be - * unregistered. - * - * @return array The list of email hashes which have been successfully - * unregistered. - */ - public function connect_unregisterUsers($email_hashes) { - return $this->call_method('facebook.connect.unregisterUsers', - array('email_hashes' => $email_hashes)); - } - - /** - * Returns events according to the filters specified. - * - * @param int $uid (Optional) User associated with events. A null - * parameter will default to the session user. - * @param array/string $eids (Optional) Filter by these event - * ids. A null parameter will get all events for - * the user. (A csv list will work but is deprecated) - * @param int $start_time (Optional) Filter with this unix time as lower - * bound. A null or zero parameter indicates no - * lower bound. - * @param int $end_time (Optional) Filter with this UTC as upper bound. - * A null or zero parameter indicates no upper - * bound. - * @param string $rsvp_status (Optional) Only show events where the given uid - * has this rsvp status. This only works if you - * have specified a value for $uid. Values are as - * in events.getMembers. Null indicates to ignore - * rsvp status when filtering. - * - * @return array The events matching the query. - */ - public function &events_get($uid=null, - $eids=null, - $start_time=null, - $end_time=null, - $rsvp_status=null) { - return $this->call_method('facebook.events.get', - array('uid' => $uid, - 'eids' => $eids, - 'start_time' => $start_time, - 'end_time' => $end_time, - 'rsvp_status' => $rsvp_status)); - } - - /** - * Returns membership list data associated with an event. - * - * @param int $eid event id - * - * @return array An assoc array of four membership lists, with keys - * 'attending', 'unsure', 'declined', and 'not_replied' - */ - public function &events_getMembers($eid) { - return $this->call_method('facebook.events.getMembers', - array('eid' => $eid)); - } - - /** - * RSVPs the current user to this event. - * - * @param int $eid event id - * @param string $rsvp_status 'attending', 'unsure', or 'declined' - * - * @return bool true if successful - */ - public function &events_rsvp($eid, $rsvp_status) { - return $this->call_method('facebook.events.rsvp', - array( - 'eid' => $eid, - 'rsvp_status' => $rsvp_status)); - } - - /** - * Cancels an event. Only works for events where application is the admin. - * - * @param int $eid event id - * @param string $cancel_message (Optional) message to send to members of - * the event about why it is cancelled - * - * @return bool true if successful - */ - public function &events_cancel($eid, $cancel_message='') { - return $this->call_method('facebook.events.cancel', - array('eid' => $eid, - 'cancel_message' => $cancel_message)); - } - - /** - * Creates an event on behalf of the user is there is a session, otherwise on - * behalf of app. Successful creation guarantees app will be admin. - * - * @param assoc array $event_info json encoded event information - * @param string $file (Optional) filename of picture to set - * - * @return int event id - */ - public function events_create($event_info, $file = null) { - if ($file) { - return $this->call_upload_method('facebook.events.create', - array('event_info' => $event_info), - $file, - $this->photo_server_addr); - } else { - return $this->call_method('facebook.events.create', - array('event_info' => $event_info)); - } - } - - /** - * Invites users to an event. If a session user exists, the session user - * must have permissions to invite friends to the event and $uids must contain - * a list of friend ids. Otherwise, the event must have been - * created by the app and $uids must contain users of the app. - * This method requires the 'create_event' extended permission to - * invite people on behalf of a user. - * - * @param $eid the event id - * @param $uids an array of users to invite - * @param $personal_message a string containing the user's message - * (text only) - * - */ - public function events_invite($eid, $uids, $personal_message) { - return $this->call_method('facebook.events.invite', - array('eid' => $eid, - 'uids' => $uids, - 'personal_message' => $personal_message)); - } - - /** - * Edits an existing event. Only works for events where application is admin. - * - * @param int $eid event id - * @param assoc array $event_info json encoded event information - * @param string $file (Optional) filename of new picture to set - * - * @return bool true if successful - */ - public function events_edit($eid, $event_info, $file = null) { - if ($file) { - return $this->call_upload_method('facebook.events.edit', - array('eid' => $eid, 'event_info' => $event_info), - $file, - $this->photo_server_addr); - } else { - return $this->call_method('facebook.events.edit', - array('eid' => $eid, - 'event_info' => $event_info)); - } - } - - /** - * Fetches and re-caches the image stored at the given URL, for use in images - * published to non-canvas pages via the API (for example, to user profiles - * via profile.setFBML, or to News Feed via feed.publishUserAction). - * - * @param string $url The absolute URL from which to refresh the image. - * - * @return bool true on success - */ - public function &fbml_refreshImgSrc($url) { - return $this->call_method('facebook.fbml.refreshImgSrc', - array('url' => $url)); - } - - /** - * Fetches and re-caches the content stored at the given URL, for use in an - * fb:ref FBML tag. - * - * @param string $url The absolute URL from which to fetch content. This URL - * should be used in a fb:ref FBML tag. - * - * @return bool true on success - */ - public function &fbml_refreshRefUrl($url) { - return $this->call_method('facebook.fbml.refreshRefUrl', - array('url' => $url)); - } - - /** - * Associates a given "handle" with FBML markup so that the handle can be - * used within the fb:ref FBML tag. A handle is unique within an application - * and allows an application to publish identical FBML to many user profiles - * and do subsequent updates without having to republish FBML on behalf of - * each user. - * - * @param string $handle The handle to associate with the given FBML. - * @param string $fbml The FBML to associate with the given handle. - * - * @return bool true on success - */ - public function &fbml_setRefHandle($handle, $fbml) { - return $this->call_method('facebook.fbml.setRefHandle', - array('handle' => $handle, 'fbml' => $fbml)); - } - - /** - * Register custom tags for the application. Custom tags can be used - * to extend the set of tags available to applications in FBML - * markup. - * - * Before you call this function, - * make sure you read the full documentation at - * - * http://wiki.developers.facebook.com/index.php/Fbml.RegisterCustomTags - * - * IMPORTANT: This function overwrites the values of - * existing tags if the names match. Use this function with care because - * it may break the FBML of any application that is using the - * existing version of the tags. - * - * @param mixed $tags an array of tag objects (the full description is on the - * wiki page) - * - * @return int the number of tags that were registered - */ - public function &fbml_registerCustomTags($tags) { - $tags = json_encode($tags); - return $this->call_method('facebook.fbml.registerCustomTags', - array('tags' => $tags)); - } - - /** - * Get the custom tags for an application. If $app_id - * is not specified, the calling app's tags are returned. - * If $app_id is different from the id of the calling app, - * only the app's public tags are returned. - * The return value is an array of the same type as - * the $tags parameter of fbml_registerCustomTags(). - * - * @param int $app_id the application's id (optional) - * - * @return mixed an array containing the custom tag objects - */ - public function &fbml_getCustomTags($app_id = null) { - return $this->call_method('facebook.fbml.getCustomTags', - array('app_id' => $app_id)); - } - - - /** - * Delete custom tags the application has registered. If - * $tag_names is null, all the application's custom tags will be - * deleted. - * - * IMPORTANT: If your application has registered public tags - * that other applications may be using, don't delete those tags! - * Doing so can break the FBML ofapplications that are using them. - * - * @param array $tag_names the names of the tags to delete (optinal) - * @return bool true on success - */ - public function &fbml_deleteCustomTags($tag_names = null) { - return $this->call_method('facebook.fbml.deleteCustomTags', - array('tag_names' => json_encode($tag_names))); - } - - /** - * Gets the best translations for native strings submitted by an application - * for translation. If $locale is not specified, only native strings and their - * descriptions are returned. If $all is true, then unapproved translations - * are returned as well, otherwise only approved translations are returned. - * - * A mapping of locale codes -> language names is available at - * http://wiki.developers.facebook.com/index.php/Facebook_Locales - * - * @param string $locale the locale to get translations for, or 'all' for all - * locales, or 'en_US' for native strings - * @param bool $all whether to return all or only approved translations - * - * @return array (locale, array(native_strings, array('best translation - * available given enough votes or manual approval', approval - * status))) - * @error API_EC_PARAM - * @error API_EC_PARAM_BAD_LOCALE - */ - public function &intl_getTranslations($locale = 'en_US', $all = false) { - return $this->call_method('facebook.intl.getTranslations', - array('locale' => $locale, - 'all' => $all)); - } - - /** - * Lets you insert text strings in their native language into the Facebook - * Translations database so they can be translated. - * - * @param array $native_strings An array of maps, where each map has a 'text' - * field and a 'description' field. - * - * @return int Number of strings uploaded. - */ - public function &intl_uploadNativeStrings($native_strings) { - return $this->call_method('facebook.intl.uploadNativeStrings', - array('native_strings' => json_encode($native_strings))); - } - - /** - * This method is deprecated for calls made on behalf of users. This method - * works only for publishing stories on a Facebook Page that has installed - * your application. To publish stories to a user's profile, use - * feed.publishUserAction instead. - * - * For more details on this call, please visit the wiki page: - * - * http://wiki.developers.facebook.com/index.php/Feed.publishTemplatizedAction - */ - public function &feed_publishTemplatizedAction($title_template, - $title_data, - $body_template, - $body_data, - $body_general, - $image_1=null, - $image_1_link=null, - $image_2=null, - $image_2_link=null, - $image_3=null, - $image_3_link=null, - $image_4=null, - $image_4_link=null, - $target_ids='', - $page_actor_id=null) { - return $this->call_method('facebook.feed.publishTemplatizedAction', - array('title_template' => $title_template, - 'title_data' => $title_data, - 'body_template' => $body_template, - 'body_data' => $body_data, - 'body_general' => $body_general, - 'image_1' => $image_1, - 'image_1_link' => $image_1_link, - 'image_2' => $image_2, - 'image_2_link' => $image_2_link, - 'image_3' => $image_3, - 'image_3_link' => $image_3_link, - 'image_4' => $image_4, - 'image_4_link' => $image_4_link, - 'target_ids' => $target_ids, - 'page_actor_id' => $page_actor_id)); - } - - /** - * Registers a template bundle. Template bundles are somewhat involved, so - * it's recommended you check out the wiki for more details: - * - * http://wiki.developers.facebook.com/index.php/Feed.registerTemplateBundle - * - * @return string A template bundle id - */ - public function &feed_registerTemplateBundle($one_line_story_templates, - $short_story_templates = array(), - $full_story_template = null, - $action_links = array()) { - - $one_line_story_templates = json_encode($one_line_story_templates); - - if (!empty($short_story_templates)) { - $short_story_templates = json_encode($short_story_templates); - } - - if (isset($full_story_template)) { - $full_story_template = json_encode($full_story_template); - } - - if (isset($action_links)) { - $action_links = json_encode($action_links); - } - - return $this->call_method('facebook.feed.registerTemplateBundle', - array('one_line_story_templates' => $one_line_story_templates, - 'short_story_templates' => $short_story_templates, - 'full_story_template' => $full_story_template, - 'action_links' => $action_links)); - } - - /** - * Retrieves the full list of active template bundles registered by the - * requesting application. - * - * @return array An array of template bundles - */ - public function &feed_getRegisteredTemplateBundles() { - return $this->call_method('facebook.feed.getRegisteredTemplateBundles', - array()); - } - - /** - * Retrieves information about a specified template bundle previously - * registered by the requesting application. - * - * @param string $template_bundle_id The template bundle id - * - * @return array Template bundle - */ - public function &feed_getRegisteredTemplateBundleByID($template_bundle_id) { - return $this->call_method('facebook.feed.getRegisteredTemplateBundleByID', - array('template_bundle_id' => $template_bundle_id)); - } - - /** - * Deactivates a previously registered template bundle. - * - * @param string $template_bundle_id The template bundle id - * - * @return bool true on success - */ - public function &feed_deactivateTemplateBundleByID($template_bundle_id) { - return $this->call_method('facebook.feed.deactivateTemplateBundleByID', - array('template_bundle_id' => $template_bundle_id)); - } - - const STORY_SIZE_ONE_LINE = 1; - const STORY_SIZE_SHORT = 2; - const STORY_SIZE_FULL = 4; - - /** - * Publishes a story on behalf of the user owning the session, using the - * specified template bundle. This method requires an active session key in - * order to be called. - * - * The parameters to this method ($templata_data in particular) are somewhat - * involved. It's recommended you visit the wiki for details: - * - * http://wiki.developers.facebook.com/index.php/Feed.publishUserAction - * - * @param int $template_bundle_id A template bundle id previously registered - * @param array $template_data See wiki article for syntax - * @param array $target_ids (Optional) An array of friend uids of the - * user who shared in this action. - * @param string $body_general (Optional) Additional markup that extends - * the body of a short story. - * @param int $story_size (Optional) A story size (see above) - * @param string $user_message (Optional) A user message for a short - * story. - * - * @return bool true on success - */ - public function &feed_publishUserAction( - $template_bundle_id, $template_data, $target_ids='', $body_general='', - $story_size=FacebookRestClient::STORY_SIZE_ONE_LINE, - $user_message='') { - - if (is_array($template_data)) { - $template_data = json_encode($template_data); - } // allow client to either pass in JSON or an assoc that we JSON for them - - if (is_array($target_ids)) { - $target_ids = json_encode($target_ids); - $target_ids = trim($target_ids, "[]"); // we don't want square brackets - } - - return $this->call_method('facebook.feed.publishUserAction', - array('template_bundle_id' => $template_bundle_id, - 'template_data' => $template_data, - 'target_ids' => $target_ids, - 'body_general' => $body_general, - 'story_size' => $story_size, - 'user_message' => $user_message)); - } - - - /** - * Publish a post to the user's stream. - * - * @param $message the user's message - * @param $attachment the post's attachment (optional) - * @param $action links the post's action links (optional) - * @param $target_id the user on whose wall the post will be posted - * (optional) - * @param $uid the actor (defaults to session user) - * @return string the post id - */ - public function stream_publish( - $message, $attachment = null, $action_links = null, $target_id = null, - $uid = null) { - - return $this->call_method( - 'facebook.stream.publish', - array('message' => $message, - 'attachment' => $attachment, - 'action_links' => $action_links, - 'target_id' => $target_id, - 'uid' => $this->get_uid($uid))); - } - - /** - * Remove a post from the user's stream. - * Currently, you may only remove stories you application created. - * - * @param $post_id the post id - * @param $uid the actor (defaults to session user) - * @return bool - */ - public function stream_remove($post_id, $uid = null) { - return $this->call_method( - 'facebook.stream.remove', - array('post_id' => $post_id, - 'uid' => $this->get_uid($uid))); - } - - /** - * Add a comment to a stream post - * - * @param $post_id the post id - * @param $comment the comment text - * @param $uid the actor (defaults to session user) - * @return string the id of the created comment - */ - public function stream_addComment($post_id, $comment, $uid = null) { - return $this->call_method( - 'facebook.stream.addComment', - array('post_id' => $post_id, - 'comment' => $comment, - 'uid' => $this->get_uid($uid))); - } - - - /** - * Remove a comment from a stream post - * - * @param $comment_id the comment id - * @param $uid the actor (defaults to session user) - * @return bool - */ - public function stream_removeComment($comment_id, $uid = null) { - return $this->call_method( - 'facebook.stream.removeComment', - array('comment_id' => $comment_id, - 'uid' => $this->get_uid($uid))); - } - - /** - * Add a like to a stream post - * - * @param $post_id the post id - * @param $uid the actor (defaults to session user) - * @return bool - */ - public function stream_addLike($post_id, $uid = null) { - return $this->call_method( - 'facebook.stream.addLike', - array('post_id' => $post_id, - 'uid' => $this->get_uid($uid))); - } - - /** - * Remove a like from a stream post - * - * @param $post_id the post id - * @param $uid the actor (defaults to session user) - * @return bool - */ - public function stream_removeLike($post_id, $uid = null) { - return $this->call_method( - 'facebook.stream.removeLike', - array('post_id' => $post_id, - 'uid' => $this->get_uid($uid))); - } - - /** - * For the current user, retrieves stories generated by the user's friends - * while using this application. This can be used to easily create a - * "News Feed" like experience. - * - * @return array An array of feed story objects. - */ - public function &feed_getAppFriendStories() { - return $this->call_method('facebook.feed.getAppFriendStories'); - } - - /** - * Makes an FQL query. This is a generalized way of accessing all the data - * in the API, as an alternative to most of the other method calls. More - * info at http://wiki.developers.facebook.com/index.php/FQL - * - * @param string $query the query to evaluate - * - * @return array generalized array representing the results - */ - public function &fql_query($query) { - return $this->call_method('facebook.fql.query', - array('query' => $query)); - } - - /** - * Makes a set of FQL queries in parallel. This method takes a dictionary - * of FQL queries where the keys are names for the queries. Results from - * one query can be used within another query to fetch additional data. More - * info about FQL queries at http://wiki.developers.facebook.com/index.php/FQL - * - * @param string $queries JSON-encoded dictionary of queries to evaluate - * - * @return array generalized array representing the results - */ - public function &fql_multiquery($queries) { - return $this->call_method('facebook.fql.multiquery', - array('queries' => $queries)); - } - - /** - * Returns whether or not pairs of users are friends. - * Note that the Facebook friend relationship is symmetric. - * - * @param array/string $uids1 list of ids (id_1, id_2,...) - * of some length X (csv is deprecated) - * @param array/string $uids2 list of ids (id_A, id_B,...) - * of SAME length X (csv is deprecated) - * - * @return array An array with uid1, uid2, and bool if friends, e.g.: - * array(0 => array('uid1' => id_1, 'uid2' => id_A, 'are_friends' => 1), - * 1 => array('uid1' => id_2, 'uid2' => id_B, 'are_friends' => 0) - * ...) - * @error - * API_EC_PARAM_USER_ID_LIST - */ - public function &friends_areFriends($uids1, $uids2) { - return $this->call_method('facebook.friends.areFriends', - array('uids1' => $uids1, - 'uids2' => $uids2)); - } - - /** - * Returns the friends of the current session user. - * - * @param int $flid (Optional) Only return friends on this friend list. - * @param int $uid (Optional) Return friends for this user. - * - * @return array An array of friends - */ - public function &friends_get($flid=null, $uid = null) { - if (isset($this->friends_list)) { - return $this->friends_list; - } - $params = array(); - if (!$uid && isset($this->canvas_user)) { - $uid = $this->canvas_user; - } - if ($uid) { - $params['uid'] = $uid; - } - if ($flid) { - $params['flid'] = $flid; - } - return $this->call_method('facebook.friends.get', $params); - - } - - /** - * Returns the mutual friends between the target uid and a source uid or - * the current session user. - * - * @param int $target_uid Target uid for which mutual friends will be found. - * @param int $source_uid (optional) Source uid for which mutual friends will - * be found. If no source_uid is specified, - * source_id will default to the session - * user. - * @return array An array of friend uids - */ - public function &friends_getMutualFriends($target_uid, $source_uid = null) { - return $this->call_method('facebook.friends.getMutualFriends', - array("target_uid" => $target_uid, - "source_uid" => $source_uid)); - } - - /** - * Returns the set of friend lists for the current session user. - * - * @return array An array of friend list objects - */ - public function &friends_getLists() { - return $this->call_method('facebook.friends.getLists'); - } - - /** - * Returns the friends of the session user, who are also users - * of the calling application. - * - * @return array An array of friends also using the app - */ - public function &friends_getAppUsers() { - return $this->call_method('facebook.friends.getAppUsers'); - } - - /** - * Returns groups according to the filters specified. - * - * @param int $uid (Optional) User associated with groups. A null - * parameter will default to the session user. - * @param array/string $gids (Optional) Array of group ids to query. A null - * parameter will get all groups for the user. - * (csv is deprecated) - * - * @return array An array of group objects - */ - public function &groups_get($uid, $gids) { - return $this->call_method('facebook.groups.get', - array('uid' => $uid, - 'gids' => $gids)); - } - - /** - * Returns the membership list of a group. - * - * @param int $gid Group id - * - * @return array An array with four membership lists, with keys 'members', - * 'admins', 'officers', and 'not_replied' - */ - public function &groups_getMembers($gid) { - return $this->call_method('facebook.groups.getMembers', - array('gid' => $gid)); - } - - /** - * Returns cookies according to the filters specified. - * - * @param int $uid User for which the cookies are needed. - * @param string $name (Optional) A null parameter will get all cookies - * for the user. - * - * @return array Cookies! Nom nom nom nom nom. - */ - public function data_getCookies($uid, $name) { - return $this->call_method('facebook.data.getCookies', - array('uid' => $uid, - 'name' => $name)); - } - - /** - * Sets cookies according to the params specified. - * - * @param int $uid User for which the cookies are needed. - * @param string $name Name of the cookie - * @param string $value (Optional) if expires specified and is in the past - * @param int $expires (Optional) Expiry time - * @param string $path (Optional) Url path to associate with (default is /) - * - * @return bool true on success - */ - public function data_setCookie($uid, $name, $value, $expires, $path) { - return $this->call_method('facebook.data.setCookie', - array('uid' => $uid, - 'name' => $name, - 'value' => $value, - 'expires' => $expires, - 'path' => $path)); - } - - /** - * Retrieves links posted by the given user. - * - * @param int $uid The user whose links you wish to retrieve - * @param int $limit The maximimum number of links to retrieve - * @param array $link_ids (Optional) Array of specific link - * IDs to retrieve by this user - * - * @return array An array of links. - */ - public function &links_get($uid, $limit, $link_ids = null) { - return $this->call_method('links.get', - array('uid' => $uid, - 'limit' => $limit, - 'link_ids' => $link_ids)); - } - - /** - * Posts a link on Facebook. - * - * @param string $url URL/link you wish to post - * @param string $comment (Optional) A comment about this link - * @param int $uid (Optional) User ID that is posting this link; - * defaults to current session user - * - * @return bool - */ - public function &links_post($url, $comment='', $uid = null) { - return $this->call_method('links.post', - array('uid' => $uid, - 'url' => $url, - 'comment' => $comment)); - } - - /** - * Permissions API - */ - - /** - * Checks API-access granted by self to the specified application. - * - * @param string $permissions_apikey Other application key - * - * @return array API methods/namespaces which are allowed access - */ - public function permissions_checkGrantedApiAccess($permissions_apikey) { - return $this->call_method('facebook.permissions.checkGrantedApiAccess', - array('permissions_apikey' => $permissions_apikey)); - } - - /** - * Checks API-access granted to self by the specified application. - * - * @param string $permissions_apikey Other application key - * - * @return array API methods/namespaces which are allowed access - */ - public function permissions_checkAvailableApiAccess($permissions_apikey) { - return $this->call_method('facebook.permissions.checkAvailableApiAccess', - array('permissions_apikey' => $permissions_apikey)); - } - - /** - * Grant API-access to the specified methods/namespaces to the specified - * application. - * - * @param string $permissions_apikey Other application key - * @param array(string) $method_arr (Optional) API methods/namespaces - * allowed - * - * @return array API methods/namespaces which are allowed access - */ - public function permissions_grantApiAccess($permissions_apikey, $method_arr) { - return $this->call_method('facebook.permissions.grantApiAccess', - array('permissions_apikey' => $permissions_apikey, - 'method_arr' => $method_arr)); - } - - /** - * Revoke API-access granted to the specified application. - * - * @param string $permissions_apikey Other application key - * - * @return bool true on success - */ - public function permissions_revokeApiAccess($permissions_apikey) { - return $this->call_method('facebook.permissions.revokeApiAccess', - array('permissions_apikey' => $permissions_apikey)); - } - - /** - * Payments Order API - */ - - /** - * Set Payments properties for an app. - * - * @param properties a map from property names to values - * @return true on success - */ - public function payments_setProperties($properties) { - return $this->call_method ('facebook.payments.setProperties', - array('properties' => json_encode($properties))); - } - - public function payments_getOrderDetails($order_id) { - return json_decode($this->call_method( - 'facebook.payments.getOrderDetails', - array('order_id' => $order_id)), true); - } - - public function payments_updateOrder($order_id, $status, - $params) { - return $this->call_method('facebook.payments.updateOrder', - array('order_id' => $order_id, - 'status' => $status, - 'params' => json_encode($params))); - } - - public function payments_getOrders($status, $start_time, - $end_time, $test_mode=false) { - return json_decode($this->call_method('facebook.payments.getOrders', - array('status' => $status, - 'start_time' => $start_time, - 'end_time' => $end_time, - 'test_mode' => $test_mode)), true); - } - - /** - * Gifts API - */ - - /** - * Get Gifts associated with an app - * - * @return array of gifts - */ - public function gifts_get() { - return json_decode( - $this->call_method('facebook.gifts.get', - array()), - true - ); - } - - /* - * Update gifts stored by an app - * - * @param array containing gift_id => gift_data to be updated - * @return array containing gift_id => true/false indicating success - * in updating that gift - */ - public function gifts_update($update_array) { - return json_decode( - $this->call_method('facebook.gifts.update', - array('update_str' => json_encode($update_array)) - ), - true - ); - } - - - /** - * Creates a note with the specified title and content. - * - * @param string $title Title of the note. - * @param string $content Content of the note. - * @param int $uid (Optional) The user for whom you are creating a - * note; defaults to current session user - * - * @return int The ID of the note that was just created. - */ - public function ¬es_create($title, $content, $uid = null) { - return $this->call_method('notes.create', - array('uid' => $uid, - 'title' => $title, - 'content' => $content)); - } - - /** - * Deletes the specified note. - * - * @param int $note_id ID of the note you wish to delete - * @param int $uid (Optional) Owner of the note you wish to delete; - * defaults to current session user - * - * @return bool - */ - public function ¬es_delete($note_id, $uid = null) { - return $this->call_method('notes.delete', - array('uid' => $uid, - 'note_id' => $note_id)); - } - - /** - * Edits a note, replacing its title and contents with the title - * and contents specified. - * - * @param int $note_id ID of the note you wish to edit - * @param string $title Replacement title for the note - * @param string $content Replacement content for the note - * @param int $uid (Optional) Owner of the note you wish to edit; - * defaults to current session user - * - * @return bool - */ - public function ¬es_edit($note_id, $title, $content, $uid = null) { - return $this->call_method('notes.edit', - array('uid' => $uid, - 'note_id' => $note_id, - 'title' => $title, - 'content' => $content)); - } - - /** - * Retrieves all notes by a user. If note_ids are specified, - * retrieves only those specific notes by that user. - * - * @param int $uid User whose notes you wish to retrieve - * @param array $note_ids (Optional) List of specific note - * IDs by this user to retrieve - * - * @return array A list of all of the given user's notes, or an empty list - * if the viewer lacks permissions or if there are no visible - * notes. - */ - public function ¬es_get($uid, $note_ids = null) { - return $this->call_method('notes.get', - array('uid' => $uid, - 'note_ids' => $note_ids)); - } - - - /** - * Returns the outstanding notifications for the session user. - * - * @return array An assoc array of notification count objects for - * 'messages', 'pokes' and 'shares', a uid list of - * 'friend_requests', a gid list of 'group_invites', - * and an eid list of 'event_invites' - */ - public function ¬ifications_get() { - return $this->call_method('facebook.notifications.get'); - } - - /** - * Sends a notification to the specified users. - * - * @return A comma separated list of successful recipients - * @error - * API_EC_PARAM_USER_ID_LIST - */ - public function ¬ifications_send($to_ids, $notification, $type) { - return $this->call_method('facebook.notifications.send', - array('to_ids' => $to_ids, - 'notification' => $notification, - 'type' => $type)); - } - - /** - * Sends an email to the specified user of the application. - * - * @param array/string $recipients array of ids of the recipients (csv is deprecated) - * @param string $subject subject of the email - * @param string $text (plain text) body of the email - * @param string $fbml fbml markup for an html version of the email - * - * @return string A comma separated list of successful recipients - * @error - * API_EC_PARAM_USER_ID_LIST - */ - public function ¬ifications_sendEmail($recipients, - $subject, - $text, - $fbml) { - return $this->call_method('facebook.notifications.sendEmail', - array('recipients' => $recipients, - 'subject' => $subject, - 'text' => $text, - 'fbml' => $fbml)); - } - - /** - * Returns the requested info fields for the requested set of pages. - * - * @param array/string $page_ids an array of page ids (csv is deprecated) - * @param array/string $fields an array of strings describing the - * info fields desired (csv is deprecated) - * @param int $uid (Optional) limit results to pages of which this - * user is a fan. - * @param string type limits results to a particular type of page. - * - * @return array An array of pages - */ - public function &pages_getInfo($page_ids, $fields, $uid, $type) { - return $this->call_method('facebook.pages.getInfo', - array('page_ids' => $page_ids, - 'fields' => $fields, - 'uid' => $uid, - 'type' => $type)); - } - - /** - * Returns true if the given user is an admin for the passed page. - * - * @param int $page_id target page id - * @param int $uid (Optional) user id (defaults to the logged-in user) - * - * @return bool true on success - */ - public function &pages_isAdmin($page_id, $uid = null) { - return $this->call_method('facebook.pages.isAdmin', - array('page_id' => $page_id, - 'uid' => $uid)); - } - - /** - * Returns whether or not the given page has added the application. - * - * @param int $page_id target page id - * - * @return bool true on success - */ - public function &pages_isAppAdded($page_id) { - return $this->call_method('facebook.pages.isAppAdded', - array('page_id' => $page_id)); - } - - /** - * Returns true if logged in user is a fan for the passed page. - * - * @param int $page_id target page id - * @param int $uid user to compare. If empty, the logged in user. - * - * @return bool true on success - */ - public function &pages_isFan($page_id, $uid = null) { - return $this->call_method('facebook.pages.isFan', - array('page_id' => $page_id, - 'uid' => $uid)); - } - - /** - * Adds a tag with the given information to a photo. See the wiki for details: - * - * http://wiki.developers.facebook.com/index.php/Photos.addTag - * - * @param int $pid The ID of the photo to be tagged - * @param int $tag_uid The ID of the user being tagged. You must specify - * either the $tag_uid or the $tag_text parameter - * (unless $tags is specified). - * @param string $tag_text Some text identifying the person being tagged. - * You must specify either the $tag_uid or $tag_text - * parameter (unless $tags is specified). - * @param float $x The horizontal position of the tag, as a - * percentage from 0 to 100, from the left of the - * photo. - * @param float $y The vertical position of the tag, as a percentage - * from 0 to 100, from the top of the photo. - * @param array $tags (Optional) An array of maps, where each map - * can contain the tag_uid, tag_text, x, and y - * parameters defined above. If specified, the - * individual arguments are ignored. - * @param int $owner_uid (Optional) The user ID of the user whose photo - * you are tagging. If this parameter is not - * specified, then it defaults to the session user. - * - * @return bool true on success - */ - public function &photos_addTag($pid, - $tag_uid, - $tag_text, - $x, - $y, - $tags, - $owner_uid=0) { - return $this->call_method('facebook.photos.addTag', - array('pid' => $pid, - 'tag_uid' => $tag_uid, - 'tag_text' => $tag_text, - 'x' => $x, - 'y' => $y, - 'tags' => (is_array($tags)) ? json_encode($tags) : null, - 'owner_uid' => $this->get_uid($owner_uid))); - } - - /** - * Creates and returns a new album owned by the specified user or the current - * session user. - * - * @param string $name The name of the album. - * @param string $description (Optional) A description of the album. - * @param string $location (Optional) A description of the location. - * @param string $visible (Optional) A privacy setting for the album. - * One of 'friends', 'friends-of-friends', - * 'networks', or 'everyone'. Default 'everyone'. - * @param int $uid (Optional) User id for creating the album; if - * not specified, the session user is used. - * - * @return array An album object - */ - public function &photos_createAlbum($name, - $description='', - $location='', - $visible='', - $uid=0) { - return $this->call_method('facebook.photos.createAlbum', - array('name' => $name, - 'description' => $description, - 'location' => $location, - 'visible' => $visible, - 'uid' => $this->get_uid($uid))); - } - - /** - * Returns photos according to the filters specified. - * - * @param int $subj_id (Optional) Filter by uid of user tagged in the photos. - * @param int $aid (Optional) Filter by an album, as returned by - * photos_getAlbums. - * @param array/string $pids (Optional) Restrict to an array of pids - * (csv is deprecated) - * - * Note that at least one of these parameters needs to be specified, or an - * error is returned. - * - * @return array An array of photo objects. - */ - public function &photos_get($subj_id, $aid, $pids) { - return $this->call_method('facebook.photos.get', - array('subj_id' => $subj_id, 'aid' => $aid, 'pids' => $pids)); - } - - /** - * Returns the albums created by the given user. - * - * @param int $uid (Optional) The uid of the user whose albums you want. - * A null will return the albums of the session user. - * @param string $aids (Optional) An array of aids to restrict - * the query. (csv is deprecated) - * - * Note that at least one of the (uid, aids) parameters must be specified. - * - * @returns an array of album objects. - */ - public function &photos_getAlbums($uid, $aids) { - return $this->call_method('facebook.photos.getAlbums', - array('uid' => $uid, - 'aids' => $aids)); - } - - /** - * Returns the tags on all photos specified. - * - * @param string $pids A list of pids to query - * - * @return array An array of photo tag objects, which include pid, - * subject uid, and two floating-point numbers (xcoord, ycoord) - * for tag pixel location. - */ - public function &photos_getTags($pids) { - return $this->call_method('facebook.photos.getTags', - array('pids' => $pids)); - } - - /** - * Uploads a photo. - * - * @param string $file The location of the photo on the local filesystem. - * @param int $aid (Optional) The album into which to upload the - * photo. - * @param string $caption (Optional) A caption for the photo. - * @param int uid (Optional) The user ID of the user whose photo you - * are uploading - * - * @return array An array of user objects - */ - public function photos_upload($file, $aid=null, $caption=null, $uid=null) { - return $this->call_upload_method('facebook.photos.upload', - array('aid' => $aid, - 'caption' => $caption, - 'uid' => $uid), - $file); - } - - - /** - * Uploads a video. - * - * @param string $file The location of the video on the local filesystem. - * @param string $title (Optional) A title for the video. Titles over 65 characters in length will be truncated. - * @param string $description (Optional) A description for the video. - * - * @return array An array with the video's ID, title, description, and a link to view it on Facebook. - */ - public function video_upload($file, $title=null, $description=null) { - return $this->call_upload_method('facebook.video.upload', - array('title' => $title, - 'description' => $description), - $file, - Facebook::get_facebook_url('api-video') . '/restserver.php'); - } - - /** - * Returns an array with the video limitations imposed on the current session's - * associated user. Maximum length is measured in seconds; maximum size is - * measured in bytes. - * - * @return array Array with "length" and "size" keys - */ - public function &video_getUploadLimits() { - return $this->call_method('facebook.video.getUploadLimits'); - } - - /** - * Returns the requested info fields for the requested set of users. - * - * @param array/string $uids An array of user ids (csv is deprecated) - * @param array/string $fields An array of info field names desired (csv is deprecated) - * - * @return array An array of user objects - */ - public function &users_getInfo($uids, $fields) { - return $this->call_method('facebook.users.getInfo', - array('uids' => $uids, - 'fields' => $fields)); - } - - /** - * Returns the requested info fields for the requested set of users. A - * session key must not be specified. Only data about users that have - * authorized your application will be returned. - * - * Check the wiki for fields that can be queried through this API call. - * Data returned from here should not be used for rendering to application - * users, use users.getInfo instead, so that proper privacy rules will be - * applied. - * - * @param array/string $uids An array of user ids (csv is deprecated) - * @param array/string $fields An array of info field names desired (csv is deprecated) - * - * @return array An array of user objects - */ - public function &users_getStandardInfo($uids, $fields) { - return $this->call_method('facebook.users.getStandardInfo', - array('uids' => $uids, - 'fields' => $fields)); - } - - /** - * Returns the user corresponding to the current session object. - * - * @return integer User id - */ - public function &users_getLoggedInUser() { - return $this->call_method('facebook.users.getLoggedInUser'); - } - - /** - * Returns 1 if the user has the specified permission, 0 otherwise. - * http://wiki.developers.facebook.com/index.php/Users.hasAppPermission - * - * @return integer 1 or 0 - */ - public function &users_hasAppPermission($ext_perm, $uid=null) { - return $this->call_method('facebook.users.hasAppPermission', - array('ext_perm' => $ext_perm, 'uid' => $uid)); - } - - /** - * Returns whether or not the user corresponding to the current - * session object has the give the app basic authorization. - * - * @return boolean true if the user has authorized the app - */ - public function &users_isAppUser($uid=null) { - if ($uid === null && isset($this->is_user)) { - return $this->is_user; - } - - return $this->call_method('facebook.users.isAppUser', array('uid' => $uid)); - } - - /** - * Returns whether or not the user corresponding to the current - * session object is verified by Facebook. See the documentation - * for Users.isVerified for details. - * - * @return boolean true if the user is verified - */ - public function &users_isVerified() { - return $this->call_method('facebook.users.isVerified'); - } - - /** - * Sets the users' current status message. Message does NOT contain the - * word "is" , so make sure to include a verb. - * - * Example: setStatus("is loving the API!") - * will produce the status "Luke is loving the API!" - * - * @param string $status text-only message to set - * @param int $uid user to set for (defaults to the - * logged-in user) - * @param bool $clear whether or not to clear the status, - * instead of setting it - * @param bool $status_includes_verb if true, the word "is" will *not* be - * prepended to the status message - * - * @return boolean - */ - public function &users_setStatus($status, - $uid = null, - $clear = false, - $status_includes_verb = true) { - $args = array( - 'status' => $status, - 'uid' => $uid, - 'clear' => $clear, - 'status_includes_verb' => $status_includes_verb, - ); - return $this->call_method('facebook.users.setStatus', $args); - } - - /** - * Gets the comments for a particular xid. This is essentially a wrapper - * around the comment FQL table. - * - * @param string $xid external id associated with the comments - * - * @return array of comment objects - */ - public function &comments_get($xid) { - $args = array('xid' => $xid); - return $this->call_method('facebook.comments.get', $args); - } - - /** - * Add a comment to a particular xid on behalf of a user. If called - * without an app_secret (with session secret), this will only work - * for the session user. - * - * @param string $xid external id associated with the comments - * @param string $text text of the comment - * @param int $uid user adding the comment (def: session user) - * @param string $title optional title for the stream story - * @param string $url optional url for the stream story - * @param bool $publish_to_stream publish a feed story about this comment? - * a link will be generated to title/url in the story - * - * @return string comment_id associated with the comment - */ - public function &comments_add($xid, $text, $uid=0, $title='', $url='', - $publish_to_stream=false) { - $args = array( - 'xid' => $xid, - 'uid' => $this->get_uid($uid), - 'text' => $text, - 'title' => $title, - 'url' => $url, - 'publish_to_stream' => $publish_to_stream); - - return $this->call_method('facebook.comments.add', $args); - } - - /** - * Remove a particular comment. - * - * @param string $xid the external id associated with the comments - * @param string $comment_id id of the comment to remove (returned by - * comments.add and comments.get) - * - * @return boolean - */ - public function &comments_remove($xid, $comment_id) { - $args = array( - 'xid' => $xid, - 'comment_id' => $comment_id); - return $this->call_method('facebook.comments.remove', $args); - } - - /** - * Gets the stream on behalf of a user using a set of users. This - * call will return the latest $limit queries between $start_time - * and $end_time. - * - * @param int $viewer_id user making the call (def: session) - * @param array $source_ids users/pages to look at (def: all connections) - * @param int $start_time start time to look for stories (def: 1 day ago) - * @param int $end_time end time to look for stories (def: now) - * @param int $limit number of stories to attempt to fetch (def: 30) - * @param string $filter_key key returned by stream.getFilters to fetch - * @param array $metadata metadata to include with the return, allows - * requested metadata to be returned, such as - * profiles, albums, photo_tags - * - * @return array( - * 'posts' => array of posts, - * // if requested, the following data may be returned - * 'profiles' => array of profile metadata of users/pages in posts - * 'albums' => array of album metadata in posts - * 'photo_tags' => array of photo_tags for photos in posts - * ) - */ - public function &stream_get($viewer_id = null, - $source_ids = null, - $start_time = 0, - $end_time = 0, - $limit = 30, - $filter_key = '', - $exportable_only = false, - $metadata = null, - $post_ids = null) { - $args = array( - 'viewer_id' => $viewer_id, - 'source_ids' => $source_ids, - 'start_time' => $start_time, - 'end_time' => $end_time, - 'limit' => $limit, - 'filter_key' => $filter_key, - 'exportable_only' => $exportable_only, - 'metadata' => $metadata, - 'post_ids' => $post_ids); - return $this->call_method('facebook.stream.get', $args); - } - - /** - * Gets the filters (with relevant filter keys for stream.get) for a - * particular user. These filters are typical things like news feed, - * friend lists, networks. They can be used to filter the stream - * without complex queries to determine which ids belong in which groups. - * - * @param int $uid user to get filters for - * - * @return array of stream filter objects - */ - public function &stream_getFilters($uid = null) { - $args = array('uid' => $uid); - return $this->call_method('facebook.stream.getFilters', $args); - } - - /** - * Gets the full comments given a post_id from stream.get or the - * stream FQL table. Initially, only a set of preview comments are - * returned because some posts can have many comments. - * - * @param string $post_id id of the post to get comments for - * - * @return array of comment objects - */ - public function &stream_getComments($post_id) { - $args = array('post_id' => $post_id); - return $this->call_method('facebook.stream.getComments', $args); - } - - /** - * Sets the FBML for the profile of the user attached to this session. - * - * @param string $markup The FBML that describes the profile - * presence of this app for the user - * @param int $uid The user - * @param string $profile Profile FBML - * @param string $profile_action Profile action FBML (deprecated) - * @param string $mobile_profile Mobile profile FBML - * @param string $profile_main Main Tab profile FBML - * - * @return array A list of strings describing any compile errors for the - * submitted FBML - */ - public function profile_setFBML($markup, - $uid=null, - $profile='', - $profile_action='', - $mobile_profile='', - $profile_main='') { - return $this->call_method('facebook.profile.setFBML', - array('markup' => $markup, - 'uid' => $uid, - 'profile' => $profile, - 'profile_action' => $profile_action, - 'mobile_profile' => $mobile_profile, - 'profile_main' => $profile_main)); - } - - /** - * Gets the FBML for the profile box that is currently set for a user's - * profile (your application set the FBML previously by calling the - * profile.setFBML method). - * - * @param int $uid (Optional) User id to lookup; defaults to session. - * @param int $type (Optional) 1 for original style, 2 for profile_main boxes - * - * @return string The FBML - */ - public function &profile_getFBML($uid=null, $type=null) { - return $this->call_method('facebook.profile.getFBML', - array('uid' => $uid, - 'type' => $type)); - } - - /** - * Returns the specified user's application info section for the calling - * application. These info sections have either been set via a previous - * profile.setInfo call or by the user editing them directly. - * - * @param int $uid (Optional) User id to lookup; defaults to session. - * - * @return array Info fields for the current user. See wiki for structure: - * - * http://wiki.developers.facebook.com/index.php/Profile.getInfo - * - */ - public function &profile_getInfo($uid=null) { - return $this->call_method('facebook.profile.getInfo', - array('uid' => $uid)); - } - - /** - * Returns the options associated with the specified info field for an - * application info section. - * - * @param string $field The title of the field - * - * @return array An array of info options. - */ - public function &profile_getInfoOptions($field) { - return $this->call_method('facebook.profile.getInfoOptions', - array('field' => $field)); - } - - /** - * Configures an application info section that the specified user can install - * on the Info tab of her profile. For details on the structure of an info - * field, please see: - * - * http://wiki.developers.facebook.com/index.php/Profile.setInfo - * - * @param string $title Title / header of the info section - * @param int $type 1 for text-only, 5 for thumbnail views - * @param array $info_fields An array of info fields. See wiki for details. - * @param int $uid (Optional) - * - * @return bool true on success - */ - public function &profile_setInfo($title, $type, $info_fields, $uid=null) { - return $this->call_method('facebook.profile.setInfo', - array('uid' => $uid, - 'type' => $type, - 'title' => $title, - 'info_fields' => json_encode($info_fields))); - } - - /** - * Specifies the objects for a field for an application info section. These - * options populate the typeahead for a thumbnail. - * - * @param string $field The title of the field - * @param array $options An array of items for a thumbnail, including - * 'label', 'link', and optionally 'image', - * 'description' and 'sublabel' - * - * @return bool true on success - */ - public function profile_setInfoOptions($field, $options) { - return $this->call_method('facebook.profile.setInfoOptions', - array('field' => $field, - 'options' => json_encode($options))); - } - - ///////////////////////////////////////////////////////////////////////////// - // Data Store API - - /** - * Set a user preference. - * - * @param pref_id preference identifier (0-200) - * @param value preferece's value - * @param uid the user id (defaults to current session user) - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - * API_EC_PERMISSION_OTHER_USER - */ - public function &data_setUserPreference($pref_id, $value, $uid = null) { - return $this->call_method('facebook.data.setUserPreference', - array('pref_id' => $pref_id, - 'value' => $value, - 'uid' => $this->get_uid($uid))); - } - - /** - * Set a user's all preferences for this application. - * - * @param values preferece values in an associative arrays - * @param replace whether to replace all existing preferences or - * merge into them. - * @param uid the user id (defaults to current session user) - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - * API_EC_PERMISSION_OTHER_USER - */ - public function &data_setUserPreferences($values, - $replace = false, - $uid = null) { - return $this->call_method('facebook.data.setUserPreferences', - array('values' => json_encode($values), - 'replace' => $replace, - 'uid' => $this->get_uid($uid))); - } - - /** - * Get a user preference. - * - * @param pref_id preference identifier (0-200) - * @param uid the user id (defaults to current session user) - * @return preference's value - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - * API_EC_PERMISSION_OTHER_USER - */ - public function &data_getUserPreference($pref_id, $uid = null) { - return $this->call_method('facebook.data.getUserPreference', - array('pref_id' => $pref_id, - 'uid' => $this->get_uid($uid))); - } - - /** - * Get a user preference. - * - * @param uid the user id (defaults to current session user) - * @return preference values - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - * API_EC_PERMISSION_OTHER_USER - */ - public function &data_getUserPreferences($uid = null) { - return $this->call_method('facebook.data.getUserPreferences', - array('uid' => $this->get_uid($uid))); - } - - /** - * Create a new object type. - * - * @param name object type's name - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_ALREADY_EXISTS - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_createObjectType($name) { - return $this->call_method('facebook.data.createObjectType', - array('name' => $name)); - } - - /** - * Delete an object type. - * - * @param obj_type object type's name - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_dropObjectType($obj_type) { - return $this->call_method('facebook.data.dropObjectType', - array('obj_type' => $obj_type)); - } - - /** - * Rename an object type. - * - * @param obj_type object type's name - * @param new_name new object type's name - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_DATA_OBJECT_ALREADY_EXISTS - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_renameObjectType($obj_type, $new_name) { - return $this->call_method('facebook.data.renameObjectType', - array('obj_type' => $obj_type, - 'new_name' => $new_name)); - } - - /** - * Add a new property to an object type. - * - * @param obj_type object type's name - * @param prop_name name of the property to add - * @param prop_type 1: integer; 2: string; 3: text blob - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_ALREADY_EXISTS - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_defineObjectProperty($obj_type, - $prop_name, - $prop_type) { - return $this->call_method('facebook.data.defineObjectProperty', - array('obj_type' => $obj_type, - 'prop_name' => $prop_name, - 'prop_type' => $prop_type)); - } - - /** - * Remove a previously defined property from an object type. - * - * @param obj_type object type's name - * @param prop_name name of the property to remove - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_undefineObjectProperty($obj_type, $prop_name) { - return $this->call_method('facebook.data.undefineObjectProperty', - array('obj_type' => $obj_type, - 'prop_name' => $prop_name)); - } - - /** - * Rename a previously defined property of an object type. - * - * @param obj_type object type's name - * @param prop_name name of the property to rename - * @param new_name new name to use - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_DATA_OBJECT_ALREADY_EXISTS - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_renameObjectProperty($obj_type, $prop_name, - $new_name) { - return $this->call_method('facebook.data.renameObjectProperty', - array('obj_type' => $obj_type, - 'prop_name' => $prop_name, - 'new_name' => $new_name)); - } - - /** - * Retrieve a list of all object types that have defined for the application. - * - * @return a list of object type names - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PERMISSION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getObjectTypes() { - return $this->call_method('facebook.data.getObjectTypes'); - } - - /** - * Get definitions of all properties of an object type. - * - * @param obj_type object type's name - * @return pairs of property name and property types - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getObjectType($obj_type) { - return $this->call_method('facebook.data.getObjectType', - array('obj_type' => $obj_type)); - } - - /** - * Create a new object. - * - * @param obj_type object type's name - * @param properties (optional) properties to set initially - * @return newly created object's id - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_createObject($obj_type, $properties = null) { - return $this->call_method('facebook.data.createObject', - array('obj_type' => $obj_type, - 'properties' => json_encode($properties))); - } - - /** - * Update an existing object. - * - * @param obj_id object's id - * @param properties new properties - * @param replace true for replacing existing properties; - * false for merging - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_updateObject($obj_id, $properties, $replace = false) { - return $this->call_method('facebook.data.updateObject', - array('obj_id' => $obj_id, - 'properties' => json_encode($properties), - 'replace' => $replace)); - } - - /** - * Delete an existing object. - * - * @param obj_id object's id - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_deleteObject($obj_id) { - return $this->call_method('facebook.data.deleteObject', - array('obj_id' => $obj_id)); - } - - /** - * Delete a list of objects. - * - * @param obj_ids objects to delete - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_deleteObjects($obj_ids) { - return $this->call_method('facebook.data.deleteObjects', - array('obj_ids' => json_encode($obj_ids))); - } - - /** - * Get a single property value of an object. - * - * @param obj_id object's id - * @param prop_name individual property's name - * @return individual property's value - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getObjectProperty($obj_id, $prop_name) { - return $this->call_method('facebook.data.getObjectProperty', - array('obj_id' => $obj_id, - 'prop_name' => $prop_name)); - } - - /** - * Get properties of an object. - * - * @param obj_id object's id - * @param prop_names (optional) properties to return; null for all. - * @return specified properties of an object - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getObject($obj_id, $prop_names = null) { - return $this->call_method('facebook.data.getObject', - array('obj_id' => $obj_id, - 'prop_names' => json_encode($prop_names))); - } - - /** - * Get properties of a list of objects. - * - * @param obj_ids object ids - * @param prop_names (optional) properties to return; null for all. - * @return specified properties of an object - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getObjects($obj_ids, $prop_names = null) { - return $this->call_method('facebook.data.getObjects', - array('obj_ids' => json_encode($obj_ids), - 'prop_names' => json_encode($prop_names))); - } - - /** - * Set a single property value of an object. - * - * @param obj_id object's id - * @param prop_name individual property's name - * @param prop_value new value to set - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_setObjectProperty($obj_id, $prop_name, - $prop_value) { - return $this->call_method('facebook.data.setObjectProperty', - array('obj_id' => $obj_id, - 'prop_name' => $prop_name, - 'prop_value' => $prop_value)); - } - - /** - * Read hash value by key. - * - * @param obj_type object type's name - * @param key hash key - * @param prop_name (optional) individual property's name - * @return hash value - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getHashValue($obj_type, $key, $prop_name = null) { - return $this->call_method('facebook.data.getHashValue', - array('obj_type' => $obj_type, - 'key' => $key, - 'prop_name' => $prop_name)); - } - - /** - * Write hash value by key. - * - * @param obj_type object type's name - * @param key hash key - * @param value hash value - * @param prop_name (optional) individual property's name - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_setHashValue($obj_type, - $key, - $value, - $prop_name = null) { - return $this->call_method('facebook.data.setHashValue', - array('obj_type' => $obj_type, - 'key' => $key, - 'value' => $value, - 'prop_name' => $prop_name)); - } - - /** - * Increase a hash value by specified increment atomically. - * - * @param obj_type object type's name - * @param key hash key - * @param prop_name individual property's name - * @param increment (optional) default is 1 - * @return incremented hash value - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_incHashValue($obj_type, - $key, - $prop_name, - $increment = 1) { - return $this->call_method('facebook.data.incHashValue', - array('obj_type' => $obj_type, - 'key' => $key, - 'prop_name' => $prop_name, - 'increment' => $increment)); - } - - /** - * Remove a hash key and its values. - * - * @param obj_type object type's name - * @param key hash key - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_removeHashKey($obj_type, $key) { - return $this->call_method('facebook.data.removeHashKey', - array('obj_type' => $obj_type, - 'key' => $key)); - } - - /** - * Remove hash keys and their values. - * - * @param obj_type object type's name - * @param keys hash keys - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_removeHashKeys($obj_type, $keys) { - return $this->call_method('facebook.data.removeHashKeys', - array('obj_type' => $obj_type, - 'keys' => json_encode($keys))); - } - - /** - * Define an object association. - * - * @param name name of this association - * @param assoc_type 1: one-way 2: two-way symmetric 3: two-way asymmetric - * @param assoc_info1 needed info about first object type - * @param assoc_info2 needed info about second object type - * @param inverse (optional) name of reverse association - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_ALREADY_EXISTS - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_defineAssociation($name, $assoc_type, $assoc_info1, - $assoc_info2, $inverse = null) { - return $this->call_method('facebook.data.defineAssociation', - array('name' => $name, - 'assoc_type' => $assoc_type, - 'assoc_info1' => json_encode($assoc_info1), - 'assoc_info2' => json_encode($assoc_info2), - 'inverse' => $inverse)); - } - - /** - * Undefine an object association. - * - * @param name name of this association - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_undefineAssociation($name) { - return $this->call_method('facebook.data.undefineAssociation', - array('name' => $name)); - } - - /** - * Rename an object association or aliases. - * - * @param name name of this association - * @param new_name (optional) new name of this association - * @param new_alias1 (optional) new alias for object type 1 - * @param new_alias2 (optional) new alias for object type 2 - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_ALREADY_EXISTS - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_renameAssociation($name, $new_name, $new_alias1 = null, - $new_alias2 = null) { - return $this->call_method('facebook.data.renameAssociation', - array('name' => $name, - 'new_name' => $new_name, - 'new_alias1' => $new_alias1, - 'new_alias2' => $new_alias2)); - } - - /** - * Get definition of an object association. - * - * @param name name of this association - * @return specified association - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getAssociationDefinition($name) { - return $this->call_method('facebook.data.getAssociationDefinition', - array('name' => $name)); - } - - /** - * Get definition of all associations. - * - * @return all defined associations - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PERMISSION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getAssociationDefinitions() { - return $this->call_method('facebook.data.getAssociationDefinitions', - array()); - } - - /** - * Create or modify an association between two objects. - * - * @param name name of association - * @param obj_id1 id of first object - * @param obj_id2 id of second object - * @param data (optional) extra string data to store - * @param assoc_time (optional) extra time data; default to creation time - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_setAssociation($name, $obj_id1, $obj_id2, $data = null, - $assoc_time = null) { - return $this->call_method('facebook.data.setAssociation', - array('name' => $name, - 'obj_id1' => $obj_id1, - 'obj_id2' => $obj_id2, - 'data' => $data, - 'assoc_time' => $assoc_time)); - } - - /** - * Create or modify associations between objects. - * - * @param assocs associations to set - * @param name (optional) name of association - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_setAssociations($assocs, $name = null) { - return $this->call_method('facebook.data.setAssociations', - array('assocs' => json_encode($assocs), - 'name' => $name)); - } - - /** - * Remove an association between two objects. - * - * @param name name of association - * @param obj_id1 id of first object - * @param obj_id2 id of second object - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_removeAssociation($name, $obj_id1, $obj_id2) { - return $this->call_method('facebook.data.removeAssociation', - array('name' => $name, - 'obj_id1' => $obj_id1, - 'obj_id2' => $obj_id2)); - } - - /** - * Remove associations between objects by specifying pairs of object ids. - * - * @param assocs associations to remove - * @param name (optional) name of association - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_removeAssociations($assocs, $name = null) { - return $this->call_method('facebook.data.removeAssociations', - array('assocs' => json_encode($assocs), - 'name' => $name)); - } - - /** - * Remove associations between objects by specifying one object id. - * - * @param name name of association - * @param obj_id who's association to remove - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_removeAssociatedObjects($name, $obj_id) { - return $this->call_method('facebook.data.removeAssociatedObjects', - array('name' => $name, - 'obj_id' => $obj_id)); - } - - /** - * Retrieve a list of associated objects. - * - * @param name name of association - * @param obj_id who's association to retrieve - * @param no_data only return object ids - * @return associated objects - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getAssociatedObjects($name, $obj_id, $no_data = true) { - return $this->call_method('facebook.data.getAssociatedObjects', - array('name' => $name, - 'obj_id' => $obj_id, - 'no_data' => $no_data)); - } - - /** - * Count associated objects. - * - * @param name name of association - * @param obj_id who's association to retrieve - * @return associated object's count - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getAssociatedObjectCount($name, $obj_id) { - return $this->call_method('facebook.data.getAssociatedObjectCount', - array('name' => $name, - 'obj_id' => $obj_id)); - } - - /** - * Get a list of associated object counts. - * - * @param name name of association - * @param obj_ids whose association to retrieve - * @return associated object counts - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_DATA_OBJECT_NOT_FOUND - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_INVALID_OPERATION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getAssociatedObjectCounts($name, $obj_ids) { - return $this->call_method('facebook.data.getAssociatedObjectCounts', - array('name' => $name, - 'obj_ids' => json_encode($obj_ids))); - } - - /** - * Find all associations between two objects. - * - * @param obj_id1 id of first object - * @param obj_id2 id of second object - * @param no_data only return association names without data - * @return all associations between objects - * @error - * API_EC_DATA_DATABASE_ERROR - * API_EC_PARAM - * API_EC_PERMISSION - * API_EC_DATA_QUOTA_EXCEEDED - * API_EC_DATA_UNKNOWN_ERROR - */ - public function &data_getAssociations($obj_id1, $obj_id2, $no_data = true) { - return $this->call_method('facebook.data.getAssociations', - array('obj_id1' => $obj_id1, - 'obj_id2' => $obj_id2, - 'no_data' => $no_data)); - } - - /** - * Get the properties that you have set for an app. - * - * @param properties List of properties names to fetch - * - * @return array A map from property name to value - */ - public function admin_getAppProperties($properties) { - return json_decode( - $this->call_method('facebook.admin.getAppProperties', - array('properties' => json_encode($properties))), true); - } - - /** - * Set properties for an app. - * - * @param properties A map from property names to values - * - * @return bool true on success - */ - public function admin_setAppProperties($properties) { - return $this->call_method('facebook.admin.setAppProperties', - array('properties' => json_encode($properties))); - } - - /** - * Sets href and text for a Live Stream Box xid's via link - * - * @param string $xid xid of the Live Stream - * @param string $via_href Href for the via link - * @param string $via_text Text for the via link - * - * @return boolWhether the set was successful - */ - public function admin_setLiveStreamViaLink($xid, $via_href, $via_text) { - return $this->call_method('facebook.admin.setLiveStreamViaLink', - array('xid' => $xid, - 'via_href' => $via_href, - 'via_text' => $via_text)); - } - - /** - * Gets href and text for a Live Stream Box xid's via link - * - * @param string $xid xid of the Live Stream - * - * @return Array Associative array with keys 'via_href' and 'via_text' - * False if there was an error. - */ - public function admin_getLiveStreamViaLink($xid) { - return $this->call_method('facebook.admin.getLiveStreamViaLink', - array('xid' => $xid)); - } - - /** - * Returns the allocation limit value for a specified integration point name - * Integration point names are defined in lib/api/karma/constants.php in the - * limit_map. - * - * @param string $integration_point_name Name of an integration point - * (see developer wiki for list). - * @param int $uid Specific user to check the limit. - * - * @return int Integration point allocation value - */ - public function &admin_getAllocation($integration_point_name, $uid=null) { - return $this->call_method('facebook.admin.getAllocation', - array('integration_point_name' => $integration_point_name, - 'uid' => $uid)); - } - - /** - * Returns values for the specified metrics for the current application, in - * the given time range. The metrics are collected for fixed-length periods, - * and the times represent midnight at the end of each period. - * - * @param start_time unix time for the start of the range - * @param end_time unix time for the end of the range - * @param period number of seconds in the desired period - * @param metrics list of metrics to look up - * - * @return array A map of the names and values for those metrics - */ - public function &admin_getMetrics($start_time, $end_time, $period, $metrics) { - return $this->call_method('admin.getMetrics', - array('start_time' => $start_time, - 'end_time' => $end_time, - 'period' => $period, - 'metrics' => json_encode($metrics))); - } - - /** - * Sets application restriction info. - * - * Applications can restrict themselves to only a limited user demographic - * based on users' age and/or location or based on static predefined types - * specified by facebook for specifying diff age restriction for diff - * locations. - * - * @param array $restriction_info The age restriction settings to set. - * - * @return bool true on success - */ - public function admin_setRestrictionInfo($restriction_info = null) { - $restriction_str = null; - if (!empty($restriction_info)) { - $restriction_str = json_encode($restriction_info); - } - return $this->call_method('admin.setRestrictionInfo', - array('restriction_str' => $restriction_str)); - } - - /** - * Gets application restriction info. - * - * Applications can restrict themselves to only a limited user demographic - * based on users' age and/or location or based on static predefined types - * specified by facebook for specifying diff age restriction for diff - * locations. - * - * @return array The age restriction settings for this application. - */ - public function admin_getRestrictionInfo() { - return json_decode( - $this->call_method('admin.getRestrictionInfo'), - true); - } - - - /** - * Bans a list of users from the app. Banned users can't - * access the app's canvas page and forums. - * - * @param array $uids an array of user ids - * @return bool true on success - */ - public function admin_banUsers($uids) { - return $this->call_method( - 'admin.banUsers', array('uids' => json_encode($uids))); - } - - /** - * Unban users that have been previously banned with - * admin_banUsers(). - * - * @param array $uids an array of user ids - * @return bool true on success - */ - public function admin_unbanUsers($uids) { - return $this->call_method( - 'admin.unbanUsers', array('uids' => json_encode($uids))); - } - - /** - * Gets the list of users that have been banned from the application. - * $uids is an optional parameter that filters the result with the list - * of provided user ids. If $uids is provided, - * only banned user ids that are contained in $uids are returned. - * - * @param array $uids an array of user ids to filter by - * @return bool true on success - */ - - public function admin_getBannedUsers($uids = null) { - return $this->call_method( - 'admin.getBannedUsers', - array('uids' => $uids ? json_encode($uids) : null)); - } - - - /* UTILITY FUNCTIONS */ - - /** - * Calls the specified normal POST method with the specified parameters. - * - * @param string $method Name of the Facebook method to invoke - * @param array $params A map of param names => param values - * - * @return mixed Result of method call; this returns a reference to support - * 'delayed returns' when in a batch context. - * See: http://wiki.developers.facebook.com/index.php/Using_batching_API - */ - public function &call_method($method, $params = array()) { - if ($this->format) { - $params['format'] = $this->format; - } - if (!$this->pending_batch()) { - if ($this->call_as_apikey) { - $params['call_as_apikey'] = $this->call_as_apikey; - } - $data = $this->post_request($method, $params); - $this->rawData = $data; - $result = $this->convert_result($data, $method, $params); - if (is_array($result) && isset($result['error_code'])) { - throw new FacebookRestClientException($result['error_msg'], - $result['error_code']); - } - } - else { - $result = null; - $batch_item = array('m' => $method, 'p' => $params, 'r' => & $result); - $this->batch_queue[] = $batch_item; - } - - return $result; - } - - protected function convert_result($data, $method, $params) { - $is_xml = (empty($params['format']) || - strtolower($params['format']) != 'json'); - return ($is_xml) ? $this->convert_xml_to_result($data, $method, $params) - : json_decode($data, true); - } - - /** - * Change the response format - * - * @param string $format The response format (json, xml) - */ - public function setFormat($format) { - $this->format = $format; - return $this; - } - - /** - * get the current response serialization format - * - * @return string 'xml', 'json', or null (which means 'xml') - */ - public function getFormat() { - return $this->format; - } - - /** - * Returns the raw JSON or XML output returned by the server in the most - * recent API call. - * - * @return string - */ - public function getRawData() { - return $this->rawData; - } - - /** - * Calls the specified file-upload POST method with the specified parameters - * - * @param string $method Name of the Facebook method to invoke - * @param array $params A map of param names => param values - * @param string $file A path to the file to upload (required) - * - * @return array A dictionary representing the response. - */ - public function call_upload_method($method, $params, $file, $server_addr = null) { - if (!$this->pending_batch()) { - if (!file_exists($file)) { - $code = - FacebookAPIErrorCodes::API_EC_PARAM; - $description = FacebookAPIErrorCodes::$api_error_descriptions[$code]; - throw new FacebookRestClientException($description, $code); - } - - if ($this->format) { - $params['format'] = $this->format; - } - $data = $this->post_upload_request($method, - $params, - $file, - $server_addr); - $result = $this->convert_result($data, $method, $params); - - if (is_array($result) && isset($result['error_code'])) { - throw new FacebookRestClientException($result['error_msg'], - $result['error_code']); - } - } - else { - $code = - FacebookAPIErrorCodes::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE; - $description = FacebookAPIErrorCodes::$api_error_descriptions[$code]; - throw new FacebookRestClientException($description, $code); - } - - return $result; - } - - protected function convert_xml_to_result($xml, $method, $params) { - $sxml = simplexml_load_string($xml); - $result = self::convert_simplexml_to_array($sxml); - - if (!empty($GLOBALS['facebook_config']['debug'])) { - // output the raw xml and its corresponding php object, for debugging: - print '
    '; - $this->cur_id++; - print $this->cur_id . ': Called ' . $method . ', show ' . - 'Params | '. - 'XML | '. - 'SXML | '. - 'PHP'; - print ''; - print ''; - print ''; - print ''; - print '
    '; - } - return $result; - } - - protected function finalize_params($method, $params) { - list($get, $post) = $this->add_standard_params($method, $params); - // we need to do this before signing the params - $this->convert_array_values_to_json($post); - $post['sig'] = Facebook::generate_sig(array_merge($get, $post), - $this->secret); - return array($get, $post); - } - - private function convert_array_values_to_json(&$params) { - foreach ($params as $key => &$val) { - if (is_array($val)) { - $val = json_encode($val); - } - } - } - - /** - * Add the generally required params to our request. - * Params method, api_key, and v should be sent over as get. - */ - private function add_standard_params($method, $params) { - $post = $params; - $get = array(); - if ($this->call_as_apikey) { - $get['call_as_apikey'] = $this->call_as_apikey; - } - if ($this->using_session_secret) { - $get['ss'] = '1'; - } - - $get['method'] = $method; - $get['session_key'] = $this->session_key; - $get['api_key'] = $this->api_key; - $post['call_id'] = microtime(true); - if ($post['call_id'] <= $this->last_call_id) { - $post['call_id'] = $this->last_call_id + 0.001; - } - $this->last_call_id = $post['call_id']; - if (isset($post['v'])) { - $get['v'] = $post['v']; - unset($post['v']); - } else { - $get['v'] = '1.0'; - } - if (isset($this->use_ssl_resources)) { - $post['return_ssl_resources'] = (bool) $this->use_ssl_resources; - } - return array($get, $post); - } - - private function create_url_string($params) { - $post_params = array(); - foreach ($params as $key => &$val) { - $post_params[] = $key.'='.urlencode($val); - } - return implode('&', $post_params); - } - - private function run_multipart_http_transaction($method, $params, $file, $server_addr) { - - // the format of this message is specified in RFC1867/RFC1341. - // we add twenty pseudo-random digits to the end of the boundary string. - $boundary = '--------------------------FbMuLtIpArT' . - sprintf("%010d", mt_rand()) . - sprintf("%010d", mt_rand()); - $content_type = 'multipart/form-data; boundary=' . $boundary; - // within the message, we prepend two extra hyphens. - $delimiter = '--' . $boundary; - $close_delimiter = $delimiter . '--'; - $content_lines = array(); - foreach ($params as $key => &$val) { - $content_lines[] = $delimiter; - $content_lines[] = 'Content-Disposition: form-data; name="' . $key . '"'; - $content_lines[] = ''; - $content_lines[] = $val; - } - // now add the file data - $content_lines[] = $delimiter; - $content_lines[] = - 'Content-Disposition: form-data; filename="' . $file . '"'; - $content_lines[] = 'Content-Type: application/octet-stream'; - $content_lines[] = ''; - $content_lines[] = file_get_contents($file); - $content_lines[] = $close_delimiter; - $content_lines[] = ''; - $content = implode("\r\n", $content_lines); - return $this->run_http_post_transaction($content_type, $content, $server_addr); - } - - public function post_request($method, $params) { - list($get, $post) = $this->finalize_params($method, $params); - $post_string = $this->create_url_string($post); - $get_string = $this->create_url_string($get); - $url_with_get = $this->server_addr . '?' . $get_string; - if ($this->use_curl_if_available && function_exists('curl_init')) { - $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion(); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url_with_get); - curl_setopt($ch, CURLOPT_POSTFIELDS, $post_string); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $useragent); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10); - curl_setopt($ch, CURLOPT_TIMEOUT, 30); - $result = $this->curl_exec($ch); - curl_close($ch); - } else { - $content_type = 'application/x-www-form-urlencoded'; - $content = $post_string; - $result = $this->run_http_post_transaction($content_type, - $content, - $url_with_get); - } - return $result; - } - - /** - * execute a curl transaction -- this exists mostly so subclasses can add - * extra options and/or process the response, if they wish. - * - * @param resource $ch a curl handle - */ - protected function curl_exec($ch) { - $result = curl_exec($ch); - return $result; - } - - protected function post_upload_request($method, $params, $file, $server_addr = null) { - $server_addr = $server_addr ? $server_addr : $this->server_addr; - list($get, $post) = $this->finalize_params($method, $params); - $get_string = $this->create_url_string($get); - $url_with_get = $server_addr . '?' . $get_string; - if ($this->use_curl_if_available && function_exists('curl_init')) { - // prepending '@' causes cURL to upload the file; the key is ignored. - $post['_file'] = '@' . $file; - $useragent = 'Facebook API PHP5 Client 1.1 (curl) ' . phpversion(); - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url_with_get); - // this has to come before the POSTFIELDS set! - curl_setopt($ch, CURLOPT_POST, 1); - // passing an array gets curl to use the multipart/form-data content type - curl_setopt($ch, CURLOPT_POSTFIELDS, $post); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_USERAGENT, $useragent); - $result = $this->curl_exec($ch); - curl_close($ch); - } else { - $result = $this->run_multipart_http_transaction($method, $post, - $file, $url_with_get); - } - return $result; - } - - private function run_http_post_transaction($content_type, $content, $server_addr) { - - $user_agent = 'Facebook API PHP5 Client 1.1 (non-curl) ' . phpversion(); - $content_length = strlen($content); - $context = - array('http' => - array('method' => 'POST', - 'user_agent' => $user_agent, - 'header' => 'Content-Type: ' . $content_type . "\r\n" . - 'Content-Length: ' . $content_length, - 'content' => $content)); - $context_id = stream_context_create($context); - $sock = fopen($server_addr, 'r', false, $context_id); - - $result = ''; - if ($sock) { - while (!feof($sock)) { - $result .= fgets($sock, 4096); - } - fclose($sock); - } - return $result; - } - - public static function convert_simplexml_to_array($sxml) { - $arr = array(); - if ($sxml) { - foreach ($sxml as $k => $v) { - if ($sxml['list']) { - $arr[] = self::convert_simplexml_to_array($v); - } else { - $arr[$k] = self::convert_simplexml_to_array($v); - } - } - } - if (sizeof($arr) > 0) { - return $arr; - } else { - return (string)$sxml; - } - } - - protected function get_uid($uid) { - return $uid ? $uid : $this->user; - } -} - - -class FacebookRestClientException extends Exception { -} - -// Supporting methods and values------ - -/** - * Error codes and descriptions for the Facebook API. - */ - -class FacebookAPIErrorCodes { - - const API_EC_SUCCESS = 0; - - /* - * GENERAL ERRORS - */ - const API_EC_UNKNOWN = 1; - const API_EC_SERVICE = 2; - const API_EC_METHOD = 3; - const API_EC_TOO_MANY_CALLS = 4; - const API_EC_BAD_IP = 5; - const API_EC_HOST_API = 6; - const API_EC_HOST_UP = 7; - const API_EC_SECURE = 8; - const API_EC_RATE = 9; - const API_EC_PERMISSION_DENIED = 10; - const API_EC_DEPRECATED = 11; - const API_EC_VERSION = 12; - const API_EC_INTERNAL_FQL_ERROR = 13; - const API_EC_HOST_PUP = 14; - const API_EC_SESSION_SECRET_NOT_ALLOWED = 15; - const API_EC_HOST_READONLY = 16; - - /* - * PARAMETER ERRORS - */ - const API_EC_PARAM = 100; - const API_EC_PARAM_API_KEY = 101; - const API_EC_PARAM_SESSION_KEY = 102; - const API_EC_PARAM_CALL_ID = 103; - const API_EC_PARAM_SIGNATURE = 104; - const API_EC_PARAM_TOO_MANY = 105; - const API_EC_PARAM_USER_ID = 110; - const API_EC_PARAM_USER_FIELD = 111; - const API_EC_PARAM_SOCIAL_FIELD = 112; - const API_EC_PARAM_EMAIL = 113; - const API_EC_PARAM_USER_ID_LIST = 114; - const API_EC_PARAM_FIELD_LIST = 115; - const API_EC_PARAM_ALBUM_ID = 120; - const API_EC_PARAM_PHOTO_ID = 121; - const API_EC_PARAM_FEED_PRIORITY = 130; - const API_EC_PARAM_CATEGORY = 140; - const API_EC_PARAM_SUBCATEGORY = 141; - const API_EC_PARAM_TITLE = 142; - const API_EC_PARAM_DESCRIPTION = 143; - const API_EC_PARAM_BAD_JSON = 144; - const API_EC_PARAM_BAD_EID = 150; - const API_EC_PARAM_UNKNOWN_CITY = 151; - const API_EC_PARAM_BAD_PAGE_TYPE = 152; - const API_EC_PARAM_BAD_LOCALE = 170; - const API_EC_PARAM_BLOCKED_NOTIFICATION = 180; - - /* - * USER PERMISSIONS ERRORS - */ - const API_EC_PERMISSION = 200; - const API_EC_PERMISSION_USER = 210; - const API_EC_PERMISSION_NO_DEVELOPERS = 211; - const API_EC_PERMISSION_OFFLINE_ACCESS = 212; - const API_EC_PERMISSION_ALBUM = 220; - const API_EC_PERMISSION_PHOTO = 221; - const API_EC_PERMISSION_MESSAGE = 230; - const API_EC_PERMISSION_OTHER_USER = 240; - const API_EC_PERMISSION_STATUS_UPDATE = 250; - const API_EC_PERMISSION_PHOTO_UPLOAD = 260; - const API_EC_PERMISSION_VIDEO_UPLOAD = 261; - const API_EC_PERMISSION_SMS = 270; - const API_EC_PERMISSION_CREATE_LISTING = 280; - const API_EC_PERMISSION_CREATE_NOTE = 281; - const API_EC_PERMISSION_SHARE_ITEM = 282; - const API_EC_PERMISSION_EVENT = 290; - const API_EC_PERMISSION_LARGE_FBML_TEMPLATE = 291; - const API_EC_PERMISSION_LIVEMESSAGE = 292; - const API_EC_PERMISSION_CREATE_EVENT = 296; - const API_EC_PERMISSION_RSVP_EVENT = 299; - - /* - * DATA EDIT ERRORS - */ - const API_EC_EDIT = 300; - const API_EC_EDIT_USER_DATA = 310; - const API_EC_EDIT_PHOTO = 320; - const API_EC_EDIT_ALBUM_SIZE = 321; - const API_EC_EDIT_PHOTO_TAG_SUBJECT = 322; - const API_EC_EDIT_PHOTO_TAG_PHOTO = 323; - const API_EC_EDIT_PHOTO_FILE = 324; - const API_EC_EDIT_PHOTO_PENDING_LIMIT = 325; - const API_EC_EDIT_PHOTO_TAG_LIMIT = 326; - const API_EC_EDIT_ALBUM_REORDER_PHOTO_NOT_IN_ALBUM = 327; - const API_EC_EDIT_ALBUM_REORDER_TOO_FEW_PHOTOS = 328; - - const API_EC_MALFORMED_MARKUP = 329; - const API_EC_EDIT_MARKUP = 330; - - const API_EC_EDIT_FEED_TOO_MANY_USER_CALLS = 340; - const API_EC_EDIT_FEED_TOO_MANY_USER_ACTION_CALLS = 341; - const API_EC_EDIT_FEED_TITLE_LINK = 342; - const API_EC_EDIT_FEED_TITLE_LENGTH = 343; - const API_EC_EDIT_FEED_TITLE_NAME = 344; - const API_EC_EDIT_FEED_TITLE_BLANK = 345; - const API_EC_EDIT_FEED_BODY_LENGTH = 346; - const API_EC_EDIT_FEED_PHOTO_SRC = 347; - const API_EC_EDIT_FEED_PHOTO_LINK = 348; - - const API_EC_EDIT_VIDEO_SIZE = 350; - const API_EC_EDIT_VIDEO_INVALID_FILE = 351; - const API_EC_EDIT_VIDEO_INVALID_TYPE = 352; - const API_EC_EDIT_VIDEO_FILE = 353; - - const API_EC_EDIT_FEED_TITLE_ARRAY = 360; - const API_EC_EDIT_FEED_TITLE_PARAMS = 361; - const API_EC_EDIT_FEED_BODY_ARRAY = 362; - const API_EC_EDIT_FEED_BODY_PARAMS = 363; - const API_EC_EDIT_FEED_PHOTO = 364; - const API_EC_EDIT_FEED_TEMPLATE = 365; - const API_EC_EDIT_FEED_TARGET = 366; - const API_EC_EDIT_FEED_MARKUP = 367; - - /** - * SESSION ERRORS - */ - const API_EC_SESSION_TIMED_OUT = 450; - const API_EC_SESSION_METHOD = 451; - const API_EC_SESSION_INVALID = 452; - const API_EC_SESSION_REQUIRED = 453; - const API_EC_SESSION_REQUIRED_FOR_SECRET = 454; - const API_EC_SESSION_CANNOT_USE_SESSION_SECRET = 455; - - - /** - * FQL ERRORS - */ - const FQL_EC_UNKNOWN_ERROR = 600; - const FQL_EC_PARSER = 601; // backwards compatibility - const FQL_EC_PARSER_ERROR = 601; - const FQL_EC_UNKNOWN_FIELD = 602; - const FQL_EC_UNKNOWN_TABLE = 603; - const FQL_EC_NOT_INDEXABLE = 604; // backwards compatibility - const FQL_EC_NO_INDEX = 604; - const FQL_EC_UNKNOWN_FUNCTION = 605; - const FQL_EC_INVALID_PARAM = 606; - const FQL_EC_INVALID_FIELD = 607; - const FQL_EC_INVALID_SESSION = 608; - const FQL_EC_UNSUPPORTED_APP_TYPE = 609; - const FQL_EC_SESSION_SECRET_NOT_ALLOWED = 610; - const FQL_EC_DEPRECATED_TABLE = 611; - const FQL_EC_EXTENDED_PERMISSION = 612; - const FQL_EC_RATE_LIMIT_EXCEEDED = 613; - const FQL_EC_UNRESOLVED_DEPENDENCY = 614; - const FQL_EC_INVALID_SEARCH = 615; - const FQL_EC_CONTAINS_ERROR = 616; - - const API_EC_REF_SET_FAILED = 700; - - /** - * DATA STORE API ERRORS - */ - const API_EC_DATA_UNKNOWN_ERROR = 800; - const API_EC_DATA_INVALID_OPERATION = 801; - const API_EC_DATA_QUOTA_EXCEEDED = 802; - const API_EC_DATA_OBJECT_NOT_FOUND = 803; - const API_EC_DATA_OBJECT_ALREADY_EXISTS = 804; - const API_EC_DATA_DATABASE_ERROR = 805; - const API_EC_DATA_CREATE_TEMPLATE_ERROR = 806; - const API_EC_DATA_TEMPLATE_EXISTS_ERROR = 807; - const API_EC_DATA_TEMPLATE_HANDLE_TOO_LONG = 808; - const API_EC_DATA_TEMPLATE_HANDLE_ALREADY_IN_USE = 809; - const API_EC_DATA_TOO_MANY_TEMPLATE_BUNDLES = 810; - const API_EC_DATA_MALFORMED_ACTION_LINK = 811; - const API_EC_DATA_TEMPLATE_USES_RESERVED_TOKEN = 812; - - /* - * APPLICATION INFO ERRORS - */ - const API_EC_NO_SUCH_APP = 900; - - /* - * BATCH ERRORS - */ - const API_EC_BATCH_TOO_MANY_ITEMS = 950; - const API_EC_BATCH_ALREADY_STARTED = 951; - const API_EC_BATCH_NOT_STARTED = 952; - const API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE = 953; - - /* - * EVENT API ERRORS - */ - const API_EC_EVENT_INVALID_TIME = 1000; - const API_EC_EVENT_NAME_LOCKED = 1001; - - /* - * INFO BOX ERRORS - */ - const API_EC_INFO_NO_INFORMATION = 1050; - const API_EC_INFO_SET_FAILED = 1051; - - /* - * LIVEMESSAGE API ERRORS - */ - const API_EC_LIVEMESSAGE_SEND_FAILED = 1100; - const API_EC_LIVEMESSAGE_EVENT_NAME_TOO_LONG = 1101; - const API_EC_LIVEMESSAGE_MESSAGE_TOO_LONG = 1102; - - /* - * PAYMENTS API ERRORS - */ - const API_EC_PAYMENTS_UNKNOWN = 1150; - const API_EC_PAYMENTS_APP_INVALID = 1151; - const API_EC_PAYMENTS_DATABASE = 1152; - const API_EC_PAYMENTS_PERMISSION_DENIED = 1153; - const API_EC_PAYMENTS_APP_NO_RESPONSE = 1154; - const API_EC_PAYMENTS_APP_ERROR_RESPONSE = 1155; - const API_EC_PAYMENTS_INVALID_ORDER = 1156; - const API_EC_PAYMENTS_INVALID_PARAM = 1157; - const API_EC_PAYMENTS_INVALID_OPERATION = 1158; - const API_EC_PAYMENTS_PAYMENT_FAILED = 1159; - const API_EC_PAYMENTS_DISABLED = 1160; - - /* - * CONNECT SESSION ERRORS - */ - const API_EC_CONNECT_FEED_DISABLED = 1300; - - /* - * Platform tag bundles errors - */ - const API_EC_TAG_BUNDLE_QUOTA = 1400; - - /* - * SHARE - */ - const API_EC_SHARE_BAD_URL = 1500; - - /* - * NOTES - */ - const API_EC_NOTE_CANNOT_MODIFY = 1600; - - /* - * COMMENTS - */ - const API_EC_COMMENTS_UNKNOWN = 1700; - const API_EC_COMMENTS_POST_TOO_LONG = 1701; - const API_EC_COMMENTS_DB_DOWN = 1702; - const API_EC_COMMENTS_INVALID_XID = 1703; - const API_EC_COMMENTS_INVALID_UID = 1704; - const API_EC_COMMENTS_INVALID_POST = 1705; - const API_EC_COMMENTS_INVALID_REMOVE = 1706; - - /* - * GIFTS - */ - const API_EC_GIFTS_UNKNOWN = 1900; - - /* - * APPLICATION MORATORIUM ERRORS - */ - const API_EC_DISABLED_ALL = 2000; - const API_EC_DISABLED_STATUS = 2001; - const API_EC_DISABLED_FEED_STORIES = 2002; - const API_EC_DISABLED_NOTIFICATIONS = 2003; - const API_EC_DISABLED_REQUESTS = 2004; - const API_EC_DISABLED_EMAIL = 2005; - - /** - * This array is no longer maintained; to view the description of an error - * code, please look at the message element of the API response or visit - * the developer wiki at http://wiki.developers.facebook.com/. - */ - public static $api_error_descriptions = array( - self::API_EC_SUCCESS => 'Success', - self::API_EC_UNKNOWN => 'An unknown error occurred', - self::API_EC_SERVICE => 'Service temporarily unavailable', - self::API_EC_METHOD => 'Unknown method', - self::API_EC_TOO_MANY_CALLS => 'Application request limit reached', - self::API_EC_BAD_IP => 'Unauthorized source IP address', - self::API_EC_PARAM => 'Invalid parameter', - self::API_EC_PARAM_API_KEY => 'Invalid API key', - self::API_EC_PARAM_SESSION_KEY => 'Session key invalid or no longer valid', - self::API_EC_PARAM_CALL_ID => 'Call_id must be greater than previous', - self::API_EC_PARAM_SIGNATURE => 'Incorrect signature', - self::API_EC_PARAM_USER_ID => 'Invalid user id', - self::API_EC_PARAM_USER_FIELD => 'Invalid user info field', - self::API_EC_PARAM_SOCIAL_FIELD => 'Invalid user field', - self::API_EC_PARAM_USER_ID_LIST => 'Invalid user id list', - self::API_EC_PARAM_FIELD_LIST => 'Invalid field list', - self::API_EC_PARAM_ALBUM_ID => 'Invalid album id', - self::API_EC_PARAM_BAD_EID => 'Invalid eid', - self::API_EC_PARAM_UNKNOWN_CITY => 'Unknown city', - self::API_EC_PERMISSION => 'Permissions error', - self::API_EC_PERMISSION_USER => 'User not visible', - self::API_EC_PERMISSION_NO_DEVELOPERS => 'Application has no developers', - self::API_EC_PERMISSION_ALBUM => 'Album not visible', - self::API_EC_PERMISSION_PHOTO => 'Photo not visible', - self::API_EC_PERMISSION_EVENT => 'Creating and modifying events required the extended permission create_event', - self::API_EC_PERMISSION_RSVP_EVENT => 'RSVPing to events required the extended permission rsvp_event', - self::API_EC_EDIT_ALBUM_SIZE => 'Album is full', - self::FQL_EC_PARSER => 'FQL: Parser Error', - self::FQL_EC_UNKNOWN_FIELD => 'FQL: Unknown Field', - self::FQL_EC_UNKNOWN_TABLE => 'FQL: Unknown Table', - self::FQL_EC_NOT_INDEXABLE => 'FQL: Statement not indexable', - self::FQL_EC_UNKNOWN_FUNCTION => 'FQL: Attempted to call unknown function', - self::FQL_EC_INVALID_PARAM => 'FQL: Invalid parameter passed in', - self::API_EC_DATA_UNKNOWN_ERROR => 'Unknown data store API error', - self::API_EC_DATA_INVALID_OPERATION => 'Invalid operation', - self::API_EC_DATA_QUOTA_EXCEEDED => 'Data store allowable quota was exceeded', - self::API_EC_DATA_OBJECT_NOT_FOUND => 'Specified object cannot be found', - self::API_EC_DATA_OBJECT_ALREADY_EXISTS => 'Specified object already exists', - self::API_EC_DATA_DATABASE_ERROR => 'A database error occurred. Please try again', - self::API_EC_BATCH_ALREADY_STARTED => 'begin_batch already called, please make sure to call end_batch first', - self::API_EC_BATCH_NOT_STARTED => 'end_batch called before begin_batch', - self::API_EC_BATCH_METHOD_NOT_ALLOWED_IN_BATCH_MODE => 'This method is not allowed in batch mode' - ); -} diff --git a/facebook/jsonwrapper/JSON/JSON.php b/facebook/jsonwrapper/JSON/JSON.php deleted file mode 100644 index 0cddbdd..0000000 --- a/facebook/jsonwrapper/JSON/JSON.php +++ /dev/null @@ -1,806 +0,0 @@ - - * @author Matt Knapp - * @author Brett Stimmerman - * @copyright 2005 Michal Migurski - * @version CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $ - * @license http://www.opensource.org/licenses/bsd-license.php - * @link http://pear.php.net/pepr/pepr-proposal-show.php?id=198 - */ - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_SLICE', 1); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_STR', 2); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_ARR', 3); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_OBJ', 4); - -/** - * Marker constant for Services_JSON::decode(), used to flag stack state - */ -define('SERVICES_JSON_IN_CMT', 5); - -/** - * Behavior switch for Services_JSON::decode() - */ -define('SERVICES_JSON_LOOSE_TYPE', 16); - -/** - * Behavior switch for Services_JSON::decode() - */ -define('SERVICES_JSON_SUPPRESS_ERRORS', 32); - -/** - * Converts to and from JSON format. - * - * Brief example of use: - * - * - * // create a new instance of Services_JSON - * $json = new Services_JSON(); - * - * // convert a complexe value to JSON notation, and send it to the browser - * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4))); - * $output = $json->encode($value); - * - * print($output); - * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]] - * - * // accept incoming POST data, assumed to be in JSON notation - * $input = file_get_contents('php://input', 1000000); - * $value = $json->decode($input); - * - */ -class Services_JSON -{ - /** - * constructs a new JSON instance - * - * @param int $use object behavior flags; combine with boolean-OR - * - * possible values: - * - SERVICES_JSON_LOOSE_TYPE: loose typing. - * "{...}" syntax creates associative arrays - * instead of objects in decode(). - * - SERVICES_JSON_SUPPRESS_ERRORS: error suppression. - * Values which can't be encoded (e.g. resources) - * appear as NULL instead of throwing errors. - * By default, a deeply-nested resource will - * bubble up with an error, so all return values - * from encode() should be checked with isError() - */ - function Services_JSON($use = 0) - { - $this->use = $use; - } - - /** - * convert a string from one UTF-16 char to one UTF-8 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf16 UTF-16 character - * @return string UTF-8 character - * @access private - */ - function utf162utf8($utf16) - { - // oh please oh please oh please oh please oh please - if(function_exists('mb_convert_encoding')) { - return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16'); - } - - $bytes = (ord($utf16{0}) << 8) | ord($utf16{1}); - - switch(true) { - case ((0x7F & $bytes) == $bytes): - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x7F & $bytes); - - case (0x07FF & $bytes) == $bytes: - // return a 2-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xC0 | (($bytes >> 6) & 0x1F)) - . chr(0x80 | ($bytes & 0x3F)); - - case (0xFFFF & $bytes) == $bytes: - // return a 3-byte UTF-8 character - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0xE0 | (($bytes >> 12) & 0x0F)) - . chr(0x80 | (($bytes >> 6) & 0x3F)) - . chr(0x80 | ($bytes & 0x3F)); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * convert a string from one UTF-8 char to one UTF-16 char - * - * Normally should be handled by mb_convert_encoding, but - * provides a slower PHP-only method for installations - * that lack the multibye string extension. - * - * @param string $utf8 UTF-8 character - * @return string UTF-16 character - * @access private - */ - function utf82utf16($utf8) - { - // oh please oh please oh please oh please oh please - if(function_exists('mb_convert_encoding')) { - return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8'); - } - - switch(strlen($utf8)) { - case 1: - // this case should never be reached, because we are in ASCII range - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return $utf8; - - case 2: - // return a UTF-16 character from a 2-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr(0x07 & (ord($utf8{0}) >> 2)) - . chr((0xC0 & (ord($utf8{0}) << 6)) - | (0x3F & ord($utf8{1}))); - - case 3: - // return a UTF-16 character from a 3-byte UTF-8 char - // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - return chr((0xF0 & (ord($utf8{0}) << 4)) - | (0x0F & (ord($utf8{1}) >> 2))) - . chr((0xC0 & (ord($utf8{1}) << 6)) - | (0x7F & ord($utf8{2}))); - } - - // ignoring UTF-32 for now, sorry - return ''; - } - - /** - * encodes an arbitrary variable into JSON format - * - * @param mixed $var any number, boolean, string, array, or object to be encoded. - * see argument 1 to Services_JSON() above for array-parsing behavior. - * if var is a strng, note that encode() always expects it - * to be in ASCII or UTF-8 format! - * - * @return mixed JSON string representation of input var or an error if a problem occurs - * @access public - */ - function encode($var) - { - switch (gettype($var)) { - case 'boolean': - return $var ? 'true' : 'false'; - - case 'NULL': - return 'null'; - - case 'integer': - return (int) $var; - - case 'double': - case 'float': - return (float) $var; - - case 'string': - // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT - $ascii = ''; - $strlen_var = strlen($var); - - /* - * Iterate over every character in the string, - * escaping with a slash or encoding to UTF-8 where necessary - */ - for ($c = 0; $c < $strlen_var; ++$c) { - - $ord_var_c = ord($var{$c}); - - switch (true) { - case $ord_var_c == 0x08: - $ascii .= '\b'; - break; - case $ord_var_c == 0x09: - $ascii .= '\t'; - break; - case $ord_var_c == 0x0A: - $ascii .= '\n'; - break; - case $ord_var_c == 0x0C: - $ascii .= '\f'; - break; - case $ord_var_c == 0x0D: - $ascii .= '\r'; - break; - - case $ord_var_c == 0x22: - case $ord_var_c == 0x2F: - case $ord_var_c == 0x5C: - // double quote, slash, slosh - $ascii .= '\\'.$var{$c}; - break; - - case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)): - // characters U-00000000 - U-0000007F (same as ASCII) - $ascii .= $var{$c}; - break; - - case (($ord_var_c & 0xE0) == 0xC0): - // characters U-00000080 - U-000007FF, mask 110XXXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, ord($var{$c + 1})); - $c += 1; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF0) == 0xE0): - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2})); - $c += 2; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xF8) == 0xF0): - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3})); - $c += 3; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFC) == 0xF8): - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4})); - $c += 4; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - - case (($ord_var_c & 0xFE) == 0xFC): - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $char = pack('C*', $ord_var_c, - ord($var{$c + 1}), - ord($var{$c + 2}), - ord($var{$c + 3}), - ord($var{$c + 4}), - ord($var{$c + 5})); - $c += 5; - $utf16 = $this->utf82utf16($char); - $ascii .= sprintf('\u%04s', bin2hex($utf16)); - break; - } - } - - return '"'.$ascii.'"'; - - case 'array': - /* - * As per JSON spec if any array key is not an integer - * we must treat the the whole array as an object. We - * also try to catch a sparsely populated associative - * array with numeric keys here because some JS engines - * will create an array with empty indexes up to - * max_index which can cause memory issues and because - * the keys, which may be relevant, will be remapped - * otherwise. - * - * As per the ECMA and JSON specification an object may - * have any string as a property. Unfortunately due to - * a hole in the ECMA specification if the key is a - * ECMA reserved word or starts with a digit the - * parameter is only accessible using ECMAScript's - * bracket notation. - */ - - // treat as a JSON object - if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) { - $properties = array_map(array($this, 'name_value'), - array_keys($var), - array_values($var)); - - foreach($properties as $property) { - if(Services_JSON::isError($property)) { - return $property; - } - } - - return '{' . join(',', $properties) . '}'; - } - - // treat it like a regular array - $elements = array_map(array($this, 'encode'), $var); - - foreach($elements as $element) { - if(Services_JSON::isError($element)) { - return $element; - } - } - - return '[' . join(',', $elements) . ']'; - - case 'object': - $vars = get_object_vars($var); - - $properties = array_map(array($this, 'name_value'), - array_keys($vars), - array_values($vars)); - - foreach($properties as $property) { - if(Services_JSON::isError($property)) { - return $property; - } - } - - return '{' . join(',', $properties) . '}'; - - default: - return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS) - ? 'null' - : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string"); - } - } - - /** - * array-walking function for use in generating JSON-formatted name-value pairs - * - * @param string $name name of key to use - * @param mixed $value reference to an array element to be encoded - * - * @return string JSON-formatted name-value pair, like '"name":value' - * @access private - */ - function name_value($name, $value) - { - $encoded_value = $this->encode($value); - - if(Services_JSON::isError($encoded_value)) { - return $encoded_value; - } - - return $this->encode(strval($name)) . ':' . $encoded_value; - } - - /** - * reduce a string by removing leading and trailing comments and whitespace - * - * @param $str string string value to strip of comments and whitespace - * - * @return string string value stripped of comments and whitespace - * @access private - */ - function reduce_string($str) - { - $str = preg_replace(array( - - // eliminate single line comments in '// ...' form - '#^\s*//(.+)$#m', - - // eliminate multi-line comments in '/* ... */' form, at start of string - '#^\s*/\*(.+)\*/#Us', - - // eliminate multi-line comments in '/* ... */' form, at end of string - '#/\*(.+)\*/\s*$#Us' - - ), '', $str); - - // eliminate extraneous space - return trim($str); - } - - /** - * decodes a JSON string into appropriate variable - * - * @param string $str JSON-formatted string - * - * @return mixed number, boolean, string, array, or object - * corresponding to given JSON input string. - * See argument 1 to Services_JSON() above for object-output behavior. - * Note that decode() always returns strings - * in ASCII or UTF-8 format! - * @access public - */ - function decode($str) - { - $str = $this->reduce_string($str); - - switch (strtolower($str)) { - case 'true': - return true; - - case 'false': - return false; - - case 'null': - return null; - - default: - $m = array(); - - if (is_numeric($str)) { - // Lookie-loo, it's a number - - // This would work on its own, but I'm trying to be - // good about returning integers where appropriate: - // return (float)$str; - - // Return float or int, as appropriate - return ((float)$str == (integer)$str) - ? (integer)$str - : (float)$str; - - } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) { - // STRINGS RETURNED IN UTF-8 FORMAT - $delim = substr($str, 0, 1); - $chrs = substr($str, 1, -1); - $utf8 = ''; - $strlen_chrs = strlen($chrs); - - for ($c = 0; $c < $strlen_chrs; ++$c) { - - $substr_chrs_c_2 = substr($chrs, $c, 2); - $ord_chrs_c = ord($chrs{$c}); - - switch (true) { - case $substr_chrs_c_2 == '\b': - $utf8 .= chr(0x08); - ++$c; - break; - case $substr_chrs_c_2 == '\t': - $utf8 .= chr(0x09); - ++$c; - break; - case $substr_chrs_c_2 == '\n': - $utf8 .= chr(0x0A); - ++$c; - break; - case $substr_chrs_c_2 == '\f': - $utf8 .= chr(0x0C); - ++$c; - break; - case $substr_chrs_c_2 == '\r': - $utf8 .= chr(0x0D); - ++$c; - break; - - case $substr_chrs_c_2 == '\\"': - case $substr_chrs_c_2 == '\\\'': - case $substr_chrs_c_2 == '\\\\': - case $substr_chrs_c_2 == '\\/': - if (($delim == '"' && $substr_chrs_c_2 != '\\\'') || - ($delim == "'" && $substr_chrs_c_2 != '\\"')) { - $utf8 .= $chrs{++$c}; - } - break; - - case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)): - // single, escaped unicode character - $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2))) - . chr(hexdec(substr($chrs, ($c + 4), 2))); - $utf8 .= $this->utf162utf8($utf16); - $c += 5; - break; - - case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F): - $utf8 .= $chrs{$c}; - break; - - case ($ord_chrs_c & 0xE0) == 0xC0: - // characters U-00000080 - U-000007FF, mask 110XXXXX - //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 2); - ++$c; - break; - - case ($ord_chrs_c & 0xF0) == 0xE0: - // characters U-00000800 - U-0000FFFF, mask 1110XXXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 3); - $c += 2; - break; - - case ($ord_chrs_c & 0xF8) == 0xF0: - // characters U-00010000 - U-001FFFFF, mask 11110XXX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 4); - $c += 3; - break; - - case ($ord_chrs_c & 0xFC) == 0xF8: - // characters U-00200000 - U-03FFFFFF, mask 111110XX - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 5); - $c += 4; - break; - - case ($ord_chrs_c & 0xFE) == 0xFC: - // characters U-04000000 - U-7FFFFFFF, mask 1111110X - // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8 - $utf8 .= substr($chrs, $c, 6); - $c += 5; - break; - - } - - } - - return $utf8; - - } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) { - // array, or object notation - - if ($str{0} == '[') { - $stk = array(SERVICES_JSON_IN_ARR); - $arr = array(); - } else { - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $stk = array(SERVICES_JSON_IN_OBJ); - $obj = array(); - } else { - $stk = array(SERVICES_JSON_IN_OBJ); - $obj = new stdClass(); - } - } - - array_push($stk, array('what' => SERVICES_JSON_SLICE, - 'where' => 0, - 'delim' => false)); - - $chrs = substr($str, 1, -1); - $chrs = $this->reduce_string($chrs); - - if ($chrs == '') { - if (reset($stk) == SERVICES_JSON_IN_ARR) { - return $arr; - - } else { - return $obj; - - } - } - - //print("\nparsing {$chrs}\n"); - - $strlen_chrs = strlen($chrs); - - for ($c = 0; $c <= $strlen_chrs; ++$c) { - - $top = end($stk); - $substr_chrs_c_2 = substr($chrs, $c, 2); - - if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) { - // found a comma that is not inside a string, array, etc., - // OR we've reached the end of the character list - $slice = substr($chrs, $top['where'], ($c - $top['where'])); - array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false)); - //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - if (reset($stk) == SERVICES_JSON_IN_ARR) { - // we are in an array, so just push an element onto the stack - array_push($arr, $this->decode($slice)); - - } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { - // we are in an object, so figure - // out the property name and set an - // element in an associative array, - // for now - $parts = array(); - - if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { - // "name":value pair - $key = $this->decode($parts[1]); - $val = $this->decode($parts[2]); - - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) { - // name:value pair, where name is unquoted - $key = $parts[1]; - $val = $this->decode($parts[2]); - - if ($this->use & SERVICES_JSON_LOOSE_TYPE) { - $obj[$key] = $val; - } else { - $obj->$key = $val; - } - } - - } - - } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) { - // found a quote, and we are not inside a string - array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c})); - //print("Found start of string at {$c}\n"); - - } elseif (($chrs{$c} == $top['delim']) && - ($top['what'] == SERVICES_JSON_IN_STR) && - ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) { - // found a quote, we're in a string, and it's not escaped - // we know that it's not escaped becase there is _not_ an - // odd number of backslashes at the end of the string so far - array_pop($stk); - //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '[') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a left-bracket, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false)); - //print("Found start of array at {$c}\n"); - - } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) { - // found a right-bracket, and we're in an array - array_pop($stk); - //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($chrs{$c} == '{') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a left-brace, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false)); - //print("Found start of object at {$c}\n"); - - } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) { - // found a right-brace, and we're in an object - array_pop($stk); - //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } elseif (($substr_chrs_c_2 == '/*') && - in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) { - // found a comment start, and we are in an array, object, or slice - array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false)); - $c++; - //print("Found start of comment at {$c}\n"); - - } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) { - // found a comment end, and we're in one now - array_pop($stk); - $c++; - - for ($i = $top['where']; $i <= $c; ++$i) - $chrs = substr_replace($chrs, ' ', $i, 1); - - //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n"); - - } - - } - - if (reset($stk) == SERVICES_JSON_IN_ARR) { - return $arr; - - } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) { - return $obj; - - } - - } - } - } - - /** - * @todo Ultimately, this should just call PEAR::isError() - */ - function isError($data, $code = null) - { - if (class_exists('pear')) { - return PEAR::isError($data, $code); - } elseif (is_object($data) && (get_class($data) == 'services_json_error' || - is_subclass_of($data, 'services_json_error'))) { - return true; - } - - return false; - } -} - -if (class_exists('PEAR_Error')) { - - class Services_JSON_Error extends PEAR_Error - { - function Services_JSON_Error($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - parent::PEAR_Error($message, $code, $mode, $options, $userinfo); - } - } - -} else { - - /** - * @todo Ultimately, this class shall be descended from PEAR_Error - */ - class Services_JSON_Error - { - function Services_JSON_Error($message = 'unknown error', $code = null, - $mode = null, $options = null, $userinfo = null) - { - - } - } - -} - -?> diff --git a/facebook/jsonwrapper/JSON/LICENSE b/facebook/jsonwrapper/JSON/LICENSE deleted file mode 100644 index 4ae6bef..0000000 --- a/facebook/jsonwrapper/JSON/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - -Redistributions of source code must retain the above copyright notice, -this list of conditions and the following disclaimer. - -Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN -NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/facebook/jsonwrapper/jsonwrapper.php b/facebook/jsonwrapper/jsonwrapper.php deleted file mode 100644 index 29509de..0000000 --- a/facebook/jsonwrapper/jsonwrapper.php +++ /dev/null @@ -1,6 +0,0 @@ - diff --git a/facebook/jsonwrapper/jsonwrapper_inner.php b/facebook/jsonwrapper/jsonwrapper_inner.php deleted file mode 100644 index 36a3f28..0000000 --- a/facebook/jsonwrapper/jsonwrapper_inner.php +++ /dev/null @@ -1,23 +0,0 @@ -encode($arg); -} - -function json_decode($arg) -{ - global $services_json; - if (!isset($services_json)) { - $services_json = new Services_JSON(); - } - return $services_json->decode($arg); -} - -?> diff --git a/include/initialize.php b/include/initialize.php new file mode 100644 index 0000000..87efb1a --- /dev/null +++ b/include/initialize.php @@ -0,0 +1,59 @@ +setLogToDb($database); + +//code for a GUI might set this to true in order to prevent logging the error but instead letting it propagate to the GUI +$propagateExceptions = false; + +//when a PHP warning comes along it is simply logged +set_error_handler('customWarningHandler', E_WARNING); +function customWarningHandler($errNo, $errMsg) { + $logger->warning('Error No '.$errNo.': '.$errMsg.'. In File '.$errFile .'on Line '.$errLine); +} + +//when a PHP error comes along an Exception is thrown +set_error_handler('customErrorHandler', E_ALL & ~E_WARNING); +function customErrorHandler($errNo, $errMsg, $errFile, $errLine) { + throw new Exception('Error No '.$errNo.': '.$errMsg.'. In File '.$errFile .' on Line '.$errLine); +} + +//ignore deprecated warnings +set_error_handler('customDeprecatedHandler', E_DEPRECATED); +function customDeprecatedHandler($errNo, $errMsg) { + +} + + + +/* FACEBOOK */ + +require_once 'lib/facebook-php-sdk/src/facebook.php'; + +$facebook = new Facebook(array( + 'appId' => $config['facebookAppId'], + 'secret' => $config['facebookSecret'], + 'cookie' => true, + 'fileUpload' => true + )); + + +?> diff --git a/index.php b/index.php index f3e22ed..ef8436d 100644 --- a/index.php +++ b/index.php @@ -1,379 +1,69 @@ getUser(); - You should have received a copy of the GNU Affero General Public License - along with iCalendar-to-Facebook-Event. If not, see . -*/ +$logger->setCurrentFbUserId($fbUserId); +if ($fbUserId) { + //if user logged in store access token in db -mb_internal_encoding('UTF-8'); -///////////////////////////////// -// CONFIGURATION -///////////////////////////////// -require_once 'config.php'; -require_once 'facebook/facebook.php'; - -$facebook = new Facebook($appapikey, $appsecret); -$user_id = $facebook->require_login("offline_access, create_event"); - -//Connect to Database -$con = mysql_connect($host,$db_user,$db_password); -if (!$con) - die('Could not connect: '. mysql_error()); -mysql_query("SET NAMES 'utf8';", $con); -mysql_query("SET CHARACTER SET 'utf8';", $con); -mysql_select_db($database_name,$con); - -function __autoload($class_name) { - require_once 'classes/' . $class_name . '.php'; -} - -// EXTERNAL FILES -//echo ''; - -//for testing the following code circumvents facebook's cache -echo ""; - - -//whether offline_access and create_event permissions are set -try{ - $perms = $facebook->api_client->users_hasAppPermission('offline_access') && $facebook->api_client->users_hasAppPermission('create_event'); -} -catch(Exception $e){ - $perms = false; -} - -//store session_key in db -if (isset($_POST["fb_sig_session_key"]) && $perms) { - $session_key = $_POST["fb_sig_session_key"]; - - $user_key = mysql_query("select session_key from users where user_id ='$user_id'") or trigger_error(mysql_error()); - if (mysql_num_rows($user_key) == 0) { - mysql_query("INSERT INTO users (user_id, session_key) VALUES ('$user_id', '$session_key')") or trigger_error(mysql_error()); + if (!$config['debugWithoutFacebook']) { + $token = $facebook->getAccessToken(); + $database->storeAccessToken($token, $fbUserId); } - elseif(mysql_result($user_key, 0) != $session_key) { - mysql_query("UPDATE users SET session_key='$session_key' WHERE user_id='$user_id'") or trigger_error(mysql_error()); - } -} - - -///////////////////////////////// -// GUI -///////////////////////////////// -?> - - This app doesn't work in Internet Explorer 5 to 8. Please use a decent browser like Firefox, Safari or Google Chrome. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

    You need to give this app permission to change your RSVP or change your settings.

    -
    - -
    If you selected Wall you need to give this app permission to publish to your wall or the wall of your fan page if you selected one. Please click here to do so.
    -
    - - - - - - - - - -
    - -
    -

    You can subscribe to a calendar which supports the iCalendar format. This calendar will then be periodically checked for new events. Help

    -
    -
    -
    -

    Name of subscription (anything meaningful):

    - -
    - -
    -

    URL/Web address:

    - -
    - -
    - '; - echo '

    Upon submitting the form, you will be prompted to grant this app permission to create events even when you are not on facebook.

    '; - } - else { - echo ''; - } - ?> - -
    -
    -

    Facebook requires you to assign a category and subcategory to your events. Choose those that best fit your calendar.

    -
    - Category:
    - Subategory: -
    - - - - - -
    -
    -
    -
    - - - - - - - -
    - - For this app to be able to create events, you need to give it permission to do so. Please resubmit the form.'; - } - else { - //echo 'Facebook permissions are now set correctly. Please review the form and resubmit it. '; - echo ''; + + $propagateExceptions = true; + + if ( isset($_GET['action']) ) { + switch ($_GET['action']) { + case "showSubscribeToiCalendar": + require 'pages/subscribeToiCalendarPage.php'; + break; + case "doUpdate": + require 'pages/doUpdatePage.php'; + break; + case "doSubscribe": + require 'pages/doSubscribePage.php'; + break; + case "doUnsubscribe": + require 'pages/doUnsubscribePage.php'; + break; + case "showErrorLog": + require 'pages/showErrorLogPage.php'; + break; + case "doEditSubscription": + require 'pages/doEditSubscriptionPage.php'; + break; + case "doDeactivate": + require 'pages/doDeactivatePage.php'; + break; + case "doActivate": + require 'pages/doActivatePage.php'; + break; + case "showDocs": + require "pages/showDocs.php"; + break; + case "showPolicy": + require "pages/showPolicy.php"; + break; + case "showPricing": + require "pages/showPricing.php"; + break; + default: + //if $action == "showSubscriptionList" + require 'pages/subscriptionListPage.php'; } + } else { + require 'pages/subscriptionListPage.php'; } - ?> -
    - - - -
    - - - - - -

    Subscription Name

    CategorySubcategoryGroup/Page
    -
    - - - -

    Have a look at the documentation for more information or if something is not working as expected, you might want to check your error log.

    -

    I'm working on this app in my spare time and running it on my private server. Please feel free to donate some money. Thank you!

    -
    - - - - -
    -
    - \ No newline at end of file +?> \ No newline at end of file diff --git a/installation.php b/installation.php deleted file mode 100644 index e6ebfac..0000000 --- a/installation.php +++ /dev/null @@ -1,70 +0,0 @@ -. -*/ - -mb_internal_encoding('UTF-8'); - -require 'config.php'; -//Connect to Database -$con = mysql_connect($host,$db_user,$db_password); -if (!$con) - die('Could not connect: '. mysql_error()); -mysql_select_db($database_name,$con); -mysql_query("SET NAMES 'utf8';", $con); -mysql_query("SET CHARACTER SET 'utf8';", $con); - -$sql = 'CREATE TABLE subscriptions -( -sub_id int NOT NULL AUTO_INCREMENT, -sub_name varchar(30), -PRIMARY KEY(sub_id), -url varchar(300), -category int, -subcategory int, -page_id bigint, -user_id bigint, -error_log varchar(900), -last_successful_update int, -active boolean, -picture boolean, -privacy char(6), -rsvp char(9), -wall boolean, -image_field varchar(200) -)'; -if (!mysql_query($sql)){ - echo "Error creating table: " . mysql_error(); -} -else{ - echo "table subscriptions created
    "; -} - -$sql = 'CREATE TABLE users -( -user_id bigint NOT NULL, -PRIMARY KEY(user_id), -session_key char(80) -)'; -if (!mysql_query($sql)){ - echo "Error creating table: " . mysql_error(); -} -else{ - echo "table users created
    "; -} - -echo "installation finished"; -?> \ No newline at end of file diff --git a/lib/facebook-php-sdk/changelog.md b/lib/facebook-php-sdk/changelog.md new file mode 100644 index 0000000..16728fe --- /dev/null +++ b/lib/facebook-php-sdk/changelog.md @@ -0,0 +1,28 @@ +Facebook PHP SDK (v.3.0.0) +========================== + +The new PHP SDK (v3.0.0) is a major upgrade to the older one (v2.2.x): + +- Uses OAuth authentication flows instead of our legacy authentication flow +- Consists of two classes. The first (class BaseFacebook) maintains the core of the upgrade, and the second one (class Facebook) is a small subclass that uses PHP sessions to store the user id and access token. + +If you’re currently using the PHP SDK (v2.2.x) for authentication, you will recall that the login code looked like this: + + $facebook = new Facebook(…); + $session = $facebook->getSession(); + if ($session) { + // proceed knowing you have a valid user session + } else { + // proceed knowing you require user login and/or authentication + } + +The login code is now: + + $facebook = new Facebook(…); + $user = $facebook->getUser(); + if ($user) { + // proceed knowing you have a logged in user who's authenticated + } else { + // proceed knowing you require user login and/or authentication + } + diff --git a/lib/facebook-php-sdk/examples/example.php b/lib/facebook-php-sdk/examples/example.php new file mode 100644 index 0000000..bd23de5 --- /dev/null +++ b/lib/facebook-php-sdk/examples/example.php @@ -0,0 +1,102 @@ + '191149314281714', + 'secret' => '73b67bf1c825fa47efae70a46c18906b', +)); + +// Get User ID +$user = $facebook->getUser(); + +// We may or may not have this data based on whether the user is logged in. +// +// If we have a $user id here, it means we know the user is logged into +// Facebook, but we don't know if the access token is valid. An access +// token is invalid if the user logged out of Facebook. + +if ($user) { + try { + // Proceed knowing you have a logged in user who's authenticated. + $user_profile = $facebook->api('/me'); + } catch (FacebookApiException $e) { + error_log($e); + $user = null; + } +} + +// Login or logout url will be needed depending on current user state. +if ($user) { + $logoutUrl = $facebook->getLogoutUrl(); +} else { + $loginUrl = $facebook->getLoginUrl(); +} + +// This call will always work since we are fetching public data. +$naitik = $facebook->api('/naitik'); + +?> + + + + php-sdk + + + +

    php-sdk

    + + + Logout + +
    + Login using OAuth 2.0 handled by the PHP SDK: + Login with Facebook +
    + + +

    PHP Session

    +
    + + +

    You

    + + +

    Your User Object (/me)

    +
    + + You are not Connected. + + +

    Public profile of Naitik

    + + + + diff --git a/lib/facebook-php-sdk/examples/with_js_sdk.php b/lib/facebook-php-sdk/examples/with_js_sdk.php new file mode 100644 index 0000000..07e377a --- /dev/null +++ b/lib/facebook-php-sdk/examples/with_js_sdk.php @@ -0,0 +1,59 @@ + '191149314281714', + 'secret' => '73b67bf1c825fa47efae70a46c18906b', +)); + +// See if there is a user from a cookie +$user = $facebook->getUser(); + +if ($user) { + try { + // Proceed knowing you have a logged in user who's authenticated. + $user_profile = $facebook->api('/me'); + } catch (FacebookApiException $e) { + echo '
    '.htmlspecialchars(print_r($e, true)).'
    '; + $user = null; + } +} + +?> + + + + + Your user profile is +
    +        
    +      
    + + + +
    + + + diff --git a/lib/facebook-php-sdk/readme.md b/lib/facebook-php-sdk/readme.md new file mode 100644 index 0000000..d933b21 --- /dev/null +++ b/lib/facebook-php-sdk/readme.md @@ -0,0 +1,77 @@ +Facebook PHP SDK (v.3.1.1) +========================== + +The [Facebook Platform](http://developers.facebook.com/) is +a set of APIs that make your application more social. Read more about +[integrating Facebook with your web site](http://developers.facebook.com/docs/guides/web) +on the Facebook developer site. + +This repository contains the open source PHP SDK that allows you to utilize the +above on your website. Except as otherwise noted, the Facebook PHP SDK +is licensed under the Apache Licence, Version 2.0 +(http://www.apache.org/licenses/LICENSE-2.0.html) + + +Usage +----- + +The [examples][examples] are a good place to start. The minimal you'll need to +have is: + + require 'php-sdk/src/facebook.php'; + + $facebook = new Facebook(array( + 'appId' => 'YOUR_APP_ID', + 'secret' => 'YOUR_APP_SECRET', + )); + + // Get User ID + $user = $facebook->getUser(); + +To make [API][API] calls: + + if ($user) { + try { + // Proceed knowing you have a logged in user who's authenticated. + $user_profile = $facebook->api('/me'); + } catch (FacebookApiException $e) { + error_log($e); + $user = null; + } + } + +Login or logout url will be needed depending on current user state. + + if ($user) { + $logoutUrl = $facebook->getLogoutUrl(); + } else { + $loginUrl = $facebook->getLoginUrl(); + } + +[examples]: http://github.com/facebook/php-sdk/blob/master/examples/example.php +[API]: http://developers.facebook.com/docs/api + + +Feedback +-------- + +File bugs or other issues [here]. + +[here]: http://bugs.developers.facebook.net/ + + + +Tests +----- + +In order to keep us nimble and allow us to bring you new functionality, without +compromising on stability, we have ensured full test coverage of the new SDK. +We are including this in the open source repository to assure you of our +commitment to quality, but also with the hopes that you will contribute back to +help keep it stable. The easiest way to do so is to file bugs and include a +test case. + +The tests can be executed by using this command from the base directory: + + phpunit --stderr --bootstrap tests/bootstrap.php tests/tests.php + diff --git a/lib/facebook-php-sdk/src/base_facebook.php b/lib/facebook-php-sdk/src/base_facebook.php new file mode 100644 index 0000000..557a758 --- /dev/null +++ b/lib/facebook-php-sdk/src/base_facebook.php @@ -0,0 +1,1121 @@ + + */ +class FacebookApiException extends Exception +{ + /** + * The result from the API server that represents the exception information. + */ + protected $result; + + /** + * Make a new API Exception with the given result. + * + * @param array $result The result from the API server + */ + public function __construct($result) { + $this->result = $result; + + $code = isset($result['error_code']) ? $result['error_code'] : 0; + + if (isset($result['error_description'])) { + // OAuth 2.0 Draft 10 style + $msg = $result['error_description']; + } else if (isset($result['error']) && is_array($result['error'])) { + // OAuth 2.0 Draft 00 style + $msg = $result['error']['message']; + } else if (isset($result['error_msg'])) { + // Rest server style + $msg = $result['error_msg']; + } else { + $msg = 'Unknown Error. Check getResult()'; + } + + parent::__construct($msg, $code); + } + + /** + * Return the associated result object returned by the API server. + * + * @return array The result from the API server + */ + public function getResult() { + return $this->result; + } + + /** + * Returns the associated type for the error. This will default to + * 'Exception' when a type is not available. + * + * @return string + */ + public function getType() { + if (isset($this->result['error'])) { + $error = $this->result['error']; + if (is_string($error)) { + // OAuth 2.0 Draft 10 style + return $error; + } else if (is_array($error)) { + // OAuth 2.0 Draft 00 style + if (isset($error['type'])) { + return $error['type']; + } + } + } + + return 'Exception'; + } + + /** + * To make debugging easier. + * + * @return string The string representation of the error + */ + public function __toString() { + $str = $this->getType() . ': '; + if ($this->code != 0) { + $str .= $this->code . ': '; + } + return $str . $this->message; + } +} + +/** + * Provides access to the Facebook Platform. This class provides + * a majority of the functionality needed, but the class is abstract + * because it is designed to be sub-classed. The subclass must + * implement the three abstract methods listed at the bottom of + * the file. + * + * @author Naitik Shah + */ +abstract class BaseFacebook +{ + /** + * Version. + */ + const VERSION = '3.1.1'; + + /** + * Default options for curl. + */ + public static $CURL_OPTS = array( + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 60, + CURLOPT_USERAGENT => 'facebook-php-3.1', + ); + + /** + * List of query parameters that get automatically dropped when rebuilding + * the current URL. + */ + protected static $DROP_QUERY_PARAMS = array( + 'code', + 'state', + 'signed_request', + ); + + /** + * Maps aliases to Facebook domains. + */ + public static $DOMAIN_MAP = array( + 'api' => 'https://api.facebook.com/', + 'api_video' => 'https://api-video.facebook.com/', + 'api_read' => 'https://api-read.facebook.com/', + 'graph' => 'https://graph.facebook.com/', + 'www' => 'https://www.facebook.com/', + ); + + /** + * The Application ID. + * + * @var string + */ + protected $appId; + + /** + * The Application API Secret. + * + * @var string + */ + protected $apiSecret; + + /** + * The ID of the Facebook user, or 0 if the user is logged out. + * + * @var integer + */ + protected $user; + + /** + * The data from the signed_request token. + */ + protected $signedRequest; + + /** + * A CSRF state variable to assist in the defense against CSRF attacks. + */ + protected $state; + + /** + * The OAuth access token received in exchange for a valid authorization + * code. null means the access token has yet to be determined. + * + * @var string + */ + protected $accessToken = null; + + /** + * Indicates if the CURL based @ syntax for file uploads is enabled. + * + * @var boolean + */ + protected $fileUploadSupport = false; + + /** + * Initialize a Facebook Application. + * + * The configuration: + * - appId: the application ID + * - secret: the application secret + * - fileUpload: (optional) boolean indicating if file uploads are enabled + * + * @param array $config The application configuration + */ + public function __construct($config) { + $this->setAppId($config['appId']); + $this->setApiSecret($config['secret']); + if (isset($config['fileUpload'])) { + $this->setFileUploadSupport($config['fileUpload']); + } + + $state = $this->getPersistentData('state'); + if (!empty($state)) { + $this->state = $this->getPersistentData('state'); + } + } + + /** + * Set the Application ID. + * + * @param string $appId The Application ID + * @return BaseFacebook + */ + public function setAppId($appId) { + $this->appId = $appId; + return $this; + } + + /** + * Get the Application ID. + * + * @return string the Application ID + */ + public function getAppId() { + return $this->appId; + } + + /** + * Set the API Secret. + * + * @param string $apiSecret The API Secret + * @return BaseFacebook + */ + public function setApiSecret($apiSecret) { + $this->apiSecret = $apiSecret; + return $this; + } + + /** + * Get the API Secret. + * + * @return string the API Secret + */ + public function getApiSecret() { + return $this->apiSecret; + } + + /** + * Set the file upload support status. + * + * @param boolean $fileUploadSupport The file upload support status. + * @return BaseFacebook + */ + public function setFileUploadSupport($fileUploadSupport) { + $this->fileUploadSupport = $fileUploadSupport; + return $this; + } + + /** + * Get the file upload support status. + * + * @return boolean true if and only if the server supports file upload. + */ + public function useFileUploadSupport() { + return $this->fileUploadSupport; + } + + /** + * Sets the access token for api calls. Use this if you get + * your access token by other means and just want the SDK + * to use it. + * + * @param string $access_token an access token. + * @return BaseFacebook + */ + public function setAccessToken($access_token) { + $this->accessToken = $access_token; + return $this; + } + + /** + * Determines the access token that should be used for API calls. + * The first time this is called, $this->accessToken is set equal + * to either a valid user access token, or it's set to the application + * access token if a valid user access token wasn't available. Subsequent + * calls return whatever the first call returned. + * + * @return string The access token + */ + public function getAccessToken() { + if ($this->accessToken !== null) { + // we've done this already and cached it. Just return. + return $this->accessToken; + } + + // first establish access token to be the application + // access token, in case we navigate to the /oauth/access_token + // endpoint, where SOME access token is required. + $this->setAccessToken($this->getApplicationAccessToken()); + if ($user_access_token = $this->getUserAccessToken()) { + $this->setAccessToken($user_access_token); + } + + return $this->accessToken; + } + + /** + * Determines and returns the user access token, first using + * the signed request if present, and then falling back on + * the authorization code if present. The intent is to + * return a valid user access token, or false if one is determined + * to not be available. + * + * @return string A valid user access token, or false if one + * could not be determined. + */ + protected function getUserAccessToken() { + // first, consider a signed request if it's supplied. + // if there is a signed request, then it alone determines + // the access token. + $signed_request = $this->getSignedRequest(); + if ($signed_request) { + // apps.facebook.com hands the access_token in the signed_request + if (array_key_exists('oauth_token', $signed_request)) { + $access_token = $signed_request['oauth_token']; + $this->setPersistentData('access_token', $access_token); + return $access_token; + } + + // the JS SDK puts a code in with the redirect_uri of '' + if (array_key_exists('code', $signed_request)) { + $code = $signed_request['code']; + $access_token = $this->getAccessTokenFromCode($code, ''); + if ($access_token) { + $this->setPersistentData('code', $code); + $this->setPersistentData('access_token', $access_token); + return $access_token; + } + } + + // signed request states there's no access token, so anything + // stored should be cleared. + $this->clearAllPersistentData(); + return false; // respect the signed request's data, even + // if there's an authorization code or something else + } + + $code = $this->getCode(); + if ($code && $code != $this->getPersistentData('code')) { + $access_token = $this->getAccessTokenFromCode($code); + if ($access_token) { + $this->setPersistentData('code', $code); + $this->setPersistentData('access_token', $access_token); + return $access_token; + } + + // code was bogus, so everything based on it should be invalidated. + $this->clearAllPersistentData(); + return false; + } + + // as a fallback, just return whatever is in the persistent + // store, knowing nothing explicit (signed request, authorization + // code, etc.) was present to shadow it (or we saw a code in $_REQUEST, + // but it's the same as what's in the persistent store) + return $this->getPersistentData('access_token'); + } + + /** + * Retrieve the signed request, either from a request parameter or, + * if not present, from a cookie. + * + * @return string the signed request, if available, or null otherwise. + */ + public function getSignedRequest() { + if (!$this->signedRequest) { + if (isset($_REQUEST['signed_request'])) { + $this->signedRequest = $this->parseSignedRequest( + $_REQUEST['signed_request']); + } else if (isset($_COOKIE[$this->getSignedRequestCookieName()])) { + $this->signedRequest = $this->parseSignedRequest( + $_COOKIE[$this->getSignedRequestCookieName()]); + } + } + return $this->signedRequest; + } + + /** + * Get the UID of the connected user, or 0 + * if the Facebook user is not connected. + * + * @return string the UID if available. + */ + public function getUser() { + if ($this->user !== null) { + // we've already determined this and cached the value. + return $this->user; + } + + return $this->user = $this->getUserFromAvailableData(); + } + + /** + * Determines the connected user by first examining any signed + * requests, then considering an authorization code, and then + * falling back to any persistent store storing the user. + * + * @return integer The id of the connected Facebook user, + * or 0 if no such user exists. + */ + protected function getUserFromAvailableData() { + // if a signed request is supplied, then it solely determines + // who the user is. + $signed_request = $this->getSignedRequest(); + if ($signed_request) { + if (array_key_exists('user_id', $signed_request)) { + $user = $signed_request['user_id']; + $this->setPersistentData('user_id', $signed_request['user_id']); + return $user; + } + + // if the signed request didn't present a user id, then invalidate + // all entries in any persistent store. + $this->clearAllPersistentData(); + return 0; + } + + $user = $this->getPersistentData('user_id', $default = 0); + $persisted_access_token = $this->getPersistentData('access_token'); + + // use access_token to fetch user id if we have a user access_token, or if + // the cached access token has changed. + $access_token = $this->getAccessToken(); + if ($access_token && + $access_token != $this->getApplicationAccessToken() && + !($user && $persisted_access_token == $access_token)) { + $user = $this->getUserFromAccessToken(); + if ($user) { + $this->setPersistentData('user_id', $user); + } else { + $this->clearAllPersistentData(); + } + } + + return $user; + } + + /** + * Get a Login URL for use with redirects. By default, full page redirect is + * assumed. If you are using the generated URL with a window.open() call in + * JavaScript, you can pass in display=popup as part of the $params. + * + * The parameters: + * - redirect_uri: the url to go to after a successful login + * - scope: comma separated list of requested extended perms + * + * @param array $params Provide custom parameters + * @return string The URL for the login flow + */ + public function getLoginUrl($params=array()) { + $this->establishCSRFTokenState(); + $currentUrl = $this->getCurrentUrl(); + + // if 'scope' is passed as an array, convert to comma separated list + $scopeParams = isset($params['scope']) ? $params['scope'] : null; + if ($scopeParams && is_array($scopeParams)) { + $params['scope'] = implode(',', $scopeParams); + } + + return $this->getUrl( + 'www', + 'dialog/oauth', + array_merge(array( + 'client_id' => $this->getAppId(), + 'redirect_uri' => $currentUrl, // possibly overwritten + 'state' => $this->state), + $params)); + } + + /** + * Get a Logout URL suitable for use with redirects. + * + * The parameters: + * - next: the url to go to after a successful logout + * + * @param array $params Provide custom parameters + * @return string The URL for the logout flow + */ + public function getLogoutUrl($params=array()) { + return $this->getUrl( + 'www', + 'logout.php', + array_merge(array( + 'next' => $this->getCurrentUrl(), + 'access_token' => $this->getAccessToken(), + ), $params) + ); + } + + /** + * Get a login status URL to fetch the status from Facebook. + * + * The parameters: + * - ok_session: the URL to go to if a session is found + * - no_session: the URL to go to if the user is not connected + * - no_user: the URL to go to if the user is not signed into facebook + * + * @param array $params Provide custom parameters + * @return string The URL for the logout flow + */ + public function getLoginStatusUrl($params=array()) { + return $this->getUrl( + 'www', + 'extern/login_status.php', + array_merge(array( + 'api_key' => $this->getAppId(), + 'no_session' => $this->getCurrentUrl(), + 'no_user' => $this->getCurrentUrl(), + 'ok_session' => $this->getCurrentUrl(), + 'session_version' => 3, + ), $params) + ); + } + + /** + * Make an API call. + * + * @return mixed The decoded response + */ + public function api(/* polymorphic */) { + $args = func_get_args(); + if (is_array($args[0])) { + return $this->_restserver($args[0]); + } else { + return call_user_func_array(array($this, '_graph'), $args); + } + } + + /** + * Constructs and returns the name of the cookie that + * potentially houses the signed request for the app user. + * The cookie is not set by the BaseFacebook class, but + * it may be set by the JavaScript SDK. + * + * @return string the name of the cookie that would house + * the signed request value. + */ + protected function getSignedRequestCookieName() { + return 'fbsr_'.$this->getAppId(); + } + + /** + * Get the authorization code from the query parameters, if it exists, + * and otherwise return false to signal no authorization code was + * discoverable. + * + * @return mixed The authorization code, or false if the authorization + * code could not be determined. + */ + protected function getCode() { + if (isset($_REQUEST['code'])) { + if ($this->state !== null && + isset($_REQUEST['state']) && + $this->state === $_REQUEST['state']) { + + // CSRF state has done its job, so clear it + $this->state = null; + $this->clearPersistentData('state'); + return $_REQUEST['code']; + } else { + self::errorLog('CSRF state token does not match one provided.'); + return false; + } + } + + return false; + } + + /** + * Retrieves the UID with the understanding that + * $this->accessToken has already been set and is + * seemingly legitimate. It relies on Facebook's Graph API + * to retrieve user information and then extract + * the user ID. + * + * @return integer Returns the UID of the Facebook user, or 0 + * if the Facebook user could not be determined. + */ + protected function getUserFromAccessToken() { + try { + $user_info = $this->api('/me'); + return $user_info['id']; + } catch (FacebookApiException $e) { + return 0; + } + } + + /** + * Returns the access token that should be used for logged out + * users when no authorization code is available. + * + * @return string The application access token, useful for gathering + * public information about users and applications. + */ + protected function getApplicationAccessToken() { + return $this->appId.'|'.$this->apiSecret; + } + + /** + * Lays down a CSRF state token for this process. + * + * @return void + */ + protected function establishCSRFTokenState() { + if ($this->state === null) { + $this->state = md5(uniqid(mt_rand(), true)); + $this->setPersistentData('state', $this->state); + } + } + + /** + * Retrieves an access token for the given authorization code + * (previously generated from www.facebook.com on behalf of + * a specific user). The authorization code is sent to graph.facebook.com + * and a legitimate access token is generated provided the access token + * and the user for which it was generated all match, and the user is + * either logged in to Facebook or has granted an offline access permission. + * + * @param string $code An authorization code. + * @return mixed An access token exchanged for the authorization code, or + * false if an access token could not be generated. + */ + protected function getAccessTokenFromCode($code, $redirect_uri = null) { + if (empty($code)) { + return false; + } + + if ($redirect_uri === null) { + $redirect_uri = $this->getCurrentUrl(); + } + + try { + // need to circumvent json_decode by calling _oauthRequest + // directly, since response isn't JSON format. + $access_token_response = + $this->_oauthRequest( + $this->getUrl('graph', '/oauth/access_token'), + $params = array('client_id' => $this->getAppId(), + 'client_secret' => $this->getApiSecret(), + 'redirect_uri' => $redirect_uri, + 'code' => $code)); + } catch (FacebookApiException $e) { + // most likely that user very recently revoked authorization. + // In any event, we don't have an access token, so say so. + return false; + } + + if (empty($access_token_response)) { + return false; + } + + $response_params = array(); + parse_str($access_token_response, $response_params); + if (!isset($response_params['access_token'])) { + return false; + } + + return $response_params['access_token']; + } + + /** + * Invoke the old restserver.php endpoint. + * + * @param array $params Method call object + * + * @return mixed The decoded response object + * @throws FacebookApiException + */ + protected function _restserver($params) { + // generic application level parameters + $params['api_key'] = $this->getAppId(); + $params['format'] = 'json-strings'; + + $result = json_decode($this->_oauthRequest( + $this->getApiUrl($params['method']), + $params + ), true); + + // results are returned, errors are thrown + if (is_array($result) && isset($result['error_code'])) { + throw new FacebookApiException($result); + } + + return $result; + } + + /** + * Invoke the Graph API. + * + * @param string $path The path (required) + * @param string $method The http method (default 'GET') + * @param array $params The query/post data + * + * @return mixed The decoded response object + * @throws FacebookApiException + */ + protected function _graph($path, $method = 'GET', $params = array()) { + if (is_array($method) && empty($params)) { + $params = $method; + $method = 'GET'; + } + $params['method'] = $method; // method override as we always do a POST + + $result = json_decode($this->_oauthRequest( + $this->getUrl('graph', $path), + $params + ), true); + + // results are returned, errors are thrown + if (is_array($result) && isset($result['error'])) { + $this->throwAPIException($result); + } + + return $result; + } + + /** + * Make a OAuth Request. + * + * @param string $url The path (required) + * @param array $params The query/post data + * + * @return string The decoded response object + * @throws FacebookApiException + */ + protected function _oauthRequest($url, $params) { + if (!isset($params['access_token'])) { + $params['access_token'] = $this->getAccessToken(); + } + + // json_encode all params values that are not strings + foreach ($params as $key => $value) { + if (!is_string($value)) { + $params[$key] = json_encode($value); + } + } + + return $this->makeRequest($url, $params); + } + + /** + * Makes an HTTP request. This method can be overridden by subclasses if + * developers want to do fancier things or use something other than curl to + * make the request. + * + * @param string $url The URL to make the request to + * @param array $params The parameters to use for the POST body + * @param CurlHandler $ch Initialized curl handle + * + * @return string The response text + */ + protected function makeRequest($url, $params, $ch=null) { + if (!$ch) { + $ch = curl_init(); + } + + $opts = self::$CURL_OPTS; + if ($this->useFileUploadSupport()) { + $opts[CURLOPT_POSTFIELDS] = $params; + } else { + $opts[CURLOPT_POSTFIELDS] = http_build_query($params, null, '&'); + } + $opts[CURLOPT_URL] = $url; + + // disable the 'Expect: 100-continue' behaviour. This causes CURL to wait + // for 2 seconds if the server does not support this header. + if (isset($opts[CURLOPT_HTTPHEADER])) { + $existing_headers = $opts[CURLOPT_HTTPHEADER]; + $existing_headers[] = 'Expect:'; + $opts[CURLOPT_HTTPHEADER] = $existing_headers; + } else { + $opts[CURLOPT_HTTPHEADER] = array('Expect:'); + } + + curl_setopt_array($ch, $opts); + $result = curl_exec($ch); + + if (curl_errno($ch) == 60) { // CURLE_SSL_CACERT + self::errorLog('Invalid or no certificate authority found, '. + 'using bundled information'); + curl_setopt($ch, CURLOPT_CAINFO, + dirname(__FILE__) . '/fb_ca_chain_bundle.crt'); + $result = curl_exec($ch); + } + + if ($result === false) { + $e = new FacebookApiException(array( + 'error_code' => curl_errno($ch), + 'error' => array( + 'message' => curl_error($ch), + 'type' => 'CurlException', + ), + )); + curl_close($ch); + throw $e; + } + curl_close($ch); + return $result; + } + + /** + * Parses a signed_request and validates the signature. + * + * @param string $signed_request A signed token + * @return array The payload inside it or null if the sig is wrong + */ + protected function parseSignedRequest($signed_request) { + list($encoded_sig, $payload) = explode('.', $signed_request, 2); + + // decode the data + $sig = self::base64UrlDecode($encoded_sig); + $data = json_decode(self::base64UrlDecode($payload), true); + + if (strtoupper($data['algorithm']) !== 'HMAC-SHA256') { + self::errorLog('Unknown algorithm. Expected HMAC-SHA256'); + return null; + } + + // check sig + $expected_sig = hash_hmac('sha256', $payload, + $this->getApiSecret(), $raw = true); + if ($sig !== $expected_sig) { + self::errorLog('Bad Signed JSON signature!'); + return null; + } + + return $data; + } + + /** + * Build the URL for api given parameters. + * + * @param $method String the method name. + * @return string The URL for the given parameters + */ + protected function getApiUrl($method) { + static $READ_ONLY_CALLS = + array('admin.getallocation' => 1, + 'admin.getappproperties' => 1, + 'admin.getbannedusers' => 1, + 'admin.getlivestreamvialink' => 1, + 'admin.getmetrics' => 1, + 'admin.getrestrictioninfo' => 1, + 'application.getpublicinfo' => 1, + 'auth.getapppublickey' => 1, + 'auth.getsession' => 1, + 'auth.getsignedpublicsessiondata' => 1, + 'comments.get' => 1, + 'connect.getunconnectedfriendscount' => 1, + 'dashboard.getactivity' => 1, + 'dashboard.getcount' => 1, + 'dashboard.getglobalnews' => 1, + 'dashboard.getnews' => 1, + 'dashboard.multigetcount' => 1, + 'dashboard.multigetnews' => 1, + 'data.getcookies' => 1, + 'events.get' => 1, + 'events.getmembers' => 1, + 'fbml.getcustomtags' => 1, + 'feed.getappfriendstories' => 1, + 'feed.getregisteredtemplatebundlebyid' => 1, + 'feed.getregisteredtemplatebundles' => 1, + 'fql.multiquery' => 1, + 'fql.query' => 1, + 'friends.arefriends' => 1, + 'friends.get' => 1, + 'friends.getappusers' => 1, + 'friends.getlists' => 1, + 'friends.getmutualfriends' => 1, + 'gifts.get' => 1, + 'groups.get' => 1, + 'groups.getmembers' => 1, + 'intl.gettranslations' => 1, + 'links.get' => 1, + 'notes.get' => 1, + 'notifications.get' => 1, + 'pages.getinfo' => 1, + 'pages.isadmin' => 1, + 'pages.isappadded' => 1, + 'pages.isfan' => 1, + 'permissions.checkavailableapiaccess' => 1, + 'permissions.checkgrantedapiaccess' => 1, + 'photos.get' => 1, + 'photos.getalbums' => 1, + 'photos.gettags' => 1, + 'profile.getinfo' => 1, + 'profile.getinfooptions' => 1, + 'stream.get' => 1, + 'stream.getcomments' => 1, + 'stream.getfilters' => 1, + 'users.getinfo' => 1, + 'users.getloggedinuser' => 1, + 'users.getstandardinfo' => 1, + 'users.hasapppermission' => 1, + 'users.isappuser' => 1, + 'users.isverified' => 1, + 'video.getuploadlimits' => 1); + $name = 'api'; + if (isset($READ_ONLY_CALLS[strtolower($method)])) { + $name = 'api_read'; + } else if (strtolower($method) == 'video.upload') { + $name = 'api_video'; + } + return self::getUrl($name, 'restserver.php'); + } + + /** + * Build the URL for given domain alias, path and parameters. + * + * @param $name string The name of the domain + * @param $path string Optional path (without a leading slash) + * @param $params array Optional query parameters + * + * @return string The URL for the given parameters + */ + protected function getUrl($name, $path='', $params=array()) { + $url = self::$DOMAIN_MAP[$name]; + if ($path) { + if ($path[0] === '/') { + $path = substr($path, 1); + } + $url .= $path; + } + if ($params) { + $url .= '?' . http_build_query($params, null, '&'); + } + + return $url; + } + + /** + * Returns the Current URL, stripping it of known FB parameters that should + * not persist. + * + * @return string The current URL + */ + protected function getCurrentUrl() { + $protocol = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on' + ? 'https://' + : 'http://'; + $currentUrl = $protocol . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + $parts = parse_url($currentUrl); + + $query = ''; + if (!empty($parts['query'])) { + // drop known fb params + $params = explode('&', $parts['query']); + $retained_params = array(); + foreach ($params as $param) { + if ($this->shouldRetainParam($param)) { + $retained_params[] = $param; + } + } + + if (!empty($retained_params)) { + $query = '?'.implode($retained_params, '&'); + } + } + + // use port if non default + $port = + isset($parts['port']) && + (($protocol === 'http://' && $parts['port'] !== 80) || + ($protocol === 'https://' && $parts['port'] !== 443)) + ? ':' . $parts['port'] : ''; + + // rebuild + return $protocol . $parts['host'] . $port . $parts['path'] . $query; + } + + /** + * Returns true if and only if the key or key/value pair should + * be retained as part of the query string. This amounts to + * a brute-force search of the very small list of Facebook-specific + * params that should be stripped out. + * + * @param string $param A key or key/value pair within a URL's query (e.g. + * 'foo=a', 'foo=', or 'foo'. + * + * @return boolean + */ + protected function shouldRetainParam($param) { + foreach (self::$DROP_QUERY_PARAMS as $drop_query_param) { + if (strpos($param, $drop_query_param.'=') === 0) { + return false; + } + } + + return true; + } + + /** + * Analyzes the supplied result to see if it was thrown + * because the access token is no longer valid. If that is + * the case, then the persistent store is cleared. + * + * @param $result array A record storing the error message returned + * by a failed API call. + */ + protected function throwAPIException($result) { + $e = new FacebookApiException($result); + switch ($e->getType()) { + // OAuth 2.0 Draft 00 style + case 'OAuthException': + // OAuth 2.0 Draft 10 style + case 'invalid_token': + $message = $e->getMessage(); + if ((strpos($message, 'Error validating access token') !== false) || + (strpos($message, 'Invalid OAuth access token') !== false)) { + $this->setAccessToken(null); + $this->user = 0; + $this->clearAllPersistentData(); + } + } + + throw $e; + } + + + /** + * Prints to the error log if you aren't in command line mode. + * + * @param string $msg Log message + */ + protected static function errorLog($msg) { + // disable error log if we are running in a CLI environment + // @codeCoverageIgnoreStart + if (php_sapi_name() != 'cli') { + error_log($msg); + } + // uncomment this if you want to see the errors on the page + // print 'error_log: '.$msg."\n"; + // @codeCoverageIgnoreEnd + } + + /** + * Base64 encoding that doesn't need to be urlencode()ed. + * Exactly the same as base64_encode except it uses + * - instead of + + * _ instead of / + * + * @param string $input base64UrlEncoded string + * @return string + */ + protected static function base64UrlDecode($input) { + return base64_decode(strtr($input, '-_', '+/')); + } + + /** + * Each of the following four methods should be overridden in + * a concrete subclass, as they are in the provided Facebook class. + * The Facebook class uses PHP sessions to provide a primitive + * persistent store, but another subclass--one that you implement-- + * might use a database, memcache, or an in-memory cache. + * + * @see Facebook + */ + + /** + * Stores the given ($key, $value) pair, so that future calls to + * getPersistentData($key) return $value. This call may be in another request. + * + * @param string $key + * @param array $value + * + * @return void + */ + abstract protected function setPersistentData($key, $value); + + /** + * Get the data for $key, persisted by BaseFacebook::setPersistentData() + * + * @param string $key The key of the data to retrieve + * @param boolean $default The default value to return if $key is not found + * + * @return mixed + */ + abstract protected function getPersistentData($key, $default = false); + + /** + * Clear the data with $key from the persistent storage + * + * @param string $key + * @return void + */ + abstract protected function clearPersistentData($key); + + /** + * Clear all data from the persistent storage + * + * @return void + */ + abstract protected function clearAllPersistentData(); +} diff --git a/lib/facebook-php-sdk/src/facebook.php b/lib/facebook-php-sdk/src/facebook.php new file mode 100644 index 0000000..c577c2a --- /dev/null +++ b/lib/facebook-php-sdk/src/facebook.php @@ -0,0 +1,93 @@ +constructSessionVariableName($key); + $_SESSION[$session_var_name] = $value; + } + + protected function getPersistentData($key, $default = false) { + if (!in_array($key, self::$kSupportedKeys)) { + self::errorLog('Unsupported key passed to getPersistentData.'); + return $default; + } + + $session_var_name = $this->constructSessionVariableName($key); + return isset($_SESSION[$session_var_name]) ? + $_SESSION[$session_var_name] : $default; + } + + protected function clearPersistentData($key) { + if (!in_array($key, self::$kSupportedKeys)) { + self::errorLog('Unsupported key passed to clearPersistentData.'); + return; + } + + $session_var_name = $this->constructSessionVariableName($key); + unset($_SESSION[$session_var_name]); + } + + protected function clearAllPersistentData() { + foreach (self::$kSupportedKeys as $key) { + $this->clearPersistentData($key); + } + } + + protected function constructSessionVariableName($key) { + return implode('_', array('fb', + $this->getAppId(), + $key)); + } +} diff --git a/lib/facebook-php-sdk/src/fb_ca_chain_bundle.crt b/lib/facebook-php-sdk/src/fb_ca_chain_bundle.crt new file mode 100644 index 0000000..b92d719 --- /dev/null +++ b/lib/facebook-php-sdk/src/fb_ca_chain_bundle.crt @@ -0,0 +1,121 @@ +-----BEGIN CERTIFICATE----- +MIIFgjCCBGqgAwIBAgIQDKKbZcnESGaLDuEaVk6fQjANBgkqhkiG9w0BAQUFADBm +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSUwIwYDVQQDExxEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBDQS0zMB4XDTEwMDExMzAwMDAwMFoXDTEzMDQxMTIzNTk1OVowaDELMAkGA1UE +BhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNVBAcTCVBhbG8gQWx0bzEX +MBUGA1UEChMORmFjZWJvb2ssIEluYy4xFzAVBgNVBAMUDiouZmFjZWJvb2suY29t +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9rzj7QIuLM3sdHu1HcI1VcR3g +b5FExKNV646agxSle1aQ/sJev1mh/u91ynwqd2BQmM0brZ1Hc3QrfYyAaiGGgEkp +xbhezyfeYhAyO0TKAYxPnm2cTjB5HICzk6xEIwFbA7SBJ2fSyW1CFhYZyo3tIBjj +19VjKyBfpRaPkzLmRwIDAQABo4ICrDCCAqgwHwYDVR0jBBgwFoAUUOpzidsp+xCP +nuUBINTeeZlIg/cwHQYDVR0OBBYEFPp+tsFBozkjrHlEnZ9J4cFj2eM0MA4GA1Ud +DwEB/wQEAwIFoDAMBgNVHRMBAf8EAjAAMF8GA1UdHwRYMFYwKaAnoCWGI2h0dHA6 +Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9jYTMtZmIuY3JsMCmgJ6AlhiNodHRwOi8vY3Js +NC5kaWdpY2VydC5jb20vY2EzLWZiLmNybDCCAcYGA1UdIASCAb0wggG5MIIBtQYL +YIZIAYb9bAEDAAEwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0 +LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIB +UgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkA +YwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEA +bgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMA +UABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkA +IABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwA +aQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8A +cgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMA +ZQAuMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjANBgkqhkiG9w0BAQUF +AAOCAQEACOkTIdxMy11+CKrbGNLBSg5xHaTvu/v1wbyn3dO/mf68pPfJnX6ShPYy +4XM4Vk0x4uaFaU4wAGke+nCKGi5dyg0Esg7nemLNKEJaFAJZ9enxZm334lSCeARy +wlDtxULGOFRyGIZZPmbV2eNq5xdU/g3IuBEhL722mTpAye9FU/J8Wsnw54/gANyO +Gzkewigua8ip8Lbs9Cht399yAfbfhUP1DrAm/xEcnHrzPr3cdCtOyJaM6SRPpRqH +ITK5Nc06tat9lXVosSinT3KqydzxBYua9gCFFiR3x3DgZfvXkC6KDdUlDrNcJUub +a1BHnLLP4mxTHL6faAXYd05IxNn/IA== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIGVTCCBT2gAwIBAgIQCFH5WYFBRcq94CTiEsnCDjANBgkqhkiG9w0BAQUFADBs +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSswKQYDVQQDEyJEaWdpQ2VydCBIaWdoIEFzc3VyYW5j +ZSBFViBSb290IENBMB4XDTA3MDQwMzAwMDAwMFoXDTIyMDQwMzAwMDAwMFowZjEL +MAkGA1UEBhMCVVMxFTATBgNVBAoTDERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3 +LmRpZ2ljZXJ0LmNvbTElMCMGA1UEAxMcRGlnaUNlcnQgSGlnaCBBc3N1cmFuY2Ug +Q0EtMzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9hCikQH17+NDdR +CPge+yLtYb4LDXBMUGMmdRW5QYiXtvCgFbsIYOBC6AUpEIc2iihlqO8xB3RtNpcv +KEZmBMcqeSZ6mdWOw21PoF6tvD2Rwll7XjZswFPPAAgyPhBkWBATaccM7pxCUQD5 +BUTuJM56H+2MEb0SqPMV9Bx6MWkBG6fmXcCabH4JnudSREoQOiPkm7YDr6ictFuf +1EutkozOtREqqjcYjbTCuNhcBoz4/yO9NV7UfD5+gw6RlgWYw7If48hl66l7XaAs +zPw82W3tzPpLQ4zJ1LilYRyyQLYoEt+5+F/+07LJ7z20Hkt8HEyZNp496+ynaF4d +32duXvsCAwEAAaOCAvcwggLzMA4GA1UdDwEB/wQEAwIBhjCCAcYGA1UdIASCAb0w +ggG5MIIBtQYLYIZIAYb9bAEDAAIwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3 +LmRpZ2ljZXJ0LmNvbS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUH +AgIwggFWHoIBUgBBAG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQBy +AHQAaQBmAGkAYwBhAHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBj +AGUAcAB0AGEAbgBjAGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAg +AEMAUAAvAEMAUABTACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQ +AGEAcgB0AHkAIABBAGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBt +AGkAdAAgAGwAaQBhAGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBj +AG8AcgBwAG8AcgBhAHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBl +AHIAZQBuAGMAZQAuMA8GA1UdEwEB/wQFMAMBAf8wNAYIKwYBBQUHAQEEKDAmMCQG +CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wgY8GA1UdHwSBhzCB +hDBAoD6gPIY6aHR0cDovL2NybDMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0SGlnaEFz +c3VyYW5jZUVWUm9vdENBLmNybDBAoD6gPIY6aHR0cDovL2NybDQuZGlnaWNlcnQu +Y29tL0RpZ2lDZXJ0SGlnaEFzc3VyYW5jZUVWUm9vdENBLmNybDAfBgNVHSMEGDAW +gBSxPsNpA/i/RwHUmCYaCALvY2QrwzAdBgNVHQ4EFgQUUOpzidsp+xCPnuUBINTe +eZlIg/cwDQYJKoZIhvcNAQEFBQADggEBAF1PhPGoiNOjsrycbeUpSXfh59bcqdg1 +rslx3OXb3J0kIZCmz7cBHJvUV5eR13UWpRLXuT0uiT05aYrWNTf58SHEW0CtWakv +XzoAKUMncQPkvTAyVab+hA4LmzgZLEN8rEO/dTHlIxxFVbdpCJG1z9fVsV7un5Tk +1nq5GMO41lJjHBC6iy9tXcwFOPRWBW3vnuzoYTYMFEuFFFoMg08iXFnLjIpx2vrF +EIRYzwfu45DC9fkpx1ojcflZtGQriLCnNseaIGHr+k61rmsb5OPs4tk8QUmoIKRU +9ZKNu8BVIASm2LAXFszj0Mi0PeXZhMbT9m5teMl5Q+h6N/9cNUm/ocU= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEQjCCA6ugAwIBAgIEQoclDjANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u +ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc +KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u +ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw0wNjEy +MjIxNTI3MjdaFw0xNDA3MjIxNTU3MjdaMGwxCzAJBgNVBAYTAlVTMRUwEwYDVQQK +EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xKzApBgNV +BAMTIkRpZ2lDZXJ0IEhpZ2ggQXNzdXJhbmNlIEVWIFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGzOVz5vvUu+UtLTKm3+WBP8nNJUm2cSrD +1ZQ0Z6IKHLBfaaZAscS3so/QmKSpQVk609yU1jzbdDikSsxNJYL3SqVTEjju80lt +cZF+Y7arpl/DpIT4T2JRvvjF7Ns4kuMG5QiRDMQoQVX7y1qJFX5x6DW/TXIJPb46 +OFBbdzEbjbPHJEWap6xtABRaBLe6E+tRCphBQSJOZWGHgUFQpnlcid4ZSlfVLuZd +HFMsfpjNGgYWpGhz0DQEE1yhcdNafFXbXmThN4cwVgTlEbQpgBLxeTmIogIRfCdm +t4i3ePLKCqg4qwpkwr9mXZWEwaElHoddGlALIBLMQbtuC1E4uEvLAgMBAAGjggET +MIIBDzASBgNVHRMBAf8ECDAGAQH/AgEBMCcGA1UdJQQgMB4GCCsGAQUFBwMBBggr +BgEFBQcDAgYIKwYBBQUHAwQwMwYIKwYBBQUHAQEEJzAlMCMGCCsGAQUFBzABhhdo +dHRwOi8vb2NzcC5lbnRydXN0Lm5ldDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8v +Y3JsLmVudHJ1c3QubmV0L3NlcnZlcjEuY3JsMB0GA1UdDgQWBBSxPsNpA/i/RwHU +mCYaCALvY2QrwzALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8BdiE1U9s/8KAGv7 +UISX8+1i0BowGQYJKoZIhvZ9B0EABAwwChsEVjcuMQMCAIEwDQYJKoZIhvcNAQEF +BQADgYEAUuVY7HCc/9EvhaYzC1rAIo348LtGIiMduEl5Xa24G8tmJnDioD2GU06r +1kjLX/ktCdpdBgXadbjtdrZXTP59uN0AXlsdaTiFufsqVLPvkp5yMnqnuI3E2o6p +NpAkoQSbB6kUCNnXcW26valgOjDLZFOnr241QiwdBAJAAE/rRa8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u +ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc +KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u +ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 +MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE +ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j +b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg +U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ +I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 +wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC +AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb +oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 +BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p +dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk +MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 +MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi +E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa +MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI +hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN +95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd +2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- diff --git a/lib/facebook-php-sdk/tests/bootstrap.php b/lib/facebook-php-sdk/tests/bootstrap.php new file mode 100644 index 0000000..e32b9f8 --- /dev/null +++ b/lib/facebook-php-sdk/tests/bootstrap.php @@ -0,0 +1,5 @@ + self::APP_ID, + 'secret' => self::SECRET, + )); + $this->assertEquals($facebook->getAppId(), self::APP_ID, + 'Expect the App ID to be set.'); + $this->assertEquals($facebook->getApiSecret(), self::SECRET, + 'Expect the API secret to be set.'); + } + + public function testConstructorWithFileUpload() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + 'fileUpload' => true, + )); + $this->assertEquals($facebook->getAppId(), self::APP_ID, + 'Expect the App ID to be set.'); + $this->assertEquals($facebook->getApiSecret(), self::SECRET, + 'Expect the API secret to be set.'); + $this->assertTrue($facebook->useFileUploadSupport(), + 'Expect file upload support to be on.'); + } + + public function testSetAppId() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $facebook->setAppId('dummy'); + $this->assertEquals($facebook->getAppId(), 'dummy', + 'Expect the App ID to be dummy.'); + } + + public function testSetAPISecret() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $facebook->setApiSecret('dummy'); + $this->assertEquals($facebook->getApiSecret(), 'dummy', + 'Expect the API secret to be dummy.'); + } + + public function testSetAccessToken() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $facebook->setAccessToken('saltydog'); + $this->assertEquals($facebook->getAccessToken(), 'saltydog', + 'Expect installed access token to remain \'saltydog\''); + } + + public function testSetFileUploadSupport() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $this->assertFalse($facebook->useFileUploadSupport(), + 'Expect file upload support to be off.'); + $facebook->setFileUploadSupport(true); + $this->assertTrue($facebook->useFileUploadSupport(), + 'Expect file upload support to be on.'); + } + + public function testGetCurrentURL() { + $facebook = new FBGetCurrentURLFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + // fake the HPHP $_SERVER globals + $_SERVER['HTTP_HOST'] = 'www.test.com'; + $_SERVER['REQUEST_URI'] = '/unit-tests.php?one=one&two=two&three=three'; + $current_url = $facebook->publicGetCurrentUrl(); + $this->assertEquals( + 'http://www.test.com/unit-tests.php?one=one&two=two&three=three', + $current_url, + 'getCurrentUrl function is changing the current URL'); + + // ensure structure of valueless GET params is retained (sometimes + // an = sign was present, and sometimes it was not) + // first test when equal signs are present + $_SERVER['HTTP_HOST'] = 'www.test.com'; + $_SERVER['REQUEST_URI'] = '/unit-tests.php?one=&two=&three='; + $current_url = $facebook->publicGetCurrentUrl(); + $this->assertEquals( + 'http://www.test.com/unit-tests.php?one=&two=&three=', + $current_url, + 'getCurrentUrl function is changing the current URL'); + + // now confirm that + $_SERVER['HTTP_HOST'] = 'www.test.com'; + $_SERVER['REQUEST_URI'] = '/unit-tests.php?one&two&three'; + $current_url = $facebook->publicGetCurrentUrl(); + $this->assertEquals( + 'http://www.test.com/unit-tests.php?one&two&three', + $current_url, + 'getCurrentUrl function is changing the current URL'); + } + + public function testGetLoginURL() { + $facebook = new Facebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + // fake the HPHP $_SERVER globals + $_SERVER['HTTP_HOST'] = 'www.test.com'; + $_SERVER['REQUEST_URI'] = '/unit-tests.php'; + $login_url = parse_url($facebook->getLoginUrl()); + $this->assertEquals($login_url['scheme'], 'https'); + $this->assertEquals($login_url['host'], 'www.facebook.com'); + $this->assertEquals($login_url['path'], '/dialog/oauth'); + $expected_login_params = + array('client_id' => self::APP_ID, + 'redirect_uri' => 'http://www.test.com/unit-tests.php'); + + $query_map = array(); + parse_str($login_url['query'], $query_map); + $this->assertIsSubset($expected_login_params, $query_map); + // we don't know what the state is, but we know it's an md5 and should + // be 32 characters long. + $this->assertEquals(strlen($query_map['state']), $num_characters = 32); + } + + public function testGetLoginURLWithExtraParams() { + $facebook = new Facebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + // fake the HPHP $_SERVER globals + $_SERVER['HTTP_HOST'] = 'www.test.com'; + $_SERVER['REQUEST_URI'] = '/unit-tests.php'; + $extra_params = array('scope' => 'email, sms', + 'nonsense' => 'nonsense'); + $login_url = parse_url($facebook->getLoginUrl($extra_params)); + $this->assertEquals($login_url['scheme'], 'https'); + $this->assertEquals($login_url['host'], 'www.facebook.com'); + $this->assertEquals($login_url['path'], '/dialog/oauth'); + $expected_login_params = + array_merge( + array('client_id' => self::APP_ID, + 'redirect_uri' => 'http://www.test.com/unit-tests.php'), + $extra_params); + $query_map = array(); + parse_str($login_url['query'], $query_map); + $this->assertIsSubset($expected_login_params, $query_map); + // we don't know what the state is, but we know it's an md5 and should + // be 32 characters long. + $this->assertEquals(strlen($query_map['state']), $num_characters = 32); + } + + public function testGetLoginURLWithScopeParamsAsArray() { + $facebook = new Facebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + // fake the HPHP $_SERVER globals + $_SERVER['HTTP_HOST'] = 'www.test.com'; + $_SERVER['REQUEST_URI'] = '/unit-tests.php'; + $scope_params_as_array = array('email','sms','read_stream'); + $extra_params = array('scope' => $scope_params_as_array, + 'nonsense' => 'nonsense'); + $login_url = parse_url($facebook->getLoginUrl($extra_params)); + $this->assertEquals($login_url['scheme'], 'https'); + $this->assertEquals($login_url['host'], 'www.facebook.com'); + $this->assertEquals($login_url['path'], '/dialog/oauth'); + // expect api to flatten array params to comma separated list + // should do the same here before asserting to make sure API is behaving + // correctly; + $extra_params['scope'] = implode(',', $scope_params_as_array); + $expected_login_params = + array_merge( + array('client_id' => self::APP_ID, + 'redirect_uri' => 'http://www.test.com/unit-tests.php'), + $extra_params); + $query_map = array(); + parse_str($login_url['query'], $query_map); + $this->assertIsSubset($expected_login_params, $query_map); + // we don't know what the state is, but we know it's an md5 and should + // be 32 characters long. + $this->assertEquals(strlen($query_map['state']), $num_characters = 32); + } + + public function testGetCodeWithValidCSRFState() { + $facebook = new FBCode(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $facebook->setCSRFStateToken(); + $code = $_REQUEST['code'] = $this->generateMD5HashOfRandomValue(); + $_REQUEST['state'] = $facebook->getCSRFStateToken(); + $this->assertEquals($code, + $facebook->publicGetCode(), + 'Expect code to be pulled from $_REQUEST[\'code\']'); + } + + public function testGetCodeWithInvalidCSRFState() { + $facebook = new FBCode(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $facebook->setCSRFStateToken(); + $code = $_REQUEST['code'] = $this->generateMD5HashOfRandomValue(); + $_REQUEST['state'] = $facebook->getCSRFStateToken().'forgery!!!'; + $this->assertFalse($facebook->publicGetCode(), + 'Expect getCode to fail, CSRF state should not match.'); + } + + public function testGetCodeWithMissingCSRFState() { + $facebook = new FBCode(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $code = $_REQUEST['code'] = $this->generateMD5HashOfRandomValue(); + // intentionally don't set CSRF token at all + $this->assertFalse($facebook->publicGetCode(), + 'Expect getCode to fail, CSRF state not sent back.'); + + } + + public function testGetUserFromSignedRequest() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $_REQUEST['signed_request'] = self::$kValidSignedRequest; + $this->assertEquals('1677846385', $facebook->getUser(), + 'Failed to get user ID from a valid signed request.'); + } + + public function testGetSignedRequestFromCookie() { + $facebook = new FBGetSignedRequestCookieFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $_COOKIE[$facebook->publicGetSignedRequestCookieName()] = + self::$kValidSignedRequest; + $this->assertNotNull($facebook->publicGetSignedRequest()); + $this->assertEquals('1677846385', $facebook->getUser(), + 'Failed to get user ID from a valid signed request.'); + } + + public function testGetSignedRequestWithIncorrectSignature() { + $facebook = new FBGetSignedRequestCookieFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $_COOKIE[$facebook->publicGetSignedRequestCookieName()] = + self::$kSignedRequestWithBogusSignature; + $this->assertNull($facebook->publicGetSignedRequest()); + } + + public function testNonUserAccessToken() { + $facebook = new FBAccessToken(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + // no cookies, and no request params, so no user or code, + // so no user access token (even with cookie support) + $this->assertEquals($facebook->publicGetApplicationAccessToken(), + $facebook->getAccessToken(), + 'Access token should be that for logged out users.'); + } + + public function testAPIForLoggedOutUsers() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $response = $facebook->api(array( + 'method' => 'fql.query', + 'query' => 'SELECT name FROM user WHERE uid=4', + )); + $this->assertEquals(count($response), 1, + 'Expect one row back.'); + $this->assertEquals($response[0]['name'], 'Mark Zuckerberg', + 'Expect the name back.'); + } + + public function testAPIWithBogusAccessToken() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $facebook->setAccessToken('this-is-not-really-an-access-token'); + // if we don't set an access token and there's no way to + // get one, then the FQL query below works beautifully, handing + // over Zuck's public data. But if you specify a bogus access + // token as I have right here, then the FQL query should fail. + // We could return just Zuck's public data, but that wouldn't + // advertise the issue that the access token is at worst broken + // and at best expired. + try { + $response = $facebook->api(array( + 'method' => 'fql.query', + 'query' => 'SELECT name FROM profile WHERE id=4', + )); + $this->fail('Should not get here.'); + } catch(FacebookApiException $e) { + $result = $e->getResult(); + $this->assertTrue(is_array($result), 'expect a result object'); + $this->assertEquals('190', $result['error_code'], 'expect code'); + } + } + + public function testAPIGraphPublicData() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $response = $facebook->api('/jerry'); + $this->assertEquals( + $response['id'], '214707', 'should get expected id.'); + } + + public function testGraphAPIWithBogusAccessToken() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $facebook->setAccessToken('this-is-not-really-an-access-token'); + try { + $response = $facebook->api('/me'); + $this->fail('Should not get here.'); + } catch(FacebookApiException $e) { + // means the server got the access token and didn't like it + $msg = 'OAuthException: Invalid OAuth access token.'; + $this->assertEquals($msg, (string) $e, + 'Expect the invalid OAuth token message.'); + } + } + + public function testGraphAPIWithExpiredAccessToken() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $facebook->setAccessToken(self::$kExpiredAccessToken); + try { + $response = $facebook->api('/me'); + $this->fail('Should not get here.'); + } catch(FacebookApiException $e) { + // means the server got the access token and didn't like it + $error_msg_start = 'OAuthException: Error validating access token:'; + $this->assertTrue(strpos((string) $e, $error_msg_start) === 0, + 'Expect the token validation error message.'); + } + } + + public function testGraphAPIMethod() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + try { + // naitik being bold about deleting his entire record.... + // let's hope this never actually passes. + $response = $facebook->api('/naitik', $method = 'DELETE'); + $this->fail('Should not get here.'); + } catch(FacebookApiException $e) { + // ProfileDelete means the server understood the DELETE + $msg = + 'OAuthException: A user access token is required to request this resource.'; + $this->assertEquals($msg, (string) $e, + 'Expect the invalid session message.'); + } + } + + public function testGraphAPIOAuthSpecError() { + $facebook = new TransientFacebook(array( + 'appId' => self::MIGRATED_APP_ID, + 'secret' => self::MIGRATED_SECRET, + )); + + try { + $response = $facebook->api('/me', array( + 'client_id' => self::MIGRATED_APP_ID)); + + $this->fail('Should not get here.'); + } catch(FacebookApiException $e) { + // means the server got the access token + $msg = 'invalid_request: An active access token must be used '. + 'to query information about the current user.'; + $this->assertEquals($msg, (string) $e, + 'Expect the invalid session message.'); + } + } + + public function testGraphAPIMethodOAuthSpecError() { + $facebook = new TransientFacebook(array( + 'appId' => self::MIGRATED_APP_ID, + 'secret' => self::MIGRATED_SECRET, + )); + + try { + $response = $facebook->api('/daaku.shah', 'DELETE', array( + 'client_id' => self::MIGRATED_APP_ID)); + $this->fail('Should not get here.'); + } catch(FacebookApiException $e) { + $this->assertEquals(strpos($e, 'invalid_request'), 0); + } + } + + public function testCurlFailure() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + if (!defined('CURLOPT_TIMEOUT_MS')) { + // can't test it if we don't have millisecond timeouts + return; + } + + $exception = null; + try { + // we dont expect facebook will ever return in 1ms + Facebook::$CURL_OPTS[CURLOPT_TIMEOUT_MS] = 50; + $facebook->api('/naitik'); + } catch(FacebookApiException $e) { + $exception = $e; + } + unset(Facebook::$CURL_OPTS[CURLOPT_TIMEOUT_MS]); + if (!$exception) { + $this->fail('no exception was thrown on timeout.'); + } + + $this->assertEquals( + CURLE_OPERATION_TIMEOUTED, $exception->getCode(), 'expect timeout'); + $this->assertEquals('CurlException', $exception->getType(), 'expect type'); + } + + public function testGraphAPIWithOnlyParams() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $response = $facebook->api('/jerry'); + $this->assertTrue(isset($response['id']), + 'User ID should be public.'); + $this->assertTrue(isset($response['name']), + 'User\'s name should be public.'); + $this->assertTrue(isset($response['first_name']), + 'User\'s first name should be public.'); + $this->assertTrue(isset($response['last_name']), + 'User\'s last name should be public.'); + $this->assertFalse(isset($response['work']), + 'User\'s work history should only be available with '. + 'a valid access token.'); + $this->assertFalse(isset($response['education']), + 'User\'s education history should only be '. + 'available with a valid access token.'); + $this->assertFalse(isset($response['verified']), + 'User\'s verification status should only be '. + 'available with a valid access token.'); + } + + public function testLoginURLDefaults() { + $_SERVER['HTTP_HOST'] = 'fbrell.com'; + $_SERVER['REQUEST_URI'] = '/examples'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $encodedUrl = rawurlencode('http://fbrell.com/examples'); + $this->assertNotNull(strpos($facebook->getLoginUrl(), $encodedUrl), + 'Expect the current url to exist.'); + } + + public function testLoginURLDefaultsDropStateQueryParam() { + $_SERVER['HTTP_HOST'] = 'fbrell.com'; + $_SERVER['REQUEST_URI'] = '/examples?state=xx42xx'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $expectEncodedUrl = rawurlencode('http://fbrell.com/examples'); + $this->assertTrue(strpos($facebook->getLoginUrl(), $expectEncodedUrl) > -1, + 'Expect the current url to exist.'); + $this->assertFalse(strpos($facebook->getLoginUrl(), 'xx42xx'), + 'Expect the session param to be dropped.'); + } + + public function testLoginURLDefaultsDropCodeQueryParam() { + $_SERVER['HTTP_HOST'] = 'fbrell.com'; + $_SERVER['REQUEST_URI'] = '/examples?code=xx42xx'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $expectEncodedUrl = rawurlencode('http://fbrell.com/examples'); + $this->assertTrue(strpos($facebook->getLoginUrl(), $expectEncodedUrl) > -1, + 'Expect the current url to exist.'); + $this->assertFalse(strpos($facebook->getLoginUrl(), 'xx42xx'), + 'Expect the session param to be dropped.'); + } + + public function testLoginURLDefaultsDropSignedRequestParamButNotOthers() { + $_SERVER['HTTP_HOST'] = 'fbrell.com'; + $_SERVER['REQUEST_URI'] = + '/examples?signed_request=xx42xx&do_not_drop=xx43xx'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $expectEncodedUrl = rawurlencode('http://fbrell.com/examples'); + $this->assertFalse(strpos($facebook->getLoginUrl(), 'xx42xx'), + 'Expect the session param to be dropped.'); + $this->assertTrue(strpos($facebook->getLoginUrl(), 'xx43xx') > -1, + 'Expect the do_not_drop param to exist.'); + } + + public function testLoginURLCustomNext() { + $_SERVER['HTTP_HOST'] = 'fbrell.com'; + $_SERVER['REQUEST_URI'] = '/examples'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $next = 'http://fbrell.com/custom'; + $loginUrl = $facebook->getLoginUrl(array( + 'redirect_uri' => $next, + 'cancel_url' => $next + )); + $currentEncodedUrl = rawurlencode('http://fbrell.com/examples'); + $expectedEncodedUrl = rawurlencode($next); + $this->assertNotNull(strpos($loginUrl, $expectedEncodedUrl), + 'Expect the custom url to exist.'); + $this->assertFalse(strpos($loginUrl, $currentEncodedUrl), + 'Expect the current url to not exist.'); + } + + public function testLogoutURLDefaults() { + $_SERVER['HTTP_HOST'] = 'fbrell.com'; + $_SERVER['REQUEST_URI'] = '/examples'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $encodedUrl = rawurlencode('http://fbrell.com/examples'); + $this->assertNotNull(strpos($facebook->getLogoutUrl(), $encodedUrl), + 'Expect the current url to exist.'); + } + + public function testLoginStatusURLDefaults() { + $_SERVER['HTTP_HOST'] = 'fbrell.com'; + $_SERVER['REQUEST_URI'] = '/examples'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $encodedUrl = rawurlencode('http://fbrell.com/examples'); + $this->assertNotNull(strpos($facebook->getLoginStatusUrl(), $encodedUrl), + 'Expect the current url to exist.'); + } + + public function testLoginStatusURLCustom() { + $_SERVER['HTTP_HOST'] = 'fbrell.com'; + $_SERVER['REQUEST_URI'] = '/examples'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $encodedUrl1 = rawurlencode('http://fbrell.com/examples'); + $okUrl = 'http://fbrell.com/here1'; + $encodedUrl2 = rawurlencode($okUrl); + $loginStatusUrl = $facebook->getLoginStatusUrl(array( + 'ok_session' => $okUrl, + )); + $this->assertNotNull(strpos($loginStatusUrl, $encodedUrl1), + 'Expect the current url to exist.'); + $this->assertNotNull(strpos($loginStatusUrl, $encodedUrl2), + 'Expect the custom url to exist.'); + } + + public function testNonDefaultPort() { + $_SERVER['HTTP_HOST'] = 'fbrell.com:8080'; + $_SERVER['REQUEST_URI'] = '/examples'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $encodedUrl = rawurlencode('http://fbrell.com:8080/examples'); + $this->assertNotNull(strpos($facebook->getLoginUrl(), $encodedUrl), + 'Expect the current url to exist.'); + } + + public function testSecureCurrentUrl() { + $_SERVER['HTTP_HOST'] = 'fbrell.com'; + $_SERVER['REQUEST_URI'] = '/examples'; + $_SERVER['HTTPS'] = 'on'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $encodedUrl = rawurlencode('https://fbrell.com/examples'); + $this->assertNotNull(strpos($facebook->getLoginUrl(), $encodedUrl), + 'Expect the current url to exist.'); + } + + public function testSecureCurrentUrlWithNonDefaultPort() { + $_SERVER['HTTP_HOST'] = 'fbrell.com:8080'; + $_SERVER['REQUEST_URI'] = '/examples'; + $_SERVER['HTTPS'] = 'on'; + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + $encodedUrl = rawurlencode('https://fbrell.com:8080/examples'); + $this->assertNotNull(strpos($facebook->getLoginUrl(), $encodedUrl), + 'Expect the current url to exist.'); + } + + public function testAppSecretCall() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET, + )); + + $proper_exception_thrown = false; + try { + $response = $facebook->api('/' . self::APP_ID . '/insights'); + $this->fail('Desktop applications need a user token for insights.'); + } catch (FacebookApiException $e) { + $proper_exception_thrown = + strpos($e->getMessage(), + 'Requires session when calling from a desktop app') !== false; + } catch (Exception $e) {} + + $this->assertTrue($proper_exception_thrown, + 'Incorrect exception type thrown when trying to gain '. + 'insights for desktop app without a user access token.'); + } + + public function testBase64UrlEncode() { + $input = 'Facebook rocks'; + $output = 'RmFjZWJvb2sgcm9ja3M'; + + $this->assertEquals(FBPublic::publicBase64UrlDecode($output), $input); + } + + public function testSignedToken() { + $facebook = new FBPublic(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET + )); + $payload = $facebook->publicParseSignedRequest(self::$kValidSignedRequest); + $this->assertNotNull($payload, 'Expected token to parse'); + $this->assertEquals($facebook->getSignedRequest(), null); + $_REQUEST['signed_request'] = self::$kValidSignedRequest; + $this->assertEquals($facebook->getSignedRequest(), $payload); + } + + public function testNonTossedSignedtoken() { + $facebook = new FBPublic(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET + )); + $payload = $facebook->publicParseSignedRequest( + self::$kNonTosedSignedRequest); + $this->assertNotNull($payload, 'Expected token to parse'); + $this->assertNull($facebook->getSignedRequest()); + $_REQUEST['signed_request'] = self::$kNonTosedSignedRequest; + $this->assertEquals($facebook->getSignedRequest(), + array('algorithm' => 'HMAC-SHA256')); + } + + public function testBundledCACert() { + $facebook = new TransientFacebook(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET + )); + + // use the bundled cert from the start + Facebook::$CURL_OPTS[CURLOPT_CAINFO] = + dirname(__FILE__) . '/../src/fb_ca_chain_bundle.crt'; + $response = $facebook->api('/naitik'); + + unset(Facebook::$CURL_OPTS[CURLOPT_CAINFO]); + $this->assertEquals( + $response['id'], '5526183', 'should get expected id.'); + } + + public function testVideoUpload() { + $facebook = new FBRecordURL(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET + )); + + $facebook->api(array('method' => 'video.upload')); + $this->assertContains('//api-video.', $facebook->getRequestedURL(), + 'video.upload should go against api-video'); + } + + public function testGetUserAndAccessTokenFromSession() { + $facebook = new PersistentFBPublic(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET + )); + + $facebook->publicSetPersistentData('access_token', + self::$kExpiredAccessToken); + $facebook->publicSetPersistentData('user_id', 12345); + $this->assertEquals(self::$kExpiredAccessToken, + $facebook->getAccessToken(), + 'Get access token from persistent store.'); + $this->assertEquals('12345', + $facebook->getUser(), + 'Get user id from persistent store.'); + } + + public function testGetUserAndAccessTokenFromSignedRequestNotSession() { + $facebook = new PersistentFBPublic(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET + )); + + $_REQUEST['signed_request'] = self::$kValidSignedRequest; + $facebook->publicSetPersistentData('user_id', 41572); + $facebook->publicSetPersistentData('access_token', + self::$kExpiredAccessToken); + $this->assertNotEquals('41572', $facebook->getUser(), + 'Got user from session instead of signed request.'); + $this->assertEquals('1677846385', $facebook->getUser(), + 'Failed to get correct user ID from signed request.'); + $this->assertNotEquals( + self::$kExpiredAccessToken, + $facebook->getAccessToken(), + 'Got access token from session instead of signed request.'); + $this->assertNotEmpty( + $facebook->getAccessToken(), + 'Failed to extract an access token from the signed request.'); + } + + public function testGetUserWithoutCodeOrSignedRequestOrSession() { + $facebook = new PersistentFBPublic(array( + 'appId' => self::APP_ID, + 'secret' => self::SECRET + )); + + // deliberately leave $_REQUEST and _$SESSION empty + $this->assertEmpty($_REQUEST, + 'GET, POST, and COOKIE params exist even though '. + 'they should. Test cannot succeed unless all of '. + '$_REQUEST is empty.'); + $this->assertEmpty($_SESSION, + 'Session is carrying state and should not be.'); + $this->assertEmpty($facebook->getUser(), + 'Got a user id, even without a signed request, '. + 'access token, or session variable.'); + $this->assertEmpty($_SESSION, + 'Session superglobal incorrectly populated by getUser.'); + } + + protected function generateMD5HashOfRandomValue() { + return md5(uniqid(mt_rand(), true)); + } + + protected function setUp() { + parent::setUp(); + } + + protected function tearDown() { + $this->clearSuperGlobals(); + parent::tearDown(); + } + + protected function clearSuperGlobals() { + unset($_SERVER['HTTPS']); + unset($_SERVER['HTTP_HOST']); + unset($_SERVER['REQUEST_URI']); + $_SESSION = array(); + $_COOKIE = array(); + $_REQUEST = array(); + $_POST = array(); + $_GET = array(); + if (session_id()) { + session_destroy(); + } + } + + /** + * Checks that the correct args are a subset of the returned obj + * @param array $correct The correct array values + * @param array $actual The values in practice + * @param string $message to be shown on failure + */ + protected function assertIsSubset($correct, $actual, $msg='') { + foreach ($correct as $key => $value) { + $actual_value = $actual[$key]; + $newMsg = (strlen($msg) ? ($msg.' ') : '').'Key: '.$key; + $this->assertEquals($value, $actual_value, $newMsg); + } + } +} + +class TransientFacebook extends BaseFacebook { + protected function setPersistentData($key, $value) {} + protected function getPersistentData($key, $default = false) { + return $default; + } + protected function clearPersistentData($key) {} + protected function clearAllPersistentData() {} +} + +class FBRecordURL extends TransientFacebook { + private $url; + + protected function _oauthRequest($url, $params) { + $this->url = $url; + } + + public function getRequestedURL() { + return $this->url; + } +} + +class FBPublic extends TransientFacebook { + public static function publicBase64UrlDecode($input) { + return self::base64UrlDecode($input); + } + public function publicParseSignedRequest($input) { + return $this->parseSignedRequest($input); + } +} + +class PersistentFBPublic extends Facebook { + public function publicParseSignedRequest($input) { + return $this->parseSignedRequest($input); + } + + public function publicSetPersistentData($key, $value) { + $this->setPersistentData($key, $value); + } +} + +class FBCode extends Facebook { + public function publicGetCode() { + return $this->getCode(); + } + + public function setCSRFStateToken() { + $this->establishCSRFTokenState(); + } + + public function getCSRFStateToken() { + return $this->getPersistentData('state'); + } +} + +class FBAccessToken extends TransientFacebook { + public function publicGetApplicationAccessToken() { + return $this->getApplicationAccessToken(); + } +} + +class FBGetCurrentURLFacebook extends TransientFacebook { + public function publicGetCurrentUrl() { + return $this->getCurrentUrl(); + } +} + +class FBGetSignedRequestCookieFacebook extends TransientFacebook { + public function publicGetSignedRequest() { + return $this->getSignedRequest(); + } + + public function publicGetSignedRequestCookieName() { + return $this->getSignedRequestCookieName(); + } +} diff --git a/lib/iCalcreator/iCalUtilityFunctions.class.php b/lib/iCalcreator/iCalUtilityFunctions.class.php new file mode 100755 index 0000000..5cccde5 --- /dev/null +++ b/lib/iCalcreator/iCalUtilityFunctions.class.php @@ -0,0 +1,1575 @@ + + * @since 2.10.1 - 2011-07-16 + * + */ +class iCalUtilityFunctions { + // Store the single instance of iCalUtilityFunctions + private static $m_pInstance; + + // Private constructor to limit object instantiation to within the class + private function __construct() { + $m_pInstance = FALSE; + } + + // Getter method for creating/returning the single instance of this class + public static function getInstance() { + if (!self::$m_pInstance) + self::$m_pInstance = new iCalUtilityFunctions(); + + return self::$m_pInstance; + } +/** + * check a date(-time) for an opt. timezone and if it is a DATE-TIME or DATE + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.16 - 2008-10-25 + * @param array $date, date to check + * @param int $parno, no of date parts (i.e. year, month.. .) + * @return array $params, property parameters + */ + public static function _chkdatecfg( $theDate, & $parno, & $params ) { + if( isset( $params['TZID'] )) + $parno = 6; + elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )) + $parno = 3; + else { + if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] )) + $parno = 7; + if( is_array( $theDate )) { + if( isset( $theDate['timestamp'] )) + $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null; + else + $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null; + if( !empty( $tzid )) { + $parno = 7; + if( !iCalUtilityFunctions::_isOffset( $tzid )) + $params['TZID'] = $tzid; // save only timezone + } + elseif( !$parno && ( 3 == count( $theDate )) && + ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))) + $parno = 3; + else + $parno = 6; + } + else { // string + $date = trim( $theDate ); + if( 'Z' == substr( $date, -1 )) + $parno = 7; // UTC DATE-TIME + elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) && + ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' )))) + $parno = 3; // DATE + $date = iCalUtilityFunctions::_date_time_string( $date, $parno ); + if( !empty( $date['tz'] )) { + $parno = 7; + if( !iCalUtilityFunctions::_isOffset( $date['tz'] )) + $params['TZID'] = $date['tz']; // save only timezone + } + elseif( empty( $parno )) + $parno = 6; + } + if( isset( $params['TZID'] )) + $parno = 6; + } + } +/** + * create (very simple) timezone and standard/daylight components + * + * Result when 'Europe/Stockholm' is used as timezone: + * + * BEGIN:VTIMEZONE + * TZID:Europe/Stockholm + * BEGIN:STANDARD + * DTSTART:20101031T020000 + * TZOFFSETFROM:+0200 + * TZOFFSETTO:+0100 + * RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 + * TZNAME:CET + * END:STANDARD + * BEGIN:DAYLIGHT + * DTSTART:20100328T030000 + * TZOFFSETFROM:+0100 + * TZOFFSETTO:+0200 + * RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3 + * TZNAME:CEST + * END:DAYLIGHT + * END:VTIMEZONE + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.3 - 2011-08-01 + * @param object $calendar, reference to an iCalcreator calendar instance + * @param string $timezone, a PHP5 (DateTimeZone) valid timezone + * @param array $xProp, *[x-propName => x-propValue], optional + * @return bool + */ + public static function createTimezone( & $calendar, $timezone, $xProp=array() ) { + if( !class_exists( 'DateTimeZone' )) + return FALSE; + if( empty( $timezone )) + return FALSE; + try { + $dtz = new DateTimeZone( $timezone ); + } + catch( Exception $e ) { + return FALSE; + } + $stdDTSTART = $stdTZOFFSETTO = $stdTZOFFSETFROM = $stdTZNAME = $dlghtDTSTART = $dlghtTZOFFSETTO = $dlghtTZOFFSETFROM = $dlghtTZNAME = FALSE; + $dateNow = new DateTime(); + $transitions = $dtz->getTransitions(); + foreach( $transitions as $trans ) { + if( FALSE === ( $date = DateTime::createFromFormat( 'Y-m-d', substr( $trans['time'], 0, 10 )))) + continue; + if( $date > $dateNow ) + break; + if( TRUE !== $trans['isdst'] ) { + $stdDTSTART = $trans['time']; + $stdTZOFFSETTO = $dlghtTZOFFSETFROM = iCalUtilityFunctions::offsetSec2His( $trans['offset'] ); + $stdTZNAME = $trans['abbr']; + } + else { + $dlghtDTSTART = $trans['time']; + $dlghtTZOFFSETTO = $stdTZOFFSETFROM = iCalUtilityFunctions::offsetSec2His( $trans['offset'] ); + $dlghtTZNAME = $trans['abbr']; + } + } + if( !$stdDTSTART || !$stdTZOFFSETTO || !$stdTZOFFSETFROM ) + return FALSE; + $tz = & $calendar->newComponent( 'vtimezone' ); + $tz->setproperty( 'tzid', $timezone ); + if( !empty( $xProp )) { + foreach( $xProp as $xPropName => $xPropValue ) + if( 'x-' == strtolower( substr( $xPropName, 0, 2 ))) + $tz->setproperty( $xPropName, $xPropValue ); + } + $std = & $tz->newComponent( 'standard' ); + $std->setProperty( 'dtstart', $stdDTSTART ); + if( $stdTZNAME ) + $std->setProperty( 'tzname', $stdTZNAME ); + $std->setProperty( 'tzoffsetto', $stdTZOFFSETTO ); + $std->setProperty( 'tzoffsetfrom', $stdTZOFFSETFROM ); + if(( $stdTZOFFSETTO != $stdTZOFFSETFROM ) && ( FALSE === iCalUtilityFunctions::_setTZrrule( $std ))) + $std->setProperty( 'RRULE', array( 'FREQ' => 'YEARLY', 'BYDAY' => array( '-1', 'DAY' => 'SU' ), 'BYMONTH' => 10 )); + if(( !$dlghtDTSTART || !$dlghtTZOFFSETTO || !$dlghtTZOFFSETFROM ) || ( $dlghtTZOFFSETTO == $dlghtTZOFFSETFROM )) + return TRUE; + $dlght = & $tz->newComponent( 'daylight' ); + $dlght->setProperty( 'dtstart', $dlghtDTSTART ); + if( $dlghtTZNAME ) + $dlght->setProperty( 'tzname', $dlghtTZNAME ); + $dlght->setProperty( 'tzoffsetto', $dlghtTZOFFSETTO ); + $dlght->setProperty( 'tzoffsetfrom', $dlghtTZOFFSETFROM ); + if( FALSE === iCalUtilityFunctions::_setTZrrule( $dlght )) + $dlght->setProperty( 'RRULE', array( 'FREQ' => 'YEARLY', 'BYDAY' => array( '-1', 'DAY' => 'SU' ), 'BYMONTH' => 3 )); + return TRUE; + } +/** + * convert date/datetime to timestamp + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.8 - 2008-10-30 + * @param array $datetime datetime/(date) + * @param string $tz timezone + * @return timestamp + */ + public static function _date2timestamp( $datetime, $tz=null ) { + $output = null; + if( !isset( $datetime['hour'] )) $datetime['hour'] = '0'; + if( !isset( $datetime['min'] )) $datetime['min'] = '0'; + if( !isset( $datetime['sec'] )) $datetime['sec'] = '0'; + foreach( $datetime as $dkey => $dvalue ) { + if( 'tz' != $dkey ) + $datetime[$dkey] = (integer) $dvalue; + } + if( $tz ) + $datetime['tz'] = $tz; + $offset = ( isset( $datetime['tz'] ) && ( '' < trim ( $datetime['tz'] ))) ? iCalUtilityFunctions::_tz2offset( $datetime['tz'] ) : 0; + $output = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] + $offset), $datetime['month'], $datetime['day'], $datetime['year'] ); + return $output; + } +/** + * ensures internal date-time/date format for input date-time/date in array format + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 0.3.0 - 2006-08-15 + * @param array $datetime + * @param int $parno optional, default FALSE + * @return array + */ + public static function _date_time_array( $datetime, $parno=FALSE ) { + $output = array(); + foreach( $datetime as $dateKey => $datePart ) { + switch ( $dateKey ) { + case '0': case 'year': $output['year'] = $datePart; break; + case '1': case 'month': $output['month'] = $datePart; break; + case '2': case 'day': $output['day'] = $datePart; break; + } + if( 3 != $parno ) { + switch ( $dateKey ) { + case '0': + case '1': + case '2': break; + case '3': case 'hour': $output['hour'] = $datePart; break; + case '4': case 'min' : $output['min'] = $datePart; break; + case '5': case 'sec' : $output['sec'] = $datePart; break; + case '6': case 'tz' : $output['tz'] = $datePart; break; + } + } + } + if( 3 != $parno ) { + if( !isset( $output['hour'] )) + $output['hour'] = 0; + if( !isset( $output['min'] )) + $output['min'] = 0; + if( !isset( $output['sec'] )) + $output['sec'] = 0; + } + return $output; + } +/** + * ensures internal date-time/date format for input date-time/date in string fromat + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.35 - 2010-12-03 + * @param array $datetime + * @param int $parno optional, default FALSE + * @return array + */ + public static function _date_time_string( $datetime, $parno=FALSE ) { + $datetime = (string) trim( $datetime ); + $tz = null; + $len = strlen( $datetime ) - 1; + if( 'Z' == substr( $datetime, -1 )) { + $tz = 'Z'; + $datetime = trim( substr( $datetime, 0, $len )); + } + elseif( ( ctype_digit( substr( $datetime, -2, 2 ))) && // time or date + ( '-' == substr( $datetime, -3, 1 )) || + ( ':' == substr( $datetime, -3, 1 )) || + ( '.' == substr( $datetime, -3, 1 ))) { + $continue = TRUE; + } + elseif( ( ctype_digit( substr( $datetime, -4, 4 ))) && // 4 pos offset + ( ' +' == substr( $datetime, -6, 2 )) || + ( ' -' == substr( $datetime, -6, 2 ))) { + $tz = substr( $datetime, -5, 5 ); + $datetime = substr( $datetime, 0, ($len - 5)); + } + elseif( ( ctype_digit( substr( $datetime, -6, 6 ))) && // 6 pos offset + ( ' +' == substr( $datetime, -8, 2 )) || + ( ' -' == substr( $datetime, -8, 2 ))) { + $tz = substr( $datetime, -7, 7 ); + $datetime = substr( $datetime, 0, ($len - 7)); + } + elseif( ( 6 < $len ) && ( ctype_digit( substr( $datetime, -6, 6 )))) { + $continue = TRUE; + } + elseif( 'T' == substr( $datetime, -7, 1 )) { + $continue = TRUE; + } + else { + $cx = $tx = 0; // 19970415T133000 US-Eastern + for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) { + $char = substr( $datetime, $cx, 1 ); + if(( ' ' == $char) || ctype_digit( $char)) + break; // if exists, tz ends here.. . ? + else + $tx--; // tz length counter + } + if( 0 > $tx ) { + $tz = substr( $datetime, $tx ); + $datetime = trim( substr( $datetime, 0, $len + $tx + 1 )); + } + } + if( 0 < substr_count( $datetime, '-' )) { + $datetime = str_replace( '-', '/', $datetime ); + } + elseif( ctype_digit( substr( $datetime, 0, 8 )) && + ( 'T' == substr( $datetime, 8, 1 )) && + ctype_digit( substr( $datetime, 9, 6 ))) { + $datetime = substr( $datetime, 4, 2 ) + .'/'.substr( $datetime, 6, 2 ) + .'/'.substr( $datetime, 0, 4 ) + .' '.substr( $datetime, 9, 2 ) + .':'.substr( $datetime, 11, 2 ) + .':'.substr( $datetime, 13); + } + $datestring = date( 'Y-m-d H:i:s', strtotime( $datetime )); + $tz = trim( $tz ); + $output = array(); + $output['year'] = substr( $datestring, 0, 4 ); + $output['month'] = substr( $datestring, 5, 2 ); + $output['day'] = substr( $datestring, 8, 2 ); + if(( 6 == $parno ) || ( 7 == $parno ) || ( !$parno && ( 'Z' == $tz ))) { + $output['hour'] = substr( $datestring, 11, 2 ); + $output['min'] = substr( $datestring, 14, 2 ); + $output['sec'] = substr( $datestring, 17, 2 ); + if( !empty( $tz )) + $output['tz'] = $tz; + } + elseif( 3 != $parno ) { + if(( '00' < substr( $datestring, 11, 2 )) || + ( '00' < substr( $datestring, 14, 2 )) || + ( '00' < substr( $datestring, 17, 2 ))) { + $output['hour'] = substr( $datestring, 11, 2 ); + $output['min'] = substr( $datestring, 14, 2 ); + $output['sec'] = substr( $datestring, 17, 2 ); + } + if( !empty( $tz )) + $output['tz'] = $tz; + } + return $output; + } +/** + * convert local startdate/enddate (Ymd[His]) to duration array + * + * uses this component dates if missing input dates + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.11 - 2010-10-21 + * @param array $startdate + * @param array $duration + * @return array duration + */ + public static function _date2duration( $startdate, $enddate ) { + $startWdate = mktime( 0, 0, 0, $startdate['month'], $startdate['day'], $startdate['year'] ); + $endWdate = mktime( 0, 0, 0, $enddate['month'], $enddate['day'], $enddate['year'] ); + $wduration = $endWdate - $startWdate; + $dur = array(); + $dur['week'] = (int) floor( $wduration / ( 7 * 24 * 60 * 60 )); + $wduration = $wduration % ( 7 * 24 * 60 * 60 ); + $dur['day'] = (int) floor( $wduration / ( 24 * 60 * 60 )); + $wduration = $wduration % ( 24 * 60 * 60 ); + $dur['hour'] = (int) floor( $wduration / ( 60 * 60 )); + $wduration = $wduration % ( 60 * 60 ); + $dur['min'] = (int) floor( $wduration / ( 60 )); + $dur['sec'] = (int) $wduration % ( 60 ); + return $dur; + } +/** + * ensures internal duration format for input in array format + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.1.1 - 2007-06-24 + * @param array $duration + * @return array + */ + public static function _duration_array( $duration ) { + $output = array(); + if( is_array( $duration ) && + ( 1 == count( $duration )) && + isset( $duration['sec'] ) && + ( 60 < $duration['sec'] )) { + $durseconds = $duration['sec']; + $output['week'] = floor( $durseconds / ( 60 * 60 * 24 * 7 )); + $durseconds = $durseconds % ( 60 * 60 * 24 * 7 ); + $output['day'] = floor( $durseconds / ( 60 * 60 * 24 )); + $durseconds = $durseconds % ( 60 * 60 * 24 ); + $output['hour'] = floor( $durseconds / ( 60 * 60 )); + $durseconds = $durseconds % ( 60 * 60 ); + $output['min'] = floor( $durseconds / ( 60 )); + $output['sec'] = ( $durseconds % ( 60 )); + } + else { + foreach( $duration as $durKey => $durValue ) { + if( empty( $durValue )) continue; + switch ( $durKey ) { + case '0': case 'week': $output['week'] = $durValue; break; + case '1': case 'day': $output['day'] = $durValue; break; + case '2': case 'hour': $output['hour'] = $durValue; break; + case '3': case 'min': $output['min'] = $durValue; break; + case '4': case 'sec': $output['sec'] = $durValue; break; + } + } + } + if( isset( $output['week'] ) && ( 0 < $output['week'] )) { + unset( $output['day'], $output['hour'], $output['min'], $output['sec'] ); + return $output; + } + unset( $output['week'] ); + if( empty( $output['day'] )) + unset( $output['day'] ); + if ( isset( $output['hour'] ) || isset( $output['min'] ) || isset( $output['sec'] )) { + if( !isset( $output['hour'] )) $output['hour'] = 0; + if( !isset( $output['min'] )) $output['min'] = 0; + if( !isset( $output['sec'] )) $output['sec'] = 0; + if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] )) + unset( $output['hour'], $output['min'], $output['sec'] ); + } + return $output; + } +/** + * ensures internal duration format for input in string format + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.0.5 - 2007-03-14 + * @param string $duration + * @return array + */ + public static function _duration_string( $duration ) { + $duration = (string) trim( $duration ); + while( 'P' != strtoupper( substr( $duration, 0, 1 ))) { + if( 0 < strlen( $duration )) + $duration = substr( $duration, 1 ); + else + return false; // no leading P !?!? + } + $duration = substr( $duration, 1 ); // skip P + $duration = str_replace ( 't', 'T', $duration ); + $duration = str_replace ( 'T', '', $duration ); + $output = array(); + $val = null; + for( $ix=0; $ix < strlen( $duration ); $ix++ ) { + switch( strtoupper( substr( $duration, $ix, 1 ))) { + case 'W': + $output['week'] = $val; + $val = null; + break; + case 'D': + $output['day'] = $val; + $val = null; + break; + case 'H': + $output['hour'] = $val; + $val = null; + break; + case 'M': + $output['min'] = $val; + $val = null; + break; + case 'S': + $output['sec'] = $val; + $val = null; + break; + default: + if( !ctype_digit( substr( $duration, $ix, 1 ))) + return false; // unknown duration control character !?!? + else + $val .= substr( $duration, $ix, 1 ); + } + } + return iCalUtilityFunctions::_duration_array( $output ); + } +/** + * convert duration to date in array format + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.7 - 2011-03-03 + * @param array $startdate + * @param array $duration + * @return array, date format + */ + public static function _duration2date( $startdate=null, $duration=null ) { + if( empty( $startdate )) return FALSE; + if( empty( $duration )) return FALSE; + $dateOnly = ( isset( $startdate['hour'] ) || isset( $startdate['min'] ) || isset( $startdate['sec'] )) ? FALSE : TRUE; + $startdate['hour'] = ( isset( $startdate['hour'] )) ? $startdate['hour'] : 0; + $startdate['min'] = ( isset( $startdate['min'] )) ? $startdate['min'] : 0; + $startdate['sec'] = ( isset( $startdate['sec'] )) ? $startdate['sec'] : 0; + $dtend = 0; + if( isset( $duration['week'] )) + $dtend += ( $duration['week'] * 7 * 24 * 60 * 60 ); + if( isset( $duration['day'] )) + $dtend += ( $duration['day'] * 24 * 60 * 60 ); + if( isset( $duration['hour'] )) + $dtend += ( $duration['hour'] * 60 *60 ); + if( isset( $duration['min'] )) + $dtend += ( $duration['min'] * 60 ); + if( isset( $duration['sec'] )) + $dtend += $duration['sec']; + $dtend = mktime( $startdate['hour'], $startdate['min'], ( $startdate['sec'] + $dtend ), $startdate['month'], $startdate['day'], $startdate['year'] ); + $dtend2 = array(); + $dtend2['year'] = date('Y', $dtend ); + $dtend2['month'] = date('m', $dtend ); + $dtend2['day'] = date('d', $dtend ); + $dtend2['hour'] = date('H', $dtend ); + $dtend2['min'] = date('i', $dtend ); + $dtend2['sec'] = date('s', $dtend ); + if( isset( $startdate['tz'] )) + $dtend2['tz'] = $startdate['tz']; + if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] ))) + unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] ); + return $dtend2; + } +/** + * if not preSet, if exist, remove key with expected value from array and return hit value else return elseValue + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.16 - 2008-11-08 + * @param array $array + * @param string $expkey, expected key + * @param string $expval, expected value + * @param int $hitVal optional, return value if found + * @param int $elseVal optional, return value if not found + * @param int $preSet optional, return value if already preset + * @return int + */ + public static function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) { + if( $preSet ) + return $preSet; + if( !is_array( $array ) || ( 0 == count( $array ))) + return $elseVal; + foreach( $array as $key => $value ) { + if( strtoupper( $expkey ) == strtoupper( $key )) { + if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) { + unset( $array[$key] ); + return $hitVal; + } + } + } + return $elseVal; + } +/** + * creates formatted output for calendar component property data value type date/date-time + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.8 - 2008-10-30 + * @param array $datetime + * @param int $parno, optional, default 6 + * @return string + */ + public static function _format_date_time( $datetime, $parno=6 ) { + if( !isset( $datetime['year'] ) && + !isset( $datetime['month'] ) && + !isset( $datetime['day'] ) && + !isset( $datetime['hour'] ) && + !isset( $datetime['min'] ) && + !isset( $datetime['sec'] )) + return ; + $output = null; + // if( !isset( $datetime['day'] )) { $o=''; foreach($datetime as $k=>$v) {if(is_array($v)) $v=implode('-',$v);$o.=" $k=>$v";} echo " day SAKNAS : $o
    \n"; } + foreach( $datetime as $dkey => & $dvalue ) + if( 'tz' != $dkey ) $dvalue = (integer) $dvalue; + $output = date('Ymd', mktime( 0, 0, 0, $datetime['month'], $datetime['day'], $datetime['year'])); + if( isset( $datetime['hour'] ) || + isset( $datetime['min'] ) || + isset( $datetime['sec'] ) || + isset( $datetime['tz'] )) { + if( isset( $datetime['tz'] ) && + !isset( $datetime['hour'] )) + $datetime['hour'] = 0; + if( isset( $datetime['hour'] ) && + !isset( $datetime['min'] )) + $datetime['min'] = 0; + if( isset( $datetime['hour'] ) && + isset( $datetime['min'] ) && + !isset( $datetime['sec'] )) + $datetime['sec'] = 0; + $date = mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year']); + $output .= date('\THis', $date ); + if( isset( $datetime['tz'] ) && ( '' < trim ( $datetime['tz'] ))) { + $datetime['tz'] = trim( $datetime['tz'] ); + if( 'Z' == $datetime['tz'] ) + $output .= 'Z'; + $offset = iCalUtilityFunctions::_tz2offset( $datetime['tz'] ); + if( 0 != $offset ) { + $date = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] + $offset), $datetime['month'], $datetime['day'], $datetime['year']); + $output = date( 'Ymd\THis\Z', $date ); + } + } + elseif( 7 == $parno ) + $output .= 'Z'; + } + return $output; + } +/** + * creates formatted output for calendar component property data value type duration + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.9 - 2011-06-17 + * @param array $duration ( week, day, hour, min, sec ) + * @return string + */ + public static function _format_duration( $duration ) { + if( isset( $duration['week'] ) || + isset( $duration['day'] ) || + isset( $duration['hour'] ) || + isset( $duration['min'] ) || + isset( $duration['sec'] )) + $ok = TRUE; + else + return; + if( isset( $duration['week'] ) && ( 0 < $duration['week'] )) + return 'P'.$duration['week'].'W'; + $output = 'P'; + if( isset($duration['day'] ) && ( 0 < $duration['day'] )) + $output .= $duration['day'].'D'; + if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) || + ( isset( $duration['min']) && ( 0 < $duration['min'] )) || + ( isset( $duration['sec']) && ( 0 < $duration['sec'] ))) + $output .= 'T'; + $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : ''; + $output .= ( isset( $duration['min']) && ( 0 < $duration['min'] )) ? $duration['min']. 'M' : ''; + $output .= ( isset( $duration['sec']) && ( 0 < $duration['sec'] )) ? $duration['sec']. 'S' : ''; + if( 'P' == $output ) + $output = 'PT0S'; + return $output; + } +/** + * checks if input array contains a date + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.16 - 2008-10-25 + * @param array $input + * @return bool + */ + public static function _isArrayDate( $input ) { + if( isset( $input['week'] ) || ( !in_array( count( $input ), array( 3, 6, 7 )))) + return FALSE; + if( 7 == count( $input )) + return TRUE; + if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] )) + return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] ); + if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] )) + return FALSE; + if( in_array( 0, $input )) + return FALSE; + if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] )) + return FALSE; + if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) && + checkdate( (int) $input[1], (int) $input[2], (int) $input[0] )) + return TRUE; + $input = iCalUtilityFunctions::_date_time_string( $input[1].'/'.$input[2].'/'.$input[0], 3 ); // m - d - Y + if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] )) + return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] ); + return FALSE; + } +/** + * checks if input array contains a timestamp date + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.16 - 2008-10-18 + * @param array $input + * @return bool + */ + public static function _isArrayTimestampDate( $input ) { + return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ; + } +/** + * controll if input string contains trailing UTC offset + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.16 - 2008-10-19 + * @param string $input + * @return bool + */ + public static function _isOffset( $input ) { + $input = trim( (string) $input ); + if( 'Z' == substr( $input, -1 )) + return TRUE; + elseif(( 5 <= strlen( $input )) && + ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) && + ( '0000' < substr( $input, -4 )) && ( '9999' >= substr( $input, -4 ))) + return TRUE; + elseif(( 7 <= strlen( $input )) && + ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) && + ( '000000' < substr( $input, -6 )) && ( '999999' >= substr( $input, -6 ))) + return TRUE; + return FALSE; + + } +/** + * transform offset in seconds to [-/+]hhmm[ss] + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2011-05-02 + * @param string $seconds + * @return string + */ + public static function offsetSec2His( $seconds ) { + if( '-' == substr( $seconds, 0, 1 )) { + $prefix = '-'; + $seconds = substr( $seconds, 1 ); + } + elseif( '+' == substr( $seconds, 0, 1 )) { + $prefix = '+'; + $seconds = substr( $seconds, 1 ); + } + else + $prefix = '+'; + $output = ''; + $hour = (int) floor( $seconds / 3600 ); + if( 10 > $hour ) + $hour = '0'.$hour; + $seconds = $seconds % 3600; + $min = (int) floor( $seconds / 60 ); + if( 10 > $min ) + $min = '0'.$min; + $output = $hour.$min; + $seconds = $seconds % 60; + if( 0 < $seconds) { + if( 9 < $seconds) + $output .= $seconds; + else + $output .= '0'.$seconds; + } + return $prefix.$output; + } +/** + * remakes a recur pattern to an array of dates + * + * if missing, UNTIL is set 1 year from startdate (emergency break) + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.10 - 2011-07-07 + * @param array $result, array to update, array([timestamp] => timestamp) + * @param array $recur, pattern for recurrency (only value part, params ignored) + * @param array $wdate, component start date + * @param array $startdate, start date + * @param array $enddate, optional + * @return array of recurrence (start-)dates as index + * @todo BYHOUR, BYMINUTE, BYSECOND, ev. BYSETPOS due to ambiguity, WEEKLY at year end/start + */ + public static function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) { + foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v; + $wdateStart = $wdate; + $wdatets = iCalUtilityFunctions::_date2timestamp( $wdate ); + $startdatets = iCalUtilityFunctions::_date2timestamp( $startdate ); + if( !$enddate ) { + $enddate = $startdate; + $enddate['year'] += 1; + } +// echo "recur __in_ comp start ".implode('-',$wdate)." period start ".implode('-',$startdate)." period end ".implode('-',$enddate)."
    \n";print_r($recur);echo "
    \n";//test### + $endDatets = iCalUtilityFunctions::_date2timestamp( $enddate ); // fix break + if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] )) + $recur['UNTIL'] = $enddate; // create break + if( isset( $recur['UNTIL'] )) { + $tdatets = iCalUtilityFunctions::_date2timestamp( $recur['UNTIL'] ); + if( $endDatets > $tdatets ) { + $endDatets = $tdatets; // emergency break + $enddate = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 ); + } + else + $recur['UNTIL'] = iCalUtilityFunctions::_timestamp2date( $endDatets, 6 ); + } + if( $wdatets > $endDatets ) { +// echo "recur out of date ".implode('-',iCalUtilityFunctions::_date_time_string(date('Y-m-d H:i:s',$wdatets),6))."
    \n";//test + return array(); // nothing to do.. . + } + if( !isset( $recur['FREQ'] )) // "MUST be specified.. ." + $recur['FREQ'] = 'DAILY'; // ?? + $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ?? + $weekStart = (int) date( 'W', ( $wdatets + $wkst )); + if( !isset( $recur['INTERVAL'] )) + $recur['INTERVAL'] = 1; + $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence + /* find out how to step up dates and set index for interval count */ + $step = array(); + if( 'YEARLY' == $recur['FREQ'] ) + $step['year'] = 1; + elseif( 'MONTHLY' == $recur['FREQ'] ) + $step['month'] = 1; + elseif( 'WEEKLY' == $recur['FREQ'] ) + $step['day'] = 7; + else + $step['day'] = 1; + if( isset( $step['year'] ) && isset( $recur['BYMONTH'] )) + $step = array( 'month' => 1 ); + if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ?? + $step = array( 'day' => 7 ); + if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] )) + $step = array( 'day' => 1 ); + $intervalarr = array(); + if( 1 < $recur['INTERVAL'] ) { + $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst ); + $intervalarr = array( $intervalix => 0 ); + } + if( isset( $recur['BYSETPOS'] )) { // save start date + weekno + $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array(); +// echo "bysetposXold_start=$bysetposYold $bysetposMold $bysetposDold
    \n"; // test ### + if( is_array( $recur['BYSETPOS'] )) { + foreach( $recur['BYSETPOS'] as $bix => $bval ) + $recur['BYSETPOS'][$bix] = (int) $bval; + } + else + $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] ); + if( 'YEARLY' == $recur['FREQ'] ) { + $wdate['month'] = $wdate['day'] = 1; // start from beginning of year + $wdatets = iCalUtilityFunctions::_date2timestamp( $wdate ); + iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'year' => 1 )); // make sure to count whole last year + } + elseif( 'MONTHLY' == $recur['FREQ'] ) { + $wdate['day'] = 1; // start from beginning of month + $wdatets = iCalUtilityFunctions::_date2timestamp( $wdate ); + iCalUtilityFunctions::_stepdate( $enddate, $endDatets, array( 'month' => 1 )); // make sure to count whole last month + } + else + iCalUtilityFunctions::_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period +// echo "BYSETPOS endDat++ =".implode('-',$enddate).' step='.var_export($step,TRUE)."
    \n";//test### + $bysetposWold = (int) date( 'W', ( $wdatets + $wkst )); + $bysetposYold = $wdate['year']; + $bysetposMold = $wdate['month']; + $bysetposDold = $wdate['day']; + } + else + iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step); + $year_old = null; + $daynames = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ); + /* MAIN LOOP */ +// echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."
    \n";//test + while( TRUE ) { + if( isset( $endDatets ) && ( $wdatets > $endDatets )) + break; + if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] )) + break; + if( $year_old != $wdate['year'] ) { + $year_old = $wdate['year']; + $daycnts = array(); + $yeardays = $weekno = 0; + $yeardaycnt = array(); + for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters + $daycnts[$m] = array(); + $weekdaycnt = array(); + foreach( $daynames as $dn ) + $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0; + $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] )); + for( $d = 1; $d <= $mcnt; $d++ ) { + $daycnts[$m][$d] = array(); + if( isset( $recur['BYYEARDAY'] )) { + $yeardays++; + $daycnts[$m][$d]['yearcnt_up'] = $yeardays; + } + if( isset( $recur['BYDAY'] )) { + $day = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] )); + $day = $daynames[$day]; + $daycnts[$m][$d]['DAY'] = $day; + $weekdaycnt[$day]++; + $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day]; + $yeardaycnt[$day]++; + $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day]; + } + if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) + $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year'])); + } + } + $daycnt = 0; + $yeardaycnt = array(); + if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) { + $weekno = null; + for( $d=31; $d > 25; $d-- ) { // get last weekno for year + if( !$weekno ) + $weekno = $daycnts[12][$d]['weekno_up']; + elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) { + $weekno = $daycnts[12][$d]['weekno_up']; + break; + } + } + } + for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters + $weekdaycnt = array(); + foreach( $daynames as $dn ) + $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0; + $monthcnt = 0; + $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] )); + for( $d = $mcnt; $d > 0; $d-- ) { + if( isset( $recur['BYYEARDAY'] )) { + $daycnt -= 1; + $daycnts[$m][$d]['yearcnt_down'] = $daycnt; + } + if( isset( $recur['BYMONTHDAY'] )) { + $monthcnt -= 1; + $daycnts[$m][$d]['monthcnt_down'] = $monthcnt; + } + if( isset( $recur['BYDAY'] )) { + $day = $daycnts[$m][$d]['DAY']; + $weekdaycnt[$day] -= 1; + $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day]; + $yeardaycnt[$day] -= 1; + $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day]; + } + if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) + $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1); + } + } + } + /* check interval */ + if( 1 < $recur['INTERVAL'] ) { + /* create interval index */ + $intervalix = iCalUtilityFunctions::_recurIntervalIx( $recur['FREQ'], $wdate, $wkst ); + /* check interval */ + $currentKey = array_keys( $intervalarr ); + $currentKey = end( $currentKey ); // get last index + if( $currentKey != $intervalix ) + $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 )); + if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) && + ( 0 != $intervalarr[$intervalix] )) { + /* step up date */ +// echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."
    \n";//test + iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step); + continue; + } + else // continue within the selected interval + $intervalarr[$intervalix] = 0; +// echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."
    \n";//test + } + $updateOK = TRUE; + if( $updateOK && isset( $recur['BYMONTH'] )) + $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTH'] + , $wdate['month'] + ,($wdate['month'] - 13)); + if( $updateOK && isset( $recur['BYWEEKNO'] )) + $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYWEEKNO'] + , $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] + , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] ); + if( $updateOK && isset( $recur['BYYEARDAY'] )) + $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYYEARDAY'] + , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up'] + , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] ); + if( $updateOK && isset( $recur['BYMONTHDAY'] )) + $updateOK = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYMONTHDAY'] + , $wdate['day'] + , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] ); +// echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "
    \n";//test### + if( $updateOK && isset( $recur['BYDAY'] )) { + $updateOK = FALSE; + $m = $wdate['month']; + $d = $wdate['day']; + if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no + $daynoexists = $daynosw = $daynamesw = FALSE; + if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] ) + $daynamesw = TRUE; + if( isset( $recur['BYDAY'][0] )) { + $daynoexists = TRUE; + if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] )) + $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0] + , $daycnts[$m][$d]['monthdayno_up'] + , $daycnts[$m][$d]['monthdayno_down'] ); + elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' )) + $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $recur['BYDAY'][0] + , $daycnts[$m][$d]['yeardayno_up'] + , $daycnts[$m][$d]['yeardayno_down'] ); + } + if(( $daynoexists && $daynosw && $daynamesw ) || + ( !$daynoexists && !$daynosw && $daynamesw )) { + $updateOK = TRUE; + } +// echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw
    \n"; // test ### + } + else { + foreach( $recur['BYDAY'] as $bydayvalue ) { + $daynoexists = $daynosw = $daynamesw = FALSE; + if( isset( $bydayvalue['DAY'] ) && + ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] )) + $daynamesw = TRUE; + if( isset( $bydayvalue[0] )) { + $daynoexists = TRUE; + if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || + isset( $recur['BYMONTH'] )) + $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0'] + , $daycnts[$m][$d]['monthdayno_up'] + , $daycnts[$m][$d]['monthdayno_down'] ); + elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' )) + $daynosw = iCalUtilityFunctions::_recurBYcntcheck( $bydayvalue['0'] + , $daycnts[$m][$d]['yeardayno_up'] + , $daycnts[$m][$d]['yeardayno_down'] ); + } +// echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw
    \n"; // test ### + if(( $daynoexists && $daynosw && $daynamesw ) || + ( !$daynoexists && !$daynosw && $daynamesw )) { + $updateOK = TRUE; + break; + } + } + } + } +// echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "
    \n"; // test ### + /* check BYSETPOS */ + if( $updateOK ) { + if( isset( $recur['BYSETPOS'] ) && + ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) { + if( isset( $recur['WEEKLY'] )) { + if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] ) + $bysetposw1[] = $wdatets; + else + $bysetposw2[] = $wdatets; + } + else { + if(( isset( $recur['FREQ'] ) && ( 'YEARLY' == $recur['FREQ'] ) && + ( $bysetposYold == $wdate['year'] )) || + ( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] ) && + (( $bysetposYold == $wdate['year'] ) && + ( $bysetposMold == $wdate['month'] ))) || + ( isset( $recur['FREQ'] ) && ( 'DAILY' == $recur['FREQ'] ) && + (( $bysetposYold == $wdate['year'] ) && + ( $bysetposMold == $wdate['month']) && + ( $bysetposDold == $wdate['day'] )))) { +// echo "bysetposymd1[]=".implode('-',iCalUtilityFunctions::_date_time_string(date('Y-m-d H:i:s',$wdatets),6))."
    \n";//test + $bysetposymd1[] = $wdatets; + } + else { +// echo "bysetposymd2[]=".implode('-',iCalUtilityFunctions::_date_time_string(date('Y-m-d H:i:s',$wdatets),6))."
    \n";//test + $bysetposymd2[] = $wdatets; + } + } + } + else { + /* update result array if BYSETPOS is set */ + $countcnt++; + if( $startdatets <= $wdatets ) { // only output within period + $result[$wdatets] = TRUE; +// echo "recur ".implode('-',iCalUtilityFunctions::_date_time_string(date('Y-m-d H:i:s',$wdatets),6))."
    \n";//test + } +// echo "recur undate ".implode('-',iCalUtilityFunctions::_date_time_string(date('Y-m-d H:i:s',$wdatets),6))." okdatstart ".implode('-',iCalUtilityFunctions::_date_time_string(date('Y-m-d H:i:s',$startdatets),6))."
    \n";//test + $updateOK = FALSE; + } + } + /* step up date */ + iCalUtilityFunctions::_stepdate( $wdate, $wdatets, $step); + /* check if BYSETPOS is set for updating result array */ + if( $updateOK && isset( $recur['BYSETPOS'] )) { + $bysetpos = FALSE; + if( isset( $recur['FREQ'] ) && ( 'YEARLY' == $recur['FREQ'] ) && + ( $bysetposYold != $wdate['year'] )) { + $bysetpos = TRUE; + $bysetposYold = $wdate['year']; + } + elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] && + (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) { + $bysetpos = TRUE; + $bysetposYold = $wdate['year']; + $bysetposMold = $wdate['month']; + } + elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY' == $recur['FREQ'] )) { + $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year'])); + if( $bysetposWold != $weekno ) { + $bysetposWold = $weekno; + $bysetpos = TRUE; + } + } + elseif( isset( $recur['FREQ'] ) && ( 'DAILY' == $recur['FREQ'] ) && + (( $bysetposYold != $wdate['year'] ) || + ( $bysetposMold != $wdate['month'] ) || + ( $bysetposDold != $wdate['day'] ))) { + $bysetpos = TRUE; + $bysetposYold = $wdate['year']; + $bysetposMold = $wdate['month']; + $bysetposDold = $wdate['day']; + } + if( $bysetpos ) { + if( isset( $recur['BYWEEKNO'] )) { + $bysetposarr1 = & $bysetposw1; + $bysetposarr2 = & $bysetposw2; + } + else { + $bysetposarr1 = & $bysetposymd1; + $bysetposarr2 = & $bysetposymd2; + } +// echo 'test före out startYMD (weekno)='.$wdateStart['year'].':'.$wdateStart['month'].':'.$wdateStart['day']." ($weekStart) "; // test ### + foreach( $recur['BYSETPOS'] as $ix ) { + if( 0 > $ix ) // both positive and negative BYSETPOS allowed + $ix = ( count( $bysetposarr1 ) + $ix + 1); + $ix--; + if( isset( $bysetposarr1[$ix] )) { + if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period +// $testdate = iCalUtilityFunctions::_timestamp2date( $bysetposarr1[$ix], 6 ); // test ### +// $testweekno = (int) date( 'W', mktime( 0, 0, $wkst, $testdate['month'], $testdate['day'], $testdate['year'] )); // test ### +// echo " testYMD (weekno)=".$testdate['year'].':'.$testdate['month'].':'.$testdate['day']." ($testweekno)"; // test ### + $result[$bysetposarr1[$ix]] = TRUE; +// echo " recur ".implode('-',iCalUtilityFunctions::_date_time_string(date('Y-m-d H:i:s',$bysetposarr1[$ix]),6)); // test ### + } + $countcnt++; + } + if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] )) + break; + } +// echo "
    \n"; // test ### + $bysetposarr1 = $bysetposarr2; + $bysetposarr2 = array(); + } + } + } + } + public static function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) { + if( is_array( $BYvalue ) && + ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue ))) + return TRUE; + elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue )) + return TRUE; + else + return FALSE; + } + public static function _recurIntervalIx( $freq, $date, $wkst ) { + /* create interval index */ + switch( $freq ) { + case 'YEARLY': + $intervalix = $date['year']; + break; + case 'MONTHLY': + $intervalix = $date['year'].'-'.$date['month']; + break; + case 'WEEKLY': + $wdatets = iCalUtilityFunctions::_date2timestamp( $date ); + $intervalix = (int) date( 'W', ( $wdatets + $wkst )); + break; + case 'DAILY': + default: + $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day']; + break; + } + return $intervalix; + } +/** + * convert input format for exrule and rrule to internal format + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.10 - 2011-07-07 + * @param array $rexrule + * @return array + */ + public static function _setRexrule( $rexrule ) { + $input = array(); + if( empty( $rexrule )) + return $input; + foreach( $rexrule as $rexrulelabel => $rexrulevalue ) { + $rexrulelabel = strtoupper( $rexrulelabel ); + if( 'UNTIL' != $rexrulelabel ) + $input[$rexrulelabel] = $rexrulevalue; + else { + if( iCalUtilityFunctions::_isArrayTimestampDate( $rexrulevalue )) // timestamp date + $input[$rexrulelabel] = iCalUtilityFunctions::_timestamp2date( $rexrulevalue, 6 ); + elseif( iCalUtilityFunctions::_isArrayDate( $rexrulevalue )) // date-time + $input[$rexrulelabel] = iCalUtilityFunctions::_date_time_array( $rexrulevalue, 6 ); + elseif( 8 <= strlen( trim( $rexrulevalue ))) // ex. 2006-08-03 10:12:18 + $input[$rexrulelabel] = iCalUtilityFunctions::_date_time_string( $rexrulevalue ); + if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] )) + $input[$rexrulelabel]['tz'] = 'Z'; + } + } + /* set recurrence rule specification in rfc2445 order */ + $input2 = array(); + if( isset( $input['FREQ'] )) + $input2['FREQ'] = $input['FREQ']; + if( isset( $input['UNTIL'] )) + $input2['UNTIL'] = $input['UNTIL']; + elseif( isset( $input['COUNT'] )) + $input2['COUNT'] = $input['COUNT']; + if( isset( $input['INTERVAL'] )) + $input2['INTERVAL'] = $input['INTERVAL']; + if( isset( $input['BYSECOND'] )) + $input2['BYSECOND'] = $input['BYSECOND']; + if( isset( $input['BYMINUTE'] )) + $input2['BYMINUTE'] = $input['BYMINUTE']; + if( isset( $input['BYHOUR'] )) + $input2['BYHOUR'] = $input['BYHOUR']; + if( isset( $input['BYDAY'] )) { + if( !is_array( $input['BYDAY'] )) // ensure upper case.. . + $input2['BYDAY'] = strtoupper( $input['BYDAY'] ); + else { + foreach( $input['BYDAY'] as $BYDAYx => $BYDAYv ) { + if( 'DAY' == strtoupper( $BYDAYx )) + $input2['BYDAY']['DAY'] = strtoupper( $BYDAYv ); + elseif( !is_array( $BYDAYv )) { + $input2['BYDAY'][$BYDAYx] = $BYDAYv; + } + else { + foreach( $BYDAYv as $BYDAYx2 => $BYDAYv2 ) { + if( 'DAY' == strtoupper( $BYDAYx2 )) + $input2['BYDAY'][$BYDAYx]['DAY'] = strtoupper( $BYDAYv2 ); + else + $input2['BYDAY'][$BYDAYx][$BYDAYx2] = $BYDAYv2; + } + } + } + } + } + if( isset( $input['BYMONTHDAY'] )) + $input2['BYMONTHDAY'] = $input['BYMONTHDAY']; + if( isset( $input['BYYEARDAY'] )) + $input2['BYYEARDAY'] = $input['BYYEARDAY']; + if( isset( $input['BYWEEKNO'] )) + $input2['BYWEEKNO'] = $input['BYWEEKNO']; + if( isset( $input['BYMONTH'] )) + $input2['BYMONTH'] = $input['BYMONTH']; + if( isset( $input['BYSETPOS'] )) + $input2['BYSETPOS'] = $input['BYSETPOS']; + if( isset( $input['WKST'] )) + $input2['WKST'] = $input['WKST']; + return $input2; + } +/** + * convert format for input date to internal date with parameters + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.4 - 2011-08-03 + * @param mixed $year + * @param mixed $month optional + * @param int $day optional + * @param int $hour optional + * @param int $min optional + * @param int $sec optional + * @param string $tz optional + * @param array $params optional + * @param string $caller optional + * @param string $objName optional + * @param string $tzid optional + * @return array + */ + public static function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null, $objName=null, $tzid=FALSE ) { + $input = $parno = null; + $localtime = (( 'dtstart' == $caller ) && in_array( $objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE; + if( iCalUtilityFunctions::_isArrayDate( $year )) { + if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); + $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); + if( isset( $input['params']['TZID'] )) { + $input['params']['VALUE'] = 'DATE-TIME'; + unset( $year['tz'] ); + } + $hitval = (( !empty( $year['tz'] ) || !empty( $year[6] ))) ? 7 : 6; + $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval ); + $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $year ), $parno ); + $input['value'] = iCalUtilityFunctions::_date_time_array( $year, $parno ); + } + elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { + if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); + $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); + if( isset( $input['params']['TZID'] )) { + $input['params']['VALUE'] = 'DATE-TIME'; + unset( $year['tz'] ); + } + $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 ); + $hitval = ( isset( $year['tz'] )) ? 7 : 6; + $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno ); + $input['value'] = iCalUtilityFunctions::_timestamp2date( $year, $parno ); + } + elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 + if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); + $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); + if( isset( $input['params']['TZID'] )) { + $input['params']['VALUE'] = 'DATE-TIME'; + $parno = 6; + } + elseif( $tzid && iCalUtilityFunctions::_isOffset( substr( $year, -7 ))) { + if(( in_array( substr( $year, -5, 1 ), array( '+', '-' ))) && + ( '0000' < substr( $year, -4 )) && ( '9999' >= substr( $year, -4 ))) + $year = substr( $year, 0, ( strlen( $year ) - 5 )); + elseif(( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) && + ( '000000' < substr( $input, -6 )) && ( '999999' >= substr( $input, -6 ))) + $year = substr( $year, 0, ( strlen( $year ) - 7 )); + $parno = 6; + } + $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno ); + $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno ); + $input['value'] = iCalUtilityFunctions::_date_time_string( $year, $parno ); + } + else { + if( is_array( $params )) { + if( $localtime ) unset ( $params['VALUE'], $params['TZID'] ); + $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )); + } + elseif( is_array( $tz )) { + $input['params'] = iCalUtilityFunctions::_setParams( $tz, array( 'VALUE' => 'DATE-TIME' )); + $tz = FALSE; + } + elseif( is_array( $hour )) { + $input['params'] = iCalUtilityFunctions::_setParams( $hour, array( 'VALUE' => 'DATE-TIME' )); + $hour = $min = $sec = $tz = FALSE; + } + if( isset( $input['params']['TZID'] )) { + $tz = null; + $input['params']['VALUE'] = 'DATE-TIME'; + } + $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE', 3 ); + $hitval = ( !empty( $tz )) ? 7 : 6; + $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno ); + $input['value'] = array( 'year' => $year, 'month' => $month, 'day' => $day ); + if( 3 != $parno ) { + $input['value']['hour'] = ( $hour ) ? $hour : '0'; + $input['value']['min'] = ( $min ) ? $min : '0'; + $input['value']['sec'] = ( $sec ) ? $sec : '0'; + if( !empty( $tz )) + $input['value']['tz'] = $tz; + } + } + if( 3 == $parno ) { + $input['params']['VALUE'] = 'DATE'; + unset( $input['value']['tz'] ); + unset( $input['params']['TZID'] ); + } + elseif( isset( $input['params']['TZID'] )) + unset( $input['value']['tz'] ); + if( $localtime ) + unset( $input['value']['tz'], $input['params']['TZID'] ); + elseif(( !isset( $input['params']['VALUE'] ) || ( $input['params']['VALUE'] != 'DATE' )) && !isset( $input['params']['TZID'] ) && $tzid ) + $input['params']['TZID'] = $tzid; + if( isset( $input['value']['tz'] )) + $input['value']['tz'] = (string) $input['value']['tz']; + if( !empty( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && + ( !iCalUtilityFunctions::_isOffset( $input['value']['tz'] ))) + $input['params']['TZID'] = $input['value']['tz']; + return $input; + } +/** + * convert format for input date (UTC) to internal date with parameters + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.17 - 2008-10-31 + * @param mixed $year + * @param mixed $month optional + * @param int $day optional + * @param int $hour optional + * @param int $min optional + * @param int $sec optional + * @param array $params optional + * @return array + */ + public static function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { + $input = null; + if( iCalUtilityFunctions::_isArrayDate( $year )) { + $input['value'] = iCalUtilityFunctions::_date_time_array( $year, 7 ); + $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) ); + } + elseif( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { + $input['value'] = iCalUtilityFunctions::_timestamp2date( $year, 7 ); + $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) ); + } + elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 + $input['value'] = iCalUtilityFunctions::_date_time_string( $year, 7 ); + $input['params'] = iCalUtilityFunctions::_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) ); + } + else { + $input['value'] = array( 'year' => $year + , 'month' => $month + , 'day' => $day + , 'hour' => $hour + , 'min' => $min + , 'sec' => $sec ); + $input['params'] = iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' )); + } + $parno = iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default + if( !isset( $input['value']['hour'] )) + $input['value']['hour'] = 0; + if( !isset( $input['value']['min'] )) + $input['value']['min'] = 0; + if( !isset( $input['value']['sec'] )) + $input['value']['sec'] = 0; + if( !isset( $input['value']['tz'] ) || !iCalUtilityFunctions::_isOffset( $input['value']['tz'] )) + $input['value']['tz'] = 'Z'; + return $input; + } +/** + * check index and set (an indexed) content in multiple value array + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.12 - 2011-01-03 + * @param array $valArr + * @param mixed $value + * @param array $params + * @param array $defaults + * @param int $index + * @return void + */ + public static function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) { + if( !is_array( $valArr )) $valArr = array(); + if( $index ) + $index = $index - 1; + elseif( 0 < count( $valArr )) { + $keys = array_keys( $valArr ); + $index = end( $keys ) + 1; + } + else + $index = 0; + $valArr[$index] = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params, $defaults )); + ksort( $valArr ); + } +/** + * set input (formatted) parameters- component property attributes + * + * default parameters can be set, if missing + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 1.x.x - 2007-05-01 + * @param array $params + * @param array $defaults + * @return array + */ + public static function _setParams( $params, $defaults=FALSE ) { + if( !is_array( $params)) + $params = array(); + $input = array(); + foreach( $params as $paramKey => $paramValue ) { + if( is_array( $paramValue )) { + foreach( $paramValue as $pkey => $pValue ) { + if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 ))) + $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 )); + } + } + elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 ))) + $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 )); + if( 'VALUE' == strtoupper( $paramKey )) + $input['VALUE'] = strtoupper( $paramValue ); + else + $input[strtoupper( $paramKey )] = $paramValue; + } + if( is_array( $defaults )) { + foreach( $defaults as $paramKey => $paramValue ) { + if( !isset( $input[$paramKey] )) + $input[$paramKey] = $paramValue; + } + } + return (0 < count( $input )) ? $input : null; + } +/** + * set RRULE in vtimezone standard/daylight components based on component dtstart + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.3 - 2011-08-01 + * @param object $obj, reference to an iCalcreator vtimezone standard/daylight instance + * @return bool + */ + public static function _setTZrrule( & $obj ) { + if( FALSE === ( $date = $obj->getProperty( 'dtstart' ))) + return FALSE; + $ts = mktime( (int) $date['hour'], (int) $date['min'], (int) $date['sec'], (int) $date['month'], (int) $date['day'], (int) $date['year'] ); + $daysNm = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU' ); + $day = $daysNm[date( 'N', $ts )]; + $daycnt = date( 't', $ts ) - $date['day']; + if( 8 > $daycnt ) + $ordwk = -1; + elseif( 15 > $daycnt) + $ordwk = -2; + elseif( 8 > $date['day'] ) + $ordwk = 1; + else + $ordwk = 2; + $obj->setProperty( 'RRULE', array( 'FREQ' => 'YEARLY', 'BYDAY' => array( (string) $ordwk, 'DAY' => $day ), 'BYMONTH' => (int) $date['month'] )); + return TRUE; + } +/** + * step date, return updated date, array and timpstamp + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.16 - 2008-10-18 + * @param array $date, date to step + * @param int $timestamp + * @param array $step, default array( 'day' => 1 ) + * @return void + */ + public static function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) { + foreach( $step as $stepix => $stepvalue ) + $date[$stepix] += $stepvalue; + $timestamp = iCalUtilityFunctions::_date2timestamp( $date ); + $date = iCalUtilityFunctions::_timestamp2date( $timestamp, 6 ); + foreach( $date as $k => $v ) { + if( ctype_digit( $v )) + $date[$k] = (int) $v; + } + } +/** + * convert timestamp to date array + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.16 - 2008-11-01 + * @param mixed $timestamp + * @param int $parno + * @return array + */ + public static function _timestamp2date( $timestamp, $parno=6 ) { + if( is_array( $timestamp )) { + if(( 7 == $parno ) && !empty( $timestamp['tz'] )) + $tz = $timestamp['tz']; + $timestamp = $timestamp['timestamp']; + } + $output = array( 'year' => date( 'Y', $timestamp ) + , 'month' => date( 'm', $timestamp ) + , 'day' => date( 'd', $timestamp )); + if( 3 != $parno ) { + $output['hour'] = date( 'H', $timestamp ); + $output['min'] = date( 'i', $timestamp ); + $output['sec'] = date( 's', $timestamp ); + if( isset( $tz )) + $output['tz'] = $tz; + } + return $output; + } +/** + * convert timestamp to duration in array format + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.23 - 2010-10-23 + * @param int $timestamp + * @return array, duration format + */ + public static function _timestamp2duration( $timestamp ) { + $dur = array(); + $dur['week'] = (int) floor( $timestamp / ( 7 * 24 * 60 * 60 )); + $timestamp = $timestamp % ( 7 * 24 * 60 * 60 ); + $dur['day'] = (int) floor( $timestamp / ( 24 * 60 * 60 )); + $timestamp = $timestamp % ( 24 * 60 * 60 ); + $dur['hour'] = (int) floor( $timestamp / ( 60 * 60 )); + $timestamp = $timestamp % ( 60 * 60 ); + $dur['min'] = (int) floor( $timestamp / ( 60 )); + $dur['sec'] = (int) $timestamp % ( 60 ); + return $dur; + } +/** + * transforms a dateTime from a timezone to another using PHP DateTime and DateTimeZone class (PHP >= PHP 5.2.0) + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.5 - 2011-08-03 + * @param string $date (date to alter) + * @param string $tzFrom, PHP valid old timezone + * @param string $tzTo, PHP valid new timezone, default 'UTC' + * @param string $format, (opt) date output format, default 'Ymd\THis' + * @return bool + */ + public static function transformDateTime( & $date, $tzFrom, $tzTo='UTC', $format = 'Ymd\THis' ) { + if( !class_exists( 'DateTime' ) || !class_exists( 'DateTimeZone' )) + return FALSE; + if( FALSE === ( $timestamp = strtotime( $date ))) + return FALSE; + try { + $d = new DateTime( date( 'Y-m-d H:i:s', $timestamp ), new DateTimeZone( $tzFrom )); + $d->setTimezone( new DateTimeZone( $tzTo )); + } + catch (Exception $e) { + return FALSE; + } + $date = $d->format( $format ); + return TRUE; + } +/** + * convert (numeric) local time offset to seconds correcting localtime to GMT + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.16 - 2008-10-19 + * @param string $offset + * @return integer + */ + public static function _tz2offset( $tz ) { + $tz = trim( (string) $tz ); + $offset = 0; + if((( 5 != strlen( $tz )) && ( 7 != strlen( $tz ))) || + (( '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) || + (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) || + (( 7 == strlen( $tz )) && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 )))) + return $offset; + $hours2sec = (int) substr( $tz, 1, 2 ) * 3600; + $min2sec = (int) substr( $tz, 3, 2 ) * 60; + $sec = ( 7 == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00'; + $offset = $hours2sec + $min2sec + $sec; + $offset = ('-' == substr( $tz, 0, 1 )) ? $offset : -1 * $offset; + return $offset; + } +} +?> \ No newline at end of file diff --git a/classes/vcalendar.php b/lib/iCalcreator/iCalcreator.class.php old mode 100644 new mode 100755 similarity index 62% rename from classes/vcalendar.php rename to lib/iCalcreator/iCalcreator.class.php index de58a49..d7bf363 --- a/classes/vcalendar.php +++ b/lib/iCalcreator/iCalcreator.class.php @@ -1,9 +1,9 @@ = '5' ) // && ( 'UTC' == date_default_timezone_get() )) { +/*********************************************************************************/ +/* only for phpversion 5.1 and later, */ +/* date management, default timezone setting */ +/* since 2.6.36 - 2010-12-31 */ +if( substr( phpversion(), 0, 3 ) >= '5.1' ) + // && ( 'UTC' == date_default_timezone_get())) date_default_timezone_set( 'Europe/Stockholm' ); - /* version string, do NOT remove!! */ -define( 'ICALCREATOR_VERSION', 'iCalcreator 2.6' ); +/*********************************************************************************/ +/* since 2.6.22 - 2010-09-25, do NOT remove!! */ +require_once 'iCalUtilityFunctions.class.php'; +/*********************************************************************************/ +/* version, do NOT remove!! */ +define( 'ICALCREATOR_VERSION', 'iCalcreator 2.10.15' ); /*********************************************************************************/ /*********************************************************************************/ /** * vcalendar class * - * @author Kjell-Inge Gustafsson - * @since 2.2.13 - 2007-12-30 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-14 */ class vcalendar { // calendar property variables @@ -75,37 +83,43 @@ class vcalendar { var $delimiter; var $nl; var $format; + var $dtzid; // component internal variables var $attributeDelimiter; var $valueInit; // component xCal declaration container var $xcaldecl; -/* +/** * constructor for calendar object * - * @author Kjell-Inge Gustafsson - * @since 2.2.13 - 2007-12-30 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-14 + * @param array $config * @return void */ - function vcalendar () { + function vcalendar ( $config = array()) { $this->_makeVersion(); $this->calscale = null; $this->method = null; $this->_makeUnique_id(); $this->prodid = null; $this->xprop = array(); -/** - * language = - */ - if( defined( 'ICAL_LANG' )) - $this->setConfig( 'language', ICAL_LANG ); - $this->setConfig( 'allowEmpty', TRUE ); - $this->setConfig( 'nl', "\n" ); - $this->setConfig( 'format', 'iCal'); + $this->language = null; $this->directory = null; $this->filename = null; $this->url = null; - $this->setConfig( 'delimiter', DIRECTORY_SEPARATOR ); + $this->dtzid = null; +/** + * language = + */ + if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) + $config['language'] = ICAL_LANG; + if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; + if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; + if( !isset( $config['format'] )) $config['format'] = 'iCal'; + if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; + $this->setConfig( $config ); + $this->xcaldecl = array(); $this->components = array(); } @@ -116,7 +130,7 @@ function vcalendar () { /** * creates formatted output for calendar property calscale * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -134,7 +148,7 @@ function createCalscale() { /** * set calendar property calscale * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @param string $value * @return void @@ -150,7 +164,7 @@ function setCalscale( $value ) { /** * creates formatted output for calendar property method * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 0.9.7 - 2006-11-20 * @return string */ @@ -168,7 +182,7 @@ function createMethod() { /** * set calendar property method * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-20-23 * @param string $value * @return bool @@ -189,7 +203,7 @@ function setMethod( $value ) { /** * creates formatted output for calendar property prodid * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 0.9.7 - 2006-11-20 * @return string */ @@ -208,12 +222,12 @@ function createProdid() { /** * make default value for calendar prodid * - * @author Kjell-Inge Gustafsson - * @since 0.3.0 - 2006-08-10 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.8 - 2009-12-30 * @return void */ function _makeProdid() { - $this->prodid = '-//'.$this->unique_id.'//NONSGML '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language ); + $this->prodid = '-//'.$this->unique_id.'//NONSGML kigkonsult.se '.ICALCREATOR_VERSION.'//'.strtoupper( $this->language ); } /** * Conformance: The property MUST be specified once in an iCalendar object. @@ -224,7 +238,7 @@ function _makeProdid() { /** * make default unique_id for calendar prodid * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 0.3.0 - 2006-08-10 * @return void */ @@ -241,7 +255,7 @@ function _makeUnique_id() { * creates formatted output for calendar property version * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 0.9.7 - 2006-11-20 * @return string */ @@ -260,7 +274,7 @@ function createVersion() { /** * set default calendar version * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 0.3.0 - 2006-08-10 * @return void */ @@ -270,7 +284,7 @@ function _makeVersion() { /** * set calendar version * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-23 * @param string $value * @return void @@ -287,22 +301,19 @@ function setVersion( $value ) { /** * creates formatted output for calendar property x-prop, iCal format only * - * @author Kjell-Inge Gustafsson - * @since 2.4.11 - 2008-11-03 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @return string */ function createXprop() { if( 'xcal' == $this->format ) return false; - if( 0 >= count( $this->xprop )) - return; + if( empty( $this->xprop ) || !is_array( $this->xprop )) return FALSE; $output = null; $toolbox = new calendarComponent(); - $toolbox->setConfig( 'language', $this->getConfig( 'language' )); - $toolbox->setConfig( 'nl', $this->getConfig( 'nl' )); - $toolbox->_createFormat( $this->getConfig( 'format' )); + $toolbox->setConfig( $this->getConfig()); foreach( $this->xprop as $label => $xpropPart ) { - if( empty( $xpropPart['value'] )) { + if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) { $output .= $toolbox->_createElement( $label ); continue; } @@ -321,19 +332,18 @@ function createXprop() { /** * set calendar property x-prop * - * @author Kjell-Inge Gustafsson - * @since 2.4.11 - 2008-11-04 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @param string $label * @param string $value * @param array $params optional * @return bool */ function setXprop( $label, $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; if( empty( $label )) return FALSE; + if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; $xprop = array( 'value' => $value ); - $toolbox = new calendarComponent(); - $xprop['params'] = $toolbox->_setParams( $params ); + $xprop['params'] = iCalUtilityFunctions::_setParams( $params ); if( !is_array( $this->xprop )) $this->xprop = array(); $this->xprop[strtoupper( $label )] = $xprop; return TRUE; @@ -342,16 +352,16 @@ function setXprop( $label, $value, $params=FALSE ) { /** * delete calendar property value * - * @author Kjell-Inge Gustafsson - * @since 2.4.5 - 2008-11-14 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-03-15 * @param mixed $propName, bool FALSE => X-property - * @param int @propix, optional, if specific property is wanted in case of multiply occurences + * @param int $propix, optional, if specific property is wanted in case of multiply occurences * @return bool, if successfull delete */ - function deleteProperty( $propName, $propix=FALSE ) { + function deleteProperty( $propName=FALSE, $propix=FALSE ) { $propName = ( $propName ) ? strtoupper( $propName ) : 'X-PROP'; if( !$propix ) - $propix = ( isset( $this->propdelix[$propName] )) ? $this->propdelix[$propName] + 2 : 1; + $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1; $this->propdelix[$propName] = --$propix; $return = FALSE; switch( $propName ) { @@ -370,7 +380,7 @@ function deleteProperty( $propName, $propix=FALSE ) { default: $reduced = array(); if( $propName != 'X-PROP' ) { - if( !isset( $this->xprop[$propName] )) return FALSE; + if( !isset( $this->xprop[$propName] )) { unset( $this->propdelix[$propName] ); return FALSE; } foreach( $this->xprop as $k => $a ) { if(( $k != $propName ) && !empty( $a )) $reduced[$k] = $a; @@ -386,6 +396,10 @@ function deleteProperty( $propName, $propix=FALSE ) { } } $this->xprop = $reduced; + if( empty( $this->xprop )) { + unset( $this->propdelix[$propName] ); + return FALSE; + } return TRUE; } return $return; @@ -393,10 +407,10 @@ function deleteProperty( $propName, $propix=FALSE ) { /** * get calendar property value/params * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-11-02 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-04-16 * @param string $propName, optional - * @param int @propix, optional, if specific property is wanted in case of multiply occurences + * @param int $propix, optional, if specific property is wanted in case of multiply occurences * @param bool $inclParam=FALSE * @return mixed */ @@ -408,11 +422,66 @@ function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) { $this->propix[$propName] = --$propix; } switch( $propName ) { + case 'ATTENDEE': + case 'CATEGORIES': + case 'DTSTART': + case 'LOCATION': + case 'ORGANIZER': + case 'PRIORITY': + case 'RESOURCES': + case 'STATUS': + case 'SUMMARY': + case 'RECURRENCE-ID-UID': + case 'R-UID': + case 'UID': + $output = array(); + foreach ( $this->components as $cix => $component) { + if( !in_array( $component->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' ))) + continue; + if(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { + $component->_getProperties( $propName, $output ); + continue; + } + elseif(( 3 < strlen( $propName )) && ( 'UID' == substr( $propName, -3 ))) { + if( FALSE !== ( $content = $component->getProperty( 'RECURRENCE-ID' ))) + $content = $component->getProperty( 'UID' ); + } + elseif( FALSE === ( $content = $component->getProperty( $propName ))) + continue; + if( FALSE === $content ) + continue; + elseif( is_array( $content )) { + if( isset( $content['year'] )) { + $key = sprintf( '%04d%02d%02d', $content['year'], $content['month'], $content['day'] ); + if( !isset( $output[$key] )) + $output[$key] = 1; + else + $output[$key] += 1; + } + else { + foreach( $content as $partValue => $partCount ) { + if( !isset( $output[$partValue] )) + $output[$partValue] = $partCount; + else + $output[$partValue] += $partCount; + } + } + } // end elseif( is_array( $content )) { + elseif( !isset( $output[$content] )) + $output[$content] = 1; + else + $output[$content] += 1; + } // end foreach ( $this->components as $cix => $component) + if( !empty( $output )) + ksort( $output ); + return $output; + break; + case 'CALSCALE': - return ( !empty( $this->calscale )) ? $this->calscale : null; + return ( !empty( $this->calscale )) ? $this->calscale : FALSE; break; case 'METHOD': - return ( !empty( $this->method )) ? $this->method : null; + return ( !empty( $this->method )) ? $this->method : FALSE; break; case 'PRODID': if( empty( $this->prodid )) @@ -420,7 +489,7 @@ function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) { return $this->prodid; break; case 'VERSION': - return ( !empty( $this->version )) ? $this->version : null; + return ( !empty( $this->version )) ? $this->version : FALSE; break; default: if( $propName != 'X-PROP' ) { @@ -438,6 +507,7 @@ function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) { else $xpropno++; } + unset( $this->propix[$propName] ); return FALSE; // not found ?? } } @@ -446,7 +516,7 @@ function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE ) { /** * general vcalendar property setting * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.2.13 - 2007-11-04 * @param mixed $args variable number of function arguments, * first argument is ALWAYS component name, @@ -477,12 +547,30 @@ function setProperty () { /** * get vcalendar config values or * calendar components * - * @author Kjell-Inge Gustafsson - * @since 2.4.10 - 2008-10-23 - * @param string $config + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-14 + * @param mixed $config * @return value */ - function getConfig( $config ) { + function getConfig( $config = FALSE ) { + if( !$config ) { + $return = array(); + $return['ALLOWEMPTY'] = $this->getConfig( 'ALLOWEMPTY' ); + $return['DELIMITER'] = $this->getConfig( 'DELIMITER' ); + $return['DIRECTORY'] = $this->getConfig( 'DIRECTORY' ); + $return['FILENAME'] = $this->getConfig( 'FILENAME' ); + $return['DIRFILE'] = $this->getConfig( 'DIRFILE' ); + $return['FILESIZE'] = $this->getConfig( 'FILESIZE' ); + $return['FORMAT'] = $this->getConfig( 'FORMAT' ); + if( FALSE !== ( $lang = $this->getConfig( 'LANGUAGE' ))) + $return['LANGUAGE'] = $lang; + $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' ); + $return['UNIQUE_ID'] = $this->getConfig( 'UNIQUE_ID' ); + if( FALSE !== ( $url = $this->getConfig( 'URL' ))) + $return['URL'] = $url; + $return['TZID'] = $this->getConfig( 'TZID' ); + return $return; + } switch( strtoupper( $config )) { case 'ALLOWEMPTY': return $this->allowEmpty; @@ -492,13 +580,11 @@ function getConfig( $config ) { $info = array(); foreach( $this->components as $cix => $component ) { if( empty( $component )) continue; - unset( $component->propix ); $info[$cix]['ordno'] = $cix + 1; $info[$cix]['type'] = $component->objName; $info[$cix]['uid'] = $component->getProperty( 'uid' ); $info[$cix]['props'] = $component->getConfig( 'propinfo' ); $info[$cix]['sub'] = $component->getConfig( 'compsinfo' ); - unset( $component->propix ); } return $info; break; @@ -531,14 +617,14 @@ function getConfig( $config ) { $size = 0; if( empty( $this->url )) { $dirfile = $this->getConfig( 'dirfile' ); - if( FALSE === ( $size = filesize( $dirfile ))) + if( !is_file( $dirfile ) || ( FALSE === ( $size = filesize( $dirfile )))) $size = 0; clearstatcache(); } return $size; break; case 'FORMAT': - return $this->format; + return ( $this->format == 'xcal' ) ? 'xCal' : 'iCal'; break; case 'LANGUAGE': /* get language for calendar component as defined in [RFC 1766] */ @@ -548,6 +634,9 @@ function getConfig( $config ) { case 'NEWLINECHAR': return $this->nl; break; + case 'TZID': + return $this->dtzid; + break; case 'UNIQUE_ID': return $this->unique_id; break; @@ -562,13 +651,29 @@ function getConfig( $config ) { /** * general vcalendar config setting * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-24 - * @param string $config + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.10 - 2011-09-19 + * @param mixed $config * @param string $value * @return void */ - function setConfig( $config, $value ) { + function setConfig( $config, $value = FALSE) { + if( is_array( $config )) { + $ak = array_keys( $config ); + foreach( $ak as $k ) { + if( 'DIRECTORY' == strtoupper( $k )) { + if( FALSE === $this->setConfig( 'DIRECTORY', $config[$k] )) + return FALSE; + unset( $config[$k] ); + break; + } + } + foreach( $config as $cKey => $cValue ) { + if( FALSE === $this->setConfig( $cKey, $cValue )) + return FALSE; + } + return TRUE; + } $res = FALSE; switch( strtoupper( $config )) { case 'ALLOWEMPTY': @@ -582,9 +687,9 @@ function setConfig( $config, $value ) { break; case 'DIRECTORY': $value = trim( $value ); - $nl = $this->getConfig('delimiter'); - if( $nl == substr( $value, ( 0 - strlen( $nl )))) - $value = substr( $value, 0, ( strlen( $value ) - strlen( $nl ))); + $del = $this->getConfig('delimiter'); + if( $del == substr( $value, ( 0 - strlen( $del )))) + $value = substr( $value, 0, ( strlen( $value ) - strlen( $del ))); if( is_dir( $value )) { /* local directory */ clearstatcache(); @@ -598,13 +703,13 @@ function setConfig( $config, $value ) { case 'FILENAME': $value = trim( $value ); if( !empty( $this->url )) { - /* remote directory+file - URL */ + /* remote directory+file -> URL */ $this->filename = $value; return TRUE; } $dirfile = $this->getConfig( 'directory' ).$this->getConfig( 'delimiter' ).$value; if( file_exists( $dirfile )) { - /* local existing file */ + /* local file exists */ if( is_readable( $dirfile ) || is_writable( $dirfile )) { clearstatcache(); $this->filename = $value; @@ -613,8 +718,8 @@ function setConfig( $config, $value ) { else return FALSE; } - elseif( FALSE !== touch( $dirfile )) { - /* new local file created */ + elseif( is_readable($this->getConfig( 'directory' ) ) || is_writable( $this->getConfig( 'directory' ) )) { + /* read- or writable directory */ $this->filename = $value; return TRUE; } @@ -622,8 +727,8 @@ function setConfig( $config, $value ) { return FALSE; break; case 'FORMAT': - $value = trim( $value ); - if( 'xcal' == strtolower( $value )) { + $value = trim( strtolower( $value )); + if( 'xcal' == $value ) { $this->format = 'xcal'; $this->attributeDelimiter = $this->nl; $this->valueInit = null; @@ -649,6 +754,11 @@ function setConfig( $config, $value ) { $subcfg = array( 'NL' => $value ); $res = TRUE; break; + case 'TZID': + $this->dtzid = $value; + $subcfg = array( 'TZID' => $value ); + $res = TRUE; + break; case 'UNIQUE_ID': $value = trim( $value ); $this->unique_id = $value; @@ -666,12 +776,14 @@ function setConfig( $config, $value ) { $parts = pathinfo( $value ); return $this->setConfig( 'filename', $parts['basename'] ); break; + default: // any unvalid config key.. . + return TRUE; } if( !$res ) return FALSE; if( isset( $subcfg ) && !empty( $this->components )) { foreach( $subcfg as $cfgkey => $cfgvalue ) { foreach( $this->components as $cix => $component ) { - $res = $component->setConfig( $cfgkey, $cfgvalue ); + $res = $component->setConfig( $cfgkey, $cfgvalue, TRUE ); if( !$res ) break 2; $this->components[$cix] = $component->copy(); // PHP4 compliant @@ -686,7 +798,7 @@ function setConfig( $config, $value ) { * * alias to setComponent * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 1.x.x - 2007-04-24 * @param object $component calendar component * @return void @@ -697,8 +809,8 @@ function addComponent( $component ) { /** * delete calendar component from container * - * @author Kjell-Inge Gustafsson - * @since 2.4.10 - 2008-08-05 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-03-15 * @param mixed $arg1 ordno / component type / component uid * @param mixed $arg2 optional, ordno if arg1 = component type * @return void @@ -716,7 +828,6 @@ function deleteComponent( $arg1, $arg2=FALSE ) { $cix1dC = 0; foreach ( $this->components as $cix => $component) { if( empty( $component )) continue; - unset( $component->propix ); if(( 'INDEX' == $argType ) && ( $index == $cix )) { unset( $this->components[$cix] ); return TRUE; @@ -738,84 +849,190 @@ function deleteComponent( $arg1, $arg2=FALSE ) { /** * get calendar component from container * - * @author Kjell-Inge Gustafsson - * @since 2.4.10 - 2008-08-06 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.1 - 2011-04-16 * @param mixed $arg1 optional, ordno/component type/ component uid * @param mixed $arg2 optional, ordno if arg1 = component type * @return object */ function getComponent( $arg1=FALSE, $arg2=FALSE ) { $index = $argType = null; - if ( !$arg1 ) { + if ( !$arg1 ) { // first or next in component chain $argType = 'INDEX'; - $index = $this->compix['INDEX'] = - ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1; + $index = $this->compix['INDEX'] = ( isset( $this->compix['INDEX'] )) ? $this->compix['INDEX'] + 1 : 1; } - elseif ( ctype_digit( (string) $arg1 )) { + elseif ( ctype_digit( (string) $arg1 )) { // specific component in chain $argType = 'INDEX'; $index = (int) $arg1; unset( $this->compix ); } - elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { + elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] ) + $arg2 = implode( '-', array_keys( $arg1 )); + $index = $this->compix[$arg2] = ( isset( $this->compix[$arg2] )) ? $this->compix[$arg2] + 1 : 1; + $dateProps = array( 'DTSTART', 'DTEND', 'DUE', 'CREATED', 'COMPLETED', 'DTSTAMP', 'LAST-MODIFIED', 'RECURRENCE-ID' ); + $otherProps = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'PRIORITY', 'RESOURCES', 'STATUS', 'SUMMARY', 'UID' ); + } + elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { // object class name unset( $this->compix['INDEX'] ); $argType = strtolower( $arg1 ); if( !$arg2 ) - $index = $this->compix[$argType] = - ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1; - else + $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1; + elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 )) $index = (int) $arg2; } - $index -= 1; - $ckeys = array_keys( $this->components ); + elseif(( strlen( $arg1 ) > strlen( 'vfreebusy' )) && ( FALSE !== strpos( $arg1, '@' ))) { // UID as 1st argument + if( !$arg2 ) + $index = $this->compix[$arg1] = ( isset( $this->compix[$arg1] )) ? $this->compix[$arg1] + 1 : 1; + elseif( isset( $arg2 ) && ctype_digit( (string) $arg2 )) + $index = (int) $arg2; + } + if( isset( $index )) + $index -= 1; + $ckeys = array_keys( $this->components ); if( !empty( $index) && ( $index > end( $ckeys ))) return FALSE; $cix1gC = 0; foreach ( $this->components as $cix => $component) { if( empty( $component )) continue; - unset( $component->propix ); if(( 'INDEX' == $argType ) && ( $index == $cix )) return $component->copy(); elseif( $argType == $component->objName ) { - if( $index == $cix1gC ) - return $component->copy(); - $cix1gC++; + if( $index == $cix1gC ) + return $component->copy(); + $cix1gC++; } - elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { - unset( $component->propix ); - return $component->copy(); + elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] ) + $hit = FALSE; + foreach( $arg1 as $pName => $pValue ) { + $pName = strtoupper( $pName ); + if( !in_array( $pName, $dateProps ) && !in_array( $pName, $otherProps )) + continue; + if(( 'ATTENDEE' == $pName ) || ( 'CATEGORIES' == $pName ) || ( 'RESOURCES' == $pName )) { // multiple ocurrence may occur + $propValues = array(); + $component->_getProperties( $pName, $propValues ); + $propValues = array_keys( $propValues ); + $hit = ( in_array( $pValue, $propValues )) ? TRUE : FALSE; + continue; + } // end if(( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { // multiple ocurrence may occur + if( FALSE === ( $value = $component->getProperty( $pName ))) { // single ocurrency + $hit = FALSE; // missing property + continue; + } + if( 'SUMMARY' == $pName ) { // exists within (any case) + $hit = ( FALSE !== stripos( $d, $pValue )) ? TRUE : FALSE; + continue; + } + if( in_array( strtoupper( $pName ), $dateProps )) { + $valuedate = sprintf( '%04d%02d%02d', $value['year'], $value['month'], $value['day'] ); + if( 8 < strlen( $pValue )) { + if( isset( $value['hour'] )) { + if( 'T' == substr( $pValue, 8, 1 )) + $pValue = str_replace( 'T', '', $pValue ); + $valuedate .= sprintf( '%02d%02d%02d', $value['hour'], $value['min'], $value['sec'] ); + } + else + $pValue = substr( $pValue, 0, 8 ); + } + $hit = ( $pValue == $valuedate ) ? TRUE : FALSE; + continue; + } + elseif( !is_array( $value )) + $value = array( $value ); + foreach( $value as $part ) { + $part = ( FALSE !== strpos( $part, ',' )) ? explode( ',', $part ) : array( $part ); + foreach( $part as $subPart ) { + if( $pValue == $subPart ) { + $hit = TRUE; + continue 2; + } + } + } + $hit = FALSE; // no hit in property + } // end foreach( $arg1 as $pName => $pValue ) + if( $hit ) { + if( $index == $cix1gC ) + return $component->copy(); + $cix1gC++; + } + } // end elseif( is_array( $arg1 )) { // array( *[propertyName => propertyValue] ) + elseif( !$argType && ($arg1 == $component->getProperty( 'uid' ))) { // UID + if( $index == $cix1gC ) + return $component->copy(); + $cix1gC++; } - } + } // end foreach ( $this->components.. . /* not found.. . */ unset( $this->compix ); return FALSE; } /** - * select components from calendar on date basis + * create new calendar component, already included within calendar + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.33 - 2011-01-03 + * @param string $compType component type + * @return object (reference) + */ + function & newComponent( $compType ) { + $config = $this->getConfig(); + $keys = array_keys( $this->components ); + $ix = end( $keys) + 1; + switch( strtoupper( $compType )) { + case 'EVENT': + case 'VEVENT': + $this->components[$ix] = new vevent( $config ); + break; + case 'TODO': + case 'VTODO': + $this->components[$ix] = new vtodo( $config ); + break; + case 'JOURNAL': + case 'VJOURNAL': + $this->components[$ix] = new vjournal( $config ); + break; + case 'FREEBUSY': + case 'VFREEBUSY': + $this->components[$ix] = new vfreebusy( $config ); + break; + case 'TIMEZONE': + case 'VTIMEZONE': + array_unshift( $this->components, new vtimezone( $config )); + $ix = 0; + break; + default: + return FALSE; + } + return $this->components[$ix]; + } +/** + * select components from calendar on date or selectOption basis * * Ensure DTSTART is set for every component. * No date controls occurs. * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-18 - * @param int $startY optional, start Year, default current Year - * @param int $startM optional, start Month, default current Month - * @param int $startD optional, start Day, default current Day - * @param int $endY optional, end Year, default $startY - * @param int $endY optional, end Month, default $startM - * @param int $endY optional, end Day, default $startD - * @param mixed $cType optional, calendar component type(-s), default FALSE=all else string/array type(-s) - * @param bool $flat optional, FALSE (default) => output : array[Year][Month][Day][] - * TRUE => output : array[] (ignores split) - * @param bool $any optional, TRUE (default) - select component that take place within period - * FALSE - only components that starts within period - * @param bool $split optional, TRUE (default) - one component copy every day it take place during the + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.13 - 2011-09-23 + * @param mixed $startY optional, start Year, default current Year ALT. array selecOptions + * @param int $startM optional, start Month, default current Month + * @param int $startD optional, start Day, default current Day + * @param int $endY optional, end Year, default $startY + * @param int $endY optional, end Month, default $startM + * @param int $endY optional, end Day, default $startD + * @param mixed $cType optional, calendar component type(-s), default FALSE=all else string/array type(-s) + * @param bool $flat optional, FALSE (default) => output : array[Year][Month][Day][] + * TRUE => output : array[] (ignores split) + * @param bool $any optional, TRUE (default) - select component that take place within period + * FALSE - only components that starts within period + * @param bool $split optional, TRUE (default) - one component copy every day it take place during the * period (implies flat=FALSE) - * FALSE - one occurance of component only in output array + * FALSE - one occurance of component only in output array * @return array or FALSE */ function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FALSE, $endM=FALSE, $endD=FALSE, $cType=FALSE, $flat=FALSE, $any=TRUE, $split=TRUE ) { /* check if empty calendar */ if( 0 >= count( $this->components )) return FALSE; + if( is_array( $startY )) + return $this->selectComponents2( $startY ); /* check default dates */ if( !$startY ) $startY = date( 'Y' ); if( !$startM ) $startM = date( 'm' ); @@ -825,6 +1042,7 @@ function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FA if( !$endM ) $endM = $startM; if( !$endD ) $endD = $startD; $endDate = mktime( 23, 59, 59, $endM, $endD, $endY ); +//echo 'selectComp arg='.date( 'Y-m-d H:i:s', $startDate).' -- '.date( 'Y-m-d H:i:s', $endDate)."
    \n"; $tcnt = 0;// test ### /* check component types */ $validTypes = array('vevent', 'vtodo', 'vjournal', 'vfreebusy' ); if( is_array( $cType )) { @@ -846,18 +1064,22 @@ function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FA $cType = $validTypes; if( 0 >= count( $cType )) $cType = $validTypes; + if(( TRUE === $flat ) && ( TRUE === $split )) // invalid combination + $split = FALSE; /* iterate components */ $result = array(); foreach ( $this->components as $cix => $component ) { if( empty( $component )) continue; - unset( $component->propix, $start ); + unset( $start ); /* deselect unvalid type components */ if( !in_array( $component->objName, $cType )) continue; - /* deselect components without dtstart set */ - if( FALSE === ( $start = $component->getProperty( 'dtstart' ))) continue; + $start = $component->getProperty( 'dtstart' ); + /* select due when dtstart is missing */ + if( empty( $start ) && ( $component->objName == 'vtodo' ) && ( FALSE === ( $start = $component->getProperty( 'due' )))) + continue; $dtendExist = $dueExist = $durationExist = $endAllDayEvent = FALSE; - unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $workstart, $workend ); // clean up - $startWdate = $component->_date2timestamp( $start ); + unset( $end, $startWdate, $endWdate, $rdurWsecs, $rdur, $exdatelist, $workstart, $workend, $endDateFormat ); // clean up + $startWdate = iCalUtilityFunctions::_date2timestamp( $start ); $startDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d'; /* get end date from dtend/due/duration properties */ $end = $component->getProperty( 'dtend' ); @@ -865,14 +1087,12 @@ function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FA $dtendExist = TRUE; $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d'; } - // if( !empty($end)) echo 'selectComp 1 start='.implode('-',$start).' end='.implode('-',$end)."
    \n"; // test ### - if( empty($end) && ( $component->objName == 'vtodo' )) { + if( empty( $end ) && ( $component->objName == 'vtodo' )) { $end = $component->getProperty( 'due' ); if( !empty( $end )) { $dueExist = TRUE; $endDateFormat = ( isset( $end['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d'; } - // if( !empty($end)) echo 'selectComp 2 start='.implode('-',$start).' end='.implode('-',$end)."
    \n"; // test ### } if( !empty( $end ) && !isset( $end['hour'] )) { /* a DTEND without time part regards an event that ends the day before, @@ -884,543 +1104,549 @@ function selectComponents( $startY=FALSE, $startM=FALSE, $startD=FALSE, $endY=FA $end['day'] = date( 'd', $endWdate ); $end['hour'] = 23; $end['min'] = $end['sec'] = 59; - // if( !empty($end)) echo 'selectComp 3 start='.implode('-',$start).' end='.implode('-',$end)."
    \n"; // test ### } if( empty( $end )) { $end = $component->getProperty( 'duration', FALSE, FALSE, TRUE );// in dtend (array) format if( !empty( $end )) $durationExist = TRUE; - // if( !empty($end)) echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."
    \n"; // test ### + $endDateFormat = ( isset( $start['hour'] )) ? 'Y-m-d H:i:s' : 'Y-m-d'; +// if( !empty($end)) echo 'selectComp 4 start='.implode('-',$start).' end='.implode('-',$end)."
    \n"; // test ### } if( empty( $end )) { // assume one day duration if missing end date $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 ); - // if( isset($end)) echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."
    \n"; // test ### } - $endWdate = $component->_date2timestamp( $end ); +// if( isset($end)) echo 'selectComp 5 start='.implode('-',$start).' end='.implode('-',$end)."
    \n"; // test ### + $endWdate = iCalUtilityFunctions::_date2timestamp( $end ); if( $endWdate < $startWdate ) { // MUST be after start date!! $end = array( 'year' => $start['year'], 'month' => $start['month'], 'day' => $start['day'], 'hour' => 23, 'min' => 59, 'sec' => 59 ); - $endWdate = $component->_date2timestamp( $end ); + $endWdate = iCalUtilityFunctions::_date2timestamp( $end ); } - $rdurWsecs = $endWdate - $startWdate; // compute component duration in seconds - $rdur = $component->_date2duration( $start, $end ); // compute component duration, array + $rdurWsecs = $endWdate - $startWdate; // compute event (component) duration in seconds /* make a list of optional exclude dates for component occurence from exrule and exdate */ $exdatelist = array(); - $workstart = $component->_timestamp2date(( $startDate - $rdurWsecs ), 6); - $workend = $component->_timestamp2date(( $endDate + $rdurWsecs ), 6); + $workstart = iCalUtilityFunctions::_timestamp2date(( $startDate - $rdurWsecs ), 6); + $workend = iCalUtilityFunctions::_timestamp2date(( $endDate + $rdurWsecs ), 6); while( FALSE !== ( $exrule = $component->getProperty( 'exrule' ))) // check exrule - $component->_recur2date( $exdatelist, $exrule, $start, $workstart, $workend ); + iCalUtilityFunctions::_recur2date( $exdatelist, $exrule, $start, $workstart, $workend ); while( FALSE !== ( $exdate = $component->getProperty( 'exdate' ))) { // check exdate foreach( $exdate as $theExdate ) { - $exWdate = $component->_date2timestamp( $theExdate ); + $exWdate = iCalUtilityFunctions::_date2timestamp( $theExdate ); + $exWdate = mktime( 0, 0, 0, date( 'm', $exWdate ), date( 'd', $exWdate ), date( 'Y', $exWdate ) ); // on a day-basis !!! if((( $startDate - $rdurWsecs ) <= $exWdate ) && ( $endDate >= $exWdate )) $exdatelist[$exWdate] = TRUE; - } - } - /* if 'any' components, check repeating components, removing all excluding dates */ + } // end - foreach( $exdate as $theExdate ) + } // end - check exdate + /* select only components with startdate within period */ + if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) { + /* add the selected component (WITHIN valid dates) to output array */ + if( $flat ) + $result[$component->getProperty( 'UID' )] = $component->copy(); // copy original to output; + elseif( $split ) { // split the original component + if( $endWdate > $endDate ) + $endWdate = $endDate; // use period end date + $rstart = $startWdate; + if( $rstart < $startDate ) + $rstart = $startDate; // use period start date + $startYMD = date( 'Ymd', $rstart ); + $endYMD = date( 'Ymd', $endWdate ); + $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! + while( date( 'Ymd', $rstart ) <= $endYMD ) { // iterate + $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! + if( isset( $exdatelist[$checkDate] )) { // exclude any recurrence date, found in exdatelist + $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day + continue; + } + if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart + $datestring = date( $startDateFormat, mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ))); + else + $datestring = date( $startDateFormat, $rstart ); + if( isset( $start['tz'] )) + $datestring .= ' '.$start['tz']; +// echo "X-CURRENT-DTSTART 3 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."
    ";$component->setProperty( 'X-CNT', $tcnt ); // test ### + $component->setProperty( 'X-CURRENT-DTSTART', $datestring ); + if( $dtendExist || $dueExist || $durationExist ) { + if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day + $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )); + else + $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! + $datestring = date( $endDateFormat, $tend ); + if( isset( $end['tz'] )) + $datestring .= ' '.$end['tz']; + $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; + $component->setProperty( $propName, $datestring ); + } // end if( $dtendExist || $dueExist || $durationExist ) + $wd = getdate( $rstart ); + $result[$wd['year']][$wd['mon']][$wd['mday']][$component->getProperty( 'UID' )] = $component->copy(); // copy to output + $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day + } // end while( $rstart <= $endWdate ) + } // end if( $split ) - else use component date + } // end if(( $startWdate >= $startDate ) && ( $startWdate <= $endDate )) + /* if 'any' components, check components with reccurrence rules, removing all excluding dates */ if( TRUE === $any ) { /* make a list of optional repeating dates for component occurence, rrule, rdate */ $recurlist = array(); while( FALSE !== ( $rrule = $component->getProperty( 'rrule' ))) // check rrule - $component->_recur2date( $recurlist, $rrule, $start, $workstart, $workend ); + iCalUtilityFunctions::_recur2date( $recurlist, $rrule, $start, $workstart, $workend ); foreach( $recurlist as $recurkey => $recurvalue ) // key=match date as timestamp $recurlist[$recurkey] = $rdurWsecs; // add duration in seconds while( FALSE !== ( $rdate = $component->getProperty( 'rdate' ))) { // check rdate foreach( $rdate as $theRdate ) { if( is_array( $theRdate ) && ( 2 == count( $theRdate )) && // all days within PERIOD array_key_exists( '0', $theRdate ) && array_key_exists( '1', $theRdate )) { - $rstart = $component->_date2timestamp( $theRdate[0] ); + $rstart = iCalUtilityFunctions::_date2timestamp( $theRdate[0] ); if(( $rstart < ( $startDate - $rdurWsecs )) || ( $rstart > $endDate )) continue; if( isset( $theRdate[1]['year'] )) // date-date period - $rend = $component->_date2timestamp( $theRdate[1] ); + $rend = iCalUtilityFunctions::_date2timestamp( $theRdate[1] ); else { // date-duration period - $rend = $component->duration2date( $theRdate[0], $theRdate[1] ); - $rend = $component->_date2timestamp( $rend ); + $rend = iCalUtilityFunctions::_duration2date( $theRdate[0], $theRdate[1] ); + $rend = iCalUtilityFunctions::_date2timestamp( $rend ); + } + while( $rstart < $rend ) { + $recurlist[$rstart] = $rdurWsecs; // set start date for recurrence instance + rdate duration in seconds + $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day } - if((( $startDate - $rdurWsecs ) <= $rstart ) && ( $endDate >= $rstart )) - $recurlist[$rstart] = ( $rstart - $rend ); // set start date + rdate duration in seconds } // PERIOD end else { // single date - $theRdate = $component->_date2timestamp( $theRdate ); + $theRdate = iCalUtilityFunctions::_date2timestamp( $theRdate ); if((( $startDate - $rdurWsecs ) <= $theRdate ) && ( $endDate >= $theRdate )) - $recurlist[$theRdate] = $rdurWsecs; // set start date + event duration in seconds + $recurlist[$theRdate] = $rdurWsecs; // set start date for recurrence instance + event duration in seconds } } - } + } // end - check rdate if( 0 < count( $recurlist )) { ksort( $recurlist ); + $xRecurrence = 1; foreach( $recurlist as $recurkey => $durvalue ) { +// echo "recurKey=".date( 'Y-m-d H:i:s', $recurkey ).' dur='.iCalUtilityFunctions::offsetSec2His( $durvalue )."
    \n"; // test ###; if((( $startDate - $rdurWsecs ) > $recurkey ) || ( $endDate < $recurkey )) // not within period continue; - if( isset( $exdatelist[$recurkey] )) // check excluded dates + $checkDate = mktime( 0, 0, 0, date( 'm', $recurkey ), date( 'd', $recurkey ), date( 'Y', $recurkey ) ); // on a day-basis !!! + if( isset( $exdatelist[$checkDate] )) // check excluded dates continue; if( $startWdate >= $recurkey ) // exclude component start date continue; $component2 = $component->copy(); - $rstart = $component2->_timestamp2date( $recurkey, 6); - $datevalue = $rstart['month'].'/'.$rstart['day'].'/'.$rstart['year']; - if( isset( $start['hour'] ) || isset( $start['min'] ) || isset( $start['sec'] )) { - $datevalue .= ( isset( $rstart['hour'] )) ? ' '.$rstart['hour'] : ' 00'; - $datevalue .= ( isset( $rstart['min'] )) ? ':'.$rstart['min'] : ':00'; - $datevalue .= ( isset( $rstart['sec'] )) ? ':'.$rstart['sec'] : ':00'; - } - $datestring = date( $startDateFormat, strtotime( $datevalue )); - if( isset( $start['tz'] )) - $datestring .= ' '.$start['tz']; - $component2->setProperty( 'X-CURRENT-DTSTART', $datestring ); - $rend = $component2->_timestamp2date(( $recurkey + $durvalue ), 6); - if( $dtendExist || $dueExist ) { - if( $endAllDayEvent ) { - $rend2 = mktime( 0, 0, 0, $rend['month'], ($rend['day'] + 1), $rend['year'] ); - $datevalue = date( 'm', $rend2 ).'/'.date( 'd', $rend2 ).'/'.date( 'Y', $rend2 ); - } - else { - $datevalue = $rend['month'].'/'.$rend['day'].'/'.$rend['year']; - if( isset( $end['hour'] ) || isset( $end['min'] ) || isset( $end['sec'] )) { - $datevalue .= ( isset( $rend['hour'] )) ? ' '.$rend['hour'] : ' 00'; - $datevalue .= ( isset( $rend['min'] )) ? ':'.$rend['min'] : ':00'; - $datevalue .= ( isset( $rend['sec'] )) ? ':'.$rend['sec'] : ':00'; - } - } - $datestring = date( $endDateFormat, strtotime( $datevalue )); - if( isset( $end['tz'] )) - $datestring .= ' '.$end['tz']; - if( $dtendExist ) - $component2->setProperty( 'X-CURRENT-DTEND', $datestring ); - elseif( $dueExist ) - $component2->setProperty( 'X-CURRENT-DUE', $datestring ); - } - $rend = $component2->_date2timestamp( $rend ); $rstart = $recurkey; - /* add repeating components within valid dates to output array, only start date */ - if( $flat ) - $result[] = $component2->copy(); // copy to output + $rend = $recurkey + $durvalue; + /* add repeating components within valid dates to output array, only start date set */ + if( $flat ) { + $datestring = date( $startDateFormat, $recurkey ); + if( isset( $start['tz'] )) + $datestring .= ' '.$start['tz']; +// echo "X-CURRENT-DTSTART 0 =$datestring tcnt =".++$tcnt."
    ";$component2->setProperty( 'X-CNT', $tcnt ); // test ### + $component2->setProperty( 'X-CURRENT-DTSTART', $datestring ); + if( $dtendExist || $dueExist || $durationExist ) { + $datestring = date( $endDateFormat, $recurkey + $durvalue ); // fixa korrekt sluttid + if( isset( $end['tz'] )) + $datestring .= ' '.$end['tz']; + $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; + $component2->setProperty( $propName, $datestring ); + } // end if( $dtendExist || $dueExist || $durationExist ) + $component2->setProperty( 'X-RECURRENCE', ++$xRecurrence ); + $result[$component2->getProperty( 'UID' )] = $component2->copy(); // copy to output + } + /* add repeating components within valid dates to output array, one each day */ elseif( $split ) { if( $rend > $endDate ) $rend = $endDate; - while( $rstart <= $rend ) { // iterate + $startYMD = date( 'Ymd', $rstart ); + $endYMD = date( 'Ymd', $rend ); +// echo "splitStart=".date( 'Y-m-d H:i:s', $rstart ).' end='.date( 'Y-m-d H:i:s', $rend )."
    \n"; // test ###; + while( $rstart <= $rend ) { // iterate.. . + $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! + if( isset( $exdatelist[$checkDate] )) // exclude any recurrence START date, found in exdatelist + break; +// echo "checking date after startdate=".date( 'Y-m-d H:i:s', $rstart ).' mot '.date( 'Y-m-d H:i:s', $startDate )."
    "; // test ###; + if( $rstart >= $startDate ) { // date after dtstart + if( date( 'Ymd', $rstart ) > $startYMD ) // date after dtstart + $datestring = date( $startDateFormat, $checkDate ); + else + $datestring = date( $startDateFormat, $rstart ); + if( isset( $start['tz'] )) + $datestring .= ' '.$start['tz']; +//echo "X-CURRENT-DTSTART 1 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."
    ";$component2->setProperty( 'X-CNT', $tcnt ); // test ### + $component2->setProperty( 'X-CURRENT-DTSTART', $datestring ); + if( $dtendExist || $dueExist || $durationExist ) { + if( date( 'Ymd', $rstart ) < $endYMD ) // not the last day + $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )); + else + $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! + $datestring = date( $endDateFormat, $tend ); + if( isset( $end['tz'] )) + $datestring .= ' '.$end['tz']; + $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; + $component2->setProperty( $propName, $datestring ); + } // end if( $dtendExist || $dueExist || $durationExist ) + $component2->setProperty( 'X-RECURRENCE', $xRecurrence ); + $wd = getdate( $rstart ); + $result[$wd['year']][$wd['mon']][$wd['mday']][$component2->getProperty( 'UID' )] = $component2->copy(); // copy to output + } // end if( $checkDate > $startYMD ) { // date after dtstart + $rstart = mktime( date( 'H', $rstart ), date( 'i', $rstart ), date( 's', $rstart ), date( 'm', $rstart ), date( 'd', $rstart ) + 1, date( 'Y', $rstart ) ); // step one day + } // end while( $rstart <= $rend ) + $xRecurrence += 1; + } // end elseif( $split ) + elseif( $rstart >= $startDate ) { // date within period //* flat=FALSE && split=FALSE *// + $checkDate = mktime( 0, 0, 0, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! + if( !isset( $exdatelist[$checkDate] )) { // exclude any recurrence START date, found in exdatelist + $xRecurrence += 1; + $datestring = date( $startDateFormat, $rstart ); + if( isset( $start['tz'] )) + $datestring .= ' '.$start['tz']; +//echo "X-CURRENT-DTSTART 2 = $datestring xRecurrence=$xRecurrence tcnt =".++$tcnt."
    ";$component2->setProperty( 'X-CNT', $tcnt ); // test ### + $component2->setProperty( 'X-CURRENT-DTSTART', $datestring ); + if( $dtendExist || $dueExist || $durationExist ) { + $rstart += $rdurWsecs; + if( date( 'Ymd', $rstart ) < date( 'Ymd', $endWdate )) + $tend = mktime( 23, 59, 59, date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart )); + else + $tend = mktime( date( 'H', $endWdate ), date( 'i', $endWdate ), date( 's', $endWdate ), date( 'm', $rstart ), date( 'd', $rstart ), date( 'Y', $rstart ) ); // on a day-basis !!! + $datestring = date( $endDateFormat, $tend ); + if( isset( $end['tz'] )) + $datestring .= ' '.$end['tz']; + $propName = ( !$dueExist ) ? 'X-CURRENT-DTEND' : 'X-CURRENT-DUE'; + $component2->setProperty( $propName, $datestring ); + } // end if( $dtendExist || $dueExist || $durationExist ) + $component2->setProperty( 'X-RECURRENCE', $xRecurrence ); $wd = getdate( $rstart ); - if(( $rstart > $startDate ) && // date after dtstart - !isset( $exdatelist[$rstart] )) // check exclude date - $result[$wd['year']][$wd['mon']][$wd['mday']][] = $component2->copy(); // copy to output - $rstart += ( 24*60*60 ); // step one day - } - } - elseif(( $rstart >= $startDate ) && // date within period - !isset( $exdatelist[$rstart] )) { // check exclude date - $wd = getdate( $rstart ); - $result[$wd['year']][$wd['mon']][$wd['mday']][] = $component2->copy(); // copy to output - } - } - } + $result[$wd['year']][$wd['mon']][$wd['mday']][$component2->getProperty( 'UID' )] = $component2->copy(); // copy to output + } // end if( !isset( $exdatelist[$checkDate] )) + } // end elseif( $rstart >= $startDate ) + } // end foreach( $recurlist as $recurkey => $durvalue ) + } // end if( 0 < count( $recurlist )) /* deselect components with startdate/enddate not within period */ - if(( $endWdate < $startDate ) || ( $startWdate > $endDate )) continue; - } - /* deselect components with startdate not within period */ - elseif(( $startWdate < $startDate ) || ( $startWdate > $endDate )) continue; - /* add selected components within valid dates to output array */ - if( $flat ) - $result[] = $component->copy(); // copy to output; - elseif( $split ) { - if( $endWdate > $endDate ) - $endWdate = $endDate; // use period end date - if( !isset( $exdatelist[$startWdate] )) { // check excluded dates - if( $startWdate < $startDate ) - $startWdate = $startDate; // use period start date - while( $startWdate <= $endWdate ) { // iterate - $wd = getdate( $startWdate ); - $result[$wd['year']][$wd['mon']][$wd['mday']][] = $component->copy(); // copy to output - $startWdate += ( 24*60*60 ); // step one day - } - } - } // use component date - elseif( !isset( $exdatelist[$startWdate] ) && // check excluded dates - ( $startWdate >= $startDate )) { // within period - $wd = getdate( $startWdate ); - $result[$wd['year']][$wd['mon']][$wd['mday']][] = $component->copy(); // copy to output - } - } + if(( $endWdate < $startDate ) || ( $startWdate > $endDate )) + continue; + } // end if( TRUE === $any ) + } // end foreach ( $this->components as $cix => $component ) if( 0 >= count( $result )) return FALSE; elseif( !$flat ) { foreach( $result as $y => $yeararr ) { foreach( $yeararr as $m => $montharr ) { + foreach( $montharr as $d => $dayarr ) + $result[$y][$m][$d] = array_values( $dayarr ); // skip tricky UID-index, hoping they are in hour order.. . ksort( $result[$y][$m] ); } ksort( $result[$y] ); } ksort( $result ); - } + } // end elseif( !$flat ) return $result; } +/** + * select components from calendar on based on Categories, Location, Resources and/or Summary + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-05-03 + * @param array $selectOptions, (string) key => (mixed) value, (key=propertyName) + * @return array + */ + function selectComponents2( $selectOptions ) { + $output = array(); + $allowedProperties = array( 'ATTENDEE', 'CATEGORIES', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY', 'UID' ); + foreach( $this->components as $cix => $component3 ) { + if( !in_array( $component3->objName, array('vevent', 'vtodo', 'vjournal', 'vfreebusy' ))) + continue; + $uid = $component3->getProperty( 'UID' ); + foreach( $selectOptions as $propName => $pvalue ) { + $propName = strtoupper( $propName ); + if( !in_array( $propName, $allowedProperties )) + continue; + if( !is_array( $pvalue )) + $pvalue = array( $pvalue ); + if(( 'UID' == $propName ) && in_array( $uid, $pvalue )) { + $output[] = $component3->copy(); + continue; + } + elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) { + $propValues = array(); + $component3->_getProperties( $propName, $propValues ); + $propValues = array_keys( $propValues ); + foreach( $pvalue as $theValue ) { + if( in_array( $theValue, $propValues ) && !isset( $output[$uid] )) { + $output[$uid] = $component3->copy(); + break; + } + } + continue; + } // end elseif(( 'ATTENDEE' == $propName ) || ( 'CATEGORIES' == $propName ) || ( 'RESOURCES' == $propName )) + elseif( FALSE === ( $d = $component3->getProperty( $propName ))) // single ocurrence + continue; + if( is_array( $d )) { + foreach( $d as $part ) { + if( in_array( $part, $pvalue ) && !isset( $output[$uid] )) + $output[$uid] = $component3->copy(); + } + } + elseif(( 'SUMMARY' == $propName ) && !isset( $output[$uid] )) { + foreach( $pvalue as $pval ) { + if( FALSE !== stripos( $d, $pval )) { + $output[$uid] = $component3->copy(); + break; + } + } + } + elseif( in_array( $d, $pvalue ) && !isset( $output[$uid] )) + $output[$uid] = $component3->copy(); + } // end foreach( $selectOptions as $propName => $pvalue ) { + } // end foreach( $this->components as $cix => $component3 ) { + if( !empty( $output )) { + ksort( $output ); + $output = array_values( $output ); + } + return $output; + } /** * add calendar component to container * - * @author Kjell-Inge Gustafsson - * @since 2.4.10 - 2008-08-06 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-03-15 * @param object $component calendar component * @param mixed $arg1 optional, ordno/component type/ component uid * @param mixed $arg2 optional, ordno if arg1 = component type * @return void */ function setComponent( $component, $arg1=FALSE, $arg2=FALSE ) { - if( '' >= $component->getConfig( 'language')) - $component->setConfig( 'language', $this->getConfig( 'language' )); - $component->setConfig( 'allowEmpty', $this->getConfig( 'allowEmpty' )); - $component->setConfig( 'nl', $this->getConfig( 'nl' )); - $component->setConfig( 'unique_id', $this->getConfig( 'unique_id' )); - $component->setConfig( 'format', $this->getConfig( 'format' )); + $component->setConfig( $this->getConfig(), FALSE, TRUE ); if( !in_array( $component->objName, array( 'valarm', 'vtimezone' ))) { - unset( $component->propix ); /* make sure dtstamp and uid is set */ $dummy1 = $component->getProperty( 'dtstamp' ); $dummy2 = $component->getProperty( 'uid' ); } - if( !$arg1 ) { + if( !$arg1 ) { // plain insert, last in chain $this->components[] = $component->copy(); return TRUE; } $argType = $index = null; - if ( ctype_digit( (string) $arg1 )) { + if ( ctype_digit( (string) $arg1 )) { // index insert/replace $argType = 'INDEX'; $index = (int) $arg1 - 1; } - elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { + elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) { $argType = strtolower( $arg1 ); $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0; } + // else if arg1 is set, arg1 must be an UID $cix1sC = 0; foreach ( $this->components as $cix => $component2) { if( empty( $component2 )) continue; - unset( $component2->propix ); - if(( 'INDEX' == $argType ) && ( $index == $cix )) { + if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace $this->components[$cix] = $component->copy(); return TRUE; } - elseif( $argType == $component2->objName ) { + elseif( $argType == $component2->objName ) { // component Type index insert/replace if( $index == $cix1sC ) { $this->components[$cix] = $component->copy(); return TRUE; } $cix1sC++; } - elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { + elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace $this->components[$cix] = $component->copy(); return TRUE; } } - /* not found.. . insert last in chain anyway .. .*/ - $this->components[] = $component->copy(); + /* arg1=index and not found.. . insert at index .. .*/ + if( 'INDEX' == $argType ) { + $this->components[$index] = $component->copy(); + ksort( $this->components, SORT_NUMERIC ); + } + else /* not found.. . insert last in chain anyway .. .*/ + $this->components[] = $component->copy(); return TRUE; } /** - * sort iCal compoments, only local date sort + * sort iCal compoments * * ascending sort on properties (if exist) x-current-dtstart, dtstart, * x-current-dtend, dtend, x-current-due, due, duration, created, dtstamp, uid + * if no arguments, otherwise sorting on argument CATEGORIES, LOCATION, SUMMARY or RESOURCES * - * @author Kjell-Inge Gustafsson - * @since 2.4.10 - 2008-09-24 - * @return sort param + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.4 - 2011-06-02 + * @param string $sortArg, optional + * @return void * */ - function sort() { + function sort( $sortArg=FALSE ) { if( is_array( $this->components )) { - $this->_sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' ); + if( $sortArg ) { + $sortArg = strtoupper( $sortArg ); + if( !in_array( $sortArg, array( 'ATTENDEE', 'CATEGORIES', 'DTSTAMP', 'LOCATION', 'ORGANIZER', 'RESOURCES', 'PRIORITY', 'STATUS', 'SUMMARY' ))) + $sortArg = FALSE; + } + /* set sort parameters for each component */ + foreach( $this->components as $cix => & $c ) { + $c->srtk = array( '0', '0', '0', '0' ); + if( 'vtimezone' == $c->objName ) { + if( FALSE === ( $c->srtk[0] = $c->getProperty( 'tzid' ))) + $c->srtk[0] = 0; + continue; + } + elseif( $sortArg ) { + if(( 'ATTENDEE' == $sortArg ) || ( 'CATEGORIES' == $sortArg ) || ( 'RESOURCES' == $sortArg )) { + $propValues = array(); + $c->_getProperties( $sortArg, $propValues ); + $c->srtk[0] = reset( array_keys( $propValues )); + } + elseif( FALSE !== ( $d = $c->getProperty( $sortArg ))) + $c->srtk[0] = $d; + continue; + } + if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTSTART' ))) + $c->srtk[0] = iCalUtilityFunctions::_date_time_string( $d[1] ); + elseif( FALSE === ( $c->srtk[0] = $c->getProperty( 'dtstart' ))) + $c->srtk[1] = 0; // sortkey 0 : dtstart + if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DTEND' ))) + $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] ); // sortkey 1 : dtend/due(/dtstart+duration) + elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'dtend' ))) { + if( FALSE !== ( $d = $c->getProperty( 'X-CURRENT-DUE' ))) + $c->srtk[1] = iCalUtilityFunctions::_date_time_string( $d[1] ); + elseif( FALSE === ( $c->srtk[1] = $c->getProperty( 'due' ))) + if( FALSE === ( $c->srtk[1] = $c->getProperty( 'duration', FALSE, FALSE, TRUE ))) + $c->srtk[1] = 0; + } + if( FALSE === ( $c->srtk[2] = $c->getProperty( 'created' ))) // sortkey 2 : created/dtstamp + if( FALSE === ( $c->srtk[2] = $c->getProperty( 'dtstamp' ))) + $c->srtk[2] = 0; + if( FALSE === ( $c->srtk[3] = $c->getProperty( 'uid' ))) // sortkey 3 : uid + $c->srtk[3] = 0; + } // end foreach( $this->components as & $c + /* sort */ usort( $this->components, array( $this, '_cmpfcn' )); } } function _cmpfcn( $a, $b ) { - if( empty( $a )) return -1; - if( empty( $b )) return 1; - if( 'vtimezone' == $a->objName) return -1; - if( 'vtimezone' == $b->objName) return 1; - $astart = ( isset( $a->xprop['X-CURRENT-DTSTART']['value'] )) ? $a->_date_time_string( $a->xprop['X-CURRENT-DTSTART']['value'] ) : null; - if( empty( $astart ) && isset( $a->dtstart['value'] )) - $astart = & $a->dtstart['value']; - $bstart = ( isset( $b->xprop['X-CURRENT-DTSTART']['value'] )) ? $b->_date_time_string( $b->xprop['X-CURRENT-DTSTART']['value'] ) : null; - if( empty( $bstart ) && isset( $b->dtstart['value'] )) - $bstart = & $b->dtstart['value']; - if( empty( $astart )) return -1; - elseif( empty( $bstart )) return 1; - foreach( $this->_sortkeys as $key ) { - if ( empty( $astart[$key] )) return -1; - elseif( empty( $bstart[$key] )) return 1; - if ( $astart[$key] == $bstart[$key]) continue; - if (( (int) $astart[$key] ) < ((int) $bstart[$key] )) - return -1; - elseif(( (int) $astart[$key] ) > ((int) $bstart[$key] )) - return 1; - } - $c = ( isset( $a->xprop['X-CURRENT-DTEND']['value'] )) ? $a->_date_time_string( $a->xprop['X-CURRENT-DTEND']['value'] ) : null; - if( empty( $c ) && !empty( $a->dtend['value'] )) - $c = & $a->dtend['value']; - if( empty( $c ) && isset( $a->xprop['X-CURRENT-DUE']['value'] )) - $c = $a->_date_time_string( $a->xprop['X-CURRENT-DUE']['value'] ); - if( empty( $c ) && !empty( $a->due['value'] )) - $c = & $a->due['value']; - if( empty( $c ) && !empty( $a->duration['value'] )) - $c = $a->duration2date(); - $d = ( isset( $b->xprop['X-CURRENT-DTEND']['value'] )) ? $b->_date_time_string( $b->xprop['X-CURRENT-DTEND']['value'] ) : null; - if( empty( $d ) && !empty( $b->dtend['value'] )) - $d = & $b->dtend['value']; - if( empty( $d ) && isset( $b->xprop['X-CURRENT-DUE']['value'] )) - $d = $b->_date_time_string( $b->xprop['X-CURRENT-DUE']['value'] ); - if( empty( $d ) && !empty( $b->due['value'] )) - $d = & $b->due['value']; - if( empty( $d ) && !empty( $b->duration['value'] )) - $d = $b->duration2date(); - if( empty( $c )) return -1; - elseif( empty( $d )) return 1; - foreach( $this->_sortkeys as $key ) { - if ( !isset( $c[$key] )) return -1; - elseif( !isset( $d[$key] )) return 1; - if ( $c[$key] == $d[$key] ) continue; - if (( (int) $c[$key] ) < ((int) $d[$key])) return -1; - elseif(( (int) $c[$key] ) > ((int) $d[$key])) return 1; - } - if( isset( $a->created['value'] )) - $e = & $a->created['value']; - else - $e = & $a->dtstamp['value']; - if( isset( $b->created['value'] )) - $f = & $b->created['value']; - else - $f = & $b->dtstamp['value']; - foreach( $this->_sortkeys as $key ) { - if( !isset( $e[$key] )) return -1; - elseif( !isset( $f[$key] )) return 1; - if ( $e[$key] == $f[$key] ) continue; - if (( (int) $e[$key] ) < ((int) $f[$key])) return -1; - elseif(( (int) $e[$key] ) > ((int) $f[$key])) return 1; - } - if (( $a->uid['value'] ) < - ( $b->uid['value'] )) return -1; - elseif(( $a->uid['value'] ) > - ( $b->uid['value'] )) return 1; + if( empty( $a )) return -1; + if( empty( $b )) return 1; + if( 'vtimezone' == $a->objName ) { + if( 'vtimezone' != $b->objName ) return -1; + elseif( $a->srtk[0] <= $b->srtk[0] ) return -1; + else return 1; + } + elseif( 'vtimezone' == $b->objName ) return 1; + $sortkeys = array( 'year', 'month', 'day', 'hour', 'min', 'sec' ); + for( $k = 0; $k < 4 ; $k++ ) { + if( empty( $a->srtk[$k] )) return -1; + elseif( empty( $b->srtk[$k] )) return 1; + if( is_array( $a->srtk[$k] )) { + if( is_array( $b->srtk[$k] )) { + foreach( $sortkeys as $key ) { + if ( empty( $a->srtk[$k][$key] )) return -1; + elseif( empty( $b->srtk[$k][$key] )) return 1; + if ( $a->srtk[$k][$key] == $b->srtk[$k][$key]) + continue; + if (( (int) $a->srtk[$k][$key] ) < ((int) $b->srtk[$k][$key] )) + return -1; + elseif(( (int) $a->srtk[$k][$key] ) > ((int) $b->srtk[$k][$key] )) + return 1; + } + } + else return -1; + } + elseif( is_array( $b->srtk[$k] )) return 1; + elseif( $a->srtk[$k] < $b->srtk[$k] ) return -1; + elseif( $a->srtk[$k] > $b->srtk[$k] ) return 1; + } return 0; } /** - * get a remote file without fopen - * - * @author Mauro Bieg - * @since 2010-02-02 - * @param string URL - * @return array - * - */ - function getFile($url){ - // get the host name and url path - $parsedUrl = parse_url($url); - $host = $parsedUrl['host']; - if (isset($parsedUrl['path'])) { - $path = $parsedUrl['path']; - } else { - // the url is pointing to the host like http://www.mysite.com - $path = '/'; - } - - if (isset($parsedUrl['query'])) { - $path .= '?' . $parsedUrl['query']; - } - - if (isset($parsedUrl['port'])) { - $port = $parsedUrl['port']; - } else { - // most sites use port 80 - $port = '80'; - } - - $timeout = 10; - $response = ''; - - // connect to the remote server - $fp = @fsockopen($host, '80', $errno, $errstr, $timeout ); - - if( !$fp ) { - throw new Exception("Cannot retrieve URL."); - } else { - // send the necessary headers to get the file - fputs($fp, "GET $path HTTP/1.0\r\n" . - "Host: $host\r\n" . - "Connection: Close\r\n\r\n"); - - // retrieve the response from the remote server - while ( $line = fread( $fp, 1024 ) ) { - $response .= $line; - } - if (strlen($response) > 1000000){ - throw new Exception("iCalendar file too large."); - } - - fclose( $fp ); - - // strip the headers - $pos = mb_strpos($response, "\r\n\r\n"); - $response = mb_substr($response, $pos + 4); - } - - - - - // split the file content in an array by lines - $fileArray = preg_split("/" . $this->nl ."/", $response); - - //append new line chars that were lost on preg_split - for ($i=0; $i < count($fileArray); $i++){ - $fileArray[$i] .= $this->nl; - } - - return $fileArray; - } -/** - * parse iCal file into vcalendar, components, properties and parameters + * parse iCal text/file into vcalendar, components, properties and parameters * - * @author Kjell-Inge Gustafsson - * @since 2.4.10 - 2008-08-06 - * @param string $filename optional filname (incl. opt. directory/path) or URL + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.2 - 2011-05-21 + * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings * @return bool FALSE if error occurs during parsing * */ - function parse( $filename=FALSE ) { - if( !$filename ) { - /* directory/filename previous set via setConfig directory+filename / url */ + function parse( $unparsedtext=FALSE ) { + $nl = $this->getConfig( 'nl' ); + if(( FALSE === $unparsedtext ) || empty( $unparsedtext )) { + /* directory+filename is set previously via setConfig directory+filename or url */ if( FALSE === ( $filename = $this->getConfig( 'url' ))) $filename = $this->getConfig( 'dirfile' ); - } - elseif(( 'http://' == strtolower( substr( $filename, 0, 7 ))) || - ( 'webcal://' == strtolower( substr( $filename, 0, 9 )))) { - /* remote file - URL */ - $this->setConfig( 'URL', $filename ); - if( !$filename = $this->getConfig( 'url' )) - return FALSE; /* err 2 */ - } - else { - /* local directory/filename */ - $parts = pathinfo( $filename ); - if( !empty( $parts['dirname'] ) && ( '.' != $parts['dirname'] )) { - if( !$this->setConfig( 'directory', $parts['dirname'] )) - return FALSE; /* err 3 */ - } - if( !$this->setConfig( 'filename', $parts['basename'] )) - return FALSE; /* err 4 */ - } - if( 'http://' != substr( $filename, 0, 7 )) { - /* local file error tests */ - if( !is_file( $filename )) /* err 5 */ - return FALSE; - if( !is_readable( $filename )) - return FALSE; /* err 6 */ - if( !filesize( $filename )) - return FALSE; /* err 7 */ - clearstatcache(); - } /* READ FILE */ - if (!$this->getConfig( 'url' ) || ini_get('allow_url_fopen')){ - //if local file or url_fopen is allowed on the system - if( FALSE === ( $rows = file( $filename ))) - return FALSE; /* err 1 */ - } - else{ - $rows = $this->getFile($filename); + if( FALSE === ( $rows = file_get_contents( $filename ))) + return FALSE; /* err 1 */ } + elseif( is_array( $unparsedtext )) + $rows = implode( '\n'.$nl, $unparsedtext ); + else + $rows = & $unparsedtext; /* identify BEGIN:VCALENDAR, MUST be first row */ - if( 'BEGIN:VCALENDAR' != strtoupper( trim( $rows[0] ))) + if( 'BEGIN:VCALENDAR' != strtoupper( substr( $rows, 0, 15 ))) return FALSE; /* err 8 */ - /* remove empty trailing lines */ - while( '' == trim( $rows[count( $rows ) - 1] )) { - unset( $rows[count( $rows ) - 1] ); - $rows = array_values( $rows ); + /* fix line folding */ + $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings + $EOLmark = FALSE; + foreach( $eolchars as $eolchar ) { + if( !$EOLmark && ( FALSE !== strpos( $rows, $eolchar ))) { + $rows = str_replace( $eolchar." ", '', $rows ); + $rows = str_replace( $eolchar."\t", '', $rows ); + if( $eolchar != $nl ) + $rows = str_replace( $eolchar, $nl, $rows ); + $EOLmark = TRUE; + } } - /* identify ending END:VCALENDAR row */ - if( 'END:VCALENDAR' != strtoupper( trim( $rows[count( $rows ) - 1] ))) { + $tmp = explode( $nl, $rows ); + $rows = array(); + foreach( $tmp as $tmpr ) + if( !empty( $tmpr )) + $rows[] = $tmpr; + /* skip trailing empty lines */ + $lix = count( $rows ) - 1; + while( empty( $rows[$lix] ) && ( 0 < $lix )) + $lix -= 1; + /* identify ending END:VCALENDAR row, MUST be last row */ + if( 'END:VCALENDAR' != strtoupper( substr( $rows[$lix], 0, 13 ))) return FALSE; /* err 9 */ - } if( 3 > count( $rows )) return FALSE; /* err 10 */ - $comp = $subcomp = null; - $actcomp = & $this; - $nl = $this->getConfig( 'nl' ); + $comp = & $this; $calsync = 0; /* identify components and update unparsed data within component */ + $config = $this->getConfig(); foreach( $rows as $line ) { if( '' == trim( $line )) continue; - if( $nl == substr( $line, 0 - strlen( $nl ))) - $line = substr( $line, 0, ( strlen( $line ) - strlen( $nl ))).'\n'; - if( 'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) { + if( 'BEGIN:VCALENDAR' == strtoupper( substr( $line, 0, 15 ))) { $calsync++; continue; } - elseif( 'END:VCALENDAR' == strtoupper( substr( $line, 0, 13 ))) { + elseif( 'END:VCALENDAR' == strtoupper( substr( $line, 0, 13 ))) { $calsync--; - continue; + break; } elseif( 1 != $calsync ) return FALSE; /* err 20 */ - if( 'END:' == strtoupper( substr( $line, 0, 4 ))) { - if( null != $subcomp ) { - $comp->setComponent( $subcomp ); - $subcomp = null; - } - else { - $this->setComponent( $comp ); - $comp = null; - } - $actcomp = null; - continue; - } // end - if ( 'END:' ==.. . - elseif( 'BEGIN:' == strtoupper( substr( $line, 0, 6 ))) { - $line = str_replace( '\n', '', $line ); - $compname = trim (strtoupper( substr( $line, 6 ))); - if( null != $comp ) { - if( 'VALARM' == $compname ) - $subcomp = new valarm(); - elseif( 'STANDARD' == $compname ) - $subcomp = new vtimezone( 'STANDARD' ); - elseif( 'DAYLIGHT' == $compname ) - $subcomp = new vtimezone( 'DAYLIGHT' ); - else - return FALSE; /* err 6 */ - $actcomp = & $subcomp; - } - else { - switch( $compname ) { - case 'VALARM': - $comp = new valarm(); - break; - case 'VEVENT': - $comp = new vevent(); - break; - case 'VFREEBUSY'; - $comp = new vfreebusy(); - break; - case 'VJOURNAL': - $comp = new vjournal(); - break; - case 'VTODO': - $comp = new vtodo(); - break; - case 'VTIMEZONE': - $comp = new vtimezone(); - break; - default: - return FALSE; // err 7 - break; - } // end - switch - $actcomp = & $comp; - } + elseif( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VE', 'END:VF', 'END:VJ', 'END:VT' ))) { + $this->components[] = $comp->copy(); continue; - } // end - elsif ( 'BEGIN:'.. . - /* update selected component with unparsed data */ - $actcomp->unparsed[] = $line; + } + + if( 'BEGIN:VEVENT' == strtoupper( substr( $line, 0, 12 ))) + $comp = new vevent( $config ); + elseif( 'BEGIN:VFREEBUSY' == strtoupper( substr( $line, 0, 15 ))) + $comp = new vfreebusy( $config ); + elseif( 'BEGIN:VJOURNAL' == strtoupper( substr( $line, 0, 14 ))) + $comp = new vjournal( $config ); + elseif( 'BEGIN:VTODO' == strtoupper( substr( $line, 0, 11 ))) + $comp = new vtodo( $config ); + elseif( 'BEGIN:VTIMEZONE' == strtoupper( substr( $line, 0, 15 ))) + $comp = new vtimezone( $config ); + else /* update component with unparsed data */ + $comp->unparsed[] = $line; } // end - foreach( rows.. . + unset( $config ); /* parse data for calendar (this) object */ - if( is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) { + if( isset( $this->unparsed ) && is_array( $this->unparsed ) && ( 0 < count( $this->unparsed ))) { /* concatenate property values spread over several lines */ $lastix = -1; $propnames = array( 'calscale','method','prodid','version','x-' ); $proprows = array(); foreach( $this->unparsed as $line ) { + if( '' == trim( $line )) + continue; $newProp = FALSE; foreach ( $propnames as $propname ) { if( $propname == strtolower( substr( $line, 0, strlen( $propname )))) { @@ -1433,27 +1659,21 @@ function parse( $filename=FALSE ) { $lastix++; $proprows[$lastix] = $line; } - else { - /* remove line breaks */ - if(( '\n' == substr( $proprows[$lastix], -2 )) && - ( ' ' == substr( $line, 0, 1 ))) { - $proprows[$lastix] = substr( $proprows[$lastix], 0, strlen( $proprows[$lastix] ) - 2 ); - $line = substr( $line, 1 ); - } - $proprows[$lastix] .= $line; - } + else + $proprows[$lastix] .= '!"#¤%&/()=?'.$line; } - $toolbox = new calendarComponent(); foreach( $proprows as $line ) { + $line = str_replace( '!"#¤%&/()=? ', '', $line ); + $line = str_replace( '!"#¤%&/()=?', '', $line ); if( '\n' == substr( $line, -2 )) $line = substr( $line, 0, strlen( $line ) - 2 ); - /* get propname */ + /* get property name */ $cix = $propname = null; - for( $cix=0; $cix < strlen( $line ); $cix++ ) { - if( in_array( $line{$cix}, array( ':', ';' ))) + for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) { + if( in_array( $line[$cix], array( ':', ';' ))) break; else - $propname .= $line{$cix}; + $propname .= $line[$cix]; } /* ignore version/prodid properties */ if( in_array( strtoupper( $propname ), array( 'VERSION', 'PRODID' ))) @@ -1464,8 +1684,10 @@ function parse( $filename=FALSE ) { $attrix = -1; $strlen = strlen( $line ); for( $cix=0; $cix < $strlen; $cix++ ) { - if(( ':' == $line{$cix} ) && + if(( ':' == $line[$cix] ) && ( '://' != substr( $line, $cix, 3 )) && + ( !in_array( strtolower( substr( $line, $cix - 3, 4 )), array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ))) && + ( !in_array( strtolower( substr( $line, $cix - 4, 5 )), array( 'crid:', 'news:', 'pres:' ))) && ( 'mailto:' != strtolower( substr( $line, $cix - 6, 7 )))) { $attrEnd = TRUE; if(( $cix < ( $strlen - 4 )) && @@ -1482,10 +1704,10 @@ function parse( $filename=FALSE ) { break; } } - if( ';' == $line{$cix} ) + if( ';' == $line[$cix] ) $attr[++$attrix] = null; else - $attr[$attrix] .= $line{$cix}; + $attr[$attrix] .= $line[$cix]; } /* make attributes in array format */ @@ -1510,22 +1732,25 @@ function parse( $filename=FALSE ) { } if( 1 < count( $content )) { foreach( $content as $cix => $contentPart ) - $content[$cix] = $toolbox->_strunrep( $contentPart ); + $content[$cix] = calendarComponent::_strunrep( $contentPart ); $this->setProperty( $propname, $content, $propattr ); continue; } else $line = reset( $content ); - $line = $toolbox->_strunrep( $line ); + $line = calendarComponent::_strunrep( $line ); } $this->setProperty( $propname, trim( $line ), $propattr ); } // end - foreach( $this->unparsed.. . } // end - if( is_array( $this->unparsed.. . + unset( $unparsedtext, $rows, $this->unparsed, $proprows ); /* parse Components */ if( is_array( $this->components ) && ( 0 < count( $this->components ))) { - for( $six = 0; $six < count( $this->components ); $six++ ) { - if( !empty( $this->components[$six] )) - $this->components[$six]->parse(); + $ckeys = array_keys( $this->components ); + foreach( $ckeys as $ckey ) { + if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) { + $this->components[$ckey]->parse(); + } } } else @@ -1536,8 +1761,8 @@ function parse( $filename=FALSE ) { /** * creates formatted output for calendar object instance * - * @author Kjell-Inge Gustafsson - * @since 2.4.10 - 2008-08-06 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.1 - 2011-03-12 * @return string */ function createCalendar() { @@ -1554,10 +1779,10 @@ function createCalendar() { $calendarStart = 'BEGIN:VCALENDAR'.$this->nl; break; } + $calendarStart .= $this->createVersion(); + $calendarStart .= $this->createProdid(); $calendarStart .= $this->createCalscale(); $calendarStart .= $this->createMethod(); - $calendarStart .= $this->createProdid(); - $calendarStart .= $this->createVersion(); switch( $this->format ) { case 'xcal': $nlstrlen = strlen( $this->nl ); @@ -1571,12 +1796,7 @@ function createCalendar() { $calendar .= $this->createXprop(); foreach( $this->components as $component ) { if( empty( $component )) continue; - if( '' >= $component->getConfig( 'language')) - $component->setConfig( 'language', $this->getConfig( 'language' )); - $component->setConfig( 'allowEmpty', $this->getConfig( 'allowEmpty' )); - $component->setConfig( 'nl', $this->getConfig( 'nl' )); - $component->setConfig( 'unique_id', $this->getConfig( 'unique_id' )); - $component->setConfig( 'format', $this->getConfig( 'format' )); + $component->setConfig( $this->getConfig(), FALSE, TRUE ); $calendar .= $component->createComponent( $this->xcaldecl ); } if(( 0 < count( $this->xcaldecl )) && ( 'xcal' == $this->format )) { // xCal only @@ -1629,16 +1849,23 @@ function createCalendar() { /** * a HTTP redirect header is sent with created, updated and/or parsed calendar * - * @author Kjell-Inge Gustafsson - * @since 2.2.12 - 2007-10-23 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.12 - 2011-07-13 + * @param bool $utf8Encode + * @param bool $gzip * @return redirect */ - function returnCalendar() { + function returnCalendar( $utf8Encode=FALSE, $gzip=FALSE ) { $filename = $this->getConfig( 'filename' ); $output = $this->createCalendar(); + if( $utf8Encode ) + $output = utf8_encode( $output ); + if( $gzip ) { + $output = gzencode( $output, 9 ); + header( 'Content-Encoding: gzip'); + header( 'Vary: *'); + } $filesize = strlen( $output ); -// if( headers_sent( $filename, $linenum )) -// die( "Headers already sent in $filename on line $linenum\n" ); if( 'xcal' == $this->format ) header( 'Content-Type: application/calendar+xml; charset=utf-8' ); else @@ -1652,7 +1879,7 @@ function returnCalendar() { /** * save content in a file * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.2.12 - 2007-12-30 * @param string $directory optional * @param string $filename optional @@ -1682,7 +1909,7 @@ function saveCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE ) { * if recent version of calendar file exists (default one hour), an HTTP redirect header is sent * else FALSE is returned * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.2.12 - 2007-10-28 * @param string $directory optional alt. int timeout * @param string $filename optional @@ -1718,7 +1945,7 @@ function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, header( 'Content-Length: '.$filesize ); header( 'Content-Disposition: attachment; filename="'.$filename.'"' ); header( 'Cache-Control: max-age=10' ); - $fp = @$fopen( $dirfile, 'r' ); + $fp = @fopen( $dirfile, 'r' ); if( $fp ) { fpassthru( $fp ); fclose( $fp ); @@ -1734,8 +1961,8 @@ function useCachedCalendar( $directory=FALSE, $filename=FALSE, $delimiter=FALSE, /** * abstract class for calendar components * - * @author Kjell-Inge Gustafsson - * @since 2.4.19 - 2008-10-12 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-14 */ class calendarComponent { // component property variables @@ -1749,6 +1976,7 @@ class calendarComponent { var $unique_id; var $format; var $objName; // created automatically at instance creation + var $dtzid; // default (local) timezone // component internal variables var $componentStart1; var $componentStart2; @@ -1766,8 +1994,8 @@ class calendarComponent { /** * constructor for calendar component object * - * @author Kjell-Inge Gustafsson - * @since 2.4.19 - 2008-10-23 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-17 */ function calendarComponent() { $this->objName = ( isset( $this->timezonetype )) ? @@ -1779,6 +2007,7 @@ function calendarComponent() { $this->nl = null; $this->unique_id = null; $this->format = null; + $this->dtzid = null; $this->allowEmpty = TRUE; $this->xcaldecl = array(); @@ -1792,7 +2021,7 @@ function calendarComponent() { /** * creates formatted output for calendar component property action * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -1806,7 +2035,7 @@ function createAction() { /** * set calendar component property action * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE" * @param mixed $params @@ -1814,7 +2043,7 @@ function createAction() { */ function setAction( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->action = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->action = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -1824,7 +2053,7 @@ function setAction( $value, $params=FALSE ) { /** * creates formatted output for calendar component property attach * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 0.9.7 - 2006-11-23 * @return string */ @@ -1843,7 +2072,7 @@ function createAttach() { /** * set calendar component property attach * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-06 * @param string $value * @param array $params, optional @@ -1852,7 +2081,7 @@ function createAttach() { */ function setAttach( $value, $params=FALSE, $index=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->_setMval( $this->attach, $value, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->attach, $value, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -1862,8 +2091,8 @@ function setAttach( $value, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property attendee * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-09-23 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.8 - 2011-05-30 * @return string */ function createAttendee() { @@ -1875,64 +2104,70 @@ function createAttendee() { $output .= $this->_createElement( 'ATTENDEE' ); continue; } - $attendee1 = $attendee2 = $attendeeLANG = $attendeeCN = null; + $attendee1 = $attendee2 = null; foreach( $attendeePart as $paramlabel => $paramvalue ) { // start foreach 2 if( 'value' == $paramlabel ) - $attendee2 .= 'MAILTO:'.$paramvalue; + $attendee2 .= $paramvalue; elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) { // start elseif + // set attenddee parameters in rfc2445 order + if( isset( $paramvalue['CUTYPE'] )) + $attendee1 .= $this->intAttrDelimiter.'CUTYPE='.$paramvalue['CUTYPE']; + if( isset( $paramvalue['MEMBER'] )) { + $attendee1 .= $this->intAttrDelimiter.'MEMBER='; + foreach( $paramvalue['MEMBER'] as $cix => $opv ) + $attendee1 .= ( $cix ) ? ', "'.$opv.'"' : '"'.$opv.'"' ; + } + if( isset( $paramvalue['ROLE'] )) + $attendee1 .= $this->intAttrDelimiter.'ROLE='.$paramvalue['ROLE']; + if( isset( $paramvalue['PARTSTAT'] )) + $attendee1 .= $this->intAttrDelimiter.'PARTSTAT='.$paramvalue['PARTSTAT']; + if( isset( $paramvalue['RSVP'] )) + $attendee1 .= $this->intAttrDelimiter.'RSVP='.$paramvalue['RSVP']; + if( isset( $paramvalue['DELEGATED-TO'] )) { + $attendee1 .= $this->intAttrDelimiter.'DELEGATED-TO='; + foreach( $paramvalue['DELEGATED-TO'] as $cix => $opv ) + $attendee1 .= ( $cix ) ? ', "'.$opv.'"' : '"'.$opv.'"' ; + } + if( isset( $paramvalue['DELEGATED-FROM'] )) { + $attendee1 .= $this->intAttrDelimiter.'DELEGATED-FROM='; + foreach( $paramvalue['DELEGATED-FROM'] as $cix => $opv ) + $attendee1 .= ( $cix ) ? ', "'.$opv.'"' : '"'.$opv.'"' ; + } + if( isset( $paramvalue['SENT-BY'] )) + $attendee1 .= $this->intAttrDelimiter.'SENT-BY="'.$paramvalue['SENT-BY'].'"'; + if( isset( $paramvalue['CN'] )) + $attendee1 .= $this->intAttrDelimiter.'CN="'.$paramvalue['CN'].'"'; + if( isset( $paramvalue['DIR'] )) + $attendee1 .= $this->intAttrDelimiter.'DIR="'.$paramvalue['DIR'].'"'; + if( isset( $paramvalue['LANGUAGE'] )) + $attendee1 .= $this->intAttrDelimiter.'LANGUAGE='.$paramvalue['LANGUAGE']; + $xparams = array(); foreach( $paramvalue as $optparamlabel => $optparamvalue ) { // start foreach 3 - $attendee11 = $attendee12 = null; - if( is_int( $optparamlabel )) { - $attendee1 .= $this->intAttrDelimiter.$optparamvalue; + if( ctype_digit( (string) $optparamlabel )) { + $xparams[] = $optparamvalue; continue; } - switch( $optparamlabel ) { // start switch - case 'CUTYPE': - case 'PARTSTAT': - case 'ROLE': - case 'RSVP': - $attendee1 .= $this->intAttrDelimiter.$optparamlabel.'="'.$optparamvalue.'"'; - break; - case 'SENT-BY': - $attendee1 .= $this->intAttrDelimiter.'SENT-BY="MAILTO:'.$optparamvalue.'"'; - break; - case 'MEMBER': - $attendee11 = $this->intAttrDelimiter.'MEMBER='; - case 'DELEGATED-TO': - $attendee11 = ( !$attendee11 ) ? $this->intAttrDelimiter.'DELEGATED-TO=' : $attendee11; - case 'DELEGATED-FROM': - $attendee11 = ( !$attendee11 ) ? $this->intAttrDelimiter.'DELEGATED-FROM=' : $attendee11; - foreach( $optparamvalue as $cix => $calUserAddress ) { - $attendee12 .= ( $cix ) ? ',' : null; - $attendee12 .= '"MAILTO:'.$calUserAddress.'"'; - } - $attendee1 .= $attendee11.$attendee12; - break; - case 'CN': - $attendeeCN .= $this->intAttrDelimiter.'CN="'.$optparamvalue.'"'; - break; - case 'DIR': - $attendee1 .= $this->intAttrDelimiter.'DIR="'.$optparamvalue.'"'; - break; - case 'LANGUAGE': - $attendeeLANG .= $this->intAttrDelimiter.'LANGUAGE='.$optparamvalue; - break; - default: - $attendee1 .= $this->intAttrDelimiter."$optparamlabel=$optparamvalue"; - break; - } // end switch + if( !in_array( $optparamlabel, array( 'CUTYPE', 'MEMBER', 'ROLE', 'PARTSTAT', 'RSVP', 'DELEGATED-TO', 'DELEGATED-FROM', 'SENT-BY', 'CN', 'DIR', 'LANGUAGE' ))) + $xparams[$optparamlabel] = $optparamvalue; + } // end foreach 3 + ksort( $xparams, SORT_STRING ); + foreach( $xparams as $paramKey => $paramValue ) { + if( ctype_digit( (string) $paramKey )) + $attendee1 .= $this->intAttrDelimiter.$paramValue; + else + $attendee1 .= $this->intAttrDelimiter."$paramKey=$paramValue"; } // end foreach 3 - } // end elseif + } // end elseif(( 'params' == $paramlabel ) && ( is_array( $paramvalue ))) } // end foreach 2 - $output .= $this->_createElement( 'ATTENDEE', $attendee1.$attendeeLANG.$attendeeCN, $attendee2 ); + $output .= $this->_createElement( 'ATTENDEE', $attendee1, $attendee2 ); } // end foreach 1 return $output; } /** * set calendar component property attach * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-11-05 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.34 - 2010-12-18 * @param string $value * @param array $params, optional * @param integer $index, optional @@ -1940,8 +2175,11 @@ function createAttendee() { */ function setAttendee( $value, $params=FALSE, $index=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $value = str_replace ( 'MAILTO:', '', $value ); - $value = str_replace ( 'mailto:', '', $value ); + // ftp://, http://, mailto:, file://, gopher://, news:, nntp://, telnet://, wais://, prospero:// may exist.. . also in params + if( FALSE !== ( $pos = strpos( substr( $value, 0, 9 ), ':' ))) + $value = strtoupper( substr( $value, 0, $pos )).substr( $value, $pos ); + elseif( !empty( $value )) + $value = 'MAILTO:'.$value; $params2 = array(); if( is_array($params )) { $optarrays = array(); @@ -1951,31 +2189,30 @@ function setAttendee( $value, $params=FALSE, $index=FALSE ) { case 'MEMBER': case 'DELEGATED-TO': case 'DELEGATED-FROM': - if( is_array( $optparamvalue )) { - foreach( $optparamvalue as $part ) { - $part = str_replace( 'MAILTO:', '', $part ); - $part = str_replace( 'mailto:', '', $part ); - if(( '"' == $part{0} ) && ( '"' == $part{strlen($part)-1} )) - $part = substr( $part, 1, ( strlen($part)-2 )); - $optarrays[$optparamlabel][] = $part; - } - } - else { - $part = str_replace( 'MAILTO:', '', $optparamvalue ); - $part = str_replace( 'mailto:', '', $part ); - if(( '"' == $part{0} ) && ( '"' == $part{strlen($part)-1} )) - $part = substr( $part, 1, ( strlen($part)-2 )); + if( !is_array( $optparamvalue )) + $optparamvalue = array( $optparamvalue ); + foreach( $optparamvalue as $part ) { + $part = trim( $part ); + if(( '"' == substr( $part, 0, 1 )) && + ( '"' == substr( $part, -1 ))) + $part = substr( $part, 1, ( strlen( $part ) - 2 )); + if( 'mailto:' != strtolower( substr( $part, 0, 7 ))) + $part = "MAILTO:$part"; + else + $part = 'MAILTO:'.substr( $part, 7 ); $optarrays[$optparamlabel][] = $part; } break; default: - if( 'SENT-BY' == $optparamlabel ) { - $optparamvalue = str_replace( 'MAILTO:', '', $optparamvalue ); - $optparamvalue = str_replace( 'mailto:', '', $optparamvalue ); - } if(( '"' == substr( $optparamvalue, 0, 1 )) && ( '"' == substr( $optparamvalue, -1 ))) $optparamvalue = substr( $optparamvalue, 1, ( strlen( $optparamvalue ) - 2 )); + if( 'SENT-BY' == $optparamlabel ) { + if( 'mailto:' != strtolower( substr( $optparamvalue, 0, 7 ))) + $optparamvalue = "MAILTO:$optparamvalue"; + else + $optparamvalue = 'MAILTO:'.substr( $optparamvalue, 7 ); + } $params2[$optparamlabel] = $optparamvalue; break; } // end switch( $optparamlabel.. . @@ -1984,17 +2221,17 @@ function setAttendee( $value, $params=FALSE, $index=FALSE ) { $params2[$optparamlabel] = $optparams; } // remove defaults - $this->_existRem( $params2, 'CUTYPE', 'INDIVIDUAL' ); - $this->_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' ); - $this->_existRem( $params2, 'ROLE', 'REQ-PARTICIPANT' ); - $this->_existRem( $params2, 'RSVP', 'FALSE' ); + iCalUtilityFunctions::_existRem( $params2, 'CUTYPE', 'INDIVIDUAL' ); + iCalUtilityFunctions::_existRem( $params2, 'PARTSTAT', 'NEEDS-ACTION' ); + iCalUtilityFunctions::_existRem( $params2, 'ROLE', 'REQ-PARTICIPANT' ); + iCalUtilityFunctions::_existRem( $params2, 'RSVP', 'FALSE' ); // check language setting if( isset( $params2['CN' ] )) { $lang = $this->getConfig( 'language' ); if( !isset( $params2['LANGUAGE' ] ) && !empty( $lang )) $params2['LANGUAGE' ] = $lang; } - $this->_setMval( $this->attendee, $value, $params2, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->attendee, $value, $params2, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -2004,7 +2241,7 @@ function setAttendee( $value, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property categories * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -2032,7 +2269,7 @@ function createCategories() { /** * set calendar component property categories * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-06 * @param mixed $value * @param array $params, optional @@ -2041,7 +2278,7 @@ function createCategories() { */ function setCategories( $value, $params=FALSE, $index=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->_setMval( $this->categories, $value, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->categories, $value, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -2051,7 +2288,7 @@ function setCategories( $value, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property class * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 0.9.7 - 2006-11-20 * @return string */ @@ -2065,7 +2302,7 @@ function createClass() { /** * set calendar component property class * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token / x-name * @param array $params optional @@ -2073,7 +2310,7 @@ function createClass() { */ function setClass( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->class = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->class = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -2083,7 +2320,7 @@ function setClass( $value, $params=FALSE ) { /** * creates formatted output for calendar component property comment * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -2104,7 +2341,7 @@ function createComment() { /** * set calendar component property comment * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-06 * @param string $value * @param array $params, optional @@ -2113,7 +2350,7 @@ function createComment() { */ function setComment( $value, $params=FALSE, $index=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->_setMval( $this->comment, $value, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->comment, $value, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -2123,7 +2360,7 @@ function setComment( $value, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property completed * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -2138,14 +2375,14 @@ function createCompleted( ) { if( $this->getConfig( 'allowEmpty' )) return $this->_createElement( 'COMPLETED' ); else return FALSE; - $formatted = $this->_format_date_time( $this->completed['value'], 7 ); + $formatted = iCalUtilityFunctions::_format_date_time( $this->completed['value'], 7 ); $attributes = $this->_createParams( $this->completed['params'] ); return $this->_createElement( 'COMPLETED', $attributes, $formatted ); } /** * set calendar component property completed * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-23 * @param mixed $year * @param mixed $month optional @@ -2159,13 +2396,13 @@ function createCompleted( ) { function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { if( empty( $year )) { if( $this->getConfig( 'allowEmpty' )) { - $this->completed = array( 'value' => null, 'params' => $this->_setParams( $params )); + $this->completed = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } else return FALSE; } - $this->completed = $this->_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); + $this->completed = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); return TRUE; } /*********************************************************************************/ @@ -2175,7 +2412,7 @@ function setCompleted( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, /** * creates formatted output for calendar component property contact * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-23 * @return string */ @@ -2195,7 +2432,7 @@ function createContact() { /** * set calendar component property contact * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-05 * @param string $value * @param array $params, optional @@ -2204,7 +2441,7 @@ function createContact() { */ function setContact( $value, $params=FALSE, $index=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->_setMval( $this->contact, $value, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->contact, $value, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -2214,20 +2451,20 @@ function setContact( $value, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property created * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ function createCreated() { if( empty( $this->created )) return FALSE; - $formatted = $this->_format_date_time( $this->created['value'], 7 ); + $formatted = iCalUtilityFunctions::_format_date_time( $this->created['value'], 7 ); $attributes = $this->_createParams( $this->created['params'] ); return $this->_createElement( 'CREATED', $attributes, $formatted ); } /** * set calendar component property created * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-23 * @param mixed $year optional * @param mixed $month optional @@ -2242,7 +2479,7 @@ function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FA if( !isset( $year )) { $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' ))); } - $this->created = $this->_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); + $this->created = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); return TRUE; } /*********************************************************************************/ @@ -2252,7 +2489,7 @@ function setCreated( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FA /** * creates formatted output for calendar component property description * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -2272,8 +2509,8 @@ function createDescription() { /** * set calendar component property description * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-11-05 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.24 - 2010-11-06 * @param string $value * @param array $params, optional * @param integer $index, optional @@ -2281,7 +2518,9 @@ function createDescription() { */ function setDescription( $value, $params=FALSE, $index=FALSE ) { if( empty( $value )) { if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; } - $this->_setMval( $this->description, $value, $params, FALSE, $index ); + if( 'vjournal' != $this->objName ) + $index = 1; + iCalUtilityFunctions::_setMval( $this->description, $value, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -2291,8 +2530,8 @@ function setDescription( $value, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property dtend * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-21 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-14 * @return string */ function createDtend() { @@ -2306,15 +2545,19 @@ function createDtend() { if( $this->getConfig( 'allowEmpty' )) return $this->_createElement( 'DTEND' ); else return FALSE; - $formatted = $this->_format_date_time( $this->dtend['value'] ); + $formatted = iCalUtilityFunctions::_format_date_time( $this->dtend['value'] ); + if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && + ( !isset( $this->dtend['params']['VALUE'] ) || ( $this->dtend['params']['VALUE'] != 'DATE' )) && + !isset( $this->dtend['params']['TZID'] )) + $this->dtend['params']['TZID'] = $tzid; $attributes = $this->_createParams( $this->dtend['params'] ); return $this->_createElement( 'DTEND', $attributes, $formatted ); } /** * set calendar component property dtend * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-23 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-14 * @param mixed $year * @param mixed $month optional * @param int $day optional @@ -2328,13 +2571,13 @@ function createDtend() { function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) { if( empty( $year )) { if( $this->getConfig( 'allowEmpty' )) { - $this->dtend = array( 'value' => null, 'params' => $this->_setParams( $params )); + $this->dtend = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } else return FALSE; } - $this->dtend = $this->_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params ); + $this->dtend = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' )); return TRUE; } /*********************************************************************************/ @@ -2344,7 +2587,7 @@ function setDtend( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $se /** * creates formatted output for calendar component property dtstamp * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.4 - 2008-03-07 * @return string */ @@ -2356,30 +2599,31 @@ function createDtstamp() { !isset( $this->dtstamp['value']['min'] ) && !isset( $this->dtstamp['value']['sec'] )) $this->_makeDtstamp(); - $formatted = $this->_format_date_time( $this->dtstamp['value'], 7 ); + $formatted = iCalUtilityFunctions::_format_date_time( $this->dtstamp['value'], 7 ); $attributes = $this->_createParams( $this->dtstamp['params'] ); return $this->_createElement( 'DTSTAMP', $attributes, $formatted ); } /** * computes datestamp for calendar component object instance dtstamp * - * @author Kjell-Inge Gustafsson - * @since 1.x.x - 2007-05-13 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.9 - 2011-08-10 * @return void */ function _makeDtstamp() { - $this->dtstamp['value'] = array( 'year' => date( 'Y' ) - , 'month' => date( 'm' ) - , 'day' => date( 'd' ) - , 'hour' => date( 'H' ) - , 'min' => date( 'i' ) - , 'sec' => date( 's' ) - date( 'Z' )); + $d = mktime( date('H'), date('i'), (date('s') - date( 'Z' )), date('m'), date('d'), date('Y')); + $this->dtstamp['value'] = array( 'year' => date( 'Y', $d ) + , 'month' => date( 'm', $d ) + , 'day' => date( 'd', $d ) + , 'hour' => date( 'H', $d ) + , 'min' => date( 'i', $d ) + , 'sec' => date( 's', $d )); $this->dtstamp['params'] = null; } /** * set calendar component property dtstamp * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-23 * @param mixed $year * @param mixed $month optional @@ -2394,7 +2638,7 @@ function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $ if( empty( $year )) $this->_makeDtstamp(); else - $this->dtstamp = $this->_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); + $this->dtstamp = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); return TRUE; } /*********************************************************************************/ @@ -2404,8 +2648,8 @@ function setDtstamp( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $ /** * creates formatted output for calendar component property dtstart * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-26 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-15 * @return string */ function createDtstart() { @@ -2415,21 +2659,26 @@ function createDtstart() { !isset( $this->dtstart['value']['day'] ) && !isset( $this->dtstart['value']['hour'] ) && !isset( $this->dtstart['value']['min'] ) && - !isset( $this->dtstart['value']['sec'] )) - if( $this->getConfig( 'allowEmpty' )) - return $this->_createElement( 'DTSTART' ); - else return FALSE; + !isset( $this->dtstart['value']['sec'] )) { + if( $this->getConfig( 'allowEmpty' )) + return $this->_createElement( 'DTSTART' ); + else return FALSE; + } if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) unset( $this->dtstart['value']['tz'], $this->dtstart['params']['TZID'] ); - $formatted = $this->_format_date_time( $this->dtstart['value'] ); + elseif(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && + ( !isset( $this->dtstart['params']['VALUE'] ) || ( $this->dtstart['params']['VALUE'] != 'DATE' )) && + !isset( $this->dtstart['params']['TZID'] )) + $this->dtstart['params']['TZID'] = $tzid; + $formatted = iCalUtilityFunctions::_format_date_time( $this->dtstart['value'] ); $attributes = $this->_createParams( $this->dtstart['params'] ); return $this->_createElement( 'DTSTART', $attributes, $formatted ); } /** * set calendar component property dtstart * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-11-04 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.22 - 2010-09-22 * @param mixed $year * @param mixed $month optional * @param int $day optional @@ -2443,13 +2692,13 @@ function createDtstart() { function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) { if( empty( $year )) { if( $this->getConfig( 'allowEmpty' )) { - $this->dtstart = array( 'value' => null, 'params' => $this->_setParams( $params )); + $this->dtstart = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } else return FALSE; } - $this->dtstart = $this->_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart' ); + $this->dtstart = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, 'dtstart', $this->objName, $this->getConfig( 'TZID' )); return TRUE; } /*********************************************************************************/ @@ -2459,7 +2708,7 @@ function setDtstart( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $ /** * creates formatted output for calendar component property due * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -2470,18 +2719,24 @@ function createDue() { !isset( $this->due['value']['day'] ) && !isset( $this->due['value']['hour'] ) && !isset( $this->due['value']['min'] ) && - !isset( $this->due['value']['sec'] )) + !isset( $this->due['value']['sec'] )) { if( $this->getConfig( 'allowEmpty' )) return $this->_createElement( 'DUE' ); - else return FALSE; - $formatted = $this->_format_date_time( $this->due['value'] ); + else + return FALSE; + } + $formatted = iCalUtilityFunctions::_format_date_time( $this->due['value'] ); + if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && + ( !isset( $this->due['params']['VALUE'] ) || ( $this->due['params']['VALUE'] != 'DATE' )) && + !isset( $this->due['params']['TZID'] )) + $this->due['params']['TZID'] = $tzid; $attributes = $this->_createParams( $this->due['params'] ); return $this->_createElement( 'DUE', $attributes, $formatted ); } /** * set calendar component property due * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param mixed $year * @param mixed $month optional @@ -2495,13 +2750,13 @@ function createDue() { function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE ) { if( empty( $year )) { if( $this->getConfig( 'allowEmpty' )) { - $this->due = array( 'value' => null, 'params' => $this->_setParams( $params )); + $this->due = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } else return FALSE; } - $this->due = $this->_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params ); + $this->due = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' )); return TRUE; } /*********************************************************************************/ @@ -2511,7 +2766,7 @@ function setDue( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec= /** * creates formatted output for calendar component property duration * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -2526,12 +2781,12 @@ function createDuration() { return $this->_createElement( 'DURATION', array(), null ); else return FALSE; $attributes = $this->_createParams( $this->duration['params'] ); - return $this->_createElement( 'DURATION', $attributes, $this->_format_duration( $this->duration['value'] )); + return $this->_createElement( 'DURATION', $attributes, iCalUtilityFunctions::_format_duration( $this->duration['value'] )); } /** * set calendar component property duration * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param mixed $week * @param mixed $day optional @@ -2544,17 +2799,17 @@ function createDuration() { function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { if( empty( $week )) if( $this->getConfig( 'allowEmpty' )) $week = null; else return FALSE; if( is_array( $week ) && ( 1 <= count( $week ))) - $this->duration = array( 'value' => $this->_duration_array( $week ), 'params' => $this->_setParams( $day )); + $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( $week ), 'params' => iCalUtilityFunctions::_setParams( $day )); elseif( is_string( $week ) && ( 3 <= strlen( trim( $week )))) { $week = trim( $week ); if( in_array( substr( $week, 0, 1 ), array( '+', '-' ))) $week = substr( $week, 1 ); - $this->duration = array( 'value' => $this->_duration_string( $week ), 'params' => $this->_setParams( $day )); + $this->duration = array( 'value' => iCalUtilityFunctions::_duration_string( $week ), 'params' => iCalUtilityFunctions::_setParams( $day )); } elseif( empty( $week ) && empty( $day ) && empty( $hour ) && empty( $min ) && empty( $sec )) return FALSE; else - $this->duration = array( 'value' => $this->_duration_array( array( $week, $day, $hour, $min, $sec )), 'params' => $this->_setParams( $params )); + $this->duration = array( 'value' => iCalUtilityFunctions::_duration_array( array( $week, $day, $hour, $min, $sec )), 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -2564,7 +2819,7 @@ function setDuration( $week, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $p /** * creates formatted output for calendar component property exdate * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -2579,7 +2834,7 @@ function createExdate() { $content = $attributes = null; foreach( $theExdate['value'] as $eix => $exdatePart ) { $parno = count( $exdatePart ); - $formatted = $this->_format_date_time( $exdatePart, $parno ); + $formatted = iCalUtilityFunctions::_format_date_time( $exdatePart, $parno ); if( isset( $theExdate['params']['TZID'] )) $formatted = str_replace( 'Z', '', $formatted); if( 0 < $eix ) { @@ -2605,7 +2860,7 @@ function createExdate() { /** * set calendar component property exdate * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-05 * @param array exdates * @param array $params, optional @@ -2615,31 +2870,31 @@ function createExdate() { function setExdate( $exdates, $params=FALSE, $index=FALSE ) { if( empty( $exdates )) { if( $this->getConfig( 'allowEmpty' )) { - $this->_setMval( $this->exdate, null, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->exdate, null, $params, FALSE, $index ); return TRUE; } else return FALSE; } - $input = array( 'params' => $this->_setParams( $params, array( 'VALUE' => 'DATE-TIME' ))); + $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ))); /* ev. check 1:st date and save ev. timezone **/ - $this->_chkdatecfg( reset( $exdates ), $parno, $input['params'] ); - $this->_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter + iCalUtilityFunctions::_chkdatecfg( reset( $exdates ), $parno, $input['params'] ); + iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default parameter foreach( $exdates as $eix => $theExdate ) { - if( $this->_isArrayTimestampDate( $theExdate )) - $exdatea = $this->_timestamp2date( $theExdate, $parno ); + if( iCalUtilityFunctions::_isArrayTimestampDate( $theExdate )) + $exdatea = iCalUtilityFunctions::_timestamp2date( $theExdate, $parno ); elseif( is_array( $theExdate )) - $exdatea = $this->_date_time_array( $theExdate, $parno ); + $exdatea = iCalUtilityFunctions::_date_time_array( $theExdate, $parno ); elseif( 8 <= strlen( trim( $theExdate ))) // ex. 2006-08-03 10:12:18 - $exdatea = $this->_date_time_string( $theExdate, $parno ); + $exdatea = iCalUtilityFunctions::_date_time_string( $theExdate, $parno ); if( 3 == $parno ) unset( $exdatea['hour'], $exdatea['min'], $exdatea['sec'], $exdatea['tz'] ); elseif( isset( $exdatea['tz'] )) $exdatea['tz'] = (string) $exdatea['tz']; if( isset( $input['params']['TZID'] ) || - ( isset( $exdatea['tz'] ) && !$this->_isOffset( $exdatea['tz'] )) || + ( isset( $exdatea['tz'] ) && !iCalUtilityFunctions::_isOffset( $exdatea['tz'] )) || ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) || - ( isset( $input['value'][0]['tz'] ) && !$this->_isOffset( $input['value'][0]['tz'] ))) + ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] ))) unset( $exdatea['tz'] ); $input['value'][] = $exdatea; } @@ -2649,7 +2904,7 @@ function setExdate( $exdates, $params=FALSE, $index=FALSE ) { $input['params']['VALUE'] = 'DATE'; unset( $input['params']['TZID'] ); } - $this->_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->exdate, $input['value'], $input['params'], FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -2659,7 +2914,7 @@ function setExdate( $exdates, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property exrule * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -2670,7 +2925,7 @@ function createExrule() { /** * set calendar component property exdate * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-05 * @param array $exruleset * @param array $params, optional @@ -2679,7 +2934,7 @@ function createExrule() { */ function setExrule( $exruleset, $params=FALSE, $index=FALSE ) { if( empty( $exruleset )) if( $this->getConfig( 'allowEmpty' )) $exruleset = null; else return FALSE; - $this->_setMval( $this->exrule, $this->_setRexrule( $exruleset ), $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->exrule, iCalUtilityFunctions::_setRexrule( $exruleset ), $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -2689,7 +2944,7 @@ function setExrule( $exruleset, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property freebusy * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -2713,7 +2968,7 @@ function createFreebusy() { $fno = 1; $cnt = count( $freebusyPart['value']); foreach( $freebusyPart['value'] as $periodix => $freebusyPeriod ) { - $formatted = $this->_format_date_time( $freebusyPeriod[0] ); + $formatted = iCalUtilityFunctions::_format_date_time( $freebusyPeriod[0] ); $content .= $formatted; $content .= '/'; $cnt2 = count( $freebusyPeriod[1]); @@ -2725,10 +2980,10 @@ function createFreebusy() { isset( $freebusyPeriod[1]['year'] ) && isset( $freebusyPeriod[1]['month'] ) && isset( $freebusyPeriod[1]['day'] )) { - $content .= $this->_format_date_time( $freebusyPeriod[1] ); + $content .= iCalUtilityFunctions::_format_date_time( $freebusyPeriod[1] ); } else { // period= -> dur-time - $content .= $this->_format_duration( $freebusyPeriod[1] ); + $content .= iCalUtilityFunctions::_format_duration( $freebusyPeriod[1] ); } if( $fno < $cnt ) $content .= ','; @@ -2741,8 +2996,8 @@ function createFreebusy() { /** * set calendar component property freebusy * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-11-05 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.10 - 2011-03-24 * @param string $fbType * @param array $fbValues * @param array $params, optional @@ -2752,7 +3007,7 @@ function createFreebusy() { function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) { if( empty( $fbValues )) { if( $this->getConfig( 'allowEmpty' )) { - $this->_setMval( $this->freebusy, null, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->freebusy, null, $params, FALSE, $index ); return TRUE; } else @@ -2764,37 +3019,39 @@ function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) { $fbType = 'BUSY'; $input = array( 'fbtype' => $fbType ); foreach( $fbValues as $fbPeriod ) { // periods => period + if( empty( $fbPeriod )) + continue; $freebusyPeriod = array(); foreach( $fbPeriod as $fbMember ) { // pairs => singlepart $freebusyPairMember = array(); if( is_array( $fbMember )) { - if( $this->_isArrayDate( $fbMember )) { // date-time value - $freebusyPairMember = $this->_date_time_array( $fbMember, 7 ); + if( iCalUtilityFunctions::_isArrayDate( $fbMember )) { // date-time value + $freebusyPairMember = iCalUtilityFunctions::_date_time_array( $fbMember, 7 ); $freebusyPairMember['tz'] = 'Z'; } - elseif( $this->_isArrayTimestampDate( $fbMember )) { // timestamp value - $freebusyPairMember = $this->_timestamp2date( $fbMember['timestamp'], 7 ); + elseif( iCalUtilityFunctions::_isArrayTimestampDate( $fbMember )) { // timestamp value + $freebusyPairMember = iCalUtilityFunctions::_timestamp2date( $fbMember['timestamp'], 7 ); $freebusyPairMember['tz'] = 'Z'; } else { // array format duration - $freebusyPairMember = $this->_duration_array( $fbMember ); + $freebusyPairMember = iCalUtilityFunctions::_duration_array( $fbMember ); } } elseif(( 3 <= strlen( trim( $fbMember ))) && // string format duration ( in_array( $fbMember{0}, array( 'P', '+', '-' )))) { if( 'P' != $fbMember{0} ) $fbmember = substr( $fbMember, 1 ); - $freebusyPairMember = $this->_duration_string( $fbMember ); + $freebusyPairMember = iCalUtilityFunctions::_duration_string( $fbMember ); } elseif( 8 <= strlen( trim( $fbMember ))) { // text date ex. 2006-08-03 10:12:18 - $freebusyPairMember = $this->_date_time_string( $fbMember, 7 ); + $freebusyPairMember = iCalUtilityFunctions::_date_time_string( $fbMember, 7 ); $freebusyPairMember['tz'] = 'Z'; } $freebusyPeriod[] = $freebusyPairMember; } $input[] = $freebusyPeriod; } - $this->_setMval( $this->freebusy, $input, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->freebusy, $input, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -2804,7 +3061,7 @@ function setFreebusy( $fbType, $fbValues, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property geo * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -2822,7 +3079,7 @@ function createGeo() { /** * set calendar component property geo * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param float $latitude * @param float $longitude @@ -2834,10 +3091,10 @@ function setGeo( $latitude, $longitude, $params=FALSE ) { if( !is_array( $this->geo )) $this->geo = array(); $this->geo['value']['latitude'] = $latitude; $this->geo['value']['longitude'] = $longitude; - $this->geo['params'] = $this->_setParams( $params ); + $this->geo['params'] = iCalUtilityFunctions::_setParams( $params ); } elseif( $this->getConfig( 'allowEmpty' )) - $this->geo = array( 'value' => null, 'params' => $this->_setParams( $params ) ); + $this->geo = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) ); else return FALSE; return TRUE; @@ -2849,20 +3106,20 @@ function setGeo( $latitude, $longitude, $params=FALSE ) { /** * creates formatted output for calendar component property last-modified * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ function createLastModified() { if( empty( $this->lastmodified )) return FALSE; $attributes = $this->_createParams( $this->lastmodified['params'] ); - $formatted = $this->_format_date_time( $this->lastmodified['value'], 7 ); + $formatted = iCalUtilityFunctions::_format_date_time( $this->lastmodified['value'], 7 ); return $this->_createElement( 'LAST-MODIFIED', $attributes, $formatted ); } /** * set calendar component property completed * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-23 * @param mixed $year optional * @param mixed $month optional @@ -2876,7 +3133,7 @@ function createLastModified() { function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { if( empty( $year )) $year = date('Ymd\THis', mktime( date( 'H' ), date( 'i' ), date( 's' ) - date( 'Z'), date( 'm' ), date( 'd' ), date( 'Y' ))); - $this->lastmodified = $this->_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); + $this->lastmodified = iCalUtilityFunctions::_setDate2( $year, $month, $day, $hour, $min, $sec, $params ); return TRUE; } /*********************************************************************************/ @@ -2886,7 +3143,7 @@ function setLastModified( $year=FALSE, $month=FALSE, $day=FALSE, $hour=FALSE, $m /** * creates formatted output for calendar component property location * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-22 * @return string */ @@ -2901,7 +3158,7 @@ function createLocation() { /** * set calendar component property location ' - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param array params optional @@ -2909,7 +3166,7 @@ function createLocation() { */ function setLocation( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->location = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->location = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -2919,8 +3176,8 @@ function setLocation( $value, $params=FALSE ) { /** * creates formatted output for calendar component property organizer * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-21 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.33 - 2010-12-17 * @return string */ function createOrganizer() { @@ -2928,27 +3185,31 @@ function createOrganizer() { if( empty( $this->organizer['value'] )) return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'ORGANIZER' ) : FALSE; $attributes = $this->_createParams( $this->organizer['params'] - , array( 'CN', 'DIR', 'LANGUAGE', 'SENT-BY' )); - $content = 'MAILTO:'.$this->organizer['value']; - return $this->_createElement( 'ORGANIZER', $attributes, $content ); + , array( 'CN', 'DIR', 'SENT-BY', 'LANGUAGE' )); + return $this->_createElement( 'ORGANIZER', $attributes, $this->organizer['value'] ); } /** * set calendar component property organizer * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-11-04 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.27 - 2010-11-29 * @param string $value * @param array params optional * @return bool */ function setOrganizer( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $value = str_replace ( 'MAILTO:', '', $value ); - $value = str_replace ( 'mailto:', '', $value ); - $this->organizer = array( 'value' => $value, 'params' => $this->_setParams( $params )); - if( isset( $this->organizer['params']['SENT-BY'] )) { - if( 'MAILTO' == strtoupper( substr( $this->organizer['params']['SENT-BY'], 0, 6 ))) - $this->organizer['params']['SENT-BY'] = substr( $this->organizer['params']['SENT-BY'], 7 ); + if( FALSE === ( $pos = strpos( substr( $value, 0, 9 ), ':' ))) + $value = 'MAILTO:'.$value; + else + $value = strtolower( substr( $value, 0, $pos )).substr( $value, $pos ); + $value = str_replace( 'mailto:', 'MAILTO:', $value ); + $this->organizer = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); + if( isset( $this->organizer['params']['SENT-BY'] )){ + if( 'mailto:' !== strtolower( substr( $this->organizer['params']['SENT-BY'], 0, 7 ))) + $this->organizer['params']['SENT-BY'] = 'MAILTO:'.$this->organizer['params']['SENT-BY']; + else + $this->organizer['params']['SENT-BY'] = 'MAILTO:'.substr( $this->organizer['params']['SENT-BY'], 7 ); } return TRUE; } @@ -2959,13 +3220,13 @@ function setOrganizer( $value, $params=FALSE ) { /** * creates formatted output for calendar component property percent-complete * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-22 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @return string */ function createPercentComplete() { - if( empty( $this->percentcomplete )) return FALSE; - if( empty( $this->percentcomplete['value'] )) + if( !isset($this->percentcomplete) || ( empty( $this->percentcomplete ) && !is_numeric( $this->percentcomplete ))) return FALSE; + if( !isset( $this->percentcomplete['value'] ) || ( empty( $this->percentcomplete['value'] ) && !is_numeric( $this->percentcomplete['value'] ))) return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PERCENT-COMPLETE' ) : FALSE; $attributes = $this->_createParams( $this->percentcomplete['params'] ); return $this->_createElement( 'PERCENT-COMPLETE', $attributes, $this->percentcomplete['value'] ); @@ -2973,15 +3234,15 @@ function createPercentComplete() { /** * set calendar component property percent-complete * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-11-04 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @param int $value * @param array $params optional * @return bool */ function setPercentComplete( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->percentcomplete = array( 'value' => $value, 'params' => $this->_setParams( $params )); + if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; + $this->percentcomplete = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -2991,13 +3252,13 @@ function setPercentComplete( $value, $params=FALSE ) { /** * creates formatted output for calendar component property priority * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-21 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @return string */ function createPriority() { - if( empty( $this->priority )) return FALSE; - if( empty( $this->priority['value'] )) + if( !isset($this->priority) || ( empty( $this->priority ) && !is_numeric( $this->priority ))) return FALSE; + if( !isset( $this->priority['value'] ) || ( empty( $this->priority['value'] ) && !is_numeric( $this->priority['value'] ))) return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'PRIORITY' ) : FALSE; $attributes = $this->_createParams( $this->priority['params'] ); return $this->_createElement( 'PRIORITY', $attributes, $this->priority['value'] ); @@ -3005,15 +3266,15 @@ function createPriority() { /** * set calendar component property priority * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-11-04 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @param int $value * @param array $params optional * @return bool */ function setPriority( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->priority = array( 'value' => $value, 'params' => $this->_setParams( $params )); + if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; + $this->priority = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3023,7 +3284,7 @@ function setPriority( $value, $params=FALSE ) { /** * creates formatted output for calendar component property rdate * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.16 - 2008-10-26 * @return string */ @@ -3050,11 +3311,11 @@ function createRdate() { isset( $theRdate['params']['VALUE'] ) && ( 'PERIOD' == $theRdate['params']['VALUE'] )) { // PERIOD if( $utctime ) unset( $rdatePart[0]['tz'] ); - $formatted = $this->_format_date_time( $rdatePart[0]); // PERIOD part 1 + $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[0]); // PERIOD part 1 if( $utctime || !empty( $theRdate['params']['TZID'] )) $formatted = str_replace( 'Z', '', $formatted); if( 0 < $rpix ) { - if( !empty( $rdatePart[0]['tz'] ) && $this->_isOffset( $rdatePart[0]['tz'] )) { + if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) { if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z'; } else @@ -3077,10 +3338,10 @@ function createRdate() { isset( $rdatePart[1]['day'] )) { if( $utctime ) unset( $rdatePart[1]['tz'] ); - $formatted = $this->_format_date_time( $rdatePart[1] ); // PERIOD part 2 + $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart[1] ); // PERIOD part 2 if( $utctime || !empty( $theRdate['params']['TZID'] )) $formatted = str_replace( 'Z', '', $formatted); - if( !empty( $rdatePart[0]['tz'] ) && $this->_isOffset( $rdatePart[0]['tz'] )) { + if( !empty( $rdatePart[0]['tz'] ) && iCalUtilityFunctions::_isOffset( $rdatePart[0]['tz'] )) { if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z'; } else @@ -3088,17 +3349,17 @@ function createRdate() { $contentPart .= $formatted; } else { // period= -> dur-time - $contentPart .= $this->_format_duration( $rdatePart[1] ); + $contentPart .= iCalUtilityFunctions::_format_duration( $rdatePart[1] ); } } // PERIOD end else { // SINGLE date start if( $utctime ) unset( $rdatePart['tz'] ); - $formatted = $this->_format_date_time( $rdatePart); + $formatted = iCalUtilityFunctions::_format_date_time( $rdatePart); if( $utctime || !empty( $theRdate['params']['TZID'] )) $formatted = str_replace( 'Z', '', $formatted); if( !$utctime && ( 0 < $rpix )) { - if( !empty( $theRdate['value'][0]['tz'] ) && $this->_isOffset( $theRdate['value'][0]['tz'] )) { + if( !empty( $theRdate['value'][0]['tz'] ) && iCalUtilityFunctions::_isOffset( $theRdate['value'][0]['tz'] )) { if( 'Z' != substr( $formatted, -1 )) $formatted .= 'Z'; } @@ -3119,7 +3380,7 @@ function createRdate() { /** * set calendar component property rdate * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-07 * @param array $rdates * @param array $params, optional @@ -3129,13 +3390,13 @@ function createRdate() { function setRdate( $rdates, $params=FALSE, $index=FALSE ) { if( empty( $rdates )) { if( $this->getConfig( 'allowEmpty' )) { - $this->_setMval( $this->rdate, null, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->rdate, null, $params, FALSE, $index ); return TRUE; } else return FALSE; } - $input = array( 'params' => $this->_setParams( $params, array( 'VALUE' => 'DATE-TIME' ))); + $input = array( 'params' => iCalUtilityFunctions::_setParams( $params, array( 'VALUE' => 'DATE-TIME' ))); if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) { unset( $input['params']['TZID'] ); $input['params']['VALUE'] = 'DATE-TIME'; @@ -3145,7 +3406,7 @@ function setRdate( $rdates, $params=FALSE, $index=FALSE ) { isset( $rdates[0] ) && is_array( $rdates[0] ) && ( 2 == count( $rdates[0] )) && isset( $rdates[0][0] ) && isset( $rdates[0][1] ) && !isset( $rdates[0]['timestamp'] ) && (( is_array( $rdates[0][0] ) && ( isset( $rdates[0][0]['timestamp'] ) || - $this->_isArrayDate( $rdates[0][0] ))) || + iCalUtilityFunctions::_isArrayDate( $rdates[0][0] ))) || ( is_string( $rdates[0][0] ) && ( 8 <= strlen( trim( $rdates[0][0] ))))) && ( is_array( $rdates[0][1] ) || ( is_string( $rdates[0][1] ) && ( 3 <= strlen( trim( $rdates[0][1] )))))) $input['params']['VALUE'] = 'PERIOD'; @@ -3153,57 +3414,57 @@ function setRdate( $rdates, $params=FALSE, $index=FALSE ) { $date = reset( $rdates ); if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) // PERIOD $date = reset( $date ); - $this->_chkdatecfg( $date, $parno, $input['params'] ); + iCalUtilityFunctions::_chkdatecfg( $date, $parno, $input['params'] ); if( in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) unset( $input['params']['TZID'] ); - $this->_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default + iCalUtilityFunctions::_existRem( $input['params'], 'VALUE', 'DATE-TIME' ); // remove default foreach( $rdates as $rpix => $theRdate ) { $inputa = null; if( is_array( $theRdate )) { if( isset( $input['params']['VALUE'] ) && ( 'PERIOD' == $input['params']['VALUE'] )) { // PERIOD foreach( $theRdate as $rix => $rPeriod ) { if( is_array( $rPeriod )) { - if( $this->_isArrayTimestampDate( $rPeriod )) // timestamp - $inputab = ( isset( $rPeriod['tz'] )) ? $this->_timestamp2date( $rPeriod, $parno ) : $this->_timestamp2date( $rPeriod, 6 ); - elseif( $this->_isArrayDate( $rPeriod )) - $inputab = ( 3 < count ( $rPeriod )) ? $this->_date_time_array( $rPeriod, $parno ) : $this->_date_time_array( $rPeriod, 6 ); + if( iCalUtilityFunctions::_isArrayTimestampDate( $rPeriod )) // timestamp + $inputab = ( isset( $rPeriod['tz'] )) ? iCalUtilityFunctions::_timestamp2date( $rPeriod, $parno ) : iCalUtilityFunctions::_timestamp2date( $rPeriod, 6 ); + elseif( iCalUtilityFunctions::_isArrayDate( $rPeriod )) + $inputab = ( 3 < count ( $rPeriod )) ? iCalUtilityFunctions::_date_time_array( $rPeriod, $parno ) : iCalUtilityFunctions::_date_time_array( $rPeriod, 6 ); elseif (( 1 == count( $rPeriod )) && ( 8 <= strlen( reset( $rPeriod )))) // text-date - $inputab = $this->_date_time_string( reset( $rPeriod ), $parno ); + $inputab = iCalUtilityFunctions::_date_time_string( reset( $rPeriod ), $parno ); else // array format duration - $inputab = $this->_duration_array( $rPeriod ); + $inputab = iCalUtilityFunctions::_duration_array( $rPeriod ); } elseif(( 3 <= strlen( trim( $rPeriod ))) && // string format duration - ( in_array( $rPeriod{0}, array( 'P', '+', '-' )))) { - if( 'P' != $rPeriod{0} ) + ( in_array( $rPeriod[0], array( 'P', '+', '-' )))) { + if( 'P' != $rPeriod[0] ) $rPeriod = substr( $rPeriod, 1 ); - $inputab = $this->_duration_string( $rPeriod ); + $inputab = iCalUtilityFunctions::_duration_string( $rPeriod ); } elseif( 8 <= strlen( trim( $rPeriod ))) // text date ex. 2006-08-03 10:12:18 - $inputab = $this->_date_time_string( $rPeriod, $parno ); + $inputab = iCalUtilityFunctions::_date_time_string( $rPeriod, $parno ); if( isset( $input['params']['TZID'] ) || - ( isset( $inputab['tz'] ) && !$this->_isOffset( $inputab['tz'] )) || + ( isset( $inputab['tz'] ) && !iCalUtilityFunctions::_isOffset( $inputab['tz'] )) || ( isset( $inputa[0] ) && ( !isset( $inputa[0]['tz'] ))) || - ( isset( $inputa[0]['tz'] ) && !$this->_isOffset( $inputa[0]['tz'] ))) + ( isset( $inputa[0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $inputa[0]['tz'] ))) unset( $inputab['tz'] ); $inputa[] = $inputab; } } // PERIOD end - elseif ( $this->_isArrayTimestampDate( $theRdate )) // timestamp - $inputa = $this->_timestamp2date( $theRdate, $parno ); - else // date[-time] - $inputa = $this->_date_time_array( $theRdate, $parno ); + elseif ( iCalUtilityFunctions::_isArrayTimestampDate( $theRdate )) // timestamp + $inputa = iCalUtilityFunctions::_timestamp2date( $theRdate, $parno ); + else // date[-time] + $inputa = iCalUtilityFunctions::_date_time_array( $theRdate, $parno ); } elseif( 8 <= strlen( trim( $theRdate ))) // text date ex. 2006-08-03 10:12:18 - $inputa = $this->_date_time_string( $theRdate, $parno ); + $inputa = iCalUtilityFunctions::_date_time_string( $theRdate, $parno ); if( !isset( $input['params']['VALUE'] ) || ( 'PERIOD' != $input['params']['VALUE'] )) { // no PERIOD if( 3 == $parno ) unset( $inputa['hour'], $inputa['min'], $inputa['sec'], $inputa['tz'] ); elseif( isset( $inputa['tz'] )) $inputa['tz'] = (string) $inputa['tz']; if( isset( $input['params']['TZID'] ) || - ( isset( $inputa['tz'] ) && !$this->_isOffset( $inputa['tz'] )) || + ( isset( $inputa['tz'] ) && !iCalUtilityFunctions::_isOffset( $inputa['tz'] )) || ( isset( $input['value'][0] ) && ( !isset( $input['value'][0]['tz'] ))) || - ( isset( $input['value'][0]['tz'] ) && !$this->_isOffset( $input['value'][0]['tz'] ))) + ( isset( $input['value'][0]['tz'] ) && !iCalUtilityFunctions::_isOffset( $input['value'][0]['tz'] ))) unset( $inputa['tz'] ); } $input['value'][] = $inputa; @@ -3212,7 +3473,7 @@ function setRdate( $rdates, $params=FALSE, $index=FALSE ) { $input['params']['VALUE'] = 'DATE'; unset( $input['params']['TZID'] ); } - $this->_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->rdate, $input['value'], $input['params'], FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -3222,23 +3483,27 @@ function setRdate( $rdates, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property recurrence-id * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-21 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-15 * @return string */ function createRecurrenceid() { if( empty( $this->recurrenceid )) return FALSE; if( empty( $this->recurrenceid['value'] )) return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'RECURRENCE-ID' ) : FALSE; - $formatted = $this->_format_date_time( $this->recurrenceid['value'] ); + $formatted = iCalUtilityFunctions::_format_date_time( $this->recurrenceid['value'] ); + if(( FALSE !== ( $tzid = $this->getConfig( 'TZID' ))) && + ( !isset( $this->recurrenceid['params']['VALUE'] ) || ( $this->recurrenceid['params']['VALUE'] != 'DATE' )) && + !isset( $this->recurrenceid['params']['TZID'] )) + $this->recurrenceid['params']['TZID'] = $tzid; $attributes = $this->_createParams( $this->recurrenceid['params'] ); return $this->_createElement( 'RECURRENCE-ID', $attributes, $formatted ); } /** * set calendar component property recurrence-id * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-23 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-15 * @param mixed $year * @param mixed $month optional * @param int $day optional @@ -3257,7 +3522,7 @@ function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FAL else return FALSE; } - $this->recurrenceid = $this->_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params ); + $this->recurrenceid = iCalUtilityFunctions::_setDate( $year, $month, $day, $hour, $min, $sec, $tz, $params, null, null, $this->getConfig( 'TZID' )); return TRUE; } /*********************************************************************************/ @@ -3267,7 +3532,7 @@ function setRecurrenceid( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FAL /** * creates formatted output for calendar component property related-to * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-23 * @return string */ @@ -3290,7 +3555,7 @@ function createRelatedTo() { /** * set calendar component property related-to * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-07 * @param float $relid * @param array $params, optional @@ -3301,8 +3566,8 @@ function setRelatedTo( $value, $params=FALSE, $index=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; if(( '<' == substr( $value, 0, 1 )) && ( '>' == substr( $value, -1 ))) $value = substr( $value, 1, ( strlen( $value ) - 2 )); - $this->_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default - $this->_setMval( $this->relatedto, $value, $params, FALSE, $index ); + iCalUtilityFunctions::_existRem( $params, 'RELTYPE', 'PARENT', TRUE ); // remove default + iCalUtilityFunctions::_setMval( $this->relatedto, $value, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -3312,13 +3577,13 @@ function setRelatedTo( $value, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property repeat * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-21 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @return string */ function createRepeat() { - if( empty( $this->repeat )) return FALSE; - if( empty( $this->repeat['value'] )) + if( !isset( $this->repeat ) || ( empty( $this->repeat ) && !is_numeric( $this->repeat ))) return FALSE; + if( !isset( $this->repeat['value']) || ( empty( $this->repeat['value'] ) && !is_numeric( $this->repeat['value'] ))) return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'REPEAT' ) : FALSE; $attributes = $this->_createParams( $this->repeat['params'] ); return $this->_createElement( 'REPEAT', $attributes, $this->repeat['value'] ); @@ -3326,15 +3591,15 @@ function createRepeat() { /** * set calendar component property transp * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-11-04 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @param string $value * @param array $params optional * @return void */ function setRepeat( $value, $params=FALSE ) { - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->repeat = array( 'value' => $value, 'params' => $this->_setParams( $params )); + if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; + $this->repeat = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3343,7 +3608,7 @@ function setRepeat( $value, $params=FALSE ) { */ /** * creates formatted output for calendar component property request-status - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-23 * @return string */ @@ -3367,7 +3632,7 @@ function createRequestStatus() { /** * set calendar component property request-status * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-05 * @param float $statcode * @param string $text @@ -3381,7 +3646,7 @@ function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $ind $input = array( 'statcode' => $statcode, 'text' => $text ); if( $extdata ) $input['extdata'] = $extdata; - $this->_setMval( $this->requeststatus, $input, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->requeststatus, $input, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -3391,7 +3656,7 @@ function setRequestStatus( $statcode, $text, $extdata=FALSE, $params=FALSE, $ind /** * creates formatted output for calendar component property resources * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-23 * @return string */ @@ -3418,7 +3683,7 @@ function createResources() { /** * set calendar component property recources * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-05 * @param mixed $value * @param array $params, optional @@ -3427,7 +3692,7 @@ function createResources() { */ function setResources( $value, $params=FALSE, $index=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->_setMval( $this->resources, $value, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->resources, $value, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -3437,7 +3702,7 @@ function setResources( $value, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property rrule * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3448,7 +3713,7 @@ function createRrule() { /** * set calendar component property rrule * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-05 * @param array $rruleset * @param array $params, optional @@ -3457,7 +3722,7 @@ function createRrule() { */ function setRrule( $rruleset, $params=FALSE, $index=FALSE ) { if( empty( $rruleset )) if( $this->getConfig( 'allowEmpty' )) $rruleset = null; else return FALSE; - $this->_setMval( $this->rrule, $this->_setRexrule( $rruleset ), $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->rrule, iCalUtilityFunctions::_setRexrule( $rruleset ), $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -3466,29 +3731,30 @@ function setRrule( $rruleset, $params=FALSE, $index=FALSE ) { */ /** * creates formatted output for calendar component property sequence - * @author Kjell-Inge Gustafsson - * @since 0.9.7 - 2006-11-20 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @return string */ function createSequence() { - if( empty( $this->sequence )) return FALSE; - if( empty( $this->sequence['value'] )) + if( !isset( $this->sequence ) || ( empty( $this->sequence ) && !is_numeric( $this->sequence ))) return FALSE; + if(( !isset($this->sequence['value'] ) || ( empty( $this->sequence['value'] ) && !is_numeric( $this->sequence['value'] ))) && + ( '0' != $this->sequence['value'] )) return ( $this->getConfig( 'allowEmpty' )) ? $this->_createElement( 'SEQUENCE' ) : FALSE; $attributes = $this->_createParams( $this->sequence['params'] ); return $this->_createElement( 'SEQUENCE', $attributes, $this->sequence['value'] ); } /** * set calendar component property sequence - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-11-04 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.8 - 2011-09-19 * @param int $value optional * @param array $params optional * @return bool */ function setSequence( $value=FALSE, $params=FALSE ) { - if( empty( $value )) - $value = ( isset( $this->sequence['value'] ) && ( 0 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : 1; - $this->sequence = array( 'value' => $value, 'params' => $this->_setParams( $params )); + if(( empty( $value ) && !is_numeric( $value )) && ( '0' != $value )) + $value = ( isset( $this->sequence['value'] ) && ( -1 < $this->sequence['value'] )) ? $this->sequence['value'] + 1 : '0'; + $this->sequence = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3498,7 +3764,7 @@ function setSequence( $value=FALSE, $params=FALSE ) { /** * creates formatted output for calendar component property status * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3512,7 +3778,7 @@ function createStatus() { /** * set calendar component property status * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param array $params optional @@ -3520,7 +3786,7 @@ function createStatus() { */ function setStatus( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->status = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->status = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3530,7 +3796,7 @@ function setStatus( $value, $params=FALSE ) { /** * creates formatted output for calendar component property summary * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3545,7 +3811,7 @@ function createSummary() { /** * set calendar component property summary * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param string $params optional @@ -3553,7 +3819,7 @@ function createSummary() { */ function setSummary( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->summary = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->summary = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3563,7 +3829,7 @@ function setSummary( $value, $params=FALSE ) { /** * creates formatted output for calendar component property transp * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3577,7 +3843,7 @@ function createTransp() { /** * set calendar component property transp * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param string $params optional @@ -3585,7 +3851,7 @@ function createTransp() { */ function setTransp( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->transp = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->transp = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3595,7 +3861,7 @@ function setTransp( $value, $params=FALSE ) { /** * creates formatted output for calendar component property trigger * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.16 - 2008-10-21 * @return string */ @@ -3607,13 +3873,13 @@ function createTrigger() { if( isset( $this->trigger['value']['year'] ) && isset( $this->trigger['value']['month'] ) && isset( $this->trigger['value']['day'] )) - $content .= $this->_format_date_time( $this->trigger['value'] ); + $content .= iCalUtilityFunctions::_format_date_time( $this->trigger['value'] ); else { if( TRUE !== $this->trigger['value']['relatedStart'] ) $attributes .= $this->intAttrDelimiter.'RELATED=END'; if( $this->trigger['value']['before'] ) $content .= '-'; - $content .= $this->_format_duration( $this->trigger['value'] ); + $content .= iCalUtilityFunctions::_format_duration( $this->trigger['value'] ); } $attributes .= $this->_createParams( $this->trigger['params'] ); return $this->_createElement( 'TRIGGER', $attributes, $content ); @@ -3621,8 +3887,8 @@ function createTrigger() { /** * set calendar component property trigger * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-11-04 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.9 - 2011-06-17 * @param mixed $year * @param mixed $month optional * @param int $day optional @@ -3638,23 +3904,23 @@ function createTrigger() { function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $relatedStart=TRUE, $before=TRUE, $params=FALSE ) { if( empty( $year ) && empty( $month ) && empty( $day ) && empty( $week ) && empty( $hour ) && empty( $min ) && empty( $sec )) if( $this->getConfig( 'allowEmpty' )) { - $this->trigger = array( 'value' => null, 'params' => $this->_setParams( $params ) ); + $this->trigger = array( 'value' => null, 'params' => iCalUtilityFunctions::_setParams( $params ) ); return TRUE; } else return FALSE; - if( $this->_isArrayTimestampDate( $year )) { // timestamp - $params = $this->_setParams( $month ); - $date = $this->_timestamp2date( $year, 7 ); + if( iCalUtilityFunctions::_isArrayTimestampDate( $year )) { // timestamp + $params = iCalUtilityFunctions::_setParams( $month ); + $date = iCalUtilityFunctions::_timestamp2date( $year, 7 ); foreach( $date as $k => $v ) $$k = $v; } elseif( is_array( $year ) && ( is_array( $month ) || empty( $month ))) { - $params = $this->_setParams( $month ); + $params = iCalUtilityFunctions::_setParams( $month ); if(!(array_key_exists( 'year', $year ) && // exclude date-time array_key_exists( 'month', $year ) && - array_key_exists( 'day', $year ))) { // so this must be a duration - if( isset( $params['RELATED'] ) && ( 'END' == $params['RELATED'] )) + array_key_exists( 'day', $year ))) { // when this must be a duration + if( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) $relatedStart = FALSE; else $relatedStart = ( array_key_exists( 'relatedStart', $year ) && ( TRUE !== $year['relatedStart'] )) ? FALSE : TRUE; @@ -3670,22 +3936,25 @@ function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $m $year = $SSYY; } elseif(is_string( $year ) && ( is_array( $month ) || empty( $month ))) { // duration or date in a string - $params = $this->_setParams( $month ); - if( in_array( $year{0}, array( 'P', '+', '-' ))) { // duration - $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == $params['RELATED'] )) ? FALSE : TRUE; - $before = ( '-' == $year{0} ) ? TRUE : FALSE; - if( 'P' != $year{0} ) + $params = iCalUtilityFunctions::_setParams( $month ); + if( in_array( $year[0], array( 'P', '+', '-' ))) { // duration + $relatedStart = ( isset( $params['RELATED'] ) && ( 'END' == strtoupper( $params['RELATED'] ))) ? FALSE : TRUE; + $before = ( '-' == $year[0] ) ? TRUE : FALSE; + if( 'P' != $year[0] ) $year = substr( $year, 1 ); - $date = $this->_duration_string( $year); + $date = iCalUtilityFunctions::_duration_string( $year); } else // date - $date = $this->_date_time_string( $year, 7 ); + $date = iCalUtilityFunctions::_date_time_string( $year, 7 ); unset( $year, $month, $day ); - foreach( $date as $k => $v ) - $$k = $v; + if( empty( $date )) + $sec = 0; + else + foreach( $date as $k => $v ) + $$k = $v; } else // single values in function input parameters - $params = $this->_setParams( $params ); + $params = iCalUtilityFunctions::_setParams( $params ); if( !empty( $year ) && !empty( $month ) && !empty( $day )) { // date $params['VALUE'] = 'DATE-TIME'; $hour = ( $hour ) ? $hour : 0; @@ -3702,19 +3971,28 @@ function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $m return TRUE; } elseif(( empty( $year ) && empty( $month )) && // duration - (!empty( $week ) || !empty( $day ) || !empty( $hour ) || !empty( $min ) || !empty( $sec ))) { + (( !empty( $week ) || ( 0 == $week )) || + ( !empty( $day ) || ( 0 == $day )) || + ( !empty( $hour ) || ( 0 == $hour )) || + ( !empty( $min ) || ( 0 == $min )) || + ( !empty( $sec ) || ( 0 == $sec )))) { unset( $params['RELATED'] ); // set at output creation (END only) unset( $params['VALUE'] ); // 'DURATION' default $this->trigger = array( 'params' => $params ); - $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE; - $before = ( FALSE !== $before ) ? TRUE : FALSE; - $this->trigger['value'] = array( 'relatedStart' => $relatedStart - , 'before' => $before ); + $this->trigger['value'] = array(); if( !empty( $week )) $this->trigger['value']['week'] = $week; if( !empty( $day )) $this->trigger['value']['day'] = $day; if( !empty( $hour )) $this->trigger['value']['hour'] = $hour; if( !empty( $min )) $this->trigger['value']['min'] = $min; if( !empty( $sec )) $this->trigger['value']['sec'] = $sec; + if( empty( $this->trigger['value'] )) { + $this->trigger['value']['sec'] = 0; + $before = FALSE; + } + $relatedStart = ( FALSE !== $relatedStart ) ? TRUE : FALSE; + $before = ( FALSE !== $before ) ? TRUE : FALSE; + $this->trigger['value']['relatedStart'] = $relatedStart; + $this->trigger['value']['before'] = $before; return TRUE; } return FALSE; @@ -3726,7 +4004,7 @@ function setTrigger( $year, $month=null, $day=null, $week=FALSE, $hour=FALSE, $m /** * creates formatted output for calendar component property tzid * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3740,7 +4018,7 @@ function createTzid() { /** * set calendar component property tzid * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param array $params optional @@ -3748,7 +4026,7 @@ function createTzid() { */ function setTzid( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->tzid = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->tzid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3759,7 +4037,7 @@ function setTzid( $value, $params=FALSE ) { /** * creates formatted output for calendar component property tzname * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3778,7 +4056,7 @@ function createTzname() { /** * set calendar component property tzname * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-05 * @param string $value * @param string $params, optional @@ -3787,7 +4065,7 @@ function createTzname() { */ function setTzname( $value, $params=FALSE, $index=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->_setMval( $this->tzname, $value, $params, FALSE, $index ); + iCalUtilityFunctions::_setMval( $this->tzname, $value, $params, FALSE, $index ); return TRUE; } /*********************************************************************************/ @@ -3797,7 +4075,7 @@ function setTzname( $value, $params=FALSE, $index=FALSE ) { /** * creates formatted output for calendar component property tzoffsetfrom * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3811,7 +4089,7 @@ function createTzoffsetfrom() { /** * set calendar component property tzoffsetfrom * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param string $params optional @@ -3819,7 +4097,7 @@ function createTzoffsetfrom() { */ function setTzoffsetfrom( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->tzoffsetfrom = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->tzoffsetfrom = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3829,7 +4107,7 @@ function setTzoffsetfrom( $value, $params=FALSE ) { /** * creates formatted output for calendar component property tzoffsetto * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3843,7 +4121,7 @@ function createTzoffsetto() { /** * set calendar component property tzoffsetto * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param string $params optional @@ -3851,7 +4129,7 @@ function createTzoffsetto() { */ function setTzoffsetto( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->tzoffsetto = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->tzoffsetto = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3861,7 +4139,7 @@ function setTzoffsetto( $value, $params=FALSE ) { /** * creates formatted output for calendar component property tzurl * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3875,7 +4153,7 @@ function createTzurl() { /** * set calendar component property tzurl * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param string $params optional @@ -3883,7 +4161,7 @@ function createTzurl() { */ function setTzurl( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->tzurl = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->tzurl = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3893,7 +4171,7 @@ function setTzurl( $value, $params=FALSE ) { /** * creates formatted output for calendar component property uid * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 0.9.7 - 2006-11-20 * @return string */ @@ -3906,7 +4184,7 @@ function createUid() { /** * create an unique id for this calendar component object instance * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.2.7 - 2007-09-04 * @return void */ @@ -3926,7 +4204,7 @@ function _makeUid() { /** * set calendar component property uid * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param string $params optional @@ -3934,7 +4212,7 @@ function _makeUid() { */ function setUid( $value, $params=FALSE ) { if( empty( $value )) return FALSE; // no allowEmpty check here !!!! - $this->uid = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->uid = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3944,7 +4222,7 @@ function setUid( $value, $params=FALSE ) { /** * creates formatted output for calendar component property url * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-10-21 * @return string */ @@ -3958,7 +4236,7 @@ function createUrl() { /** * set calendar component property url * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.4.8 - 2008-11-04 * @param string $value * @param string $params optional @@ -3966,7 +4244,7 @@ function createUrl() { */ function setUrl( $value, $params=FALSE ) { if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; - $this->url = array( 'value' => $value, 'params' => $this->_setParams( $params )); + $this->url = array( 'value' => $value, 'params' => iCalUtilityFunctions::_setParams( $params )); return TRUE; } /*********************************************************************************/ @@ -3976,15 +4254,15 @@ function setUrl( $value, $params=FALSE ) { /** * creates formatted output for calendar component property x-prop * - * @author Kjell-Inge Gustafsson - * @since 2.4.11 - 2008-10-22 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @return string */ function createXprop() { if( empty( $this->xprop )) return FALSE; $output = null; foreach( $this->xprop as $label => $xpropPart ) { - if( empty( $xpropPart['value'] )) { + if( !isset($xpropPart['value']) || ( empty( $xpropPart['value'] ) && !is_numeric( $xpropPart['value'] ))) { if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $label ); continue; } @@ -4003,8 +4281,8 @@ function createXprop() { /** * set calendar component property x-prop * - * @author Kjell-Inge Gustafsson - * @since 2.4.11 - 2008-11-04 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.3 - 2011-05-14 * @param string $label * @param mixed $value * @param array $params optional @@ -4012,10 +4290,9 @@ function createXprop() { */ function setXprop( $label, $value, $params=FALSE ) { if( empty( $label )) return; - if( empty( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; + if( empty( $value ) && !is_numeric( $value )) if( $this->getConfig( 'allowEmpty' )) $value = null; else return FALSE; $xprop = array( 'value' => $value ); - $toolbox = new calendarComponent(); - $xprop['params'] = $toolbox->_setParams( $params ); + $xprop['params'] = iCalUtilityFunctions::_setParams( $params ); if( !is_array( $this->xprop )) $this->xprop = array(); $this->xprop[strtoupper( $label )] = $xprop; return TRUE; @@ -4025,7 +4302,7 @@ function setXprop( $label, $value, $params=FALSE ) { /** * create element format parts * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.0.6 - 2006-06-20 * @return string */ @@ -4064,15 +4341,22 @@ function _createFormat() { /** * creates formatted output for calendar component property * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-23 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.22 - 2010-12-06 * @param string $label property name * @param string $attributes property attributes * @param string $content property content (optional) * @return string */ function _createElement( $label, $attributes=null, $content=FALSE ) { - $label = $this->_formatPropertyName( $label ); + switch( $this->format ) { + case 'xcal': + $label = strtolower( $label ); + break; + default: + $label = strtoupper( $label ); + break; + } $output = $this->elementStart1.$label; $categoriesAttrLang = null; $attachInlineBinary = FALSE; @@ -4145,1473 +4429,239 @@ function _createElement( $label, $attributes=null, $content=FALSE ) { $content = $this->nl.$this->_createElement( 'b64bin', $attachfmttype, $content ); // max one attribute } $output .= $attributes; - if( !$content ) { + if( !$content && ( '0' != $content )) { switch( $this->format ) { case 'xcal': $output .= ' /'; $output .= $this->elementStart2; return $output; - break; - default: - $output .= $this->elementStart2.$this->valueInit; - return $this->_size75( $output ); - break; - } - } - $output .= $this->elementStart2; - $output .= $this->valueInit.$content; - switch( $this->format ) { - case 'xcal': - return $output.$this->elementEnd1.$label.$this->elementEnd2; - break; - default: - return $this->_size75( $output ); - break; - } - } -/** - * creates formatted output for calendar component property parameters - * - * @author Kjell-Inge Gustafsson - * @since 0.9.22 - 2007-04-10 - * @param array $params optional - * @param array $ctrKeys optional - * @return string - */ - function _createParams( $params=array(), $ctrKeys=array() ) { - $attrLANG = $attr1 = $attr2 = null; - $CNattrKey = ( in_array( 'CN', $ctrKeys )) ? TRUE : FALSE ; - $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ; - $CNattrExist = $LANGattrExist = FALSE; - if( is_array( $params )) { - foreach( $params as $paramKey => $paramValue ) { - if( is_int( $paramKey )) - $attr2 .= $this->intAttrDelimiter.$paramValue; - elseif(( 'LANGUAGE' == $paramKey ) && $LANGattrKey ) { - $attrLANG .= $this->intAttrDelimiter."LANGUAGE=$paramValue"; - $LANGattrExist = TRUE; - } - elseif(( 'CN' == $paramKey ) && $CNattrKey ) { - $attr1 = $this->intAttrDelimiter.'CN="'.$paramValue.'"'; - $CNattrExist = TRUE; - } - elseif(( 'ALTREP' == $paramKey ) && in_array( $paramKey, $ctrKeys )) - $attr2 .= $this->intAttrDelimiter.'ALTREP="'.$paramValue.'"'; - elseif(( 'DIR' == $paramKey ) && in_array( $paramKey, $ctrKeys )) - $attr2 .= $this->intAttrDelimiter.'DIR="'.$paramValue.'"'; - elseif(( 'SENT-BY' == $paramKey ) && in_array( $paramKey, $ctrKeys )) - $attr2 .= $this->intAttrDelimiter.'SENT-BY="MAILTO:'.$paramValue.'"'; - else - $attr2 .= $this->intAttrDelimiter."$paramKey=$paramValue"; - } - } - if( !$LANGattrExist ) { - $lang = $this->getConfig( 'language' ); - if(( $CNattrExist || $LANGattrKey ) && $lang ) - $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang; - } - return $attrLANG.$attr1.$attr2; - } -/** - * check a date(-time) for an opt. timezone and if it is a DATE-TIME or DATE - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-25 - * @param array $date, date to check - * @param int $parno, no of date parts (i.e. year, month.. .) - * @return array $params, property parameters - */ - function _chkdatecfg( $theDate, & $parno, & $params ) { - if( isset( $params['TZID'] )) - $parno = 6; - elseif( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] )) - $parno = 3; - else { - if( isset( $params['VALUE'] ) && ( 'PERIOD' == $params['VALUE'] )) - $parno = 7; - if( is_array( $theDate )) { - if( isset( $theDate['timestamp'] )) - $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : null; - else - $tzid = ( isset( $theDate['tz'] )) ? $theDate['tz'] : ( 7 == count( $theDate )) ? end( $theDate ) : null; - if( !empty( $tzid )) { - $parno = 7; - if( !$this->_isOffset( $tzid )) - $params['TZID'] = $tzid; // save only timezone - } - elseif( !$parno && ( 3 == count( $theDate )) && - ( isset( $params['VALUE'] ) && ( 'DATE' == $params['VALUE'] ))) - $parno = 3; - else - $parno = 6; - } - else { // string - $date = trim( $theDate ); - if( 'Z' == substr( $date, -1 )) - $parno = 7; // UTC DATE-TIME - elseif((( 8 == strlen( $date ) && ctype_digit( $date )) || ( 11 >= strlen( $date ))) && - ( !isset( $params['VALUE'] ) || !in_array( $params['VALUE'], array( 'DATE-TIME', 'PERIOD' )))) - $parno = 3; // DATE - $date = $this->_date_time_string( $date, $parno ); - if( !empty( $date['tz'] )) { - $parno = 7; - if( !$this->_isOffset( $date['tz'] )) - $params['TZID'] = $date['tz']; // save only timezone - } - elseif( empty( $parno )) - $parno = 6; - } - if( isset( $params['TZID'] )) - $parno = 6; - } - } -/** - * convert local startdate/enddate (Ymd[His]) to duration - * - * uses this component dates if missing input dates - * - * @author Kjell-Inge Gustafsson - * @since 2.2.11 - 2007-11-03 - * @param array $startdate, optional - * @param array $duration, optional - * @return array duration - */ - function _date2duration( $startdate=FALSE, $enddate=FALSE ) { - if( !$startdate || !$enddate ) { - if( FALSE === ( $startdate = $this->getProperty( 'dtstart' ))) - return null; - if( FALSE === ( $enddate = $this->getProperty( 'dtend' ))) // vevent/vfreebusy - if( FALSE === ( $enddate = $this->getProperty( 'due' ))) // vtodo - return null; - } - if( !$startdate || !$enddate ) - return null; - $startWdate = mktime( 0, 0, 0, $startdate['month'], $startdate['day'], $startdate['year'] ); - $endWdate = mktime( 0, 0, 0, $enddate['month'], $enddate['day'], $enddate['year'] ); - $wduration = $endWdate - $startWdate; - $dur = array(); - $dur['week'] = (int) floor( $wduration / ( 7 * 24 * 60 * 60 )); - $wduration = $wduration % ( 7 * 24 * 60 * 60 ); - $dur['day'] = (int) floor( $wduration / ( 24 * 60 * 60 )); - $wduration = $wduration % ( 24 * 60 * 60 ); - $dur['hour'] = (int) floor( $wduration / ( 60 * 60 )); - $wduration = $wduration % ( 60 * 60 ); - $dur['min'] = (int) floor( $wduration / ( 60 )); - $dur['sec'] = (int) $wduration % ( 60 ); - return $dur; - } -/** - * convert date/datetime to timestamp - * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-30 - * @param array $datetime datetime/(date) - * @param string $tz timezone - * @return timestamp - */ - function _date2timestamp( $datetime, $tz=null ) { - $output = null; - if( !isset( $datetime['hour'] )) $datetime['hour'] = '0'; - if( !isset( $datetime['min'] )) $datetime['min'] = '0'; - if( !isset( $datetime['sec'] )) $datetime['sec'] = '0'; - foreach( $datetime as $dkey => $dvalue ) { - if( 'tz' != $dkey ) - $datetime[$dkey] = (integer) $dvalue; - } - if( $tz ) - $datetime['tz'] = $tz; - $offset = ( isset( $datetime['tz'] ) && ( '' < trim ( $datetime['tz'] ))) ? $this->_tz2offset( $datetime['tz'] ) : 0; - $output = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] + $offset), $datetime['month'], $datetime['day'], $datetime['year'] ); - return $output; - } -/** - * ensures internal date-time/date format for input date-time/date in array format - * - * @author Kjell-Inge Gustafsson - * @since 0.3.0 - 2006-08-15 - * @param array $datetime - * @param int $parno optional, default FALSE - * @return array - */ - function _date_time_array( $datetime, $parno=FALSE ) { - $output = array(); - foreach( $datetime as $dateKey => $datePart ) { - switch ( $dateKey ) { - case '0': case 'year': $output['year'] = $datePart; break; - case '1': case 'month': $output['month'] = $datePart; break; - case '2': case 'day': $output['day'] = $datePart; break; - } - if( 3 != $parno ) { - switch ( $dateKey ) { - case '0': - case '1': - case '2': break; - case '3': case 'hour': $output['hour'] = $datePart; break; - case '4': case 'min' : $output['min'] = $datePart; break; - case '5': case 'sec' : $output['sec'] = $datePart; break; - case '6': case 'tz' : $output['tz'] = $datePart; break; - } - } - } - if( 3 != $parno ) { - if( !isset( $output['hour'] )) - $output['hour'] = 0; - if( !isset( $output['min'] )) - $output['min'] = 0; - if( !isset( $output['sec'] )) - $output['sec'] = 0; - } - return $output; - } -/** - * ensures internal date-time/date format for input date-time/date in string fromat - * - * @author Kjell-Inge Gustafsson - * @since 2.2.10 - 2007-10-19 - * @param array $datetime - * @param int $parno optional, default FALSE - * @return array - */ - function _date_time_string( $datetime, $parno=FALSE ) { - $datetime = (string) trim( $datetime ); - $tz = null; - $len = strlen( $datetime ) - 1; - if( 'Z' == substr( $datetime, -1 )) { - $tz = 'Z'; - $datetime = trim( substr( $datetime, 0, $len )); - } - elseif( ( ctype_digit( substr( $datetime, -2, 2 ))) && // time or date - ( '-' == substr( $datetime, -3, 1 )) || - ( ':' == substr( $datetime, -3, 1 )) || - ( '.' == substr( $datetime, -3, 1 ))) { - $continue = TRUE; - } - elseif( ( ctype_digit( substr( $datetime, -4, 4 ))) && // 4 pos offset - ( ' +' == substr( $datetime, -6, 2 )) || - ( ' -' == substr( $datetime, -6, 2 ))) { - $tz = substr( $datetime, -5, 5 ); - $datetime = substr( $datetime, 0, ($len - 5)); - } - elseif( ( ctype_digit( substr( $datetime, -6, 6 ))) && // 6 pos offset - ( ' +' == substr( $datetime, -8, 2 )) || - ( ' -' == substr( $datetime, -8, 2 ))) { - $tz = substr( $datetime, -7, 7 ); - $datetime = substr( $datetime, 0, ($len - 7)); - } - elseif( ( 6 < $len ) && ( ctype_digit( substr( $datetime, -6, 6 )))) { - $continue = TRUE; - } - elseif( 'T' == substr( $datetime, -7, 1 )) { - $continue = TRUE; - } - else { - $cx = $tx = 0; // 19970415T133000 US-Eastern - for( $cx = -1; $cx > ( 9 - $len ); $cx-- ) { - if(( ' ' == substr( $datetime, $cx, 1 )) || ctype_digit( substr( $datetime, $cx, 1 ))) - break; // if exists, tz ends here.. . ? - elseif( ctype_alpha( substr( $datetime, $cx, 1 )) || - ( in_array( substr( $datetime, $cx, 1 ), array( '-', '/' )))) - $tx--; // tz length counter - } - if( 0 > $tx ) { - $tz = substr( $datetime, $tx ); - $datetime = trim( substr( $datetime, 0, $len + $tx + 1 )); - } - } - if( 0 < substr_count( $datetime, '-' )) { - $datetime = str_replace( '-', '/', $datetime ); - } - elseif( ctype_digit( substr( $datetime, 0, 8 )) && - ( 'T' == substr( $datetime, 8, 1 )) && - ctype_digit( substr( $datetime, 9, 6 ))) { - $datetime = substr( $datetime, 4, 2 ) - .'/'.substr( $datetime, 6, 2 ) - .'/'.substr( $datetime, 0, 4 ) - .' '.substr( $datetime, 9, 2 ) - .':'.substr( $datetime, 11, 2 ) - .':'.substr( $datetime, 13); - } - $datestring = date( 'Y-m-d H:i:s', strtotime( $datetime )); - $tz = trim( $tz ); - $output = array(); - $output['year'] = substr( $datestring, 0, 4 ); - $output['month'] = substr( $datestring, 5, 2 ); - $output['day'] = substr( $datestring, 8, 2 ); - if(( 6 == $parno ) || ( 7 == $parno )) { - $output['hour'] = substr( $datestring, 11, 2 ); - $output['min'] = substr( $datestring, 14, 2 ); - $output['sec'] = substr( $datestring, 17, 2 ); - if( !empty( $tz )) - $output['tz'] = $tz; - } - elseif( 3 != $parno ) { - if(( '00' < substr( $datestring, 11, 2 )) || - ( '00' < substr( $datestring, 14, 2 )) || - ( '00' < substr( $datestring, 17, 2 ))) { - $output['hour'] = substr( $datestring, 11, 2 ); - $output['min'] = substr( $datestring, 14, 2 ); - $output['sec'] = substr( $datestring, 17, 2 ); - } - if( !empty( $tz )) - $output['tz'] = $tz; - } - return $output; - } -/** - * ensures internal duration format for input in array format - * - * @author Kjell-Inge Gustafsson - * @since 2.1.1 - 2007-06-24 - * @param array $duration - * @return array - */ - function _duration_array( $duration ) { - $output = array(); - if( is_array( $duration ) && - ( 1 == count( $duration )) && - isset( $duration['sec'] ) && - ( 60 < $duration['sec'] )) { - $durseconds = $duration['sec']; - $output['week'] = floor( $durseconds / ( 60 * 60 * 24 * 7 )); - $durseconds = $durseconds % ( 60 * 60 * 24 * 7 ); - $output['day'] = floor( $durseconds / ( 60 * 60 * 24 )); - $durseconds = $durseconds % ( 60 * 60 * 24 ); - $output['hour'] = floor( $durseconds / ( 60 * 60 )); - $durseconds = $durseconds % ( 60 * 60 ); - $output['min'] = floor( $durseconds / ( 60 )); - $output['sec'] = ( $durseconds % ( 60 )); - } - else { - foreach( $duration as $durKey => $durValue ) { - if( empty( $durValue )) continue; - switch ( $durKey ) { - case '0': case 'week': $output['week'] = $durValue; break; - case '1': case 'day': $output['day'] = $durValue; break; - case '2': case 'hour': $output['hour'] = $durValue; break; - case '3': case 'min': $output['min'] = $durValue; break; - case '4': case 'sec': $output['sec'] = $durValue; break; - } - } - } - if( isset( $output['week'] ) && ( 0 < $output['week'] )) { - unset( $output['day'], $output['hour'], $output['min'], $output['sec'] ); - return $output; - } - unset( $output['week'] ); - if( empty( $output['day'] )) - unset( $output['day'] ); - if ( isset( $output['hour'] ) || isset( $output['min'] ) || isset( $output['sec'] )) { - if( !isset( $output['hour'] )) $output['hour'] = 0; - if( !isset( $output['min'] )) $output['min'] = 0; - if( !isset( $output['sec'] )) $output['sec'] = 0; - if(( 0 == $output['hour'] ) && ( 0 == $output['min'] ) && ( 0 == $output['sec'] )) - unset( $output['hour'], $output['min'], $output['sec'] ); - } - return $output; - } -/** - * convert duration to date in array format based on input or dtstart value - * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-30 - * @param array $startdate, optional - * @param array $duration, optional - * @return array, date format - */ - function duration2date( $startdate=FALSE, $duration=FALSE ) { - if( $startdate && $duration ) { - $d1 = $startdate; - $dur = $duration; - } - elseif( isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) { - $d1 = $this->dtstart['value']; - $dur = $this->duration['value']; - } - else - return null; - $dateOnly = ( isset( $d1['hour'] ) || isset( $d1['min'] ) || isset( $d1['sec'] )) ? FALSE : TRUE; - $d1['hour'] = ( isset( $d1['hour'] )) ? $d1['hour'] : 0; - $d1['min'] = ( isset( $d1['min'] )) ? $d1['min'] : 0; - $d1['sec'] = ( isset( $d1['sec'] )) ? $d1['sec'] : 0; - $dtend = mktime( $d1['hour'], $d1['min'], $d1['sec'], $d1['month'], $d1['day'], $d1['year'] ); - if( isset( $dur['week'] )) - $dtend += ( $dur['week'] * 7 * 24 * 60 * 60 ); - if( isset( $dur['day'] )) - $dtend += ( $dur['day'] * 24 * 60 * 60 ); - if( isset( $dur['hour'] )) - $dtend += ( $dur['hour'] * 60 *60 ); - if( isset( $dur['min'] )) - $dtend += ( $dur['min'] * 60 ); - if( isset( $dur['sec'] )) - $dtend += $dur['sec']; - $dtend2 = array(); - $dtend2['year'] = date('Y', $dtend ); - $dtend2['month'] = date('m', $dtend ); - $dtend2['day'] = date('d', $dtend ); - $dtend2['hour'] = date('H', $dtend ); - $dtend2['min'] = date('i', $dtend ); - $dtend2['sec'] = date('s', $dtend ); - if( isset( $d1['tz'] )) - $dtend2['tz'] = $d1['tz']; - if( $dateOnly && (( 0 == $dtend2['hour'] ) && ( 0 == $dtend2['min'] ) && ( 0 == $dtend2['sec'] ))) - unset( $dtend2['hour'], $dtend2['min'], $dtend2['sec'] ); - return $dtend2; - } -/** - * ensures internal duration format for input in string format - * - * @author Kjell-Inge Gustafsson - * @since 2.0.5 - 2007-03-14 - * @param string $duration - * @return array - */ - function _duration_string( $duration ) { - $duration = (string) trim( $duration ); - while( 'P' != strtoupper( substr( $duration, 0, 1 ))) { - if( 0 < strlen( $duration )) - $duration = substr( $duration, 1 ); - else - return false; // no leading P !?!? - } - $duration = substr( $duration, 1 ); // skip P - $duration = str_replace ( 't', 'T', $duration ); - $duration = str_replace ( 'T', '', $duration ); - $output = array(); - $val = null; - for( $ix=0; $ix < strlen( $duration ); $ix++ ) { - switch( strtoupper( $duration{$ix} )) { - case 'W': - $output['week'] = $val; - $val = null; - break; - case 'D': - $output['day'] = $val; - $val = null; - break; - case 'H': - $output['hour'] = $val; - $val = null; - break; - case 'M': - $output['min'] = $val; - $val = null; - break; - case 'S': - $output['sec'] = $val; - $val = null; - break; - default: - if( !ctype_digit( $duration{$ix} )) - return false; // unknown duration controll character !?!? - else - $val .= $duration{$ix}; - } - } - return $this->_duration_array( $output ); - } -/** - * if not preSet, if exist, remove key with expected value from array and return hit value else return elseValue - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-11-08 - * @param array $array - * @param string $expkey, expected key - * @param string $expval, expected value - * @param int $hitVal optional, return value if found - * @param int $elseVal optional, return value if not found - * @param int $preSet optional, return value if already preset - * @return int - */ - function _existRem( &$array, $expkey, $expval=FALSE, $hitVal=null, $elseVal=null, $preSet=null ) { - if( $preSet ) - return $preSet; - if( !is_array( $array ) || ( 0 == count( $array ))) - return $elseVal; - foreach( $array as $key => $value ) { - if( strtoupper( $expkey ) == strtoupper( $key )) { - if( !$expval || ( strtoupper( $expval ) == strtoupper( $array[$key] ))) { - unset( $array[$key] ); - return $hitVal; - } - } - } - return $elseVal; - } -/** - * creates formatted output for calendar component property data value type date/date-time - * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-30 - * @param array $datetime - * @param int $parno, optional, default 6 - * @return string - */ - function _format_date_time( $datetime, $parno=6 ) { - if( !isset( $datetime['year'] ) && - !isset( $datetime['month'] ) && - !isset( $datetime['day'] ) && - !isset( $datetime['hour'] ) && - !isset( $datetime['min'] ) && - !isset( $datetime['sec'] )) - return ; - $output = null; - // if( !isset( $datetime['day'] )) { $o=''; foreach($datetime as $k=>$v) {if(is_array($v)) $v=implode('-',$v);$o.=" $k=>$v";} echo " day SAKNAS : $o
    \n"; } - foreach( $datetime as $dkey => $dvalue ) { - if( 'tz' != $dkey ) - $datetime[$dkey] = (integer) $dvalue; - } - $output = date('Ymd', mktime( 0, 0, 0, $datetime['month'], $datetime['day'], $datetime['year'])); - if( isset( $datetime['hour'] ) || - isset( $datetime['min'] ) || - isset( $datetime['sec'] ) || - isset( $datetime['tz'] )) { - if( isset( $datetime['tz'] ) && - !isset( $datetime['hour'] )) - $datetime['hour'] = 0; - if( isset( $datetime['hour'] ) && - !isset( $datetime['min'] )) - $datetime['min'] = 0; - if( isset( $datetime['hour'] ) && - isset( $datetime['min'] ) && - !isset( $datetime['sec'] )) - $datetime['sec'] = 0; - $date = mktime( $datetime['hour'], $datetime['min'], $datetime['sec'], $datetime['month'], $datetime['day'], $datetime['year']); - $output .= date('\THis', $date ); - if( isset( $datetime['tz'] ) && ( '' < trim ( $datetime['tz'] ))) { - $datetime['tz'] = trim( $datetime['tz'] ); - if( 'Z' == $datetime['tz'] ) - $output .= 'Z'; - $offset = $this->_tz2offset( $datetime['tz'] ); - if( 0 != $offset ) { - $date = mktime( $datetime['hour'], $datetime['min'], ($datetime['sec'] + $offset), $datetime['month'], $datetime['day'], $datetime['year']); - $output = date( 'Ymd\THis\Z', $date ); - } - } - elseif( 7 == $parno ) - $output .= 'Z'; - } - return $output; - } -/** - * creates formatted output for calendar component property data value type duration - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-10 - * @param array $duration ( week, day, hour, min, sec ) - * @return string - */ - function _format_duration( $duration ) { - if( !isset( $duration['week'] ) && - !isset( $duration['day'] ) && - !isset( $duration['hour'] ) && - !isset( $duration['min'] ) && - !isset( $duration['sec'] )) - return; - $output = 'P'; - if( isset( $duration['week'] ) && ( 0 < $duration['week'] )) - $output .= $duration['week'].'W'; - else { - if( isset($duration['day'] ) && ( 0 < $duration['day'] )) - $output .= $duration['day'].'D'; - if(( isset( $duration['hour']) && ( 0 < $duration['hour'] )) || - ( isset( $duration['min']) && ( 0 < $duration['min'] )) || - ( isset( $duration['sec']) && ( 0 < $duration['sec'] ))) { - $output .= 'T'; - $output .= ( isset( $duration['hour']) && ( 0 < $duration['hour'] )) ? $duration['hour'].'H' : '0H'; - $output .= ( isset( $duration['min']) && ( 0 < $duration['min'] )) ? $duration['min']. 'M' : '0M'; - $output .= ( isset( $duration['sec']) && ( 0 < $duration['sec'] )) ? $duration['sec']. 'S' : '0S'; - } - } - return $output; - } -/** - * creates formatted output for calendar component property data value type recur - * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-22 - * @param array $recurlabel - * @param array $recurdata - * @return string - */ - function _format_recur( $recurlabel, $recurdata ) { - $output = null; - foreach( $recurdata as $therule ) { - if( empty( $therule['value'] )) { - if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel ); - continue; - } - $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null; - $content1 = $content2 = null; - foreach( $therule['value'] as $rulelabel => $rulevalue ) { - switch( $rulelabel ) { - case 'FREQ': { - $content1 .= "FREQ=$rulevalue"; - break; - } - case 'UNTIL': { - $content2 .= ";UNTIL="; - $content2 .= $this->_format_date_time( $rulevalue ); - break; - } - case 'COUNT': - case 'INTERVAL': - case 'WKST': { - $content2 .= ";$rulelabel=$rulevalue"; - break; - } - case 'BYSECOND': - case 'BYMINUTE': - case 'BYHOUR': - case 'BYMONTHDAY': - case 'BYYEARDAY': - case 'BYWEEKNO': - case 'BYMONTH': - case 'BYSETPOS': { - $content2 .= ";$rulelabel="; - if( is_array( $rulevalue )) { - foreach( $rulevalue as $vix => $valuePart ) { - $content2 .= ( $vix ) ? ',' : null; - $content2 .= $valuePart; - } - } - else - $content2 .= $rulevalue; - break; - } - case 'BYDAY': { - $content2 .= ";$rulelabel="; - $bydaycnt = 0; - foreach( $rulevalue as $vix => $valuePart ) { - $content21 = $content22 = null; - if( is_array( $valuePart )) { - $content2 .= ( $bydaycnt ) ? ',' : null; - foreach( $valuePart as $vix2 => $valuePart2 ) { - if( 'DAY' != strtoupper( $vix2 )) - $content21 .= $valuePart2; - else - $content22 .= $valuePart2; - } - $content2 .= $content21.$content22; - $bydaycnt++; - } - else { - $content2 .= ( $bydaycnt ) ? ',' : null; - if( 'DAY' != strtoupper( $vix )) - $content21 .= $valuePart; - else { - $content22 .= $valuePart; - $bydaycnt++; - } - $content2 .= $content21.$content22; - } - } - break; - } - default: { - $content2 .= ";$rulelabel=$rulevalue"; - break; - } - } - } - $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 ); - } - return $output; - } -/** - * create property name case - lower/upper - * - * @author Kjell-Inge Gustafsson - * @since 0.9.7 - 2006-11-20 - * @param string $propertyName - * @return string - */ - function _formatPropertyName( $propertyName ) { - switch( $this->format ) { - case 'xcal': - return strtolower( $propertyName ); - break; - default: - return strtoupper( $propertyName ); - break; - } - } -/** - * checks if input array contains a date - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-25 - * @param array $input - * @return bool - */ - function _isArrayDate( $input ) { - if( isset( $input['week'] ) || ( !in_array( count( $input ), array( 3, 6, 7 )))) - return FALSE; - if( 7 == count( $input )) - return TRUE; - if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] )) - return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] ); - if( isset( $input['day'] ) || isset( $input['hour'] ) || isset( $input['min'] ) || isset( $input['sec'] )) - return FALSE; - if( in_array( 0, $input )) - return FALSE; - if(( 1970 > $input[0] ) || ( 12 < $input[1] ) || ( 31 < $input[2] )) - return FALSE; - if(( isset( $input[0] ) && isset( $input[1] ) && isset( $input[2] )) && - checkdate( (int) $input[1], (int) $input[2], (int) $input[0] )) - return TRUE; - $input = $this->_date_time_string( $input[1].'/'.$input[2].'/'.$input[0], 3 ); // m - d - Y - if( isset( $input['year'] ) && isset( $input['month'] ) && isset( $input['day'] )) - return checkdate( (int) $input['month'], (int) $input['day'], (int) $input['year'] ); - return FALSE; - } -/** - * checks if input array contains a timestamp date - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-18 - * @param array $input - * @return bool - */ - function _isArrayTimestampDate( $input ) { - return ( is_array( $input ) && isset( $input['timestamp'] )) ? TRUE : FALSE ; - } -/** - * controll if input string contains traling UTC offset - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-19 - * @param string $input - * @return bool - */ - function _isOffset( $input ) { - $input = trim( (string) $input ); - if( 'Z' == substr( $input, -1 )) - return TRUE; - elseif(( 5 <= strlen( $input )) && - ( in_array( substr( $input, -5, 1 ), array( '+', '-' ))) && - ( '0000' < substr( $input, -4 )) && ( '9999' >= substr( $input, -4 ))) - return TRUE; - elseif(( 7 <= strlen( $input )) && - ( in_array( substr( $input, -7, 1 ), array( '+', '-' ))) && - ( '000000' < substr( $input, -6 )) && ( '999999' >= substr( $input, -6 ))) - return TRUE; - return FALSE; - - } -/** - * check if property not exists within component - * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-10-15 - * @param string $propName - * @return bool - */ - function _notExistProp( $propName ) { - if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed - $propName = strtolower( $propName ); - if( 'last-modified' == $propName ) { if( !isset( $this->lastmodified )) return TRUE; } - elseif( 'percent-complete' == $propName ) { if( !isset( $this->percentcomplete )) return TRUE; } - elseif( 'recurrence-id' == $propName ) { if( !isset( $this->recurrenceid )) return TRUE; } - elseif( 'related-to' == $propName ) { if( !isset( $this->relatedto )) return TRUE; } - elseif( 'request-status' == $propName ) { if( !isset( $this->requeststatus )) return TRUE; } - elseif(( 'x-' != substr($propName,0,2)) && !isset( $this->$propName )) return TRUE; - return FALSE; - } -/** - * remakes a recur pattern to an array of dates - * - * if missing, UNTIL is set 1 year from startdate (emergency break) - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-18 - * @param array $result, array to update, array([timestamp] => timestamp) - * @param array $recur, pattern for recurrency (only value part, params ignored) - * @param array $wdate, component start date - * @param array $startdate, start date - * @param array $enddate, optional - * @return array of recurrence (start-)dates as index - * @todo BYHOUR, BYMINUTE, BYSECOND, ev. BYSETPOS due to ambiguity, WEEKLY at year end/start - */ - function _recur2date( & $result, $recur, $wdate, $startdate, $enddate=FALSE ) { - foreach( $wdate as $k => $v ) if( ctype_digit( $v )) $wdate[$k] = (int) $v; - $wdatets = $this->_date2timestamp( $wdate ); - $startdatets = $this->_date2timestamp( $startdate ); - if( !$enddate ) { - $enddate = $startdate; - $enddate['year'] += 1; -// echo "recur __in_ ".implode('-',$startdate)." period start ".implode('-',$wdate)." period end ".implode('-',$enddate)."
    \n";print_r($recur);echo "
    \n";//test### - } - $endDatets = $this->_date2timestamp( $enddate ); // fix break - if( !isset( $recur['COUNT'] ) && !isset( $recur['UNTIL'] )) - $recur['UNTIL'] = $enddate; // create break - if( isset( $recur['UNTIL'] )) { - $tdatets = $this->_date2timestamp( $recur['UNTIL'] ); - if( $endDatets > $tdatets ) { - $endDatets = $tdatets; // emergency break - $enddate = $this->_timestamp2date( $endDatets, 6 ); - } - else - $recur['UNTIL'] = $this->_timestamp2date( $endDatets, 6 ); - } - if( $wdatets > $endDatets ) { - //echo "recur out of date ".implode('-',$this->_date_time_string(date('Y-m-d H:i:s',$wdatets),6))."
    \n";//test - return array(); // nothing to do.. . - } - if( !isset( $recur['FREQ'] )) // "MUST be specified.. ." - $recur['FREQ'] = 'DAILY'; // ?? - $wkst = ( isset( $recur['WKST'] ) && ( 'SU' == $recur['WKST'] )) ? 24*60*60 : 0; // ?? - if( !isset( $recur['INTERVAL'] )) - $recur['INTERVAL'] = 1; - $countcnt = ( !isset( $recur['BYSETPOS'] )) ? 1 : 0; // DTSTART counts as the first occurrence - /* find out how to step up dates and set index for interval count */ - $step = array(); - if( 'YEARLY' == $recur['FREQ'] ) - $step['year'] = 1; - elseif( 'MONTHLY' == $recur['FREQ'] ) - $step['month'] = 1; - elseif( 'WEEKLY' == $recur['FREQ'] ) - $step['day'] = 7; - else - $step['day'] = 1; - if( isset( $step['year'] ) && isset( $recur['BYMONTH'] )) - $step = array( 'month' => 1 ); - if( empty( $step ) && isset( $recur['BYWEEKNO'] )) // ?? - $step = array( 'day' => 7 ); - if( isset( $recur['BYYEARDAY'] ) || isset( $recur['BYMONTHDAY'] ) || isset( $recur['BYDAY'] )) - $step = array( 'day' => 1 ); - $intervalarr = array(); - if( 1 < $recur['INTERVAL'] ) { - $intervalix = $this->_recurIntervalIx( $recur['FREQ'], $wdate, $wkst ); - $intervalarr = array( $intervalix => 0 ); - } - if( isset( $recur['BYSETPOS'] )) { // save start date + weekno - $bysetposymd1 = $bysetposymd2 = $bysetposw1 = $bysetposw2 = array(); - $bysetposWold = (int) date( 'W', ( $wdatets + $wkst )); - $bysetposYold = $wdate['year']; - $bysetposMold = $wdate['month']; - $bysetposDold = $wdate['day']; - if( is_array( $recur['BYSETPOS'] )) { - foreach( $recur['BYSETPOS'] as $bix => $bval ) - $recur['BYSETPOS'][$bix] = (int) $bval; - } - else - $recur['BYSETPOS'] = array( (int) $recur['BYSETPOS'] ); - $this->_stepdate( $enddate, $endDatets, $step); // make sure to count whole last period - } - $this->_stepdate( $wdate, $wdatets, $step); - $year_old = null; - $daynames = array( 'SU', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA' ); - /* MAIN LOOP */ - // echo "recur start ".implode('-',$wdate)." end ".implode('-',$enddate)."
    \n";//test - while( TRUE ) { - if( isset( $endDatets ) && ( $wdatets > $endDatets )) - break; - if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] )) - break; - if( $year_old != $wdate['year'] ) { - $year_old = $wdate['year']; - $daycnts = array(); - $yeardays = $weekno = 0; - $yeardaycnt = array(); - for( $m = 1; $m <= 12; $m++ ) { // count up and update up-counters - $daycnts[$m] = array(); - $weekdaycnt = array(); - foreach( $daynames as $dn ) - $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0; - $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] )); - for( $d = 1; $d <= $mcnt; $d++ ) { - $daycnts[$m][$d] = array(); - if( isset( $recur['BYYEARDAY'] )) { - $yeardays++; - $daycnts[$m][$d]['yearcnt_up'] = $yeardays; - } - if( isset( $recur['BYDAY'] )) { - $day = date( 'w', mktime( 0, 0, 0, $m, $d, $wdate['year'] )); - $day = $daynames[$day]; - $daycnts[$m][$d]['DAY'] = $day; - $weekdaycnt[$day]++; - $daycnts[$m][$d]['monthdayno_up'] = $weekdaycnt[$day]; - $yeardaycnt[$day]++; - $daycnts[$m][$d]['yeardayno_up'] = $yeardaycnt[$day]; - } - if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) - $daycnts[$m][$d]['weekno_up'] =(int)date('W',mktime(0,0,$wkst,$m,$d,$wdate['year'])); - } - } - $daycnt = 0; - $yeardaycnt = array(); - if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) { - $weekno = null; - for( $d=31; $d > 25; $d-- ) { // get last weekno for year - if( !$weekno ) - $weekno = $daycnts[12][$d]['weekno_up']; - elseif( $weekno < $daycnts[12][$d]['weekno_up'] ) { - $weekno = $daycnts[12][$d]['weekno_up']; - break; - } - } - } - for( $m = 12; $m > 0; $m-- ) { // count down and update down-counters - $weekdaycnt = array(); - foreach( $daynames as $dn ) - $yeardaycnt[$dn] = $weekdaycnt[$dn] = 0; - $monthcnt = 0; - $mcnt = date( 't', mktime( 0, 0, 0, $m, 1, $wdate['year'] )); - for( $d = $mcnt; $d > 0; $d-- ) { - if( isset( $recur['BYYEARDAY'] )) { - $daycnt -= 1; - $daycnts[$m][$d]['yearcnt_down'] = $daycnt; - } - if( isset( $recur['BYMONTHDAY'] )) { - $monthcnt -= 1; - $daycnts[$m][$d]['monthcnt_down'] = $monthcnt; - } - if( isset( $recur['BYDAY'] )) { - $day = $daycnts[$m][$d]['DAY']; - $weekdaycnt[$day] -= 1; - $daycnts[$m][$d]['monthdayno_down'] = $weekdaycnt[$day]; - $yeardaycnt[$day] -= 1; - $daycnts[$m][$d]['yeardayno_down'] = $yeardaycnt[$day]; - } - if( isset( $recur['BYWEEKNO'] ) || ( $recur['FREQ'] == 'WEEKLY' )) - $daycnts[$m][$d]['weekno_down'] = ($daycnts[$m][$d]['weekno_up'] - $weekno - 1); - } - } - } - /* check interval */ - if( 1 < $recur['INTERVAL'] ) { - /* create interval index */ - $intervalix = $this->_recurIntervalIx( $recur['FREQ'], $wdate, $wkst ); - /* check interval */ - $currentKey = array_keys( $intervalarr ); - $currentKey = end( $currentKey ); // get last index - if( $currentKey != $intervalix ) - $intervalarr = array( $intervalix => ( $intervalarr[$currentKey] + 1 )); - if(( $recur['INTERVAL'] != $intervalarr[$intervalix] ) && - ( 0 != $intervalarr[$intervalix] )) { - /* step up date */ - //echo "skip: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."
    \n";//test - $this->_stepdate( $wdate, $wdatets, $step); - continue; - } - else // continue within the selected interval - $intervalarr[$intervalix] = 0; - //echo "cont: ".implode('-',$wdate)." ix=$intervalix old=$currentKey interval=".$intervalarr[$intervalix]."
    \n";//test - } - $updateOK = TRUE; - if( $updateOK && isset( $recur['BYMONTH'] )) - $updateOK = $this->_recurBYcntcheck( $recur['BYMONTH'] - , $wdate['month'] - ,($wdate['month'] - 13)); - if( $updateOK && isset( $recur['BYWEEKNO'] )) - $updateOK = $this->_recurBYcntcheck( $recur['BYWEEKNO'] - , $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] - , $daycnts[$wdate['month']][$wdate['day']]['weekno_down'] ); - if( $updateOK && isset( $recur['BYYEARDAY'] )) - $updateOK = $this->_recurBYcntcheck( $recur['BYYEARDAY'] - , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_up'] - , $daycnts[$wdate['month']][$wdate['day']]['yearcnt_down'] ); - if( $updateOK && isset( $recur['BYMONTHDAY'] )) - $updateOK = $this->_recurBYcntcheck( $recur['BYMONTHDAY'] - , $wdate['day'] - , $daycnts[$wdate['month']][$wdate['day']]['monthcnt_down'] ); - //echo "efter BYMONTHDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "
    \n";//test### - if( $updateOK && isset( $recur['BYDAY'] )) { - $updateOK = FALSE; - $m = $wdate['month']; - $d = $wdate['day']; - if( isset( $recur['BYDAY']['DAY'] )) { // single day, opt with year/month day order no - $daynoexists = $daynosw = $daynamesw = FALSE; - if( $recur['BYDAY']['DAY'] == $daycnts[$m][$d]['DAY'] ) - $daynamesw = TRUE; - if( isset( $recur['BYDAY'][0] )) { - $daynoexists = TRUE; - if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || isset( $recur['BYMONTH'] )) - $daynosw = $this->_recurBYcntcheck( $recur['BYDAY'][0] - , $daycnts[$m][$d]['monthdayno_up'] - , $daycnts[$m][$d]['monthdayno_down'] ); - elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' )) - $daynosw = $this->_recurBYcntcheck( $recur['BYDAY'][0] - , $daycnts[$m][$d]['yeardayno_up'] - , $daycnts[$m][$d]['yeardayno_down'] ); - } - if(( $daynoexists && $daynosw && $daynamesw ) || - ( !$daynoexists && !$daynosw && $daynamesw )) { - $updateOK = TRUE; - } - //echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw
    \n"; // test ### - } - else { - foreach( $recur['BYDAY'] as $bydayvalue ) { - $daynoexists = $daynosw = $daynamesw = FALSE; - if( isset( $bydayvalue['DAY'] ) && - ( $bydayvalue['DAY'] == $daycnts[$m][$d]['DAY'] )) - $daynamesw = TRUE; - if( isset( $bydayvalue[0] )) { - $daynoexists = TRUE; - if(( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'MONTHLY' )) || - isset( $recur['BYMONTH'] )) - $daynosw = $this->_recurBYcntcheck( $bydayvalue['0'] - , $daycnts[$m][$d]['monthdayno_up'] - , $daycnts[$m][$d]['monthdayno_down'] ); - elseif( isset( $recur['FREQ'] ) && ( $recur['FREQ'] == 'YEARLY' )) - $daynosw = $this->_recurBYcntcheck( $bydayvalue['0'] - , $daycnts[$m][$d]['yeardayno_up'] - , $daycnts[$m][$d]['yeardayno_down'] ); - } - //echo "daynoexists:$daynoexists daynosw:$daynosw daynamesw:$daynamesw
    \n"; // test ### - if(( $daynoexists && $daynosw && $daynamesw ) || - ( !$daynoexists && !$daynosw && $daynamesw )) { - $updateOK = TRUE; - break; - } - } - } - } - //echo "efter BYDAY: ".implode('-',$wdate).' status: '; echo ($updateOK) ? 'TRUE' : 'FALSE'; echo "
    \n"; // test ### - /* check BYSETPOS */ - if( $updateOK ) { - if( isset( $recur['BYSETPOS'] ) && - ( in_array( $recur['FREQ'], array( 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY' )))) { - if( isset( $recur['WEEKLY'] )) { - if( $bysetposWold == $daycnts[$wdate['month']][$wdate['day']]['weekno_up'] ) - $bysetposw1[] = $wdatets; - else - $bysetposw2[] = $wdatets; - } - else { - if(( isset( $recur['FREQ'] ) && ( 'YEARLY' == $recur['FREQ'] ) && - ( $bysetposYold == $wdate['year'] )) || - ( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] ) && - (( $bysetposYold == $wdate['year'] ) && - ( $bysetposMold == $wdate['month'] ))) || - ( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] ) && - (( $bysetposYold == $wdate['year'] ) && - ( $bysetposMold == $wdate['month']) && - ( $bysetposDold == $wdate['sday'] )))) - $bysetposymd1[] = $wdatets; - else - $bysetposymd2[] = $wdatets; - } - } - else { - /* update result array if BYSETPOS is set */ - $countcnt++; - if( $startdatets <= $wdatets ) { // only output within period - $result[$wdatets] = TRUE; - //echo "recur ".implode('-',$this->_date_time_string(date('Y-m-d H:i:s',$wdatets),6))."
    \n";//test - } - //else echo "recur undate ".implode('-',$this->_date_time_string(date('Y-m-d H:i:s',$wdatets),6))." okdatstart ".implode('-',$this->_date_time_string(date('Y-m-d H:i:s',$startdatets),6))."
    \n";//test - $updateOK = FALSE; - } - } - /* step up date */ - $this->_stepdate( $wdate, $wdatets, $step); - /* check if BYSETPOS is set for updating result array */ - if( $updateOK && isset( $recur['BYSETPOS'] )) { - $bysetpos = FALSE; - if( isset( $recur['FREQ'] ) && ( 'YEARLY' == $recur['FREQ'] ) && - ( $bysetposYold != $wdate['year'] )) { - $bysetpos = TRUE; - $bysetposYold = $wdate['year']; - } - elseif( isset( $recur['FREQ'] ) && ( 'MONTHLY' == $recur['FREQ'] && - (( $bysetposYold != $wdate['year'] ) || ( $bysetposMold != $wdate['month'] )))) { - $bysetpos = TRUE; - $bysetposYold = $wdate['year']; - $bysetposMold = $wdate['month']; - } - elseif( isset( $recur['FREQ'] ) && ( 'WEEKLY' == $recur['FREQ'] )) { - $weekno = (int) date( 'W', mktime( 0, 0, $wkst, $wdate['month'], $wdate['day'], $wdate['year'])); - if( $bysetposWold != $weekno ) { - $bysetposWold = $weekno; - $bysetpos = TRUE; - } - } - elseif( isset( $recur['FREQ'] ) && ( 'DAILY' == $recur['FREQ'] ) && - (( $bysetposYold != $wdate['year'] ) || - ( $bysetposMold != $wdate['month'] ) || - ( $bysetposDold != $wdate['sday'] ))) { - $bysetpos = TRUE; - $bysetposYold = $wdate['year']; - $bysetposMold = $wdate['month']; - $bysetposDold = $wdate['day']; - } - if( $bysetpos ) { - if( isset( $recur['BYWEEKNO'] )) { - $bysetposarr1 = & $bysetposw1; - $bysetposarr2 = & $bysetposw2; - } - else { - $bysetposarr1 = & $bysetposymd1; - $bysetposarr2 = & $bysetposymd2; - } - foreach( $recur['BYSETPOS'] as $ix ) { - if( 0 > $ix ) // both positive and negative BYSETPOS allowed - $ix = ( count( $bysetposarr1 ) + $ix + 1); - $ix--; - if( isset( $bysetposarr1[$ix] )) { - if( $startdatets <= $bysetposarr1[$ix] ) { // only output within period - $result[$bysetposarr1[$ix]] = TRUE; - //echo "recur ".implode('-',$this->_date_time_string(date('Y-m-d H:i:s',$bysetposarr1[$ix]),6))."
    \n";//test - } - $countcnt++; - } - if( isset( $recur['COUNT'] ) && ( $countcnt >= $recur['COUNT'] )) - break; - } - $bysetposarr1 = $bysetposarr2; - $bysetposarr2 = array(); - } - } - } - } - function _recurBYcntcheck( $BYvalue, $upValue, $downValue ) { - if( is_array( $BYvalue ) && - ( in_array( $upValue, $BYvalue ) || in_array( $downValue, $BYvalue ))) - return TRUE; - elseif(( $BYvalue == $upValue ) || ( $BYvalue == $downValue )) - return TRUE; - else - return FALSE; - } - function _recurIntervalIx( $freq, $date, $wkst ) { - /* create interval index */ - switch( $freq ) { - case 'YEARLY': - $intervalix = $date['year']; - break; - case 'MONTHLY': - $intervalix = $date['year'].'-'.$date['month']; - break; - case 'WEEKLY': - $wdatets = $this->_date2timestamp( $date ); - $intervalix = (int) date( 'W', ( $wdatets + $wkst )); - break; - case 'DAILY': - default: - $intervalix = $date['year'].'-'.$date['month'].'-'.$date['day']; - break; - } - return $intervalix; - } -/** - * convert input format for exrule and rrule to internal format - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-19 - * @param array $rexrule - * @return array - */ - function _setRexrule( $rexrule ) { - $input = array(); - if( empty( $rexrule )) - return $input; - foreach( $rexrule as $rexrulelabel => $rexrulevalue ) { - $rexrulelabel = strtoupper( $rexrulelabel ); - if( 'UNTIL' != $rexrulelabel ) - $input[$rexrulelabel] = $rexrulevalue; - else { - if( $this->_isArrayTimestampDate( $rexrulevalue )) // timestamp date - $input[$rexrulelabel] = $this->_timestamp2date( $rexrulevalue, 6 ); - elseif( $this->_isArrayDate( $rexrulevalue )) // date-time - $input[$rexrulelabel] = $this->_date_time_array( $rexrulevalue, 6 ); - elseif( 8 <= strlen( trim( $rexrulevalue ))) // ex. 2006-08-03 10:12:18 - $input[$rexrulelabel] = $this->_date_time_string( $rexrulevalue ); - if(( 3 < count( $input[$rexrulelabel] )) && !isset( $input[$rexrulelabel]['tz'] )) - $input[$rexrulelabel]['tz'] = 'Z'; - } - } - return $input; - } -/** - * convert format for input date to internal date with parameters - * - * @author Kjell-Inge Gustafsson - * @since 2.4.17 - 2008-10-31 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param array $params optional - * @param string $caller optional - * @return array - */ - function _setDate( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $tz=FALSE, $params=FALSE, $caller=null ) { - $input = $parno = null; - $localtime = (( 'dtstart' == $caller ) && in_array( $this->objName, array( 'vtimezone', 'standard', 'daylight' ))) ? TRUE : FALSE; - if( $this->_isArrayDate( $year )) { - if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); - $input['params'] = $this->_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); - if( isset( $input['params']['TZID'] )) { - $input['params']['VALUE'] = 'DATE-TIME'; - unset( $year['tz'] ); - } - $hitval = (( !empty( $year['tz'] ) || !empty( $year[6] ))) ? 7 : 6; - $parno = $this->_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval ); - $parno = $this->_existRem( $input['params'], 'VALUE', 'DATE', 3, count( $year ), $parno ); - $input['value'] = $this->_date_time_array( $year, $parno ); - } - elseif( $this->_isArrayTimestampDate( $year )) { - if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); - $input['params'] = $this->_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); - if( isset( $input['params']['TZID'] )) { - $input['params']['VALUE'] = 'DATE-TIME'; - unset( $year['tz'] ); - } - $parno = $this->_existRem( $input['params'], 'VALUE', 'DATE', 3 ); - $hitval = ( isset( $year['tz'] )) ? 7 : 6; - $parno = $this->_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno ); - $input['value'] = $this->_timestamp2date( $year, $parno ); - } - elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 - if( $localtime ) unset ( $month['VALUE'], $month['TZID'] ); - $input['params'] = $this->_setParams( $month, array( 'VALUE' => 'DATE-TIME' )); - if( isset( $input['params']['TZID'] )) { - $input['params']['VALUE'] = 'DATE-TIME'; - $parno = 6; - } - $parno = $this->_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7, $parno ); - $parno = $this->_existRem( $input['params'], 'VALUE', 'DATE', 3, $parno, $parno ); - $input['value'] = $this->_date_time_string( $year, $parno ); - } - else { - if( is_array( $params )) { - if( $localtime ) unset ( $params['VALUE'], $params['TZID'] ); - $input['params'] = $this->_setParams( $params, array( 'VALUE' => 'DATE-TIME' )); - } - elseif( is_array( $tz )) { - $input['params'] = $this->_setParams( $tz, array( 'VALUE' => 'DATE-TIME' )); - $tz = FALSE; - } - elseif( is_array( $hour )) { - $input['params'] = $this->_setParams( $hour, array( 'VALUE' => 'DATE-TIME' )); - $hour = $min = $sec = $tz = FALSE; - } - if( isset( $input['params']['TZID'] )) { - $tz = null; - $input['params']['VALUE'] = 'DATE-TIME'; - } - $parno = $this->_existRem( $input['params'], 'VALUE', 'DATE', 3 ); - $hitval = ( !empty( $tz )) ? 7 : 6; - $parno = $this->_existRem( $input['params'], 'VALUE', 'DATE-TIME', $hitval, $parno, $parno ); - $input['value'] = array( 'year' => $year, 'month' => $month, 'day' => $day ); - if( 3 != $parno ) { - $input['value']['hour'] = ( $hour ) ? $hour : '0'; - $input['value']['min'] = ( $min ) ? $min : '0'; - $input['value']['sec'] = ( $sec ) ? $sec : '0'; - if( !empty( $tz )) - $input['value']['tz'] = $tz; - } - } - if( 3 == $parno ) { - $input['params']['VALUE'] = 'DATE'; - unset( $input['value']['tz'] ); - unset( $input['params']['TZID'] ); - } - elseif( isset( $input['params']['TZID'] )) - unset( $input['value']['tz'] ); - if( $localtime ) unset( $input['value']['tz'], $input['params']['TZID'] ); - if( isset( $input['value']['tz'] )) - $input['value']['tz'] = (string) $input['value']['tz']; - if( !empty( $input['value']['tz'] ) && ( 'Z' != $input['value']['tz'] ) && - ( !$this->_isOffset( $input['value']['tz'] ))) - $input['params']['TZID'] = $input['value']['tz']; - return $input; - } -/** - * convert format for input date (UTC) to internal date with parameters - * - * @author Kjell-Inge Gustafsson - * @since 2.4.17 - 2008-10-31 - * @param mixed $year - * @param mixed $month optional - * @param int $day optional - * @param int $hour optional - * @param int $min optional - * @param int $sec optional - * @param array $params optional - * @return array - */ - function _setDate2( $year, $month=FALSE, $day=FALSE, $hour=FALSE, $min=FALSE, $sec=FALSE, $params=FALSE ) { - $input = null; - if( $this->_isArrayDate( $year )) { - $input['value'] = $this->_date_time_array( $year, 7 ); - $input['params'] = $this->_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) ); - } - elseif( $this->_isArrayTimestampDate( $year )) { - $input['value'] = $this->_timestamp2date( $year, 7 ); - $input['params'] = $this->_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) ); - } - elseif( 8 <= strlen( trim( $year ))) { // ex. 2006-08-03 10:12:18 - $input['value'] = $this->_date_time_string( $year, 7 ); - $input['params'] = $this->_setParams( $month, array( 'VALUE' => 'DATE-TIME' ) ); + break; + default: + $output .= $this->elementStart2.$this->valueInit; + return $this->_size75( $output ); + break; + } } - else { - $input['value'] = array( 'year' => $year - , 'month' => $month - , 'day' => $day - , 'hour' => $hour - , 'min' => $min - , 'sec' => $sec ); - $input['params'] = $this->_setParams( $params, array( 'VALUE' => 'DATE-TIME' )); - } - $parno = $this->_existRem( $input['params'], 'VALUE', 'DATE-TIME', 7 ); // remove default - if( !isset( $input['value']['hour'] )) - $input['value']['hour'] = 0; - if( !isset( $input['value']['min'] )) - $input['value']['min'] = 0; - if( !isset( $input['value']['sec'] )) - $input['value']['sec'] = 0; - if( !isset( $input['value']['tz'] ) || !$this->_isOffset( $input['value']['tz'] )) - $input['value']['tz'] = 'Z'; - return $input; - } -/** - * check index and set (an indexed) content in multiple value array - * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-11-06 - * @param array $valArr - * @param mixed $value - * @param array $params - * @param array $defaults - * @param int $index - * @return void - */ - function _setMval( & $valArr, $value, $params=FALSE, $defaults=FALSE, $index=FALSE ) { - if( !is_array( $valArr )) $valArr = array(); - if( $index ) - $index = $index - 1; - elseif( 0 < count( $valArr )) { - $index = end( array_keys( $valArr )); - $index += 1; + $output .= $this->elementStart2; + $output .= $this->valueInit.$content; + switch( $this->format ) { + case 'xcal': + return $output.$this->elementEnd1.$label.$this->elementEnd2; + break; + default: + return $this->_size75( $output ); + break; } - else - $index = 0; - $valArr[$index] = array( 'value' => $value, 'params' => $this->_setParams( $params, $defaults )); - ksort( $valArr ); } /** - * set input (formatted) parameters- component property attributes - * - * default parameters can be set, if missing + * creates formatted output for calendar component property parameters * - * @author Kjell-Inge Gustafsson - * @since 1.x.x - 2007-05-01 - * @param array $params - * @param array $defaults - * @return array + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.33 - 2010-12-18 + * @param array $params optional + * @param array $ctrKeys optional + * @return string */ - function _setParams( $params, $defaults=FALSE ) { - if( !is_array( $params)) + function _createParams( $params=array(), $ctrKeys=array() ) { + if( !is_array( $params ) || empty( $params )) $params = array(); - $input = array(); + $attrLANG = $attr1 = $attr2 = $lang = null; + $CNattrKey = ( in_array( 'CN', $ctrKeys )) ? TRUE : FALSE ; + $LANGattrKey = ( in_array( 'LANGUAGE', $ctrKeys )) ? TRUE : FALSE ; + $CNattrExist = $LANGattrExist = FALSE; + $xparams = array(); foreach( $params as $paramKey => $paramValue ) { - if( is_array( $paramValue )) { - foreach( $paramValue as $pkey => $pValue ) { - if(( '"' == substr( $pValue, 0, 1 )) && ( '"' == substr( $pValue, -1 ))) - $paramValue[$pkey] = substr( $pValue, 1, ( strlen( $pValue ) - 2 )); - } + if( ctype_digit( (string) $paramKey )) { + $xparams[] = $paramValue; + continue; } - elseif(( '"' == substr( $paramValue, 0, 1 )) && ( '"' == substr( $paramValue, -1 ))) - $paramValue = substr( $paramValue, 1, ( strlen( $paramValue ) - 2 )); - if( 'VALUE' == strtoupper( $paramKey )) - $input['VALUE'] = strtoupper( $paramValue ); + $paramKey = strtoupper( $paramKey ); + if( !in_array( $paramKey, array( 'ALTREP', 'CN', 'DIR', 'ENCODING', 'FMTTYPE', 'LANGUAGE', 'RANGE', 'RELTYPE', 'SENT-BY', 'TZID', 'VALUE' ))) + $xparams[$paramKey] = $paramValue; + else + $params[$paramKey] = $paramValue; + } + ksort( $xparams, SORT_STRING ); + foreach( $xparams as $paramKey => $paramValue ) { + if( ctype_digit( (string) $paramKey )) + $attr2 .= $this->intAttrDelimiter.$paramValue; else - $input[strtoupper( $paramKey )] = $paramValue; + $attr2 .= $this->intAttrDelimiter."$paramKey=$paramValue"; + } + if( isset( $params['FMTTYPE'] ) && !in_array( 'FMTTYPE', $ctrKeys )) { + $attr1 .= $this->intAttrDelimiter.'FMTTYPE='.$params['FMTTYPE'].$attr2; + $attr2 = null; } - if( is_array( $defaults )) { - foreach( $defaults as $paramKey => $paramValue ) { - if( !isset( $input[$paramKey] )) - $input[$paramKey] = $paramValue; + if( isset( $params['ENCODING'] ) && !in_array( 'ENCODING', $ctrKeys )) { + if( !empty( $attr2 )) { + $attr1 .= $attr2; + $attr2 = null; } + $attr1 .= $this->intAttrDelimiter.'ENCODING='.$params['ENCODING']; + } + if( isset( $params['VALUE'] ) && !in_array( 'VALUE', $ctrKeys )) + $attr1 .= $this->intAttrDelimiter.'VALUE='.$params['VALUE']; + if( isset( $params['TZID'] ) && !in_array( 'TZID', $ctrKeys )) + $attr1 .= $this->intAttrDelimiter.'TZID='.$params['TZID']; + if( isset( $params['RANGE'] ) && !in_array( 'RANGE', $ctrKeys )) + $attr1 .= $this->intAttrDelimiter.'RANGE='.$params['RANGE']; + if( isset( $params['RELTYPE'] ) && !in_array( 'RELTYPE', $ctrKeys )) + $attr1 .= $this->intAttrDelimiter.'RELTYPE='.$params['RELTYPE']; + if( isset( $params['CN'] ) && $CNattrKey ) { + $attr1 = $this->intAttrDelimiter.'CN="'.$params['CN'].'"'; + $CNattrExist = TRUE; + } + if( isset( $params['DIR'] ) && in_array( 'DIR', $ctrKeys )) + $attr1 .= $this->intAttrDelimiter.'DIR="'.$params['DIR'].'"'; + if( isset( $params['SENT-BY'] ) && in_array( 'SENT-BY', $ctrKeys )) + $attr1 .= $this->intAttrDelimiter.'SENT-BY="'.$params['SENT-BY'].'"'; + if( isset( $params['ALTREP'] ) && in_array( 'ALTREP', $ctrKeys )) + $attr1 .= $this->intAttrDelimiter.'ALTREP="'.$params['ALTREP'].'"'; + if( isset( $params['LANGUAGE'] ) && $LANGattrKey ) { + $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$params['LANGUAGE']; + $LANGattrExist = TRUE; } - return (0 < count( $input )) ? $input : null; - } -/** - * step date, return updated date, array and timpstamp - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-18 - * @param array $date, date to step - * @param int $timestamp - * @param array $step, default array( 'day' => 1 ) - * @return void - */ - function _stepdate( &$date, &$timestamp, $step=array( 'day' => 1 )) { - foreach( $step as $stepix => $stepvalue ) - $date[$stepix] += $stepvalue; - $timestamp = $this->_date2timestamp( $date ); - $date = $this->_timestamp2date( $timestamp, 6 ); - foreach( $date as $k => $v ) { - if( ctype_digit( $v )) - $date[$k] = (int) $v; + if( !$LANGattrExist ) { + $lang = $this->getConfig( 'language' ); + if(( $CNattrExist || $LANGattrKey ) && $lang ) + $attrLANG .= $this->intAttrDelimiter.'LANGUAGE='.$lang; } + return $attr1.$attrLANG.$attr2; } /** - * convert timestamp to date array + * creates formatted output for calendar component property data value type recur * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-11-01 - * @param mixed $timestamp - * @param int $parno - * @return array + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.4.8 - 2008-10-22 + * @param array $recurlabel + * @param array $recurdata + * @return string */ - function _timestamp2date( $timestamp, $parno=6 ) { - if( is_array( $timestamp )) { - if(( 7 == $parno ) && !empty( $timestamp['tz'] )) - $tz = $timestamp['tz']; - $timestamp = $timestamp['timestamp']; - } - $output = array( 'year' => date( 'Y', $timestamp ) - , 'month' => date( 'm', $timestamp ) - , 'day' => date( 'd', $timestamp )); - if( 3 != $parno ) { - $output['hour'] = date( 'H', $timestamp ); - $output['min'] = date( 'i', $timestamp ); - $output['sec'] = date( 's', $timestamp ); - if( isset( $tz )) - $output['tz'] = $tz; + function _format_recur( $recurlabel, $recurdata ) { + $output = null; + foreach( $recurdata as $therule ) { + if( empty( $therule['value'] )) { + if( $this->getConfig( 'allowEmpty' )) $output .= $this->_createElement( $recurlabel ); + continue; + } + $attributes = ( isset( $therule['params'] )) ? $this->_createParams( $therule['params'] ) : null; + $content1 = $content2 = null; + foreach( $therule['value'] as $rulelabel => $rulevalue ) { + switch( $rulelabel ) { + case 'FREQ': { + $content1 .= "FREQ=$rulevalue"; + break; + } + case 'UNTIL': { + $content2 .= ";UNTIL="; + $content2 .= iCalUtilityFunctions::_format_date_time( $rulevalue ); + break; + } + case 'COUNT': + case 'INTERVAL': + case 'WKST': { + $content2 .= ";$rulelabel=$rulevalue"; + break; + } + case 'BYSECOND': + case 'BYMINUTE': + case 'BYHOUR': + case 'BYMONTHDAY': + case 'BYYEARDAY': + case 'BYWEEKNO': + case 'BYMONTH': + case 'BYSETPOS': { + $content2 .= ";$rulelabel="; + if( is_array( $rulevalue )) { + foreach( $rulevalue as $vix => $valuePart ) { + $content2 .= ( $vix ) ? ',' : null; + $content2 .= $valuePart; + } + } + else + $content2 .= $rulevalue; + break; + } + case 'BYDAY': { + $content2 .= ";$rulelabel="; + $bydaycnt = 0; + foreach( $rulevalue as $vix => $valuePart ) { + $content21 = $content22 = null; + if( is_array( $valuePart )) { + $content2 .= ( $bydaycnt ) ? ',' : null; + foreach( $valuePart as $vix2 => $valuePart2 ) { + if( 'DAY' != strtoupper( $vix2 )) + $content21 .= $valuePart2; + else + $content22 .= $valuePart2; + } + $content2 .= $content21.$content22; + $bydaycnt++; + } + else { + $content2 .= ( $bydaycnt ) ? ',' : null; + if( 'DAY' != strtoupper( $vix )) + $content21 .= $valuePart; + else { + $content22 .= $valuePart; + $bydaycnt++; + } + $content2 .= $content21.$content22; + } + } + break; + } + default: { + $content2 .= ";$rulelabel=$rulevalue"; + break; + } + } + } + $output .= $this->_createElement( $recurlabel, $attributes, $content1.$content2 ); } return $output; } /** - * convert (numeric) local time offset to seconds correcting localtime to GMT - * - * @author Kjell-Inge Gustafsson - * @since 2.4.16 - 2008-10-19 - * @param string $offset - * @return integer - */ - function _tz2offset( $tz ) { - $tz = trim( (string) $tz ); - $offset = 0; - if((( 5 != strlen( $tz )) && ( 7 != strlen( $tz ))) || - (( '+' != substr( $tz, 0, 1 )) && ( '-' != substr( $tz, 0, 1 ))) || - (( '0000' >= substr( $tz, 1, 4 )) && ( '9999' < substr( $tz, 1, 4 ))) || - (( 7 == strlen( $tz )) && ( '00' > substr( $tz, 5, 2 )) && ( '99' < substr( $tz, 5, 2 )))) - return $offset; - $hours2sec = (int) substr( $tz, 1, 2 ) * 3600; - $min2sec = (int) substr( $tz, 3, 2 ) * 60; - $sec = ( 7 == strlen( $tz )) ? (int) substr( $tz, -2 ) : '00'; - $offset = $hours2sec + $min2sec + $sec; - $offset = ('-' == substr( $tz, 0, 1 )) ? $offset : -1 * $offset; - return $offset; + * check if property not exists within component + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.5.1 - 2008-10-15 + * @param string $propName + * @return bool + */ + function _notExistProp( $propName ) { + if( empty( $propName )) return FALSE; // when deleting x-prop, an empty propName may be used=allowed + $propName = strtolower( $propName ); + if( 'last-modified' == $propName ) { if( !isset( $this->lastmodified )) return TRUE; } + elseif( 'percent-complete' == $propName ) { if( !isset( $this->percentcomplete )) return TRUE; } + elseif( 'recurrence-id' == $propName ) { if( !isset( $this->recurrenceid )) return TRUE; } + elseif( 'related-to' == $propName ) { if( !isset( $this->relatedto )) return TRUE; } + elseif( 'request-status' == $propName ) { if( !isset( $this->requeststatus )) return TRUE; } + elseif(( 'x-' != substr($propName,0,2)) && !isset( $this->$propName )) return TRUE; + return FALSE; } /*********************************************************************************/ /*********************************************************************************/ /** * get general component config variables or info about subcomponents * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-11-02 - * @param string $config + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-14 + * @param mixed $config * @return value */ - function getConfig( $config ) { + function getConfig( $config = FALSE) { + if( !$config ) { + $return = array(); + $return['ALLOWEMPTY'] = $this->getConfig( 'ALLOWEMPTY' ); + $return['FORMAT'] = $this->getConfig( 'FORMAT' ); + if( FALSE !== ( $lang = $this->getConfig( 'LANGUAGE' ))) + $return['LANGUAGE'] = $lang; + $return['NEWLINECHAR'] = $this->getConfig( 'NEWLINECHAR' ); + $return['TZTD'] = $this->getConfig( 'TZID' ); + $return['UNIQUE_ID'] = $this->getConfig( 'UNIQUE_ID' ); + return $return; + } switch( strtoupper( $config )) { case 'ALLOWEMPTY': return $this->allowEmpty; @@ -5622,13 +4672,11 @@ function getConfig( $config ) { if( isset( $this->components )) { foreach( $this->components as $cix => $component ) { if( empty( $component )) continue; - unset( $component->propix ); $info[$cix]['ordno'] = $cix + 1; $info[$cix]['type'] = $component->objName; $info[$cix]['uid'] = $component->getProperty( 'uid' ); $info[$cix]['props'] = $component->getConfig( 'propinfo' ); $info[$cix]['sub'] = $component->getConfig( 'compsinfo' ); - unset( $component->propix ); } } return $info; @@ -5683,18 +4731,22 @@ function getConfig( $config ) { if( !empty( $this->requeststatus )) $output['REQUEST-STATUS'] = count( $this->requeststatus ); if( !empty( $this->resources )) $output['RESOURCES'] = count( $this->resources ); if( !empty( $this->sequence )) $output['SEQUENCE'] = 1; + if( !empty( $this->sequence )) $output['SEQUENCE'] = 1; if( !empty( $this->status )) $output['STATUS'] = 1; if( !empty( $this->transp )) $output['TRANSP'] = 1; if( !empty( $this->trigger )) $output['TRIGGER'] = 1; if( !empty( $this->tzid )) $output['TZID'] = 1; if( !empty( $this->tzname )) $output['TZNAME'] = count( $this->tzname ); - if( !empty( $this->tzoffsetfrom )) $output['TZOFFSETTFROM'] = 1; + if( !empty( $this->tzoffsetfrom )) $output['TZOFFSETFROM'] = 1; if( !empty( $this->tzoffsetto )) $output['TZOFFSETTO'] = 1; if( !empty( $this->tzurl )) $output['TZURL'] = 1; if( !empty( $this->url )) $output['URL'] = 1; if( !empty( $this->xprop )) $output['X-PROP'] = count( $this->xprop ); return $output; break; + case 'TZID': + return $this->dtzid; + break; case 'UNIQUE_ID': if( empty( $this->unique_id )) $this->unique_id = ( isset( $_SERVER['SERVER_NAME'] )) ? gethostbyname( $_SERVER['SERVER_NAME'] ) : 'localhost'; @@ -5705,13 +4757,21 @@ function getConfig( $config ) { /** * general component config setting * - * @author Kjell-Inge Gustafsson - * @since 2.4.8 - 2008-10-24 - * @param string $config + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.9.6 - 2011-05-14 + * @param mixed $config * @param string $value + * @param bool $softUpdate * @return void */ - function setConfig( $config, $value ) { + function setConfig( $config, $value = FALSE, $softUpdate = FALSE ) { + if( is_array( $config )) { + foreach( $config as $cKey => $cValue ) { + if( FALSE === $this->setConfig( $cKey, $cValue, $softUpdate )) + return FALSE; + } + return TRUE; + } $res = FALSE; switch( strtoupper( $config )) { case 'ALLOWEMPTY': @@ -5720,7 +4780,7 @@ function setConfig( $config, $value ) { $res = TRUE; break; case 'FORMAT': - $value = trim( $value ); + $value = trim( strtolower( $value )); $this->format = $value; $this->_createFormat(); $subcfg = array( 'FORMAT' => $value ); @@ -5729,7 +4789,8 @@ function setConfig( $config, $value ) { case 'LANGUAGE': // set language for calendar component as defined in [RFC 1766] $value = trim( $value ); - $this->language = $value; + if( empty( $this->language ) || !$softUpdate ) + $this->language = $value; $subcfg = array( 'LANGUAGE' => $value ); $res = TRUE; break; @@ -5739,21 +4800,28 @@ function setConfig( $config, $value ) { $subcfg = array( 'NL' => $value ); $res = TRUE; break; + case 'TZID': + $this->dtzid = $value; + $subcfg = array( 'TZID' => $value ); + $res = TRUE; + break; case 'UNIQUE_ID': $value = trim( $value ); $this->unique_id = $value; $subcfg = array( 'UNIQUE_ID' => $value ); $res = TRUE; break; + default: // any unvalid config key.. . + return TRUE; } if( !$res ) return FALSE; if( isset( $subcfg ) && !empty( $this->components )) { foreach( $subcfg as $cfgkey => $cfgvalue ) { foreach( $this->components as $cix => $component ) { - $res = $component->setConfig( $cfgkey, $cfgvalue ); + $res = $component->setConfig( $cfgkey, $cfgvalue, $softUpdate ); if( !$res ) break 2; - $this->components[$cix] = $component; // PHP4 compliant + $this->components[$cix] = $component->copy(); // PHP4 compliant } } } @@ -5763,19 +4831,19 @@ function setConfig( $config, $value ) { /** * delete component property value * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-11-14 - * @param string $propName - * @param int @propix, optional, if specific property is wanted in case of multiply occurences + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-03-15 + * @param mixed $propName, bool FALSE => X-property + * @param int $propix, optional, if specific property is wanted in case of multiply occurences * @return bool, if successfull delete TRUE */ - function deleteProperty( $propName, $propix=FALSE ) { + function deleteProperty( $propName=FALSE, $propix=FALSE ) { if( $this->_notExistProp( $propName )) return FALSE; $propName = strtoupper( $propName ); if( in_array( $propName, array( 'ATTACH', 'ATTENDEE', 'CATEGORIES', 'COMMENT', 'CONTACT', 'DESCRIPTION', 'EXDATE', 'EXRULE', 'FREEBUSY', 'RDATE', 'RELATED-TO', 'RESOURCES', 'RRULE', 'REQUEST-STATUS', 'TZNAME', 'X-PROP' ))) { if( !$propix ) - $propix = ( isset( $this->propdelix[$propName] )) ? $this->propdelix[$propName] + 2 : 1; + $propix = ( isset( $this->propdelix[$propName] ) && ( 'X-PROP' != $propName )) ? $this->propdelix[$propName] + 2 : 1; $this->propdelix[$propName] = --$propix; } $return = FALSE; @@ -5787,13 +4855,13 @@ function deleteProperty( $propName, $propix=FALSE ) { } break; case 'ATTACH': - return $this->deletePropertyM( $this->attach, $propix ); + return $this->deletePropertyM( $this->attach, $this->propdelix[$propName] ); break; case 'ATTENDEE': - return $this->deletePropertyM( $this->attendee, $propix ); + return $this->deletePropertyM( $this->attendee, $this->propdelix[$propName] ); break; case 'CATEGORIES': - return $this->deletePropertyM( $this->categories, $propix ); + return $this->deletePropertyM( $this->categories, $this->propdelix[$propName] ); break; case 'CLASS': if( !empty( $this->class )) { @@ -5802,7 +4870,7 @@ function deleteProperty( $propName, $propix=FALSE ) { } break; case 'COMMENT': - return $this->deletePropertyM( $this->comment, $propix ); + return $this->deletePropertyM( $this->comment, $this->propdelix[$propName] ); break; case 'COMPLETED': if( !empty( $this->completed )) { @@ -5811,7 +4879,7 @@ function deleteProperty( $propName, $propix=FALSE ) { } break; case 'CONTACT': - return $this->deletePropertyM( $this->contact, $propix ); + return $this->deletePropertyM( $this->contact, $this->propdelix[$propName] ); break; case 'CREATED': if( !empty( $this->created )) { @@ -5820,7 +4888,7 @@ function deleteProperty( $propName, $propix=FALSE ) { } break; case 'DESCRIPTION': - return $this->deletePropertyM( $this->description, $propix ); + return $this->deletePropertyM( $this->description, $this->propdelix[$propName] ); break; case 'DTEND': if( !empty( $this->dtend )) { @@ -5855,13 +4923,13 @@ function deleteProperty( $propName, $propix=FALSE ) { } break; case 'EXDATE': - return $this->deletePropertyM( $this->exdate, $propix ); + return $this->deletePropertyM( $this->exdate, $this->propdelix[$propName] ); break; case 'EXRULE': - return $this->deletePropertyM( $this->exrule, $propix ); + return $this->deletePropertyM( $this->exrule, $this->propdelix[$propName] ); break; case 'FREEBUSY': - return $this->deletePropertyM( $this->freebusy, $propix ); + return $this->deletePropertyM( $this->freebusy, $this->propdelix[$propName] ); break; case 'GEO': if( !empty( $this->geo )) { @@ -5900,7 +4968,7 @@ function deleteProperty( $propName, $propix=FALSE ) { } break; case 'RDATE': - return $this->deletePropertyM( $this->rdate, $propix ); + return $this->deletePropertyM( $this->rdate, $this->propdelix[$propName] ); break; case 'RECURRENCE-ID': if( !empty( $this->recurrenceid )) { @@ -5909,7 +4977,7 @@ function deleteProperty( $propName, $propix=FALSE ) { } break; case 'RELATED-TO': - return $this->deletePropertyM( $this->relatedto, $propix ); + return $this->deletePropertyM( $this->relatedto, $this->propdelix[$propName] ); break; case 'REPEAT': if( !empty( $this->repeat )) { @@ -5918,13 +4986,13 @@ function deleteProperty( $propName, $propix=FALSE ) { } break; case 'REQUEST-STATUS': - return $this->deletePropertyM( $this->requeststatus, $propix ); + return $this->deletePropertyM( $this->requeststatus, $this->propdelix[$propName] ); break; case 'RESOURCES': - return $this->deletePropertyM( $this->resources, $propix ); + return $this->deletePropertyM( $this->resources, $this->propdelix[$propName] ); break; case 'RRULE': - return $this->deletePropertyM( $this->rrule, $propix ); + return $this->deletePropertyM( $this->rrule, $this->propdelix[$propName] ); break; case 'SEQUENCE': if( !empty( $this->sequence )) { @@ -5963,7 +5031,7 @@ function deleteProperty( $propName, $propix=FALSE ) { } break; case 'TZNAME': - return $this->deletePropertyM( $this->tzname, $propix ); + return $this->deletePropertyM( $this->tzname, $this->propdelix[$propName] ); break; case 'TZOFFSETFROM': if( !empty( $this->tzoffsetfrom )) { @@ -6007,7 +5075,7 @@ function deleteProperty( $propName, $propix=FALSE ) { } } else { - if( count( $this->xprop ) <= $propix ) return FALSE; + if( count( $this->xprop ) <= $propix ) { unset( $this->propdelix[$propName] ); return FALSE; } $xpropno = 0; foreach( $this->xprop as $xpropkey => $xpropvalue ) { if( $propix != $xpropno ) @@ -6016,6 +5084,10 @@ function deleteProperty( $propName, $propix=FALSE ) { } } $this->xprop = $reduced; + if( empty( $this->xprop )) { + unset( $this->propdelix[$propName] ); + return FALSE; + } return TRUE; } return $return; @@ -6024,25 +5096,30 @@ function deleteProperty( $propName, $propix=FALSE ) { /** * delete component property value, fixing components with multiple occurencies * - * @author Kjell-Inge Gustafsson - * @since 2.4.5 - 2008-11-07 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-03-15 * @param array $multiprop, reference to a component property - * @param int @propix, default 0 + * @param int $propix, reference to removal counter * @return bool TRUE */ - function deletePropertyM( & $multiprop, $propix=0 ) { - if( !isset( $multiprop[$propix])) return FALSE; - unset( $multiprop[$propix] ); - if( empty( $multiprop )) $multiprop = ''; - return ( isset( $this->multiprop[$propix] )) ? FALSE : TRUE; + function deletePropertyM( & $multiprop, & $propix ) { + if( isset( $multiprop[$propix] )) + unset( $multiprop[$propix] ); + if( empty( $multiprop )) { + $multiprop = ''; + unset( $propix ); + return FALSE; + } + else + return TRUE; } /** * get component property value/params * * if property has multiply values, consequtive function calls are needed * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-11-02 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.1 - 2011-07-16 * @param string $propName, optional * @param int @propix, optional, if specific property is wanted in case of multiply occurences * @param bool $inclParam=FALSE @@ -6063,36 +5140,54 @@ function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specfor if( !empty( $this->action['value'] )) return ( $inclParam ) ? $this->action : $this->action['value']; break; case 'ATTACH': - if( !isset( $this->attach[$propix] )) return FALSE; + $ak = ( is_array( $this->attach )) ? array_keys( $this->attach ) : array(); + while( is_array( $this->attach ) && !isset( $this->attach[$propix] ) && ( 0 < count( $this->attach )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->attach[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->attach[$propix] : $this->attach[$propix]['value']; break; case 'ATTENDEE': - if( !isset( $this->attendee[$propix] )) return FALSE; + $ak = ( is_array( $this->attendee )) ? array_keys( $this->attendee ) : array(); + while( is_array( $this->attendee ) && !isset( $this->attendee[$propix] ) && ( 0 < count( $this->attendee )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->attendee[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->attendee[$propix] : $this->attendee[$propix]['value']; break; case 'CATEGORIES': - if( !isset( $this->categories[$propix] )) return FALSE; + $ak = ( is_array( $this->categories )) ? array_keys( $this->categories ) : array(); + while( is_array( $this->categories ) && !isset( $this->categories[$propix] ) && ( 0 < count( $this->categories )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->categories[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->categories[$propix] : $this->categories[$propix]['value']; break; case 'CLASS': if( !empty( $this->class['value'] )) return ( $inclParam ) ? $this->class : $this->class['value']; break; case 'COMMENT': - if( !isset( $this->comment[$propix] )) return FALSE; + $ak = ( is_array( $this->comment )) ? array_keys( $this->comment ) : array(); + while( is_array( $this->comment ) && !isset( $this->comment[$propix] ) && ( 0 < count( $this->comment )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->comment[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->comment[$propix] : $this->comment[$propix]['value']; break; case 'COMPLETED': if( !empty( $this->completed['value'] )) return ( $inclParam ) ? $this->completed : $this->completed['value']; break; case 'CONTACT': - if( !isset( $this->contact[$propix] )) return FALSE; + $ak = ( is_array( $this->contact )) ? array_keys( $this->contact ) : array(); + while( is_array( $this->contact ) && !isset( $this->contact[$propix] ) && ( 0 < count( $this->contact )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->contact[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->contact[$propix] : $this->contact[$propix]['value']; break; case 'CREATED': if( !empty( $this->created['value'] )) return ( $inclParam ) ? $this->created : $this->created['value']; break; case 'DESCRIPTION': - if( !isset( $this->description[$propix] )) return FALSE; + $ak = ( is_array( $this->description )) ? array_keys( $this->description ) : array(); + while( is_array( $this->description ) && !isset( $this->description[$propix] ) && ( 0 < count( $this->description )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->description[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->description[$propix] : $this->description[$propix]['value']; break; case 'DTEND': @@ -6113,19 +5208,28 @@ function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specfor break; case 'DURATION': if( !isset( $this->duration['value'] )) return FALSE; - $value = ( $specform ) ? $this->duration2date() : $this->duration['value']; + $value = ( $specform && isset( $this->dtstart['value'] ) && isset( $this->duration['value'] )) ? iCalUtilityFunctions::_duration2date( $this->dtstart['value'], $this->duration['value'] ) : $this->duration['value']; return ( $inclParam ) ? array( 'value' => $value, 'params' => $this->duration['params'] ) : $value; break; case 'EXDATE': - if( !isset( $this->exdate[$propix] )) return FALSE; + $ak = ( is_array( $this->exdate )) ? array_keys( $this->exdate ) : array(); + while( is_array( $this->exdate ) && !isset( $this->exdate[$propix] ) && ( 0 < count( $this->exdate )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->exdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->exdate[$propix] : $this->exdate[$propix]['value']; break; case 'EXRULE': - if( !isset( $this->exrule[$propix] )) return FALSE; + $ak = ( is_array( $this->exrule )) ? array_keys( $this->exrule ) : array(); + while( is_array( $this->exrule ) && !isset( $this->exrule[$propix] ) && ( 0 < count( $this->exrule )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->exrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->exrule[$propix] : $this->exrule[$propix]['value']; break; case 'FREEBUSY': - if( !isset( $this->freebusy[$propix] )) return FALSE; + $ak = ( is_array( $this->freebusy )) ? array_keys( $this->freebusy ) : array(); + while( is_array( $this->freebusy ) && !isset( $this->freebusy[$propix] ) && ( 0 < count( $this->freebusy )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->freebusy[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->freebusy[$propix] : $this->freebusy[$propix]['value']; break; case 'GEO': @@ -6141,39 +5245,54 @@ function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specfor if( !empty( $this->organizer['value'] )) return ( $inclParam ) ? $this->organizer : $this->organizer['value']; break; case 'PERCENT-COMPLETE': - if( !empty( $this->percentcomplete['value'] )) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value']; + if( !empty( $this->percentcomplete['value'] ) || ( isset( $this->percentcomplete['value'] ) && ( '0' == $this->percentcomplete['value'] ))) return ( $inclParam ) ? $this->percentcomplete : $this->percentcomplete['value']; break; case 'PRIORITY': - if( !empty( $this->priority['value'] )) return ( $inclParam ) ? $this->priority : $this->priority['value']; + if( !empty( $this->priority['value'] ) || ( isset( $this->priority['value'] ) && ('0' == $this->priority['value'] ))) return ( $inclParam ) ? $this->priority : $this->priority['value']; break; case 'RDATE': - if( !isset( $this->rdate[$propix] )) return FALSE; + $ak = ( is_array( $this->rdate )) ? array_keys( $this->rdate ) : array(); + while( is_array( $this->rdate ) && !isset( $this->rdate[$propix] ) && ( 0 < count( $this->rdate )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->rdate[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->rdate[$propix] : $this->rdate[$propix]['value']; break; case 'RECURRENCE-ID': if( !empty( $this->recurrenceid['value'] )) return ( $inclParam ) ? $this->recurrenceid : $this->recurrenceid['value']; break; case 'RELATED-TO': - if( !isset( $this->relatedto[$propix] )) return FALSE; + $ak = ( is_array( $this->relatedto )) ? array_keys( $this->relatedto ) : array(); + while( is_array( $this->relatedto ) && !isset( $this->relatedto[$propix] ) && ( 0 < count( $this->relatedto )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->relatedto[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->relatedto[$propix] : $this->relatedto[$propix]['value']; break; case 'REPEAT': - if( !empty( $this->repeat['value'] )) return ( $inclParam ) ? $this->repeat : $this->repeat['value']; + if( !empty( $this->repeat['value'] ) || ( isset( $this->repeat['value'] ) && ( '0' == $this->repeat['value'] ))) return ( $inclParam ) ? $this->repeat : $this->repeat['value']; break; case 'REQUEST-STATUS': - if( !isset( $this->requeststatus[$propix] )) return FALSE; + $ak = ( is_array( $this->requeststatus )) ? array_keys( $this->requeststatus ) : array(); + while( is_array( $this->requeststatus ) && !isset( $this->requeststatus[$propix] ) && ( 0 < count( $this->requeststatus )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->requeststatus[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->requeststatus[$propix] : $this->requeststatus[$propix]['value']; break; case 'RESOURCES': - if( !isset( $this->resources[$propix] )) return FALSE; + $ak = ( is_array( $this->resources )) ? array_keys( $this->resources ) : array(); + while( is_array( $this->resources ) && !isset( $this->resources[$propix] ) && ( 0 < count( $this->resources )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->resources[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->resources[$propix] : $this->resources[$propix]['value']; break; case 'RRULE': - if( !isset( $this->rrule[$propix] )) return FALSE; + $ak = ( is_array( $this->rrule )) ? array_keys( $this->rrule ) : array(); + while( is_array( $this->rrule ) && !isset( $this->rrule[$propix] ) && ( 0 < count( $this->rrule )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->rrule[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->rrule[$propix] : $this->rrule[$propix]['value']; break; case 'SEQUENCE': - if( !empty( $this->sequence['value'] )) return ( $inclParam ) ? $this->sequence : $this->sequence['value']; + if( isset( $this->sequence['value'] ) && ( isset( $this->sequence['value'] ) && ( '0' <= $this->sequence['value'] ))) return ( $inclParam ) ? $this->sequence : $this->sequence['value']; break; case 'STATUS': if( !empty( $this->status['value'] )) return ( $inclParam ) ? $this->status : $this->status['value']; @@ -6191,7 +5310,10 @@ function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specfor if( !empty( $this->tzid['value'] )) return ( $inclParam ) ? $this->tzid : $this->tzid['value']; break; case 'TZNAME': - if( !isset( $this->tzname[$propix] )) return FALSE; + $ak = ( is_array( $this->tzname )) ? array_keys( $this->tzname ) : array(); + while( is_array( $this->tzname ) && !isset( $this->tzname[$propix] ) && ( 0 < count( $this->tzname )) && ( $propix < end( $ak ))) + $propix++; + if( !isset( $this->tzname[$propix] )) { unset( $this->propix[$propName] ); return FALSE; } return ( $inclParam ) ? $this->tzname[$propix] : $this->tzname[$propix]['value']; break; case 'TZOFFSETFROM': @@ -6234,10 +5356,70 @@ function getProperty( $propName=FALSE, $propix=FALSE, $inclParam=FALSE, $specfor } return FALSE; } +/** + * returns calendar property unique values for 'CATEGORIES', 'RESOURCES' or 'ATTENDEE' and each number of ocurrence + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-04-13 + * @param string $propName + * @param array $output, incremented result array + */ + function _getProperties( $propName, & $output ) { + if( !in_array( strtoupper( $propName ), array( 'ATTENDEE', 'CATEGORIES', 'RESOURCES' ))) + return output; + while( FALSE !== ( $content = $this->getProperty( $propName ))) { + if( is_array( $content )) { + foreach( $content as $part ) { + if( FALSE !== strpos( $part, ',' )) { + $part = explode( ',', $part ); + foreach( $part as $thePart ) { + $thePart = trim( $thePart ); + if( !empty( $thePart )) { + if( !isset( $output[$thePart] )) + $output[$thePart] = 1; + else + $output[$thePart] += 1; + } + } + } + else { + $part = trim( $part ); + if( !isset( $output[$part] )) + $output[$part] = 1; + else + $output[$part] += 1; + } + } + } + elseif( FALSE !== strpos( $content, ',' )) { + $content = explode( ',', $content ); + foreach( $content as $thePart ) { + $thePart = trim( $thePart ); + if( !empty( $thePart )) { + if( !isset( $output[$thePart] )) + $output[$thePart] = 1; + else + $output[$thePart] += 1; + } + } + } + else { + $content = trim( $content ); + if( !empty( $content )) { + if( !isset( $output[$content] )) + $output[$content] = 1; + else + $output[$content] += 1; + } + } + } + ksort( $output ); + return $output; + } /** * general component property setting * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-05 * @param mixed $args variable number of function arguments, * first argument is ALWAYS component name, @@ -6348,40 +5530,75 @@ function setProperty() { } return FALSE; } + + + //property inserted by Mauro Bieg + public $unparsedRrule; + + /*********************************************************************************/ /** * parse component unparsed data into properties * - * @author Kjell-Inge Gustafsson - * @since 2.5.2 - 2008-10-23 - * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of property strings + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.2 - 2011-07-17 + * @param mixed $unparsedtext, optional, strict rfc2445 formatted, single property string or array of strings * @return bool FALSE if error occurs during parsing * */ function parse( $unparsedtext=null ) { - if( $unparsedtext ) { - $this->unparsed = array(); - if( is_array( $unparsedtext )) { - $comp = & $this; - foreach ( $unparsedtext as $line ) { - if( 'END:VALARM' == strtoupper( substr( $line, 0, 10 ))) { - $this->setComponent( $comp ); - $comp = & $this; - continue; - } - elseif( 'BEGIN:VALARM' == strtoupper( substr( $line, 0, 12 ))) { - $comp = new valarm(); - continue; - } - else - $comp->unparsed[] = $line; + if( !empty( $unparsedtext )) { + $nl = $this->getConfig( 'nl' ); + if( is_array( $unparsedtext )) + $unparsedtext = implode( '\n'.$nl, $unparsedtext ); + /* fix line folding */ + $eolchars = array( "\r\n", "\n\r", "\n", "\r" ); // check all line endings + $EOLmark = FALSE; + foreach( $eolchars as $eolchar ) { + if( !$EOLmark && ( FALSE !== strpos( $unparsedtext, $eolchar ))) { + $unparsedtext = str_replace( $eolchar." ", '', $unparsedtext ); + $unparsedtext = str_replace( $eolchar."\t", '', $unparsedtext ); + if( $eolchar != $nl ) + $unparsedtext = str_replace( $eolchar, $nl, $unparsedtext ); + $EOLmark = TRUE; } } - else - $this->unparsed = array( trim( $unparsedtext )); + $tmp = explode( $nl, $unparsedtext ); + $unparsedtext = array(); + foreach( $tmp as $tmpr ) + if( !empty( $tmpr )) + $unparsedtext[] = $tmpr; } elseif( !isset( $this->unparsed )) - $this->unparsed = array(); + $unparsedtext = array(); + else + $unparsedtext = $this->unparsed; + $this->unparsed = array(); + $comp = & $this; + $config = $this->getConfig(); + foreach ( $unparsedtext as $line ) { + // echo $comp->objName.": $line
    "; // test ### + if( in_array( strtoupper( substr( $line, 0, 6 )), array( 'END:VA', 'END:DA' ))) + $this->components[] = $comp->copy(); + elseif( 'END:ST' == strtoupper( substr( $line, 0, 6 ))) + array_unshift( $this->components, $comp->copy()); + elseif( 'END:' == strtoupper( substr( $line, 0, 4 ))) + break; + elseif( 'BEGIN:VALARM' == strtoupper( substr( $line, 0, 12 ))) + $comp = new valarm( $config); + elseif( 'BEGIN:STANDARD' == strtoupper( substr( $line, 0, 14 ))) + $comp = new vtimezone( 'standard', $config ); + elseif( 'BEGIN:DAYLIGHT' == strtoupper( substr( $line, 0, 14 ))) + $comp = new vtimezone( 'daylight', $config ); + elseif( 'BEGIN:' == strtoupper( substr( $line, 0, 6 ))) + continue; + else { + $comp->unparsed[] = $line; +// echo $comp->objName.": $line
    \n"; // test ### + } + } + unset( $config ); +// echo $this->objName.'
    '.var_export( $this->unparsed, TRUE )."
    \n"; // test ### /* concatenate property values spread over several lines */ $lastix = -1; $propnames = array( 'action', 'attach', 'attendee', 'categories', 'comment', 'completed' @@ -6402,32 +5619,28 @@ function parse( $unparsedtext=null ) { } } if( $newProp ) { + if( -1 < $lastix ) + $proprows[$lastix] = $proprows[$lastix]; $newProp = FALSE; $lastix++; $proprows[$lastix] = $line; } - else { - /* remove line breaks */ - if(( '\n' == substr( $proprows[$lastix], -2 )) && - ( ' ' == substr( $line, 0, 1 ))) { - $proprows[$lastix] = substr( $proprows[$lastix], 0, strlen( $proprows[$lastix] ) - 2 ); - $line = substr( $line, 1 ); - } - $proprows[$lastix] .= $line; - } + else + $proprows[$lastix] .= '!"#¤%&/()=?'.$line; } /* parse each property 'line' */ foreach( $proprows as $line ) { - $line = str_replace( "\n ", '', $line ); + $line = str_replace( '!"#¤%&/()=? ', '', $line ); + $line = str_replace( '!"#¤%&/()=?', '', $line ); if( '\n' == substr( $line, -2 )) $line = substr( $line, 0, strlen( $line ) - 2 ); /* get propname, (problem with x-properties, otherwise in previous loop) */ $cix = $propname = null; - for( $cix=0; $cix < strlen( $line ); $cix++ ) { - if( in_array( $line{$cix}, array( ':', ';' ))) + for( $cix=0, $clen = strlen( $line ); $cix < $clen; $cix++ ) { + if( in_array( $line[$cix], array( ':', ';' ))) break; else { - $propname .= $line{$cix}; + $propname .= $line[$cix]; } } if(( 'x-' == substr( $propname, 0, 2 )) || ( 'X-' == substr( $propname, 0, 2 ))) { @@ -6439,13 +5652,15 @@ function parse( $unparsedtext=null ) { /* separate attributes from value */ $attr = array(); $attrix = -1; - $strlen = strlen( $line ); - for( $cix=0; $cix < $strlen; $cix++ ) { - if(( ':' == $line{$cix} ) && + $clen = strlen( $line ); + for( $cix=0; $cix < $clen; $cix++ ) { + if(( ':' == $line[$cix] ) && ( '://' != substr( $line, $cix, 3 )) && + ( !in_array( strtolower( substr( $line, $cix - 3, 4 )), array( 'fax:', 'cid:', 'sms:', 'tel:', 'urn:' ))) && + ( !in_array( strtolower( substr( $line, $cix - 4, 5 )), array( 'crid:', 'news:', 'pres:' ))) && ( 'mailto:' != strtolower( substr( $line, $cix - 6, 7 )))) { $attrEnd = TRUE; - if(( $cix < ( $strlen - 4 )) && + if(( $cix < ( $clen - 4 )) && ctype_digit( substr( $line, $cix+1, 4 ))) { // an URI with a (4pos) portnr?? for( $c2ix = $cix; 3 < $c2ix; $c2ix-- ) { if( '://' == substr( $line, $c2ix - 2, 3 )) { @@ -6459,10 +5674,10 @@ function parse( $unparsedtext=null ) { break; } } - if( ';' == $line{$cix} ) + if( ';' == $line[$cix] ) $attr[++$attrix] = null; else - $attr[$attrix] .= $line{$cix}; + $attr[$attrix] .= $line[$cix]; } /* make attributes in array format */ $propattr = array(); @@ -6474,7 +5689,7 @@ function parse( $unparsedtext=null ) { $propattr[] = $attribute; } /* call setProperty( $propname.. . */ - switch( $propname ) { + switch( strtoupper( $propname )) { case 'ATTENDEE': foreach( $propattr as $pix => $attr ) { $attr2 = explode( ',', $attr ); @@ -6498,7 +5713,7 @@ function parse( $unparsedtext=null ) { if( 1 < count( $content )) { $content = array_values( $content ); foreach( $content as $cix => $contentPart ) - $content[$cix] = $this->_strunrep( $contentPart ); + $content[$cix] = calendarComponent::_strunrep( $contentPart ); $this->setProperty( $propname, $content, $propattr ); break; } @@ -6514,13 +5729,13 @@ function parse( $unparsedtext=null ) { case 'SUMMARY': if( empty( $line )) $propattr = null; - $this->setProperty( $propname, $this->_strunrep( $line ), $propattr ); + $this->setProperty( $propname, calendarComponent::_strunrep( $line ), $propattr ); unset( $propname2 ); break; case 'REQUEST-STATUS': $values = explode( ';', $line, 3 ); - $values[1] = ( !isset( $values[1] )) ? null : $this->_strunrep( $values[1] ); - $values[2] = ( !isset( $values[2] )) ? null : $this->_strunrep( $values[2] ); + $values[1] = ( !isset( $values[1] )) ? null : calendarComponent::_strunrep( $values[1] ); + $values[2] = ( !isset( $values[2] )) ? null : calendarComponent::_strunrep( $values[2] ); $this->setProperty( $propname , $values[0] // statcode , $values[1] // statdesc @@ -6563,6 +5778,8 @@ function parse( $unparsedtext=null ) { break; case 'EXRULE': case 'RRULE': + //inserted by Mauro Bieg + $this->unparsedRrule = $line; $values = explode( ';', $line ); $recur = array(); foreach( $values as $value2 ) { @@ -6620,32 +5837,43 @@ function parse( $unparsedtext=null ) { } // end - foreach( $values.. . $this->setProperty( $propname, $recur, $propattr ); break; + case 'ACTION': + case 'CLASSIFICATION': + case 'STATUS': + case 'TRANSP': + case 'UID': + case 'TZID': + case 'RELATED-TO': + case 'TZNAME': + $line = calendarComponent::_strunrep( $line ); default: $this->setProperty( $propname, $line, $propattr ); break; } // end switch( $propname.. . } // end - foreach( $proprows.. . - unset( $this->unparsed, $proprows ); + unset( $unparsedtext, $this->unparsed, $proprows ); if( isset( $this->components ) && is_array( $this->components ) && ( 0 < count( $this->components ))) { - for( $six = 0; $six < count( $this->components ); $six++ ) { - if( !empty( $this->components[$six]->unparsed )) - $this->components[$six]->parse(); + $ckeys = array_keys( $this->components ); + foreach( $ckeys as $ckey ) { + if( !empty( $this->components[$ckey] ) && !empty( $this->components[$ckey]->unparsed )) { + $this->components[$ckey]->parse(); + } } } + return TRUE; } /*********************************************************************************/ /*********************************************************************************/ /** * return a copy of this component * - * @author Kjell-Inge Gustafsson - * @since 2.2.16 - 2007-11-07 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-03-15 * @return object */ function copy() { - $serialized_contents = serialize($this); - $copy = unserialize($serialized_contents); - unset( $copy->propix ); + $serialized_contents = serialize( $this ); + $copy = unserialize( $serialized_contents ); return $copy; } /*********************************************************************************/ @@ -6653,8 +5881,8 @@ function copy() { /** * delete calendar subcomponent from component container * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-10-15 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-03-15 * @param mixed $arg1 ordno / component type / component uid * @param mixed $arg2 optional, ordno if arg1 = component type * @return void @@ -6673,7 +5901,6 @@ function deleteComponent( $arg1, $arg2=FALSE ) { $cix2dC = 0; foreach ( $this->components as $cix => $component) { if( empty( $component )) continue; - unset( $component->propix ); if(( 'INDEX' == $argType ) && ( $index == $cix )) { unset( $this->components[$cix] ); return TRUE; @@ -6695,8 +5922,8 @@ function deleteComponent( $arg1, $arg2=FALSE ) { /** * get calendar component subcomponent from component container * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-10-15 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-03-15 * @param mixed $arg1 optional, ordno/component type/ component uid * @param mixed $arg2 optional, ordno if arg1 = component type * @return object @@ -6718,8 +5945,7 @@ function getComponent ( $arg1=FALSE, $arg2=FALSE ) { unset( $this->compix['INDEX'] ); $argType = strtolower( $arg1 ); if( !$arg2 ) - $index = $this->compix[$argType] = - ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1; + $index = $this->compix[$argType] = ( isset( $this->compix[$argType] )) ? $this->compix[$argType] + 1 : 1; else $index = (int) $arg2; } @@ -6730,7 +5956,6 @@ function getComponent ( $arg1=FALSE, $arg2=FALSE ) { $cix2gC = 0; foreach( $this->components as $cix => $component ) { if( empty( $component )) continue; - unset( $component->propix ); if(( 'INDEX' == $argType ) && ( $index == $cix )) return $component->copy(); elseif( $argType == $component->objName ) { @@ -6738,10 +5963,8 @@ function getComponent ( $arg1=FALSE, $arg2=FALSE ) { return $component->copy(); $cix2gC++; } - elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' ))) { - unset( $component->propix ); + elseif( !$argType && ( $arg1 == $component->getProperty( 'uid' ))) return $component->copy(); - } } /* not found.. . */ unset( $this->compix ); @@ -6750,7 +5973,7 @@ function getComponent ( $arg1=FALSE, $arg2=FALSE ) { /** * add calendar component as subcomponent to container for subcomponents * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 1.x.x - 2007-04-24 * @param object $component calendar component * @return void @@ -6758,11 +5981,40 @@ function getComponent ( $arg1=FALSE, $arg2=FALSE ) { function addSubComponent ( $component ) { $this->setComponent( $component ); } +/** + * create new calendar component subcomponent, already included within component + * + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.33 - 2011-01-03 + * @param string $compType subcomponent type + * @return object (reference) + */ + function & newComponent( $compType ) { + $config = $this->getConfig(); + $keys = array_keys( $this->components ); + $ix = end( $keys) + 1; + switch( strtoupper( $compType )) { + case 'ALARM': + case 'VALARM': + $this->components[$ix] = new valarm( $config ); + break; + case 'STANDARD': + array_unshift( $this->components, new vtimezone( 'STANDARD', $config )); + $ix = 0; + break; + case 'DAYLIGHT': + $this->components[$ix] = new vtimezone( 'DAYLIGHT', $config ); + break; + default: + return FALSE; + } + return $this->components[$ix]; + } /** * add calendar component as subcomponent to container for subcomponents * - * @author Kjell-Inge Gustafsson - * @since 2.4.13 - 2008-09-24 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.8 - 2011-03-15 * @param object $component calendar component * @param mixed $arg1 optional, ordno/component type/ component uid * @param mixed $arg2 optional, ordno if arg1 = component type @@ -6770,72 +6022,66 @@ function addSubComponent ( $component ) { */ function setComponent( $component, $arg1=FALSE, $arg2=FALSE ) { if( !isset( $this->components )) return FALSE; - if( '' >= $component->getConfig( 'language')) - $component->setConfig( 'language', $this->getConfig( 'language' )); - $component->setConfig( 'allowEmpty', $this->getConfig( 'allowEmpty' )); - $component->setConfig( 'nl', $this->getConfig( 'nl' )); - $component->setConfig( 'unique_id', $this->getConfig( 'unique_id' )); - $component->setConfig( 'format', $this->getConfig( 'format' )); + $component->setConfig( $this->getConfig(), FALSE, TRUE ); if( !in_array( $component->objName, array( 'valarm', 'vtimezone', 'standard', 'daylight' ))) { - unset( $component->propix ); /* make sure dtstamp and uid is set */ $dummy = $component->getProperty( 'dtstamp' ); $dummy = $component->getProperty( 'uid' ); } - if( !$arg1 ) { + if( !$arg1 ) { // plain insert, last in chain $this->components[] = $component->copy(); return TRUE; } $argType = $index = null; - if ( ctype_digit( (string) $arg1 )) { + if ( ctype_digit( (string) $arg1 )) { // index insert/replace $argType = 'INDEX'; $index = (int) $arg1 - 1; } - elseif(( strlen( $arg1 ) <= strlen( 'vfreebusy' )) && ( FALSE === strpos( $arg1, '@' ))) { + elseif( in_array( strtolower( $arg1 ), array( 'vevent', 'vtodo', 'vjournal', 'vfreebusy', 'valarm', 'vtimezone' ))) { $argType = strtolower( $arg1 ); $index = ( ctype_digit( (string) $arg2 )) ? ((int) $arg2) - 1 : 0; } + // else if arg1 is set, arg1 must be an UID $cix2sC = 0; foreach ( $this->components as $cix => $component2 ) { if( empty( $component2 )) continue; - unset( $component2->propix ); - if(( 'INDEX' == $argType ) && ( $index == $cix )) { + if(( 'INDEX' == $argType ) && ( $index == $cix )) { // index insert/replace $this->components[$cix] = $component->copy(); return TRUE; } - elseif( $argType == $component2->objName ) { + elseif( $argType == $component2->objName ) { // component Type index insert/replace if( $index == $cix2sC ) { $this->components[$cix] = $component->copy(); return TRUE; } $cix2sC++; } - elseif( !$argType && ($arg1 == $component2->getProperty( 'uid' ))) { + elseif( !$argType && ( $arg1 == $component2->getProperty( 'uid' ))) { // UID insert/replace $this->components[$cix] = $component->copy(); return TRUE; } } - /* not found.. . insert anyway.. .*/ + /* arg1=index and not found.. . insert at index .. .*/ + if( 'INDEX' == $argType ) { + $this->components[$index] = $component->copy(); + ksort( $this->components, SORT_NUMERIC ); + } + else /* not found.. . insert last in chain anyway .. .*/ $this->components[] = $component->copy(); return TRUE; } /** * creates formatted output for subcomponents * - * @author Kjell-Inge Gustafsson - * @since 2.4.10 - 2008-08-06 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.27 - 2010-12-12 * @return string */ function createSubComponent() { $output = null; foreach( $this->components as $component ) { if( empty( $component )) continue; - if( '' >= $component->getConfig( 'language')) - $component->setConfig( 'language', $this->getConfig( 'language' )); - $component->setConfig( 'allowEmpty', $this->getConfig( 'allowEmpty' )); - $component->setConfig( 'nl', $this->getConfig( 'nl' )); - $component->setConfig( 'unique_id', $this->getConfig( 'unique_id' )); - $component->setConfig( 'format', $this->getConfig( 'format' )); + $component->setConfig( $this->getConfig(), FALSE, TRUE ); $output .= $component->createComponent( $this->xcaldecl ); } return $output; @@ -6858,34 +6104,85 @@ function createSubComponent() { * folding of lines, causing ambiguity in the return string. * Fix uses var $breakAtChar=75 and breaks the line at $breakAtChar-1 if need be. * - * @author Kjell-Inge Gustafsson - * @since 2.2.8 - 2006-09-03 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.10.11 - 2011-09-01 * @param string $value * @return string */ function _size75( $string ) { - $strlen = strlen( $string ); - $tmp = $string; - $string = null; - while( $strlen > 75 ) { - $breakAtChar = 75; - if( substr( $tmp, ( $breakAtChar - 1 ), strlen( '\n' )) == '\n' ) - $breakAtChar = $breakAtChar - 1; - $string .= substr( $tmp, 0, $breakAtChar ); - $string .= $this->nl; - $tmp = ' '.substr( $tmp, $breakAtChar ); - $strlen = strlen( $tmp ); - } // while - $string .= rtrim( $tmp ); // the rest - if( $this->nl != substr( $string, ( 0 - strlen( $this->nl )))) - $string .= $this->nl; + $tmp = $string; + $string = ''; + $eolcharlen = strlen( '\n' ); + /* if PHP is config with mb_string and conf overload.. . */ + if( defined( 'MB_OVERLOAD_STRING' ) && ( 1 < ini_get( 'mbstring.func_overload' ))) { + $strlen = mb_strlen( $tmp ); + while( $strlen > 75 ) { + if( '\n' == mb_substr( $tmp, 75, $eolcharlen )) + $breakAtChar = 74; + else + $breakAtChar = 75; + $string .= mb_substr( $tmp, 0, $breakAtChar ); + if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl )))) + $string .= $this->nl; + $tmp = mb_substr( $tmp, $breakAtChar ); + if( !empty( $tmp )) + $tmp = ' '.$tmp; + $strlen = mb_strlen( $tmp ); + } // end while + if( 0 < $strlen ) { + $string .= $tmp; // the rest + if( $this->nl != mb_substr( $string, ( 0 - mb_strlen( $this->nl )))) + $string .= $this->nl; + } + return $string; + } + /* if PHP is not config with mb_string.. . */ + while( TRUE ) { + $bytecnt = strlen( $tmp ); + $charCnt = $ix = 0; + for( $ix = 0; $ix < $bytecnt; $ix++ ) { + if(( 73 < $charCnt ) && ( '\n' == substr( $tmp, $ix, $eolcharlen ))) + break; // break before '\n' + elseif( 74 < $charCnt ) { + if( '\n' == substr( $tmp, $ix, $eolcharlen )) + $ix -= 1; // don't break inside '\n' + break; // always break while-loop here + } + else { + $byte = ord( $tmp[$ix] ); + if ($byte <= 127) { // add a one byte character + $string .= substr( $tmp, $ix, 1 ); + $charCnt += 1; + } + else if ($byte >= 194 && $byte <= 223) { // start byte in two byte character + $string .= substr( $tmp, $ix, 2 ); // add a two bytes character + $charCnt += 1; + } + else if ($byte >= 224 && $byte <= 239) { // start byte in three bytes character + $string .= substr( $tmp, $ix, 3 ); // add a three bytes character + $charCnt += 1; + } + else if ($byte >= 240 && $byte <= 244) { // start byte in four bytes character + $string .= substr( $tmp, $ix, 4 ); // add a four bytes character + $charCnt += 1; + } + } + } // end for + if( $this->nl != substr( $string, ( 0 - strlen( $this->nl )))) + $string .= $this->nl; + $tmp = substr( $tmp, $ix ); + if( empty( $tmp )) + break; // while-loop breakes here + else + $tmp = ' '.$tmp; + } // end while return $string; } /** * special characters management output * - * @author Kjell-Inge Gustafsson - * @since 2.3.3 - 2007-12-20 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.15 - 2010-09-24 * @param string $string * @return string */ @@ -6897,11 +6194,12 @@ function _strrep( $string ) { break; default: $pos = 0; + $specChars = array( 'n', 'N', 'r', ',', ';' ); while( $pos <= strlen( $string )) { $pos = strpos( $string, "\\", $pos ); if( FALSE === $pos ) break; - if( !in_array( $string{($pos + 1)}, array( 'n', 'N', 'r', ',', ';' ))) { + if( !in_array( substr( $string, $pos, 1 ), $specChars )) { $string = substr( $string, 0, $pos )."\\".substr( $string, ( $pos + 1 )); $pos += 1; } @@ -6913,10 +6211,15 @@ function _strrep( $string ) { $string = str_replace(',', '\,', $string); if( FALSE !== strpos( $string, ';' )) $string = str_replace(';', '\;', $string); + if( FALSE !== strpos( $string, "\r\n" )) $string = str_replace( "\r\n", '\n', $string); elseif( FALSE !== strpos( $string, "\r" )) $string = str_replace( "\r", '\n', $string); + + elseif( FALSE !== strpos( $string, "\n" )) + $string = str_replace( "\n", '\n', $string); + if( FALSE !== strpos( $string, '\N' )) $string = str_replace( '\N', '\n', $string); // if( FALSE !== strpos( $string, $this->nl )) @@ -6928,12 +6231,12 @@ function _strrep( $string ) { /** * special characters management input (from iCal file) * - * @author Kjell-Inge Gustafsson - * @since 2.3.3 - 2007-11-23 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.6.22 - 2010-10-17 * @param string $string * @return string */ - function _strunrep( $string ) { + static function _strunrep( $string ) { $string = str_replace( '\\\\', '\\', $string); $string = str_replace( '\,', ',', $string); $string = str_replace( '\;', ';', $string); @@ -6946,7 +6249,7 @@ function _strunrep( $string ) { /** * class for calendar component VEVENT * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-10-12 */ class vevent extends calendarComponent { @@ -6985,11 +6288,12 @@ class vevent extends calendarComponent { /** * constructor for calendar component VEVENT object * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-10-31 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.2 - 2011-05-01 + * @param array $config * @return void */ - function vevent() { + function vevent( $config = array()) { $this->calendarComponent(); $this->attach = ''; @@ -7025,11 +6329,19 @@ function vevent() { $this->components = array(); + if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) + $config['language'] = ICAL_LANG; + if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; + if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; + if( !isset( $config['format'] )) $config['format'] = 'iCal'; + if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; + $this->setConfig( $config ); + } /** * create formatted output for calendar component VEVENT object instance * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-07 * @param array $xcaldecl * @return string @@ -7083,7 +6395,7 @@ function createComponent( &$xcaldecl ) { /** * class for calendar component VTODO * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-10-12 */ class vtodo extends calendarComponent { @@ -7123,11 +6435,12 @@ class vtodo extends calendarComponent { /** * constructor for calendar component VTODO object * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-10-31 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.2 - 2011-05-01 + * @param array $config * @return void */ - function vtodo() { + function vtodo( $config = array()) { $this->calendarComponent(); $this->attach = ''; @@ -7163,11 +6476,20 @@ function vtodo() { $this->xprop = ''; $this->components = array(); + + if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) + $config['language'] = ICAL_LANG; + if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; + if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; + if( !isset( $config['format'] )) $config['format'] = 'iCal'; + if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; + $this->setConfig( $config ); + } /** * create formatted output for calendar component VTODO object instance * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-11-07 * @param array $xcaldecl * @return string @@ -7222,7 +6544,7 @@ function createComponent( &$xcaldecl ) { /** * class for calendar component VJOURNAL * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-10-12 */ class vjournal extends calendarComponent { @@ -7252,11 +6574,12 @@ class vjournal extends calendarComponent { /** * constructor for calendar component VJOURNAL object * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-10-31 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.2 - 2011-05-01 + * @param array $config * @return void */ - function vjournal() { + function vjournal( $config = array()) { $this->calendarComponent(); $this->attach = ''; @@ -7282,11 +6605,20 @@ function vjournal() { $this->summary = ''; $this->url = ''; $this->xprop = ''; + + if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) + $config['language'] = ICAL_LANG; + if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; + if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; + if( !isset( $config['format'] )) $config['format'] = 'iCal'; + if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; + $this->setConfig( $config ); + } /** * create formatted output for calendar component VJOURNAL object instance * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-10-12 * @param array $xcaldecl * @return string @@ -7332,7 +6664,7 @@ function createComponent( &$xcaldecl ) { /** * class for calendar component VFREEBUSY * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-10-12 */ class vfreebusy extends calendarComponent { @@ -7352,11 +6684,12 @@ class vfreebusy extends calendarComponent { /** * constructor for calendar component VFREEBUSY object * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-10-31 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.2 - 2011-05-01 + * @param array $config * @return void */ - function vfreebusy() { + function vfreebusy( $config = array()) { $this->calendarComponent(); $this->attendee = ''; @@ -7370,11 +6703,20 @@ function vfreebusy() { $this->requeststatus = ''; $this->url = ''; $this->xprop = ''; + + if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) + $config['language'] = ICAL_LANG; + if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; + if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; + if( !isset( $config['format'] )) $config['format'] = 'iCal'; + if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; + $this->setConfig( $config ); + } /** * create formatted output for calendar component VFREEBUSY object instance * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.3.1 - 2007-11-19 * @param array $xcaldecl * @return string @@ -7408,7 +6750,7 @@ function createComponent( &$xcaldecl ) { /** * class for calendar component VALARM * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-10-12 */ class valarm extends calendarComponent { @@ -7424,11 +6766,12 @@ class valarm extends calendarComponent { /** * constructor for calendar component VALARM object * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-10-31 + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.2 - 2011-05-01 + * @param array $config * @return void */ - function valarm() { + function valarm( $config = array()) { $this->calendarComponent(); $this->action = ''; @@ -7440,11 +6783,20 @@ function valarm() { $this->summary = ''; $this->trigger = ''; $this->xprop = ''; + + if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) + $config['language'] = ICAL_LANG; + if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; + if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; + if( !isset( $config['format'] )) $config['format'] = 'iCal'; + if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; + $this->setConfig( $config ); + } /** * create formatted output for calendar component VALARM object instance * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-10-22 * @param array $xcaldecl * @return string @@ -7470,7 +6822,7 @@ function createComponent( &$xcaldecl ) { /** * class for calendar component VTIMEZONE * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-10-12 */ class vtimezone extends calendarComponent { @@ -7492,12 +6844,17 @@ class vtimezone extends calendarComponent { /** * constructor for calendar component VTIMEZONE object * - * @author Kjell-Inge Gustafsson - * @since 2.5.1 - 2008-10-31 - * @param string $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT ) + * @author Kjell-Inge Gustafsson, kigkonsult + * @since 2.8.2 - 2011-05-01 + * @param mixed $timezonetype optional, default FALSE ( STANDARD / DAYLIGHT ) + * @param array $config * @return void */ - function vtimezone( $timezonetype=FALSE ) { + function vtimezone( $timezonetype=FALSE, $config = array()) { + if( is_array( $timezonetype )) { + $config = $timezonetype; + $timezonetype = FALSE; + } if( !$timezonetype ) $this->timezonetype = 'VTIMEZONE'; else @@ -7517,11 +6874,20 @@ function vtimezone( $timezonetype=FALSE ) { $this->xprop = ''; $this->components = array(); + + if( defined( 'ICAL_LANG' ) && !isset( $config['language'] )) + $config['language'] = ICAL_LANG; + if( !isset( $config['allowEmpty'] )) $config['allowEmpty'] = TRUE; + if( !isset( $config['nl'] )) $config['nl'] = "\r\n"; + if( !isset( $config['format'] )) $config['format'] = 'iCal'; + if( !isset( $config['delimiter'] )) $config['delimiter'] = DIRECTORY_SEPARATOR; + $this->setConfig( $config ); + } /** * create formatted output for calendar component VTIMEZONE object instance * - * @author Kjell-Inge Gustafsson + * @author Kjell-Inge Gustafsson, kigkonsult * @since 2.5.1 - 2008-10-25 * @param array $xcaldecl * @return string diff --git a/lib/iCalcreator/lgpl.txt b/lib/iCalcreator/lgpl.txt new file mode 100755 index 0000000..5ab7695 --- /dev/null +++ b/lib/iCalcreator/lgpl.txt @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/lib/iCalcreator/samples.php b/lib/iCalcreator/samples.php new file mode 100755 index 0000000..16500e3 --- /dev/null +++ b/lib/iCalcreator/samples.php @@ -0,0 +1,275 @@ +\n
    \n"; + +$v = new vcalendar( array( 'unique_id' => 'test.org' )); + // initiate new CALENDAR + +$e = & $v->newComponent( 'vevent' ); // initiate a new EVENT +$e->setProperty( 'categories' + , 'FAMILY' ); // catagorize +$e->setProperty( 'dtstart' + , 2006, 12, 24, 19, 30, 00 ); // 24 dec 2006 19.30 +$e->setProperty( 'duration' + , 0, 0, 3 ); // 3 hours +$e->setProperty( 'description' + , 'x-mas evening - diner' ); // describe the event +$e->setProperty( 'location' + , 'Home' ); // locate the event + +/* alt. production */ +// $v->returnCalendar(); // generate and redirect output to user browser + +/* alt. dev. and test */ +$str = $v->createCalendar(); // generate and get output in string, for testing? +echo $str; +echo "
    \n\n"; + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +$v = new vcalendar( array( 'unique_id' => 'test.org' )); + // initiate new CALENDAR + +$v->setProperty( 'X-WR-CALNAME' + , 'Sample calendar' ); // set some X-properties, name, content.. . +$v->setProperty( 'X-WR-CALDESC' + , 'Description of the calendar' ); +$v->setProperty( 'X-WR-TIMEZONE' + , 'Europe/Stockholm' ); + +$e = & $v->newComponent( 'vevent' ); // initiate a new EVENT +$e->setProperty( 'categories' + , 'FAMILY' ); // catagorize +$e->setProperty( 'dtstart' + , 2007, 12, 24, 19, 30, 00 ); // 24 dec 2007 19.30 +$e->setProperty( 'duration' + , 0, 0, 3 ); // 3 hours +$e->setProperty( 'description' + , 'x-mas evening - diner' ); // describe the event +$e->setProperty( 'location' + , 'Home' ); // locate the event + +$a = & $e->newComponent( 'valarm' ); // initiate ALARM +$a->setProperty( 'action' + , 'DISPLAY' ); // set what to do +$a->setProperty( 'description' + , 'Buy X-mas gifts' ); // describe alarm +$a->setProperty( 'trigger' + , array( 'week' => 1 )); // set trigger one week before + +/* alt. production */ +// $v->returnCalendar(); // generate and redirect output to user browser +/* alt. dev. and test */ +$str = $v->createCalendar(); // generate and get output in string, for testing? +echo $str; +echo "
    \n\n"; + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* define timezone */ +$v = new vcalendar( array( 'unique_id' => 'test.org' )); +$t = & $v->newComponent( 'vtimezone' ); +$t->setProperty( 'tzid' + , 'US-Eastern'); +$t->setProperty( 'last-modified' + , 1987, 1, 1 ); + +$ts = & $t->newComponent( 'standard' ); // initiate timezone standard +$ts->setProperty( 'dtstart' + , 1997, 10, 26, 2 ); +$rdate1 = array ( 'year' => 1997, 'month' => 10, 'day' => 26, 'hour' => 02, 'min' => 0, 'sec' => 0 ); +$ts->setProperty( 'rdate' + , array( $rdate1 )); +$ts->setProperty( 'tzoffsetfrom' + , '-0400' ); +$ts->setProperty( 'tzoffsetto' + , '-0500' ); +$ts->setProperty( 'tzname' + , 'EST' ); + +$td = & $t->newComponent( 'daylight' ); // initiate timezone daylight +$td->setProperty( 'dtstart' + , 1997, 10, 26, 2 ); +$rdate1 = array ( 'year' => 1997, 'month' => 4, 'day' => 6, 'hour' => 02, 'min' => 0, 'sec' => 0 ); +$td->setProperty( 'rdate' + , array( $rdate1 )); +$td->setProperty( 'tzoffsetfrom' + , '-0500' ); +$td->setProperty( 'tzoffsetto' + , '-0400' ); +$td->setProperty( 'tzname' + , 'EDT' ); + +/* alt. production +$v->returnCalendar(); // generate and redirect output to user browser +*/ +/* alt. dev. and test */ +$str = $v->createCalendar(); // generate and get output in string, for testing? +echo $str; +echo "
    \n\n"; + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* + * Samples from RFC2445, all output as strings to display + */ + +/* + * Example: The following is an example of the "VEVENT" calendar + * component used to represent a meeting that will also be opaque to + * searches for busy time: + * BEGIN:VEVENT + * UID:19970901T130000Z-123401@host.com + * DTSTAMP:19970901T1300Z + * DTSTART:19970903T163000Z + * DTEND:19970903T190000Z + * SUMMARY:Annual Employee Review + * CLASS:PRIVATE + * CATEGORIES:BUSINESS,HUMAN RESOURCES + * END:VEVENT + */ +$c = new vcalendar( array( 'unique_id' => 'test.org' )); +$e = & $c->newComponent( 'vevent' ); +$e->setProperty( 'dtstart' + , '19970901T163000Z' ); +$e->setProperty( 'dtend' + , '19970903T190000Z' ); +$e->setProperty( 'summary' + , 'Annual Employee Review' ); +$e->setProperty( 'class' + , 'PRIVATE' ); +$e->setProperty( 'categories' + , 'BUSINESS' ); +$e->setProperty( 'categories' + , 'HUMAN RESOURCES' ); + +$str = $c->createCalendar(); +echo $str; +echo "
    \n\n"; +/* + * The following is an example of the "VEVENT" calendar component used + * to represent a reminder that will not be opaque, but rather + * transparent, to searches for busy time: + * + * BEGIN:VEVENT + * UID:19970901T130000Z-123402@host.com + * DTSTAMP:19970901T1300Z + * DTSTART:19970401T163000Z + * DTEND:19970402T010000Z + * SUMMARY:Laurel is in sensitivity awareness class. + * CLASS:PUBLIC + * CATEGORIES:BUSINESS,HUMAN RESOURCES + * TRANSP:TRANSPARENT + * END:VEVENT + */ + +$c = new vcalendar( array( 'unique_id' => 'test.org' )); +$e = & $c->newComponent( 'vevent' ); +$e->setProperty( 'dtstart' + , '19970401T163000Z' ); +$e->setProperty( 'dtend' + , '19970402T010000Z' ); +$e->setProperty( 'summary' + , 'Laurel is in sensitivity awareness class.' ); +$e->setProperty( 'class' + , 'PUBLIC' ); +$e->setProperty( 'categories' + , 'BUSINESS' ); +$e->setProperty( 'categories' + , 'HUMAN RESOURCES' ); +$e->setProperty( 'transp' + , 'TRANSPARENT' ); +$str = $c->createCalendar(); +echo $str; +echo "
    \n\n"; +/* + * The following is an example of the "VEVENT" calendar component used + * to represent an anniversary that will occur annually. Since it takes + * up no time, it will not appear as opaque in a search for busy time; + * no matter what the value of the "TRANSP" property indicates: + * + * BEGIN:VEVENT + * UID:19970901T130000Z-123403@host.com + * DTSTAMP:19970901T1300Z + * DTSTART:19971102 + * SUMMARY:Our Blissful Anniversary + * CLASS:CONFIDENTIAL + * CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION + * RRULE:FREQ=YEARLY + * END:VEVENT + */ + +$c = new vcalendar( array( 'unique_id' => 'test.org' )); +$e = & $c->newComponent( 'vevent' ); +$e->setProperty( 'dtstart' + , '19971102' ); +$e->setProperty( 'summary' + , 'Our Blissful Anniversary' ); +$e->setProperty( 'class' + , 'CONFIDENTIAL' ); +$e->setProperty( 'categories' + , 'ANNIVERSARY' ); +$e->setProperty( 'categories' + , 'PERSONAL' ); +$e->setProperty( 'categories' + , 'SPECIAL OCCASION' ); +$e->setProperty( 'rrule' + , array( 'FREQ' => 'YEARLY' )); + +$str = $c->createCalendar(); +echo $str; +echo "
    \n\n"; +/* + * BEGIN:VTODO + * UID:19970901T130000Z-123404@host.com + * DTSTAMP:19970901T1300Z + * DTSTART:19970415T133000Z + * DUE:19970416T045959Z + * SUMMARY:1996 Income Tax Preparation + * CLASS:CONFIDENTIAL + * CATEGORIES:FAMILY,FINANCE + * PRIORITY:1 + * STATUS:NEEDS-ACTION + * END:VTODO + */ +$c = new vcalendar( array( 'unique_id' => 'test.org' )); +$t = & $c->newComponent( 'vtodo' ); +$t->setProperty( 'dtstart' + , '19970415T133000 GMT' ); +$t->setProperty( 'due' + , '19970416T045959 GMT' ); +$t->setProperty( 'summary' + , '1996 Income Tax Preparation' ); +$t->setProperty( 'class' + , 'CONFIDENTIAL' ); +$t->setProperty( 'categories' + , 'FAMILY' ); +$t->setProperty( 'categories' + , 'FINANCE' ); +$t->setProperty( 'priority' + , 1 ); +$t->setProperty( 'status' + , 'NEEDS-ACTION' ); + +$str = $c->createCalendar(); +echo $str; +?> \ No newline at end of file diff --git a/lib/iCalcreator/summary.html b/lib/iCalcreator/summary.html new file mode 100755 index 0000000..9e674e3 --- /dev/null +++ b/lib/iCalcreator/summary.html @@ -0,0 +1,302 @@ + + + +iCalcreator 2.10.15 summary + + + + + + + +

    iCalcreator v2.10.15

    +iCalcreator class v2.10.15
    +copyright (c) 2007-2011 Kjell-Inge Gustafsson, kigkonsult
    +kigkonsult.se/iCalcreator
    +ical@kigkonsult.se
    +
    +iCalcreator is a PHP class managing iCal formatted files for non-calendar +systems like CMS, project management systems and other applications able +to process calendar information like agendas, tasks, reports, totos, +journaling data and for communication with calendar systems and applications. +

    +This is a short summary how to use iCalcreator; create, parse, edit, select and output functionality. +

    +iCalcreator is built of a class file with support of a function class file and are calendar component property oriented. +Development environment is PHP version 5.x but coding is done to meet 4.x backward compatibility and may work. +

    iCal

    +A short iCal description is found at Wikipedia. If You are not familiar with iCal, read this first!
    +Knowledge of calendar protocol rfc5545/rfc5546 is to recommend;
    +rfc5545 + - Internet Calendaring and Scheduling Core Object Specification (iCalendar)
    +rfc5546 + - iCalendar Transport-Independent Interoperability Protocol (iTIP) Scheduling Events, BusyTime, To-dos and Journal Entries
    +rfc5545 and +rfc5546 +obsoletes, respectively, +rfc2445 and +rfc2446.
    +All functions calls are made as simple as possible BUT (, !!!,) read these rfc's properly!
    + + +

    Contact

    +The main support channel is using iCalcreator +Sourceforge forum. +For specific queries, improvement and development issues, proposals and ideas, please contact page. +Please note that paid support or consulting service has a higher priority. +

    Downloads

    +Download +complete manual +and +coding samples. +from kigkonsult.se. +

    INSTALL

    +Unpack to any folder
    +- add this folder to your include-path
    +- or unpack to your application-(include)-folder
    +Add "require_once '[folder/]iCalcreator.class.php';" to your php-script. +
    +
    +If using php version 5.1 or higher, the default timezone need to be set/altered, now "Europe/Stockholm", +line 50 in the iCalcreator.class.php file. +
    +When creating a new calendar/component instance, review config settings. +
    +
    +To really boost perfomance, visit kigkonsult.se contact page for information. +
    + +

    CREATE

    + +

    require_once( "iCalcreator.class.php" ); +$config = array( "unique_id" => "icaldomain.com" ); // set Your unique id +$v = new vcalendar( $config ); // create a new calendar instance + +$v->setProperty( "method", "PUBLISH" ); // required of some calendar software +$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software +$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software +$tz = "Europe/Stockholm"; +$v->setProperty( "X-WR-TIMEZONE", $tz ); // required of some calendar software +.. . +iCalUtilityFunctions::createTimezone( $v, $tz ) // creates (very simple) timezone component(-s) +.. . +$vevent = & $v->newComponent( "vevent" ); // create an event calendar component +$vevent->setProperty( "dtstart", array( "year"=>2007, "month"=>4, "day"=>1, "hour"=>19, "min"=>0, "sec"=>0 )); +$vevent->setProperty( "dtend", array( "year"=>2007, "month"=>4, "day"=>1, "hour"=>22, "min"=>30, "sec"=>0 )); +$vevent->setProperty( "LOCATION", "Central Placa" ); // property name - case independent +$vevent->setProperty( "summary", "PHP summit" ); +$vevent->setProperty( "description", "This is a description" ); +$vevent->setProperty( "comment", "This is a comment" ); +$vevent->setProperty( "attendee", "attendee1@icaldomain.net" ); +.. . +$vevent = & $v->newComponent( "vevent" ); // create next event calendar component +$vevent->setProperty( "dtstart", "20070401", array("VALUE" => "DATE"));// alt. date format, now for an all-day event +$vevent->setProperty( "organizer" , "boss@icaldomain.com" ); +$vevent->setProperty( "summary", "ALL-DAY event" ); +$vevent->setProperty( "description", "This is a description for an all-day event" ); +$vevent->setProperty( "resources", "COMPUTER PROJECTOR" ); +$vevent->setProperty( "rrule", array( "FREQ" => "WEEKLY", "count" => 4));// weekly, four occasions +$vevent->parse( "LOCATION:1CP Conference Room 4350" ); // supporting parse of strict rfc5545 formatted text +.. . +.. .// all calendar components are described in rfc5545 +.. .// a complete iCalcreator function list (ex. setProperty) in iCalcreator manual +.. . +$v->returnCalendar(); // redirect calendar file to browser +

    +

    PARSE

    +

    require_once( "iCalcreator.class.php" ); +$config = array( "unique_id" => "icaldomain.com" ); // set Your unique id, required if any component UID is missing +$v = new vcalendar( $config ); // create a new calendar instance + + /* start parse of local file */ +$config = array( "directory" => "calendar", "filename" => "file.ics" ); +$v->setConfig( $config ); // set directory and file name +$v->parse(); + + /* start parse of remote file */ +$v->setConfig( "url", "http://www.aDomain.net/file.ics" ); // iCalcreator also support parse from or write to remote files +$v->parse(); + +$v->setProperty( "method", "PUBLISH" ); // required of some calendar software +$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software +$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software +$v->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); // required of some calendar software + +.. . +$v->sort(); // ensure start date order +.. . +

    +

    EDIT

    +

    require_once( "iCalcreator.class.php" ); +$config = array( "unique_id" => "icaldomain.com", "directory" => "calendar", "filename" => "file.ics" ); + // set Your unique id, import directory and file name +$v = new vcalendar( $config ); // create a new calendar instance + +$v->parse(); + +$v->setProperty( "method", "PUBLISH" ); // required of some calendar software +$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software +$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software +$v->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); // required of some calendar software + +while( $vevent = $v->getComponent( "vevent" )) { // read events, one by one + $uid = $vevent->getProperty( "uid" ); // uid required, one occurence (unique id/key for component) + .. . + $dtstart = $vevent->getProperty( "dtstart" ); // dtstart required, one occurence + .. . + if( $description = $vevent->getProperty( "description", 1 )) { // description optional, first occurence + .. . // edit the description + $vevent->setProperty( "description", $description, FALSE, 1 ); // update/replace the description + } + while( $comment = $vevent->getProperty( "comment" )) { // comment optional, may occur more than once + .. . // manage comments + } + .. . + while( $vevent->deleteProperty( "attendee" )) + continue; // remove all ATTENDEE properties .. . + .. . + $v->setComponent ( $vevent, $uid ); // update/replace event in calendar with uid as key +} +.. . +.. .// a complete iCalcreator function list (ex. getProperty, deleteProperty) in iCalcreator manual +.. . +

    +

    SELECT

    +

    require_once( "iCalcreator.class.php" ); +$config = array( "unique_id" => "icaldomain.com" ); // set Your unique id +$v = new vcalendar( $config ); // create a new calendar instance + +$v->setConfig( "url", "http://www.aDomain.net/file.ics" ); // iCalcreator also support remote files +$v->parse(); +$v->sort(); // ensure start date order + +$v->setProperty( "method", "PUBLISH" ); // required of some calendar software +$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software +$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software +$v->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); // required of some calendar software +

    +

    Date based select

    +

    $eventArray = $v->selectComponents(); // select components occuring today + // (including components with recurrence pattern) +foreach( $eventArray as $year => $yearArray) { + foreach( $yearArray as $month => $monthArray ) { + foreach( $monthArray as $day => $dailyEventsArray ) { + foreach( $dailyEventsArray as $vevent ) { + $currddate = $event->getProperty( "x-current-dtstart" ); + // if member of a recurrence set (2nd occurence etc) + // returns array( "x-current-dtstart" + // , <(string) date("Y-m-d [H:i:s][timezone/UTC offset]")>) + $dtstart = $vevent->getProperty( "dtstart" ); // dtstart required, one occurence, (orig. start date) + $summary = $vevent->getProperty( "summary" ); + $description = $vevent->getProperty( "description" ); + .. . + .. . + } + } + } +} +

    +

    Select specific property values

    +

    $valueOcurr = $v->getProperty( "CATEGORIES" ); // fetch specific property (unique) values and number of ocurrencies + // ATTENDEE, CATEGORIES, DTSTART, LOCATION, ORGANIZER, PRIORITY, RESOURCES, STATUS, SUMMARY, UID +foreach( $valueOcurr as $uniqueValue => $ocurr ) { + .. . +} +

    +

    Specific property value select

    +

    $selectSpec = array( "CATEGORIES" => "course1" ); +$specComps = $v->selectComponents( $selectSpec ); // selects components based on specific property value(-s) + // ATTENDEE, CATEGORIES, LOCATION, ORGANIZER, PRIORITY, RESOURCES, STATUS, SUMMARY, UID +foreach( $specComps as $comp ) { + .. . +} +

    +

    OUTPUT

    +

    require_once( "iCalcreator.class.php" ); +$config = array( "unique_id" => "icaldomain.com" ); // set Your unique id +$v = new vcalendar( $config ); // create a new calendar instance + +$v->setProperty( "method", "PUBLISH" ); // required of some calendar software +$v->setProperty( "x-wr-calname", "Calendar Sample" ); // required of some calendar software +$v->setProperty( "X-WR-CALDESC", "Calendar Description" ); // required of some calendar software +$v->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); // required of some calendar software +.. . +.. .// parse calendar file(s) and/or edit/create calendar components.. . +.. . +

    +

    // opt 1

    +

    .. . +$v->returnCalendar(); // redirect calendar file to browser +

    +

    // opt 2

    +

    .. . +$config = array( "directory" => "depot", "filename" => "calendar.ics" ); +$v->setConfig( $config ); // set output directory and file name +$v->saveCalendar(); // save calendar to (local) file +

    +

    // opt 3

    +

    .. . +$config = array( "url" => "http://www.aDomain.net/file.ics" ); +$v->setConfig( $config ); // set output url +$v->saveCalendar(); // save calendar to remote url +

    + +

    COPYRIGHT AND LICENSE

    + +

    Copyright

    +iCalcreator v2.10.15
    +copyright (c) 2007-2011 Kjell-Inge Gustafsson, kigkonsult
    +kigkonsult.se/iCalcreator
    +ical@kigkonsult.se
    + +

    License

    + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. +

    +This library 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 +Lesser General Public License for more details. +

    +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +or download it here. +
    +
    + + \ No newline at end of file diff --git a/lib/iCalcreator/using.html b/lib/iCalcreator/using.html new file mode 100755 index 0000000..5612c04 --- /dev/null +++ b/lib/iCalcreator/using.html @@ -0,0 +1,5752 @@ + + + +iCalcreator 2.10.15 manual + + + + + + + + +

    iCalcreator 2.10.15

    +iCalcreator class v2.10.15
    +copyright (c) 2007-2011 Kjell-Inge Gustafsson, kigkonsult
    +kigkonsult.se/iCalcreator
    +ical@kigkonsult.se
    + +

    Description:

    +iCalcreator is a PHP implementation of RFC2445/RFC2446 to manage iCal/xCal formatted files. +
    + +

    1. INTRO

    +

    +iCalcreator is a PHP class managing iCal formatted files for non-calendar +systems like CMS, project management systems and other applications able +to process calendar information like agendas, tasks, reports, todos, +journalling data and for communication with calendar systems and applications. +

    +

    +iCalcreator features create, parse, edit and select calendar and calendar components. +

    +

    +iCalcreator is built of a class file with support of a function class file and are calendar +component property oriented. Development environment is PHP version 5.x but coding is done +to meet 4.x backward compatibility and may work. Some functions requires PHP >= 5.2.0. +

    +[index] [top] + +

    1.1 Ical

    + +The iCalendar format, as described in +
    +
    rfc5545 +
    "Internet Calendaring and Scheduling Core Object Specification (iCalendar)" +
    rfc5546 +
    "iCalendar Transport-Independent Interoperability Protocol (iTIP)"
    Scheduling Events, BusyTime, To-dos and Journal Entries +
    +

    . ..allows for the capture and exchange of information
    normally stored within a calendaring and scheduling application.

    +and +

    . ..is an exchange format between applications or systems.

    +

    rfc5545 and +rfc5546 +obsoletes, respectively, +rfc2445 and +rfc2446. +

    +

    +A short iCal description is found at Wikipedia. If You are not familiar with iCal, read this first! +Knowledge of calendar protocol rfc5545/rfc5546 is to recommend. +

    +

    +Any references to rfc2445, below, corresponds to rfc5545. +

    +

    +All iCalcreator functions calls are made as simple as possible BUT (, !!!,) read these rfc's properly! +xCal (iCal xml) output format is supported but still experimental. +

    +
    +[index] [top] + +

    1.2 This manual

    +This style is used for text. +

    This style is used for formats.

    +

    This style is used for PHP coding examples. + // this style is used for coding comments. +

    +

    This style is used for content details.

    +

    This style is used for RFC2445 quotes.

    +

    +[index] [top] + +

    1.3 Versioning

    + +The release numbering convention used is major.minor(.micro / suffix). +
    +
    Major +
    Indicates a very large change in the core package. Rewrites or major milestones. +
    Minor +
    Significant amount of feature addition/modification.
    odd number - development/experimental release
    even number - production release +
    Micro +
    Primarily bug fix and maintenance number. +
    Suffix +
    rc1 for first release candidate etc. +
    +[index] [top] + +

    1.4 Support

    +

    +The main support channel is using iCalcreator +Sourceforge forum. +

    +

    +Use the contact page +for queries, improvement/development issues or professional support and development. +Please note that paid support or consulting service has the highest priority. +

    +

    +Our services are available for support and designing and developing iCalcreator etc. customizations, +adaptations and other PHP/MySQL solutions with a special focus on software utility and reliability, +supported through our iterative acquire/design/transition process modell. +

    +[index] [top] + +

    1.5 Install

    + +

    1.5.1 Basic

    +Unpack to any folder
    +- add this folder to your include-path
    +- or unpack to your application-(include)-folder
    +Add +

    require_once "[folder/]iCalcreator.class.php";

    +to your php-script. +
    +
    +If using php version 5.1 or higher, the default timezone need to be set/altered, now "Europe/Stockholm", +line 50 in the iCalcreator.class.php file. +
    +
    +When creating a new calendar/component instance, review config settings. +
    +
    +[index] [top] + +

    1.5.2 Boost performance

    +To really boost perfomance, kigkonsult can now offer PHP (4 and 5) packages (iCalcreator etc) in byte coded files, +using ionCube encoder. +
    +
    +Encoded files use a platform independent file format, and can be run on any platform for which ionCube supply a (free) Loader. +Currently supported platforms are Windows (e.g. NT, XP, W2K), Intel Linux, FreeBSD, NetBSD, OpenBSD, OS X, and Sparc Solaris. +
    +
    +Visit kigkonsult.se contact page for information and purchase. +
    +
    +Use basic install (above), install ionCube Loader (requires an update of "php.ini" or additional file in "/etc/php.d" folder AND the execute rights to use the PHP "dl" function) and simply replace the PHP class files with the encoded files. +
    +
    +[index] [top] + +

    1.6 Additional Descriptors

    +The following properties (as described in wikipedia:iCal) may be required when importing iCal files into some calendaring software (MS etc.): +
    +
    on calendar level +
    METHOD property (value PUBLISH etc.) +
    X-WR-CALNAME x-property +
    X-WR-CALDESC x-property +
    X-WR-RELCALID x-property (a UUID.) +
    X-WR-TIMEZONE x-property +
    on component level +
    DTSTAMP property (in iCalcreator created automatically, if not set) +
    UID property (in iCalcreator created automatically, if not set) +
    on component level in vtimezone component +
    X-LIC-LOCATION x-property +
    +Recommendation is also to set unique_id when creating a new vcalendar/component instance, to ensure accurate setting of all components UID property, even before parse. +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->setProperty( "method", "PUBLISH" ) +$vcalendar->setProperty( "x-wr-calname", "Calendar Sample" ); +$vcalendar->setProperty( "X-WR-CALDESC", "Calendar Description" ); +$uuid = "3E26604A-50F4-4449-8B3E-E4F4932D05B5"; +$vcalendar->setProperty( "X-WR-RELCALID", $uuid ); +$vcalendar->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); +.. . +

    +[index] [top] + +

    1.7 Addendum

    +

    +Download iCalcreator coding samples. +

    +
    +
    Examples how to employ iCalcreator in software development +
    tinycal, calendar-in-a-box. +
    The iCal file event editor +
    +

    +If you are downloading the iCalcreatorusing sub-package, there are free iCal/xCal icons in the images directory, to use as buttons on a web page. +

    +The PHP codings in this document or "coding samples" (above) are only for display of usage, recommendation is to use a +coding standard, the following, incomplete, list is a good start; +
    +
    http://www.dagbladet.no/development/phpcodingstandard/ + +
    http://framework.zend.com/manual/en/coding-standard.overview.html + +
    http://pear.php.net/manual/en/standards.php +
    +
    +[index] [top] + +

    1.8 INDEX

    + +1. INTRO
    +
    +1.1 Ical
    +1.2 This manual
    +1.3 Versioning
    +1.4 Support
    +1.5 Install
    +1.5.1 Basic
    +1.5.2 Boost performance
    +1.6 Additional_Descriptors
    +1.7 Addendum
    +1.8 INDEX
    +
    +2. Calendar Component list
    +
    +2.1 VCALENDAR
    +2.2 VEVENT
    +2.3 VTODO
    +2.4 VJOURNAL
    +2.5 VFREEBUSY
    +2.6 VALARM
    +2.7 VTIMEZONE
    +2.10 Component Properties
    +
    +3. Function list
    +
    +3.1 Calendar object functions
    +
    +3.1.1 Constructors
    +3.1.1.1 vcalendar
    +3.1.1.2 vevent
    +3.1.1.3 vtodo
    +3.1.1.4 vjournal
    +3.1.1.5 vfreebusy
    +3.1.1.6 valarm
    +3.1.1.7 vtimezone
    +3.1.1.8 standard
    +3.1.1.9 daylight
    +
    +3.1.2 Calendar property functions
    +3.1.2.1 deleteProperty
    +3.1.2.2 getProperty
    +3.1.2.3 setProperty
    +3.1.2.4 CALSCALE
    +3.1.2.5 METHOD
    +3.1.2.6 VERSION
    +3.1.2.7 X-PROPERTY
    +
    +3.1.3 Calendar component functions
    +3.1.3.1 deleteComponent
    +3.1.3.2 getComponent
    +3.1.3.3 newComponent
    +3.1.3.4 selectComponents
    +3.1.3.5 setComponent
    +
    +3.1.4 Calendar input/output functions
    +3.1.4.1 parse and merge
    +3.1.4.2 createCalendar
    +3.1.4.3 returnCalendar
    +3.1.4.4 saveCalendar
    +3.1.4.5 sort
    +3.1.4.6 useCachedCalendar
    +
    +3.1.5 Calendar configuration functions
    +3.1.5.1 configuration keys
    +3.1.5.2 getConfig
    +3.1.5.3 calendar/component initialization
    +3.1.5.4 setConfig
    +3.1.5.5 Allow empty components
    +3.1.5.6 Component information
    +3.1.5.7 Delimiter
    +3.1.5.8 Directory
    +3.1.5.9 Fileinfo
    +3.1.5.10 Filename
    +3.1.5.11 Filesize
    +3.1.5.12 Format
    +3.1.5.13 Language
    +3.1.5.14 NewlineChar
    +3.1.5.15 TZID
    +3.1.5.16 Unique_id
    +3.1.5.17 URL
    +
    +3.2 Calendar component/object property function list
    +
    +3.2.1 deleteProperty
    +3.2.2 getProperty
    +3.2.3 parse
    +3.2.4 setProperty
    +
    +3.2.5 ACTION
    +3.2.6 ATTACH
    +3.2.7 ATTENDEE
    +3.2.8 CATEGORIES
    +3.2.9 CLASS
    +3.2.10 COMMENT
    +3.2.11 COMPLETED
    +3.2.12 CONTACT
    +3.2.13 CREATED
    +3.2.14 DESCRIPTION
    +3.2.15 DTEND
    +3.2.16 DTSTAMP
    +3.2.17 DTSTART
    +3.2.18 DUE
    +3.2.19 DURATION
    +3.2.20 EXDATE
    +3.2.21 EXRULE
    +3.2.22 FREEBUSY
    +3.2.23 GEO
    +3.2.24 LAST-MODIFIED
    +3.2.25 LOCATION
    +3.2.26 ORGANIZER
    +3.2.27 PERCENT-COMPLETE
    +3.2.28 PRIORITY
    +3.2.29 RDATE
    +3.2.30 RECURRENCE-ID
    +3.2.31 RELATED-TO
    +3.2.32 REPEAT
    +3.2.33 REQUEST-STATUS
    +3.2.34 RESOURCES
    +3.2.35 RRULE
    +3.2.36 SEQUENCE
    +3.2.37 STATUS
    +3.2.38 SUMMARY
    +3.2.39 TRANSP
    +3.2.40 TRIGGER
    +3.2.41 TZID
    +3.2.42 TZNAME
    +3.2.43 TZOFFSETFROM
    +3.2.44 TZOFFSETTO
    +3.2.45 TZURL
    +3.2.46 UID
    +3.2.47 URL
    +3.2.48 X-PROPERTY
    +
    +3.3 Calendar Component configuration functions
    +
    +3.3.1 Language
    +
    +3.4 Calendar component object misc. functions
    +
    +3.4.1 deleteComponent
    +3.4.2 getComponent
    +3.4.3 newComponent
    +3.4.4 setComponent
    +
    +4. iCalUtilityFunctions
    +
    +4.1 createTimezone
    +4.2 transformDateTime
    +
    +5. COPYRIGHT AND LICENSE
    +
    +[index] [top] + +

    2. Calendar Component list

    +Quote from RFC2445 - Internet Calendaring and Scheduling Core Object Specification (iCalendar)! +(Described in iCal output format, content corresponds to xCal format.)
    +

    2.1 VCALENDAR

    +

    icalobject = 1*("BEGIN" ":" "VCALENDAR" CRLF

    +

    icalbody

    +

    "END" ":" "VCALENDAR" CRLF)

    +
    +icalbody = calprops component +
    +calprops = 2*( +
    +

    "prodid" and "version" are both REQUIRED, but MUST NOT occur more than once +

    prodid / version /

    +

    "calscale"and "method"are optional, but MUST NOT occur more than once

    +

    calscale / method /

    +

    x-prop

    +) +
    +

    component = 1*(eventc / todoc / journalc / freebusyc / timezonec / iana-comp* / x-comp*)

    +

    iana-comp = "BEGIN" ":" iana-token CRLF

    +

    1*contentline

    +

    "END" ":" iana-token CRLF

    +

    x-comp = "BEGIN" ":" x-name CRLF

    +

    1*contentline

    +

    "END" ":" x-name CRLF

    +*) not supported by iCalcreator +
    +[index] [top] [up] + +

    2.2 VEVENT

    +

    "BEGIN" ":" "VEVENT" CRLF

    +

    eventprop *alarmc

    +

    "END" ":" "VEVENT" CRLF

    +eventprop = *( +

    the following are optional,but MUST NOT occur more than once

    +

    class / created / description / dtstart /

    +

    geo / last-mod / location / organizer / priority /

    +

    dtstamp / seq / status / summary /

    +

    transp / uid / url / recurid /

    +

    either "dtend" or "duration" may appear in a "eventprop",

    +

    but "dtend" and "duration" MUST NOT occur in the same "eventprop"

    +

    dtend / duration /

    +

    the following are optional, and MAY occur more than once

    +

    attach / attendee / categories / comment /

    +

    contact / exdate / exrule / rstatus /

    +

    related / resources / rdate / rrule / x-prop

    +) +
    +[index] [top] [up] + +

    2.3 VTODO

    +

    "BEGIN" ":" "VTODO" CRLF

    +

    todoprop *alarmc

    +

    "END" ":" "VTODO" CRLF

    +todoprop = *( +

    the following are optional, but MUST NOT occur more than once

    +

    class / completed / created / description / dtstamp / dtstart /

    +

    geo / last-mod / location / organizer / percent / priority /

    +

    recurid / seq / status / summary /uid / url /

    +

    either "due" or "duration" may appear in a "todoprop",

    +

    but "due" and "duration" MUST NOT occur in the same "todoprop"

    +

    due / duration /

    +

    the following are optional,and MAY occur more than once

    +

    attach / attendee / categories / comment /

    +

    contact / exdate / exrule / rstatus /

    +

    related / resources / rdate / rrule / x-prop

    +) +
    +[index] [top] [up] + +

    2.4 VJOURNAL

    +

    journalc = "BEGIN" ":" "VJOURNAL" CRLF

    +

    jourprop

    +

    "END" ":" "VJOURNAL" CRLF

    +jourprop = *( +

    the following are optional, but MUST NOT occur more than once

    +

    class / created / description / dtstart /

    +

    dtstamp / last-mod / organizer / recurid /

    +

    seq / status / summary /uid / url /

    +

    the following are optional,and MAY occur more than once

    +

    attach / attendee / categories / comment /

    +

    contact / exdate / exrule / related /

    +

    rdate / rrule / rstatus / x-prop

    +) +
    +[index] [top] [up] + +

    2.5 VFREEBUSY

    +

    "BEGIN" ":" "VFREEBUSY" CRLF

    +

    fbprop

    +

    "END" ":" "VFREEBUSY" CRLF

    +fbprop = *( +

    the following are optional, but MUST NOT occur more than once

    +

    contact / dtstart / dtend / duration /

    +

    dtstamp / organizer / uid / url /

    +

    the following are optional,and MAY occur more than once

    +

    attendee / comment / freebusy / rstatus / x-prop

    +) +
    +[index] [top] [up] + +

    2.6 VALARM

    +

    "BEGIN" ":" "VALARM" CRLF

    +

    (audioprop / dispprop / emailprop / procprop)

    +

    "END" ":" "VALARM" CRLF

    audioprop = 2*( +

    "action" and "trigger" are both REQUIRED, but MUST NOT occur more than once

    +

    action / trigger /

    +

    "duration" and "repeat" are both optional,and MUST NOT occur more than once each,

    +

    but if one occurs, so MUST the other

    +

    duration / repeat /

    +

    the following is optional, but MUST NOT occur more than once

    +

    attach /

    +

    the following is optional, and MAY occur more than once

    +

    x-prop

    +) +
    +dispprop = 3*( +

    the following are all REQUIRED, but MUST NOT occur more than once

    +

    action / description / trigger /

    +

    "duration" and "repeat" are both optional,and MUST NOT occur more than once each,

    +

    but if one occurs, so MUST the other

    +

    duration / repeat /

    +

    the following is optional, and MAY occur more than once

    +

    x-prop

    +) +
    +emailprop = 5*( +

    the following are all REQUIRED, but MUST NOT occur more than once

    +

    action / description / trigger / summary

    +

    the following is REQUIRED, and MAY occur more than once

    +

    attendee /

    +

    "duration" and "repeat" are both optional, and MUST NOT occur more than once each,

    +

    but if one occurs, so MUST the other

    +

    duration / repeat /

    +

    the following are optional, and MAY occur more than once

    +

    attach / x-prop

    +) +
    +procprop = 3*( +

    the following are all REQUIRED, but MUST NOT occur more than once

    +

    action / attach / trigger /

    +

    "duration" and "repeat" are both optional, and MUST NOT occur more than once each,

    +

    but if one occurs, so MUST the other

    +

    duration / +repeat /

    +

    "description" is optional, and MUST NOT occur more than once

    +

    description /

    +

    the following is optional, and MAY occur more than once

    +

    x-prop

    +) +
    +[index] [top] [up] + +

    2.7 VTIMEZONE

    +

    "BEGIN" ":" "VTIMEZONE" CRLF

    +2*( +

    "tzid" is required, but MUST NOT occur more than once

    +

    tzid /

    +

    "last-mod" and "tzurl" are optional, but MUST NOT occur more than once

    +

    last-mod / tzurl /

    +

    one of "standardc" or "daylightc" MUST occur and each MAY occur more than once.

    +

    standardc / daylightc /

    +

    the following is optional, and MAY occur more than once

    +

    x-prop

    +) +

    "END" ":" "VTIMEZONE" CRLF

    +

    standardc = "BEGIN" ":" "STANDARD" CRLF

    +

    tzprop

    +

    "END" ":" "STANDARD" CRLF

    +

    daylightc = "BEGIN" ":" "DAYLIGHT" CRLF

    +

    tzprop

    +

    "END" ":" "DAYLIGHT" CRLF

    +
    +tzprop = 3*( +

    the following are each REQUIRED, but MUST NOT occur more than once

    +

    dtstart / tzoffsetto / tzoffsetfrom /

    +

    the following are optional, and MAY occur more than once

    +

    comment /rdate / rrule / tzname / x-prop

    +) +
    +[index] [top] [up] + +

    2.8 Component Properties

    +A comprehensive table showing relation between calendar components and properties. +vtimezone properties are not included. +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    0-1OPTIONAL property, MUST NOT occur more than once.
    0-mOPTIONAL property, MAY occur more than once.
    0 / 1=1A pair of OPTIONAL properties, MUST NOT occur more than once each.
    If one occurs, so MUST the other
    0*1A pair of OPTIONAL properties, MUST NOT occur more than once each.
    If one occurs, so MUST NOT the other
    1-mREQUIRED property, MAY occur more than once.
    1REQUIRED property, MUST NOT occur more than once.
     
     v
    e
    v
    e
    n
    t
    v
    t
    o
    d
    o
    v
    j
    o
    u
    r
    n
    a
    l
    v
    f
    r
    e
    e
    b
    u
    s
    y
    v a l a r m
     



    a
    u
    d
    i
    o


    d
    i
    s
    p
    l
    a
    y




    e
    m
    a
    i
    l
    p
    r
    o
    c
    e
    d
    u
    r
    e
    action    1111
    attach0-m0-m0-m0-1 0-m1
    attendee0-m0-m0-m0-m  1-m 
    categories0-m0-m0-m     
    class0-10-10-1     
    comment0-m0-m0-m0-m    
    completed 0-1      
    contact0-m0-m0-m0-1    
    created0-10-10-1     
    description0-10-10-m  110-1
    dtend0*1  0-1    
    dtstamp0-10-10-10-1    
    dtstart0-10-10-10-1    
    due 0*1      
    duration0*10*1 0-10 / 1=10 / 1=10 / 1=10 / 1=1
    exdate0-m0-m0-m     
    exrule0-m0-m0-m     
    freebusy   0-m    
    geo0-10-1      
    last-mod0-10-10-1    
    location0-10-1      
    organizer0-10-10-10-1    
    percent 0-1      
    priority0-10-1      
    rdate0-m0-m0-m     
    recurid0-10-10-1     
    related0-m0-m0-m     
    repeat    0 / 1=10 / 1=10 / 1=10 / 1=1
    resources0-m0-m      
    rrule0-m0-m0-m     
    rstatus0-m0-m0-m0-m    
    sequence0-10-10-1     
    status0-10-10-1     
    summary0-10-10-1   1 
    transp0-1       
    trigger    1111
    uid0-10-10-10-1    
    url0-10-10-10-1    
    x-prop0-m0-m0-m0-m0-m0-m0-m0-m
    +

    +If not set, DTSTART and UID +are created automatic by iCalcreator for vevent, +vtodo, vjournal and vfreebusy components +when using calendar functions saveCalendar or returnCalendar +or when fetching DTSTART/UID property value with +component function getProperty. +

    +
    +[index] [top] [up] + +

    3. Function list

    + +

    3.1 Calendar object functions

    + +

    3.1.1 Constructors

    +

    3.1.1.1 vcalendar

    +Create a new VCALENDAR object. +

    Format

    +

    vcalendar()

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +

    +
    +[index] [top] [up] + +

    3.1.1.2 vevent

    +

    Format 1

    +Create a new VEVENT object using a calendar factory-method, returning a reference to the new component. +

    newComponent( "vevent" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vevent = & $vcalendar->newComponent( "vevent" ); +$vevent->setProperty(... +... +

    +

    Format 2

    +Create a new VEVENT object. +

    vevent()

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vevent = new vevent(); +$vevent->setProperty(... +... +$vcalendar->setComponent( $vevent ); +... +

    +
    +[index] [top] [up] + +

    3.1.1.3 vtodo

    +

    Format 1

    +Create a new VTODO object using a calendar factory-method, returning a reference to the new component. +

    newComponent( "vtodo" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vtodo = & $vcalendar->newComponent( "vtodo" ); +$vtodo->setProperty(... +... +

    +

    Format 2

    +Create a new VTODO object. +

    vtodo()

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vtodo = new vtodo(); +$vtodo->setProperty(... +... +$vcalendar->setComponent( $vtodo ); +... +

    +
    +[index] [top] [up] + +

    3.1.1.4 vjournal

    +

    Format 1

    +Create a new VJOURNAL object using a calendar factory-method, returning a reference to the new component. +

    newComponent( "vjournal" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vjournal = & $vcalendar->newComponent( "vjournal" ); +$vjournal->setProperty(... +... +

    +

    Format 2

    +Create a new VJOURNAL object. +

    vjournal()

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vjournal = new vjournal(); +$vjournal->setProperty(... +... +$vcalendar->setComponent( $vjournal ); +... +

    +
    +[index] [top] [up] + +

    3.1.1.5 vfreebusy

    +

    Format 1

    +Create a new VFREEBUSY object using a calendar factory-method, returning a reference to the new component. +

    newComponent( "vfreebusy" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vfreebusy = & $vcalendar->newComponent( "vfreebusy" ); +$vfreebusy->setProperty(... +... +

    +

    Format 2

    +Create a new VFREEBUSY object. +

    vfreebusy()

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vfreebusy = new vfreebusy(); +$vfreebusy->setProperty(... +... +$vcalendar->setComponent( $vfreebusy ); +... +

    +
    +[index] [top] [up] + +

    3.1.1.6 valarm

    +

    Format 1

    +Create a new VALARM object using a component factory-method, returning a reference to the new (sub-)component. +

    newComponent( "valarm" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vevent = & $vcalendar->newComponent( "vevent" ); +$vevent->setProperty(... +... +$valarm = & $vevent->newComponent( "valarm" ); +$valarm->setProperty(... +... +

    +

    Format 2

    +Create a new VALARM object. +

    valarm()

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vevent = new vevent(); +$vevent->setProperty(... +... +$valarm = new valarm(); +$valarm->setProperty(... +... +$vevent->setComponent( $valarm ); +... +$vcalendar->setComponent( $vevent ); +... +

    +
    +[index] [top] [up] + +

    3.1.1.7 vtimezone

    +

    Format 1

    +Create a new VTIMEZONE object using a calendar factory-method, returning a reference to the new component. +

    newComponent( "vtimezone" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vtimezone = & $vcalendar->newComponent( "vtimezone" ); +$vtimezone->setProperty(... +... +

    +

    Format 2

    +Create a new VTIMEZONE object. +

    vtimezone()

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vtimezone = new vtimezone(); +$vtimezone->setProperty(... +... +$vcalendar->setComponent( $vtimezone ); +... +

    +
    +
    Creation of timezone components
    +It is possible to create timezone components, using a function in iCalUtilityFunctions class, createTimezone +and utilizing The PHP DateTimeZone class (PHP 5 >= 5.2.0). +
    +
    +[index] [top] [up] + +

    3.1.1.8 standard

    +

    Format 1

    +Create a new VTIMEZONE standard object using a component factory-method, returning a reference to the new (sub-)component. +

    newComponent( "standard" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vtimezone = & $vcalendar->newComponent( "vtimezone" ); +$vtimezone->setProperty(... +... +$standard = & $vtimezone->newComponent( "standard" ); +$standard->setProperty(... +... +$daylight = & $vtimezone->newComponent( "daylight" ); +$daylight->setProperty(... +... +

    +

    Format 2

    +Create a new VTIMEZONE STANDARD object. +

    vtimezone( "standard" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vtimezone = new vtimezone(); +$vtimezone->setProperty(... +... +$standard = new vtimezone( "standard" ); +$standard->setProperty(... +... +$vtimezone->setComponent( $standard ); +... +$daylight = new vtimezone( "daylight" ); +$daylight->setProperty(... +... +$vtimezone->setComponent( $daylight ); +... +$vcalendar->setComponent( $vtimezone ); +... +

    +
    +[index] [top] [up] + +

    3.1.1.9 daylight

    +

    Format 1

    +Create a new VTIMEZONE daylight object using a component factory-method, returning a reference to the new (sub-)component. +

    newComponent( "standard" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vtimezone = & $vcalendar->newComponent( "vtimezone" ); +... +$standard = & $vtimezone->newComponent( "standard" ); +... +$daylight = & $vtimezone->newComponent( "daylight" ); +... +

    +

    Format 2

    +Create a new VTIMEZONE DAYLIGHT object. +

    vtimezone( "daylight" )

    +

    Example

    +

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vtimezone = new vtimezone(); +... +$standard = new vtimezone( "standard" ); +... +$vtimezone->setComponent( $standard ); +$daylight = new vtimezone( "daylight" ); +... +$vtimezone->setComponent( $daylight ); +$vcalendar->setComponent( $vtimezone ); +... +

    +
    +[index] [top] [up] + + +

    3.1.2 Calendar property functions

    + +

    3.1.2.1 deleteProperty

    +General calendar deleteProperty function, simplifying removal of calendar properties.
    +FALSE is returned if no property exists or when end-of-properties at consecutive function calls. +

    Format

    +

    deleteProperty( [ string PropName [, int order=1 ] )

    +

    propName - case independent, rfc2445 component property names, + unknown/missing propName will be regarded as X-property. +order - if missing 1st/next occurrence, + used with multiple (property) occurrences +

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +if( !$vcalendar->deleteProperty( "method" )) +  echo "METHOD property not found"; +.. . +

    +
    +[index] [top] [up] + +

    3.1.2.2 getProperty

    +

    Format 1

    +General calendar getProperty function, simplifying fetch of calendar properties.
    +FALSE is returned if no property exists or when end-of-properties at consecutive function calls. +

    getProperty( [ string PropName [, int order=1 [, bool complete=FALSE ]]] )

    +

    propName - case independent, rfc2445 component property names, + unknown/missing propName will be regarded as X-property. +order - if missing 1st/next occurrence, + used with multiply (property) occurrences +complete - FALSE (default) : output only property value + - TRUE : output = + array( "value"=> <value> ,"params" => <parameter array>) +

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$calscale = $vcalendar->getProperty( "calscale" ); +.. . +

    +

    Example 2

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +while( $xprop = $vcalendar->getProperty( )) { // get x-properties in order.. . +.. . +

    +

    Format 2

    +Ability to fetch specific property (unique) values and number of ocurrencies, +ATTENDEE, CATEGORIES, DTSTART, LOCATION, ORGANIZER, PRIORITY, RESOURCES, +STATUS, SUMMARY, UID or "RECURRENCE-ID-UID" (alt. "R-UID" ) from ALL components within calendar.
    +Outputs an array( *[<unique-property-value> => <number of occurrence>] )
    or an empty array if no hits.
    +If a property contains multiple values (ex. "RESOURCES:pc,developer" or "CATAGORIES:course1,courseB"), they are split into unique values. +

    getProperty( string PropName )

    +

    propName - case independent, above +DTSTART as argument returns dates, in format "YYYYMMDD", +"RECURRENCE-ID-UID"returns UID values for component(-s) where RECURRENCE-ID is set. +ATTENDEE and ORGANIZER values are prefixed by protocol ex."MAILTO:chair@ical.net". +

    +
    +[index] [top] [up] + +

    3.1.2.3 setProperty

    +General calendar setProperty function,simplifying insert of calendar properties.
    +A successful update returns TRUE. +

    Format

    +

    setProperty( string PropName, mixed Proparg_1 *[, mixed Proparg_n] )

    +propName case independent, strict rfc2445 calendar property names, unknown propName will be regarded as X-property. +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); // initiate new CALENDAR +$vcalendar->setProperty( "calscale", "GREGORIAN" ); +

    +
    +[index] [top] [up] + +

    3.1.2.4 CALSCALE

    +This property defines the calendar scale used for the calendar information specified in the iCalendar object. +

    +The default value is "GREGORIAN", implied when missing. +
    Delete CALSCALE
    +Remove CALSCALE from component. +

    Format

    +

    deleteProperty( "calscale" )

    +

    Example

    +

    $vcalendar->deleteProperty( "CALSCALE" );

    +
    Get Calscale
    +Fetch property value. +

    Format

    +

    getProperty( "calscale" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$calscale = $vcalendar->getProperty( "calscale" ); +.. . +

    +
    Set CALSCALE
    +Insert property value. +

    Format

    +

    setProperty( "calscale", string value )

    +

    Example

    +

    +$config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); // initiate new CALENDAR +$vcalendar->setProperty( "calscale", "GREGORIAN" ); +.. . +

    +
    +[index] [top] [up] + + +

    3.1.2.5 METHOD

    +This property defines the iCalendar object method associated with the calendar object. +

    +METHOD property (value PUBLISH etc.) may be required when importing iCal files +into some calendaring software (MS etc.), as well as x-properties +"X-WR-CALNAME", "X-WR-CALDESC" and "X-WR-TIMEZONE" +and the (automatically created) DTSTAMP and UID properties. +
    Delete METHOD
    +Remove METHOD from component. +

    Format

    +

    deleteProperty( "METHOD" )

    +

    Example

    +

    $vcalendar->deleteProperty( "METHOD" );

    +
    Get METHOD
    +Fetch property value. +

    Format

    +

    getProperty( "method" );

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$method = $vcalendar->getProperty( "method" ) +.. . +

    +
    Set METHOD
    +Insert property value. +

    Format

    +

    setProperty( "method", string value )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); // initiate new CALENDAR +$vcalendar->setProperty( "method", "PUBLISH" ) +

    +
    +[index] [top] [up] + + +

    3.1.2.6 VERSION

    +This property specifies the identifier corresponding to the highest version number or the minimum +and maximum range of the iCalendar specification that is required in order to interpret the iCalendar object.
    +This property is always placed first in the calendar file.
    +
    Get Version
    +Fetch property value. +

    Format

    +

    getProperty( "version" )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$version = $vcalendar->getProperty( "version" ) +.. . +

    +
    Set Version
    +Insert property value. +Only version 2.0 valid, version is AUTO generated at calendar creation. +

    Format

    +

    setProperty( "version", string version )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); // initiate new CALENDAR +$vcalendar->setProperty( "version", "2.0" ) +

    +
    +[index] [top] [up] + +

    3.1.2.7 X-PROPERTY

    +A calendar, non-standard property with a TEXT value and a name with an "X-" prefix. In a calendar, +an x-property, with an unique name, can occur only once but the number of x-properties are unlimited. +

    +X-properties "X-WR-CALNAME", "X-WR-CALDESC" and "X-WR-TIMEZONE" may be required when importing iCal files +into some calendaring software (MS etc.), as well as METHOD property (value PUBLISH etc.) +and the (automatically created) DTSTAMP and UID properties. +
    Delete X-PROPERTY
    +Remove X-PROPERTY from calendar. +

    Format

    +

    deleteProperty( "<X-PROPERTY>" )

    +

    Example 1

    +

    $vcalendar->deleteProperty( "<X-PROPERTY>" );

    +

    Example 2

    +Deleting all x-properties. +

    while( $vcalendar->deleteProperty()) + continue;

    +
    Get X-PROPERTY
    +Fetch property value. +

    Format

    +

    getProperty()
    +getProperty( "<X-PROPERTY>" )

    +

    output = array( propertyName1, propertyData2 )

    +

    getProperty( FALSE, propOrderNo/FALSE, TRUE )

    +

    output = array( propertyName1 + , array( "value" => propertyData2 ) + , "params" => params 3)) +

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +while( $xprop = $vcalendar->getProperty( )) { //read all x-props in a loop +.. . +

    +

    $xprop = array( propertyName1, propertyData2 )

    +

    Example 2

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +if( $xprop = $vcalendar->getProperty( "X-WR-TIMEZONE" )) { + //if exists, read X-WR-TIMEZONE x-prop +.. . +

    +

    $xprop = array( "X-WR-TIMEZONE", propertyData2 )

    +

    Example 3

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +while( $xprop = $vcalendar->getProperty( FALSE, FALSE, TRUE )) { +.. . +

    +

    $xprop = array( propertyName1 + , array( "value " => propertyData2 ) + , "params "=> params 3 ) +

    +
    Set X-PROPERTY
    +Insert property name and value. If an x-prop with the same name already exists, it will be replaced. +PropertyNames are always stored upperCase, ex. x-wr-calname => X-WR-CALNAME. +

    Format

    +

    setProperty( propertyName, propertyData [, params ] )

    +

    propertyName1 = Any property name with a "X-" prefix +propertyData2 = Value type TEXT +params3 = array( ["LANGUAGE" => "<lang>"] [, xparam] ) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); // initiate new CALENDAR + // set some X-properties.. . +$vcalendar->setProperty( "x-wr-calname", "Calendar Sample" ) +$vcalendar->setProperty( "X-WR-CALDESC", "Calendar Description" ); +$vcalendar->setProperty( "X-WR-TIMEZONE", "Europe/Stockholm" ); +

    +
    +[index] [top] [up] + +

    3.1.3 Calendar component functions

    + +

    3.1.3.1 deleteComponent

    +Remove component from calendar.
    +FALSE is returned if no property exists or when end-of-properties at consecutive function calls. +

    format 1

    +Remove component with order number (1st=1, 2nd=2.. .). +

    deleteComponent( int orderNumber )

    +

    format 2

    +Remove component with component type (e.g. "vevent") and order 1 alt. suborder number. +

    deleteComponent( string componentType [, int componentSuborderNumber])

    +

    format 3

    +Remove component with UID. N.B UID is NOT set for +ALARM / TIMEZONE components. +

    deleteComponent( string UID )

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$vcalendar->deleteComponent( 1 ); +$vcalendar->deleteComponent( "vtodo", 2 ); +$vcalendar->deleteComponent( "20070803T194810CEST-0123U3PXiX@domain.com"); +.. . +

    +

    Example 2

    +Deleting all components, using format 2 without order number. +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +.. . +while( $vcalendar->deleteComponent( "vevent")) + continue; +.. . +$vtodo = $vcalendar->getComponent( "vtodo" ); +while( $vtodo->deleteComponent( "valarm")) + continue; +.. . +

    +
    +[index] [top] [up] + +

    3.1.3.2 getComponent

    +Get component from calendar.
    +FALSE is returned if no property exists or when end-of-properties at consecutive function calls. + +

    format 1

    +Get next component, until end-of-components. +

    getComponent()

    + +

    format 2

    +Get specific component with order number (1st=1, 2nd=2.. .). +

    getComponent( int orderNumber )

    + +

    format 3

    +Get (first/next) component with component type (until end-of-components) alt. +get specific component with component type and suborder number (1st=1, 2nd=2.. .). +

    getComponent( string componentType [, int componentSuborderNumber])

    + +

    format 4

    +Get (first/next) component with UID as key. (UID is NOT set for +ALARM / TIMEZONE components.) +

    getComponent( string UID )

    + +

    format 5

    +Get (first/next) component based on specific property contents; +DTSTART, DTEND, DUE, CREATED, COMPLETED, DTSTAMP, LAST-MODIFIED, RECURRENCE-ID, +ATTENDEE, CATEGORIES, LOCATION, ORGANIZER, PRIORITY, RESOURCES, STATUS, SUMMARY, UID. +For the property "SUMMARY" ,if a search value (any case) exists within property value, a hit exists. +For the other, non-date, properties an exact (strict case) match is required.
    +Note, ATTENDEE and ORGANIZER values must be prefixed by protocol ex."MAILTO:chair@ical.net". + +

    getComponent( array(*[string propertyName => string uniqueValue] ))

    +

    propertyName = property name, above +propertyData = unique property value (strict case), + date format "YYYYMMDD" (if any side is DATE, only dates are used), + datetime format "YYYYMMDDTHHMMSS" +

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +while( $comp = $vcalendar->getComponent()) { +.. . +} +.. . +

    +

    Example 2

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +if( $comp = $vcalendar->getComponent( 1 )) { +.. . +} +.. . +

    +

    Example 3

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +if( $comp = $vcalendar->getComponent( "vtodo", 2 ) { +.. . +} +.. . +

    +

    Example 4

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$uid = "20070803T194810CEST-0123U3PXiX@domain.com"; +if($comp = $vcalendar->getComponent( $uid ){ +.. . +} +.. . +

    +
    +[index] [top] [up] + +

    3.1.3.3 newComponent

    +Create component (EVENT / VTODO / VJOURNAL / VFREEBUSY / VTIMEZONE) +using a calendar factory-method, returning a reference to the new component. +

    Format

    +

    newComponent( string componentType )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vevent = & $vcalendar->newComponent( "vevent" ); +... +

    + +
    +[index] [top] [up] + +

    3.1.3.4 selectComponents

    +

    Format 1

    +Selects EVENT / VTODO / VJOURNAL / VFREEBUSY components from calendar on based on dates (notice date restriction), based on the initial DTSTART property +along with the RRULE, RDATE, EXDATE and EXRULE properties in the component. +If property DTSTART is missing in a VTODO component then DUE is used.
    +
    +Limitation; if using component properties UID in combination with SEQUENCE and RECURRENCE-ID (i.e an individual instance within the recurrence set), +the use of recurrence-id parameter "RANGE" ("THISANDPRIOR" and/or "THISANDFUTURE") has no effect.
    +
    +This function requires calendar components in order, i.e. execute sort before "selectComponents", notice example below.
    +
    +FALSE is returned if no selected component exists. +

    selectComponents([ int startYear, int startMonth, int startDay + [, int endYear, int endMonth, int endDay + [, mixed cType [, bool flat [,bool any [,bool split]]]]]]) +

    +Returns an array with components (events.. .). +For all recurrent instances of a calendar component, an x-property, +"X-CURRENT-DTSTART" and opt. also "X-CURRENT-DTEND" alt. "X-CURRENT-DUE", +has been created with a TEXT content, "Y-m-d [H:i:s][timezone/UTC offset]" +showing the current start and opt. also end alt. due date.
    +Also a "X-RECURRENCE" x-property is set with order number (valid if selectComponents is called from DTSTART date). +

    startYear : start year (4*digit), default current year +startMonth : start month (1-2*digit), default current month +startDay : start day (1-2*digit), default current day +endYear : end year (4*digit), default startYear +endMonth : end month (1-2*digit), default startMonth +endDay : end day (1-2*digit), default startDay +cType : calendar component type(-s), string/array + ("vevent", "vtodo", "vjournal", "vfreebusy") + FALSE (default) => all +flat : TRUE => output : array[] (ignores split) + component where recurrence pattern exists within period + FALSE (default) => output : array[Year][Month][Day][] +any : TRUE (default) => selects components with recurrence pattern in period + FALSE => only components that starts (DTSTART) within period +split : TRUE (default) => one component copy for every day it occurs + within the period (only when flat=FALSE) + FALSE => one occurrence of component in output array, + start date/recurrence (start) date +

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import", + "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$vcalendar->sort(); +$events_arr = $vcalendar->selectComponents( 2007,11,1,2007,11,30,"vevent"); + // select all events occurring 1-30 nov. 2007 +foreach( $events_arr as $year => $year_arr ) { + foreach( $year_arr as $month => $month_arr ) { + foreach( $month_arr as $day => $day_arr ) { + foreach( $day_arr as $event ) { + $currddate = $event->getProperty( "x-current-dtstart" ); + // if member of a recurrence set, returns + // array(" x-current-dtstart", + // <(string) date("Y-m-d [H:i:s][timezone/UTC offset]")>) + $startDate = $event->getProperty( "dtstart" ); + $summary = $event->getProperty( "summary" ); + $description = $event->getProperty( "description" ); + .. . +

    +

    format 2

    +Using this format, the function selects components based on specific property value(-s), +ATTENDEE, CATEGORIES, LOCATION, ORGANIZER, PRIORITY, RESOURCES, STATUS, SUMMARY or UID. +For the property "SUMMARY" ,if a search value (any case) exists within property value, a hit is found. +For the other properties an exact (strict case) match is required.
    +ATTENDEE and ORGANIZER search values must be prefixed by protocol ex. "MAILTO:chair@ical.net". +Multiple search properties may coexist. For retrieving property values, see getProperty (Format 2) on calendar level. +

    selectComponents( searchArray )

    +Outputs an array of matched (unique) components in UID order. +

    searchArray : array( propertyName => propertyValue ) +propertyName : above (any case) +propertyValue : string value / array( *[string value] ) +

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import", + "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$vcalendar->sort(); +$searchArray = array( "PRIORITY" => array( 1, 2, 3, 4, 5 )); +$mediumHighPrioArr = $vcalendar->selectComponents( $searchArray ); + // select all components with high (1) and medium priority +if( !empty( $mediumHighPrioArr )) { + $mediumHighPrioCal = new vcalendar(); + $mediumHighPrioCal->setProperty("X-WR-CALDESC", "High-Medium events"); + foreach( $mediumHighPrioArr as $mediumHighPrioComponent ) + $mediumHighPrioCal->setComponent( $mediumHighPrioComponent ); + $mediumHighPrioCal->returnCalendar(); +} +.. . +

    +
    +[index] [top] [up] + +

    3.1.3.5 setComponent

    +Replace or update component in calendar. +Also add calendar component to calendar when calendar component is created with the procedural (non-factory) method, +see example VEVENT, format 2. +A successful update return TRUE. +

    format 1

    +Insert last in component chain. +

    setComponent( component ) +addComponent( component ) // alias +

    +

    addComponent, may be removed i future versions.

    +

    format 2

    +Insert/replace component with order number (1st=1, 2nd=2.. .). +If replace and orderNumber is not found, component is inserted last in chain. +

    setComponent( component, int orderNumber )

    +

    format 3

    +Replace component with component type and 1st alt. component order number. +If orderNumber is not found, component is inserted last in chain. +

    setComponent( component, string componentType [,int component suborder no])

    +

    format 4

    +Replace component with UID. +N.B UID is NOT set for ALARM / TIMEZONE components. +If UID is not found, component is inserted last in chain. +

    setComponent( component, string UID )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$vevent = vcalendar->getComponent( 1 ); //fetch first EVENT +$vevent->setProperty( "dtstart" //update DTSTART property + , 2006, 12, 24, 19, 30, 00 ); +.. . +$vcalendar->setComponent( $vevent, 1 ); // replace first component +.. . +

    +
    +[index] [top] [up] + +

    3.1.4 Calendar input/output functions

    + +

    3.1.4.1 parse and merge

    +Parse iCal file(-s) or string/array calendarContent into a single vcalendar object (components, properties and parameters), +including multiple vcalendars (within a single ICS file) parse, e.g. Oracle Calendar exports. + +

    +As long as php.ini directive "allow_url_fopen" is enabled, remote files, URLs; protocol "http" ("webcal"), are supported. A remote file, URL, must be prefixed by "http://" ("webcal://") and suffixed by a valid filename.! Recommendation is to download (cache) remote file before parsing, due to execution time and control. +

    +If missing, component property UID is created when parsing. For that reason UNIQUE_ID might need to be set before parsing, Se examples below. +

    +Notice date restriction! +

    +If parse error occurs (like file access error, invalid calendar file or calendar file without components), FALSE is returned. +

    Format

    +

    parse( [ mixed textToParse ] )

    +

    textToParse = string calendarContent + ex. result from - file_get_contents( "filename") + array calendarContent + ex. result from - file( "filename", FILE_IGNORE_NEW_LINES )

    +

    parse example 1

    +

    $config = array( "unique_id" => "kigkonsult.se", "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +.. . +

    +

    parse example 2

    +

    +$config = array( "unique_id" => "kigkonsult.se", + "url" => "http://www.ical.net/calendars/calendar.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +.. . +

    +

    parse example 3

    +

    $config = array( "unique_id" => "kigkonsult.se", + "url" => "http://www.ical.net/calendars/calendar.ics" ); +$vcalendar = new vcalendar( $config ); +... +$str = array( +"BEGIN:VCALENDAR", +"PRODID:-//test.org//NONSGML kigkonsult.se iCalcreator 2.10.15//", +"VERSION:2.0", +"BEGIN:VEVENT", +"DTSTART:20101224T190000Z", +"DTEND:20101224T200000Z", +"DTSTAMP:20101020T103827Z", +"UID:20101020T113827-1234GkdhFR@test.org", +"DESCRIPTION:example", +"END:VEVENT", +"END:VCALENDAR"); +$vcalendar->parse( $str ); +... +

    +

    merge example

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import" ); +$vcalendar = new vcalendar( $config ); + +$vcalendar->setConfig( "filename",  "file1.ics" ); +$vcalendar->parse(); + +$vcalendar->setConfig( "filename",  "file2.ics" ); +$vcalendar->parse(); + +$vcalendar->sort(); +$vcalendar->setConfig( "directory", "export" ); +$vcalendar->setConfig( "filename",  "icalmerge.ics" ); +$vcalendar->saveCalendar(); +.. . +

    +
    +[index] [top] [up] + +

    3.1.4.2 createCalendar

    +Generate and return calendar in a string, testing.. .? +

    Format

    +

    createCalendar()

    +

    Example

    +

    .. . +$str = $vcalendar->createCalendar(); +echo $str; +

    +
    +[index] [top] [up] + +

    3.1.4.3 returnCalendar

    +Redirect calendar content to user browser. Filename, addressed to browser, is automatically generated if missing or not set;
    +$filename = date( "YmdHis" ).".ics"
    +

    Format

    +

    returnCalendar( [bool utf8Encode [, bool gzip ]] )

    +

    utf8Encode = TRUE: utf8 encoded output, FALSE: (default) no encoding +gzip = TRUE: gzip compressed output and header "Content-Encoding: gzip" set, + FALSE: (default) no compressing

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +.. . +$vevent = & $vcalendar->newComponent( "vevent" ); +$vevent->setProperty( "dtstart", array( "year" => 2007 + , "month" => 4 + , "day" => 1 + , "hour" => 19 )); +$vevent->setProperty( "duration", 0, 0, 3 )); +$vevent->setProperty( "LOCATION", "Central Plaza" ); +$vevent->setProperty( "summary", "PHP summit" ); +.. . +$vcalendar->returnCalendar(); +

    +

    Example 2

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import", + "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$utf8Encode = TRUE; +$hacPar = "HTTP_ACCEPT_ENCODING"; +if( isset( $_SERVER[$hacPar] ) && + ( FALSE !== strpos( strtolower( $_SERVER[$hacPar] ), "gzip" ))) + $gzip = TRUE; +else + $gzip = FALSE; +$vcalendar->returnCalendar( $utf8Encode, $gzip ); +

    +
    +[index] [top] [up] + +

    3.1.4.4 saveCalendar

    +Save ical calendar in a file, uses present directory if directory not set, filename is automatically generated if missing or not set;
    +$filename = date( "YmdHis" ).".ics"
    +Directory/filename must be writeable, delimiter default PHP constant DIRECTORY_SEPARATOR. +

    +As long as php.ini directive "allow_url_fopen" is enabled, remote files, URLs; protocol "http" ("webcal"), are supported. Recommendation is to save to a local file and upload later, due to execution time and control. +

    +If file error occurs, FALSE is returned. +

    Format

    +

    saveCalendar ( string directory/FALSE + [, string filename/FALSE + [, string delimiter/FALSE ]] ) +

    +Parameters for directory/filename/delimiter, kept for backward compatibility, +may be removed i future versions. Recommendation is to use setConfig, Se +example below. +

    Example

    +

    .. . +$vcalendar->setConfig( array( "directory" => "depot", + "filename" => "calendar.ics" )); +$result = $vcalendar->saveCalendar(); +if( !$result ) +  echo "error when saving.. ." +

    +
    +[index] [top] [up] + +

    3.1.4.5 sort

    +

    Format 1

    +Sort created/parsed calendar components on the following (prioritized) keys:
    +1 - X-CURRENT-DTSTART - X-CURRENT-DTEND/X-CURRENT-DUE
    +    (if created in function selectComponents)
    +1 - DTSTART - DTEND alt. DURATION (VEVENT and VFREEBUSY components)
    +1 - DTSTART - DUE alt. DURATION (VTODO components)
    +1 - DTSTART (VJOURNAL components)
    +2 - CREATED / DTSTAMP
    +3 - UID
    +VTIMEZONE component(-s) are always sorted first in chain.
    +Sub-components (STANDARD / DAYLIGHT / ALARM), if exists, are not sorted. +

    sort()

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import", + "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$vcalendar->sort(); +$vcalendar->returnCalendar(); +

    + +

    Format 2

    +Sort created/parsed calendar components on specific property values and ascending order. +

    sort( sortArgument )

    +

    sortArgument: "ATTENDEE" / "CATEGORIES" / "DTSTAMP" / "LOCATION"" / + "ORGANIZER" /"RESOURCES" / "PRIORITY" / "STATUS" / "SUMMARY" + for a property where multiple ocurrence may exist + (ATTENDEE, CATEGORIES, RESOURCES ), + lowest (alphabetic) value is used as sort parameter. +

    +
    +[index] [top] [up] + +

    3.1.4.6 useCachedCalendar

    +If recent version of local (non-empty and saved) calendar file exists, an HTTP redirect header is sent otherwise FALSE is returned. +

    Format

    +

    useCachedCalendar( [ int timeout ] ) +useCachedCalendar( string directory/FALSE + , string filename/FALSE + , string delimiter/FALSE + [, int timeout ] ) +

    +

    timeout : default 3600 sec +Second format with parameters for directory/filename/delimiter,
    kept for backward compatibility, may be removed i future
    versions. Recommendation is to use setConfig, Se example below.

    +

    Example

    +

    .. . +$vcalendar->setConfig( "directory", "depot" ); +$vcalendar->setConfig( "filename", "calendar.ics" ); +$vcalendar->useCachedCalendar(); +

    +
    +[index] [top] [up] + +

    3.1.5 Calendar configuration functions

    + +

    3.1.5.1 configuration keys

    +All configuration keys (allowEmpty, compsInfo etc.) case independent. +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    keycalendarcomponentremark
    allowEmpty** 
    Compsinfo**getConfig only
    Delimiter*  
    Directory*  
    Filename*  
    Dirfile* getConfig only
    Filesize* getConfig only
    Format*  
    Language** 
    NewlineChar** 
    TZID** 
    Unique_id** 
    URL*  
    +
    +
    +[index] [top] [up] + +

    3.1.5.2 getConfig

    +

    getConfig( [string key )

    +

    Example 1

    +

    .. . +$filename = $vcalendar->getConfig( "filename" ); +.. .

    +In this example, notice Filename +
    +

    Example 2

    +

    .. . +$config = $vcalendar->getConfig(); +.. .

    +

    Output= array( string key => mixed value + *[, string key => mixed value] )

    +
    +[index] [top] [up] + +

    3.1.5.3 calendar/component initialization

    +

    Format

    +When creating a new calendar. +

    vcalendar( array( string key => mixed value *[,string key => mixed value]))

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import", + "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +.. . +

    +When creating a new calendar component. +

    component( array( string key => mixed value *[,string key => mixed value]))

    +

    Example 2

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vevent = new vevent( $config ); +.. . +

    +

    Example 3

    +

    . .. +$config = $vcalendar->getConfig(); +$vevent = new vevent( $config ); +.. . +

    +Only component relevant configuration are set. If using the newComponent function, configuration is set automatically. +
    +
    +[index] [top] [up] + +

    3.1.5.4 setConfig

    +A successful "setConfig" returns TRUE. +

    Format 1

    +

    setConfig( array( string key=>mixed value *[, string key=>mixed value]))

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import", + "filename" => "file.ics" ); +$vcalendar = new vcalendar(); +$vcalendar->setConfig( $config ); +.. . +

    +

    Example 2

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vevent = new vevent(); +$vevent->setConfig( $config ); +.. . +

    +

    Format 2

    +

    setConfig( string key, string value )

    +

    Example 1

    +

    $vcalendar = new vcalendar(); +$vcalendar->setConfig( "directory", "depot" ); +.. . +

    +

    Example 2

    +

    $vevent = new vevent(); +$vevent->setConfig( "unique_id", "kigkonsult.se" ); +.. . +

    +
    +[index] [top] [up] + +

    3.1.5.5 Allow empty components

    +Allow or reject empty calendar properties, default allow (TRUE). + +
    +[index] [top] [up] + +

    3.1.5.6 Component information

    +Only to use with function getConfig.
    +
    +Get information about calendar components. Returns array with basic information +about all components (in array format) within calendar. +

    Output = array ( *compinfo ) +compinfo = array ( "ordno" => int ordno, + // order number (1st=1, 2nd=2..) + , "type" => string type + // component type (vevent, vtodo.. . + , "uid" => string uid + // component UID (not for ALARM / TIMEZONE) + , "props" => + array( *[ propertyName => Property count ]) + // for every set property + , "sub" => array( *compinfo )) + // if subcomponents exists, an array for each subcomponent

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import", + "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$compsinfo = $vcalendar->getConfig( "compsinfo" ); +foreach( $compsinfo as compinfo) { + echo " order number : ".$compinfo["ordno"]."<br />"; + echo " type : ".$compinfo["type"]."<br />"; + echo " UID : ".$compinfo["uid"]."<br />"; + foreach( $compinfo["props"] as $propertyName => $propertyCount ) +  echo " $propertyName = $propertyCount"; + if( is_array( $compinfo["sub"] )) { +  foreach( $compinfo["sub"] as $subcompinfo ) { +   echo " order number : ".$subcompinfo["ordno"]."<br />"; +   /* .. dito if subcomponents exists .. . */ +  } + } +} +

    +
    +[index] [top] [up] + +

    3.1.5.7 Delimiter

    +Directory/filename delimiter. +
    +
    +Default PHP constant DIRECTORY_SEPARATOR. If used, must be set BEFORE filename! +
    +
    +[index] [top] [up] + +

    3.1.5.8 Directory

    +Local directory to store/read iCal files, default  ".". +
    +
    +Directory must be set BEFORE filename and must exist and be writeable otherwise FALSE is returned. +If set using an config array and together with Filename, Directory are set first. +When setting Directory any previously set URL is removed. +
    +
    +[index] [top] [up] + +

    3.1.5.9 Fileinfo

    +Only available in function getConfig, giving information in array format about directory, filename and filesize. +

    Example

    +

    $fileinfo = $vcalendar->getConfig( "fileinfo" );

    +

    output = array( <directory>, <filename>, <filesize> )

    +
    +[index] [top] [up] + +

    3.1.5.10 Filename

    +iCal local file name, default created like (if not set):
    +

    $filename = date( "YmdHis" ).".ics";

    +

    $filename = date( "YmdHis" ).".xml"; // if format set to "xcal"

    +
    +If not set, filename is created when requested, ex. in functions saveCalendar or getConfig("filename"). +
    +
    +Local filename must be set AFTER setting directory (and opt. delimiter)! +Filename (and opt. directory) must be readable/writeable otherwise FALSE is returned. +
    +
    +[index] [top] [up] + +

    3.1.5.11 Filesize

    +Only when getting configuration (using function getConfig).
    +Returns the size of the file in bytes, to be called
    +- after "saveCalendar()"
    +or
    +- after a "setConfig( "directory" / "filename" )" and before/after "parse()".
    +Getting the filesize for a remote file (URL) will always return zero. +
    +
    +[index] [top] [up] + +

    3.1.5.12 Format

    +Format for calendar output, "iCal"/"xCal", any case. +
    +"iCal" is default (rfc2445), "xCal" force xml formatted output. +
    +
    +[index] [top] [up] + +

    3.1.5.13 Language

    +Language for calendar and component TEXT value properties as defined in [RFC 1766]. +
    +
    +If NOT set in TEXT property parameters, language from "setConfig( "language", .." at component level will be used, if set, +otherwise language from "setConfig( "language", .." at calendar level will be used, if set. +
    +
    +[index] [top] [up] + +

    3.1.5.14 NewlineChar

    +Character(s) used for carriage return + line feed (CR+LF), default "\r\n". +
    +
    +[index] [top] [up] + +

    3.1.5.15 TZID

    +Default (local?) timezone, will be used if no TZID parameter is supplied when setting DTSTART, DTEND, DUE or RECURRENCE-ID (auto completion). +Note, some calendar software also require X-WR-TIMEZONE (to be set manually). +
    +
    +[index] [top] [up] + +

    3.1.5.16 Unique_id

    +Unique_id is used in property PRODID at calendar level and UID at component level, both created automatically, if not set. +

    PRODID +The identifier is RECOMMENDED to be the identical syntax to the +[RFC 822] addr-spec. A good method to assure uniqueness is to put the +domain name or a domain literal IP address of the host on which.. .

    +Default AUTOMATICALLY generated by using PHP function gethostbyname( $_SERVER["SERVER_NAME"] ) +when running in a web server environment or "localhost" when using command line interface. +Used when setting other (domain) name than server name. +
    +
    +A strong recommendation is always to set unique_id when creating a new vcalendar or component object, +to ensure accurate creation of all components UID property, also before parse, in case of missing UID. +
    +
    + +[index] [top] [up] + +

    3.1.5.17 URL

    +When managing remote files with URL (writing using saveCalendar(), or reading using parse()), +only protocol "http" ("webcal") is supported, +i.e. url must be prefixed by "http://" ("webcal://") and suffixed by a valid filename. +
    +When setting URL, any previously set Directory is removed.
    +The URL filename part can be retrieved by - getConfig( "filename" ). +
    +
    +When storing a remote iCal file locally, only directory need to be set, +filename remains unchanged (i.e. 1. set URL, 2. parse, 3. set directory). +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", + "url", "http://www.iCal.net/depot/calendar.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$vcalendar->sort(); +.. . +.. . +$vcalendar->setConfig( "directory", "depot" ); +$vcalendar->saveCalendar(); // local save in "depot" folder, + // using original filename +

    +
    +[index] [top] [up] + +

    3.2 Calendar component object property function list

    +

    +All calendar component property functions for get/set data.
    +For property format in detail, see +RFC2445 - Internet Calendaring and Scheduling Core Object Specification (iCalendar). +

    + +Notice: for properties and DATE-TIME with UTC time. +

    RFC2445: +The date with UTC time, or absolute time, is identified by a LATIN +CAPITAL LETTER Z suffix character (US-ASCII decimal 90), the UTC +designator, appended to the time value. For example, the following +represents January 19, 1998, at 0700 UTC: +

    +

    DTSTART:19980119T070000Z

    +

    The TZID property parameter MUST NOT be applied to DATE-TIME +properties whose time values are specified in UTC. +

    +

    + +Notice: date limitation.
    +Due to a limitation in PHP date functions, e.g. mktime, +strtotime, a date (e.g. while setting DTSTART property) +before "January 1 1970 00:00:00 GMT" may force a PHP date function to generate an error or set date to "January 1 1970". +

    +

    3.2.1 deleteProperty

    +General calendar delete property function,simplifying removal of calendar properties.
    +FALSE is returned if no property exists or when end-of-properties at consecutive function calls. +

    Format

    +

    deleteProperty( [ string PropName [, int order=1 ] )

    +

    propName - case independent, rfc2445 component property names, + unknown/missing propName will be used as X-property. +order - if missing 1st/next occurrence, + used with multiply (property) occurrences

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import", + "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$e = $vcalendar->getComponent( "vevent" ); +while( $e->deleteProperty( "comment" )) +  continue; // remove all COMMENT properties +.. . +

    +
    +[index] [top] [up] + +

    3.2.2 getProperty

    +General get property function, simplifying fetch of calendar properties.
    +FALSE is returned if no property exists or when end-of-properties at consecutive function calls. +

    Format

    +

    getProperty( string PropName [, int order=1 [, bool complete=FALSE ]] )

    +

    propName - case independent, rfc2445 component property names, + unknown/missing propName will be used as X-property. +order - if missing/FALSE 1st/next occurrence, + otherwise with multiply occurrences (1st=1, 2nd=2.. .) +complete - FALSE (default): output only property value + TRUE : output = + array("value" => <value> + ,"params"=> <parameter array>) +

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", + "directory" => "import", + "filename" => "file.ics" ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +while( $vevent = $vcalendar->getComponent( "vevent" )) { + $dtstart = $vevent->getProperty( "dtstart" ); // one occurrence + $description = $vevent->getProperty( "description" ); // one occurrence + while( $comment = $vevent->getProperty( "comment" )) { + // MAY occur more than once + .. . + } +.. . +

    +
    +[index] [top] [up] + +

    3.2.3 parse

    +Parse strict rfc2445 component property text and/or ALARMs. +

    +A rfc2445 strict formatted property text, in string or array format and starting with property name. +
    +
    +Complete ALARMs, all properties included, in array format and +first array row as "BEGIN:VALARM", last as "END:VALARM" +as well as TIMEZONE and standard/daylight subcomponents. +

    +FALSE is returned if (read-file) problems occur. +

    Format

    +

    parse( mixed propertyText )

    +

    +propertyText = string/array, + rcf2445 formatted property/properties, + property name must begin (first) line

    +

    example

    +

    .. . +$vevent = & $vcalendar->newComponent( "vevent" ); +$e->parse( "DTSTAMP:19970324T1200Z" ); +$e->parse( "SEQUENCE:0" ); +$e->parse( "ORGANIZER:MAILTO:jdoe@host1.com" ); +$e->parse( array( +"ATTENDEE;RSVP=TRUE:MAILTO:jsmith@host1.com", +"ATTENDEE;RSVP=TRUE:MAILTO:jsmith@host2.com", +"ATTENDEE;RSVP=TRUE:MAILTO:jsmith@host3.com", +"ATTENDEE;RSVP=TRUE:MAILTO:jsmith@host4.com" )); +$e->parse( "DTSTART:19970324T123000Z" ); +$e->parse( "DTEND:19970324T210000Z" ); +$e->parse( "CATEGORIES:MEETING,PROJECT" ); +$e->parse( "CLASS:PUBLIC" ); +$e->parse( "SUMMARY:Calendaring Interoperability Planning Meeting" ); +$e->parse( "STATUS:DRAFT" ); +$e->parse( array( + "DESCRIPTION:Project xyz Review Meeting Minutes" +,"Agenda" +,"1. Review of project version 1.0 requirements." +,"2. Definition of project processes." +,"3. Review of project schedule." +,"Participants: John Smith, Jane Doe, Jim Dandy" +,"- It was decided that the requirements need to be signed off by ". + "product marketing." +,"- Project processes were accepted." +,"- Project schedule needs to account for scheduled holidays and employee". + " vacation time. Check with HR for specific dates." +,"- New schedule will be distributed by Friday." +,"- Next weeks meeting is cancelled. No meeting until 3/23." )); +$e->parse( "LOCATION:LDB Lobby" ); +$e->parse( +"ATTACH;FMTTYPE=application/postscript:ftp://xyz.com/pub/conf/bkgrnd.ps" ); +$e->parse( array( +"BEGIN:VALARM", +"ACTION:AUDIO", +"TRIGGER;VALUE=DATE-TIME:19970224T070000Z", +"ATTACH;FMTTYPE=audio/basic:http://host.com/pub/audio-files/ssbanner.aud", +"REPEAT:4", +"DURATION:PT1H", +"X-alarm:non-standard ALARM property", +"END:VALARM" )); +$e->parse( +"X-xomment:non-standard property will be displayed, comma escaped"); +.. . +

    +
    +[index] [top] [up] + + +

    3.2.4 setProperty

    +General set property function, simplifying insert of component properties. For properties where +multiple occurrences are allowed, last parameter is an index, implementing replaceProperty functionality.
    +A successful update returns TRUE. +

    Format

    +

    setProperty( string PropName, mixed Proparg_1 *[, mixed Proparg_n] )

    +

    propName case independent, rfc2445 component property names, +unknown propName will be regarded as X-property.

    +

    Example

    +

    $vevent = & $vcalendar->newComponent( "vevent" ); +$vevent->setProperty( "dtstart" + , array("year"=>2007,"month"=>4,"day"=>1,"hour"=>19)); +$vevent->setProperty( "duration", 0, 0, 3 )); +$vevent->setProperty( "LOCATION", "Central Plaza" ); +$vevent->setProperty( "summary", "PHP summit" ); +.. . +

    +
    +[index] [top] [up] + + +

    3.2.5 ACTION

    +This property defines the action to be invoked when an VALARM is triggered,
    "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE". This property is REQUIRED and MUST NOT occur more than once. +
    Delete ACTION
    +Remove ACTION from component. +

    Format

    +

    deleteProperty( "Action" )

    +

    Example

    +

    $valarm->deleteProperty( "Action" );

    +
    Get ACTION
    +Fetch property value. +

    Format 1

    +

    getProperty( "Action" )

    +

    output = actionValue 1

    +

    Format 2

    +

    getProperty( "Action", FALSE , TRUE )

    +

    output = array( "value" => actionValue1 + , "params" => xparam2 )

    +

    Example

    +

    $action = $valarm->getProperty( "action" );

    +
    Set ACTION
    +Insert property value. +

    Format

    +

    setProperty( "Action", actionValue [, xparam ] )

    +

    actionValue1 = one of "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE" +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $valarm->setProperty( "action", "DISPLAY" );

    +
    +[index] [top] [up] + + +

    3.2.6 ATTACH

    +The property provides the capability to associate a document object with a calendar component. The property is +is REQUIRED and MUST NOT occur more than once in an "ALARM" ("ACTION" "procedure"), +OPTIONAL and MUST NOT occur more than once in an "ALARM" ("ACTION" "audio") and +OPTIONAL and MAY occur more than once in VEVENT, VTODO, VJOURNAL and VALARM ("ACTION" "email") components. +

    +The default value type for ATTACH is URI. The value type can also be set to BINARY to indicate inline binary encoded content information (params 2). +
    Delete ATTACH
    +Remove ATTACH from component. +

    Format

    +

    deleteProperty( "ATTACH" )

    +

    Example 1

    +

    $valarm->deleteProperty( "ATTACH" );

    +

    Example 2

    +Delete ATTACH property no 2. +

    $valarm->deleteProperty( "ATTACH", 2 );

    +

    Example 3

    +Deleting all ATTACH properties. +

    while( $valarm->deleteProperty( "ATTACH" )) + continue;

    +
    Get ATTACH
    +Fetch property value. +

    Format 1

    +

    getProperty( "Attach" )

    +

    output = attachValue1

    +

    Format 2

    +

    getProperty( "ATTACH", propOrderNo/FALSE , TRUE )

    +

    output = array( "value" => attachValue1 + , "params" => params2 )

    +

    Format 3

    +

    getProperty( "Attach", propOrderNo )

    +

    Get propOrderNo ATTACH

    +

    Example

    +

    $attach = $valarm->getProperty( "attach" );

    +
    Set ATTACH
    +Insert property value. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "attach", attachValue1 [, params [, propOrderNo ]] )

    +

    attachValue1 = URI / inline binary encoded content information. +params2 = array( [ "ENCODING" => "BASE64", "VALUE" => "BINARY" ] + [, "FMTTYPE" => contentType ] + [, xparam ] ) +contentType = The parameter value MUST be the TEXT for either + an IANA registered content type or a non-standard + content type. +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $vevent->setProperty( "attach" + , "ftp://domain.com/pub/docs/agenda.doc" + , array( "FMTTYPE" => "application/binary" )); +

    +
    +[index] [top] [up] + + +

    3.2.7 ATTENDEE

    +The property defines an "Attendee" within a calendar component and is OPTIONAL and MUST NOT occur more than once +in a VALARM ("ACTION" "email"), OPTIONAL and MAY occur more than once in VEVENT, VTODO, VJOURNAL and VFREEBUSY components. +

    +This value type for ATTENDEE is URI, a calendar user address. +
    Delete ATTENDEE
    +Remove ATTENDEE from component. +

    Format

    +

    deleteProperty( "ATTENDEE" )

    +

    Example 1

    +

    $valarm->deleteProperty( "ATTENDEE" );

    +

    Example 2

    +Delete ATTENDEE property no 2. +

    $valarm->deleteProperty( "ATTENDEE", 2 );

    +

    Example 3

    +Deleting all ATTENDEE properties. +

    while( $valarm->deleteProperty( "ATTENDEE" )) + continue;

    +
    Get ATTENDEE
    +Fetch property value. +

    Format 1

    +

    getProperty( "Attendee" )

    +

    output = attendeeValue 1

    +

    Format 2

    +

    getProperty( "ATTENDEE", propOrderNo/FALSE , TRUE )

    +

    output = array( "value" => attendeeValue1 + , "params" => array( params2 ))

    +

    Format 3

    +

    getProperty( "ATTENDEE", propOrderNo )

    +

    Get propOrderNo ATTENDEE

    +

    Example

    +

    $attendee = $valarm->getProperty( "attendee" );

    +
    Set ATTENDEE
    +Insert property value. If exist, default parameter values are removed after input (params2). +Property value must be prefixed by protocol (ftp://, http://,mailto:, file://.. . ref. rfc 1738 ), if missing, "mailto:" is set (ex. indicating an internet mail address). +Also MEMBER and DIR parameters must be prefixed by protocol. DELEGATED-TO, DELEGATED-FROM, SENT-BY parameters must use protocol "mailto:", prefixed if missing (ex. indicating an internet mail address). +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "attendee", attendeeValue [, params [, propOrderNo ]] )

    +

    attendeeValue1 = a calendar user address, a URI as defined by + [RFC 1738] or any other IANA registered form + for a URI. +params2 = array( [CUTYPE] [,MEMBER] [,ROLE] [,PARTSTAT] + [,RVSP] [,DELEGATED-TO] [,DELEGATED-FROM] + [,SENT-BY] [,CN] [,DIR] [,LANGUAGE] + [,xparams] ) +CUTYPE = "CUTYPE" => "INDIVIDUAL" + (An individual, Default) + / "GROUP" + (A group of individuals) + / "RESOURCE" + (A physical resource) + / "ROOM" + (A room resource) + / "UNKNOWN" + (Otherwise not known) + / x-name + (Experimental type) + / iana-token + (Other IANA registered type) +MEMBER = "MEMBER" => array( *[ "single member + of the group or list membership"]) +ROLE = "ROLE" => "CHAIR" + (Indicates chair of the calendar + entity) + / "REQ-PARTICIPANT" + (required participation, Default) + / "OPT-PARTICIPANT" + (optional participation) + / "NON-PARTICIPANT" + (information purposes only) + / x-name + (Experimental role) + / iana-token + (Other IANA role) +PARTSTAT = "PARTSTAT" => "NEEDS-ACTION" + (Event needs action, Default) + / "ACCEPTED" + (Event accepted) + / "DECLINED" + (Event declined) + / "TENTATIVE" + (Event tentatively accepted) + / "DELEGATED" + (Event delegated) + / "NEEDS-ACTION" + (To-do needs action, Default) + / "ACCEPTED" + (To-do accepted) + / "DECLINED" + (To-do declined) + / "TENTATIVE" + (To-do tentatively accepted) + / "DELEGATED" + (To-do delegated) + / "COMPLETED" + (To-do completed. + COMPLETED property + has date/time completed) + / "IN-PROCESS" + (To-do in process of being completed) + / "NEEDS-ACTION" + (Journal needs action, Default) + / "ACCEPTED" + (Journal accepted) + / "DECLINED" + (Journal declined) + / x-name + (Experimental status) + / iana-token + (Other IANA registered status) +RSVP = "RSVP" => "TRUE" + / "FALSE", Default (reply expectation) +DELEGATED-TO = "DELEGATED-TO" => array(*["single calendar user + to specified by the property + has delegated participation"]) +DELEGATED-FROM = "DELEGATED-FROM" => array( *[ "single calendar user that + have delegated their + participation to the + calendar user specified + by the property" ] ) +SENT-BY = "SENT-BY" => single calendar user that is + acting on behalf + of the calendar user + specified by the property" +LANGUAGE = "LANGUAGE" => language for text values in CN parameter" +CN = "CN" => "common name to be associated with the calendar + user specified by the property" +DIR = "DIR" => "reference to a directory entry associated with + the calendar user specified by the property" +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +See rules in detail in RFC2445 - Internet Calendaring and Scheduling Core Object Specification (iCalendar). +

    Example

    +

    $vevent->setProperty( "attendee" + , "attendee1@ical.net" +$vevent->setProperty( "attendee" + , "attendee2@ical.net" + , array( "cutype" => "INDIVIDUAL" + , "member" => array( "member1@ical.net" + , "member2@ical.net" + , "member3@ical.net" ) + , "role" => "CHAIR" + , "PARTSTAT" => "ACCEPTED" + , "RSVP" => "TRUE" + , "DELEgated-to" => array( "part1@ical.net" + , "part2@ical.net" + , "part3@ical.net" ) + , "delegateD-FROM" =>array( "cio@ical.net" + , "vice.cio@ical.net") + , "SENT-BY" => "secretary@ical.net" + , "LANGUAGE" => "us-EN" + , "CN" => "John Doe" + , "DIR" => "http://www.ical.net/info.doc" + , "x-agenda" => "status reports" // xparam + , "x-length" => "15 min" )); // xparam +

    +
    +[index] [top] [up] + +

    3.2.8 CATEGORIES

    +This property defines the categories for a calendar component and is OPTIONAL and MAY occur more than once in VEVENT, VTODO and VJOURNAL components.

    +The value type for CATEGORIES is TEXT. +
    Delete CATEGORIES
    +Remove CATEGORIES from component. +

    Format

    +

    deleteProperty( "CATEGORIES" )

    +

    Example 1

    +

    $vevent->deleteProperty( "CATEGORIES" );

    +

    Example 2

    +Delete CATEGORIES property no 2. +

    $vevent->deleteProperty( "CATEGORIES", 2 );

    +

    Example 3

    +Deleting all CATEGORIES properties. +

    while( $vevent->deleteProperty( "CATEGORIES" )) + continue;

    +
    Get CATEGORIES
    +Fetch property value. +

    Format 1

    +

    getProperty( "CATEGORIES" )

    +

    output = categoryValue1

    +

    Format 2

    +

    getProperty( "CATEGORIES", propOrderNo/FALSE , TRUE )

    +

    output = array( "value" => categories1 + , "params" => params2 )

    +

    Format 3

    +

    getProperty( "CATEGORIES", propOrderNo )

    +

    Get propOrderNo CATEGORIES

    +

    Example

    +

    $categories = $valarm->getProperty( "categories" );

    +
    Set CATEGORIES
    +Insert property value. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "categories", mixed categories [, params [, propOrderNo ]] )

    +

    categories1 = string categoryValue / array( *categoryValue ) +categoryValue = textual categories or subtypes of the calendar component, + can be specified as a list of categories + separated by the COMMA character +params2 = array( ["LANGUAGE" => "<lang>"][, xparam] ) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $vevent->setProperty( "categories", "project_x" );

    +
    +[index] [top] [up] + + +

    3.2.9 CLASS

    +This property defines the access classification for a calendar component and is OPTIONAL +and MUST NOT occur more than once in VEVENT, VTODO and VJOURNAL components. +
    Delete CLASS
    +Remove CLASS from component. +

    Format

    +

    deleteProperty( "CLASS" )

    +

    Example

    +

    $vjournal->deleteProperty( "CLASS" );

    +
    Get CLASS
    +Fetch property value. +

    Format 1

    +

    getProperty( "CLASS" )

    +

    output = classValue1

    +

    Format 2

    +

    getProperty( "CLASS", FALSE , TRUE )

    +

    output = array "value" => classValue1 + , "params" => xparam2 )

    +

    Example

    +

    $class = $valarm->getProperty( "class" );

    +
    Set CLASS
    +Insert property value. +

    Format

    +

    setProperty( "class", string classvalue [, xparam ] )

    +

    classvalue1 = "PUBLIC" + / "PRIVATE" + / "CONFIDENTIAL" + / iana-token + / x-name +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $vevent->setProperty( "class", "CONFIDENTIAL" );

    +
    +[index] [top] [up] + +

    3.2.10 COMMENT

    +This property specifies non-processing information intended to provide a comment to the calendar user +and is OPTIONAL and MAY occur more than once in VEVENT, VTODO, VJOURNAL, VFREEBUSY, STANDARD and DAYLIGHT components.

    +The value type for COMMENT is TEXT. +
    Delete COMMENT
    +Remove COMMENT from component. +

    Format

    +

    deleteProperty( "COMMENT" )

    +

    Example 1

    +

    $vevent->deleteProperty( "COMMENT" );

    +

    Example 2

    +Delete COMMENT property no 2. +

    $vevent->deleteProperty( "COMMENT", 2 );

    +

    Example 3

    +Deleting all COMMENT properties. +

    while( $vevent->deleteProperty( "COMMENT" )) + continue;

    +
    Get COMMENT
    +Fetch property value. +

    Format 1

    +

    getProperty( "COMMENT" )

    +

    output = commentValue1

    +

    Format 2

    +

    getProperty( "COMMENT", propOrderNo/FALSE , TRUE )

    +

    output = array( "value" => commentValue1 + , "params" => params2 )

    +

    Format 3

    +

    getProperty( "COMMENT", propOrderNo )

    +

    Get propOrderNo COMMENT

    +

    Example

    +

    $comment = $vevent->getProperty( "comment" );

    +
    Set COMMENT
    +Insert property value. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "comment", commentValue [, params [, propOrderNo ]] )

    +

    commentValue1 = Value type Text +params2 = array( ["ALTREP" => "<an alternate text representation, URI>"] + [, "LANGUAGE" => "<lang>"] + [, xparam ] ) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $vevent->setProperty( "comment", "this is a comment" );

    +
    +[index] [top] [up] + + +

    3.2.11 COMPLETED

    +This property defines the date and time that a VTODO was actually completed and is OPTIONAL and MUST NOT occur more than once.

    +The value type for COMPLETED is UTC DATE-TIME. +
    Delete COMPLETED
    +Remove COMPLETED from component. +

    Format

    +

    deleteProperty( "COMPLETED" )

    +

    Example

    +

    $vtodo->deleteProperty( "COMPLETED" );

    +
    Get COMPLETED
    +Fetch property value. +

    Format 1

    +

    getProperty( "COMPLETED" )

    +

    output = completedDate1

    +

    Format 2

    +

    getProperty( "COMPLETED", FALSE , TRUE )

    +

    output = array( "value" => completedDate1 + , "params" => xparam2 )

    +

    Example

    +

    $completed = $vtodo->getProperty( "completed" );

    +
    Set COMPLETED
    +Insert property value. Input date is always a UTC DATE-TIME or, +if "offset" parameter is used, converted to a UTC DATE-TIME. +Notice, use function transformDateTime to change a datetime from local to UTC datetime. +

    Format

    +

    setProperty( "completed", completedDate [, xparam ] )

    +

    completedDate1 = array( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + , "tz" => offset ]] ) +completedDate = int year + , int month + , int day + [, int hour + , int min + , int sec ] +completedDate = array( int year + , int month + , int day + [, int hour + , int min + , int sec + [, offset ]] ) +completedDate = array ( "timestamp" => int timestamp [, "tz" => offset]) +completedDate = string datestring // string date, + acceptable by strtotime function, + ex.  "14 august 2006 16.00.00" + (notice date restriction) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se"); +$vcalendar = new vcalendar( $config ); +$vtodo = & $vcalendar->newComponent( "vtodo" ); +.. . +$vtodo->setProperty( "completed", 2006, 8, 10, 10, 0, 0 ); + // 10 august 2006 10.00 UTC

    +

    Example 2

    +

    $date = array("year" => 2006, "month" => 10, "day" => 10, + "hour" => 10, "min" => 0, "sec" => 0, "tz" => "+0200"); + // local date + UTC offset => UTCDATE-TIME +$vtodo->setProperty( "completed", $date );

    +
    +[index] [top] [up] + + +

    3.2.12 CONTACT

    +The property is used to represent textual contact information or alternately a reference to textual contact information associated with the calendar component. The property is OPTIONAL and MUST NOT occur more than once in a VFREEBUSY or MAY occur more than once in VEVENT, VTODO and VJOURNAL components.

    +The value type for CONTACT is TEXT. +
    Delete CONTACT
    +Remove CONTACT from component. +

    Format

    +

    deleteProperty( "CONTACT" )

    +

    Example 1

    +

    $vevent->deleteProperty( "CONTACT" );

    +

    Example 2

    +Delete CONTACT property no 2. +

    $vevent->deleteProperty( "CONTACT", 2 );

    +

    Example 3

    +Deleting all CONTACT properties. +

    while( $vevent->deleteProperty( "CONTACT" )) + continue;

    +
    Get CONTACT
    +Fetch property value. +

    Format 1

    +

    getProperty( "CONTACT" )

    +

    output = contactValue1

    +

    Format 2

    +

    getProperty( "CONTACT", propOrderNo/FALSE , TRUE )

    +

    output = array( "value" => contactValue1 + , "params" => params2 )

    +

    Format 3

    +

    getProperty( "CONTACT", propOrderNo )

    +

    Get propOrderNo CONTACT

    +

    Example

    +

    $contact = $vevent->getProperty( "contact" );

    +
    Set CONTACT
    +Insert property value. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setproperty( "contact", contactValue [, params [, propOrderNo ]] )

    +

    contactValue1 = Value type TEXT +params2 = array ( ["ALTREP" => "<an alternate text representation, URI>"] + [, "LANGUAGE" => "<lang>"] + [, xparam] ) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $c->setProperty( "contact", "tel 012-34 56 789" )

    +
    +[index] [top] [up] + + +

    3.2.13 CREATED

    +This property specifies the date and time that the calendar information was created by the calendar user agent in the calendar store. Note: This is analogous to the creation date and time for a file in the file system. The property is OPTIONAL and MUST NOT occur more than once in VEVENT, VTODO and VJOURNAL components.

    +The value type for CREATED is UTC DATE-TIME. +
    Delete CREATED
    +Remove CREATED from component. +

    Format

    +

    deleteProperty( "CREATED" )

    +

    Example

    +

    $vevent->deleteProperty( "CREATED" );

    +
    Get CREATED
    +Fetch property value. +

    Format 1

    +

    getProperty( "CREATED" )

    +

    output = createdDate1

    +

    Format 2

    +

    getProperty( "CREATED", FALSE , TRUE )

    +

    output = array( "value" => createdDate1 + , "params" => xparam2 )

    +

    Example

    +

    $created = $vevent->getProperty( "CREATED" );

    +
    Set CREATED
    +Insert property value. Input date is always a UTC DATE-TIME or, +if "offset" parameter is used, converted to a UTC DATE-TIME. +Notice, use function transformDateTime to change a datetime from local to UTC datetime. +

    Format

    +

    setProperty( "created", [ createdDate [, xparam ]] )

    +

    createdDate1 = array( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + , "tz" => offset ]] ) +createdDate = int year + , int month + , int day + [, int hour + , int min + , int sec ] +createdDate = array( int year + , int month + , int day + [, int hour + , int min + , int sec + [, offset ]] ) +createdDate = array ( "timestamp" => int timestamp [, "tz" => offset ]) +createdDate = string datestring // string date, + acceptable by strtotime function, + ex.  "14 august 2006 16.00.00" + (notice date restriction) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vtodo = & $vcalendar->newComponent( "vtodo" ); +.. . +$vtodo->setProperty( "created", 2006, 8, 11, 14, 30, 35 ); + // 11 august 2006 14.30.35 UTC

    +

    Example 2

    +

    $date = array("year" => 2006, "month" => 10, "day" => 10, + "hour" => 10, "min" => 0, "sec" => 0, "tz" => "+0200"); + // local date + UTC offset => UTC DATE-TIME +$vtodo->setProperty( "created", $date ); +.. .

    +

    Example 3

    +

    $vevent->setProperty( "created" ); + // current UTC date-time is set if called without parameters

    +
    +[index] [top] [up] + + +

    3.2.14 DESCRIPTION

    +This property provides a more complete textual description of the calendar component, than that provided by the SUMMARY property (, analogous to a mail BODY). The property is OPTIONAL, MUST NOT occur more than once within VEVENT, VTODO or VALARM (PROCEDURE) but can be specified multiple times within a VJOURNAL calendar component. The property is REQUIRED in VALARM (DISPLAY, EMAIL) component.

    +The value type for DESCRIPTION is TEXT. +
    Delete DESCRIPTION
    +Remove DESCRIPTION from component. +

    Format

    +

    deleteProperty( "DESCRIPTION" )

    +

    Example 1

    +

    $vevent->deleteProperty( "DESCRIPTION" );

    +

    Example 2

    +Delete DESCRIPTION property no 2. +

    $vjournal->deleteProperty( "DESCRIPTION", 2 );

    +

    Example 3

    +Deleting all DESCRIPTION properties. +

    while( $vjournal->deleteProperty( "DESCRIPTION" )) + continue;

    +
    Get DESCRIPTION
    +Fetch property value. +

    Format 1

    +

    getProperty( "DESCRIPTION" )

    +

    output = descriptionValue1

    +

    Format 2

    +

    getProperty( "DESCRIPTION", FALSE , TRUE )

    +

    output = array( "value" => descriptionValue1 + , "params" => params2 )

    +

    Example

    +

    $description = $vevent->getProperty( "description" );

    +
    Set DESCRIPTION
    +Insert property value. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "description", descriptionValue [, params [, propOrderNo ]] )

    +

    descriptionValue1 = Value type TEXT +params2 = array( ["ALTREP" => "<an alternate text representation, URI>"] + [, "LANGUAGE" => "<lang>"] + [, xparam] ) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $vevent->setProperty( "description", "This is a description" );

    +
    +[index] [top] [up] + + +

    3.2.15 DTEND

    +This property specifies the date and time that a calendar component ends. +The property is OPTIONAL and MUST NOT occur more than once in VFREEBUSY and VEVENT. In VEVENT, it only occurs +if DURATION NOT occurs. +

    +The default value type for DTEND is DATE-TIME, can be set to a DATE value type. +

    +Notice that an end date without a time is in effect midnight of the day before the date, so for timeless dates, use the date following the event date for it to be correct. For an "all-day event" and using timeless dates, the DTEND is equal DTSTART plus one day, example all-day event (2007-12-01)
    DTSTART;VALUE=DATE:20071201
    DTEND;VALUE=DATE:20071202. +
    Delete DTEND
    +Remove DTEND from component. +

    Format

    +

    deleteProperty( "DTEND" )

    +

    Example

    +

    $vevent->deleteProperty( "DTEND" );

    +
    Get DTEND
    +Fetch property value. +

    Format 1

    +

    getProperty( "DTEND" )

    +

    output = dtendDate1

    +

    Format 2

    +

    getProperty( "DTEND", FALSE , TRUE )

    +

    output = array( "value" => dtendDate1 + , "params" => params2 )

    +

    Example

    +

    $dtend = $vevent->getProperty( "dtend" );

    +
    Set DTEND
    +Insert property value. If DATE value type is expected, "VALUE" = "DATE" must be set +(in params2) otherwise DATE-TIME (default) value type is set. +
    +
    +If no timezone parameter (tz or tzidparam below) is set (then local time is assumed) and config TZID is set, +date-time values will be set WITH timezone from config. Notice, use function transformDateTime +to change a datetime from a time zone to another. +
    +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "dtend", dtendDate [, params2 ] ) +

    dtendDate1 = array ( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + [, "tz" => mixed tz ]] ) +dtendDate = int year + , int month + , int day + [, int hour + , int min + , int sec + [, mixed tz ]] +dtendDate = array( int year + , int month + , int day + [, int hour + , int min + , int sec + [, mixed tz ]] ) +dtendDate = array( "timestamp" => int timestamp [,"tz" => mixed tz]) +dtendDate = string datestring // string date, + acceptable by strtotime function, + ex. "14 august 2006 16.00.00" + (notice date restriction) +dtendDate : Within the "VFREEBUSY" calendar component, + the time MUST be specified in the UTC time format. +tz = <timezone identifier> / UTC offset + (timezone will be used as tzidparam (below), if tzidparam not set) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +params2 = array([ tzidparam/datetimeparam/dateparam ] *[,xparams]) +tzidparam = "TZID" => <timezone identifier> + // output as local date-time with timezone identifier +datetimeparam = "VALUE" => "DATE-TIME" // default, output as date-time +dateparam = "VALUE" => "DATE" // output as DATE, ex. all-day event +xparams = xparamkey => xparamvalue

    +

    Example 1

    +

    $vevent->setProperty( "dtend" + , 2006, 8, 11, 16, 30, 0 ); + // 11 august 2006 16.30.00 local date

    +

    Example 2

    +

    $vfreebusy->setProperty( "dtend" + , 2006, 8, 11, 16, 30, 0, "-040000" ); + // 11 august 2006 16.30.00 -040000 : + local date + UTC offset => UTC DATE-TIME

    +

    Example 3

    +

    $vevent->setProperty( "dtend" + , array( "year" =>, 2006, "month" => 8, "day"=> 11 ) + , array( "VALUE" => "DATE" )); + // end of one or more all-day events

    +
    +[index] [top] [up] + + +

    3.2.16 DTSTAMP

    +The property indicates the date/time that the instance of the iCalendar object was created and is OPTIONAL and MUST NOT occur more than once in VEVENT, VTODO, VJOURNAL and VFREEBUSY components. However, DTSTAMP is AUTOMATICALLY GENERATED in iCalcreator. +

    +DTSTAMP may be required when importing iCal files into some calendaring software
    (MS etc.), +as well as (calendar) x-properties "X-WR-CALNAME", "X-WR-CALDESC" and
    "X-WR-TIMEZONE", +METHOD property (value PUBLISH etc.) and the (also created) UID property. +

    +The value type for DTSTAMP is UTC DATE-TIME. +
    Delete DTSTAMP
    +If DTSTAMP if removed from a component, DTSTAMP will automatically be recreated when calendar output functions like createCalendar, returnCalendar or saveCalendar is executed. +

    Format

    +

    deleteProperty( "DTSTAMP" )

    +

    Example

    +

    $vevent->deleteProperty( "DTSTAMP" );

    +
    Get DTSTAMP
    +Fetch property value. +

    Format 1

    +

    getProperty( "DTSTAMP" )

    +

    output = dtstampDate1

    +

    Format 2

    +

    getProperty( "DTSTAMP", FALSE , TRUE )

    +

    output = array( "value" => dtstampDate1 + , "params" => xparam2 )

    +

    Example

    +

    $dtstamp = $vevent->getProperty( "dtstamp" );

    +
    Set DTSTAMP
    +Insert property value. Input date is always a UTC DATE-TIME or, +if "offset" parameter is used, converted to a UTC DATE-TIME. +Notice, use function transformDateTime to change a datetime To UTC. +

    Format

    +

    setProperty( "dtstamp", dtstampDate [, xparam ] )

    +

    dtstampDate1 = array( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + , "tz" => offset ]] ) +dtstampDate = int year + , int month + , int day + [, int hour + , int min + , int sec ] +dtstampDate = array( int year + , int month + , int day + [, int hour + , int min + , int sec + [, offset ]] ) +dtstampDate = array ( "timestamp" => int timestamp [, "tz" => offset ]) +dtstampDate = string datestring // string date, + acceptable by strtotime function, + ex.  "14 august 2006 16.00.00" + (notice date restriction) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vtodo = & $vcalendar->newComponent( "vtodo" ); +.. . +$vtodo->setProperty( "dstamp" + , 2006, 8, 11, 7, 30, 1 ); + // 11 august 2006 07.30.01 UTC

    +

    Example 2

    +

    $date = array("year" => 2006, "month" => 10, "day" => 10, + "hour" => 10, "min" => 0, "sec" => 0, "tz" => "+0200"); + // local date + UTC offset => UTC DATE-TIME +$vtodo->setProperty( "dtstamp", $date ); +.. .

    +
    +[index] [top] [up] + +

    3.2.17 DTSTART

    +This property specifies when the calendar component begins.
    +The property is OPTIONAL and MUST NOT occur more than once in VEVENT, VTODO, VJOURNAL and VFREEBUSY components.
    +The property is REQUIRED, but MUST NOT occur more than once in STANDARD and DAYLIGHT components.

    +The default value type for DTSTART is DATE-TIME, can be set to a DATE value type.

    +For an "all-day event" and using timeless dates, example (2007-12-01)
    +DTSTART;VALUE=DATE:20071201
    +DTEND;VALUE=DATE:20071202. // opt., in effect midnight of the day before the date!! +
    Delete DTSTART
    +Remove DTSTART from component. +

    Format

    +

    deleteProperty( "DTSTART" )

    +

    Example

    +

    $vevent->deleteProperty( "DTSTART" );

    +
    Get DTSTART
    +Fetch property value. +

    Format 1

    +

    getProperty( "DTSTART" )

    +

    output = dtstartDate1

    +

    Format 2

    +

    getProperty( "DTSTART", FALSE , TRUE )

    +

    output = array( "value" => dtstartDate1 + , "params" => params2 )

    +

    Example

    +

    $dtstart = $vevent->getProperty( "dtstart" );

    +
    Set DTSTART
    +Insert property value. If DATE value type is expected, "VALUE" = "DATE" must be set (in params2) otherwise DATE-TIME (default) value type is set. +
    +
    +If no timezone parameter (tz or tzidparam below) is set (then local time is assumed) and config TZID is set, +date-time values will be set WITH timezone from config. Notice, use function transformDateTime +to change a datetime from a time zones to another. +
    +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "dtstart", dtstartDate [, params ] )

    +

    dtstartDate1 = array( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + [, "tz" => mixed tz ]] ) +dtstartDate = int year + , int month + , int day + [, int hour + , int min + , int sec + [, mixed tz ]] +dtstartDate = array( int year + , int month + , int day + [, int hour + , int min + , int sec + [, mixed tz ]] ) +dtstartDate = array("timestamp" => int timestamp [, "tz" => mixed tz]) +dtstartDate = string datestring // string date, + acceptable by strtotime function, + ex. "14 august 2006 16.00.00" + (notice date restriction) +dtstartDate : Within the "VFREEBUSY" calendar component, + the dtstartDate MUST be specified in the UTC time format. +tz = <timezone identifier> / offset + (timezone will be used as tzidparam (below), if tzidparam not set) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +params2 = array([ tzidparam/datetimeparam/dateparam ] *[, xparams]) +tzidparam = "TZID" => <timezone identifier> + // output as local date-time with timezone identifier +datetimeparam = "VALUE" => "DATE-TIME" // default, output as date-time +dateparam = "VALUE" => "DATE" // output as DATE, ex. all-day event +xparams = xparamkey => xparamvalue

    +

    Example 1

    +

    $vevent->setProperty( "dstart" + , 2006, 8, 11, 7, 30, 1 ); + // 11 august 2006 07.30.01 local date

    +

    Example 2

    +

    $vevent->setProperty( "dstart" + , 2006, 8, 11, 16, 30, 0, "-040000" ); + // 11 august 2006 16.30.00 -040000, + // local date + UTC offset => UTC DATE-TIME

    +

    Example 3

    +

    $vevent->setProperty( "dtstart" + , array( "year" =>, 2006, "month" => 8, "day"=> 11 ) + , array( "VALUE" => "DATE" )); + // start of an all-day event, or a period of (entire) days

    +
    +[index] [top] [up] + + +

    3.2.18 DUE

    +This property defines the date and time when a VTODO is expected to be completed +and is OPTIONAL and MUST NOT occur more than once and only if DURATION NOT occurs.

    +The default value type for DUE is DATE-TIME, can be set to a DATE value type. +
    Delete DUE
    +Remove DUE from component. +

    Format

    +

    deleteProperty( "DUE" )

    +

    Example

    +

    $vtodo->deleteProperty( "DUE" );

    +
    Get DUE
    +Fetch property value. +

    Format 1

    +

    getProperty( "DUE" )

    +

    output = dueDate1

    +

    Format 2

    +

    getProperty( "DUE", FALSE , TRUE )

    +

    output = array( "value" => dueDate1 + , "params" => params2 )

    +

    Example

    +

    $due = $vtodo->getProperty( "due" );

    +
    Set DUE
    +Insert property value. If DATE value type is expected, "VALUE" = "DATE" must be set +(in params2) otherwise DATE-TIME (default) value type is set. +
    +
    +If no timezone parameter (tz or tzidparam below) is set (then local time is assumed) and config TZID is set, +date-time values will be set WITH timezone from config. Notice, use function transformDateTime +to change a datetime from a time zone to another. +
    +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "due", dueDate [, params ] )

    +

    dueDate1 = array( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + [, "tz" => mixed tz ]] ) +dueDate = int year + , int month + , int day + [, int hour + , int min + , int sec + [, mixed tz ]] +dueDate = array( int year + , int month + , int day + [, int hour + , int min + , int sec + [, mixed tz ]] ) +dueDate = array( "timestamp" => int timestamp [, "tz" => mixed tz]) +dueDate = string datestring // string date, + acceptable by strtotime function, + ex. "14 august 2006 16.00.00" + (notice date restriction) +tz = <timezone identifier> / offset + (timezone will be used as tzidparam (below), if tzidparam not set) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +params2 = array([ tzidparam/datetimeparam/dateparam ] *[, xparams]) +tzidparam = "TZID" => <timezone identifier> + // output as local date-time with timezone identifier +datetimeparam = "VALUE" => "DATE-TIME" // default, output as date-time +dateparam = "VALUE" => "DATE" // output as DATE, "during the day" +xparams = xparamkey => xparamvalue

    +

    Example 1

    +

    $vtodo->setProperty( "due" + , 2006, 8, 11, 18, 0, 0 ); + // 11 august 2005 18.00.00 local date

    +

    Example 2

    +

    $vtodo->setProperty( "due" + , 2006, 8, 11, 16, 30, 0, "-040000" ); + // 11 august 2006 16.30.00 -040000 + // local date + UTC offset sets UTCDATE-TIME

    +

    Example 3

    +

    $vtodo->setProperty( "due" + , array( "year" =>, 2006, "month" => 8, "day"=> 11 ) + , array( "VALUE" => "DATE" )); + // due "during the day"

    +
    +[index] [top] [up] + + +

    3.2.19 DURATION

    +The property specifies a positive duration of time
    +In a VEVENT it is OPTIONAL and MUST NOT occur more than once and MUST NOT occur in pair with DTEND. If one occurs, so MUST NOT the other.
    +In a VTODO it is OPTIONAL and MUST NOT occur more than once and MUST NOT occur in pair with DUE. If one occurs, so MUST NOT the other.
    +In a VFREEBUSY it is OPTIONAL and MUST NOT occur more than once.
    +In a VALARM it is OPTIONAL and MUST NOT occur more than once and MUST occur in pair with TRIGGER. If one occurs, so MUST the other. +
    Delete DURATION
    +Remove DURATION from component. +

    Format

    +

    deleteProperty( "DURATION" )

    +

    Example

    +

    $valarm->deleteProperty( "DURATION" );

    +
    Get DURATION
    +Fetch property value. +

    Format 1

    +

    getProperty( "DURATION" )

    +

    output = duration1

    +

    Format 2

    +

    getProperty( "DURATION", FALSE , TRUE )

    +

    output = array( "value" => duration1 + , "params" => xparam2 )

    +

    Example

    +

    $duration = $vtodo->getProperty( "duration" );

    +

    option

    +If a 4th argument is used and set to TRUE, returned output is in a DATE-TIME + output format (like DTEND / DUE), based on +DTSTART value with added DURATION value. +
    Set DURATION
    +Insert property value. +

    Format

    +

    setProperty( "duration", duration [, xparam ] )

    +

    +duration1 = array ( "week" => int week ) +duration1 = array ( "day" => int day ) + [, "hour" => int hour + , "min" => int min + , "sec" => int sec ]) +duration = array ( "sec" => int sec ) +duration = array( int week/false + [, int day/false + [, int hour + , int min + , int sec ]] ) +duration = int week/false + [, int day/false + [, int hour + , int min + , int sec ]] +duration = string dur-value = ["+"] "P" (dur-date/dur-time/dur-week) +dur-date = dur-day [dur-time] +dur-time = "T" (dur-hour / dur-minute / dur-second) +dur-week = 1*DIGIT "W" +dur-hour = 1*DIGIT "H" [dur-minute] +dur-minute = 1*DIGIT "M" [dur-second] +dur-second = 1*DIGIT "S" +dur-day = 1*DIGIT "D" +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example 1

    +

    $vtodo->setProperty "duration" + , array( "day" => 1 ));

    +

    // one day

    +

    Example 2

    +

    $vtodo->setProperty( "duration" + , "PT4H" ); +

    // four hours

    +
    +[index] [top] [up] + + +

    3.2.20 EXDATE

    +This property defines the list of date/time exceptions for a recurring calendar component and is OPTIONAL and MAY occur more than once in VEVENT, VTODO, VJOURNAL components.

    +The default value type for EXDATE is DATE-TIME, can be set to a DATE value type. +
    Delete EXDATE
    +Remove EXDATE from component. +

    Format

    +

    deleteProperty( "EXDATE" )

    +

    Example 1

    +

    $vtodo->deleteProperty( "EXDATE" );

    +

    Example 2

    +Delete EXDATE property no 2. +

    $vjournal->deleteProperty( "EXDATE", 2 );

    +

    Example 3

    +Deleting all EXDATE properties. +

    while( $vjournal->deleteProperty( "EXDATE" )) + continue;

    +
    Get EXDATE
    +Fetch property value. +

    Format 1

    +

    getProperty( "EXDATE" )

    +

    output = exdates1

    +

    Format 2

    +

    getProperty( "exdate", propOrderNo/FALSE, TRUE )

    +

    output = array( "value" => exdates1 + , "params" => xparams2 )

    +

    Format 3

    +

    getProperty( "EXDATE", propOrderNo )

    +

    Get propOrderNo EXDATE

    +

    Example

    +

    $exdate = $vtodo->getProperty( "exdate" );

    +
    Set EXDATE
    +Insert property value.
    +If "TZID" is set in params, ex. "TZID" = "CET", +all timezone or offset in dates are ignored and DATE-TIME value type is set.
    +If DATE value type is set in params ("VALUE" = "DATE"), all timezone or offset in dates are ignored.
    +If no "VALUE" parameter in params, DATE-TIME (default) value type is set.
    +If empty params and offset in 1st date, all remaining dates are set to UTC.
    +If no "TZID" is set in params and timezone in 1st date, all remaining dates are within this timezone and param "TZID" is set.
    +If none of the above rules are applicable, DATE-TIME and local date is set default.
    +Notice, use function transformDateTime +to change a datetime from a time zone to another. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "exdate", exdates [, xparams [, propOrderNo ]] )

    +

    exdates1 = array ( date *[, date ] ) +date = array( int year + , int month + , int day + [, int hour + , int min + , int sec + [, mixed tz ]] ) +date1 = array( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + [, "tz" => mixed tz ]] ) +date = array( "timestamp" => int timestamp [, "tz" => mixed tz]) +date = string datestring // string date, + acceptable by strtotime function, + ex. "14 august 2006 16.00.00" + (notice date restriction) +tz = <timezone identifier> / offset + (timezone will be used as tzidparam (below), if tzidparam not set) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +params2 = array([(datetimeparam/dateparam) / tzidparam] [,xparam]) +datetimeparam = "VALUE" => "DATE-TIME" // default, output as date-time +dateparam = "VALUE" => "DATE" // output as DATE +tzidparam = "TZID" => <timezone identifier> +xparams = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example 1

    +

    $vevent->setProperty( "exdate" + , array( array( 2006, 8, 14, 16, 0, 0 )); + // >exclude 2006-08-14 16.00.00 (local date) from recurrence pattern

    +

    Example 2

    +

    $vevent->setProperty( "exdate" + , array( array("year" =>,2006,"month" => 8,"day"=> 11)) + , array( "VALUE" => "DATE" )); + // exclude 2006-08-11 from recurrence pattern;

    +
    +[index] [top] [up] + + +

    3.2.21 EXRULE

    +This property defines a rule or repeating pattern for an exception to a recurrence set and is OPTIONAL and MAY occur more than once in VEVENT, VTODO, VJOURNAL components. +
    Delete EXRULE
    +Remove EXRULE from component. +

    Format

    +

    deleteProperty( "EXRULE" )

    +

    Example 1

    +

    $vtodo->deleteProperty( "EXRULE" );

    +

    Example 2

    +Delete EXRULE property no 2. +

    $vjournal->deleteProperty( "EXRULE", 2 );

    +

    Example 3

    +Deleting all EXRULE properties. +

    while( $vjournal->deleteProperty( "EXRULE" )) + continue;

    +
    Get EXRULE
    +Fetch property value. +

    Format 1

    +

    getProperty( "EXRULE" )

    +

    output = recur1

    +

    Format 2

    +

    getProperty( "exrule", propOrderNo/FALSE, TRUE )

    +

    output = array( "value" => recur1 + , "params" => xparam2 )

    +

    Format 3

    +

    getProperty( "EXRULE", propOrderNo )

    +

    Get propOrderNo EXRULE

    +

    Example

    +

    $exrule = $vtodo->getProperty( "exrule" );

    +
    Set EXRULE
    +Insert property value.
    +Notice, use function transformDateTime +to change a datetime to UTC time zone.
    +Parameters, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "exrule", recur [, xparams [, propOrderNo ]] )

    +See rules in detail in RFC2445 - Internet Calendaring and Scheduling Core Object Specification (iCalendar). +

    recur1 = array( "FREQ"=>freq + // either UNTIL or COUNT may appear in a "recur", + but UNTIL and COUNT MUST NOT occur in the same "recur" + [, "UNTIL" "=>" >enddate ] + [, "COUNT" "=>" 1*DIGIT ] + // the rest of these keywords are optional, + but MUST NOT occur more than once + [, "INTERVAL" "=>" 1*DIGIT ] + [, "BYSECOND" "=>" byseclist ] + [, "BYMINUTE" "=>" byminlist ] + [, "BYHOUR" "=>" byhrlist ] + [, "BYDAY" "=>" bywdaylist ] + [, "BYMONTHDAY" "=>" bymodaylist ] + [, "BYYEARDAY" "=>" byyrdaylist ] + [, "BYWEEKNO" "=>" bywknolist ] + [, "BYMONTH" "=>" bymolist ] + [, "BYSETPOS" "=>" bysplist ] + [, "WKST" "=>" weekday ] + [, x-name "=>" text ] ) +freq = "SECONDLY" / + "MINUTELY" / + "HOURLY" / + "DAILY" / + "WEEKLY" / + "MONTHLY" / + "YEARLY" +enddate = date +enddate = / date-time ;An UTC DATE-TIME value +byseclist = seconds +byseclist = array(seconds *(, seconds )) +seconds = 1DIGIT / 2DIGIT ;0 to 59 +byminlist = minutes +byminlist = array( minutes *(, minutes )) +minutes = 1DIGIT / 2DIGIT ;0 to 59 +byhrlist = hour +byhrlist = array( hour *(, hour )) +hour = 1DIGIT / 2DIGIT ;0 to 23 +bywdaylist = weekdaynum +bywdaylist = array( weekdaynum *("," weekdaynum )) +weekdaynum = array( [([plus] ordwk / minus ordwk)], "DAY" => weekday ) +plus = "+" +minus = "-" +ordwk = 1DIGIT / 2DIGIT ;1 to 53 +weekday = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA" + ; Corresponding to + ; SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, + ; FRIDAY, SATURDAY and SUNDAY days of the week. +bymodaylist = monthdaynum +bymodaylist = array( monthdaynum *(, monthdaynum )) +monthdaynum = ( [plus] ordmoday ) / ( minus ordmoday ) +ordmoday = 1DIGIT / 2DIGIT ;1 to 31 +byyrdaylist = yeardaynum +byyrdaylist = array( yeardaynum *(, yeardaynum )) +yeardaynum = ( [plus] ordyrday ) / ( minus ordyrday ) +ordyrday = 1DIGIT / 2DIGIT / 3DIGIT ;1 to 366 +bywknolist = weeknum +bywknolist = array( weeknum *(, weeknum )) +weeknum = ( [plus] ordwk ) / ( minus ordwk ) +bymolist = monthnum +bymolist = array( monthnum *(, monthnum )) +monthnum = 1DIGIT / 2DIGIT ;1 to 12 +bysplist = setposday +bysplist = array( setposday *(, setposday )) +setposday = yeardaynum

    +

    +xparam2 = array( *[ xparamkey => xparamvalue ] ) +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $vevent->setProperty( "Exrule" + , array( "FREQ" => "MONTHLY" + , "UNTIL" => "20060831" + // DATE / DATE-TIME in UTC format; string/array, see CREATED format + , "INTERVAL" => 2 + , "WKST" => "SU" + , "BYSECOND" => 2 + , "BYMINUTE" => array( 2, -4, 6 ) // (*) + , "BYHOUR" => array( 2, 4, -6 ) // (*) + , "BYMONTHDAY" => -2 // (*) + , "BYYEARDAY" => 2 // (*) + , "BYWEEKNO" => array( 2, -4, 6 ) // (*) + , "BYMONTH" => 2 // (*) + , "BYSETPOS" => array( 2, -4, 6 ) // (*) + , "BYday" => array( array(-2, "DAY" => "WE" ) + , array( 3, "DAY" => "TH") + , array( 5, "DAY" => "FR") + , array( "DAY" => "MO")) + // (**) + , "X-NAME" => "x-value" ) + , array( "xparamkey" => "xparamValue" )); + +$vtodo->setProperty( >"exrule" + , array( "FREQ" => "WEEKLY" + , "COUNT" => 2 + , "INTERVAL" => 2 + , "WKST" => "SU" + , "BYSECOND" => array( -2, 4, 6 ) // (*) + , "BYMINUTE" => -2 // (*) + , "BYHOUR" => 2 // (*) + , "BYMONTHDAY" => array( 2, -4, 6 ) // (*) + , "BYYEARDAY" => array( -2, 4, 6 ) // (*) + , "BYWEEKNO" => -2 // (*) + , "BYMONTH" => array( 2, 4, -6 ) // (*) + , "BYSETPOS" => -2 // (*) + , "BYday" => array( 5, "DAY" => "WE" ) + // (**) + , "X-NAME" => "x-value" ) + , array( "xparamkey" => "xparamValue" )); + //(*) single value/array of values + //(**) single value array /array of arrays +

    +
    +[index] [top] [up] + + +

    3.2.22 FREEBUSY

    +The property defines one or more free or busy time intervals in a VFREEBUSY calendar component.

    +The value type for FREEBUSY is PERIOD. A PERIOD is a DATE-TIME/DATE-TIME or a DATE-TIME/duration. +
    Delete FREEBUSY
    +Remove FREEBUSY from component. +

    Format

    +

    deleteProperty( "FREEBUSY" )

    +

    Example 1

    +

    $vfreebusy->deleteProperty( "FREEBUSY" );

    +

    Example 2

    +Delete FREEBUSY property no 2. +

    $vfreebusy->deleteProperty( "FREEBUSY", 2 );

    +

    Example 3

    +Deleting all FREEBUSY properties. +

    while( $vfreebusy->deleteProperty( "FREEBUSY" )) + continue;

    +
    Get FREEBUSY
    +Fetch property value. +

    Format 1

    +

    getProperty( "FREEBUSY" )

    +

    output = array( "fbtyp" => freebusytype1 , periods2 )

    +

    Format 3

    +

    getProperty( "FREEBUSY", propOrderNo/FALSE , TRUE )

    +

    output = array( "value" => array("fbtype" => freebusytype1 ,periods2) + , "params" => xparams 3 )

    +

    Format 3

    +

    getProperty( "FREEBUSY", propOrderNo )

    +

    Get propOrderNo FREEBUSY

    +

    Example

    +

    $freebusy = $vfreebusy->getProperty( "FREEBUSY" );

    +
    Set FREEBUSY
    +Insert property value. A FREEBUSY input date is always a UTC DATE-TIME. +

    Format

    +

    setProperty( "freebusy",freebusytype,fbperiods [,xparams [,propOrderNo ]] )

    +

    freebusytype1 = one of "FREE" + / "BUSY" Default + / "BUSY-UNAVAILABLE" + / "BUSY-TENTATIVE" + / x-name +fbperiods = array( periods2 )  +periods2 = array( startdate, enddate/duration ) + *[, array( startdate, enddate/duration )] +startdate/enddate = array( int year + , int month + , int day + , int int hour + , int min + , int day ) +startdate/enddate = array( "year" => int year + , "month" => int month + , "day" => int day + , "hour" => int hour + , "min" => int min + , "sec" => int sec ) // output format +startdate/enddate = array( "timestamp" => int timestamp ) +startdate/enddate = string datestring // string date, + acceptable by strtotime function, + ex. "14 august 2006 16.00.00" + (notice date restriction) +startdate/enddate : date and time values MUST be an UTC DATE-TIME +duration = array( int week/false + [, int day/false + , int hour + , int min + , int sec] ) +duration = array( "week" => int week/false + [, "day" => int day/false + [, "hour" => int hour + , "min" => int min + , "sec" => int sec ]] ); // output format +duration = array( "sec" => int sec ) +duration = string dur-value + = (["+"]/"-") "P" (dur-date/dur-time/dur-week) +dur-date = dur-day [dur-time] +dur-time = "T" (dur-hour / dur-minute / dur-second) +dur-week = 1*DIGIT "W" +dur-hour = 1*DIGIT "H" [dur-minute] +dur-minute = 1*DIGIT "M" [dur-second] +dur-second = 1*DIGIT "S" +dur-day = 1*DIGIT "D" +xparams3 = array( *[ xparamkey => xparamvalue ] ) +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +See rules in detail in RFC2445 - Internet Calendaring and Scheduling Core Object Specification (iCalendar). +

    $fdate1 = array ( 2001, 1, 1, 1, 1, 1 ); +alt. +$fdate1 = array ( "year" => 2001 + , "month" => 1 + , "day" => 1 + , "hour" => 1 + , "min" => 1 + , "sec" => 1 ); +$fdate2 = array ( 2002, 2, 2, 2, 2, 2 ); +$fdate3 = array ( 2003, 3, 3, 3, 3, 3 ); +$fdate4 = "4 April 2005 4:4:4"; +$fdate7 = array ( "year" => 2007 + , "month" => 7 + , "day" => 7 ); +$fdur6 = array ( "week" => 0 + , "day" => 5 + , "hour" => 5 + , "min" => 5 + , "sec" => 5 ); +$fdur7 = array ( 0, 0, 6 ); // duration for 6 hours +$fdur8 = "P2D"; // duration two days +$freebusy->setProperty "freebusy" + , "FREE" + , array( array( $fdate1, $fdate2 ) + , array( $fdate3, $fdur6 ) + , array( $fdate4, $fdate5 ))); +$freebusy->setProperty("freebusy" + , "Busy" + , array( array( array( $fdate1, $fdate2 ) + , array( $fdate3, $fdur8 ) + , array( $fdate4, $fdur7 ) + , array( $fdate1, $fdate3 )));

    +
    +[index] [top] [up] + + +

    3.2.23 GEO

    +This property specifies information related to the global position for the activity specified by VEVENT and VTODO components and is OPTIONAL and MUST NOT occur more than once. +

    +Value type for latitude and longitude is FLOAT. "Values for latitude and longitude shall be expressed as decimal fractions of degrees. Whole degrees of latitude shall be represented by a two-digit decimal number ranging from 0 through 90. Whole degrees of longitude shall be represented by a decimal number ranging from 0 through 180. When a decimal fraction of a degree is specified, it shall be separated from the whole number of degrees by a decimal point." +
    Delete GEO
    +Remove GEO from component. +

    Format

    +

    deleteProperty( "GEO" )

    +

    Example

    +

    $vevent->deleteProperty( "GEO" );

    +
    Get GEO
    +Fetch property value. +

    Format 1

    +

    getProperty( "GEO" )

    +

    output = array( "latitude" => <latitude> + , "longitude" => <longitude>))

    +

    Format 2

    +

    getProperty( "GEO", FALSE , TRUE )

    +

    output = array( "value" => array ( "latitude" => <latitude> + , "longitude" => <longitude>)) + , "params" => xparam 1 )

    +

    Example

    +

    $geo = $vevent->getProperty( "GEO" );

    +
    Set GEO
    +Insert property value. +

    Format

    +

    setProperty( "geo", float latitude, float longitude [, xparam ] )

    +

    xparam 1 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $vevent->setProperty( "geo", 11.23456, -23.45678 );

    +
    +[index] [top] [up] + + +

    3.2.24 LAST-MODIFIED

    +The property specifies the date and time that the information associated with the calendar component was last revised in the calendar store. The property is OPTIONAL and MUST NOT occur more than once in VEVENT, VTODO, VJOURNAL and VTIMEZONE components.

    +The value type for LAST-MODIFIED is UTC DATE-TIME. +
    Delete LAST-MODIFIED
    +Remove LAST-MODIFIED from component. +

    Format

    +

    deleteProperty( "LAST-MODIFIED" )

    +

    Example

    +

    $vevent->deleteProperty( "LAST-MODIFIED" );

    +
    Get LAST-MODIFIED
    +Fetch property value. +

    Format 1

    +

    getProperty( "LAST-MODIFIED" )

    +

    output = moddate1

    +

    Format 2

    +

    getProperty( "LAST-MODIFIED", FALSE , TRUE )

    +

    output = array( "value" => moddate1 + , "params" => xparam2 )

    +

    Example

    +

    $lastMod = $vevent->getProperty( "LAST-MODIFIED" );

    +
    Set LAST-MODIFIED
    +Insert property value. Input date is always a UTC DATE-TIME or, +if "offset" parameter is used, converted to a UTC DATE-TIME. +Notice, use function transformDateTime +to change a datetime to UTC time zone. +

    Format

    +

    setProperty( "Last-Modified" [, moddate [, xparam ]] )

    +

    moddate1 = array( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + , "tz" => offset ]] ) +completedDate = int year + , int month + , int day + [, int hour + , int min + , int sec ] +completedDate = array( int year + , int month + , int day + [, int hour + , int min + , int sec + [, offset ]] ) +completedDate = array ( "timestamp" => int timestamp [, "tz" => offset]) +completedDate = string datestring // string date, + acceptable by strtotime function, + ex.  "14 august 2006 16.00.00" + (notice date restriction) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vtodo = & $vcalendar->newComponent( "vtodo" ); +.. . +$vtodo->setProperty("last-modified" + , 2006, 8, 14, 12, 1, 2 ); + // 14 august 2006 12.01.02 UTC

    +

    Example 2

    +

    $date = array("year" => 2006, "month" => 10, "day" => 10, + "hour" => 10, "min" => 0, "sec" => 0, "tz" => "+0200"); + // local date + UTC offset => UTC DATE-TIME +$vtodo->setProperty( "last-modified", $date ); +.. .

    +

    Example 3

    +

    $vevent->setProperty( "last-modified" ); + // current UTC DATE-TIME is set if called without parameters

    +
    +[index] [top] [up] + +

    3.2.25 LOCATION

    +The property defines the intended venue for the activity defined by a calendar component. The property is OPTIONAL and MUST NOT occur more than once in VEVENT and VTODO components. +

    +The value type for LOCATION is TEXT. +
    Delete LOCATION
    +Remove LOCATION from component. +

    Format

    +

    deleteProperty( "LOCATION" )

    +

    Example

    +

    $vevent->deleteProperty( "LOCATION" );

    +
    Get LOCATION
    +Fetch property value. +

    Format 1

    +

    getProperty( "LOCATION" )

    +

    output = location1

    +

    Format 2

    +

    getProperty( "LOCATION", FALSE , TRUE )

    +

    output = array( "value" => location1 + , "params" => param2 )

    +

    Example

    +

    $location = $vevent->getProperty( "LOCATION" );

    +
    Set LOCATION
    +Insert property value. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "location", string location [, array param] )

    +

    location1 = Value type TEXT +params2 = array( ["ALTREP" => "<an alternate text representation, URI>"] + [, "LANGUAGE" => "<lang>"] + [, xparam ] +xparam = *[ xparamkey => xparamvalue ]

    +

    Example

    +

    $vevent->setProperty( "location", "Buckingham Palace" );

    +
    +[index] [top] [up] + + +

    3.2.26 ORGANIZER

    +The property defines the organizer for a calendar component and is OPTIONAL and MUST NOT occur more than once in VEVENT, VTODO, VJOURNAL and VFREEBUSY components.

    +This value type for ORGANIZER is URI, a calendar user address. +
    Delete ORGANIZER
    +Remove ORGANIZER from component. +

    Format

    +

    deleteProperty( "ORGANIZER" )

    +

    Example

    +

    $vevent->deleteProperty( "ORGANIZER" );

    +
    Get ORGANIZER
    +Fetch property value. +

    Format 1

    +

    getProperty( "ORGANIZER" )

    +

    output = organizer1

    +

    Format 2

    +

    getProperty( "ORGANIZER", FALSE , TRUE )

    +

    output = array( "value" => organizer1 + , "params" => params2 )

    +

    Example

    +

    $organizer = $vevent->getProperty( "ORGANIZER" );

    +
    Set ORGANIZER
    +Insert property value. +Property value must be prefixed by protocol (ftp://, http://,mailto:, file://.. . ref. rfc 1738 ). +Also DIR parameter must be prefixed by protocol. +SENT-BY parameter must use protocol "mailto:", prefixed if missing. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "organizer", organizer [, params] )

    +

    organizer1 = a calendar user address, a URI as defined by [RFC + 1738] or any other IANA registered form for a URI. +params2 = array( ["LANGUAGE" => "<lang>" + (applies to the CN parameter value) ] + [, "CN" => "common name to be associated + with the calendar user + specified by the property"] + [, "DIR" => "reference to a directory + entry associated with the calendar user + specified by the property" ] + [, "SENT-BY" => "single calendar user + that is acting on behalf + of the calendar user + specified by the property" ] + [, xparam ] +xparam = *[ xparamkey => xparamvalue ]

    +

    Example

    +

    +$dir = "ldap://domain.com:6666/o=3DDC%20Comp,c=3DUS??(cn=3DJohn%20Doe)"; +$vevent->setProperty( "organizer" + , "ical@domain.com" + , array( "CN" => "John Doe" + , "DIR" => $dir + , "SENT-BY" => "secretary@domain.com" + , "X-Key1" => "X-Value1" + , "X-Key2" => "X-Value2" ));

    +
    +[index] [top] [up] + + +

    3.2.27 PERCENT-COMPLETE

    +This property is used by an assignee or delegatee of a VTODO to convey the percent completion of a VTODO to the Organizer and is OPTIONAL and MUST NOT occur more than once.

    +The property value is a positive integer between zero and one hundred. A value of "0" indicates the VTODO has not yet been started. A value of "100" indicates that the VTODO has been completed. Integer values in between indicate the percent partially complete. +
    Delete PERCENT-COMPLETE
    +Remove PERCENT-COMPLETE from component. +

    Format

    +

    deleteProperty( "PERCENT-COMPLETE" )

    +

    Example

    +

    $vtodo->deleteProperty( "PERCENT-COMPLETE" );

    +
    Get PERCENT-COMPLETE
    +Fetch property value. +

    Format 1

    +

    getProperty( "PERCENT-COMPLETE" )

    +

    output = percent1

    +

    Format 2

    +

    getProperty( "PRIORITY", FALSE , TRUE )

    +

    output = array( "value" => percent1 + , "params" => xparam2 )

    +

    Example

    +

    $percent = $vtodo->getProperty( "PERCENT-COMPLETE" );

    +
    Set PERCENT-COMPLETE
    +Insert property value. +

    Format

    +

    setProperty( "Percent-Complete", percent [, xparam ] )

    +

    percent1 = Value type INTEGER +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $vtodo->setProperty( "percent-complete", 90 );

    +
    +[index] [top] [up] + + +

    3.2.28 PRIORITY

    +The property defines the relative priority for a calendar component and is OPTIONAL and MUST NOT occur more than once in VEVENT and VTODO components.

    +The priority is specified as an integer in the range zero to nine.
    +A value of zero (US-ASCII decimal 48) specifies an undefined priority.
    +A value of one (US-ASCII decimal 49) is the highest priority.
    +A value of two (US-ASCII decimal 50) is the second highest priority.
    +Subsequent numbers specify a decreasing ordinal priority.
    +A value of nine (US-ASCII decimal 58) is the lowest priority. +
    Delete PRIORITY
    +Remove PRIORITY from component. +

    Format

    +

    deleteProperty( "PRIORITY" )

    +

    Example

    +

    $vevent->deleteProperty( "PRIORITY" );

    +
    Get PRIORITY
    +Fetch property value. +

    Format 1

    +

    getProperty( "PRIORITY" )

    +

    output = priority1

    +

    Format 2

    +

    getProperty( "PRIORITY", FALSE , TRUE )

    +

    output = array( "value" => priority1 + , "params" => xparam2 )

    +

    Example

    +

    $priority = $vevent->getProperty( "priority" );

    +
    Set PRIORITY
    +Insert property value. +

    Format

    +

    setProperty( "priority", priority [, xparam ] )

    +

    priority1 = Value type INTEGER +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $vevent->setProperty( "priority", 3 );

    +
    +[index] [top] [up] + + +

    3.2.29 RDATE

    +This property defines the list of date/times for a recurrence set and is OPTIONAL and MAY occur more than once in VEVENT, VTODO, VJOURNAL, STANDARD and DAYLIGHT components.

    +The default value type for RDATE is DATE-TIME, can be set to DATE or PERIOD (params 2). +
    Delete RDATE
    +Remove RDATE from component. +

    Format

    +

    deleteProperty( "RDATE" )

    +

    Example 1

    +

    $vtodo->deleteProperty( "RDATE" );

    +

    Example 2

    +Delete RDATE property no 2. +

    $vjournal->deleteProperty( "RDATE", 2 );

    +

    Example 3

    +Deleting all RDATE properties. +

    while( $vjournal->deleteProperty( "RDATE" )) + continue;

    +
    Get RDATE
    +Fetch property value. +

    Format 1

    +

    getProperty( "RDATE" )

    +

    output = dates1

    +

    Format 2

    +

    getProperty( "RDATE", propOrderNo/FALSE , TRUE )

    +

    output = array( "value" => dates1 + , "params" => params2 )

    +

    Format 3

    +

    getProperty( "RDATE", propOrderNo )

    +

    Get propOrderNo RDATE

    +

    Example

    +

    $rdates = $vevent->getProperty( "RDATE" );

    +
    Set RDATE
    +Insert property value.
    +If "TZID" is set in params, ex. "TZID" = "CET", all timezone or offset in dates are ignored and DATE-TIME value type is set.
    +If DATE value type is set in params ("VALUE" = "DATE"), all timezone or offset in dates are ignored.
    +If "PERIOD" is set in params ("VALUE" = "PERIOD"), DATE-TIME value type is set.
    +If no "VALUE" parameter in params, DATE-TIME (default) value type is set.
    +If empty params and offset in 1st date, all remaining dates are set to UTC.
    +If no "TZID" is set in params and timezone in 1st date, all remaining dates are within this timezone and param "TZID" is set.
    +If none of the above rules are applicable, DATE-TIME and local date is set default. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "rdate", dates [, params [, propOrderNo ]] )

    +

    dates1 = array ( date2 *[, date2 ] ) +date2 = date +date2 = array( startdate, enddate/duration ) ] +startdate = date +enddate = date +date = array( int year + , int month + , int day + [, int int hour + , int min + , int day + , mixed tz ] ) +date = array( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + [, "tz" => mixed tz ]] ) + // output format +date = array( "timestamp" => int timestamp + [, "tz" => mixed tz ] ) +date = string datestring // string date, + acceptable by strtotime function, + ex. "14 august 2006 16.00.00" + (notice date restriction) +tz = timezone / offset + (timezone will be used as tzidparam, if tzidparam not exists) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +duration = array( int week/false + [, int day/false + , int hour + , int min + , int sec] ) +duration = array([ "week" => int week/false ,] / + [ "day" => int day/false + [, "hour" => int hour + , "min" => int min + , "sec" => int sec ]] ); + // output format, only used keys +duration = array( "sec" => int sec ); +duration = string format duration like "P15DT5H0M20S" +params2 = ([tzidparam ( / datetimeparam / dateparam / periodparam )] + *[, xparams ] ) +tzidparam = "TZID" => <timezone identifier> + // output as local DATE-TIME with timezone identifier +datetimeparam = "VALUE" => "DATE-TIME" // default, output as DATE-TIME +dateparam = "VALUE" => "DATE" // output as DATE +periodparam = "VALUE" => "PERIOD" // output as PERIOD (datetime) +xparams = xparamkey => xparamvalue +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +See rules in detail in RFC2445 - Internet Calendaring and Scheduling Core Object Specification (iCalendar).
    +

    // $rdate1 = array ( 2001, 1, 1, 1, 1, 1 ); +// alt. +$rdate1 = array( "year" => 2001 + , "month" => 1 + , "day" => 1 + , "hour" => 1 + , "min" => 1 + , "sec" => 1 + , "tz" => "GMT" ); +$rdate2 = array( 2002, 2, 2, 2, 2, 2, "GMT" ); +$rdate3 = "3 March 2003 03.03.03"; +$rdate4 = array( 2004, 4, 4, 4, 4, 4, "GMT" ); +$rdate5 = array( 2005, 10, 5, 5, 5, 5 ); +$rdate8 = array( "year" => 2007, "month" => 7, "day" => 7 ); +$rdur6 = array( "week" => 0 + , "day" => 0 + , "hour" => 5 + , "min" => 5 + , "sec" => 5 ); +$rdur7 = array( 0, 0, 6 ); + // duration for 6 hours +$rdur8 = array( "week" => 8 ); + // duration for 8 weeks + +$vevent = & $vcalendar->newComponent( "vevent" ); +$vevent->setProperty( "rdate", array( $rdate1 )); + // one recurrence date, date in 7-params format (DATE-TIME) + +$vevent->setProperty( "rdate", array( $rdate1, $rdate2 )); + // two dates, date 7-params format (DATE-TIME) + +$vevent->setProperty( "rdate", array( array( $rdate1, $rdate2 ) + , array( $rdate3, $rdate4 )) + , array( "VALUE" => "PERIOD" )); + // Both fromdate and enddate must have 7 params (DATE-TIME) !!! + +$vevent->setProperty( "rdate", array( array( $rdate2, $rdur6 )) + , array( "VALUE" => "PERIOD" )); + // one duration (fromdate-duration) + +$vevent->setProperty( "rdate", array( array( $rdate1, $date2 ) + , array( $rdate3, $rdur7 )) + , array( "VALUE" => "PERIOD" )); + // period, pairs of fromdate+enddate and fromdate-duration + +$vevent->setProperty( "rdate", array( $rdate5, $date8 )) + , array( "VALUE" => "DATE" )); + // dates in DATE format +.. .

    +
    +[index] [top] [up] + + +

    3.2.30 RECURRENCE-ID

    +This property is used in conjunction with the UID and SEQUENCE property to identify a specific instance of a recurring VEVENT, VTODO or VJOURNAL calendar component and is OPTIONAL and MAY NOT occur more than once.

    +The property value is the effective value of the DTSTART property of the recurrence instance. The default value type is DATE-TIME, can be set to DATE (params 2). +
    Delete RECURRENCE-ID
    +Remove RECURRENCE-ID from component. +

    Format

    +

    deleteProperty( "RECURRENCE-ID" )

    +

    Example

    +

    $vevent->deleteProperty( "RECURRENCE-ID" );

    +
    Get RECURRENCE-ID
    +Fetch property value. +

    Format 1

    +

    getProperty( "RECURRENCE-ID" )

    +

    output = recurrIdDate1

    +

    Format 2

    +

    getProperty( "RECURRENCE-ID", FALSE , TRUE )

    +

    output = array( "value" => recurrIdDate1 + , "params" => params2 )

    +

    Example

    +

    $recurrDate = $vtodo->getProperty( "RECURRENCE-ID" );

    +
    Set RECURRENCE-ID
    +Insert property value. If DATE value type is expected, "VALUE" = "DATE" must be set (in params2) otherwise DATE-TIME (default) value type is set. +
    +
    +If no timezone parameter (tz or tzidparam below) is set (then local time is assumed) and config TZID is set, +date-time values will be set WITH timezone from config. +Notice, use function transformDateTime +to change a datetime from a time zone to another. +

    Format

    +

    setProperty( "recurrence-id", recurrIdDate [, params ] )

    +

    recurrIdDate1 = array( "year" => int year + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec + [, "tz" => mixed tz ]] ) +recurrIdDate = int year + , int month + , int day + [, int hour + , int min + , int sec + [, mixed tz ]] +recurrIdDate = array( int year + , int month + , int day + [, int hour + , int min + , int sec + [, mixed tz ]] ) +recurrIdDate = array( "timestamp" => int timestamp + [, "tz" => mixed tz ] ) +recurrIdDate = string datestring // string date, + acceptable by strtotime function, + ex. "14 august 2006 16.00.00" + (notice date restriction) +tz = timezone / offset + (timezone will be used as tzidparam, if tzidparam not exists) +offset = (+/-)HHmm[ss], local date + UTC offset => UTC DATE-TIME +params2 = array([ datetimeparam/dateparam/tzidparam ] + [, rangeparam ] + [, xparam ] ) +datetimeparam = "VALUE" => "DATE-TIME" // default, output as DATE-TIME +dateparam = "VALUE" => "DATE" // output as DATE +tzidparam = "TZID" => <timezone identifier> + // output as local date-time with timezone identifier +rangeparam = "RANGE" => ( "THISANDPRIOR" / "THISANDFUTURE" ) + // range parameter +xparam = *[ xparamkey => xparamvalue ]

    +

    Example

    +

    $vtodo->setProperty( "recurrence-id", "3 March 2003 03.03.03" ); + // 3 march 2003 03.03.03 local time

    +
    +[index] [top] [up] + + +

    3.2.31 RELATED-TO

    +The property is used to represent a relationship or reference between one calendar component and another and is OPTIONAL and MAY occur more than once in VEVENT, VTODO, VJOURNAL components.

    +The property value consists of the persistent, globally unique identifier of another calendar component. This value would be represented in a calendar component by the UID property.

    +The value type for RELATED-TO is TEXT. +
    Delete RELATED-TO
    +Remove RELATED-TO from component. +

    Format

    +

    deleteProperty( "RELATED-TO" )

    +

    Example 1

    +

    $vtodo->deleteProperty( "RELATED-TO" );

    +

    Example 2

    +Delete RELATED-TO property no 2. +

    $vjournal->deleteProperty( "RELATED-TO", 2 );

    +

    Example 3

    +Deleting all RELATED-TO properties. +

    while( $vjournal->deleteProperty( "RELATED-TO" )) + continue;

    +
    Get RELATED-TO
    +Fetch property value. +

    Format 1

    +

    getProperty( "RELATED-TO" )

    +

    output = relid1

    +

    Format 2

    +

    getProperty( "RELATED-TO", propOrderNo/FALSE , TRUE )

    +

    output = array( "value" => relid1 + , "params" => params2 )

    +

    Format 3

    +

    getProperty( "RELATED-TO", propOrderNo )

    +

    Get propOrderNo RELATED-TO

    +

    Example

    +

    $relatedId = $vtodo->getProperty( "RELATED-TO" );

    +
    Set RELATED-TO
    +Insert property value. +

    Format

    +

    setProperty( "Related-To", relid [, params [, propOrderNo ]] )

    +

    relid1 = Value type TEXT. +params2 = array( [ reltype ] [, xparam] ) +reltype = "RELTYPE" => ("PARENT" (Default) + / "CHILD" + / "SIBLING" + / iana-token + / x-name) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $vtodo->setProperty( "related-to", "19960401-080045-4000F192713@host.com");

    +
    +[index] [top] [up] + +

    3.2.32 REPEAT

    +This property defines the number of time the ALARM should be repeated, after the initial trigger. +If the ALARM triggers more than once, then this property MUST be specified along with the DURATION property. +
    Delete REPEAT
    +Remove REPEAT from component. +

    Format

    +

    deleteProperty( "REPEAT" )

    +

    Example

    +

    $valarm->deleteProperty( "REPEAT" );

    +
    Get REPEAT
    +Fetch property value. +

    Format 1

    +

    getProperty( "REPEAT" )

    +

    output = repeatTimes1

    +

    Format 2

    +

    getProperty( "REPEAT", FALSE , TRUE )

    +

    output = array( "value" => repeatTimes1 + , "params" => xparam2 )

    +

    Example

    +

    $repeat = $vtodo->getProperty( "REPEAT" );

    +
    Set REPEAT
    +Insert property value. +

    Format

    +

    setProperty( "repeat", repeatTimes [, xparam ] )

    +

    repeatTimes1 = Value type INTEGER +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $valarm->setProperty( "repeat", 2 );

    +
    +[index] [top] [up] + + +

    3.2.33 REQUEST-STATUS

    +This property defines the status code returned for a scheduling request and is OPTIONAL and MAY occur more than once in VEVENT, VTODO, VJOURNAL and VFREEBUSY components. +
    Delete REQUEST-STATUS
    +Remove REQUEST-STATUS from component. +

    Format

    +

    deleteProperty( "REQUEST-STATUS" )

    +

    Example 1

    +

    $vtodo->deleteProperty( "REQUEST-STATUS" );

    +

    Example 2

    +Delete REQUEST-STATUS property no 2. +

    $vjournal->deleteProperty( "REQUEST-STATUS", 2 );

    +

    Example 3

    +Deleting all REQUEST-STATUS properties. +

    while( $vjournal->deleteProperty( "REQUEST-STATUS" )) + continue;

    +
    Get REQUEST-STATUS
    +Fetch property value. +

    Format 1

    +

    getProperty( "REQUEST-STATUS" )

    +

    output = array( "statcode" => statcode1 + , "text" => errtext2 + [ , "extdata" => extraData 3 ] )

    +

    Format 2

    +

    getProperty( "REQUEST-STATUS", propOrderNo/FALSE, TRUE )

    +

    output = array( "value" => array( "statcode" => statcode1 + , "text" => errtext2 + [ , "extdata" => extraData3 ] ) + , "params" => params4 )

    +

    Format 3

    +

    getProperty( "REQUEST-STATUS", propOrderNo )

    +

    Get propOrderNo REQUEST-STATUS

    +

    Example

    +

    $requestStatus = $vtodo->getProperty( "REQUEST-STATUS" );

    +
    Set REQUEST-STATUS
    +Insert property value. +

    Format

    +

    setProperty( "Request-Status" + , statcode, errtext [,extraData/FALSE [,params [,propOrderNo]]])

    +

    statcode1 = Hierarchical, numeric return status code + (1*DIGIT *("." 1*DIGIT)) +errtext2 = Textual status description +extraData3 = Textual exception data. + For example, the offending property name and value + or complete property line. +params4 = array( ["LANGUAGE" => "<lang>"] [, xparam ] ) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $vfreebusy->setProperty("request-status" + , 2.0 + , "Invalid property value" + , "DTSTART:96-Apr-31");

    +
    +[index] [top] [up] + + +

    3.2.34 RESOURCES

    +This property defines the equipment or resources anticipated for an activity specified by a calendar entity and is OPTIONAL and MAY occur more than once in VEVENT and VTODO components.

    +The value type for RESOURCES is TEXT. +
    Delete RESOURCES
    +Remove RESOURCES from component. +

    Format

    +

    deleteProperty( "RESOURCES" )

    +

    Example 1

    +

    $vevent->deleteProperty( "RESOURCES" );

    +

    Example 2

    +Delete RESOURCES property no 2. +

    $vevent->deleteProperty( "RESOURCES", 2 );

    +

    Example 3

    +Deleting all RESOURCES properties. +

    while( $vevent->deleteProperty( "RESOURCES" )) + continue;

    +
    Get RESOURCES
    +Fetch property value. +

    Format 1

    +

    getProperty( "RESOURCES" )

    +

    output = resources1

    +

    Format 2

    +

    getProperty( "RESOURCES", propOrderNo/FALSE, TRUE )

    +

    output = array( "value" => resources1 + , "params" => params2 )

    +

    Format 3

    +

    getProperty( "RESOURCES", propOrderNo )

    +

    Get propOrderNo RESOURCES

    +

    Example

    +

    $resources = $vtodo->getProperty( "RESOURCES" );

    +
    Set RESOURCES
    +Insert property value. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "resources", resources [, params [, propOrderNo ]] )

    +

    resources1 = string resource / array( *resource ) +resource = textual resources or subtypes of the calendar component, + can be specified as a list of resources + separated by the COMMA character. +params2 = array([ "ALTREP" => "<an alternate text representation, URI>"] + [, "LANGUAGE" => "<lang>"] + [, xparam] ) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $vevent->setProperty( "resources", "COMPUTER PROJECTOR" );

    +
    +[index] [top] [up] + + +

    3.2.35 RRULE

    +This property defines a rule or repeating pattern for recurring EVENTs, TODOs, STANDARD or DAYLIGHT definitions and is OPTIONAL and MAY occur more than once. +
    Delete RRULE
    +Remove RRULE from component. +

    Format

    +

    deleteProperty( "RRULE" )

    +

    Example 1

    +

    $vevent->deleteProperty( "RRULE" );

    +

    Example 2

    +Delete RRULE property no 2. +

    $vevent->deleteProperty( "RRULE", 2 );

    +

    Example 3

    +Deleting all RRULE properties. +

    while( $vevent->deleteProperty( "RRULE" )) + continue;

    +
    Get RRULE
    +Fetch property value. +

    Format 1

    +

    getProperty( "RRULE" )

    +

    output = recur1

    +

    Format 2

    +

    getProperty( "RRULE", propOrderNo/FALSE, TRUE )

    +

    output = array( "value" => recur1 + , "params" => xparams2 )

    +

    Format 3

    +

    getProperty( "RRULE", propOrderNo )

    +

    Get propOrderNo RRULE

    +

    Example

    +

    $rrules = $vtodo->getProperty( "RRULE" );

    +
    Set RRULE
    +Insert property value. +
    +Parameters will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "rrule", recur [, xparams [, propOrderNo ]] )

    +

    +For rules example see Exrule format and in detail in RFC2445 - Internet Calendaring and Scheduling Core Object Specification (iCalendar). +

    +

    recur1 = see Exrule +xparams2 = array( *[ xparamkey => xparamvalue ] ) +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +
    +[index] [top] [up] + +

    3.2.36 SEQUENCE

    +This property defines the revision sequence number of the calendar component within a sequence of revisions. +The property is OPTIONAL and MUST NOT occur more than once in VEVENT, +VTODO and VJOURNAL components. +
    +

    +It is monotonically incremented by the ORGANIZER's CUA (Calendar +User Agent) each time the ORGANIZER makes a significant revision +to the calendar component. + +When the ORGANIZER makes changes to one of the following +properties, the sequence number MUST be incremented: DTSTART, +DTEND, DUE, RDATE, RRULE, +EXDATE, EXRULE, STATUS. In addition, +changes made by the ORGANIZER to other properties can also force +the sequence number to be incremented. The ORGANIZER CUA MUST +increment the sequence number when ever it makes changes to +properties in the calendar component that the ORGANIZER +deems will jeopardize the validity of the participation status of the +Attendees. For example, changing the location +of a meeting from one locale to another distant locale could +effectively impact the participation status of the Attendees. +

    +
    Delete SEQUENCE
    +Remove SEQUENCE from component. +

    Format

    +

    deleteProperty( "SEQUENCE" )

    +

    Example

    +

    $vtodo->deleteProperty( "SEQUENCE" );

    +
    Get SEQUENCE
    +Fetch property value. +

    Format 1

    +

    getProperty( "SEQUENCE" )

    +

    output = sequence1

    +

    Format 2

    +

    getProperty( "SEQUENCE", FALSE , TRUE )

    +

    output = array( "value" => sequence1 + , "params" => xparam2 )

    +

    Example

    +

    $sequence = $vtodo->getProperty( "SEQUENCE" );

    +
    Set SEQUENCE
    +Insert property value. +

    Format

    +

    setProperty( "sequence" [, sequence [, xparam ]] )

    +

    sequence1 = Value type INTEGER +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example 1

    +

    $vevent->setProperty( "sequence", 2 ); + // set sequence number to 2

    +

    Example 2

    +

    $vevent->setProperty( "sequence" ); + // force sequence number to be set to 0 + // or, if sequence exists, incremented by 1

    +
    +[index] [top] [up] + + +

    3.2.37 STATUS

    +This property defines the overall status or confirmation for the calendar component. The property is OPTIONAL and MUST NOT occur more than once in VEVENT, VTODO and VJOURNAL components. +
    Delete STATUS
    +Remove STATUS from component. +

    Format

    +

    deleteProperty( "STATUS" )

    +

    Example

    +

    $vtodo->deleteProperty( "STATUS" );

    +
    Get STATUS
    +Fetch property value. +

    Format 1

    +

    getProperty( "STATUS" )

    +

    output = status1

    +

    Format 2

    +

    getProperty( "STATUS", FALSE , TRUE )

    +

    output = array( "value" => status1 + , "params" => xparam2 )

    +

    Example

    +

    $status = $vtodo->getProperty( "STATUS" );

    +
    Set STATUS
    +Insert property value. +

    Format

    +

    setProperty( "status", status [, xparam ] )

    +

    // Status values for a VEVENT +status1 = "TENTATIVE" // Indicates event is tentative + / "CONFIRMED" // Indicates event is definite + / "CANCELLED" // Indicates event was cancelled + // Status values for VTODO +status1 = "NEEDS-ACTION" // Indicates to-do needs action + / "COMPLETED" // Indicates to-do completed + / "IN-PROCESS" // Indicates to-do in process of + / "CANCELLED" // Indicates to-do was cancelled + // Status values for VJOURNAL +status1 = "DRAFT" // Indicates journal is draft + / "FINAL" // Indicates journal is final + / "CANCELLED" // Indicates journal is removed +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $vevent->setProperty( "Status", "COMPLETED" );

    +
    +[index] [top] [up] + + +

    3.2.38 SUMMARY

    +This property defines a short ("one line") summary or subject for the calendar component. (In "rfc2445, Recommended Practices", up to 255 characters) (, analogous to a mail SUBJECT). The property is OPTIONAL and MUST NOT occur more than once in VEVENT, VTODO and VJOURNAL components. The property is REQUIRED and MUST occur once in VALARM (EMAIL) calendar component.

    +The value type for SUMMARY is TEXT. +
    Delete SUMMARY
    +Remove SUMMARY from component. +

    Format

    +

    deleteProperty( "SUMMARY" )

    +

    Example

    +

    $vevent->deleteProperty( "SUMMARY" );

    +
    Get SUMMARY
    +Fetch property value. +

    Format 1

    +

    getProperty( "SUMMARY" )

    +

    output = summary1

    +

    Format 2

    +

    getProperty( "SUMMARY", FALSE , TRUE )

    +

    output = array( "value" => summary1 + , "params" => params2 )

    +

    Example

    +

    $summary = $vtodo->getProperty( "SUMMARY" );

    +
    Set SUMMARY
    +Insert property value. +
    +Parameters, if any, will be ordered as prescribed in rcf2445. +

    Format

    +

    setProperty( "summary", summary [, params ] )

    +

    summary1 = Value type TEXT, + a short, one line summary about the activity or journal entry. +params2 = array( ["ALTREP" => "<an alternate text representation, URI>"] + [, "LANGUAGE" => "<lang>"] + [, xparam ] ) +xparam = *[ xparamkey => xparamvalue ]

    +

    Example

    +

    $vevent->setProperty( "summary", "This is a summary" );

    +
    +[index] [top] [up] + + +

    3.2.39 TRANSP

    +This property defines whether an EVENT is transparent or not to busy time searches and is OPTIONAL and MUST NOT occur more than once. +
    Delete TRANSP
    +Remove TRANSP from component. +

    Format

    +

    deleteProperty( "TRANSP" )

    +

    Example

    +

    $vevent->deleteProperty( "TRANSP" );

    +
    Get TRANSP
    +Fetch property value. +

    Format 1

    +

    getProperty( "TRANSP" )

    +

    output = transp1

    +

    Format 2

    +

    getProperty( "TRANSP", FALSE , TRUE )

    +

    output = array( "value" => transp1 + , "params" => xparam2 )

    +

    Example

    +

    $transp = $vtodo->getProperty( "TRANSP" );

    +
    Set TRANSP
    +Insert property value. +

    Format

    +

    setProperty( "transp", transp [, xparam ] )

    +

    transp1 = "OPAQUE" / "TRANSPARENT" +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $vevent->setProperty( "transp", "TRANSPARENT" );

    +
    +[index] [top] [up] + + +

    3.2.40 TRIGGER

    +This property specifies when an ALARM will trigger and is REQUIRED and MUST NOT occur more than once.

    +The default value type is DURATION. The value type can be set to a DATE-TIME value type, in which case the value MUST specify an UTC formatted DATE-TIME value. +
    Delete TRIGGER
    +Remove TRIGGER from component. +

    Format

    +

    deleteProperty( "TRIGGER" )

    +

    Example

    +

    $valarm->deleteProperty( "TRIGGER" );

    +
    Get TRIGGER
    +Fetch property value. +

    Format 1

    +

    getProperty( "TRIGGER" )

    +

    output = duration/date

    +

    Format 2

    +

    getProperty( "TRIGGER", FALSE , TRUE )

    +

    output = array( "value" => duration1/date3 ) + , "params" => params4 ) +

    Example

    +

    $trigger = $vtodo->getProperty( "TRIGGER" );

    +
    Set TRIGGER
    +Insert property value.
    +Note, use function transformDateTime +to change a datetime (in a local time zone) to UTC time zone. +

    Format 1

    +

    setProperty( "trigger", duration1 [, params4 ] )

    +

    Format 2

    +

    setProperty( "trigger", duration2 [, params4 ] )

    +

    Format 3

    +

    setProperty( "trigger", date3 [, params4 ] )

    +

    Format

    +

    setProperty( "trigger", int year/FALSE + , int month/FALSE + , int day/FALSE + [, int week/FALSE + [, int hour/FALSE + , int min/FALSE + , int sec/FALSE + [, bool relatedStart=TRUE + [, bool before=TRUE + [, array params4 ]]]]] )

    +

    duration1 = array( "week" => int week + , "relatedStart" => bool relstart + , "before" => bool before ) +duration1 = array( "day" => int day + , "hour" => int hour + , "min" => int min + , "sec" => int sec + , "relatedStart" => bool relstart + , "before" => bool before ) +relatedStart = TRUE : related start (default), + FALSE : related end +before = TRUE : before relatedStart (default), + FALSE : after relatedStart +duration2 = string dur-value = (["+"]/"-")"P"(dur-date/dur-time/dur-week) +dur-date = dur-day [dur-time] +dur-time = "T" (dur-hour / dur-minute / dur-second) +dur-week = 1*DIGIT "W" +dur-day = 1*DIGIT "D" +dur-hour = 1*DIGIT "H" [dur-minute] +dur-minute = 1*DIGIT "M" [dur-second] +dur-second = 1*DIGIT "S" +date3 = array( "year" => int year // UTC DATE-TIME + , "month" => int month + , "day" => int day + [, "hour" => int hour + , "min" => int min + , "sec" => int sec ]) +date3 = array ( "timestamp" => int timestamp ) // UTCDATE-TIME +date3 = string datestring // string date, + acceptable by strtotime function, + ex. "14 august 2006 16.00.00" + (notice date restriction) + // UTCDATE-TIME +params4 = array( [[ reltype [, trigRelparam ]] / datetimeparam ] + [, xparams ] ) +reltyp = "RELATED" => "START" (default) / "END" +trigRelparam = "VALUE" => "DURATION" +datetimeparam= "VALUE" => "DATE-TIME" +xparam = *[ xparamkey => xparamvalue ]

    +

    Example 1

    +

    $valarm->setProperty( "trigger" + , FALSE, FALSE, FALSE, FALSE, 1, 2, 3 ); + // duration, 1 hour 2 min 3 sec, before start

    +

    Example 2

    +

    $valarm->setProperty( "trigger" + , array ("hour"=>1,"min"=>2,"sec"=>3 ); + // duration, 1 hour 2 min 3 sec, before start

    +

    Example 3

    +

    $valarm->setProperty( "trigger" + , "PT1H2M3S" ); + // duration, 1 hour 2 min 3 sec, before start

    +

    Example 4

    +

    $valarm->setProperty( "trigger" + , FALSE, FALSE, FALSE, 1 + , FALSE, FALSE, FALSE, FALSE, FALSE ); + // duration, 1 week after end

    +

    Example 5

    +

    $valarm->setProperty( "trigger" + , array ( "week" => 1 + , "relatedStart" => FALSE + , "before" => FALSE )); + // duration, 1 week after end

    +

    Example 6

    +

    $valarm->setProperty( "trigger" + , "P1W" + , array( "related" => "END" )); + // duration, 1 week after end

    +

    Example 7

    +

    $valarm->setProperty( "trigger" + , array( "year" => 2007 + , "month" => 6 + , "day" => 5, + , "hour" => 2 + , "min" => 2 + , "sec" => 3 ));

    +
    +[index] [top] [up] + + +

    3.2.41 TZID

    +This property specifies the text value that uniquely identifies the VTIMEZONE calendar component and is REQUIRED, but MUST NOT occur more than once.

    +The value type for TZID is TEXT. +
    Delete TZID
    +Remove TZID from component. +

    Format

    +

    deleteProperty( "TZID" )

    +

    Example

    +

    $vtimezone->deleteProperty( "TZID" );

    +
    Get TZID
    +Fetch property value. +

    Format 1

    +

    getProperty( "TZID" )

    +

    output = tzid1

    +

    Format 2

    +

    getProperty( "TZID", FALSE , TRUE )

    +

    output = array( "value" => tzid1 + , "params" => xparam2 )

    +

    Example

    +

    $tzid = $vtimezone->getProperty( "TZID" );

    +
    Set TZID
    +Insert property value. +

    Format

    +

    setProperty( "tzid", tzid [, xparam ] )

    +

    tzid1 = Value type TEXT +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vtimezone = & $vcalendar->newComponent( "vtimezone" ); +$vtimezone->setProperty( "tzid", "US-Eastern" ); +.. .

    +
    +[index] [top] [up] + + +

    3.2.42 TZNAME

    +This property specifies the customary designation for a STANDARD or DAYLIGHT description and is OPTIONAL and MAY occur more than once.

    +The value type for TZNAME is TEXT. +
    Delete TZNAME
    +Remove TZNAME from component. +

    Format

    +

    deleteProperty( "TZNAME" )

    +

    Example 1

    +

    $vtimezonestd->deleteProperty( "TZNAME" );

    +

    Example 2

    +Delete TZNAME property no 2. +

    $vtimezonestd->deleteProperty( "TZNAME", 2 );

    +

    Example 3

    +Deleting all TZNAME properties. +

    while( $vtimezonestd->deleteProperty( "TZNAME" )) + continue;

    +
    Get TZNAME
    +Fetch property value. +

    Format 1

    +

    getProperty( "TZNAME" )

    +

    output = tzname1

    +

    Format 2

    +

    getProperty( "TZNAME", propOrderNo/FALSE, TRUE )

    +

    output = array( "value" => tzname1 + , "params" => params2 )

    +

    Format 3

    +

    getProperty( "TZNAME", propOrderNo )

    +

    Get propOrderNo TZNAME

    +

    Example

    +

    $tzname = $timezonestandard->getProperty( "TZNAME" );

    +
    Set TZNAME
    +Insert property value. +

    Format

    +

    setProperty( "tzname", tzname [, params [, propOrderNo ]] )

    +

    tzname1 = Value type TEXT +params2 = array( [ "LANGUAGE" => "<lang>" ] [, xparam ] ) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vtimezone = & $vcalendar->newComponent( "vtimezone" ); +$vtimezone->setProperty( "Tzid", "US-Eastern" ); +$vtimezone->setProperty( "Last-Modified", "19870101" ); +$standard = & $vtimezone->newComponent( "standard" ); +$standard->setProperty( "tzname", "EST" ); +.. .

    +
    +[index] [top] [up] + + +

    3.2.43 TZOFFSETFROM

    +This property specifies the offset which is in use prior to this TIMEZONE observance. +The property is REQUIRED, but MUST NOT occur more than once in STANDARD and DAYLIGHT components. +
    Delete TZOFFSETFROM
    +Remove TZOFFSETFROM from component. +

    Format

    +

    deleteProperty( "TZOFFSETFROM" )

    +

    Example

    +

    $vtimezonestd->deleteProperty( "TZOFFSETFROM" );

    +
    Get TZOFFSETFROM
    +Fetch property value. +

    Format 1

    +

    getProperty( "TZOFFSETFROM" )

    +

    output = tzoffsetfrom1

    +

    Format 2

    +

    getProperty( "TZOFFSETFROM", FALSE , TRUE )

    +

    output = array( "value" => tzoffsetfrom1 + , "params" => xparam2 )

    +

    Example

    +

    $tzoffsetfrom = $timezonestandard->getProperty( "TZOFFSETFROM" );

    +
    Set TZOFFSETFROM
    +Insert property value. +

    Format

    +

    setProperty( "tzoffsetfrom", tzoffsetfrom [, xparam ] )

    +

    tzoffsetfrom1 = (+/-)HHmm[ss], UTC offset +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vtimezone = & $vcalendar->newComponent( "vtimezone" ); +$vtimezone->setProperty( "Tzid", "US-Eastern" ); +$vtimezone->setProperty( "Last-Modified", "19870101" ); +$standard = & $vtimezone->newComponent( "standard" ); +$standard->setProperty( "tzname", "EST" ); +$standard->setProperty( "tzoffsetfrom", "-0500" ); +.. .

    +
    +[index] [top] [up] + + +

    3.2.44 TZOFFSETTO

    +This property specifies the offset which is in use in this TIMEZONE observance. +The property is REQUIRED, but MUST NOT occur more than once in STANDARD and DAYLIGHT components. +
    Delete TZOFFSETTO
    +Remove TZOFFSETTO from component. +

    Format

    +

    deleteProperty( "TZOFFSETTO" )

    +

    Example

    +

    $daylight->deleteProperty( "TZOFFSETTO" );

    +
    Get TZOFFSETTO
    +Fetch property value. +

    Format 1

    +

    getProperty( "TZOFFSETTO" )

    +

    output = tzoffsetto1

    +

    Format 2

    +

    getProperty( "TZOFFSETTO", FALSE , TRUE )

    +

    output = array( "value" => tzoffsetto1 + , "params" => xparam2 )

    +

    Example

    +

    $tzoffsetto = $timezonestandard->getProperty( "TZOFFSETTO" );

    +
    Set TZOFFSETTO
    +Insert property value. +

    Format

    +

    setproperty( "tzoffsetto", tzoffsetto [, xparam ] )

    +

    tzoffsetto1 = (+/-)HHmm[ss], UTC offset +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vtimezone = & $vcalendar->newComponent( "vtimezone" ); +$vtimezone->setProperty( "Tzid", "US-Eastern" ); +$vtimezone->setProperty( "Last-Modified", "19870101" ); +$standard = & $vtimezone->newComponent( "standard" ); +.. . +$daylight = & $vtimezone->newComponent( "daylight" ); +$daylight->setProperty( "tzoffsetto", "1345" ); +.. .

    +
    +[index] [top] [up] + + +

    3.2.45 TZURL

    +The TZURL provides a means for a VTIMEZONE component to point to +a network location that can be used to retrieve an up-to-date version of itself. The property +is OPTIONAL and MUST NOT occur more than once. +
    Delete TZURL
    +Remove TZURL from component. +

    Format

    +

    deleteProperty( "TZURL" )

    +

    Example

    +

    $vtimezone->deleteProperty( "TZURL" );

    +
    Get TZURL
    +Fetch property value. +

    Format 1

    +

    getProperty( "TZURL" )

    +

    output = tzurl1

    +

    Format 2

    +

    getProperty( "TZURL", FALSE , TRUE )

    +

    output = array( "value" => tzurl1 + , "params" => xparam2 )

    +

    Example

    +

    $tzurl = $timezonestandard->getProperty( "TZURL" );

    +
    Set TZURL
    +Insert property value. +

    Format

    +

    setProperty( "tzurl", tzurl [, xparam ] )

    +

    tzurl1 = Value type URI +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $tz = "http://zones.stds_r_us.net/tz/US-Eastern" ); +$config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +$vtimezone = & $vcalendar->newComponent( "vtimezone" ); +$vtimezone->setProperty( "Tzid", "US-Eastern" ); +$vtimezone->setProperty( "Last-Modified", "19870101T000000" ); +$vtimezone->setProperty( "tzurl", $tz ); +.. . +.. .

    +
    +[index] [top] [up] + + +

    3.2.46 UID

    +The persistent, globally Unique IDentifier for the calendar component. +The property is OPTIONAL and MUST NOT occur more than once in VEVENT, VTODO, VJOURNAL and VFREEBUSY components.
    +However, UID is AUTOMATICALLY generated in iCalcreator and configuration unique_id, is used when auto-creating component UID. +
    +
    +UID generated format : +

    date("Ymd\THisT")."-".[microSec][random]."@".unique_id +

    +microSec = microseconds, 4 pos
    +random = 6 characters aA-zZ, 0-9 +

    Example

    +

    "20070803T194810CEST-0123U3PXiX@kigkonsult.se"

    +UID may be required when importing iCal files into some calendaring software (MS etc.), +as well as (calendar) properties x-properties "X-WR-CALNAME", "X-WR-CALDESC" and "X-WR-TIMEZONE", +METHOD (value PUBLISH etc.) and the (also automatically created) DTSTAMP property. +

    +The value type for UID is TEXT. +
    Delete UID
    +If UID is remove from a component, UID will automatically be recreated when calendar output functions like createCalendar, returnCalendar or saveCalendar is executed. +

    Format

    +

    deleteProperty( "UID" )

    +

    Example

    +

    $vevent->deleteProperty( "UID" );

    +
    Get UID
    +Fetch property value. +

    Format 1

    +

    getProperty( "UID" )

    +

    output = uid1

    +

    Format 2

    +

    getProperty( "UID", FALSE , TRUE )

    +

    output = array( "value" => uid1 + , "params" => xparam2 )

    +

    Example

    +

    $uid = $vevent->getProperty( "UID" );

    +
    Set UID
    +Insert property value, overrides any previously set or auto-created UID.
    +Do NOT use an integer UID or only a component name in UID (ex. "vevent"), this may cause malfunction in setComponent with index or UID argument. +

    Format

    +

    setProperty( "uid", uid [, xparam ] )

    +

    uid1 = Value type TEXT +xparam2 = array( *[ xparamkey => xparamvalue ] )

    +

    Example

    +

    $vevent->setProperty("uid","20070803T194810CEST-0123U3PXiX@domain.com");

    +
    +[index] [top] [up] + + +

    3.2.47 URL

    +This property defines a Uniform Resource Locator (URL) associated with the iCalendar object. +The property is OPTIONAL and MUST NOT occur more than once in VEVENT, VTODO, VJOURNAL and VFREEBUSY components. +
    Delete URL
    +Remove URL from component. +

    Format

    +

    deleteProperty( "URL" )

    +

    Example

    +

    $vevent->deleteProperty( "URL" );

    +
    Get URL
    +Fetch property value. +

    Format 1

    +

    getProperty( "URL" )

    +

    output = url1

    +

    Format 2

    +

    getProperty( "URL", FALSE , TRUE )

    +

    output = array "value" => url1 + , "params" => xparam2 )

    +

    Example

    +

    $url = $vevent->getProperty( "URL" );

    +
    Set URL
    +Insert property value.
    +

    Format

    +

    setProperty( "url", url [, xparam ] )

    +

    url1 = Value type URI +xparam2 = array( *[ xparamkey => xparamvalue ] (

    +

    Example

    +

    $vtodo->setProperty( "url", "http://www.icaldomain.net" );

    +
    +[index] [top] [up] + + +

    3.2.48 X-PROPERTY

    +A component, non-standard property with a TEXT value and a name with an "X-" prefix. In a component, +an x-property, with an unique name, can occur only once but the number of x-properties are unlimited. +
    +
    Delete X-PROPERTY
    +Remove X-PROPERTY from component. +

    Format

    +

    deleteProperty( "<X-PROPERTY>" )

    +

    Example 1

    +

    $vevent->deleteProperty( "<X-PROPERTY>" );

    +

    Example 2

    +Deleting all x-properties. +

    while( $vevent->deleteProperty()) + continue;

    +
    Get X-property
    +Fetch property value. +

    Format 1

    +

    getProperty( "<X-PROPERTY>" )

    +

    output = array( propertyName1 + , propertyData2 )

    +

    Format 2

    +

    getProperty()

    +

    output = array( propertyName1 + , propertyData2 )

    +

    Format 3

    +

    getProperty( FALSE, propOrderNo/FALSE, TRUE )

    +

    output = array( propertyName1 + , array ( "value" => propertyData2 ) + , "params" => params 3))

    +

    Format 4

    +

    getProperty( FALSE, propOrderNo )

    +

    Get propOrderNo X-property

    + +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se", + "filename", "file.ics" ); ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +if( FALSE !== ( $d = $vcalendar->getProperty( "X-WR-TIMEZONE" ))) + echo $d[1]; +.. .

    +

    // $xprop = array( propertyName1, propertyData2 )

    + +

    Example 2

    +

    $config = array( "unique_id" => "kigkonsult.se", + "filename", "file.ics" ); ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +while( $xprop = $vcalendar->getProperty( )) { +.. .

    +

    // $xprop = array( propertyName1, propertyData2 )

    + +

    Example 3

    +

    $config = array( "unique_id" => "kigkonsult.se", + "filename", "file.ics" ); ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +while( $xprop = $vcalendar->getProperty( "X-ABC-MMSUBJ" )) { +.. .

    +

    // $xprop = array( "X-ABC-MMSUBJ", propertyData2 )

    +

    Example 4

    +

    $config = array( "unique_id" => "kigkonsult.se", + "filename", "file.ics" ); ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +while( $xprop = $vcalendar->getProperty( FALSE, FALSE, TRUE )) { +.. .

    +

    // $xprop = array( propertyName1 + // , array( "value" => propertyData2 ) + // , "params" => params 3 )

    +
    Set X-property
    +Insert property name and value. If an x-prop with the same name already exists, it will be replaced. +

    Format

    +

    setProperty( propertyName, propertyData [, params ] )

    +

    propertyName1 = Any property name with a "X-" prefix +propertyData2 = Value type TEXT +params3 = array( ["LANGUAGE" => "<lang>"] [, xparam] ) +xparam = *[ xparamkey => xparamvalue ] +propOrderNo = int ordernumber, 1=1st, 2=2nd etc

    +

    Example

    +

    $component->setProperty("X-ABC-MMSUBJ","http://load.noise.org/mysubj.wav");

    +
    +[index] [top] [up] + + + +

    3.3 Calendar Component configuration functions

    + +

    3.3.1 Language

    +Language for specific calendar component as defined in [RFC 1766].
    +Language set at component level can be overridden by specific component property parameter.
    +A successful "setConfig" returns TRUE. +
    Get language
    +Language for calendar (only if language is set at component level). +

    Format

    +

    getConfig( "language" )

    +

    Example

    +

    $lang = $vevent->getConfig( "language" );

    +
    Set LANGUAGE
    +

    Format

    +

    setConfig( "language", string <lang> )

    +

    Example

    +

    $vevent->setConfig( "language", "en" );

    +
    +[index] [top] [up] + +

    3.4 Calendar component object misc. functions

    +Calendar component subcomponent functions + +

    3.4.1 deleteComponent

    +Remove subcomponent from component. +

    Format

    +

    deleteComponent( int orderNumber )

    +

    Remove component with order number (1st=1, 2nd=2.. .).

    +

    deleteComponent( string componentType [, int componentSuborderNumber])

    +

    Remove component with component type (ex. "vevent") +and order 1 alt. suborder number.

    +

    deleteComponent( string UID )

    +

    Remove component with UID. +N.B UID is NOT set for ALARM / timezone components.

    +

    Example 1

    +Delete first subcomponent. +

    $config = array( "unique_id" => "kigkonsult.se", + "filename", "file.ics" ); ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$comp1 = $vcalendar->getComponent(); +$comp1->deleteComponent( 1 ); +.. .

    +

    Example 2

    +Delete all subcomponents. +

    $config = array( "unique_id" => "kigkonsult.se", + "filename", "file.ics" ); ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$comp1 = $vcalendar->getComponent(); +while( $comp1->deleteComponent( "valarm" ) + continue; +.. .

    +
    +[index] [top] [up] + +

    3.4.2 getComponent

    +Get subComponent from component. +

    Format 1

    +

    getComponent()

    +

    Get next component until end-of-components.

    +

    Format 2

    +

    getComponent( int orderNumber )

    +

    Get component with order number (1st=1, 2nd=2.. .).

    +

    Format 3

    +

    getComponent( string componentType [, int componentSuborderNumber])

    +

    Get (next) component with component type (until end-of-components) +alt. component with component type and suborder number (1st=1, 2nd=2..).

    +

    Format 4

    +

    getComponent( string UID )

    +

    Get component with UID. +N.B UID is NOT set for ALARM / timezone components.

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se", + "filename", "file.ics" ); ); +$vcalendar = new vcalendar( $config ); +$vcalendar->parse(); +$comp1 = $vcalendar->getComponent()); +while( $subComp = $comp1->getComponent()) { +.. .

    +
    +[index] [top] [up] + +

    3.4.3 newComponent

    +Create subcomponent (ALARN / VTIMEZONE STANDARD / VTIMEZONE DAYLIGHT) +using a component factory-method, returning a reference to the new component. +

    Format

    +

    newComponent( string componentType )

    +

    Example 1

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vevent = & $vcalendar->newComponent( "vevent" ); +$vevent->setProperty( "dtstart" // add some EVENT properties + , 2006, 12, 24, 19, 30, 00 ); +$vevent->setProperty(.. . +... +$valarm = & $vevent->newComponent( "valarm" ); +$valarm->setProperty( "trigger", .. . +... +

    +

    Example 2

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +... +$vtimezone = & $vcalendar->newComponent( "vtimezone" ); +$vtimezone->setProperty(.. . +... +$standard = & $vtimezone->newComponent( "standard" ); +$standard->setProperty(.. . +... +$daylight = & $vtimezone->newComponent( "daylight" ); +$daylight->setProperty(.. . +... +

    + +
    +[index] [top] [up] + +

    3.4.4 setComponent

    +Add calendar component to calendar or replace/update component in calendar. +

    Format 1

    +

    setComponent( component ) +addSubComponent( component ) // alias

    +

    Insert last in component chain.

    +

    Format 2

    +

    setComponent( component, int orderNumber )

    +

    Replace component with order number(1st=1, 2nd=2.. .). +If orderNumber is not found, component is inserted last in chain.

    +

    Format 3

    +

    setComponent( component, string componentType [,component suborder number])

    +

    Replace component with component type and component order number. +if orderNumber is not found, component is inserted last in chain.

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); // initiate new CALENDAR +.. . +$vevent = new vevent(); +$vevent->setProperty( "dtstart" // add some EVENT properties + , 2006, 12, 24, 19, 30, 00 ); +$vevent->setProperty(.. . +.. . +$valarm = new valarm(); +$valarm->setProperty( "trigger", .. . +.. . +$vevent->setComponent( $valarm ); +$vcalendar->setComponent( $vevent ); +.. . +

    +
    +[index] [top] [up] + +

    4. iCalUtilityFunctions

    +iCalUtilityFunctions.class.php contains static functions used by iCalcreator, +also usable outside the iCalcreator class. Please examine the class file to explore other functions. +
    +
    +

    4.1 createTimezone

    +The function, applied on a iCalcrator instance and using a PHP valid timezone (as arguments) +creates simple vtimezone, standard and (opt.) daylight components, based on PHP DateTimeZone class and +the most recent transition dates for the timezone.
    +(PHP 5 >= 5.2.0) +
    +
    +FALSE is returned if not using a PHP valid timezone. +

    Format

    +

    createTimezone( calendar, timezone [, xprops ] )

    +

    calendar = iCalcreator instance +timezone = an PHP (DateTimeZone) valid timezone +xprops = array( *[ x-propName => value ] ), timezone non-standard properties +

    +

    Example

    +

    $config = array( "unique_id" => "kigkonsult.se" ); +$vcalendar = new vcalendar( $config ); +.. . +$PHPtz = "Europe/Stockholm"; +$xprops = array( "X-LIC-LOCATION" => $PHPtz ); +iCalUtilityFunctions::createTimezone( $vcalendar, $PHPtz, $xprops ); +.. . +

    +Output (when using createCalendar or returnCalendar functions):
    + +BEGIN:VTIMEZONE
    +TZID:Europe/Stockholm
    +X-LIC-LOCATION:Europe/Stockholm
    +BEGIN:STANDARD
    +DTSTART:20101031T020000
    +TZOFFSETFROM:+0200
    +TZOFFSETTO:+0100
    +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10
    +TZNAME:CET
    +END:STANDARD
    +BEGIN:DAYLIGHT
    +DTSTART:20100328T030000
    +TZOFFSETFROM:+0100
    +TZOFFSETTO:+0200
    +RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=3
    +TZNAME:CEST
    +END:DAYLIGHT
    +END:VTIMEZONE +
    +
    +
    +[index] [top] [up] + +

    4.2 transformDateTime

    +Transforms a datetime from a time zone to another. (Requires PHP >= 5.2.0 and PHP DateTimeZone acceptable time zones) +
    +
    +FALSE is returned if not using a "strtotime" acceptable datetime or unacceptable PHP time zones. +If TRUE, the dateTime argument (below) is converted to the new time zone, otherwise unaltered. +

    Format

    +

    transformDateTime( dateTime, timezoneFrom, timezoneTo [, format ] )

    +

    dateTime = string datetime // acceptable by strtotime function + ex.  "14 august 2006 16.00.00" + (notice date restriction) +timezoneFrom = string, a PHP (DateTimeZone) valid time zone +timezoneTo = string, a PHP (DateTimeZone) valid time zone, default "UTC" +format = string, format accepted by date(), default "Ymd\THis" +

    +

    Example

    +

    +.. . +$d = date( "Y-m-d H:i:s" ); +$tzFr = "Europe/Stockholm"; +if( FALSE !== iCalUtilityFunctions::transformDateTime( $d, $tzFr )) + $event->setProperty( "dtstart", $d."Z" ); +else + $event->setProperty( "dtstart", $d, array( "TZID" => $tzFr )); +.. .
    +

    +[index] [top] [up] + +

    5. COPYRIGHT AND LICENSE

    +

    Copyright

    +iCalcreator class
    +copyright (c) 2007-2011 Kjell-Inge Gustafsson, kigkonsult
    +kigkonsult.se/iCalcreator
    +ical@kigkonsult.se
    + +

    License

    + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. +

    +This library 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 +Lesser General Public License for more details. +

    +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +or download it here. +
    +
    +[index] [top] + + \ No newline at end of file diff --git a/lib/qCal/VERSION b/lib/qCal/VERSION new file mode 100644 index 0000000..9a47481 --- /dev/null +++ b/lib/qCal/VERSION @@ -0,0 +1 @@ +QCal v0.0.2 diff --git a/lib/qCal/docs/CHANGES b/lib/qCal/docs/CHANGES new file mode 100644 index 0000000..2c82d57 --- /dev/null +++ b/lib/qCal/docs/CHANGES @@ -0,0 +1,15 @@ +Changelog for qCal, since Version 0.0.2 +--------------------------------------------- + +January 14, 2010: v0.0.2 +-------------------- + + * Integrated qCal_DateTime sub-components into the library. + * Removed old qCal_Date component (replaced by qCal_DateTime, qCal_DateV2, + which has been renamed qCal_Date, and qCal_Time) + * Added full documentation for the library at http://qcal.lukevisinoni.com/ as + well as inside the "docs" sub-folder. + * Added this changelog. + * Added README and VERSION files. + +-- End of Changes -- diff --git a/lib/qCal/docs/LICENSE b/lib/qCal/docs/LICENSE new file mode 100644 index 0000000..fc8a5de --- /dev/null +++ b/lib/qCal/docs/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/lib/qCal/docs/README b/lib/qCal/docs/README new file mode 100644 index 0000000..cc1ccb9 --- /dev/null +++ b/lib/qCal/docs/README @@ -0,0 +1,33 @@ +qCal iCalendar Library for PHP5, by Luke Visinoni +------------------------------------------------------------------------------- + +qCal is an object-oriented, open-source iCalendar library for PHP5. +It is released under the LGPL license. + +Homepage: http://qcal.lukevisinoni.com/ +Documentation: http://qcal.lukevisinoni.com/doku.php?id=start +Mailing List: http://groups.google.com/group/qcal +Bugs: http://code.google.com/p/qcal/issues/list +Repository: https://qcal.googlecode.com/svn/ + +Installation +------------ + +This library requires no installation. At least not in the traditional sense. +Simply upload to a path within PHP's include path and include +"lib/autoload.php". + +For more info, visit http://qcal.lukevisinoni.com/doku.php?id=installation + +Show your support! +------------------ + +If you use the library and would like to show your support, feel free to make a +donation. I accept donations via PayPal. You can send me money at the following +e-mail address. + + luke.visinoni@gmail.com + +I didn't write this library to make money, but I do appreciate donations. In +fact, those users who donate to the project are by far cooler than those who +don't. I like donors better than non-donors. LOL! \ No newline at end of file diff --git a/lib/qCal/docs/VERSION b/lib/qCal/docs/VERSION new file mode 100644 index 0000000..38c4ba3 --- /dev/null +++ b/lib/qCal/docs/VERSION @@ -0,0 +1 @@ +qCal-v0.0.2 \ No newline at end of file diff --git a/lib/qCal/docs/example.php b/lib/qCal/docs/example.php new file mode 100644 index 0000000..c7ba1d5 --- /dev/null +++ b/lib/qCal/docs/example.php @@ -0,0 +1,45 @@ + $filepath, +)); +// parse a file +$ical = $parser->parseFile('simple.ics'); +// parse raw data +// $rawdata = file_get_contents($filepath . '/simple.ics'); +// $ical->parse($rawdata); + +/** + * Render an iCal object as an icalendar file + */ +$iCalData = $ical->render(); + +// eventually we can use other renderers as well... +// $xCal = $ical->render(new qCal_Renderer_xCal()); // xCal is an implementation of icalendar in xml +// $hCal = $ical->render(new qCal_Renderer_hCal()); // hCal is a microformat (html version of icalendar format) + +/** + * Build an iCal object from scratch + */ +$calendar = new qCal(array( + 'prodid' => '-//Some Calendar Company//Calendar Program v0.1//EN' +)); +$todo = new qCal_Component_Vtodo(array( + 'class' => 'private', + 'dtstart' => '20090909', + 'description' => 'Eat some bacon!!', + 'summary' => 'Eat bacon', + 'priority' => 1, +)); +$todo->attach(new qCal_Component_Valarm(array( + 'action' => 'audio', + 'trigger' => '20090423', + 'attach' => 'http://www.example.com/foo.wav', +))); +$calendar->attach($todo); +// now you can render the calendar if you want, or just echo it out \ No newline at end of file diff --git a/lib/qCal/docs/iso/ISO_8601-2004_E.pdf b/lib/qCal/docs/iso/ISO_8601-2004_E.pdf new file mode 100644 index 0000000..146e6cf Binary files /dev/null and b/lib/qCal/docs/iso/ISO_8601-2004_E.pdf differ diff --git a/lib/qCal/docs/rfc/rfc1738.txt b/lib/qCal/docs/rfc/rfc1738.txt new file mode 100644 index 0000000..3728866 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc1738.txt @@ -0,0 +1,1403 @@ + + + + + + +Network Working Group T. Berners-Lee +Request for Comments: 1738 CERN +Category: Standards Track L. Masinter + Xerox Corporation + M. McCahill + University of Minnesota + Editors + December 1994 + + + Uniform Resource Locators (URL) + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This document specifies a Uniform Resource Locator (URL), the syntax + and semantics of formalized information for location and access of + resources via the Internet. + +1. Introduction + + This document describes the syntax and semantics for a compact string + representation for a resource available via the Internet. These + strings are called "Uniform Resource Locators" (URLs). + + The specification is derived from concepts introduced by the World- + Wide Web global information initiative, whose use of such objects + dates from 1990 and is described in "Universal Resource Identifiers + in WWW", RFC 1630. The specification of URLs is designed to meet the + requirements laid out in "Functional Requirements for Internet + Resource Locators" [12]. + + This document was written by the URI working group of the Internet + Engineering Task Force. Comments may be addressed to the editors, or + to the URI-WG . Discussions of the group are archived + at + + + + + + + + +Berners-Lee, Masinter & McCahill [Page 1] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +2. General URL Syntax + + Just as there are many different methods of access to resources, + there are several schemes for describing the location of such + resources. + + The generic syntax for URLs provides a framework for new schemes to + be established using protocols other than those defined in this + document. + + URLs are used to `locate' resources, by providing an abstract + identification of the resource location. Having located a resource, + a system may perform a variety of operations on the resource, as + might be characterized by such words as `access', `update', + `replace', `find attributes'. In general, only the `access' method + needs to be specified for any URL scheme. + +2.1. The main parts of URLs + + A full BNF description of the URL syntax is given in Section 5. + + In general, URLs are written as follows: + + : + + A URL contains the name of the scheme being used () followed + by a colon and then a string (the ) whose + interpretation depends on the scheme. + + Scheme names consist of a sequence of characters. The lower case + letters "a"--"z", digits, and the characters plus ("+"), period + ("."), and hyphen ("-") are allowed. For resiliency, programs + interpreting URLs should treat upper case letters as equivalent to + lower case in scheme names (e.g., allow "HTTP" as well as "http"). + +2.2. URL Character Encoding Issues + + URLs are sequences of characters, i.e., letters, digits, and special + characters. A URLs may be represented in a variety of ways: e.g., ink + on paper, or a sequence of octets in a coded character set. The + interpretation of a URL depends only on the identity of the + characters used. + + In most URL schemes, the sequences of characters in different parts + of a URL are used to represent sequences of octets used in Internet + protocols. For example, in the ftp scheme, the host name, directory + name and file names are such sequences of octets, represented by + parts of the URL. Within those parts, an octet may be represented by + + + +Berners-Lee, Masinter & McCahill [Page 2] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + the chararacter which has that octet as its code within the US-ASCII + [20] coded character set. + + In addition, octets may be encoded by a character triplet consisting + of the character "%" followed by the two hexadecimal digits (from + "0123456789ABCDEF") which forming the hexadecimal value of the octet. + (The characters "abcdef" may also be used in hexadecimal encodings.) + + Octets must be encoded if they have no corresponding graphic + character within the US-ASCII coded character set, if the use of the + corresponding character is unsafe, or if the corresponding character + is reserved for some other interpretation within the particular URL + scheme. + + No corresponding graphic US-ASCII: + + URLs are written only with the graphic printable characters of the + US-ASCII coded character set. The octets 80-FF hexadecimal are not + used in US-ASCII, and the octets 00-1F and 7F hexadecimal represent + control characters; these must be encoded. + + Unsafe: + + Characters can be unsafe for a number of reasons. The space + character is unsafe because significant spaces may disappear and + insignificant spaces may be introduced when URLs are transcribed or + typeset or subjected to the treatment of word-processing programs. + The characters "<" and ">" are unsafe because they are used as the + delimiters around URLs in free text; the quote mark (""") is used to + delimit URLs in some systems. The character "#" is unsafe and should + always be encoded because it is used in World Wide Web and in other + systems to delimit a URL from a fragment/anchor identifier that might + follow it. The character "%" is unsafe because it is used for + encodings of other characters. Other characters are unsafe because + gateways and other transport agents are known to sometimes modify + such characters. These characters are "{", "}", "|", "\", "^", "~", + "[", "]", and "`". + + All unsafe characters must always be encoded within a URL. For + example, the character "#" must be encoded within URLs even in + systems that do not normally deal with fragment or anchor + identifiers, so that if the URL is copied into another system that + does use them, it will not be necessary to change the URL encoding. + + + + + + + + +Berners-Lee, Masinter & McCahill [Page 3] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + Reserved: + + Many URL schemes reserve certain characters for a special meaning: + their appearance in the scheme-specific part of the URL has a + designated semantics. If the character corresponding to an octet is + reserved in a scheme, the octet must be encoded. The characters ";", + "/", "?", ":", "@", "=" and "&" are the characters which may be + reserved for special meaning within a scheme. No other characters may + be reserved within a scheme. + + Usually a URL has the same interpretation when an octet is + represented by a character and when it encoded. However, this is not + true for reserved characters: encoding a character reserved for a + particular scheme may change the semantics of a URL. + + Thus, only alphanumerics, the special characters "$-_.+!*'(),", and + reserved characters used for their reserved purposes may be used + unencoded within a URL. + + On the other hand, characters that are not required to be encoded + (including alphanumerics) may be encoded within the scheme-specific + part of a URL, as long as they are not being used for a reserved + purpose. + +2.3 Hierarchical schemes and relative links + + In some cases, URLs are used to locate resources that contain + pointers to other resources. In some cases, those pointers are + represented as relative links where the expression of the location of + the second resource is in terms of "in the same place as this one + except with the following relative path". Relative links are not + described in this document. However, the use of relative links + depends on the original URL containing a hierarchical structure + against which the relative link is based. + + Some URL schemes (such as the ftp, http, and file schemes) contain + names that can be considered hierarchical; the components of the + hierarchy are separated by "/". + + + + + + + + + + + + + +Berners-Lee, Masinter & McCahill [Page 4] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +3. Specific Schemes + + The mapping for some existing standard and experimental protocols is + outlined in the BNF syntax definition. Notes on particular protocols + follow. The schemes covered are: + + ftp File Transfer protocol + http Hypertext Transfer Protocol + gopher The Gopher protocol + mailto Electronic mail address + news USENET news + nntp USENET news using NNTP access + telnet Reference to interactive sessions + wais Wide Area Information Servers + file Host-specific file names + prospero Prospero Directory Service + + Other schemes may be specified by future specifications. Section 4 of + this document describes how new schemes may be registered, and lists + some scheme names that are under development. + +3.1. Common Internet Scheme Syntax + + While the syntax for the rest of the URL may vary depending on the + particular scheme selected, URL schemes that involve the direct use + of an IP-based protocol to a specified host on the Internet use a + common syntax for the scheme-specific data: + + //:@:/ + + Some or all of the parts ":@", ":", + ":", and "/" may be excluded. The scheme specific + data start with a double slash "//" to indicate that it complies with + the common Internet scheme syntax. The different components obey the + following rules: + + user + An optional user name. Some schemes (e.g., ftp) allow the + specification of a user name. + + password + An optional password. If present, it follows the user + name separated from it by a colon. + + The user name (and password), if present, are followed by a + commercial at-sign "@". Within the user and password field, any ":", + "@", or "/" must be encoded. + + + + +Berners-Lee, Masinter & McCahill [Page 5] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + Note that an empty user name or password is different than no user + name or password; there is no way to specify a password without + specifying a user name. E.g., has an empty + user name and no password, has no user name, + while has a user name of "foo" and an + empty password. + + host + The fully qualified domain name of a network host, or its IP + address as a set of four decimal digit groups separated by + ".". Fully qualified domain names take the form as described + in Section 3.5 of RFC 1034 [13] and Section 2.1 of RFC 1123 + [5]: a sequence of domain labels separated by ".", each domain + label starting and ending with an alphanumerical character and + possibly also containing "-" characters. The rightmost domain + label will never start with a digit, though, which + syntactically distinguishes all domain names from the IP + addresses. + + port + The port number to connect to. Most schemes designate + protocols that have a default port number. Another port number + may optionally be supplied, in decimal, separated from the + host by a colon. If the port is omitted, the colon is as well. + + url-path + The rest of the locator consists of data specific to the + scheme, and is known as the "url-path". It supplies the + details of how the specified resource can be accessed. Note + that the "/" between the host (or port) and the url-path is + NOT part of the url-path. + + The url-path syntax depends on the scheme being used, as does the + manner in which it is interpreted. + +3.2. FTP + + The FTP URL scheme is used to designate files and directories on + Internet hosts accessible using the FTP protocol (RFC959). + + A FTP URL follow the syntax described in Section 3.1. If : is + omitted, the port defaults to 21. + + + + + + + + + +Berners-Lee, Masinter & McCahill [Page 6] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +3.2.1. FTP Name and Password + + A user name and password may be supplied; they are used in the ftp + "USER" and "PASS" commands after first making the connection to the + FTP server. If no user name or password is supplied and one is + requested by the FTP server, the conventions for "anonymous" FTP are + to be used, as follows: + + The user name "anonymous" is supplied. + + The password is supplied as the Internet e-mail address + of the end user accessing the resource. + + If the URL supplies a user name but no password, and the remote + server requests a password, the program interpreting the FTP URL + should request one from the user. + +3.2.2. FTP url-path + + The url-path of a FTP URL has the following syntax: + + //...//;type= + + Where through and are (possibly encoded) strings + and is one of the characters "a", "i", or "d". The part + ";type=" may be omitted. The and parts may be + empty. The whole url-path may be omitted, including the "/" + delimiting it from the prefix containing user, password, host, and + port. + + The url-path is interpreted as a series of FTP commands as follows: + + Each of the elements is to be supplied, sequentially, as the + argument to a CWD (change working directory) command. + + If the typecode is "d", perform a NLST (name list) command with + as the argument, and interpret the results as a file + directory listing. + + Otherwise, perform a TYPE command with as the argument, + and then access the file whose name is (for example, using + the RETR command.) + + Within a name or CWD component, the characters "/" and ";" are + reserved and must be encoded. The components are decoded prior to + their use in the FTP protocol. In particular, if the appropriate FTP + sequence to access a particular file requires supplying a string + containing a "/" as an argument to a CWD or RETR command, it is + + + +Berners-Lee, Masinter & McCahill [Page 7] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + necessary to encode each "/". + + For example, the URL is + interpreted by FTP-ing to "host.dom", logging in as "myname" + (prompting for a password if it is asked for), and then executing + "CWD /etc" and then "RETR motd". This has a different meaning from + which would "CWD etc" and then + "RETR motd"; the initial "CWD" might be executed relative to the + default directory for "myname". On the other hand, + , would "CWD " with a null + argument, then "CWD etc", and then "RETR motd". + + FTP URLs may also be used for other operations; for example, it is + possible to update a file on a remote file server, or infer + information about it from the directory listings. The mechanism for + doing so is not spelled out here. + +3.2.3. FTP Typecode is Optional + + The entire ;type= part of a FTP URL is optional. If it is + omitted, the client program interpreting the URL must guess the + appropriate mode to use. In general, the data content type of a file + can only be guessed from the name, e.g., from the suffix of the name; + the appropriate type code to be used for transfer of the file can + then be deduced from the data content of the file. + +3.2.4 Hierarchy + + For some file systems, the "/" used to denote the hierarchical + structure of the URL corresponds to the delimiter used to construct a + file name hierarchy, and thus, the filename will look similar to the + URL path. This does NOT mean that the URL is a Unix filename. + +3.2.5. Optimization + + Clients accessing resources via FTP may employ additional heuristics + to optimize the interaction. For some FTP servers, for example, it + may be reasonable to keep the control connection open while accessing + multiple URLs from the same server. However, there is no common + hierarchical model to the FTP protocol, so if a directory change + command has been given, it is impossible in general to deduce what + sequence should be given to navigate to another directory for a + second retrieval, if the paths are different. The only reliable + algorithm is to disconnect and reestablish the control connection. + + + + + + + +Berners-Lee, Masinter & McCahill [Page 8] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +3.3. HTTP + + The HTTP URL scheme is used to designate Internet resources + accessible using HTTP (HyperText Transfer Protocol). + + The HTTP protocol is specified elsewhere. This specification only + describes the syntax of HTTP URLs. + + An HTTP URL takes the form: + + http://:/? + + where and are as described in Section 3.1. If : + is omitted, the port defaults to 80. No user name or password is + allowed. is an HTTP selector, and is a query + string. The is optional, as is the and its + preceding "?". If neither nor is present, the "/" + may also be omitted. + + Within the and components, "/", ";", "?" are + reserved. The "/" character may be used within HTTP to designate a + hierarchical structure. + +3.4. GOPHER + + The Gopher URL scheme is used to designate Internet resources + accessible using the Gopher protocol. + + The base Gopher protocol is described in RFC 1436 and supports items + and collections of items (directories). The Gopher+ protocol is a set + of upward compatible extensions to the base Gopher protocol and is + described in [2]. Gopher+ supports associating arbitrary sets of + attributes and alternate data representations with Gopher items. + Gopher URLs accommodate both Gopher and Gopher+ items and item + attributes. + +3.4.1. Gopher URL syntax + + A Gopher URL takes the form: + + gopher://:/ + + where is one of + + + %09 + %09%09 + + + + +Berners-Lee, Masinter & McCahill [Page 9] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + If : is omitted, the port defaults to 70. is a + single-character field to denote the Gopher type of the resource to + which the URL refers. The entire may also be empty, in + which case the delimiting "/" is also optional and the + defaults to "1". + + is the Gopher selector string. In the Gopher protocol, + Gopher selector strings are a sequence of octets which may contain + any octets except 09 hexadecimal (US-ASCII HT or tab) 0A hexadecimal + (US-ASCII character LF), and 0D (US-ASCII character CR). + + Gopher clients specify which item to retrieve by sending the Gopher + selector string to a Gopher server. + + Within the , no characters are reserved. + + Note that some Gopher strings begin with a copy of the + character, in which case that character will occur twice + consecutively. The Gopher selector string may be an empty string; + this is how Gopher clients refer to the top-level directory on a + Gopher server. + +3.4.2 Specifying URLs for Gopher Search Engines + + If the URL refers to a search to be submitted to a Gopher search + engine, the selector is followed by an encoded tab (%09) and the + search string. To submit a search to a Gopher search engine, the + Gopher client sends the string (after decoding), a tab, + and the search string to the Gopher server. + +3.4.3 URL syntax for Gopher+ items + + URLs for Gopher+ items have a second encoded tab (%09) and a Gopher+ + string. Note that in this case, the %09 string must be + supplied, although the element may be the empty string. + + The is used to represent information required for + retrieval of the Gopher+ item. Gopher+ items may have alternate + views, arbitrary sets of attributes, and may have electronic forms + associated with them. + + To retrieve the data associated with a Gopher+ URL, a client will + connect to the server and send the Gopher selector, followed by a tab + and the search string (which may be empty), followed by a tab and the + Gopher+ commands. + + + + + + +Berners-Lee, Masinter & McCahill [Page 10] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +3.4.4 Default Gopher+ data representation + + When a Gopher server returns a directory listing to a client, the + Gopher+ items are tagged with either a "+" (denoting Gopher+ items) + or a "?" (denoting Gopher+ items which have a +ASK form associated + with them). A Gopher URL with a Gopher+ string consisting of only a + "+" refers to the default view (data representation) of the item + while a Gopher+ string containing only a "?" refer to an item with a + Gopher electronic form associated with it. + +3.4.5 Gopher+ items with electronic forms + + Gopher+ items which have a +ASK associated with them (i.e. Gopher+ + items tagged with a "?") require the client to fetch the item's +ASK + attribute to get the form definition, and then ask the user to fill + out the form and return the user's responses along with the selector + string to retrieve the item. Gopher+ clients know how to do this but + depend on the "?" tag in the Gopher+ item description to know when to + handle this case. The "?" is used in the Gopher+ string to be + consistent with Gopher+ protocol's use of this symbol. + +3.4.6 Gopher+ item attribute collections + + To refer to the Gopher+ attributes of an item, the Gopher URL's + Gopher+ string consists of "!" or "$". "!" refers to the all of a + Gopher+ item's attributes. "$" refers to all the item attributes for + all items in a Gopher directory. + +3.4.7 Referring to specific Gopher+ attributes + + To refer to specific attributes, the URL's gopher+_string is + "!" or "$". For example, to refer to + the attribute containing the abstract of an item, the gopher+_string + would be "!+ABSTRACT". + + To refer to several attributes, the gopher+_string consists of the + attribute names separated by coded spaces. For example, + "!+ABSTRACT%20+SMELL" refers to the +ABSTRACT and +SMELL attributes + of an item. + +3.4.8 URL syntax for Gopher+ alternate views + + Gopher+ allows for optional alternate data representations (alternate + views) of items. To retrieve a Gopher+ alternate view, a Gopher+ + client sends the appropriate view and language identifier (found in + the item's +VIEW attribute). To refer to a specific Gopher+ alternate + view, the URL's Gopher+ string would be in the form: + + + + +Berners-Lee, Masinter & McCahill [Page 11] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + +%20 + + For example, a Gopher+ string of "+application/postscript%20Es_ES" + refers to the Spanish language postscript alternate view of a Gopher+ + item. + +3.4.9 URL syntax for Gopher+ electronic forms + + The gopher+_string for a URL that refers to an item referenced by a + Gopher+ electronic form (an ASK block) filled out with specific + values is a coded version of what the client sends to the server. + The gopher+_string is of the form: + ++%091%0D%0A+-1%0D%0A%0D%0A%0D%0A.%0D%0A + + To retrieve this item, the Gopher client sends: + + +1 + +-1 + + + . + + to the Gopher server. + +3.5. MAILTO + + The mailto URL scheme is used to designate the Internet mailing + address of an individual or service. No additional information other + than an Internet mailing address is present or implied. + + A mailto URL takes the form: + + mailto: + + where is (the encoding of an) addr-spec, as + specified in RFC 822 [6]. Within mailto URLs, there are no reserved + characters. + + Note that the percent sign ("%") is commonly used within RFC 822 + addresses and must be encoded. + + Unlike many URLs, the mailto scheme does not represent a data object + to be accessed directly; there is no sense in which it designates an + object. It has a different use than the message/external-body type in + MIME. + + + + + +Berners-Lee, Masinter & McCahill [Page 12] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +3.6. NEWS + + The news URL scheme is used to refer to either news groups or + individual articles of USENET news, as specified in RFC 1036. + + A news URL takes one of two forms: + + news: + news: + + A is a period-delimited hierarchical name, such as + "comp.infosystems.www.misc". A corresponds to the + Message-ID of section 2.1.5 of RFC 1036, without the enclosing "<" + and ">"; it takes the form @. A message + identifier may be distinguished from a news group name by the + presence of the commercial at "@" character. No additional characters + are reserved within the components of a news URL. + + If is "*" (as in ), it is used to refer + to "all available news groups". + + The news URLs are unusual in that by themselves, they do not contain + sufficient information to locate a single resource, but, rather, are + location-independent. + +3.7. NNTP + + The nntp URL scheme is an alternative method of referencing news + articles, useful for specifying news articles from NNTP servers (RFC + 977). + + A nntp URL take the form: + + nntp://:// + + where and are as described in Section 3.1. If : + is omitted, the port defaults to 119. + + The is the name of the group, while the is the numeric id of the article within that newsgroup. + + Note that while nntp: URLs specify a unique location for the article + resource, most NNTP servers currently on the Internet today are + configured only to allow access from local clients, and thus nntp + URLs do not designate globally accessible resources. Thus, the news: + form of URL is preferred as a way of identifying news articles. + + + + + +Berners-Lee, Masinter & McCahill [Page 13] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +3.8. TELNET + + The Telnet URL scheme is used to designate interactive services that + may be accessed by the Telnet protocol. + + A telnet URL takes the form: + + telnet://:@:/ + + as specified in Section 3.1. The final "/" character may be omitted. + If : is omitted, the port defaults to 23. The : can + be omitted, as well as the whole : part. + + This URL does not designate a data object, but rather an interactive + service. Remote interactive services vary widely in the means by + which they allow remote logins; in practice, the and + supplied are advisory only: clients accessing a telnet URL + merely advise the user of the suggested username and password. + +3.9. WAIS + + The WAIS URL scheme is used to designate WAIS databases, searches, or + individual documents available from a WAIS database. WAIS is + described in [7]. The WAIS protocol is described in RFC 1625 [17]; + Although the WAIS protocol is based on Z39.50-1988, the WAIS URL + scheme is not intended for use with arbitrary Z39.50 services. + + A WAIS URL takes one of the following forms: + + wais://:/ + wais://:/? + wais://:/// + + where and are as described in Section 3.1. If : + is omitted, the port defaults to 210. The first form designates a + WAIS database that is available for searching. The second form + designates a particular search. is the name of the WAIS + database being queried. + + The third form designates a particular document within a WAIS + database to be retrieved. In this form is the WAIS + designation of the type of the object. Many WAIS implementations + require that a client know the "type" of an object prior to + retrieval, the type being returned along with the internal object + identifier in the search response. The is included in the + URL in order to allow the client interpreting the URL adequate + information to actually retrieve the document. + + + + +Berners-Lee, Masinter & McCahill [Page 14] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + The of a WAIS URL consists of the WAIS document-id, encoded + as necessary using the method described in Section 2.2. The WAIS + document-id should be treated opaquely; it may only be decomposed by + the server that issued it. + +3.10 FILES + + The file URL scheme is used to designate files accessible on a + particular host computer. This scheme, unlike most other URL schemes, + does not designate a resource that is universally accessible over the + Internet. + + A file URL takes the form: + + file:/// + + where is the fully qualified domain name of the system on + which the is accessible, and is a hierarchical + directory path of the form //.../. + + For example, a VMS file + + DISK$USER:[MY.NOTES]NOTE123456.TXT + + might become + + + + As a special case, can be the string "localhost" or the empty + string; this is interpreted as `the machine from which the URL is + being interpreted'. + + The file URL scheme is unusual in that it does not specify an + Internet protocol or access method for such files; as such, its + utility in network protocols between hosts is limited. + +3.11 PROSPERO + + The Prospero URL scheme is used to designate resources that are + accessed via the Prospero Directory Service. The Prospero protocol is + described elsewhere [14]. + + A prospero URLs takes the form: + + prospero://:/;= + + where and are as described in Section 3.1. If : + is omitted, the port defaults to 1525. No username or password is + + + +Berners-Lee, Masinter & McCahill [Page 15] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + allowed. + + The is the host-specific object name in the Prospero + protocol, suitably encoded. This name is opaque and interpreted by + the Prospero server. The semicolon ";" is reserved and may not + appear without quoting in the . + + Prospero URLs are interpreted by contacting a Prospero directory + server on the specified host and port to determine appropriate access + methods for a resource, which might themselves be represented as + different URLs. External Prospero links are represented as URLs of + the underlying access method and are not represented as Prospero + URLs. + + Note that a slash "/" may appear in the without quoting and + no significance may be assumed by the application. Though slashes + may indicate hierarchical structure on the server, such structure is + not guaranteed. Note that many s begin with a slash, in + which case the host or port will be followed by a double slash: the + slash from the URL syntax, followed by the initial slash from the + . (E.g., designates a + of "/pros/name".) + + In addition, after the , optional fields and values + associated with a Prospero link may be specified as part of the URL. + When present, each field/value pair is separated from each other and + from the rest of the URL by a ";" (semicolon). The name of the field + and its value are separated by a "=" (equal sign). If present, these + fields serve to identify the target of the URL. For example, the + OBJECT-VERSION field can be specified to identify a specific version + of an object. + +4. REGISTRATION OF NEW SCHEMES + + A new scheme may be introduced by defining a mapping onto a + conforming URL syntax, using a new prefix. URLs for experimental + schemes may be used by mutual agreement between parties. Scheme names + starting with the characters "x-" are reserved for experimental + purposes. + + The Internet Assigned Numbers Authority (IANA) will maintain a + registry of URL schemes. Any submission of a new URL scheme must + include a definition of an algorithm for accessing of resources + within that scheme and the syntax for representing such a scheme. + + URL schemes must have demonstrable utility and operability. One way + to provide such a demonstration is via a gateway which provides + objects in the new scheme for clients using an existing protocol. If + + + +Berners-Lee, Masinter & McCahill [Page 16] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + the new scheme does not locate resources that are data objects, the + properties of names in the new space must be clearly defined. + + New schemes should try to follow the same syntactic conventions of + existing schemes, where appropriate. It is likewise recommended + that, where a protocol allows for retrieval by URL, that the client + software have provision for being configured to use specific gateway + locators for indirect access through new naming schemes. + + The following scheme have been proposed at various times, but this + document does not define their syntax or use at this time. It is + suggested that IANA reserve their scheme names for future definition: + + afs Andrew File System global file names. + mid Message identifiers for electronic mail. + cid Content identifiers for MIME body parts. + nfs Network File System (NFS) file names. + tn3270 Interactive 3270 emulation sessions. + mailserver Access to data available from mail servers. + z39.50 Access to ANSI Z39.50 services. + +5. BNF for specific URL schemes + + This is a BNF-like description of the Uniform Resource Locator + syntax, using the conventions of RFC822, except that "|" is used to + designate alternatives, and brackets [] are used around optional or + repeated elements. Briefly, literals are quoted with "", optional + elements are enclosed in [brackets], and elements may be preceded + with * to designate n or more repetitions of the following + element; n defaults to 0. + +; The generic form of a URL is: + +genericurl = scheme ":" schemepart + +; Specific predefined schemes are defined here; new schemes +; may be registered with IANA + +url = httpurl | ftpurl | newsurl | + nntpurl | telneturl | gopherurl | + waisurl | mailtourl | fileurl | + prosperourl | otherurl + +; new schemes follow the general syntax +otherurl = genericurl + +; the scheme is in lower case; interpreters should use case-ignore +scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] + + + +Berners-Lee, Masinter & McCahill [Page 17] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +schemepart = *xchar | ip-schemepart + + +; URL schemeparts for ip based protocols: + +ip-schemepart = "//" login [ "/" urlpath ] + +login = [ user [ ":" password ] "@" ] hostport +hostport = host [ ":" port ] +host = hostname | hostnumber +hostname = *[ domainlabel "." ] toplabel +domainlabel = alphadigit | alphadigit *[ alphadigit | "-" ] alphadigit +toplabel = alpha | alpha *[ alphadigit | "-" ] alphadigit +alphadigit = alpha | digit +hostnumber = digits "." digits "." digits "." digits +port = digits +user = *[ uchar | ";" | "?" | "&" | "=" ] +password = *[ uchar | ";" | "?" | "&" | "=" ] +urlpath = *xchar ; depends on protocol see section 3.1 + +; The predefined schemes: + +; FTP (see also RFC959) + +ftpurl = "ftp://" login [ "/" fpath [ ";type=" ftptype ]] +fpath = fsegment *[ "/" fsegment ] +fsegment = *[ uchar | "?" | ":" | "@" | "&" | "=" ] +ftptype = "A" | "I" | "D" | "a" | "i" | "d" + +; FILE + +fileurl = "file://" [ host | "localhost" ] "/" fpath + +; HTTP + +httpurl = "http://" hostport [ "/" hpath [ "?" search ]] +hpath = hsegment *[ "/" hsegment ] +hsegment = *[ uchar | ";" | ":" | "@" | "&" | "=" ] +search = *[ uchar | ";" | ":" | "@" | "&" | "=" ] + +; GOPHER (see also RFC1436) + +gopherurl = "gopher://" hostport [ / [ gtype [ selector + [ "%09" search [ "%09" gopher+_string ] ] ] ] ] +gtype = xchar +selector = *xchar +gopher+_string = *xchar + + + + +Berners-Lee, Masinter & McCahill [Page 18] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +; MAILTO (see also RFC822) + +mailtourl = "mailto:" encoded822addr +encoded822addr = 1*xchar ; further defined in RFC822 + +; NEWS (see also RFC1036) + +newsurl = "news:" grouppart +grouppart = "*" | group | article +group = alpha *[ alpha | digit | "-" | "." | "+" | "_" ] +article = 1*[ uchar | ";" | "/" | "?" | ":" | "&" | "=" ] "@" host + +; NNTP (see also RFC977) + +nntpurl = "nntp://" hostport "/" group [ "/" digits ] + +; TELNET + +telneturl = "telnet://" login [ "/" ] + +; WAIS (see also RFC1625) + +waisurl = waisdatabase | waisindex | waisdoc +waisdatabase = "wais://" hostport "/" database +waisindex = "wais://" hostport "/" database "?" search +waisdoc = "wais://" hostport "/" database "/" wtype "/" wpath +database = *uchar +wtype = *uchar +wpath = *uchar + +; PROSPERO + +prosperourl = "prospero://" hostport "/" ppath *[ fieldspec ] +ppath = psegment *[ "/" psegment ] +psegment = *[ uchar | "?" | ":" | "@" | "&" | "=" ] +fieldspec = ";" fieldname "=" fieldvalue +fieldname = *[ uchar | "?" | ":" | "@" | "&" ] +fieldvalue = *[ uchar | "?" | ":" | "@" | "&" ] + +; Miscellaneous definitions + +lowalpha = "a" | "b" | "c" | "d" | "e" | "f" | "g" | "h" | + "i" | "j" | "k" | "l" | "m" | "n" | "o" | "p" | + "q" | "r" | "s" | "t" | "u" | "v" | "w" | "x" | + "y" | "z" +hialpha = "A" | "B" | "C" | "D" | "E" | "F" | "G" | "H" | "I" | + "J" | "K" | "L" | "M" | "N" | "O" | "P" | "Q" | "R" | + "S" | "T" | "U" | "V" | "W" | "X" | "Y" | "Z" + + + +Berners-Lee, Masinter & McCahill [Page 19] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +alpha = lowalpha | hialpha +digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | + "8" | "9" +safe = "$" | "-" | "_" | "." | "+" +extra = "!" | "*" | "'" | "(" | ")" | "," +national = "{" | "}" | "|" | "\" | "^" | "~" | "[" | "]" | "`" +punctuation = "<" | ">" | "#" | "%" | <"> + + +reserved = ";" | "/" | "?" | ":" | "@" | "&" | "=" +hex = digit | "A" | "B" | "C" | "D" | "E" | "F" | + "a" | "b" | "c" | "d" | "e" | "f" +escape = "%" hex hex + +unreserved = alpha | digit | safe | extra +uchar = unreserved | escape +xchar = unreserved | reserved | escape +digits = 1*digit + +6. Security Considerations + + The URL scheme does not in itself pose a security threat. Users + should beware that there is no general guarantee that a URL which at + one time points to a given object continues to do so, and does not + even at some later time point to a different object due to the + movement of objects on servers. + + A URL-related security threat is that it is sometimes possible to + construct a URL such that an attempt to perform a harmless idempotent + operation such as the retrieval of the object will in fact cause a + possibly damaging remote operation to occur. The unsafe URL is + typically constructed by specifying a port number other than that + reserved for the network protocol in question. The client + unwittingly contacts a server which is in fact running a different + protocol. The content of the URL contains instructions which when + interpreted according to this other protocol cause an unexpected + operation. An example has been the use of gopher URLs to cause a rude + message to be sent via a SMTP server. Caution should be used when + using any URL which specifies a port number other than the default + for the protocol, especially when it is a number within the reserved + space. + + Care should be taken when URLs contain embedded encoded delimiters + for a given protocol (for example, CR and LF characters for telnet + protocols) that these are not unencoded before transmission. This + would violate the protocol but could be used to simulate an extra + operation or parameter, again causing an unexpected and possible + harmful remote operation to be performed. + + + +Berners-Lee, Masinter & McCahill [Page 20] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + The use of URLs containing passwords that should be secret is clearly + unwise. + +7. Acknowledgements + + This paper builds on the basic WWW design (RFC 1630) and much + discussion of these issues by many people on the network. The + discussion was particularly stimulated by articles by Clifford Lynch, + Brewster Kahle [10] and Wengyik Yeong [18]. Contributions from John + Curran, Clifford Neuman, Ed Vielmetti and later the IETF URL BOF and + URI working group were incorporated. + + Most recently, careful readings and comments by Dan Connolly, Ned + Freed, Roy Fielding, Guido van Rossum, Michael Dolan, Bert Bos, John + Kunze, Olle Jarnefors, Peter Svanberg and many others have helped + refine this RFC. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Berners-Lee, Masinter & McCahill [Page 21] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +APPENDIX: Recommendations for URLs in Context + + URIs, including URLs, are intended to be transmitted through + protocols which provide a context for their interpretation. + + In some cases, it will be necessary to distinguish URLs from other + possible data structures in a syntactic structure. In this case, is + recommended that URLs be preceeded with a prefix consisting of the + characters "URL:". For example, this prefix may be used to + distinguish URLs from other kinds of URIs. + + In addition, there are many occasions when URLs are included in other + kinds of text; examples include electronic mail, USENET news + messages, or printed on paper. In such cases, it is convenient to + have a separate syntactic wrapper that delimits the URL and separates + it from the rest of the text, and in particular from punctuation + marks that might be mistaken for part of the URL. For this purpose, + is recommended that angle brackets ("<" and ">"), along with the + prefix "URL:", be used to delimit the boundaries of the URL. This + wrapper does not form part of the URL and should not be used in + contexts in which delimiters are already specified. + + In the case where a fragment/anchor identifier is associated with a + URL (following a "#"), the identifier would be placed within the + brackets as well. + + In some cases, extra whitespace (spaces, linebreaks, tabs, etc.) may + need to be added to break long URLs across lines. The whitespace + should be ignored when extracting the URL. + + No whitespace should be introduced after a hyphen ("-") character. + Because some typesetters and printers may (erroneously) introduce a + hyphen at the end of line when breaking a line, the interpreter of a + URL containing a line break immediately after a hyphen should ignore + all unencoded whitespace around the line break, and should be aware + that the hyphen may or may not actually be part of the URL. + + Examples: + + Yes, Jim, I found it under but you can probably pick it up from . Note the warning in . + + + + + + + + +Berners-Lee, Masinter & McCahill [Page 22] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + +References + + [1] Anklesaria, F., McCahill, M., Lindner, P., Johnson, D., + Torrey, D., and B. Alberti, "The Internet Gopher Protocol + (a distributed document search and retrieval protocol)", + RFC 1436, University of Minnesota, March 1993. + + + [2] Anklesaria, F., Lindner, P., McCahill, M., Torrey, D., + Johnson, D., and B. Alberti, "Gopher+: Upward compatible + enhancements to the Internet Gopher protocol", + University of Minnesota, July 1993. + + + [3] Berners-Lee, T., "Universal Resource Identifiers in WWW: A + Unifying Syntax for the Expression of Names and Addresses of + Objects on the Network as used in the World-Wide Web", RFC + 1630, CERN, June 1994. + + + [4] Berners-Lee, T., "Hypertext Transfer Protocol (HTTP)", + CERN, November 1993. + + + [5] Braden, R., Editor, "Requirements for Internet Hosts -- + Application and Support", STD 3, RFC 1123, IETF, October 1989. + + + [6] Crocker, D. "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, UDEL, April 1982. + + + [7] Davis, F., Kahle, B., Morris, H., Salem, J., Shen, T., Wang, R., + Sui, J., and M. Grinbaum, "WAIS Interface Protocol Prototype + Functional Specification", (v1.5), Thinking Machines + Corporation, April 1990. + + + [8] Horton, M. and R. Adams, "Standard For Interchange of USENET + Messages", RFC 1036, AT&T Bell Laboratories, Center for Seismic + Studies, December 1987. + + + [9] Huitema, C., "Naming: Strategies and Techniques", Computer + Networks and ISDN Systems 23 (1991) 107-110. + + + + + +Berners-Lee, Masinter & McCahill [Page 23] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + [10] Kahle, B., "Document Identifiers, or International Standard + Book Numbers for the Electronic Age", 1991. + + + [11] Kantor, B. and P. Lapsley, "Network News Transfer Protocol: + A Proposed Standard for the Stream-Based Transmission of News", + RFC 977, UC San Diego & UC Berkeley, February 1986. + + + [12] Kunze, J., "Functional Requirements for Internet Resource + Locators", Work in Progress, December 1994. + + + [13] Mockapetris, P., "Domain Names - Concepts and Facilities", + STD 13, RFC 1034, USC/Information Sciences Institute, + November 1987. + + + [14] Neuman, B., and S. Augart, "The Prospero Protocol", + USC/Information Sciences Institute, June 1993. + + + [15] Postel, J. and J. Reynolds, "File Transfer Protocol (FTP)", + STD 9, RFC 959, USC/Information Sciences Institute, + October 1985. + + + [16] Sollins, K. and L. Masinter, "Functional Requirements for + Uniform Resource Names", RFC 1737, MIT/LCS, Xerox Corporation, + December 1994. + + + [17] St. Pierre, M, Fullton, J., Gamiel, K., Goldman, J., Kahle, B., + Kunze, J., Morris, H., and F. Schiettecatte, "WAIS over + Z39.50-1988", RFC 1625, WAIS, Inc., CNIDR, Thinking Machines + Corp., UC Berkeley, FS Consulting, June 1994. + + + [18] Yeong, W. "Towards Networked Information Retrieval", Technical + report 91-06-25-01, Performance Systems International, Inc. + , June 1991. + + [19] Yeong, W., "Representing Public Archives in the Directory", + Work in Progress, November 1991. + + + + + +Berners-Lee, Masinter & McCahill [Page 24] + +RFC 1738 Uniform Resource Locators (URL) December 1994 + + + [20] "Coded Character Set -- 7-bit American Standard Code for + Information Interchange", ANSI X3.4-1986. + +Editors' Addresses + +Tim Berners-Lee +World-Wide Web project +CERN, +1211 Geneva 23, +Switzerland + +Phone: +41 (22)767 3755 +Fax: +41 (22)767 7155 +EMail: timbl@info.cern.ch + + +Larry Masinter +Xerox PARC +3333 Coyote Hill Road +Palo Alto, CA 94034 + +Phone: (415) 812-4365 +Fax: (415) 812-4333 +EMail: masinter@parc.xerox.com + + +Mark McCahill +Computer and Information Services, +University of Minnesota +Room 152 Shepherd Labs +100 Union Street SE +Minneapolis, MN 55455 + +Phone: (612) 625 1300 +EMail: mpm@boombox.micro.umn.edu + + + + + + + + + + + + + + + + +Berners-Lee, Masinter & McCahill [Page 25] + diff --git a/lib/qCal/docs/rfc/rfc1766.txt b/lib/qCal/docs/rfc/rfc1766.txt new file mode 100644 index 0000000..901c50e --- /dev/null +++ b/lib/qCal/docs/rfc/rfc1766.txt @@ -0,0 +1,507 @@ + + + + + + +Network Working Group H. Alvestrand +Request for Comments: 1766 UNINETT +Category: Standards Track March 1995 + + + Tags for the Identification of Languages + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + This document describes a language tag for use in cases where it is + desired to indicate the language used in an information object. + + It also defines a Content-language: header, for use in the case where + one desires to indicate the language of something that has RFC-822- + like headers, like MIME body parts or Web documents, and a new + parameter to the Multipart/Alternative type, to aid in the usage of + the Content-Language: header. + +1. Introduction + + There are a number of languages spoken by human beings in this world. + + A great number of these people would prefer to have information + presented in a language that they understand. + + In some contexts, it is possible to have information in more than one + language, or it might be possible to provide tools for assisting in + the understanding of a language (like dictionaries). + + A prerequisite for any such function is a means of labelling the + information content with an identifier for the language in which is + is written. + + In the tradition of solving only problems that we think we + understand, this document specifies an identifier mechanism, and one + possible use for it. + + + + + + + +Alvestrand [Page 1] + +RFC 1766 Language Tag March 1995 + + +2. The Language tag + + The language tag is composed of 1 or more parts: A primary language + tag and a (possibly empty) series of subtags. + + The syntax of this tag in RFC-822 EBNF is: + + Language-Tag = Primary-tag *( "-" Subtag ) + Primary-tag = 1*8ALPHA + Subtag = 1*8ALPHA + + Whitespace is not allowed within the tag. + + All tags are to be treated as case insensitive; there exist + conventions for capitalization of some of them, but these should not + be taken to carry meaning. + + The namespace of language tags is administered by the IANA according + to the rules in section 5 of this document. + + The following registrations are predefined: + + In the primary language tag: + + - All 2-letter tags are interpreted according to ISO standard + 639, "Code for the representation of names of languages" [ISO + 639]. + + - The value "i" is reserved for IANA-defined registrations + + - The value "x" is reserved for private use. Subtags of "x" + will not be registered by the IANA. + + - Other values cannot be assigned except by updating this + standard. + + The reason for reserving all other tags is to be open towards new + revisions of ISO 639; the use of "i" and "x" is the minimum we can do + here to be able to extend the mechanism to meet our requirements. + + In the first subtag: + + - All 2-letter codes are interpreted as ISO 3166 alpha-2 + country codes denoting the area in which the language is + used. + + - Codes of 3 to 8 letters may be registered with the IANA by + anyone who feels a need for it, according to the rules in + + + +Alvestrand [Page 2] + +RFC 1766 Language Tag March 1995 + + + chapter 5 of this document. + + The information in the subtag may for instance be: + + - Country identification, such as en-US (this usage is + described in ISO 639) + + - Dialect or variant information, such as no-nynorsk or en- + cockney + + - Languages not listed in ISO 639 that are not variants of + any listed language, which can be registered with the i- + prefix, such as i-cherokee + + - Script variations, such as az-arabic and az-cyrillic + + In the second and subsequent subtag, any value can be registered. + + NOTE: The ISO 639/ISO 3166 convention is that language names are + written in lower case, while country codes are written in upper case. + This convention is recommended, but not enforced; the tags are case + insensitive. + + NOTE: ISO 639 defines a registration authority for additions to and + changes in the list of languages in ISO 639. This authority is: + + International Information Centre for Terminology (Infoterm) + P.O. Box 130 + A-1021 Wien + Austria + Phone: +43 1 26 75 35 Ext. 312 + Fax: +43 1 216 32 72 + + The following codes have been added in 1989 (nothing later): ug + (Uigur), iu (Inuktitut, also called Eskimo), za (Zhuang), he (Hebrew, + replacing iw), yi (Yiddish, replacing ji), and id (Indonesian, + replacing in). + + NOTE: The registration agency for ISO 3166 (country codes) is: + + ISO 3166 Maintenance Agency Secretariat + c/o DIN Deutches Institut fuer Normung + Burggrafenstrasse 6 + Postfach 1107 + D-10787 Berlin + Germany + Phone: +49 30 26 01 320 + Fax: +49 30 26 01 231 + + + +Alvestrand [Page 3] + +RFC 1766 Language Tag March 1995 + + + The country codes AA, QM-QZ, XA-XZ and ZZ are reserved by ISO 3166 as + user-assigned codes. + +2.1. Meaning of the language tag + + The language tag always defines a language as spoken (or written) by + human beings for communication of information to other human beings. + Computer languages are explicitly excluded. + + There is no guaranteed relationship between languages whose tags + start out with the same series of subtags; especially, they are NOT + guraranteed to be mutually comprehensible, although this will + sometimes be the case. + + Applications should always treat language tags as a single token; the + division into main tag and subtags is an administrative mechanism, + not a navigation aid. + + The relationship between the tag and the information it relates to is + defined by the standard describing the context in which it appears. + So, this section can only give possible examples of its usage. + + - For a single information object, it should be taken as the + set of languages that is required for a complete + comprehension of the complete object. Example: Simple text. + + - For an aggregation of information objects, it should be taken + as the set of languages used inside components of that + aggregation. Examples: Document stores and libraries. + + - For information objects whose purpose in life is providing + alternatives, it should be regarded as a hint that the + material inside is provided in several languages, and that + one has to inspect each of the alternatives in order to find + its language or languages. In this case, multiple languages + need not mean that one needs to be multilingual to get + complete understanding of the document. Example: MIME + multipart/alternative. + + - It would be possible to define (for instance) an SGML DTD + that defines a tag for indicating that following or + contained text is written in this language, such that one + could write "C'est la vie"; the Norwegian- + speaking user could then access a French-Norwegian dictionary + to find out what the quote meant. + + + + + + +Alvestrand [Page 4] + +RFC 1766 Language Tag March 1995 + + +3. The Content-language header + + The Language header is intended for use in the case where one desires + to indicate the language(s) of something that has RFC-822-like + headers, like MIME body parts or Web documents. + + The RFC-822 EBNF of the Language header is: + + Language-Header = "Content-Language" ":" 1#Language-tag + + Note that the Language-Header is allowed to list several languages in + a comma-separated list. + + Whitespace is allowed, which means also that one can place + parenthesized comments anywhere in the language sequence. + +3.1. Examples of Content-language values + + NOTE: NONE of the subtags shown in this document have actually been + assigned; they are used for illustration purposes only. + + Norwegian official document, with parallel text in both official + versions of Norwegian. (Both versions are readable by all + Norwegians). + + Content-Type: multipart/alternative; + differences=content-language + Content-Language: no-nynorsk, no-bokmaal + + Voice recording from the London docks + + Content-type: audio/basic + Content-Language: en-cockney + + Document in Sami, which does not have an ISO 639 code, and is spoken + in several countries, but with about half the speakers in Norway, + with six different, mutually incomprehensible dialects: + + Content-type: text/plain; charset=iso-8859-10 + Content-Language: i-sami-no (North Sami) + + An English-French dictionary + + Content-type: application/dictionary + Content-Language: en, fr (This is a dictionary) + + An official EC document (in a few of its official languages) + + + + +Alvestrand [Page 5] + +RFC 1766 Language Tag March 1995 + + + Content-type: multipart/alternative + Content-Language: en, fr, de, da, el, it + + An excerpt from Star Trek + + Content-type: video/mpeg + Content-Language: x-klingon + +4. Use of Content-Language with Multipart/Alternative + + When using the Multipart/Alternative body part of MIME, it is + possible to have the body parts giving the same information content + in different languages. In this case, one should put a Content- + Language header on each of the body parts, and a summary Content- + Language header onto the Multipart/Alternative itself. + +4.1. The differences parameter to multipart/alternative + + As defined in RFC 1541, Multipart/Alternative only has one parameter: + boundary. + + The common usage of Multipart/Alternative is to have more than one + format of the same message (f.ex. PostScript and ASCII). + + The use of language tags to differentiate between different + alternatives will certainly not lead all MIME UAs to present the most + sensible body part as default. + + Therefore, a new parameter is defined, to allow the configuration of + MIME readers to handle language differences in a sensible manner. + + Name: Differences + Value: One or more of + Content-Type + Content-Language + + Further values can be registered with IANA; it must be the name of a + header for which a definition exists in a published RFC. If not + present, Differences=Content-Type is assumed. + + The intent is that the MIME reader can look at these headers of the + message component to do an intelligent choice of what to present to + the user, based on knowledge about the user preferences and + capabilities. + + (The intent of having registration with IANA of the fields used in + this context is to maintain a list of usages that a mail UA may + expect to see, not to reject usages.) + + + +Alvestrand [Page 6] + +RFC 1766 Language Tag March 1995 + + + (NOTE: The MIME specification [RFC 1521], section 7.2, states that + headers not beginning with "Content-" are generally to be ignored in + body parts. People defining a header for use with "differences=" + should take note of this.) + + The mechanism for deciding which body part to present is outside the + scope of this document. + + MIME EXAMPLE: + + Content-Type: multipart/alternative; differences=Content-Language; + boundary="limit" + Content-Language: en, fr, de + + --limit + Content-Language: fr + + Le renard brun et agile saute par dessus le chien paresseux + --limit + Content-Language: de + Content-Type: text/plain; charset=iso-8859-1 + Content-Transfer-encoding: quoted-printable + + Der schnelle braune Fuchs h=FCpft =FCber den faulen Hund + --limit + Content-Language: en + + The quick brown fox jumps over the lazy dog + --limit-- + + When composing a message, the choice of sequence may be somewhat + arbitrary. However, non-MIME mail readers will show the first body + part first, meaning that this should most likely be the language + understood by most of the recipients. + +5. IANA registration procedure for language tags + + Any language tag must start with an existing tag, and extend it. + + This registration form should be used by anyone who wants to use a + language tag not defined by ISO or IANA. + + + + + + + + + + +Alvestrand [Page 7] + +RFC 1766 Language Tag March 1995 + + +---------------------------------------------------------------------- +LANGUAGE TAG REGISTRATION FORM + +Name of requester : +E-mail address of requester: +Tag to be registered : + +English name of language : + +Native name of language (transcribed into ASCII): + +Reference to published description of the language (book or article): +---------------------------------------------------------------------- + + The language form must be sent to for a 2- + week review period before submitting it to IANA. (This is an open + list. Requests to be added should be sent to .) + + When the two week period has passed, the language tag reviewer, who + is appointed by the IETF Applications Area Director, either forwards + the request to IANA@ISI.EDU, or rejects it because of significant + objections raised on the list. + + Decisions made by the reviewer may be appealed to the IESG. + + All registered forms are available online in the directory + ftp://ftp.isi.edu/in-notes/iana/assignments/languages/ + +6. Security Considerations + + Security issues are not discussed in this memo. + +7. Character set considerations + + Codes may always be expressed using the US-ASCII character repertoire + (a-z), which is present in most character sets. + + The issue of deciding upon the rendering of a character set based on + the language tag is not addressed in this memo; however, it is + thought impossible to make such a decision correctly for all cases + unless means of switching language in the middle of a text are + defined (for example, a rendering engine that decides font based on + Japanese or Chinese language will fail to work when a mixed + Japanese-Chinese text is encountered) + + + + + + +Alvestrand [Page 8] + +RFC 1766 Language Tag March 1995 + + +8. Acknowledgements + + This document has benefited from innumberable rounds of review and + comments in various fora of the IETF and the Internet working groups. + As so, any list of contributors is bound to be incomplete; please + regard the following as only a selection from the group of people who + have contributed to make this document what it is today. + + In alphabetical order: + + Tim Berners-Lee, Nathaniel Borenstein, Jim Conklin, Dave Crocker, + Ned Freed, Tim Goodwin, Olle Jarnefors, John Klensin, Keith Moore, + Masataka Ohta, Keld Jorn Simonsen, Rhys Weatherley, and many, many + others. + +9. Author's Address + + Harald Tveit Alvestrand + UNINETT + Pb. 6883 Elgeseter + N-7002 TRONDHEIM + NORWAY + + EMail: Harald.T.Alvestrand@uninett.no + Phone: +47 73 59 70 94 + +10. References + + [ISO 639] + ISO 639:1988 (E/F) - Code for the representation of names of + languages - The International Organization for + Standardization, 1st edition, 1988 17 pages Prepared by + ISO/TC 37 - Terminology (principles and coordination). + + [ISO 3166] + ISO 3166:1988 (E/F) - Codes for the representation of names + of countries - The International Organization for + Standardization, 3rd edition, 1988-08-15. + + [RFC 1521] + Borenstein, N., and N. Freed, "MIME Part One: Mechanisms for + Specifying and Describing the Format of Internet Message + Bodies", RFC 1521, Bellcore, Innosoft, September 1993. + + [RFC 1327] + Kille, S., "Mapping between X.400(1988) / ISO 10021 and RFC + 822", RFC 1327, University College London, May 1992. + + + + +Alvestrand [Page 9] + diff --git a/lib/qCal/docs/rfc/rfc2045.txt b/lib/qCal/docs/rfc/rfc2045.txt new file mode 100644 index 0000000..9f286b1 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2045.txt @@ -0,0 +1,1739 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2045 Innosoft +Obsoletes: 1521, 1522, 1590 N. Borenstein +Category: Standards Track First Virtual + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part One: + Format of Internet Message Bodies + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + This initial document specifies the various headers used to describe + the structure of MIME messages. The second document, RFC 2046, + defines the general structure of the MIME media typing system and + defines an initial set of media types. The third document, RFC 2047, + describes extensions to RFC 822 to allow non-US-ASCII text data in + + + +Freed & Borenstein Standards Track [Page 1] + +RFC 2045 Internet Message Bodies November 1996 + + + Internet mail header fields. The fourth document, RFC 2048, specifies + various IANA registration procedures for MIME-related facilities. The + fifth and final document, RFC 2049, describes MIME conformance + criteria as well as providing some illustrative examples of MIME + message formats, acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521, 1522, and 1590, which + themselves were revisions of RFCs 1341 and 1342. An appendix in RFC + 2049 describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction ......................................... 3 + 2. Definitions, Conventions, and Generic BNF Grammar .... 5 + 2.1 CRLF ................................................ 5 + 2.2 Character Set ....................................... 6 + 2.3 Message ............................................. 6 + 2.4 Entity .............................................. 6 + 2.5 Body Part ........................................... 7 + 2.6 Body ................................................ 7 + 2.7 7bit Data ........................................... 7 + 2.8 8bit Data ........................................... 7 + 2.9 Binary Data ......................................... 7 + 2.10 Lines .............................................. 7 + 3. MIME Header Fields ................................... 8 + 4. MIME-Version Header Field ............................ 8 + 5. Content-Type Header Field ............................ 10 + 5.1 Syntax of the Content-Type Header Field ............. 12 + 5.2 Content-Type Defaults ............................... 14 + 6. Content-Transfer-Encoding Header Field ............... 14 + 6.1 Content-Transfer-Encoding Syntax .................... 14 + 6.2 Content-Transfer-Encodings Semantics ................ 15 + 6.3 New Content-Transfer-Encodings ...................... 16 + 6.4 Interpretation and Use .............................. 16 + 6.5 Translating Encodings ............................... 18 + 6.6 Canonical Encoding Model ............................ 19 + 6.7 Quoted-Printable Content-Transfer-Encoding .......... 19 + 6.8 Base64 Content-Transfer-Encoding .................... 24 + 7. Content-ID Header Field .............................. 26 + 8. Content-Description Header Field ..................... 27 + 9. Additional MIME Header Fields ........................ 27 + 10. Summary ............................................. 27 + 11. Security Considerations ............................. 27 + 12. Authors' Addresses .................................. 28 + A. Collected Grammar .................................... 29 + + + + + + +Freed & Borenstein Standards Track [Page 2] + +RFC 2045 Internet Message Bodies November 1996 + + +1. Introduction + + Since its publication in 1982, RFC 822 has defined the standard + format of textual mail messages on the Internet. Its success has + been such that the RFC 822 format has been adopted, wholly or + partially, well beyond the confines of the Internet and the Internet + SMTP transport defined by RFC 821. As the format has seen wider use, + a number of limitations have proven increasingly restrictive for the + user community. + + RFC 822 was intended to specify a format for text messages. As such, + non-text messages, such as multimedia messages that might include + audio or images, are simply not mentioned. Even in the case of text, + however, RFC 822 is inadequate for the needs of mail users whose + languages require the use of character sets richer than US-ASCII. + Since RFC 822 does not specify mechanisms for mail containing audio, + video, Asian language text, or even text in most European languages, + additional specifications are needed. + + One of the notable limitations of RFC 821/822 based mail systems is + the fact that they limit the contents of electronic mail messages to + relatively short lines (e.g. 1000 characters or less [RFC-821]) of + 7bit US-ASCII. This forces users to convert any non-textual data + that they may wish to send into seven-bit bytes representable as + printable US-ASCII characters before invoking a local mail UA (User + Agent, a program with which human users send and receive mail). + Examples of such encodings currently used in the Internet include + pure hexadecimal, uuencode, the 3-in-4 base 64 scheme specified in + RFC 1421, the Andrew Toolkit Representation [ATK], and many others. + + The limitations of RFC 822 mail become even more apparent as gateways + are designed to allow for the exchange of mail messages between RFC + 822 hosts and X.400 hosts. X.400 [X400] specifies mechanisms for the + inclusion of non-textual material within electronic mail messages. + The current standards for the mapping of X.400 messages to RFC 822 + messages specify either that X.400 non-textual material must be + converted to (not encoded in) IA5Text format, or that they must be + discarded, notifying the RFC 822 user that discarding has occurred. + This is clearly undesirable, as information that a user may wish to + receive is lost. Even though a user agent may not have the + capability of dealing with the non-textual material, the user might + have some mechanism external to the UA that can extract useful + information from the material. Moreover, it does not allow for the + fact that the message may eventually be gatewayed back into an X.400 + message handling system (i.e., the X.400 message is "tunneled" + through Internet mail), where the non-textual information would + definitely become useful again. + + + + +Freed & Borenstein Standards Track [Page 3] + +RFC 2045 Internet Message Bodies November 1996 + + + This document describes several mechanisms that combine to solve most + of these problems without introducing any serious incompatibilities + with the existing world of RFC 822 mail. In particular, it + describes: + + (1) A MIME-Version header field, which uses a version + number to declare a message to be conformant with MIME + and allows mail processing agents to distinguish + between such messages and those generated by older or + non-conformant software, which are presumed to lack + such a field. + + (2) A Content-Type header field, generalized from RFC 1049, + which can be used to specify the media type and subtype + of data in the body of a message and to fully specify + the native representation (canonical form) of such + data. + + (3) A Content-Transfer-Encoding header field, which can be + used to specify both the encoding transformation that + was applied to the body and the domain of the result. + Encoding transformations other than the identity + transformation are usually applied to data in order to + allow it to pass through mail transport mechanisms + which may have data or character set limitations. + + (4) Two additional header fields that can be used to + further describe the data in a body, the Content-ID and + Content-Description header fields. + + All of the header fields defined in this document are subject to the + general syntactic rules for header fields specified in RFC 822. In + particular, all of these header fields except for Content-Disposition + can include RFC 822 comments, which have no semantic content and + should be ignored during MIME processing. + + Finally, to specify and promote interoperability, RFC 2049 provides a + basic applicability statement for a subset of the above mechanisms + that defines a minimal level of "conformance" with this document. + + HISTORICAL NOTE: Several of the mechanisms described in this set of + documents may seem somewhat strange or even baroque at first reading. + It is important to note that compatibility with existing standards + AND robustness across existing practice were two of the highest + priorities of the working group that developed this set of documents. + In particular, compatibility was always favored over elegance. + + + + + +Freed & Borenstein Standards Track [Page 4] + +RFC 2045 Internet Message Bodies November 1996 + + + Please refer to the current edition of the "Internet Official + Protocol Standards" for the standardization state and status of this + protocol. RFC 822 and STD 3, RFC 1123 also provide essential + background for MIME since no conforming implementation of MIME can + violate them. In addition, several other informational RFC documents + will be of interest to the MIME implementor, in particular RFC 1344, + RFC 1345, and RFC 1524. + +2. Definitions, Conventions, and Generic BNF Grammar + + Although the mechanisms specified in this set of documents are all + described in prose, most are also described formally in the augmented + BNF notation of RFC 822. Implementors will need to be familiar with + this notation in order to understand this set of documents, and are + referred to RFC 822 for a complete explanation of the augmented BNF + notation. + + Some of the augmented BNF in this set of documents makes named + references to syntax rules defined in RFC 822. A complete formal + grammar, then, is obtained by combining the collected grammar + appendices in each document in this set with the BNF of RFC 822 plus + the modifications to RFC 822 defined in RFC 1123 (which specifically + changes the syntax for `return', `date' and `mailbox'). + + All numeric and octet values are given in decimal notation in this + set of documents. All media type values, subtype values, and + parameter names as defined are case-insensitive. However, parameter + values are case-sensitive unless otherwise specified for the specific + parameter. + + FORMATTING NOTE: Notes, such at this one, provide additional + nonessential information which may be skipped by the reader without + missing anything essential. The primary purpose of these non- + essential notes is to convey information about the rationale of this + set of documents, or to place these documents in the proper + historical or evolutionary context. Such information may in + particular be skipped by those who are focused entirely on building a + conformant implementation, but may be of use to those who wish to + understand why certain design choices were made. + +2.1. CRLF + + The term CRLF, in this set of documents, refers to the sequence of + octets corresponding to the two US-ASCII characters CR (decimal value + 13) and LF (decimal value 10) which, taken together, in this order, + denote a line break in RFC 822 mail. + + + + + +Freed & Borenstein Standards Track [Page 5] + +RFC 2045 Internet Message Bodies November 1996 + + +2.2. Character Set + + The term "character set" is used in MIME to refer to a method of + converting a sequence of octets into a sequence of characters. Note + that unconditional and unambiguous conversion in the other direction + is not required, in that not all characters may be representable by a + given character set and a character set may provide more than one + sequence of octets to represent a particular sequence of characters. + + This definition is intended to allow various kinds of character + encodings, from simple single-table mappings such as US-ASCII to + complex table switching methods such as those that use ISO 2022's + techniques, to be used as character sets. However, the definition + associated with a MIME character set name must fully specify the + mapping to be performed. In particular, use of external profiling + information to determine the exact mapping is not permitted. + + NOTE: The term "character set" was originally to describe such + straightforward schemes as US-ASCII and ISO-8859-1 which have a + simple one-to-one mapping from single octets to single characters. + Multi-octet coded character sets and switching techniques make the + situation more complex. For example, some communities use the term + "character encoding" for what MIME calls a "character set", while + using the phrase "coded character set" to denote an abstract mapping + from integers (not octets) to characters. + +2.3. Message + + The term "message", when not further qualified, means either a + (complete or "top-level") RFC 822 message being transferred on a + network, or a message encapsulated in a body of type "message/rfc822" + or "message/partial". + +2.4. Entity + + The term "entity", refers specifically to the MIME-defined header + fields and contents of either a message or one of the parts in the + body of a multipart entity. The specification of such entities is + the essence of MIME. Since the contents of an entity are often + called the "body", it makes sense to speak about the body of an + entity. Any sort of field may be present in the header of an entity, + but only those fields whose names begin with "content-" actually have + any MIME-related meaning. Note that this does NOT imply thay they + have no meaning at all -- an entity that is also a message has non- + MIME header fields whose meanings are defined by RFC 822. + + + + + + +Freed & Borenstein Standards Track [Page 6] + +RFC 2045 Internet Message Bodies November 1996 + + +2.5. Body Part + + The term "body part" refers to an entity inside of a multipart + entity. + +2.6. Body + + The term "body", when not further qualified, means the body of an + entity, that is, the body of either a message or of a body part. + + NOTE: The previous four definitions are clearly circular. This is + unavoidable, since the overall structure of a MIME message is indeed + recursive. + +2.7. 7bit Data + + "7bit data" refers to data that is all represented as relatively + short lines with 998 octets or less between CRLF line separation + sequences [RFC-821]. No octets with decimal values greater than 127 + are allowed and neither are NULs (octets with decimal value 0). CR + (decimal value 13) and LF (decimal value 10) octets only occur as + part of CRLF line separation sequences. + +2.8. 8bit Data + + "8bit data" refers to data that is all represented as relatively + short lines with 998 octets or less between CRLF line separation + sequences [RFC-821]), but octets with decimal values greater than 127 + may be used. As with "7bit data" CR and LF octets only occur as part + of CRLF line separation sequences and no NULs are allowed. + +2.9. Binary Data + + "Binary data" refers to data where any sequence of octets whatsoever + is allowed. + +2.10. Lines + + "Lines" are defined as sequences of octets separated by a CRLF + sequences. This is consistent with both RFC 821 and RFC 822. + "Lines" only refers to a unit of data in a message, which may or may + not correspond to something that is actually displayed by a user + agent. + + + + + + + + +Freed & Borenstein Standards Track [Page 7] + +RFC 2045 Internet Message Bodies November 1996 + + +3. MIME Header Fields + + MIME defines a number of new RFC 822 header fields that are used to + describe the content of a MIME entity. These header fields occur in + at least two contexts: + + (1) As part of a regular RFC 822 message header. + + (2) In a MIME body part header within a multipart + construct. + + The formal definition of these header fields is as follows: + + entity-headers := [ content CRLF ] + [ encoding CRLF ] + [ id CRLF ] + [ description CRLF ] + *( MIME-extension-field CRLF ) + + MIME-message-headers := entity-headers + fields + version CRLF + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + MIME-part-headers := entity-headers + [ fields ] + ; Any field not beginning with + ; "content-" can have no defined + ; meaning and may be ignored. + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + The syntax of the various specific MIME header fields will be + described in the following sections. + +4. MIME-Version Header Field + + Since RFC 822 was published in 1982, there has really been only one + format standard for Internet messages, and there has been little + perceived need to declare the format standard in use. This document + is an independent specification that complements RFC 822. Although + the extensions in this document have been defined in such a way as to + be compatible with RFC 822, there are still circumstances in which it + might be desirable for a mail-processing agent to know whether a + message was composed with the new standard in mind. + + + +Freed & Borenstein Standards Track [Page 8] + +RFC 2045 Internet Message Bodies November 1996 + + + Therefore, this document defines a new header field, "MIME-Version", + which is to be used to declare the version of the Internet message + body format standard in use. + + Messages composed in accordance with this document MUST include such + a header field, with the following verbatim text: + + MIME-Version: 1.0 + + The presence of this header field is an assertion that the message + has been composed in compliance with this document. + + Since it is possible that a future document might extend the message + format standard again, a formal BNF is given for the content of the + MIME-Version field: + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + Thus, future format specifiers, which might replace or extend "1.0", + are constrained to be two integer fields, separated by a period. If + a message is received with a MIME-version value other than "1.0", it + cannot be assumed to conform with this document. + + Note that the MIME-Version header field is required at the top level + of a message. It is not required for each body part of a multipart + entity. It is required for the embedded headers of a body of type + "message/rfc822" or "message/partial" if and only if the embedded + message is itself claimed to be MIME-conformant. + + It is not possible to fully specify how a mail reader that conforms + with MIME as defined in this document should treat a message that + might arrive in the future with some value of MIME-Version other than + "1.0". + + It is also worth noting that version control for specific media types + is not accomplished using the MIME-Version mechanism. In particular, + some formats (such as application/postscript) have version numbering + conventions that are internal to the media format. Where such + conventions exist, MIME does nothing to supersede them. Where no + such conventions exist, a MIME media type might use a "version" + parameter in the content-type field if necessary. + + + + + + + + + + +Freed & Borenstein Standards Track [Page 9] + +RFC 2045 Internet Message Bodies November 1996 + + + NOTE TO IMPLEMENTORS: When checking MIME-Version values any RFC 822 + comment strings that are present must be ignored. In particular, the + following four MIME-Version fields are equivalent: + + MIME-Version: 1.0 + + MIME-Version: 1.0 (produced by MetaSend Vx.x) + + MIME-Version: (produced by MetaSend Vx.x) 1.0 + + MIME-Version: 1.(produced by MetaSend Vx.x)0 + + In the absence of a MIME-Version field, a receiving mail user agent + (whether conforming to MIME requirements or not) may optionally + choose to interpret the body of the message according to local + conventions. Many such conventions are currently in use and it + should be noted that in practice non-MIME messages can contain just + about anything. + + It is impossible to be certain that a non-MIME mail message is + actually plain text in the US-ASCII character set since it might well + be a message that, using some set of nonstandard local conventions + that predate MIME, includes text in another character set or non- + textual data presented in a manner that cannot be automatically + recognized (e.g., a uuencoded compressed UNIX tar file). + +5. Content-Type Header Field + + The purpose of the Content-Type field is to describe the data + contained in the body fully enough that the receiving user agent can + pick an appropriate agent or mechanism to present the data to the + user, or otherwise deal with the data in an appropriate manner. The + value in this field is called a media type. + + HISTORICAL NOTE: The Content-Type header field was first defined in + RFC 1049. RFC 1049 used a simpler and less powerful syntax, but one + that is largely compatible with the mechanism given here. + + The Content-Type header field specifies the nature of the data in the + body of an entity by giving media type and subtype identifiers, and + by providing auxiliary information that may be required for certain + media types. After the media type and subtype names, the remainder + of the header field is simply a set of parameters, specified in an + attribute=value notation. The ordering of parameters is not + significant. + + + + + + +Freed & Borenstein Standards Track [Page 10] + +RFC 2045 Internet Message Bodies November 1996 + + + In general, the top-level media type is used to declare the general + type of data, while the subtype specifies a specific format for that + type of data. Thus, a media type of "image/xyz" is enough to tell a + user agent that the data is an image, even if the user agent has no + knowledge of the specific image format "xyz". Such information can + be used, for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of text, but not for + unrecognized subtypes of image or audio. For this reason, registered + subtypes of text, image, audio, and video should not contain embedded + information that is really of a different type. Such compound + formats should be represented using the "multipart" or "application" + types. + + Parameters are modifiers of the media subtype, and as such do not + fundamentally affect the nature of the content. The set of + meaningful parameters depends on the media type and subtype. Most + parameters are associated with a single specific subtype. However, a + given top-level media type may define parameters which are applicable + to any subtype of that type. Parameters may be required by their + defining content type or subtype or they may be optional. MIME + implementations must ignore any parameters whose names they do not + recognize. + + For example, the "charset" parameter is applicable to any subtype of + "text", while the "boundary" parameter is required for any subtype of + the "multipart" media type. + + There are NO globally-meaningful parameters that apply to all media + types. Truly global mechanisms are best addressed, in the MIME + model, by the definition of additional Content-* header fields. + + An initial set of seven top-level media types is defined in RFC 2046. + Five of these are discrete types whose content is essentially opaque + as far as MIME processing is concerned. The remaining two are + composite types whose contents require additional handling by MIME + processors. + + This set of top-level media types is intended to be substantially + complete. It is expected that additions to the larger set of + supported types can generally be accomplished by the creation of new + subtypes of these initial types. In the future, more top-level types + may be defined only by a standards-track extension to this standard. + If another top-level type is to be used for any reason, it must be + given a name starting with "X-" to indicate its non-standard status + and to avoid a potential conflict with a future official name. + + + + + +Freed & Borenstein Standards Track [Page 11] + +RFC 2045 Internet Message Bodies November 1996 + + +5.1. Syntax of the Content-Type Header Field + + In the Augmented BNF notation of RFC 822, a Content-Type header field + value is defined as follows: + + content := "Content-Type" ":" type "/" subtype + *(";" parameter) + ; Matching of media type and subtype + ; is ALWAYS case-insensitive. + + type := discrete-type / composite-type + + discrete-type := "text" / "image" / "audio" / "video" / + "application" / extension-token + + composite-type := "message" / "multipart" / extension-token + + extension-token := ietf-token / x-token + + ietf-token := + + x-token := + + subtype := extension-token / iana-token + + iana-token := + + parameter := attribute "=" value + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + value := token / quoted-string + + token := 1* + + tspecials := "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / <"> + "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + + +Freed & Borenstein Standards Track [Page 12] + +RFC 2045 Internet Message Bodies November 1996 + + + Note that the definition of "tspecials" is the same as the RFC 822 + definition of "specials" with the addition of the three characters + "/", "?", and "=", and the removal of ".". + + Note also that a subtype specification is MANDATORY -- it may not be + omitted from a Content-Type header field. As such, there are no + default subtypes. + + The type, subtype, and parameter names are not case sensitive. For + example, TEXT, Text, and TeXt are all equivalent top-level media + types. Parameter values are normally case sensitive, but sometimes + are interpreted in a case-insensitive fashion, depending on the + intended use. (For example, multipart boundaries are case-sensitive, + but the "access-type" parameter for message/External-body is not + case-sensitive.) + + Note that the value of a quoted string parameter does not include the + quotes. That is, the quotation marks in a quoted-string are not a + part of the value of the parameter, but are merely used to delimit + that parameter value. In addition, comments are allowed in + accordance with RFC 822 rules for structured header fields. Thus the + following two forms + + Content-type: text/plain; charset=us-ascii (Plain text) + + Content-type: text/plain; charset="us-ascii" + + are completely equivalent. + + Beyond this syntax, the only syntactic constraint on the definition + of subtype names is the desire that their uses must not conflict. + That is, it would be undesirable to have two different communities + using "Content-Type: application/foobar" to mean two different + things. The process of defining new media subtypes, then, is not + intended to be a mechanism for imposing restrictions, but simply a + mechanism for publicizing their definition and usage. There are, + therefore, two acceptable mechanisms for defining new media subtypes: + + (1) Private values (starting with "X-") may be defined + bilaterally between two cooperating agents without + outside registration or standardization. Such values + cannot be registered or standardized. + + (2) New standard values should be registered with IANA as + described in RFC 2048. + + The second document in this set, RFC 2046, defines the initial set of + media types for MIME. + + + +Freed & Borenstein Standards Track [Page 13] + +RFC 2045 Internet Message Bodies November 1996 + + +5.2. Content-Type Defaults + + Default RFC 822 messages without a MIME Content-Type header are taken + by this protocol to be plain text in the US-ASCII character set, + which can be explicitly specified as: + + Content-type: text/plain; charset=us-ascii + + This default is assumed if no Content-Type header field is specified. + It is also recommend that this default be assumed when a + syntactically invalid Content-Type header field is encountered. In + the presence of a MIME-Version header field and the absence of any + Content-Type header field, a receiving User Agent can also assume + that plain US-ASCII text was the sender's intent. Plain US-ASCII + text may still be assumed in the absence of a MIME-Version or the + presence of an syntactically invalid Content-Type header field, but + the sender's intent might have been otherwise. + +6. Content-Transfer-Encoding Header Field + + Many media types which could be usefully transported via email are + represented, in their "natural" format, as 8bit character or binary + data. Such data cannot be transmitted over some transfer protocols. + For example, RFC 821 (SMTP) restricts mail messages to 7bit US-ASCII + data with lines no longer than 1000 characters including any trailing + CRLF line separator. + + It is necessary, therefore, to define a standard mechanism for + encoding such data into a 7bit short line format. Proper labelling + of unencoded material in less restrictive formats for direct use over + less restrictive transports is also desireable. This document + specifies that such encodings will be indicated by a new "Content- + Transfer-Encoding" header field. This field has not been defined by + any previous standard. + +6.1. Content-Transfer-Encoding Syntax + + The Content-Transfer-Encoding field's value is a single token + specifying the type of encoding, as enumerated below. Formally: + + encoding := "Content-Transfer-Encoding" ":" mechanism + + mechanism := "7bit" / "8bit" / "binary" / + "quoted-printable" / "base64" / + ietf-token / x-token + + These values are not case sensitive -- Base64 and BASE64 and bAsE64 + are all equivalent. An encoding type of 7BIT requires that the body + + + +Freed & Borenstein Standards Track [Page 14] + +RFC 2045 Internet Message Bodies November 1996 + + + is already in a 7bit mail-ready representation. This is the default + value -- that is, "Content-Transfer-Encoding: 7BIT" is assumed if the + Content-Transfer-Encoding header field is not present. + +6.2. Content-Transfer-Encodings Semantics + + This single Content-Transfer-Encoding token actually provides two + pieces of information. It specifies what sort of encoding + transformation the body was subjected to and hence what decoding + operation must be used to restore it to its original form, and it + specifies what the domain of the result is. + + The transformation part of any Content-Transfer-Encodings specifies, + either explicitly or implicitly, a single, well-defined decoding + algorithm, which for any sequence of encoded octets either transforms + it to the original sequence of octets which was encoded, or shows + that it is illegal as an encoded sequence. Content-Transfer- + Encodings transformations never depend on any additional external + profile information for proper operation. Note that while decoders + must produce a single, well-defined output for a valid encoding no + such restrictions exist for encoders: Encoding a given sequence of + octets to different, equivalent encoded sequences is perfectly legal. + + Three transformations are currently defined: identity, the "quoted- + printable" encoding, and the "base64" encoding. The domains are + "binary", "8bit" and "7bit". + + The Content-Transfer-Encoding values "7bit", "8bit", and "binary" all + mean that the identity (i.e. NO) encoding transformation has been + performed. As such, they serve simply as indicators of the domain of + the body data, and provide useful information about the sort of + encoding that might be needed for transmission in a given transport + system. The terms "7bit data", "8bit data", and "binary data" are + all defined in Section 2. + + The quoted-printable and base64 encodings transform their input from + an arbitrary domain into material in the "7bit" range, thus making it + safe to carry over restricted transports. The specific definition of + the transformations are given below. + + The proper Content-Transfer-Encoding label must always be used. + Labelling unencoded data containing 8bit characters as "7bit" is not + allowed, nor is labelling unencoded non-line-oriented data as + anything other than "binary" allowed. + + Unlike media subtypes, a proliferation of Content-Transfer-Encoding + values is both undesirable and unnecessary. However, establishing + only a single transformation into the "7bit" domain does not seem + + + +Freed & Borenstein Standards Track [Page 15] + +RFC 2045 Internet Message Bodies November 1996 + + + possible. There is a tradeoff between the desire for a compact and + efficient encoding of largely- binary data and the desire for a + somewhat readable encoding of data that is mostly, but not entirely, + 7bit. For this reason, at least two encoding mechanisms are + necessary: a more or less readable encoding (quoted-printable) and a + "dense" or "uniform" encoding (base64). + + Mail transport for unencoded 8bit data is defined in RFC 1652. As of + the initial publication of this document, there are no standardized + Internet mail transports for which it is legitimate to include + unencoded binary data in mail bodies. Thus there are no + circumstances in which the "binary" Content-Transfer-Encoding is + actually valid in Internet mail. However, in the event that binary + mail transport becomes a reality in Internet mail, or when MIME is + used in conjunction with any other binary-capable mail transport + mechanism, binary bodies must be labelled as such using this + mechanism. + + NOTE: The five values defined for the Content-Transfer-Encoding field + imply nothing about the media type other than the algorithm by which + it was encoded or the transport system requirements if unencoded. + +6.3. New Content-Transfer-Encodings + + Implementors may, if necessary, define private Content-Transfer- + Encoding values, but must use an x-token, which is a name prefixed by + "X-", to indicate its non-standard status, e.g., "Content-Transfer- + Encoding: x-my-new-encoding". Additional standardized Content- + Transfer-Encoding values must be specified by a standards-track RFC. + The requirements such specifications must meet are given in RFC 2048. + As such, all content-transfer-encoding namespace except that + beginning with "X-" is explicitly reserved to the IETF for future + use. + + Unlike media types and subtypes, the creation of new Content- + Transfer-Encoding values is STRONGLY discouraged, as it seems likely + to hinder interoperability with little potential benefit + +6.4. Interpretation and Use + + If a Content-Transfer-Encoding header field appears as part of a + message header, it applies to the entire body of that message. If a + Content-Transfer-Encoding header field appears as part of an entity's + headers, it applies only to the body of that entity. If an entity is + of type "multipart" the Content-Transfer-Encoding is not permitted to + have any value other than "7bit", "8bit" or "binary". Even more + severe restrictions apply to some subtypes of the "message" type. + + + + +Freed & Borenstein Standards Track [Page 16] + +RFC 2045 Internet Message Bodies November 1996 + + + It should be noted that most media types are defined in terms of + octets rather than bits, so that the mechanisms described here are + mechanisms for encoding arbitrary octet streams, not bit streams. If + a bit stream is to be encoded via one of these mechanisms, it must + first be converted to an 8bit byte stream using the network standard + bit order ("big-endian"), in which the earlier bits in a stream + become the higher-order bits in a 8bit byte. A bit stream not ending + at an 8bit boundary must be padded with zeroes. RFC 2046 provides a + mechanism for noting the addition of such padding in the case of the + application/octet-stream media type, which has a "padding" parameter. + + The encoding mechanisms defined here explicitly encode all data in + US-ASCII. Thus, for example, suppose an entity has header fields + such as: + + Content-Type: text/plain; charset=ISO-8859-1 + Content-transfer-encoding: base64 + + This must be interpreted to mean that the body is a base64 US-ASCII + encoding of data that was originally in ISO-8859-1, and will be in + that character set again after decoding. + + Certain Content-Transfer-Encoding values may only be used on certain + media types. In particular, it is EXPRESSLY FORBIDDEN to use any + encodings other than "7bit", "8bit", or "binary" with any composite + media type, i.e. one that recursively includes other Content-Type + fields. Currently the only composite media types are "multipart" and + "message". All encodings that are desired for bodies of type + multipart or message must be done at the innermost level, by encoding + the actual body that needs to be encoded. + + It should also be noted that, by definition, if a composite entity + has a transfer-encoding value such as "7bit", but one of the enclosed + entities has a less restrictive value such as "8bit", then either the + outer "7bit" labelling is in error, because 8bit data are included, + or the inner "8bit" labelling placed an unnecessarily high demand on + the transport system because the actual included data were actually + 7bit-safe. + + NOTE ON ENCODING RESTRICTIONS: Though the prohibition against using + content-transfer-encodings on composite body data may seem overly + restrictive, it is necessary to prevent nested encodings, in which + data are passed through an encoding algorithm multiple times, and + must be decoded multiple times in order to be properly viewed. + Nested encodings add considerable complexity to user agents: Aside + from the obvious efficiency problems with such multiple encodings, + they can obscure the basic structure of a message. In particular, + they can imply that several decoding operations are necessary simply + + + +Freed & Borenstein Standards Track [Page 17] + +RFC 2045 Internet Message Bodies November 1996 + + + to find out what types of bodies a message contains. Banning nested + encodings may complicate the job of certain mail gateways, but this + seems less of a problem than the effect of nested encodings on user + agents. + + Any entity with an unrecognized Content-Transfer-Encoding must be + treated as if it has a Content-Type of "application/octet-stream", + regardless of what the Content-Type header field actually says. + + NOTE ON THE RELATIONSHIP BETWEEN CONTENT-TYPE AND CONTENT-TRANSFER- + ENCODING: It may seem that the Content-Transfer-Encoding could be + inferred from the characteristics of the media that is to be encoded, + or, at the very least, that certain Content-Transfer-Encodings could + be mandated for use with specific media types. There are several + reasons why this is not the case. First, given the varying types of + transports used for mail, some encodings may be appropriate for some + combinations of media types and transports but not for others. (For + example, in an 8bit transport, no encoding would be required for text + in certain character sets, while such encodings are clearly required + for 7bit SMTP.) + + Second, certain media types may require different types of transfer + encoding under different circumstances. For example, many PostScript + bodies might consist entirely of short lines of 7bit data and hence + require no encoding at all. Other PostScript bodies (especially + those using Level 2 PostScript's binary encoding mechanism) may only + be reasonably represented using a binary transport encoding. + Finally, since the Content-Type field is intended to be an open-ended + specification mechanism, strict specification of an association + between media types and encodings effectively couples the + specification of an application protocol with a specific lower-level + transport. This is not desirable since the developers of a media + type should not have to be aware of all the transports in use and + what their limitations are. + +6.5. Translating Encodings + + The quoted-printable and base64 encodings are designed so that + conversion between them is possible. The only issue that arises in + such a conversion is the handling of hard line breaks in quoted- + printable encoding output. When converting from quoted-printable to + base64 a hard line break in the quoted-printable form represents a + CRLF sequence in the canonical form of the data. It must therefore be + converted to a corresponding encoded CRLF in the base64 form of the + data. Similarly, a CRLF sequence in the canonical form of the data + obtained after base64 decoding must be converted to a quoted- + printable hard line break, but ONLY when converting text data. + + + + +Freed & Borenstein Standards Track [Page 18] + +RFC 2045 Internet Message Bodies November 1996 + + +6.6. Canonical Encoding Model + + There was some confusion, in the previous versions of this RFC, + regarding the model for when email data was to be converted to + canonical form and encoded, and in particular how this process would + affect the treatment of CRLFs, given that the representation of + newlines varies greatly from system to system, and the relationship + between content-transfer-encodings and character sets. A canonical + model for encoding is presented in RFC 2049 for this reason. + +6.7. Quoted-Printable Content-Transfer-Encoding + + The Quoted-Printable encoding is intended to represent data that + largely consists of octets that correspond to printable characters in + the US-ASCII character set. It encodes the data in such a way that + the resulting octets are unlikely to be modified by mail transport. + If the data being encoded are mostly US-ASCII text, the encoded form + of the data remains largely recognizable by humans. A body which is + entirely US-ASCII may also be encoded in Quoted-Printable to ensure + the integrity of the data should the message pass through a + character-translating, and/or line-wrapping gateway. + + In this encoding, octets are to be represented as determined by the + following rules: + + (1) (General 8bit representation) Any octet, except a CR or + LF that is part of a CRLF line break of the canonical + (standard) form of the data being encoded, may be + represented by an "=" followed by a two digit + hexadecimal representation of the octet's value. The + digits of the hexadecimal alphabet, for this purpose, + are "0123456789ABCDEF". Uppercase letters must be + used; lowercase letters are not allowed. Thus, for + example, the decimal value 12 (US-ASCII form feed) can + be represented by "=0C", and the decimal value 61 (US- + ASCII EQUAL SIGN) can be represented by "=3D". This + rule must be followed except when the following rules + allow an alternative encoding. + + (2) (Literal representation) Octets with decimal values of + 33 through 60 inclusive, and 62 through 126, inclusive, + MAY be represented as the US-ASCII characters which + correspond to those octets (EXCLAMATION POINT through + LESS THAN, and GREATER THAN through TILDE, + respectively). + + (3) (White Space) Octets with values of 9 and 32 MAY be + represented as US-ASCII TAB (HT) and SPACE characters, + + + +Freed & Borenstein Standards Track [Page 19] + +RFC 2045 Internet Message Bodies November 1996 + + + respectively, but MUST NOT be so represented at the end + of an encoded line. Any TAB (HT) or SPACE characters + on an encoded line MUST thus be followed on that line + by a printable character. In particular, an "=" at the + end of an encoded line, indicating a soft line break + (see rule #5) may follow one or more TAB (HT) or SPACE + characters. It follows that an octet with decimal + value 9 or 32 appearing at the end of an encoded line + must be represented according to Rule #1. This rule is + necessary because some MTAs (Message Transport Agents, + programs which transport messages from one user to + another, or perform a portion of such transfers) are + known to pad lines of text with SPACEs, and others are + known to remove "white space" characters from the end + of a line. Therefore, when decoding a Quoted-Printable + body, any trailing white space on a line must be + deleted, as it will necessarily have been added by + intermediate transport agents. + + (4) (Line Breaks) A line break in a text body, represented + as a CRLF sequence in the text canonical form, must be + represented by a (RFC 822) line break, which is also a + CRLF sequence, in the Quoted-Printable encoding. Since + the canonical representation of media types other than + text do not generally include the representation of + line breaks as CRLF sequences, no hard line breaks + (i.e. line breaks that are intended to be meaningful + and to be displayed to the user) can occur in the + quoted-printable encoding of such types. Sequences + like "=0D", "=0A", "=0A=0D" and "=0D=0A" will routinely + appear in non-text data represented in quoted- + printable, of course. + + Note that many implementations may elect to encode the + local representation of various content types directly + rather than converting to canonical form first, + encoding, and then converting back to local + representation. In particular, this may apply to plain + text material on systems that use newline conventions + other than a CRLF terminator sequence. Such an + implementation optimization is permissible, but only + when the combined canonicalization-encoding step is + equivalent to performing the three steps separately. + + (5) (Soft Line Breaks) The Quoted-Printable encoding + REQUIRES that encoded lines be no more than 76 + characters long. If longer lines are to be encoded + with the Quoted-Printable encoding, "soft" line breaks + + + +Freed & Borenstein Standards Track [Page 20] + +RFC 2045 Internet Message Bodies November 1996 + + + must be used. An equal sign as the last character on a + encoded line indicates such a non-significant ("soft") + line break in the encoded text. + + Thus if the "raw" form of the line is a single unencoded line that + says: + + Now's the time for all folk to come to the aid of their country. + + This can be represented, in the Quoted-Printable encoding, as: + + Now's the time = + for all folk to come= + to the aid of their country. + + This provides a mechanism with which long lines are encoded in such a + way as to be restored by the user agent. The 76 character limit does + not count the trailing CRLF, but counts all other characters, + including any equal signs. + + Since the hyphen character ("-") may be represented as itself in the + Quoted-Printable encoding, care must be taken, when encapsulating a + quoted-printable encoded body inside one or more multipart entities, + to ensure that the boundary delimiter does not appear anywhere in the + encoded body. (A good strategy is to choose a boundary that includes + a character sequence such as "=_" which can never appear in a + quoted-printable body. See the definition of multipart messages in + RFC 2046.) + + NOTE: The quoted-printable encoding represents something of a + compromise between readability and reliability in transport. Bodies + encoded with the quoted-printable encoding will work reliably over + most mail gateways, but may not work perfectly over a few gateways, + notably those involving translation into EBCDIC. A higher level of + confidence is offered by the base64 Content-Transfer-Encoding. A way + to get reasonably reliable transport through EBCDIC gateways is to + also quote the US-ASCII characters + + !"#$@[\]^`{|}~ + + according to rule #1. + + Because quoted-printable data is generally assumed to be line- + oriented, it is to be expected that the representation of the breaks + between the lines of quoted-printable data may be altered in + transport, in the same manner that plain text mail has always been + altered in Internet mail when passing between systems with differing + newline conventions. If such alterations are likely to constitute a + + + +Freed & Borenstein Standards Track [Page 21] + +RFC 2045 Internet Message Bodies November 1996 + + + corruption of the data, it is probably more sensible to use the + base64 encoding rather than the quoted-printable encoding. + + NOTE: Several kinds of substrings cannot be generated according to + the encoding rules for the quoted-printable content-transfer- + encoding, and hence are formally illegal if they appear in the output + of a quoted-printable encoder. This note enumerates these cases and + suggests ways to handle such illegal substrings if any are + encountered in quoted-printable data that is to be decoded. + + (1) An "=" followed by two hexadecimal digits, one or both + of which are lowercase letters in "abcdef", is formally + illegal. A robust implementation might choose to + recognize them as the corresponding uppercase letters. + + (2) An "=" followed by a character that is neither a + hexadecimal digit (including "abcdef") nor the CR + character of a CRLF pair is illegal. This case can be + the result of US-ASCII text having been included in a + quoted-printable part of a message without itself + having been subjected to quoted-printable encoding. A + reasonable approach by a robust implementation might be + to include the "=" character and the following + character in the decoded data without any + transformation and, if possible, indicate to the user + that proper decoding was not possible at this point in + the data. + + (3) An "=" cannot be the ultimate or penultimate character + in an encoded object. This could be handled as in case + (2) above. + + (4) Control characters other than TAB, or CR and LF as + parts of CRLF pairs, must not appear. The same is true + for octets with decimal values greater than 126. If + found in incoming quoted-printable data by a decoder, a + robust implementation might exclude them from the + decoded data and warn the user that illegal characters + were discovered. + + (5) Encoded lines must not be longer than 76 characters, + not counting the trailing CRLF. If longer lines are + found in incoming, encoded data, a robust + implementation might nevertheless decode the lines, and + might report the erroneous encoding to the user. + + + + + + +Freed & Borenstein Standards Track [Page 22] + +RFC 2045 Internet Message Bodies November 1996 + + + WARNING TO IMPLEMENTORS: If binary data is encoded in quoted- + printable, care must be taken to encode CR and LF characters as "=0D" + and "=0A", respectively. In particular, a CRLF sequence in binary + data should be encoded as "=0D=0A". Otherwise, if CRLF were + represented as a hard line break, it might be incorrectly decoded on + platforms with different line break conventions. + + For formalists, the syntax of quoted-printable data is described by + the following grammar: + + quoted-printable := qp-line *(CRLF qp-line) + + qp-line := *(qp-segment transport-padding CRLF) + qp-part transport-padding + + qp-part := qp-section + ; Maximum length of 76 characters + + qp-segment := qp-section *(SPACE / TAB) "=" + ; Maximum length of 76 characters + + qp-section := [*(ptext / SPACE / TAB) ptext] + + ptext := hex-octet / safe-char + + safe-char := + ; Characters not listed as "mail-safe" in + ; RFC 2049 are also not recommended. + + hex-octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + ; Octet must be used for characters > 127, =, + ; SPACEs or TABs at the ends of lines, and is + ; recommended for any character not listed in + ; RFC 2049 as "mail-safe". + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + IMPORTANT: The addition of LWSP between the elements shown in this + BNF is NOT allowed since this BNF does not specify a structured + header field. + + + + + +Freed & Borenstein Standards Track [Page 23] + +RFC 2045 Internet Message Bodies November 1996 + + +6.8. Base64 Content-Transfer-Encoding + + The Base64 Content-Transfer-Encoding is designed to represent + arbitrary sequences of octets in a form that need not be humanly + readable. The encoding and decoding algorithms are simple, but the + encoded data are consistently only about 33 percent larger than the + unencoded data. This encoding is virtually identical to the one used + in Privacy Enhanced Mail (PEM) applications, as defined in RFC 1421. + + A 65-character subset of US-ASCII is used, enabling 6 bits to be + represented per printable character. (The extra 65th character, "=", + is used to signify a special processing function.) + + NOTE: This subset has the important property that it is represented + identically in all versions of ISO 646, including US-ASCII, and all + characters in the subset are also represented identically in all + versions of EBCDIC. Other popular encodings, such as the encoding + used by the uuencode utility, Macintosh binhex 4.0 [RFC-1741], and + the base85 encoding specified as part of Level 2 PostScript, do not + share these properties, and thus do not fulfill the portability + requirements a binary transport encoding for mail must meet. + + The encoding process represents 24-bit groups of input bits as output + strings of 4 encoded characters. Proceeding from left to right, a + 24-bit input group is formed by concatenating 3 8bit input groups. + These 24 bits are then treated as 4 concatenated 6-bit groups, each + of which is translated into a single digit in the base64 alphabet. + When encoding a bit stream via the base64 encoding, the bit stream + must be presumed to be ordered with the most-significant-bit first. + That is, the first bit in the stream will be the high-order bit in + the first 8bit byte, and the eighth bit will be the low-order bit in + the first 8bit byte, and so on. + + Each 6-bit group is used as an index into an array of 64 printable + characters. The character referenced by the index is placed in the + output string. These characters, identified in Table 1, below, are + selected so as to be universally representable, and the set excludes + characters with particular significance to SMTP (e.g., ".", CR, LF) + and to the multipart boundary delimiters defined in RFC 2046 (e.g., + "-"). + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 24] + +RFC 2045 Internet Message Bodies November 1996 + + + Table 1: The Base64 Alphabet + + Value Encoding Value Encoding Value Encoding Value Encoding + 0 A 17 R 34 i 51 z + 1 B 18 S 35 j 52 0 + 2 C 19 T 36 k 53 1 + 3 D 20 U 37 l 54 2 + 4 E 21 V 38 m 55 3 + 5 F 22 W 39 n 56 4 + 6 G 23 X 40 o 57 5 + 7 H 24 Y 41 p 58 6 + 8 I 25 Z 42 q 59 7 + 9 J 26 a 43 r 60 8 + 10 K 27 b 44 s 61 9 + 11 L 28 c 45 t 62 + + 12 M 29 d 46 u 63 / + 13 N 30 e 47 v + 14 O 31 f 48 w (pad) = + 15 P 32 g 49 x + 16 Q 33 h 50 y + + The encoded output stream must be represented in lines of no more + than 76 characters each. All line breaks or other characters not + found in Table 1 must be ignored by decoding software. In base64 + data, characters other than those in Table 1, line breaks, and other + white space probably indicate a transmission error, about which a + warning message or even a message rejection might be appropriate + under some circumstances. + + Special processing is performed if fewer than 24 bits are available + at the end of the data being encoded. A full encoding quantum is + always completed at the end of a body. When fewer than 24 input bits + are available in an input group, zero bits are added (on the right) + to form an integral number of 6-bit groups. Padding at the end of + the data is performed using the "=" character. Since all base64 + input is an integral number of octets, only the following cases can + arise: (1) the final quantum of encoding input is an integral + multiple of 24 bits; here, the final unit of encoded output will be + an integral multiple of 4 characters with no "=" padding, (2) the + final quantum of encoding input is exactly 8 bits; here, the final + unit of encoded output will be two characters followed by two "=" + padding characters, or (3) the final quantum of encoding input is + exactly 16 bits; here, the final unit of encoded output will be three + characters followed by one "=" padding character. + + Because it is used only for padding at the end of the data, the + occurrence of any "=" characters may be taken as evidence that the + end of the data has been reached (without truncation in transit). No + + + +Freed & Borenstein Standards Track [Page 25] + +RFC 2045 Internet Message Bodies November 1996 + + + such assurance is possible, however, when the number of octets + transmitted was a multiple of three and no "=" characters are + present. + + Any characters outside of the base64 alphabet are to be ignored in + base64-encoded data. + + Care must be taken to use the proper octets for line breaks if base64 + encoding is applied directly to text material that has not been + converted to canonical form. In particular, text line breaks must be + converted into CRLF sequences prior to base64 encoding. The + important thing to note is that this may be done directly by the + encoder rather than in a prior canonicalization step in some + implementations. + + NOTE: There is no need to worry about quoting potential boundary + delimiters within base64-encoded bodies within multipart entities + because no hyphen characters are used in the base64 encoding. + +7. Content-ID Header Field + + In constructing a high-level user agent, it may be desirable to allow + one body to make reference to another. Accordingly, bodies may be + labelled using the "Content-ID" header field, which is syntactically + identical to the "Message-ID" header field: + + id := "Content-ID" ":" msg-id + + Like the Message-ID values, Content-ID values must be generated to be + world-unique. + + The Content-ID value may be used for uniquely identifying MIME + entities in several contexts, particularly for caching data + referenced by the message/external-body mechanism. Although the + Content-ID header is generally optional, its use is MANDATORY in + implementations which generate data of the optional MIME media type + "message/external-body". That is, each message/external-body entity + must have a Content-ID field to permit caching of such data. + + It is also worth noting that the Content-ID value has special + semantics in the case of the multipart/alternative media type. This + is explained in the section of RFC 2046 dealing with + multipart/alternative. + + + + + + + + +Freed & Borenstein Standards Track [Page 26] + +RFC 2045 Internet Message Bodies November 1996 + + +8. Content-Description Header Field + + The ability to associate some descriptive information with a given + body is often desirable. For example, it may be useful to mark an + "image" body as "a picture of the Space Shuttle Endeavor." Such text + may be placed in the Content-Description header field. This header + field is always optional. + + description := "Content-Description" ":" *text + + The description is presumed to be given in the US-ASCII character + set, although the mechanism specified in RFC 2047 may be used for + non-US-ASCII Content-Description values. + +9. Additional MIME Header Fields + + Future documents may elect to define additional MIME header fields + for various purposes. Any new header field that further describes + the content of a message should begin with the string "Content-" to + allow such fields which appear in a message header to be + distinguished from ordinary RFC 822 message header fields. + + MIME-extension-field := + +10. Summary + + Using the MIME-Version, Content-Type, and Content-Transfer-Encoding + header fields, it is possible to include, in a standardized way, + arbitrary types of data with RFC 822 conformant mail messages. No + restrictions imposed by either RFC 821 or RFC 822 are violated, and + care has been taken to avoid problems caused by additional + restrictions imposed by the characteristics of some Internet mail + transport mechanisms (see RFC 2049). + + The next document in this set, RFC 2046, specifies the initial set of + media types that can be labelled and transported using these headers. + +11. Security Considerations + + Security issues are discussed in the second document in this set, RFC + 2046. + + + + + + + + +Freed & Borenstein Standards Track [Page 27] + +RFC 2045 Internet Message Bodies November 1996 + + +12. Authors' Addresses + + For more information, the authors of this document are best contacted + via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + Nathaniel S. Borenstein + First Virtual Holdings + 25 Washington Avenue + Morristown, NJ 07960 + USA + + Phone: +1 201 540 8967 + Fax: +1 201 993 3032 + EMail: nsb@nsb.fv.com + + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on RFC 822 Extensions. The chairman of that group, + Greg Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Octel Network Services + 17080 Dallas Parkway + Dallas, TX 75248-1905 + USA + + EMail: Greg.Vaudreuil@Octel.Com + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 28] + +RFC 2045 Internet Message Bodies November 1996 + + +Appendix A -- Collected Grammar + + This appendix contains the complete BNF grammar for all the syntax + specified by this document. + + By itself, however, this grammar is incomplete. It refers by name to + several syntax rules that are defined by RFC 822. Rather than + reproduce those definitions here, and risk unintentional differences + between the two, this document simply refers the reader to RFC 822 + for the remaining definitions. Wherever a term is undefined, it + refers to the RFC 822 definition. + + attribute := token + ; Matching of attributes + ; is ALWAYS case-insensitive. + + composite-type := "message" / "multipart" / extension-token + + content := "Content-Type" ":" type "/" subtype + *(";" parameter) + ; Matching of media type and subtype + ; is ALWAYS case-insensitive. + + description := "Content-Description" ":" *text + + discrete-type := "text" / "image" / "audio" / "video" / + "application" / extension-token + + encoding := "Content-Transfer-Encoding" ":" mechanism + + entity-headers := [ content CRLF ] + [ encoding CRLF ] + [ id CRLF ] + [ description CRLF ] + *( MIME-extension-field CRLF ) + + extension-token := ietf-token / x-token + + hex-octet := "=" 2(DIGIT / "A" / "B" / "C" / "D" / "E" / "F") + ; Octet must be used for characters > 127, =, + ; SPACEs or TABs at the ends of lines, and is + ; recommended for any character not listed in + ; RFC 2049 as "mail-safe". + + iana-token := + + + + +Freed & Borenstein Standards Track [Page 29] + +RFC 2045 Internet Message Bodies November 1996 + + + ietf-token := + + id := "Content-ID" ":" msg-id + + mechanism := "7bit" / "8bit" / "binary" / + "quoted-printable" / "base64" / + ietf-token / x-token + + MIME-extension-field := + + MIME-message-headers := entity-headers + fields + version CRLF + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + MIME-part-headers := entity-headers + [fields] + ; Any field not beginning with + ; "content-" can have no defined + ; meaning and may be ignored. + ; The ordering of the header + ; fields implied by this BNF + ; definition should be ignored. + + parameter := attribute "=" value + + ptext := hex-octet / safe-char + + qp-line := *(qp-segment transport-padding CRLF) + qp-part transport-padding + + qp-part := qp-section + ; Maximum length of 76 characters + + qp-section := [*(ptext / SPACE / TAB) ptext] + + qp-segment := qp-section *(SPACE / TAB) "=" + ; Maximum length of 76 characters + + quoted-printable := qp-line *(CRLF qp-line) + + + + + +Freed & Borenstein Standards Track [Page 30] + +RFC 2045 Internet Message Bodies November 1996 + + + safe-char := + ; Characters not listed as "mail-safe" in + ; RFC 2049 are also not recommended. + + subtype := extension-token / iana-token + + token := 1* + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + tspecials := "(" / ")" / "<" / ">" / "@" / + "," / ";" / ":" / "\" / <"> + "/" / "[" / "]" / "?" / "=" + ; Must be in quoted-string, + ; to use within parameter values + + type := discrete-type / composite-type + + value := token / quoted-string + + version := "MIME-Version" ":" 1*DIGIT "." 1*DIGIT + + x-token := + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 31] + diff --git a/lib/qCal/docs/rfc/rfc2046.txt b/lib/qCal/docs/rfc/rfc2046.txt new file mode 100644 index 0000000..84d90c1 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2046.txt @@ -0,0 +1,2467 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2046 Innosoft +Obsoletes: 1521, 1522, 1590 N. Borenstein +Category: Standards Track First Virtual + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part Two: + Media Types + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822 defines a message representation protocol specifying + considerable detail about US-ASCII message headers, but which leaves + the message content, or message body, as flat US-ASCII text. This + set of documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + The initial document in this set, RFC 2045, specifies the various + headers used to describe the structure of MIME messages. This second + document defines the general structure of the MIME media typing + system and defines an initial set of media types. The third document, + RFC 2047, describes extensions to RFC 822 to allow non-US-ASCII text + + + +Freed & Borenstein Standards Track [Page 1] + +RFC 2046 Media Types November 1996 + + + data in Internet mail header fields. The fourth document, RFC 2048, + specifies various IANA registration procedures for MIME-related + facilities. The fifth and final document, RFC 2049, describes MIME + conformance criteria as well as providing some illustrative examples + of MIME message formats, acknowledgements, and the bibliography. + + These documents are revisions of RFCs 1521 and 1522, which themselves + were revisions of RFCs 1341 and 1342. An appendix in RFC 2049 + describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction ......................................... 3 + 2. Definition of a Top-Level Media Type ................. 4 + 3. Overview Of The Initial Top-Level Media Types ........ 4 + 4. Discrete Media Type Values ........................... 6 + 4.1 Text Media Type ..................................... 6 + 4.1.1 Representation of Line Breaks ..................... 7 + 4.1.2 Charset Parameter ................................. 7 + 4.1.3 Plain Subtype ..................................... 11 + 4.1.4 Unrecognized Subtypes ............................. 11 + 4.2 Image Media Type .................................... 11 + 4.3 Audio Media Type .................................... 11 + 4.4 Video Media Type .................................... 12 + 4.5 Application Media Type .............................. 12 + 4.5.1 Octet-Stream Subtype .............................. 13 + 4.5.2 PostScript Subtype ................................ 14 + 4.5.3 Other Application Subtypes ........................ 17 + 5. Composite Media Type Values .......................... 17 + 5.1 Multipart Media Type ................................ 17 + 5.1.1 Common Syntax ..................................... 19 + 5.1.2 Handling Nested Messages and Multiparts ........... 24 + 5.1.3 Mixed Subtype ..................................... 24 + 5.1.4 Alternative Subtype ............................... 24 + 5.1.5 Digest Subtype .................................... 26 + 5.1.6 Parallel Subtype .................................. 27 + 5.1.7 Other Multipart Subtypes .......................... 28 + 5.2 Message Media Type .................................. 28 + 5.2.1 RFC822 Subtype .................................... 28 + 5.2.2 Partial Subtype ................................... 29 + 5.2.2.1 Message Fragmentation and Reassembly ............ 30 + 5.2.2.2 Fragmentation and Reassembly Example ............ 31 + 5.2.3 External-Body Subtype ............................. 33 + 5.2.4 Other Message Subtypes ............................ 40 + 6. Experimental Media Type Values ....................... 40 + 7. Summary .............................................. 41 + 8. Security Considerations .............................. 41 + 9. Authors' Addresses ................................... 42 + + + +Freed & Borenstein Standards Track [Page 2] + +RFC 2046 Media Types November 1996 + + + A. Collected Grammar .................................... 43 + +1. Introduction + + The first document in this set, RFC 2045, defines a number of header + fields, including Content-Type. The Content-Type field is used to + specify the nature of the data in the body of a MIME entity, by + giving media type and subtype identifiers, and by providing auxiliary + information that may be required for certain media types. After the + type and subtype names, the remainder of the header field is simply a + set of parameters, specified in an attribute/value notation. The + ordering of parameters is not significant. + + In general, the top-level media type is used to declare the general + type of data, while the subtype specifies a specific format for that + type of data. Thus, a media type of "image/xyz" is enough to tell a + user agent that the data is an image, even if the user agent has no + knowledge of the specific image format "xyz". Such information can + be used, for example, to decide whether or not to show a user the raw + data from an unrecognized subtype -- such an action might be + reasonable for unrecognized subtypes of "text", but not for + unrecognized subtypes of "image" or "audio". For this reason, + registered subtypes of "text", "image", "audio", and "video" should + not contain embedded information that is really of a different type. + Such compound formats should be represented using the "multipart" or + "application" types. + + Parameters are modifiers of the media subtype, and as such do not + fundamentally affect the nature of the content. The set of + meaningful parameters depends on the media type and subtype. Most + parameters are associated with a single specific subtype. However, a + given top-level media type may define parameters which are applicable + to any subtype of that type. Parameters may be required by their + defining media type or subtype or they may be optional. MIME + implementations must also ignore any parameters whose names they do + not recognize. + + MIME's Content-Type header field and media type mechanism has been + carefully designed to be extensible, and it is expected that the set + of media type/subtype pairs and their associated parameters will grow + significantly over time. Several other MIME facilities, such as + transfer encodings and "message/external-body" access types, are + likely to have new values defined over time. In order to ensure that + the set of such values is developed in an orderly, well-specified, + and public manner, MIME sets up a registration process which uses the + Internet Assigned Numbers Authority (IANA) as a central registry for + MIME's various areas of extensibility. The registration process for + these areas is described in a companion document, RFC 2048. + + + +Freed & Borenstein Standards Track [Page 3] + +RFC 2046 Media Types November 1996 + + + The initial seven standard top-level media type are defined and + described in the remainder of this document. + +2. Definition of a Top-Level Media Type + + The definition of a top-level media type consists of: + + (1) a name and a description of the type, including + criteria for whether a particular type would qualify + under that type, + + (2) the names and definitions of parameters, if any, which + are defined for all subtypes of that type (including + whether such parameters are required or optional), + + (3) how a user agent and/or gateway should handle unknown + subtypes of this type, + + (4) general considerations on gatewaying entities of this + top-level type, if any, and + + (5) any restrictions on content-transfer-encodings for + entities of this top-level type. + +3. Overview Of The Initial Top-Level Media Types + + The five discrete top-level media types are: + + (1) text -- textual information. The subtype "plain" in + particular indicates plain text containing no + formatting commands or directives of any sort. Plain + text is intended to be displayed "as-is". No special + software is required to get the full meaning of the + text, aside from support for the indicated character + set. Other subtypes are to be used for enriched text in + forms where application software may enhance the + appearance of the text, but such software must not be + required in order to get the general idea of the + content. Possible subtypes of "text" thus include any + word processor format that can be read without + resorting to software that understands the format. In + particular, formats that employ embeddded binary + formatting information are not considered directly + readable. A very simple and portable subtype, + "richtext", was defined in RFC 1341, with a further + revision in RFC 1896 under the name "enriched". + + + + + +Freed & Borenstein Standards Track [Page 4] + +RFC 2046 Media Types November 1996 + + + (2) image -- image data. "Image" requires a display device + (such as a graphical display, a graphics printer, or a + FAX machine) to view the information. An initial + subtype is defined for the widely-used image format + JPEG. . subtypes are defined for two widely-used image + formats, jpeg and gif. + + (3) audio -- audio data. "Audio" requires an audio output + device (such as a speaker or a telephone) to "display" + the contents. An initial subtype "basic" is defined in + this document. + + (4) video -- video data. "Video" requires the capability + to display moving images, typically including + specialized hardware and software. An initial subtype + "mpeg" is defined in this document. + + (5) application -- some other kind of data, typically + either uninterpreted binary data or information to be + processed by an application. The subtype "octet- + stream" is to be used in the case of uninterpreted + binary data, in which case the simplest recommended + action is to offer to write the information into a file + for the user. The "PostScript" subtype is also defined + for the transport of PostScript material. Other + expected uses for "application" include spreadsheets, + data for mail-based scheduling systems, and languages + for "active" (computational) messaging, and word + processing formats that are not directly readable. + Note that security considerations may exist for some + types of application data, most notably + "application/PostScript" and any form of active + messaging. These issues are discussed later in this + document. + + The two composite top-level media types are: + + (1) multipart -- data consisting of multiple entities of + independent data types. Four subtypes are initially + defined, including the basic "mixed" subtype specifying + a generic mixed set of parts, "alternative" for + representing the same data in multiple formats, + "parallel" for parts intended to be viewed + simultaneously, and "digest" for multipart entities in + which each part has a default type of "message/rfc822". + + + + + + +Freed & Borenstein Standards Track [Page 5] + +RFC 2046 Media Types November 1996 + + + (2) message -- an encapsulated message. A body of media + type "message" is itself all or a portion of some kind + of message object. Such objects may or may not in turn + contain other entities. The "rfc822" subtype is used + when the encapsulated content is itself an RFC 822 + message. The "partial" subtype is defined for partial + RFC 822 messages, to permit the fragmented transmission + of bodies that are thought to be too large to be passed + through transport facilities in one piece. Another + subtype, "external-body", is defined for specifying + large bodies by reference to an external data source. + + It should be noted that the list of media type values given here may + be augmented in time, via the mechanisms described above, and that + the set of subtypes is expected to grow substantially. + +4. Discrete Media Type Values + + Five of the seven initial media type values refer to discrete bodies. + The content of these types must be handled by non-MIME mechanisms; + they are opaque to MIME processors. + +4.1. Text Media Type + + The "text" media type is intended for sending material which is + principally textual in form. A "charset" parameter may be used to + indicate the character set of the body text for "text" subtypes, + notably including the subtype "text/plain", which is a generic + subtype for plain text. Plain text does not provide for or allow + formatting commands, font attribute specifications, processing + instructions, interpretation directives, or content markup. Plain + text is seen simply as a linear sequence of characters, possibly + interrupted by line breaks or page breaks. Plain text may allow the + stacking of several characters in the same position in the text. + Plain text in scripts like Arabic and Hebrew may also include + facilitites that allow the arbitrary mixing of text segments with + opposite writing directions. + + Beyond plain text, there are many formats for representing what might + be known as "rich text". An interesting characteristic of many such + representations is that they are to some extent readable even without + the software that interprets them. It is useful, then, to + distinguish them, at the highest level, from such unreadable data as + images, audio, or text represented in an unreadable form. In the + absence of appropriate interpretation software, it is reasonable to + show subtypes of "text" to the user, while it is not reasonable to do + so with most nontextual data. Such formatted textual data should be + represented using subtypes of "text". + + + +Freed & Borenstein Standards Track [Page 6] + +RFC 2046 Media Types November 1996 + + +4.1.1. Representation of Line Breaks + + The canonical form of any MIME "text" subtype MUST always represent a + line break as a CRLF sequence. Similarly, any occurrence of CRLF in + MIME "text" MUST represent a line break. Use of CR and LF outside of + line break sequences is also forbidden. + + This rule applies regardless of format or character set or sets + involved. + + NOTE: The proper interpretation of line breaks when a body is + displayed depends on the media type. In particular, while it is + appropriate to treat a line break as a transition to a new line when + displaying a "text/plain" body, this treatment is actually incorrect + for other subtypes of "text" like "text/enriched" [RFC-1896]. + Similarly, whether or not line breaks should be added during display + operations is also a function of the media type. It should not be + necessary to add any line breaks to display "text/plain" correctly, + whereas proper display of "text/enriched" requires the appropriate + addition of line breaks. + + NOTE: Some protocols defines a maximum line length. E.g. SMTP [RFC- + 821] allows a maximum of 998 octets before the next CRLF sequence. + To be transported by such protocols, data which includes too long + segments without CRLF sequences must be encoded with a suitable + content-transfer-encoding. + +4.1.2. Charset Parameter + + A critical parameter that may be specified in the Content-Type field + for "text/plain" data is the character set. This is specified with a + "charset" parameter, as in: + + Content-type: text/plain; charset=iso-8859-1 + + Unlike some other parameter values, the values of the charset + parameter are NOT case sensitive. The default character set, which + must be assumed in the absence of a charset parameter, is US-ASCII. + + The specification for any future subtypes of "text" must specify + whether or not they will also utilize a "charset" parameter, and may + possibly restrict its values as well. For other subtypes of "text" + than "text/plain", the semantics of the "charset" parameter should be + defined to be identical to those specified here for "text/plain", + i.e., the body consists entirely of characters in the given charset. + In particular, definers of future "text" subtypes should pay close + attention to the implications of multioctet character sets for their + subtype definitions. + + + +Freed & Borenstein Standards Track [Page 7] + +RFC 2046 Media Types November 1996 + + + The charset parameter for subtypes of "text" gives a name of a + character set, as "character set" is defined in RFC 2045. The rules + regarding line breaks detailed in the previous section must also be + observed -- a character set whose definition does not conform to + these rules cannot be used in a MIME "text" subtype. + + An initial list of predefined character set names can be found at the + end of this section. Additional character sets may be registered + with IANA. + + Other media types than subtypes of "text" might choose to employ the + charset parameter as defined here, but with the CRLF/line break + restriction removed. Therefore, all character sets that conform to + the general definition of "character set" in RFC 2045 can be + registered for MIME use. + + Note that if the specified character set includes 8-bit characters + and such characters are used in the body, a Content-Transfer-Encoding + header field and a corresponding encoding on the data are required in + order to transmit the body via some mail transfer protocols, such as + SMTP [RFC-821]. + + The default character set, US-ASCII, has been the subject of some + confusion and ambiguity in the past. Not only were there some + ambiguities in the definition, there have been wide variations in + practice. In order to eliminate such ambiguity and variations in the + future, it is strongly recommended that new user agents explicitly + specify a character set as a media type parameter in the Content-Type + header field. "US-ASCII" does not indicate an arbitrary 7-bit + character set, but specifies that all octets in the body must be + interpreted as characters according to the US-ASCII character set. + National and application-oriented versions of ISO 646 [ISO-646] are + usually NOT identical to US-ASCII, and in that case their use in + Internet mail is explicitly discouraged. The omission of the ISO 646 + character set from this document is deliberate in this regard. The + character set name of "US-ASCII" explicitly refers to the character + set defined in ANSI X3.4-1986 [US- ASCII]. The new international + reference version (IRV) of the 1991 edition of ISO 646 is identical + to US-ASCII. The character set name "ASCII" is reserved and must not + be used for any purpose. + + NOTE: RFC 821 explicitly specifies "ASCII", and references an earlier + version of the American Standard. Insofar as one of the purposes of + specifying a media type and character set is to permit the receiver + to unambiguously determine how the sender intended the coded message + to be interpreted, assuming anything other than "strict ASCII" as the + default would risk unintentional and incompatible changes to the + semantics of messages now being transmitted. This also implies that + + + +Freed & Borenstein Standards Track [Page 8] + +RFC 2046 Media Types November 1996 + + + messages containing characters coded according to other versions of + ISO 646 than US-ASCII and the 1991 IRV, or using code-switching + procedures (e.g., those of ISO 2022), as well as 8bit or multiple + octet character encodings MUST use an appropriate character set + specification to be consistent with MIME. + + The complete US-ASCII character set is listed in ANSI X3.4- 1986. + Note that the control characters including DEL (0-31, 127) have no + defined meaning in apart from the combination CRLF (US-ASCII values + 13 and 10) indicating a new line. Two of the characters have de + facto meanings in wide use: FF (12) often means "start subsequent + text on the beginning of a new page"; and TAB or HT (9) often (though + not always) means "move the cursor to the next available column after + the current position where the column number is a multiple of 8 + (counting the first column as column 0)." Aside from these + conventions, any use of the control characters or DEL in a body must + either occur + + (1) because a subtype of text other than "plain" + specifically assigns some additional meaning, or + + (2) within the context of a private agreement between the + sender and recipient. Such private agreements are + discouraged and should be replaced by the other + capabilities of this document. + + NOTE: An enormous proliferation of character sets exist beyond US- + ASCII. A large number of partially or totally overlapping character + sets is NOT a good thing. A SINGLE character set that can be used + universally for representing all of the world's languages in Internet + mail would be preferrable. Unfortunately, existing practice in + several communities seems to point to the continued use of multiple + character sets in the near future. A small number of standard + character sets are, therefore, defined for Internet use in this + document. + + The defined charset values are: + + (1) US-ASCII -- as defined in ANSI X3.4-1986 [US-ASCII]. + + (2) ISO-8859-X -- where "X" is to be replaced, as + necessary, for the parts of ISO-8859 [ISO-8859]. Note + that the ISO 646 character sets have deliberately been + omitted in favor of their 8859 replacements, which are + the designated character sets for Internet mail. As of + the publication of this document, the legitimate values + for "X" are the digits 1 through 10. + + + + +Freed & Borenstein Standards Track [Page 9] + +RFC 2046 Media Types November 1996 + + + Characters in the range 128-159 has no assigned meaning in ISO-8859- + X. Characters with values below 128 in ISO-8859-X have the same + assigned meaning as they do in US-ASCII. + + Part 6 of ISO 8859 (Latin/Arabic alphabet) and part 8 (Latin/Hebrew + alphabet) includes both characters for which the normal writing + direction is right to left and characters for which it is left to + right, but do not define a canonical ordering method for representing + bi-directional text. The charset values "ISO-8859-6" and "ISO-8859- + 8", however, specify that the visual method is used [RFC-1556]. + + All of these character sets are used as pure 7bit or 8bit sets + without any shift or escape functions. The meaning of shift and + escape sequences in these character sets is not defined. + + The character sets specified above are the ones that were relatively + uncontroversial during the drafting of MIME. This document does not + endorse the use of any particular character set other than US-ASCII, + and recognizes that the future evolution of world character sets + remains unclear. + + Note that the character set used, if anything other than US- ASCII, + must always be explicitly specified in the Content-Type field. + + No character set name other than those defined above may be used in + Internet mail without the publication of a formal specification and + its registration with IANA, or by private agreement, in which case + the character set name must begin with "X-". + + Implementors are discouraged from defining new character sets unless + absolutely necessary. + + The "charset" parameter has been defined primarily for the purpose of + textual data, and is described in this section for that reason. + However, it is conceivable that non-textual data might also wish to + specify a charset value for some purpose, in which case the same + syntax and values should be used. + + In general, composition software should always use the "lowest common + denominator" character set possible. For example, if a body contains + only US-ASCII characters, it SHOULD be marked as being in the US- + ASCII character set, not ISO-8859-1, which, like all the ISO-8859 + family of character sets, is a superset of US-ASCII. More generally, + if a widely-used character set is a subset of another character set, + and a body contains only characters in the widely-used subset, it + should be labelled as being in that subset. This will increase the + chances that the recipient will be able to view the resulting entity + correctly. + + + +Freed & Borenstein Standards Track [Page 10] + +RFC 2046 Media Types November 1996 + + +4.1.3. Plain Subtype + + The simplest and most important subtype of "text" is "plain". This + indicates plain text that does not contain any formatting commands or + directives. Plain text is intended to be displayed "as-is", that is, + no interpretation of embedded formatting commands, font attribute + specifications, processing instructions, interpretation directives, + or content markup should be necessary for proper display. The + default media type of "text/plain; charset=us-ascii" for Internet + mail describes existing Internet practice. That is, it is the type + of body defined by RFC 822. + + No other "text" subtype is defined by this document. + +4.1.4. Unrecognized Subtypes + + Unrecognized subtypes of "text" should be treated as subtype "plain" + as long as the MIME implementation knows how to handle the charset. + Unrecognized subtypes which also specify an unrecognized charset + should be treated as "application/octet- stream". + +4.2. Image Media Type + + A media type of "image" indicates that the body contains an image. + The subtype names the specific image format. These names are not + case sensitive. An initial subtype is "jpeg" for the JPEG format + using JFIF encoding [JPEG]. + + The list of "image" subtypes given here is neither exclusive nor + exhaustive, and is expected to grow as more types are registered with + IANA, as described in RFC 2048. + + Unrecognized subtypes of "image" should at a miniumum be treated as + "application/octet-stream". Implementations may optionally elect to + pass subtypes of "image" that they do not specifically recognize to a + secure and robust general-purpose image viewing application, if such + an application is available. + + NOTE: Using of a generic-purpose image viewing application this way + inherits the security problems of the most dangerous type supported + by the application. + +4.3. Audio Media Type + + A media type of "audio" indicates that the body contains audio data. + Although there is not yet a consensus on an "ideal" audio format for + use with computers, there is a pressing need for a format capable of + providing interoperable behavior. + + + +Freed & Borenstein Standards Track [Page 11] + +RFC 2046 Media Types November 1996 + + + The initial subtype of "basic" is specified to meet this requirement + by providing an absolutely minimal lowest common denominator audio + format. It is expected that richer formats for higher quality and/or + lower bandwidth audio will be defined by a later document. + + The content of the "audio/basic" subtype is single channel audio + encoded using 8bit ISDN mu-law [PCM] at a sample rate of 8000 Hz. + + Unrecognized subtypes of "audio" should at a miniumum be treated as + "application/octet-stream". Implementations may optionally elect to + pass subtypes of "audio" that they do not specifically recognize to a + robust general-purpose audio playing application, if such an + application is available. + +4.4. Video Media Type + + A media type of "video" indicates that the body contains a time- + varying-picture image, possibly with color and coordinated sound. + The term 'video' is used in its most generic sense, rather than with + reference to any particular technology or format, and is not meant to + preclude subtypes such as animated drawings encoded compactly. The + subtype "mpeg" refers to video coded according to the MPEG standard + [MPEG]. + + Note that although in general this document strongly discourages the + mixing of multiple media in a single body, it is recognized that many + so-called video formats include a representation for synchronized + audio, and this is explicitly permitted for subtypes of "video". + + Unrecognized subtypes of "video" should at a minumum be treated as + "application/octet-stream". Implementations may optionally elect to + pass subtypes of "video" that they do not specifically recognize to a + robust general-purpose video display application, if such an + application is available. + +4.5. Application Media Type + + The "application" media type is to be used for discrete data which do + not fit in any of the other categories, and particularly for data to + be processed by some type of application program. This is + information which must be processed by an application before it is + viewable or usable by a user. Expected uses for the "application" + media type include file transfer, spreadsheets, data for mail-based + scheduling systems, and languages for "active" (computational) + material. (The latter, in particular, can pose security problems + which must be understood by implementors, and are considered in + detail in the discussion of the "application/PostScript" media type.) + + + + +Freed & Borenstein Standards Track [Page 12] + +RFC 2046 Media Types November 1996 + + + For example, a meeting scheduler might define a standard + representation for information about proposed meeting dates. An + intelligent user agent would use this information to conduct a dialog + with the user, and might then send additional material based on that + dialog. More generally, there have been several "active" messaging + languages developed in which programs in a suitably specialized + language are transported to a remote location and automatically run + in the recipient's environment. + + Such applications may be defined as subtypes of the "application" + media type. This document defines two subtypes: + + octet-stream, and PostScript. + + The subtype of "application" will often be either the name or include + part of the name of the application for which the data are intended. + This does not mean, however, that any application program name may be + used freely as a subtype of "application". + +4.5.1. Octet-Stream Subtype + + The "octet-stream" subtype is used to indicate that a body contains + arbitrary binary data. The set of currently defined parameters is: + + (1) TYPE -- the general type or category of binary data. + This is intended as information for the human recipient + rather than for any automatic processing. + + (2) PADDING -- the number of bits of padding that were + appended to the bit-stream comprising the actual + contents to produce the enclosed 8bit byte-oriented + data. This is useful for enclosing a bit-stream in a + body when the total number of bits is not a multiple of + 8. + + Both of these parameters are optional. + + An additional parameter, "CONVERSIONS", was defined in RFC 1341 but + has since been removed. RFC 1341 also defined the use of a "NAME" + parameter which gave a suggested file name to be used if the data + were to be written to a file. This has been deprecated in + anticipation of a separate Content-Disposition header field, to be + defined in a subsequent RFC. + + The recommended action for an implementation that receives an + "application/octet-stream" entity is to simply offer to put the data + in a file, with any Content-Transfer-Encoding undone, or perhaps to + use it as input to a user-specified process. + + + +Freed & Borenstein Standards Track [Page 13] + +RFC 2046 Media Types November 1996 + + + To reduce the danger of transmitting rogue programs, it is strongly + recommended that implementations NOT implement a path-search + mechanism whereby an arbitrary program named in the Content-Type + parameter (e.g., an "interpreter=" parameter) is found and executed + using the message body as input. + +4.5.2. PostScript Subtype + + A media type of "application/postscript" indicates a PostScript + program. Currently two variants of the PostScript language are + allowed; the original level 1 variant is described in [POSTSCRIPT] + and the more recent level 2 variant is described in [POSTSCRIPT2]. + + PostScript is a registered trademark of Adobe Systems, Inc. Use of + the MIME media type "application/postscript" implies recognition of + that trademark and all the rights it entails. + + The PostScript language definition provides facilities for internal + labelling of the specific language features a given program uses. + This labelling, called the PostScript document structuring + conventions, or DSC, is very general and provides substantially more + information than just the language level. The use of document + structuring conventions, while not required, is strongly recommended + as an aid to interoperability. Documents which lack proper + structuring conventions cannot be tested to see whether or not they + will work in a given environment. As such, some systems may assume + the worst and refuse to process unstructured documents. + + The execution of general-purpose PostScript interpreters entails + serious security risks, and implementors are discouraged from simply + sending PostScript bodies to "off- the-shelf" interpreters. While it + is usually safe to send PostScript to a printer, where the potential + for harm is greatly constrained by typical printer environments, + implementors should consider all of the following before they add + interactive display of PostScript bodies to their MIME readers. + + The remainder of this section outlines some, though probably not all, + of the possible problems with the transport of PostScript entities. + + (1) Dangerous operations in the PostScript language + include, but may not be limited to, the PostScript + operators "deletefile", "renamefile", "filenameforall", + and "file". "File" is only dangerous when applied to + something other than standard input or output. + Implementations may also define additional nonstandard + file operators; these may also pose a threat to + security. "Filenameforall", the wildcard file search + operator, may appear at first glance to be harmless. + + + +Freed & Borenstein Standards Track [Page 14] + +RFC 2046 Media Types November 1996 + + + Note, however, that this operator has the potential to + reveal information about what files the recipient has + access to, and this information may itself be + sensitive. Message senders should avoid the use of + potentially dangerous file operators, since these + operators are quite likely to be unavailable in secure + PostScript implementations. Message receiving and + displaying software should either completely disable + all potentially dangerous file operators or take + special care not to delegate any special authority to + their operation. These operators should be viewed as + being done by an outside agency when interpreting + PostScript documents. Such disabling and/or checking + should be done completely outside of the reach of the + PostScript language itself; care should be taken to + insure that no method exists for re-enabling full- + function versions of these operators. + + (2) The PostScript language provides facilities for exiting + the normal interpreter, or server, loop. Changes made + in this "outer" environment are customarily retained + across documents, and may in some cases be retained + semipermanently in nonvolatile memory. The operators + associated with exiting the interpreter loop have the + potential to interfere with subsequent document + processing. As such, their unrestrained use + constitutes a threat of service denial. PostScript + operators that exit the interpreter loop include, but + may not be limited to, the exitserver and startjob + operators. Message sending software should not + generate PostScript that depends on exiting the + interpreter loop to operate, since the ability to exit + will probably be unavailable in secure PostScript + implementations. Message receiving and displaying + software should completely disable the ability to make + retained changes to the PostScript environment by + eliminating or disabling the "startjob" and + "exitserver" operations. If these operations cannot be + eliminated or completely disabled the password + associated with them should at least be set to a hard- + to-guess value. + + (3) PostScript provides operators for setting system-wide + and device-specific parameters. These parameter + settings may be retained across jobs and may + potentially pose a threat to the correct operation of + the interpreter. The PostScript operators that set + system and device parameters include, but may not be + + + +Freed & Borenstein Standards Track [Page 15] + +RFC 2046 Media Types November 1996 + + + limited to, the "setsystemparams" and "setdevparams" + operators. Message sending software should not + generate PostScript that depends on the setting of + system or device parameters to operate correctly. The + ability to set these parameters will probably be + unavailable in secure PostScript implementations. + Message receiving and displaying software should + disable the ability to change system and device + parameters. If these operators cannot be completely + disabled the password associated with them should at + least be set to a hard-to-guess value. + + (4) Some PostScript implementations provide nonstandard + facilities for the direct loading and execution of + machine code. Such facilities are quite obviously open + to substantial abuse. Message sending software should + not make use of such features. Besides being totally + hardware-specific, they are also likely to be + unavailable in secure implementations of PostScript. + Message receiving and displaying software should not + allow such operators to be used if they exist. + + (5) PostScript is an extensible language, and many, if not + most, implementations of it provide a number of their + own extensions. This document does not deal with such + extensions explicitly since they constitute an unknown + factor. Message sending software should not make use + of nonstandard extensions; they are likely to be + missing from some implementations. Message receiving + and displaying software should make sure that any + nonstandard PostScript operators are secure and don't + present any kind of threat. + + (6) It is possible to write PostScript that consumes huge + amounts of various system resources. It is also + possible to write PostScript programs that loop + indefinitely. Both types of programs have the + potential to cause damage if sent to unsuspecting + recipients. Message-sending software should avoid the + construction and dissemination of such programs, which + is antisocial. Message receiving and displaying + software should provide appropriate mechanisms to abort + processing after a reasonable amount of time has + elapsed. In addition, PostScript interpreters should be + limited to the consumption of only a reasonable amount + of any given system resource. + + + + + +Freed & Borenstein Standards Track [Page 16] + +RFC 2046 Media Types November 1996 + + + (7) It is possible to include raw binary information inside + PostScript in various forms. This is not recommended + for use in Internet mail, both because it is not + supported by all PostScript interpreters and because it + significantly complicates the use of a MIME Content- + Transfer-Encoding. (Without such binary, PostScript + may typically be viewed as line-oriented data. The + treatment of CRLF sequences becomes extremely + problematic if binary and line-oriented data are mixed + in a single Postscript data stream.) + + (8) Finally, bugs may exist in some PostScript interpreters + which could possibly be exploited to gain unauthorized + access to a recipient's system. Apart from noting this + possibility, there is no specific action to take to + prevent this, apart from the timely correction of such + bugs if any are found. + +4.5.3. Other Application Subtypes + + It is expected that many other subtypes of "application" will be + defined in the future. MIME implementations must at a minimum treat + any unrecognized subtypes as being equivalent to "application/octet- + stream". + +5. Composite Media Type Values + + The remaining two of the seven initial Content-Type values refer to + composite entities. Composite entities are handled using MIME + mechanisms -- a MIME processor typically handles the body directly. + +5.1. Multipart Media Type + + In the case of multipart entities, in which one or more different + sets of data are combined in a single body, a "multipart" media type + field must appear in the entity's header. The body must then contain + one or more body parts, each preceded by a boundary delimiter line, + and the last one followed by a closing boundary delimiter line. + After its boundary delimiter line, each body part then consists of a + header area, a blank line, and a body area. Thus a body part is + similar to an RFC 822 message in syntax, but different in meaning. + + A body part is an entity and hence is NOT to be interpreted as + actually being an RFC 822 message. To begin with, NO header fields + are actually required in body parts. A body part that starts with a + blank line, therefore, is allowed and is a body part for which all + default values are to be assumed. In such a case, the absence of a + Content-Type header usually indicates that the corresponding body has + + + +Freed & Borenstein Standards Track [Page 17] + +RFC 2046 Media Types November 1996 + + + a content-type of "text/plain; charset=US-ASCII". + + The only header fields that have defined meaning for body parts are + those the names of which begin with "Content-". All other header + fields may be ignored in body parts. Although they should generally + be retained if at all possible, they may be discarded by gateways if + necessary. Such other fields are permitted to appear in body parts + but must not be depended on. "X-" fields may be created for + experimental or private purposes, with the recognition that the + information they contain may be lost at some gateways. + + NOTE: The distinction between an RFC 822 message and a body part is + subtle, but important. A gateway between Internet and X.400 mail, + for example, must be able to tell the difference between a body part + that contains an image and a body part that contains an encapsulated + message, the body of which is a JPEG image. In order to represent + the latter, the body part must have "Content-Type: message/rfc822", + and its body (after the blank line) must be the encapsulated message, + with its own "Content-Type: image/jpeg" header field. The use of + similar syntax facilitates the conversion of messages to body parts, + and vice versa, but the distinction between the two must be + understood by implementors. (For the special case in which parts + actually are messages, a "digest" subtype is also defined.) + + As stated previously, each body part is preceded by a boundary + delimiter line that contains the boundary delimiter. The boundary + delimiter MUST NOT appear inside any of the encapsulated parts, on a + line by itself or as the prefix of any line. This implies that it is + crucial that the composing agent be able to choose and specify a + unique boundary parameter value that does not contain the boundary + parameter value of an enclosing multipart as a prefix. + + All present and future subtypes of the "multipart" type must use an + identical syntax. Subtypes may differ in their semantics, and may + impose additional restrictions on syntax, but must conform to the + required syntax for the "multipart" type. This requirement ensures + that all conformant user agents will at least be able to recognize + and separate the parts of any multipart entity, even those of an + unrecognized subtype. + + As stated in the definition of the Content-Transfer-Encoding field + [RFC 2045], no encoding other than "7bit", "8bit", or "binary" is + permitted for entities of type "multipart". The "multipart" boundary + delimiters and header fields are always represented as 7bit US-ASCII + in any case (though the header fields may encode non-US-ASCII header + text as per RFC 2047) and data within the body parts can be encoded + on a part-by-part basis, with Content-Transfer-Encoding fields for + each appropriate body part. + + + +Freed & Borenstein Standards Track [Page 18] + +RFC 2046 Media Types November 1996 + + +5.1.1. Common Syntax + + This section defines a common syntax for subtypes of "multipart". + All subtypes of "multipart" must use this syntax. A simple example + of a multipart message also appears in this section. An example of a + more complex multipart message is given in RFC 2049. + + The Content-Type field for multipart entities requires one parameter, + "boundary". The boundary delimiter line is then defined as a line + consisting entirely of two hyphen characters ("-", decimal value 45) + followed by the boundary parameter value from the Content-Type header + field, optional linear whitespace, and a terminating CRLF. + + NOTE: The hyphens are for rough compatibility with the earlier RFC + 934 method of message encapsulation, and for ease of searching for + the boundaries in some implementations. However, it should be noted + that multipart messages are NOT completely compatible with RFC 934 + encapsulations; in particular, they do not obey RFC 934 quoting + conventions for embedded lines that begin with hyphens. This + mechanism was chosen over the RFC 934 mechanism because the latter + causes lines to grow with each level of quoting. The combination of + this growth with the fact that SMTP implementations sometimes wrap + long lines made the RFC 934 mechanism unsuitable for use in the event + that deeply-nested multipart structuring is ever desired. + + WARNING TO IMPLEMENTORS: The grammar for parameters on the Content- + type field is such that it is often necessary to enclose the boundary + parameter values in quotes on the Content-type line. This is not + always necessary, but never hurts. Implementors should be sure to + study the grammar carefully in order to avoid producing invalid + Content-type fields. Thus, a typical "multipart" Content-Type header + field might look like this: + + Content-Type: multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p + + But the following is not valid: + + Content-Type: multipart/mixed; boundary=gc0pJq0M:08jU534c0p + + (because of the colon) and must instead be represented as + + Content-Type: multipart/mixed; boundary="gc0pJq0M:08jU534c0p" + + This Content-Type value indicates that the content consists of one or + more parts, each with a structure that is syntactically identical to + an RFC 822 message, except that the header area is allowed to be + completely empty, and that the parts are each preceded by the line + + + + +Freed & Borenstein Standards Track [Page 19] + +RFC 2046 Media Types November 1996 + + + --gc0pJq0M:08jU534c0p + + The boundary delimiter MUST occur at the beginning of a line, i.e., + following a CRLF, and the initial CRLF is considered to be attached + to the boundary delimiter line rather than part of the preceding + part. The boundary may be followed by zero or more characters of + linear whitespace. It is then terminated by either another CRLF and + the header fields for the next part, or by two CRLFs, in which case + there are no header fields for the next part. If no Content-Type + field is present it is assumed to be "message/rfc822" in a + "multipart/digest" and "text/plain" otherwise. + + NOTE: The CRLF preceding the boundary delimiter line is conceptually + attached to the boundary so that it is possible to have a part that + does not end with a CRLF (line break). Body parts that must be + considered to end with line breaks, therefore, must have two CRLFs + preceding the boundary delimiter line, the first of which is part of + the preceding body part, and the second of which is part of the + encapsulation boundary. + + Boundary delimiters must not appear within the encapsulated material, + and must be no longer than 70 characters, not counting the two + leading hyphens. + + The boundary delimiter line following the last body part is a + distinguished delimiter that indicates that no further body parts + will follow. Such a delimiter line is identical to the previous + delimiter lines, with the addition of two more hyphens after the + boundary parameter value. + + --gc0pJq0M:08jU534c0p-- + + NOTE TO IMPLEMENTORS: Boundary string comparisons must compare the + boundary value with the beginning of each candidate line. An exact + match of the entire candidate line is not required; it is sufficient + that the boundary appear in its entirety following the CRLF. + + There appears to be room for additional information prior to the + first boundary delimiter line and following the final boundary + delimiter line. These areas should generally be left blank, and + implementations must ignore anything that appears before the first + boundary delimiter line or after the last one. + + NOTE: These "preamble" and "epilogue" areas are generally not used + because of the lack of proper typing of these parts and the lack of + clear semantics for handling these areas at gateways, particularly + X.400 gateways. However, rather than leaving the preamble area + blank, many MIME implementations have found this to be a convenient + + + +Freed & Borenstein Standards Track [Page 20] + +RFC 2046 Media Types November 1996 + + + place to insert an explanatory note for recipients who read the + message with pre-MIME software, since such notes will be ignored by + MIME-compliant software. + + NOTE: Because boundary delimiters must not appear in the body parts + being encapsulated, a user agent must exercise care to choose a + unique boundary parameter value. The boundary parameter value in the + example above could have been the result of an algorithm designed to + produce boundary delimiters with a very low probability of already + existing in the data to be encapsulated without having to prescan the + data. Alternate algorithms might result in more "readable" boundary + delimiters for a recipient with an old user agent, but would require + more attention to the possibility that the boundary delimiter might + appear at the beginning of some line in the encapsulated part. The + simplest boundary delimiter line possible is something like "---", + with a closing boundary delimiter line of "-----". + + As a very simple example, the following multipart message has two + parts, both of them plain text, one of them explicitly typed and one + of them implicitly typed: + + From: Nathaniel Borenstein + To: Ned Freed + Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST) + Subject: Sample message + MIME-Version: 1.0 + Content-type: multipart/mixed; boundary="simple boundary" + + This is the preamble. It is to be ignored, though it + is a handy place for composition agents to include an + explanatory note to non-MIME conformant readers. + + --simple boundary + + This is implicitly typed plain US-ASCII text. + It does NOT end with a linebreak. + --simple boundary + Content-type: text/plain; charset=us-ascii + + This is explicitly typed plain US-ASCII text. + It DOES end with a linebreak. + + --simple boundary-- + + This is the epilogue. It is also to be ignored. + + + + + + +Freed & Borenstein Standards Track [Page 21] + +RFC 2046 Media Types November 1996 + + + The use of a media type of "multipart" in a body part within another + "multipart" entity is explicitly allowed. In such cases, for obvious + reasons, care must be taken to ensure that each nested "multipart" + entity uses a different boundary delimiter. See RFC 2049 for an + example of nested "multipart" entities. + + The use of the "multipart" media type with only a single body part + may be useful in certain contexts, and is explicitly permitted. + + NOTE: Experience has shown that a "multipart" media type with a + single body part is useful for sending non-text media types. It has + the advantage of providing the preamble as a place to include + decoding instructions. In addition, a number of SMTP gateways move + or remove the MIME headers, and a clever MIME decoder can take a good + guess at multipart boundaries even in the absence of the Content-Type + header and thereby successfully decode the message. + + The only mandatory global parameter for the "multipart" media type is + the boundary parameter, which consists of 1 to 70 characters from a + set of characters known to be very robust through mail gateways, and + NOT ending with white space. (If a boundary delimiter line appears to + end with white space, the white space must be presumed to have been + added by a gateway, and must be deleted.) It is formally specified + by the following BNF: + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + "+" / "_" / "," / "-" / "." / + "/" / ":" / "=" / "?" + + Overall, the body of a "multipart" entity may be specified as + follows: + + dash-boundary := "--" boundary + ; boundary taken from the value of + ; boundary parameter of the + ; Content-Type field. + + multipart-body := [preamble CRLF] + dash-boundary transport-padding CRLF + body-part *encapsulation + close-delimiter transport-padding + [CRLF epilogue] + + + + + +Freed & Borenstein Standards Track [Page 22] + +RFC 2046 Media Types November 1996 + + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + encapsulation := delimiter transport-padding + CRLF body-part + + delimiter := CRLF dash-boundary + + close-delimiter := delimiter "--" + + preamble := discard-text + + epilogue := discard-text + + discard-text := *(*text CRLF) *text + ; May be ignored or discarded. + + body-part := MIME-part-headers [CRLF *OCTET] + ; Lines in a body-part must not start + ; with the specified dash-boundary and + ; the delimiter must not appear anywhere + ; in the body part. Note that the + ; semantics of a body-part differ from + ; the semantics of a message, as + ; described in the text. + + OCTET := + + IMPORTANT: The free insertion of linear-white-space and RFC 822 + comments between the elements shown in this BNF is NOT allowed since + this BNF does not specify a structured header field. + + NOTE: In certain transport enclaves, RFC 822 restrictions such as + the one that limits bodies to printable US-ASCII characters may not + be in force. (That is, the transport domains may exist that resemble + standard Internet mail transport as specified in RFC 821 and assumed + by RFC 822, but without certain restrictions.) The relaxation of + these restrictions should be construed as locally extending the + definition of bodies, for example to include octets outside of the + US-ASCII range, as long as these extensions are supported by the + transport and adequately documented in the Content- Transfer-Encoding + header field. However, in no event are headers (either message + headers or body part headers) allowed to contain anything other than + US-ASCII characters. + + + +Freed & Borenstein Standards Track [Page 23] + +RFC 2046 Media Types November 1996 + + + NOTE: Conspicuously missing from the "multipart" type is a notion of + structured, related body parts. It is recommended that those wishing + to provide more structured or integrated multipart messaging + facilities should define subtypes of multipart that are syntactically + identical but define relationships between the various parts. For + example, subtypes of multipart could be defined that include a + distinguished part which in turn is used to specify the relationships + between the other parts, probably referring to them by their + Content-ID field. Old implementations will not recognize the new + subtype if this approach is used, but will treat it as + multipart/mixed and will thus be able to show the user the parts that + are recognized. + +5.1.2. Handling Nested Messages and Multiparts + + The "message/rfc822" subtype defined in a subsequent section of this + document has no terminating condition other than running out of data. + Similarly, an improperly truncated "multipart" entity may not have + any terminating boundary marker, and can turn up operationally due to + mail system malfunctions. + + It is essential that such entities be handled correctly when they are + themselves imbedded inside of another "multipart" structure. MIME + implementations are therefore required to recognize outer level + boundary markers at ANY level of inner nesting. It is not sufficient + to only check for the next expected marker or other terminating + condition. + +5.1.3. Mixed Subtype + + The "mixed" subtype of "multipart" is intended for use when the body + parts are independent and need to be bundled in a particular order. + Any "multipart" subtypes that an implementation does not recognize + must be treated as being of subtype "mixed". + +5.1.4. Alternative Subtype + + The "multipart/alternative" type is syntactically identical to + "multipart/mixed", but the semantics are different. In particular, + each of the body parts is an "alternative" version of the same + information. + + Systems should recognize that the content of the various parts are + interchangeable. Systems should choose the "best" type based on the + local environment and references, in some cases even through user + interaction. As with "multipart/mixed", the order of body parts is + significant. In this case, the alternatives appear in an order of + increasing faithfulness to the original content. In general, the + + + +Freed & Borenstein Standards Track [Page 24] + +RFC 2046 Media Types November 1996 + + + best choice is the LAST part of a type supported by the recipient + system's local environment. + + "Multipart/alternative" may be used, for example, to send a message + in a fancy text format in such a way that it can easily be displayed + anywhere: + + From: Nathaniel Borenstein + To: Ned Freed + Date: Mon, 22 Mar 1993 09:41:09 -0800 (PST) + Subject: Formatted text mail + MIME-Version: 1.0 + Content-Type: multipart/alternative; boundary=boundary42 + + --boundary42 + Content-Type: text/plain; charset=us-ascii + + ... plain text version of message goes here ... + + --boundary42 + Content-Type: text/enriched + + ... RFC 1896 text/enriched version of same message + goes here ... + + --boundary42 + Content-Type: application/x-whatever + + ... fanciest version of same message goes here ... + + --boundary42-- + + In this example, users whose mail systems understood the + "application/x-whatever" format would see only the fancy version, + while other users would see only the enriched or plain text version, + depending on the capabilities of their system. + + In general, user agents that compose "multipart/alternative" entities + must place the body parts in increasing order of preference, that is, + with the preferred format last. For fancy text, the sending user + agent should put the plainest format first and the richest format + last. Receiving user agents should pick and display the last format + they are capable of displaying. In the case where one of the + alternatives is itself of type "multipart" and contains unrecognized + sub-parts, the user agent may choose either to show that alternative, + an earlier alternative, or both. + + + + + +Freed & Borenstein Standards Track [Page 25] + +RFC 2046 Media Types November 1996 + + + NOTE: From an implementor's perspective, it might seem more sensible + to reverse this ordering, and have the plainest alternative last. + However, placing the plainest alternative first is the friendliest + possible option when "multipart/alternative" entities are viewed + using a non-MIME-conformant viewer. While this approach does impose + some burden on conformant MIME viewers, interoperability with older + mail readers was deemed to be more important in this case. + + It may be the case that some user agents, if they can recognize more + than one of the formats, will prefer to offer the user the choice of + which format to view. This makes sense, for example, if a message + includes both a nicely- formatted image version and an easily-edited + text version. What is most critical, however, is that the user not + automatically be shown multiple versions of the same data. Either + the user should be shown the last recognized version or should be + given the choice. + + THE SEMANTICS OF CONTENT-ID IN MULTIPART/ALTERNATIVE: Each part of a + "multipart/alternative" entity represents the same data, but the + mappings between the two are not necessarily without information + loss. For example, information is lost when translating ODA to + PostScript or plain text. It is recommended that each part should + have a different Content-ID value in the case where the information + content of the two parts is not identical. And when the information + content is identical -- for example, where several parts of type + "message/external-body" specify alternate ways to access the + identical data -- the same Content-ID field value should be used, to + optimize any caching mechanisms that might be present on the + recipient's end. However, the Content-ID values used by the parts + should NOT be the same Content-ID value that describes the + "multipart/alternative" as a whole, if there is any such Content-ID + field. That is, one Content-ID value will refer to the + "multipart/alternative" entity, while one or more other Content-ID + values will refer to the parts inside it. + +5.1.5. Digest Subtype + + This document defines a "digest" subtype of the "multipart" Content- + Type. This type is syntactically identical to "multipart/mixed", but + the semantics are different. In particular, in a digest, the default + Content-Type value for a body part is changed from "text/plain" to + "message/rfc822". This is done to allow a more readable digest + format that is largely compatible (except for the quoting convention) + with RFC 934. + + Note: Though it is possible to specify a Content-Type value for a + body part in a digest which is other than "message/rfc822", such as a + "text/plain" part containing a description of the material in the + + + +Freed & Borenstein Standards Track [Page 26] + +RFC 2046 Media Types November 1996 + + + digest, actually doing so is undesireble. The "multipart/digest" + Content-Type is intended to be used to send collections of messages. + If a "text/plain" part is needed, it should be included as a seperate + part of a "multipart/mixed" message. + + A digest in this format might, then, look something like this: + + From: Moderator-Address + To: Recipient-List + Date: Mon, 22 Mar 1994 13:34:51 +0000 + Subject: Internet Digest, volume 42 + MIME-Version: 1.0 + Content-Type: multipart/mixed; + boundary="---- main boundary ----" + + ------ main boundary ---- + + ...Introductory text or table of contents... + + ------ main boundary ---- + Content-Type: multipart/digest; + boundary="---- next message ----" + + ------ next message ---- + + From: someone-else + Date: Fri, 26 Mar 1993 11:13:32 +0200 + Subject: my opinion + + ...body goes here ... + + ------ next message ---- + + From: someone-else-again + Date: Fri, 26 Mar 1993 10:07:13 -0500 + Subject: my different opinion + + ... another body goes here ... + + ------ next message ------ + + ------ main boundary ------ + +5.1.6. Parallel Subtype + + This document defines a "parallel" subtype of the "multipart" + Content-Type. This type is syntactically identical to + "multipart/mixed", but the semantics are different. In particular, + + + +Freed & Borenstein Standards Track [Page 27] + +RFC 2046 Media Types November 1996 + + + in a parallel entity, the order of body parts is not significant. + + A common presentation of this type is to display all of the parts + simultaneously on hardware and software that are capable of doing so. + However, composing agents should be aware that many mail readers will + lack this capability and will show the parts serially in any event. + +5.1.7. Other Multipart Subtypes + + Other "multipart" subtypes are expected in the future. MIME + implementations must in general treat unrecognized subtypes of + "multipart" as being equivalent to "multipart/mixed". + +5.2. Message Media Type + + It is frequently desirable, in sending mail, to encapsulate another + mail message. A special media type, "message", is defined to + facilitate this. In particular, the "rfc822" subtype of "message" is + used to encapsulate RFC 822 messages. + + NOTE: It has been suggested that subtypes of "message" might be + defined for forwarded or rejected messages. However, forwarded and + rejected messages can be handled as multipart messages in which the + first part contains any control or descriptive information, and a + second part, of type "message/rfc822", is the forwarded or rejected + message. Composing rejection and forwarding messages in this manner + will preserve the type information on the original message and allow + it to be correctly presented to the recipient, and hence is strongly + encouraged. + + Subtypes of "message" often impose restrictions on what encodings are + allowed. These restrictions are described in conjunction with each + specific subtype. + + Mail gateways, relays, and other mail handling agents are commonly + known to alter the top-level header of an RFC 822 message. In + particular, they frequently add, remove, or reorder header fields. + These operations are explicitly forbidden for the encapsulated + headers embedded in the bodies of messages of type "message." + +5.2.1. RFC822 Subtype + + A media type of "message/rfc822" indicates that the body contains an + encapsulated message, with the syntax of an RFC 822 message. + However, unlike top-level RFC 822 messages, the restriction that each + "message/rfc822" body must include a "From", "Date", and at least one + destination header is removed and replaced with the requirement that + at least one of "From", "Subject", or "Date" must be present. + + + +Freed & Borenstein Standards Track [Page 28] + +RFC 2046 Media Types November 1996 + + + It should be noted that, despite the use of the numbers "822", a + "message/rfc822" entity isn't restricted to material in strict + conformance to RFC822, nor are the semantics of "message/rfc822" + objects restricted to the semantics defined in RFC822. More + specifically, a "message/rfc822" message could well be a News article + or a MIME message. + + No encoding other than "7bit", "8bit", or "binary" is permitted for + the body of a "message/rfc822" entity. The message header fields are + always US-ASCII in any case, and data within the body can still be + encoded, in which case the Content-Transfer-Encoding header field in + the encapsulated message will reflect this. Non-US-ASCII text in the + headers of an encapsulated message can be specified using the + mechanisms described in RFC 2047. + +5.2.2. Partial Subtype + + The "partial" subtype is defined to allow large entities to be + delivered as several separate pieces of mail and automatically + reassembled by a receiving user agent. (The concept is similar to IP + fragmentation and reassembly in the basic Internet Protocols.) This + mechanism can be used when intermediate transport agents limit the + size of individual messages that can be sent. The media type + "message/partial" thus indicates that the body contains a fragment of + a larger entity. + + Because data of type "message" may never be encoded in base64 or + quoted-printable, a problem might arise if "message/partial" entities + are constructed in an environment that supports binary or 8bit + transport. The problem is that the binary data would be split into + multiple "message/partial" messages, each of them requiring binary + transport. If such messages were encountered at a gateway into a + 7bit transport environment, there would be no way to properly encode + them for the 7bit world, aside from waiting for all of the fragments, + reassembling the inner message, and then encoding the reassembled + data in base64 or quoted-printable. Since it is possible that + different fragments might go through different gateways, even this is + not an acceptable solution. For this reason, it is specified that + entities of type "message/partial" must always have a content- + transfer-encoding of 7bit (the default). In particular, even in + environments that support binary or 8bit transport, the use of a + content- transfer-encoding of "8bit" or "binary" is explicitly + prohibited for MIME entities of type "message/partial". This in turn + implies that the inner message must not use "8bit" or "binary" + encoding. + + + + + + +Freed & Borenstein Standards Track [Page 29] + +RFC 2046 Media Types November 1996 + + + Because some message transfer agents may choose to automatically + fragment large messages, and because such agents may use very + different fragmentation thresholds, it is possible that the pieces of + a partial message, upon reassembly, may prove themselves to comprise + a partial message. This is explicitly permitted. + + Three parameters must be specified in the Content-Type field of type + "message/partial": The first, "id", is a unique identifier, as close + to a world-unique identifier as possible, to be used to match the + fragments together. (In general, the identifier is essentially a + message-id; if placed in double quotes, it can be ANY message-id, in + accordance with the BNF for "parameter" given in RFC 2045.) The + second, "number", an integer, is the fragment number, which indicates + where this fragment fits into the sequence of fragments. The third, + "total", another integer, is the total number of fragments. This + third subfield is required on the final fragment, and is optional + (though encouraged) on the earlier fragments. Note also that these + parameters may be given in any order. + + Thus, the second piece of a 3-piece message may have either of the + following header fields: + + Content-Type: Message/Partial; number=2; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Content-Type: Message/Partial; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com"; + number=2 + + But the third piece MUST specify the total number of fragments: + + Content-Type: Message/Partial; number=3; total=3; + id="oc=jpbe0M2Yt4s@thumper.bellcore.com" + + Note that fragment numbering begins with 1, not 0. + + When the fragments of an entity broken up in this manner are put + together, the result is always a complete MIME entity, which may have + its own Content-Type header field, and thus may contain any other + data type. + +5.2.2.1. Message Fragmentation and Reassembly + + The semantics of a reassembled partial message must be those of the + "inner" message, rather than of a message containing the inner + message. This makes it possible, for example, to send a large audio + message as several partial messages, and still have it appear to the + recipient as a simple audio message rather than as an encapsulated + + + +Freed & Borenstein Standards Track [Page 30] + +RFC 2046 Media Types November 1996 + + + message containing an audio message. That is, the encapsulation of + the message is considered to be "transparent". + + When generating and reassembling the pieces of a "message/partial" + message, the headers of the encapsulated message must be merged with + the headers of the enclosing entities. In this process the following + rules must be observed: + + (1) Fragmentation agents must split messages at line + boundaries only. This restriction is imposed because + splits at points other than the ends of lines in turn + depends on message transports being able to preserve + the semantics of messages that don't end with a CRLF + sequence. Many transports are incapable of preserving + such semantics. + + (2) All of the header fields from the initial enclosing + message, except those that start with "Content-" and + the specific header fields "Subject", "Message-ID", + "Encrypted", and "MIME-Version", must be copied, in + order, to the new message. + + (3) The header fields in the enclosed message which start + with "Content-", plus the "Subject", "Message-ID", + "Encrypted", and "MIME-Version" fields, must be + appended, in order, to the header fields of the new + message. Any header fields in the enclosed message + which do not start with "Content-" (except for the + "Subject", "Message-ID", "Encrypted", and "MIME- + Version" fields) will be ignored and dropped. + + (4) All of the header fields from the second and any + subsequent enclosing messages are discarded by the + reassembly process. + +5.2.2.2. Fragmentation and Reassembly Example + + If an audio message is broken into two pieces, the first piece might + look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Date: Fri, 26 Mar 1993 12:59:38 -0500 (EST) + Subject: Audio mail (part 1 of 2) + Message-ID: + MIME-Version: 1.0 + Content-type: message/partial; id="ABC@host.com"; + + + +Freed & Borenstein Standards Track [Page 31] + +RFC 2046 Media Types November 1996 + + + number=1; total=2 + + X-Weird-Header-1: Bar + X-Weird-Header-2: Hello + Message-ID: + Subject: Audio mail + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here ... + + and the second half might look something like this: + + From: Bill@host.com + To: joe@otherhost.com + Date: Fri, 26 Mar 1993 12:59:38 -0500 (EST) + Subject: Audio mail (part 2 of 2) + MIME-Version: 1.0 + Message-ID: + Content-type: message/partial; + id="ABC@host.com"; number=2; total=2 + + ... second half of encoded audio data goes here ... + + Then, when the fragmented message is reassembled, the resulting + message to be displayed to the user should look something like this: + + X-Weird-Header-1: Foo + From: Bill@host.com + To: joe@otherhost.com + Date: Fri, 26 Mar 1993 12:59:38 -0500 (EST) + Subject: Audio mail + Message-ID: + MIME-Version: 1.0 + Content-type: audio/basic + Content-transfer-encoding: base64 + + ... first half of encoded audio data goes here ... + ... second half of encoded audio data goes here ... + + The inclusion of a "References" field in the headers of the second + and subsequent pieces of a fragmented message that references the + Message-Id on the previous piece may be of benefit to mail readers + that understand and track references. However, the generation of + such "References" fields is entirely optional. + + + + + +Freed & Borenstein Standards Track [Page 32] + +RFC 2046 Media Types November 1996 + + + Finally, it should be noted that the "Encrypted" header field has + been made obsolete by Privacy Enhanced Messaging (PEM) [RFC-1421, + RFC-1422, RFC-1423, RFC-1424], but the rules above are nevertheless + believed to describe the correct way to treat it if it is encountered + in the context of conversion to and from "message/partial" fragments. + +5.2.3. External-Body Subtype + + The external-body subtype indicates that the actual body data are not + included, but merely referenced. In this case, the parameters + describe a mechanism for accessing the external data. + + When a MIME entity is of type "message/external-body", it consists of + a header, two consecutive CRLFs, and the message header for the + encapsulated message. If another pair of consecutive CRLFs appears, + this of course ends the message header for the encapsulated message. + However, since the encapsulated message's body is itself external, it + does NOT appear in the area that follows. For example, consider the + following message: + + Content-type: message/external-body; + access-type=local-file; + name="/u/nsb/Me.jpeg" + + Content-type: image/jpeg + Content-ID: + Content-Transfer-Encoding: binary + + THIS IS NOT REALLY THE BODY! + + The area at the end, which might be called the "phantom body", is + ignored for most external-body messages. However, it may be used to + contain auxiliary information for some such messages, as indeed it is + when the access-type is "mail- server". The only access-type defined + in this document that uses the phantom body is "mail-server", but + other access-types may be defined in the future in other + specifications that use this area. + + The encapsulated headers in ALL "message/external-body" entities MUST + include a Content-ID header field to give a unique identifier by + which to reference the data. This identifier may be used for caching + mechanisms, and for recognizing the receipt of the data when the + access-type is "mail-server". + + Note that, as specified here, the tokens that describe external-body + data, such as file names and mail server commands, are required to be + in the US-ASCII character set. + + + + +Freed & Borenstein Standards Track [Page 33] + +RFC 2046 Media Types November 1996 + + + If this proves problematic in practice, a new mechanism may be + required as a future extension to MIME, either as newly defined + access-types for "message/external-body" or by some other mechanism. + + As with "message/partial", MIME entities of type "message/external- + body" MUST have a content-transfer-encoding of 7bit (the default). + In particular, even in environments that support binary or 8bit + transport, the use of a content- transfer-encoding of "8bit" or + "binary" is explicitly prohibited for entities of type + "message/external-body". + +5.2.3.1. General External-Body Parameters + + The parameters that may be used with any "message/external- body" + are: + + (1) ACCESS-TYPE -- A word indicating the supported access + mechanism by which the file or data may be obtained. + This word is not case sensitive. Values include, but + are not limited to, "FTP", "ANON-FTP", "TFTP", "LOCAL- + FILE", and "MAIL-SERVER". Future values, except for + experimental values beginning with "X-", must be + registered with IANA, as described in RFC 2048. + This parameter is unconditionally mandatory and MUST be + present on EVERY "message/external-body". + + (2) EXPIRATION -- The date (in the RFC 822 "date-time" + syntax, as extended by RFC 1123 to permit 4 digits in + the year field) after which the existence of the + external data is not guaranteed. This parameter may be + used with ANY access-type and is ALWAYS optional. + + (3) SIZE -- The size (in octets) of the data. The intent + of this parameter is to help the recipient decide + whether or not to expend the necessary resources to + retrieve the external data. Note that this describes + the size of the data in its canonical form, that is, + before any Content-Transfer-Encoding has been applied + or after the data have been decoded. This parameter + may be used with ANY access-type and is ALWAYS + optional. + + (4) PERMISSION -- A case-insensitive field that indicates + whether or not it is expected that clients might also + attempt to overwrite the data. By default, or if + permission is "read", the assumption is that they are + not, and that if the data is retrieved once, it is + never needed again. If PERMISSION is "read-write", + + + +Freed & Borenstein Standards Track [Page 34] + +RFC 2046 Media Types November 1996 + + + this assumption is invalid, and any local copy must be + considered no more than a cache. "Read" and "Read- + write" are the only defined values of permission. This + parameter may be used with ANY access-type and is + ALWAYS optional. + + The precise semantics of the access-types defined here are described + in the sections that follow. + +5.2.3.2. The 'ftp' and 'tftp' Access-Types + + An access-type of FTP or TFTP indicates that the message body is + accessible as a file using the FTP [RFC-959] or TFTP [RFC- 783] + protocols, respectively. For these access-types, the following + additional parameters are mandatory: + + (1) NAME -- The name of the file that contains the actual + body data. + + (2) SITE -- A machine from which the file may be obtained, + using the given protocol. This must be a fully + qualified domain name, not a nickname. + + (3) Before any data are retrieved, using FTP, the user will + generally need to be asked to provide a login id and a + password for the machine named by the site parameter. + For security reasons, such an id and password are not + specified as content-type parameters, but must be + obtained from the user. + + In addition, the following parameters are optional: + + (1) DIRECTORY -- A directory from which the data named by + NAME should be retrieved. + + (2) MODE -- A case-insensitive string indicating the mode + to be used when retrieving the information. The valid + values for access-type "TFTP" are "NETASCII", "OCTET", + and "MAIL", as specified by the TFTP protocol [RFC- + 783]. The valid values for access-type "FTP" are + "ASCII", "EBCDIC", "IMAGE", and "LOCALn" where "n" is a + decimal integer, typically 8. These correspond to the + representation types "A" "E" "I" and "L n" as specified + by the FTP protocol [RFC-959]. Note that "BINARY" and + "TENEX" are not valid values for MODE and that "OCTET" + or "IMAGE" or "LOCAL8" should be used instead. IF MODE + is not specified, the default value is "NETASCII" for + TFTP and "ASCII" otherwise. + + + +Freed & Borenstein Standards Track [Page 35] + +RFC 2046 Media Types November 1996 + + +5.2.3.3. The 'anon-ftp' Access-Type + + The "anon-ftp" access-type is identical to the "ftp" access type, + except that the user need not be asked to provide a name and password + for the specified site. Instead, the ftp protocol will be used with + login "anonymous" and a password that corresponds to the user's mail + address. + +5.2.3.4. The 'local-file' Access-Type + + An access-type of "local-file" indicates that the actual body is + accessible as a file on the local machine. Two additional parameters + are defined for this access type: + + (1) NAME -- The name of the file that contains the actual + body data. This parameter is mandatory for the + "local-file" access-type. + + (2) SITE -- A domain specifier for a machine or set of + machines that are known to have access to the data + file. This optional parameter is used to describe the + locality of reference for the data, that is, the site + or sites at which the file is expected to be visible. + Asterisks may be used for wildcard matching to a part + of a domain name, such as "*.bellcore.com", to indicate + a set of machines on which the data should be directly + visible, while a single asterisk may be used to + indicate a file that is expected to be universally + available, e.g., via a global file system. + +5.2.3.5. The 'mail-server' Access-Type + + The "mail-server" access-type indicates that the actual body is + available from a mail server. Two additional parameters are defined + for this access-type: + + (1) SERVER -- The addr-spec of the mail server from which + the actual body data can be obtained. This parameter + is mandatory for the "mail-server" access-type. + + (2) SUBJECT -- The subject that is to be used in the mail + that is sent to obtain the data. Note that keying mail + servers on Subject lines is NOT recommended, but such + mail servers are known to exist. This is an optional + parameter. + + + + + + +Freed & Borenstein Standards Track [Page 36] + +RFC 2046 Media Types November 1996 + + + Because mail servers accept a variety of syntaxes, some of which is + multiline, the full command to be sent to a mail server is not + included as a parameter in the content-type header field. Instead, + it is provided as the "phantom body" when the media type is + "message/external-body" and the access-type is mail-server. + + Note that MIME does not define a mail server syntax. Rather, it + allows the inclusion of arbitrary mail server commands in the phantom + body. Implementations must include the phantom body in the body of + the message it sends to the mail server address to retrieve the + relevant data. + + Unlike other access-types, mail-server access is asynchronous and + will happen at an unpredictable time in the future. For this reason, + it is important that there be a mechanism by which the returned data + can be matched up with the original "message/external-body" entity. + MIME mail servers must use the same Content-ID field on the returned + message that was used in the original "message/external-body" + entities, to facilitate such matching. + +5.2.3.6. External-Body Security Issues + + "Message/external-body" entities give rise to two important security + issues: + + (1) Accessing data via a "message/external-body" reference + effectively results in the message recipient performing + an operation that was specified by the message + originator. It is therefore possible for the message + originator to trick a recipient into doing something + they would not have done otherwise. For example, an + originator could specify a action that attempts + retrieval of material that the recipient is not + authorized to obtain, causing the recipient to + unwittingly violate some security policy. For this + reason, user agents capable of resolving external + references must always take steps to describe the + action they are to take to the recipient and ask for + explicit permisssion prior to performing it. + + The 'mail-server' access-type is particularly + vulnerable, in that it causes the recipient to send a + new message whose contents are specified by the + original message's originator. Given the potential for + abuse, any such request messages that are constructed + should contain a clear indication that they were + generated automatically (e.g. in a Comments: header + field) in an attempt to resolve a MIME + + + +Freed & Borenstein Standards Track [Page 37] + +RFC 2046 Media Types November 1996 + + + "message/external-body" reference. + + (2) MIME will sometimes be used in environments that + provide some guarantee of message integrity and + authenticity. If present, such guarantees may apply + only to the actual direct content of messages -- they + may or may not apply to data accessed through MIME's + "message/external-body" mechanism. In particular, it + may be possible to subvert certain access mechanisms + even when the messaging system itself is secure. + + It should be noted that this problem exists either with + or without the availabilty of MIME mechanisms. A + casual reference to an FTP site containing a document + in the text of a secure message brings up similar + issues -- the only difference is that MIME provides for + automatic retrieval of such material, and users may + place unwarranted trust is such automatic retrieval + mechanisms. + +5.2.3.7. Examples and Further Explanations + + When the external-body mechanism is used in conjunction with the + "multipart/alternative" media type it extends the functionality of + "multipart/alternative" to include the case where the same entity is + provided in the same format but via different accces mechanisms. + When this is done the originator of the message must order the parts + first in terms of preferred formats and then by preferred access + mechanisms. The recipient's viewer should then evaluate the list + both in terms of format and access mechanisms. + + With the emerging possibility of very wide-area file systems, it + becomes very hard to know in advance the set of machines where a file + will and will not be accessible directly from the file system. + Therefore it may make sense to provide both a file name, to be tried + directly, and the name of one or more sites from which the file is + known to be accessible. An implementation can try to retrieve remote + files using FTP or any other protocol, using anonymous file retrieval + or prompting the user for the necessary name and password. If an + external body is accessible via multiple mechanisms, the sender may + include multiple entities of type "message/external-body" within the + body parts of an enclosing "multipart/alternative" entity. + + However, the external-body mechanism is not intended to be limited to + file retrieval, as shown by the mail-server access-type. Beyond + this, one can imagine, for example, using a video server for external + references to video clips. + + + + +Freed & Borenstein Standards Track [Page 38] + +RFC 2046 Media Types November 1996 + + + The embedded message header fields which appear in the body of the + "message/external-body" data must be used to declare the media type + of the external body if it is anything other than plain US-ASCII + text, since the external body does not have a header section to + declare its type. Similarly, any Content-transfer-encoding other + than "7bit" must also be declared here. Thus a complete + "message/external-body" message, referring to an object in PostScript + format, might look like this: + + From: Whomever + To: Someone + Date: Whenever + Subject: whatever + MIME-Version: 1.0 + Message-ID: + Content-Type: multipart/alternative; boundary=42 + Content-ID: + + --42 + Content-Type: message/external-body; name="BodyFormats.ps"; + site="thumper.bellcore.com"; mode="image"; + access-type=ANON-FTP; directory="pub"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; access-type=local-file; + name="/u/nsb/writing/rfcs/RFC-MIME.ps"; + site="thumper.bellcore.com"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + --42 + Content-Type: message/external-body; + access-type=mail-server + server="listserv@bogus.bitnet"; + expiration="Fri, 14 Jun 1991 19:13:14 -0400 (EDT)" + + Content-type: application/postscript + Content-ID: + + get RFC-MIME.DOC + + --42-- + + + +Freed & Borenstein Standards Track [Page 39] + +RFC 2046 Media Types November 1996 + + + Note that in the above examples, the default Content-transfer- + encoding of "7bit" is assumed for the external postscript data. + + Like the "message/partial" type, the "message/external-body" media + type is intended to be transparent, that is, to convey the data type + in the external body rather than to convey a message with a body of + that type. Thus the headers on the outer and inner parts must be + merged using the same rules as for "message/partial". In particular, + this means that the Content-type and Subject fields are overridden, + but the From field is preserved. + + Note that since the external bodies are not transported along with + the external body reference, they need not conform to transport + limitations that apply to the reference itself. In particular, + Internet mail transports may impose 7bit and line length limits, but + these do not automatically apply to binary external body references. + Thus a Content-Transfer-Encoding is not generally necessary, though + it is permitted. + + Note that the body of a message of type "message/external-body" is + governed by the basic syntax for an RFC 822 message. In particular, + anything before the first consecutive pair of CRLFs is header + information, while anything after it is body information, which is + ignored for most access-types. + +5.2.4. Other Message Subtypes + + MIME implementations must in general treat unrecognized subtypes of + "message" as being equivalent to "application/octet-stream". + + Future subtypes of "message" intended for use with email should be + restricted to "7bit" encoding. A type other than "message" should be + used if restriction to "7bit" is not possible. + +6. Experimental Media Type Values + + A media type value beginning with the characters "X-" is a private + value, to be used by consenting systems by mutual agreement. Any + format without a rigorous and public definition must be named with an + "X-" prefix, and publicly specified values shall never begin with + "X-". (Older versions of the widely used Andrew system use the "X- + BE2" name, so new systems should probably choose a different name.) + + In general, the use of "X-" top-level types is strongly discouraged. + Implementors should invent subtypes of the existing types whenever + possible. In many cases, a subtype of "application" will be more + appropriate than a new top-level type. + + + + +Freed & Borenstein Standards Track [Page 40] + +RFC 2046 Media Types November 1996 + + +7. Summary + + The five discrete media types provide provide a standardized + mechanism for tagging entities as "audio", "image", or several other + kinds of data. The composite "multipart" and "message" media types + allow mixing and hierarchical structuring of entities of different + types in a single message. A distinguished parameter syntax allows + further specification of data format details, particularly the + specification of alternate character sets. Additional optional + header fields provide mechanisms for certain extensions deemed + desirable by many implementors. Finally, a number of useful media + types are defined for general use by consenting user agents, notably + "message/partial" and "message/external-body". + +9. Security Considerations + + Security issues are discussed in the context of the + "application/postscript" type, the "message/external-body" type, and + in RFC 2048. Implementors should pay special attention to the + security implications of any media types that can cause the remote + execution of any actions in the recipient's environment. In such + cases, the discussion of the "application/postscript" type may serve + as a model for considering other media types with remote execution + capabilities. + + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 41] + +RFC 2046 Media Types November 1996 + + +9. Authors' Addresses + + For more information, the authors of this document are best contacted + via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + Nathaniel S. Borenstein + First Virtual Holdings + 25 Washington Avenue + Morristown, NJ 07960 + USA + + Phone: +1 201 540 8967 + Fax: +1 201 993 3032 + EMail: nsb@nsb.fv.com + + + MIME is a result of the work of the Internet Engineering Task Force + Working Group on RFC 822 Extensions. The chairman of that group, + Greg Vaudreuil, may be reached at: + + Gregory M. Vaudreuil + Octel Network Services + 17080 Dallas Parkway + Dallas, TX 75248-1905 + USA + + EMail: Greg.Vaudreuil@Octel.Com + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 42] + +RFC 2046 Media Types November 1996 + + +Appendix A -- Collected Grammar + + This appendix contains the complete BNF grammar for all the syntax + specified by this document. + + By itself, however, this grammar is incomplete. It refers by name to + several syntax rules that are defined by RFC 822. Rather than + reproduce those definitions here, and risk unintentional differences + between the two, this document simply refers the reader to RFC 822 + for the remaining definitions. Wherever a term is undefined, it + refers to the RFC 822 definition. + + boundary := 0*69 bcharsnospace + + bchars := bcharsnospace / " " + + bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" / + "+" / "_" / "," / "-" / "." / + "/" / ":" / "=" / "?" + + body-part := <"message" as defined in RFC 822, with all + header fields optional, not starting with the + specified dash-boundary, and with the + delimiter not occurring anywhere in the + body part. Note that the semantics of a + part differ from the semantics of a message, + as described in the text.> + + close-delimiter := delimiter "--" + + dash-boundary := "--" boundary + ; boundary taken from the value of + ; boundary parameter of the + ; Content-Type field. + + delimiter := CRLF dash-boundary + + discard-text := *(*text CRLF) + ; May be ignored or discarded. + + encapsulation := delimiter transport-padding + CRLF body-part + + epilogue := discard-text + + multipart-body := [preamble CRLF] + dash-boundary transport-padding CRLF + body-part *encapsulation + + + +Freed & Borenstein Standards Track [Page 43] + +RFC 2046 Media Types November 1996 + + + close-delimiter transport-padding + [CRLF epilogue] + + preamble := discard-text + + transport-padding := *LWSP-char + ; Composers MUST NOT generate + ; non-zero length transport + ; padding, but receivers MUST + ; be able to handle padding + ; added by message transports. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed & Borenstein Standards Track [Page 44] + diff --git a/lib/qCal/docs/rfc/rfc2048.txt b/lib/qCal/docs/rfc/rfc2048.txt new file mode 100644 index 0000000..a3b18b3 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2048.txt @@ -0,0 +1,1180 @@ + + + + + + +Network Working Group N. Freed +Request for Comments: 2048 Innosoft +BCP: 13 J. Klensin +Obsoletes: 1521, 1522, 1590 MCI +Category: Best Current Practice J. Postel + ISI + November 1996 + + + Multipurpose Internet Mail Extensions + (MIME) Part Four: + Registration Procedures + +Status of this Memo + + This document specifies an Internet Best Current Practices for the + Internet Community, and requests discussion and suggestions for + improvements. Distribution of this memo is unlimited. + +Abstract + + STD 11, RFC 822, defines a message representation protocol specifying + considerable detail about US-ASCII message headers, and leaves the + message content, or message body, as flat US-ASCII text. This set of + documents, collectively called the Multipurpose Internet Mail + Extensions, or MIME, redefines the format of messages to allow for + + (1) textual message bodies in character sets other than + US-ASCII, + + (2) an extensible set of different formats for non-textual + message bodies, + + (3) multi-part message bodies, and + + (4) textual header information in character sets other than + US-ASCII. + + These documents are based on earlier work documented in RFC 934, STD + 11, and RFC 1049, but extends and revises them. Because RFC 822 said + so little about message bodies, these documents are largely + orthogonal to (rather than a revision of) RFC 822. + + + + + + + + + +Freed, et. al. Best Current Practice [Page 1] + +RFC 2048 MIME Registration Procedures November 1996 + + + This fourth document, RFC 2048, specifies various IANA registration + procedures for the following MIME facilities: + + (1) media types, + + (2) external body access types, + + (3) content-transfer-encodings. + + Registration of character sets for use in MIME is covered elsewhere + and is no longer addressed by this document. + + These documents are revisions of RFCs 1521 and 1522, which themselves + were revisions of RFCs 1341 and 1342. An appendix in RFC 2049 + describes differences and changes from previous versions. + +Table of Contents + + 1. Introduction ......................................... 3 + 2. Media Type Registration .............................. 4 + 2.1 Registration Trees and Subtype Names ................ 4 + 2.1.1 IETF Tree ......................................... 4 + 2.1.2 Vendor Tree ....................................... 4 + 2.1.3 Personal or Vanity Tree ........................... 5 + 2.1.4 Special `x.' Tree ................................. 5 + 2.1.5 Additional Registration Trees ..................... 6 + 2.2 Registration Requirements ........................... 6 + 2.2.1 Functionality Requirement ......................... 6 + 2.2.2 Naming Requirements ............................... 6 + 2.2.3 Parameter Requirements ............................ 7 + 2.2.4 Canonicalization and Format Requirements .......... 7 + 2.2.5 Interchange Recommendations ....................... 8 + 2.2.6 Security Requirements ............................. 8 + 2.2.7 Usage and Implementation Non-requirements ......... 9 + 2.2.8 Publication Requirements .......................... 10 + 2.2.9 Additional Information ............................ 10 + 2.3 Registration Procedure .............................. 11 + 2.3.1 Present the Media Type to the Community for Review 11 + 2.3.2 IESG Approval ..................................... 12 + 2.3.3 IANA Registration ................................. 12 + 2.4 Comments on Media Type Registrations ................ 12 + 2.5 Location of Registered Media Type List .............. 12 + 2.6 IANA Procedures for Registering Media Types ......... 12 + 2.7 Change Control ...................................... 13 + 2.8 Registration Template ............................... 14 + 3. External Body Access Types ........................... 14 + 3.1 Registration Requirements ........................... 15 + 3.1.1 Naming Requirements ............................... 15 + + + +Freed, et. al. Best Current Practice [Page 2] + +RFC 2048 MIME Registration Procedures November 1996 + + + 3.1.2 Mechanism Specification Requirements .............. 15 + 3.1.3 Publication Requirements .......................... 15 + 3.1.4 Security Requirements ............................. 15 + 3.2 Registration Procedure .............................. 15 + 3.2.1 Present the Access Type to the Community .......... 16 + 3.2.2 Access Type Reviewer .............................. 16 + 3.2.3 IANA Registration ................................. 16 + 3.3 Location of Registered Access Type List ............. 16 + 3.4 IANA Procedures for Registering Access Types ........ 16 + 4. Transfer Encodings ................................... 17 + 4.1 Transfer Encoding Requirements ...................... 17 + 4.1.1 Naming Requirements ............................... 17 + 4.1.2 Algorithm Specification Requirements .............. 18 + 4.1.3 Input Domain Requirements ......................... 18 + 4.1.4 Output Range Requirements ......................... 18 + 4.1.5 Data Integrity and Generality Requirements ........ 18 + 4.1.6 New Functionality Requirements .................... 18 + 4.2 Transfer Encoding Definition Procedure .............. 19 + 4.3 IANA Procedures for Transfer Encoding Registration... 19 + 4.4 Location of Registered Transfer Encodings List ...... 19 + 5. Authors' Addresses ................................... 20 + A. Grandfathered Media Types ............................ 21 + +1. Introduction + + Recent Internet protocols have been carefully designed to be easily + extensible in certain areas. In particular, MIME [RFC 2045] is an + open-ended framework and can accommodate additional object types, + character sets, and access methods without any changes to the basic + protocol. A registration process is needed, however, to ensure that + the set of such values is developed in an orderly, well-specified, + and public manner. + + This document defines registration procedures which use the Internet + Assigned Numbers Authority (IANA) as a central registry for such + values. + + Historical Note: The registration process for media types was + initially defined in the context of the asynchronous Internet mail + environment. In this mail environment there is a need to limit the + number of possible media types to increase the likelihood of + interoperability when the capabilities of the remote mail system are + not known. As media types are used in new environments, where the + proliferation of media types is not a hindrance to interoperability, + the original procedure was excessively restrictive and had to be + generalized. + + + + + +Freed, et. al. Best Current Practice [Page 3] + +RFC 2048 MIME Registration Procedures November 1996 + + +2. Media Type Registration + + Registration of a new media type or types starts with the + construction of a registration proposal. Registration may occur in + several different registration trees, which have different + requirements as discussed below. In general, the new registration + proposal is circulated and reviewed in a fashion appropriate to the + tree involved. The media type is then registered if the proposal is + acceptable. The following sections describe the requirements and + procedures used for each of the different registration trees. + +2.1. Registration Trees and Subtype Names + + In order to increase the efficiency and flexibility of the + registration process, different structures of subtype names may be + registered to accomodate the different natural requirements for, + e.g., a subtype that will be recommended for wide support and + implementation by the Internet Community or a subtype that is used to + move files associated with proprietary software. The following + subsections define registration "trees", distinguished by the use of + faceted names (e.g., names of the form "tree.subtree...type"). Note + that some media types defined prior to this document do not conform + to the naming conventions described below. See Appendix A for a + discussion of them. + +2.1.1. IETF Tree + + The IETF tree is intended for types of general interest to the + Internet Community. Registration in the IETF tree requires approval + by the IESG and publication of the media type registration as some + form of RFC. + + Media types in the IETF tree are normally denoted by names that are + not explicitly faceted, i.e., do not contain period (".", full stop) + characters. + + The "owner" of a media type registration in the IETF tree is assumed + to be the IETF itself. Modification or alteration of the + specification requires the same level of processing (e.g. standards + track) required for the initial registration. + +2.1.2. Vendor Tree + + The vendor tree is used for media types associated with commercially + available products. "Vendor" or "producer" are construed as + equivalent and very broadly in this context. + + + + + +Freed, et. al. Best Current Practice [Page 4] + +RFC 2048 MIME Registration Procedures November 1996 + + + A registration may be placed in the vendor tree by anyone who has + need to interchange files associated with the particular product. + However, the registration formally belongs to the vendor or + organization producing the software or file format. Changes to the + specification will be made at their request, as discussed in + subsequent sections. + + Registrations in the vendor tree will be distinguished by the leading + facet "vnd.". That may be followed, at the discretion of the + registration, by either a media type name from a well-known producer + (e.g., "vnd.mudpie") or by an IANA-approved designation of the + producer's name which is then followed by a media type or product + designation (e.g., vnd.bigcompany.funnypictures). + + While public exposure and review of media types to be registered in + the vendor tree is not required, using the ietf-types list for review + is strongly encouraged to improve the quality of those + specifications. Registrations in the vendor tree may be submitted + directly to the IANA. + +2.1.3. Personal or Vanity Tree + + Registrations for media types created experimentally or as part of + products that are not distributed commercially may be registered in + the personal or vanity tree. The registrations are distinguished by + the leading facet "prs.". + + The owner of "personal" registrations and associated specifications + is the person or entity making the registration, or one to whom + responsibility has been transferred as described below. + + While public exposure and review of media types to be registered in + the personal tree is not required, using the ietf-types list for + review is strongly encouraged to improve the quality of those + specifications. Registrations in the personl tree may be submitted + directly to the IANA. + +2.1.4. Special `x.' Tree + + For convenience and symmetry with this registration scheme, media + type names with "x." as the first facet may be used for the same + purposes for which names starting in "x-" are normally used. These + types are unregistered, experimental, and should be used only with + the active agreement of the parties exchanging them. + + + + + + + +Freed, et. al. Best Current Practice [Page 5] + +RFC 2048 MIME Registration Procedures November 1996 + + + However, with the simplified registration procedures described above + for vendor and personal trees, it should rarely, if ever, be + necessary to use unregistered experimental types, and as such use of + both "x-" and "x." forms is discouraged. + +2.1.5. Additional Registration Trees + + From time to time and as required by the community, the IANA may, + with the advice and consent of the IESG, create new top-level + registration trees. It is explicitly assumed that these trees may be + created for external registration and management by well-known + permanent bodies, such as scientific societies for media types + specific to the sciences they cover. In general, the quality of + review of specifications for one of these additional registration + trees is expected to be equivalent to that which IETF would give to + registrations in its own tree. Establishment of these new trees will + be announced through RFC publication approved by the IESG. + +2.2. Registration Requirements + + Media type registration proposals are all expected to conform to + various requirements laid out in the following sections. Note that + requirement specifics sometimes vary depending on the registration + tree, again as detailed in the following sections. + +2.2.1. Functionality Requirement + + Media types must function as an actual media format: Registration of + things that are better thought of as a transfer encoding, as a + character set, or as a collection of separate entities of another + type, is not allowed. For example, although applications exist to + decode the base64 transfer encoding [RFC 2045], base64 cannot be + registered as a media type. + + This requirement applies regardless of the registration tree + involved. + +2.2.2. Naming Requirements + + All registered media types must be assigned MIME type and subtype + names. The combination of these names then serves to uniquely + identify the media type and the format of the subtype name identifies + the registration tree. + + The choice of top-level type name must take the nature of media type + involved into account. For example, media normally used for + representing still images should be a subtype of the image content + type, whereas media capable of representing audio information belongs + + + +Freed, et. al. Best Current Practice [Page 6] + +RFC 2048 MIME Registration Procedures November 1996 + + + under the audio content type. See RFC 2046 for additional information + on the basic set of top-level types and their characteristics. + + New subtypes of top-level types must conform to the restrictions of + the top-level type, if any. For example, all subtypes of the + multipart content type must use the same encapsulation syntax. + + In some cases a new media type may not "fit" under any currently + defined top-level content type. Such cases are expected to be quite + rare. However, if such a case arises a new top-level type can be + defined to accommodate it. Such a definition must be done via + standards-track RFC; no other mechanism can be used to define + additional top-level content types. + + These requirements apply regardless of the registration tree + involved. + +2.2.3. Parameter Requirements + + Media types may elect to use one or more MIME content type + parameters, or some parameters may be automatically made available to + the media type by virtue of being a subtype of a content type that + defines a set of parameters applicable to any of its subtypes. In + either case, the names, values, and meanings of any parameters must + be fully specified when a media type is registered in the IETF tree, + and should be specified as completely as possible when media types + are registered in the vendor or personal trees. + + New parameters must not be defined as a way to introduce new + functionality in types registered in the IETF tree, although new + parameters may be added to convey additional information that does + not otherwise change existing functionality. An example of this + would be a "revision" parameter to indicate a revision level of an + external specification such as JPEG. Similar behavior is encouraged + for media types registered in the vendor or personal trees but is not + required. + +2.2.4. Canonicalization and Format Requirements + + All registered media types must employ a single, canonical data + format, regardless of registration tree. + + A precise and openly available specification of the format of each + media type is required for all types registered in the IETF tree and + must at a minimum be referenced by, if it isn't actually included in, + the media type registration proposal itself. + + + + + +Freed, et. al. Best Current Practice [Page 7] + +RFC 2048 MIME Registration Procedures November 1996 + + + The specifications of format and processing particulars may or may + not be publically available for media types registered in the vendor + tree, and such registration proposals are explicitly permitted to + include only a specification of which software and version produce or + process such media types. References to or inclusion of format + specifications in registration proposals is encouraged but not + required. + + Format specifications are still required for registration in the + personal tree, but may be either published as RFCs or otherwise + deposited with IANA. The deposited specifications will meet the same + criteria as those required to register a well-known TCP port and, in + particular, need not be made public. + + Some media types involve the use of patented technology. The + registration of media types involving patented technology is + specifically permitted. However, the restrictions set forth in RFC + 1602 on the use of patented technology in standards-track protocols + must be respected when the specification of a media type is part of a + standards-track protocol. + +2.2.5. Interchange Recommendations + + Media types should, whenever possible, interoperate across as many + systems and applications as possible. However, some media types will + inevitably have problems interoperating across different platforms. + Problems with different versions, byte ordering, and specifics of + gateway handling can and will arise. + + Universal interoperability of media types is not required, but known + interoperability issues should be identified whenever possible. + Publication of a media type does not require an exhaustive review of + interoperability, and the interoperability considerations section is + subject to continuing evaluation. + + These recommendations apply regardless of the registration tree + involved. + +2.2.6. Security Requirements + + An analysis of security issues is required for for all types + registered in the IETF Tree. (This is in accordance with the basic + requirements for all IETF protocols.) A similar analysis for media + types registered in the vendor or personal trees is encouraged but + not required. However, regardless of what security analysis has or + has not been done, all descriptions of security issues must be as + accurate as possible regardless of registration tree. In particular, + a statement that there are "no security issues associated with this + + + +Freed, et. al. Best Current Practice [Page 8] + +RFC 2048 MIME Registration Procedures November 1996 + + + type" must not be confused with "the security issues associates with + this type have not been assessed". + + There is absolutely no requirement that media types registered in any + tree be secure or completely free from risks. Nevertheless, all + known security risks must be identified in the registration of a + media type, again regardless of registration tree. + + The security considerations section of all registrations is subject + to continuing evaluation and modification, and in particular may be + extended by use of the "comments on media types" mechanism described + in subsequent sections. + + Some of the issues that should be looked at in a security analysis of + a media type are: + + (1) Complex media types may include provisions for + directives that institute actions on a recipient's + files or other resources. In many cases provision is + made for originators to specify arbitrary actions in an + unrestricted fashion which may then have devastating + effects. See the registration of the + application/postscript media type in RFC 2046 for + an example of such directives and how to handle them. + + (2) Complex media types may include provisions for + directives that institute actions which, while not + directly harmful to the recipient, may result in + disclosure of information that either facilitates a + subsequent attack or else violates a recipient's + privacy in some way. Again, the registration of the + application/postscript media type illustrates how such + directives can be handled. + + (3) A media type might be targeted for applications that + require some sort of security assurance but not provide + the necessary security mechanisms themselves. For + example, a media type could be defined for storage of + confidential medical information which in turn requires + an external confidentiality service. + +2.2.7. Usage and Implementation Non-requirements + + In the asynchronous mail environment, where information on the + capabilities of the remote mail agent is frequently not available to + the sender, maximum interoperability is attained by restricting the + number of media types used to those "common" formats expected to be + widely implemented. This was asserted in the past as a reason to + + + +Freed, et. al. Best Current Practice [Page 9] + +RFC 2048 MIME Registration Procedures November 1996 + + + limit the number of possible media types and resulted in a + registration process with a significant hurdle and delay for those + registering media types. + + However, the need for "common" media types does not require limiting + the registration of new media types. If a limited set of media types + is recommended for a particular application, that should be asserted + by a separate applicability statement specific for the application + and/or environment. + + As such, universal support and implementation of a media type is NOT + a requirement for registration. If, however, a media type is + explicitly intended for limited use, this should be noted in its + registration. + +2.2.8. Publication Requirements + + Proposals for media types registered in the IETF tree must be + published as RFCs. RFC publication of vendor and personal media type + proposals is encouraged but not required. In all cases IANA will + retain copies of all media type proposals and "publish" them as part + of the media types registration tree itself. + + Other than in the IETF tree, the registration of a data type does not + imply endorsement, approval, or recommendation by IANA or IETF or + even certification that the specification is adequate. To become + Internet Standards, protocol, data objects, or whatever must go + through the IETF standards process. This is too difficult and too + lengthy a process for the convenient registration of media types. + + The IETF tree exists for media types that do require require a + substantive review and approval process with the vendor and personal + trees exist for those that do not. It is expected that applicability + statements for particular applications will be published from time to + time that recommend implementation of, and support for, media types + that have proven particularly useful in those contexts. + + As discussed above, registration of a top-level type requires + standards-track processing and, hence, RFC publication. + +2.2.9. Additional Information + + Various sorts of optional information may be included in the + specification of a media type if it is available: + + (1) Magic number(s) (length, octet values). Magic numbers + are byte sequences that are always present and thus can + be used to identify entities as being of a given media + + + +Freed, et. al. Best Current Practice [Page 10] + +RFC 2048 MIME Registration Procedures November 1996 + + + type. + + (2) File extension(s) commonly used on one or more + platforms to indicate that some file containing a given + type of media. + + (3) Macintosh File Type code(s) (4 octets) used to label + files containing a given type of media. + + Such information is often quite useful to implementors and if + available should be provided. + +2.3. Registration Procedure + + The following procedure has been implemented by the IANA for review + and approval of new media types. This is not a formal standards + process, but rather an administrative procedure intended to allow + community comment and sanity checking without excessive time delay. + For registration in the IETF tree, the normal IETF processes should + be followed, treating posting of an internet-draft and announcement + on the ietf-types list (as described in the next subsection) as a + first step. For registrations in the vendor or personal tree, the + initial review step described below may be omitted and the type + registered directly by submitting the template and an explanation + directly to IANA (at iana@iana.org). However, authors of vendor or + personal media type specifications are encouraged to seek community + review and comment whenever that is feasible. + +2.3.1. Present the Media Type to the Community for Review + + Send a proposed media type registration to the "ietf-types@iana.org" + mailing list for a two week review period. This mailing list has + been established for the purpose of reviewing proposed media and + access types. Proposed media types are not formally registered and + must not be used; the "x-" prefix specified in RFC 2045 can be used + until registration is complete. + + The intent of the public posting is to solicit comments and feedback + on the choice of type/subtype name, the unambiguity of the references + with respect to versions and external profiling information, and a + review of any interoperability or security considerations. The + submitter may submit a revised registration, or withdraw the + registration completely, at any time. + + + + + + + + +Freed, et. al. Best Current Practice [Page 11] + +RFC 2048 MIME Registration Procedures November 1996 + + +2.3.2. IESG Approval + + Media types registered in the IETF tree must be submitted to the IESG + for approval. + +2.3.3. IANA Registration + + Provided that the media type meets the requirements for media types + and has obtained approval that is necessary, the author may submit + the registration request to the IANA, which will register the media + type and make the media type registration available to the community. + +2.4. Comments on Media Type Registrations + + Comments on registered media types may be submitted by members of the + community to IANA. These comments will be passed on to the "owner" + of the media type if possible. Submitters of comments may request + that their comment be attached to the media type registration itself, + and if IANA approves of this the comment will be made accessible in + conjunction with the type registration itself. + +2.5. Location of Registered Media Type List + + Media type registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/media-types/" + and all registered media types will be listed in the periodically + issued "Assigned Numbers" RFC [currently STD 2, RFC 1700]. The media + type description and other supporting material may also be published + as an Informational RFC by sending it to "rfc-editor@isi.edu" (please + follow the instructions to RFC authors [RFC-1543]). + +2.6. IANA Procedures for Registering Media Types + + The IANA will only register media types in the IETF tree in response + to a communication from the IESG stating that a given registration + has been approved. Vendor and personal types will be registered by + the IANA automatically and without any formal review as long as the + following minimal conditions are met: + + (1) Media types must function as an actual media format. + In particular, character sets and transfer encodings + may not be registered as media types. + + (2) All media types must have properly formed type and + subtype names. All type names must be defined by a + standards-track RFC. All subtype names must be unique, + must conform to the MIME grammar for such names, and + must contain the proper tree prefix. + + + +Freed, et. al. Best Current Practice [Page 12] + +RFC 2048 MIME Registration Procedures November 1996 + + + (3) Types registered in the personal tree must either + provide a format specification or a pointer to one. + + (4) Any security considerations given must not be obviously + bogus. (It is neither possible nor necessary for the + IANA to conduct a comprehensive security review of + media type registrations. Nevertheless, IANA has the + authority to identify obviously incompetent material + and exclude it.) + +2.7. Change Control + + Once a media type has been published by IANA, the author may request + a change to its definition. The descriptions of the different + registration trees above designate the "owners" of each type of + registration. The change request follows the same procedure as the + registration request: + + (1) Publish the revised template on the ietf-types list. + + (2) Leave at least two weeks for comments. + + (3) Publish using IANA after formal review if required. + + Changes should be requested only when there are serious omission or + errors in the published specification. When review is required, a + change request may be denied if it renders entities that were valid + under the previous definition invalid under the new definition. + + The owner of a content type may pass responsibility for the content + type to another person or agency by informing IANA and the ietf-types + list; this can be done without discussion or review. + + The IESG may reassign responsibility for a media type. The most + common case of this will be to enable changes to be made to types + where the author of the registration has died, moved out of contact + or is otherwise unable to make changes that are important to the + community. + + Media type registrations may not be deleted; media types which are no + longer believed appropriate for use can be declared OBSOLETE by a + change to their "intended use" field; such media types will be + clearly marked in the lists published by IANA. + + + + + + + + +Freed, et. al. Best Current Practice [Page 13] + +RFC 2048 MIME Registration Procedures November 1996 + + +2.8. Registration Template + + To: ietf-types@iana.org + Subject: Registration of MIME media type XXX/YYY + + MIME media type name: + + MIME subtype name: + + Required parameters: + + Optional parameters: + + Encoding considerations: + + Security considerations: + + Interoperability considerations: + + Published specification: + + Applications which use this media type: + + Additional information: + + Magic number(s): + File extension(s): + Macintosh File Type Code(s): + + Person & email address to contact for further information: + + Intended usage: + + (One of COMMON, LIMITED USE or OBSOLETE) + + Author/Change controller: + + (Any other information that the author deems interesting may be + added below this line.) + +3. External Body Access Types + + RFC 2046 defines the message/external-body media type, whereby a MIME + entity can act as pointer to the actual body data in lieu of + including the data directly in the entity body. Each + message/external-body reference specifies an access type, which + determines the mechanism used to retrieve the actual body data. RFC + 2046 defines an initial set of access types, but allows for the + + + +Freed, et. al. Best Current Practice [Page 14] + +RFC 2048 MIME Registration Procedures November 1996 + + + registration of additional access types to accommodate new retrieval + mechanisms. + +3.1. Registration Requirements + + New access type specifications must conform to a number of + requirements as described below. + +3.1.1. Naming Requirements + + Each access type must have a unique name. This name appears in the + access-type parameter in the message/external-body content-type + header field, and must conform to MIME content type parameter syntax. + +3.1.2. Mechanism Specification Requirements + + All of the protocols, transports, and procedures used by a given + access type must be described, either in the specification of the + access type itself or in some other publicly available specification, + in sufficient detail for the access type to be implemented by any + competent implementor. Use of secret and/or proprietary methods in + access types are expressly prohibited. The restrictions imposed by + RFC 1602 on the standardization of patented algorithms must be + respected as well. + +3.1.3. Publication Requirements + + All access types must be described by an RFC. The RFC may be + informational rather than standards-track, although standard-track + review and approval are encouraged for all access types. + +3.1.4. Security Requirements + + Any known security issues that arise from the use of the access type + must be completely and fully described. It is not required that the + access type be secure or that it be free from risks, but that the + known risks be identified. Publication of a new access type does not + require an exhaustive security review, and the security + considerations section is subject to continuing evaluation. + Additional security considerations should be addressed by publishing + revised versions of the access type specification. + +3.2. Registration Procedure + + Registration of a new access type starts with the construction of a + draft of an RFC. + + + + + +Freed, et. al. Best Current Practice [Page 15] + +RFC 2048 MIME Registration Procedures November 1996 + + +3.2.1. Present the Access Type to the Community + + Send a proposed access type specification to the "ietf- + types@iana.org" mailing list for a two week review period. This + mailing list has been established for the purpose of reviewing + proposed access and media types. Proposed access types are not + formally registered and must not be used. + + The intent of the public posting is to solicit comments and feedback + on the access type specification and a review of any security + considerations. + +3.2.2. Access Type Reviewer + + When the two week period has passed, the access type reviewer, who is + appointed by the IETF Applications Area Director, either forwards the + request to iana@isi.edu, or rejects it because of significant + objections raised on the list. + + Decisions made by the reviewer must be posted to the ietf-types + mailing list within 14 days. Decisions made by the reviewer may be + appealed to the IESG. + +3.2.3. IANA Registration + + Provided that the access type has either passed review or has been + successfully appealed to the IESG, the IANA will register the access + type and make the registration available to the community. The + specification of the access type must also be published as an RFC. + Informational RFCs are published by sending them to "rfc- + editor@isi.edu" (please follow the instructions to RFC authors [RFC- + 1543]). + +3.3. Location of Registered Access Type List + + Access type registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/access-types/" + and all registered access types will be listed in the periodically + issued "Assigned Numbers" RFC [currently RFC-1700]. + +3.4. IANA Procedures for Registering Access Types + + The identity of the access type reviewer is communicated to the IANA + by the IESG. The IANA then only acts in response to access type + definitions that either are approved by the access type reviewer and + forwarded by the reviewer to the IANA for registration, or in + response to a communication from the IESG that an access type + definition appeal has overturned the access type reviewer's ruling. + + + +Freed, et. al. Best Current Practice [Page 16] + +RFC 2048 MIME Registration Procedures November 1996 + + +4. Transfer Encodings + + Transfer encodings are tranformations applied to MIME media types + after conversion to the media type's canonical form. Transfer + encodings are used for several purposes: + + (1) Many transports, especially message transports, can + only handle data consisting of relatively short lines + of text. There can also be severe restrictions on what + characters can be used in these lines of text -- some + transports are restricted to a small subset of US-ASCII + and others cannot handle certain character sequences. + Transfer encodings are used to transform binary data + into textual form that can survive such transports. + Examples of this sort of transfer encoding include the + base64 and quoted-printable transfer encodings defined + in RFC 2045. + + (2) Image, audio, video, and even application entities are + sometimes quite large. Compression algorithms are often + quite effective in reducing the size of large entities. + Transfer encodings can be used to apply general-purpose + non-lossy compression algorithms to MIME entities. + + (3) Transport encodings can be defined as a means of + representing existing encoding formats in a MIME + context. + + IMPORTANT: The standardization of a large numbers of different + transfer encodings is seen as a significant barrier to widespread + interoperability and is expressely discouraged. Nevertheless, the + following procedure has been defined to provide a means of defining + additional transfer encodings, should standardization actually be + justified. + +4.1. Transfer Encoding Requirements + + Transfer encoding specifications must conform to a number of + requirements as described below. + +4.1.1. Naming Requirements + + Each transfer encoding must have a unique name. This name appears in + the Content-Transfer-Encoding header field and must conform to the + syntax of that field. + + + + + + +Freed, et. al. Best Current Practice [Page 17] + +RFC 2048 MIME Registration Procedures November 1996 + + +4.1.2. Algorithm Specification Requirements + + All of the algorithms used in a transfer encoding (e.g. conversion + to printable form, compression) must be described in their entirety + in the transfer encoding specification. Use of secret and/or + proprietary algorithms in standardized transfer encodings are + expressly prohibited. The restrictions imposed by RFC 1602 on the + standardization of patented algorithms must be respected as well. + +4.1.3. Input Domain Requirements + + All transfer encodings must be applicable to an arbitrary sequence of + octets of any length. Dependence on particular input forms is not + allowed. + + It should be noted that the 7bit and 8bit encodings do not conform to + this requirement. Aside from the undesireability of having + specialized encodings, the intent here is to forbid the addition of + additional encodings along the lines of 7bit and 8bit. + +4.1.4. Output Range Requirements + + There is no requirement that a particular tranfer encoding produce a + particular form of encoded output. However, the output format for + each transfer encoding must be fully and completely documented. In + particular, each specification must clearly state whether the output + format always lies within the confines of 7bit data, 8bit data, or is + simply pure binary data. + +4.1.5. Data Integrity and Generality Requirements + + All transfer encodings must be fully invertible on any platform; it + must be possible for anyone to recover the original data by + performing the corresponding decoding operation. Note that this + requirement effectively excludes all forms of lossy compression as + well as all forms of encryption from use as a transfer encoding. + +4.1.6. New Functionality Requirements + + All transfer encodings must provide some sort of new functionality. + Some degree of functionality overlap with previously defined transfer + encodings is acceptable, but any new transfer encoding must also + offer something no other transfer encoding provides. + + + + + + + + +Freed, et. al. Best Current Practice [Page 18] + +RFC 2048 MIME Registration Procedures November 1996 + + +4.2. Transfer Encoding Definition Procedure + + Definition of a new transfer encoding starts with the construction of + a draft of a standards-track RFC. The RFC must define the transfer + encoding precisely and completely, and must also provide substantial + justification for defining and standardizing a new transfer encoding. + This specification must then be presented to the IESG for + consideration. The IESG can + + (1) reject the specification outright as being + inappropriate for standardization, + + (2) approve the formation of an IETF working group to work + on the specification in accordance with IETF + procedures, or, + + (3) accept the specification as-is and put it directly on + the standards track. + + Transfer encoding specifications on the standards track follow normal + IETF rules for standards track documents. A transfer encoding is + considered to be defined and available for use once it is on the + standards track. + +4.3. IANA Procedures for Transfer Encoding Registration + + There is no need for a special procedure for registering Transfer + Encodings with the IANA. All legitimate transfer encoding + registrations must appear as a standards-track RFC, so it is the + IESG's responsibility to notify the IANA when a new transfer encoding + has been approved. + +4.4. Location of Registered Transfer Encodings List + + Transfer encoding registrations will be posted in the anonymous FTP + directory "ftp://ftp.isi.edu/in-notes/iana/assignments/transfer- + encodings/" and all registered transfer encodings will be listed in + the periodically issued "Assigned Numbers" RFC [currently RFC-1700]. + + + + + + + + + + + + + +Freed, et. al. Best Current Practice [Page 19] + +RFC 2048 MIME Registration Procedures November 1996 + + +5. Authors' Addresses + + For more information, the authors of this document are best + contacted via Internet mail: + + Ned Freed + Innosoft International, Inc. + 1050 East Garvey Avenue South + West Covina, CA 91790 + USA + + Phone: +1 818 919 3600 + Fax: +1 818 919 3614 + EMail: ned@innosoft.com + + + John Klensin + MCI + 2100 Reston Parkway + Reston, VA 22091 + + Phone: +1 703 715-7361 + Fax: +1 703 715-7436 + EMail: klensin@mci.net + + + Jon Postel + USC/Information Sciences Institute + 4676 Admiralty Way + Marina del Rey, CA 90292 + USA + + + Phone: +1 310 822 1511 + Fax: +1 310 823 6714 + EMail: Postel@ISI.EDU + + + + + + + + + + + + + + + +Freed, et. al. Best Current Practice [Page 20] + +RFC 2048 MIME Registration Procedures November 1996 + + +Appendix A -- Grandfathered Media Types + + A number of media types, registered prior to 1996, would, if + registered under the guidelines in this document, be placed into + either the vendor or personal trees. Reregistration of those types + to reflect the appropriate trees is encouraged, but not required. + Ownership and change control principles outlined in this document + apply to those types as if they had been registered in the trees + described above. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Freed, et. al. Best Current Practice [Page 21] + diff --git a/lib/qCal/docs/rfc/rfc2111.txt b/lib/qCal/docs/rfc/rfc2111.txt new file mode 100644 index 0000000..3ccd234 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2111.txt @@ -0,0 +1,283 @@ + + + + + + +Network Working Group E. Levinson +Request for Comments: 2111 XIson, Inc. +Category: Standards Track March 1997 + + + Content-ID and Message-ID Uniform Resource Locators + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Abstract + + The Uniform Resource Locator (URL) schemes, "cid:" and "mid:" allow + references to messages and the body parts of messages. For example, + within a single multipart message, one HTML body part might include + embedded references to other parts of the same message. + +1. Introduction + + The use of [MIME] within email to convey Web pages and their + associated images requires a URL scheme to permit the HTML to refer + to the images or other data included in the message. The Content-ID + Uniform Resource Locator, "cid:", serves that purpose. + + Similarly Net News readers use Message-IDs to link related messages + together. The Message-ID URL provides a scheme, "mid:", to refer to + such messages as a "resource". + + The "mid" (Message-ID) and "cid" (Content-ID) URL schemes provide + identifiers for messages and their body parts. The "mid" scheme uses + (a part of) the message-id of an email message to refer to a specific + message. The "cid" scheme refers to a specific body part of a + message; its use is generally limited to references to other body + parts in the same message as the referring body part. The "mid" + scheme may also refer to a specific body part within a designated + message, by including the content-ID's address. + + A note on terminology. The terms "body part" and "MIME entity" are + used interchangeably. They refer to the headers and body of a MIME + message, either the message itself or one of the body parts contained + in a Multipart message. + + + + + +Levinson Standards Track [Page 1] + +RFC 2111 CID and MID URLs March 1997 + + +2. The MID and CID URL Schemes + + RFC1738 [URL] reserves the "mid" and "cid" schemes for Message-ID and + Content-ID respectively. This memorandum defines the syntax for + those URLs. Because they use the same syntactic elements they are + presented together. + + The URLs take the form + + content-id = url-addr-spec + + message-id = url-addr-spec + + url-addr-spec = addr-spec ; URL encoding of RFC 822 addr-spec + + cid-url = "cid" ":" content-id + + mid-url = "mid" ":" message-id [ "/" content-id ] + + Note: in Internet mail messages, the addr-spec in a Content-ID + [MIME] or Message-ID [822] header are enclosed in angle brackets + (<>). Since addr-spec in a Message-ID or Content-ID might contain + characters not allowed within a URL; any such character (including + "/", which is reserved within the "mid" scheme) must be hex- + encoded using the %hh escape mechanism in [URL]. + + A "mid" URL with only a "message-id" refers to an entire message. + With the appended "content-id", it refers to a body part within a + message, as does a "cid" URL. The Content-ID of a MIME body part is + required to be globally unique. However, in many systems that store + messages, body parts are not indexed independently their context + (message). The "mid" URL long form was designed to supply the + context needed to support interoperability with such systems. + + A implementation conforming to this specification is required to + support the "mid" URL long form (message-id/content-id). Conforming + implementations can choose to, but are not required to, take + advantage of the content-id's uniqueness and interpret a "cid" URL to + refer to any body part within the message store. + + In limited circumstances (e.g., within multipart/alternate), a single + message may contain several body parts that have the same Content-ID. + That occurs, for example, when identical data can be accessed through + different methods [MIME, sect. 7.2.3]. In those cases, conforming + implementations are required to use the rules of the containing MIME + entity (e.g., multi-part/alternate) to select the body part to which + the Content-ID refers. + + + + +Levinson Standards Track [Page 2] + +RFC 2111 CID and MID URLs March 1997 + + + A "cid" URL is converted to the corresponding Content-ID message + header [MIME] by removing the "cid:" prefix, converting %hh hex- + escaped characters to their ASCII equivalents and enclosing the + remaining parts with an angle bracket pair, "<" and ">". For + example, "mid:foo4%25foo1@bar.net" corresponds to + + Message-ID: + + A "mid" URL is converted to a Message-ID or Message-ID/Content-ID + pair in a similar fashion. + + Both message-id and content-id are required to be globally unique. + That is, no two different messages will ever have the same Message-ID + addr-spec; no different body parts will ever have the same Content-ID + addr-spec. A common technique used by many message systems is to use + a time and date stamp along with the local host's domain name, e.g., + 950124.162336@XIson.com. + +Some Examples + + The following message contains an HTML body part that refers to an + image contained in another body part. Both body parts are contained + in a Multipart/Related MIME entity. The HTML IMG tag contains a + cidurl which points to the image. + + From: foo1@bar.net + To: foo2@bar.net + Subject: A simple example + Mime-Version: 1.0 + Content-Type: multipart/related; boundary="boundary-example-1"; + type=Text/HTML + + --boundary-example 1 + Content-Type: Text/HTML; charset=US-ASCII + + ... text of the HTML document, which might contain a hyperlink + to the other body part, for example through a statement such as: + IETF logo + + --boundary-example-1 + Content-ID: foo4*foo1@bar.net + Content-Type: IMAGE/GIF + Content-Transfer-Encoding: BASE64 + + + + + + + + +Levinson Standards Track [Page 3] + +RFC 2111 CID and MID URLs March 1997 + + + R0lGODlhGAGgAPEAAP/////ZRaCgoAAAACH+PUNvcHlyaWdodCAoQykgMTk5 + NSBJRVRGLiBVbmF1dGhvcml6ZWQgZHVwbGljYXRpb24gcHJvaGliaXRlZC4A + etc... + + --boundary-example-1-- + + The following message points to another message (hopefully still in + the recipient's message store). + + From: bar@none.com + To: phooey@all.com + Subject: Here's how to do it + Content-type: text/html; charset=usascii + + ... The items in my + + previous message, shows how the approach you propose can be + used to accomplish ... + +3. Security Considerations + + The URLs defined here provide an addressing or referencing mechanism. + The values of these URLs disclose no more about the originators + environment than the corresponding Message-ID and Content-ID values. + Where concern exists about such disclosures the originator of a + message using mid and cid URLs must take precautions to insure that + confidential information is not disclosed. Those precautions should + already be in place to handle existing mail use of the Message-ID and + Content-ID. + +4. References + +[822] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages," August 1982, University of Delaware, STD 11, RFC + 822. + +[MIME] N. Borenstein, N. Freed, "MIME (Multipurpose Internet Mail + Extensions) Part One: Mechanisms for Specifying and + Describing the Format of Internet Message Bodies," + September 1993, RFC 1521. + +[URL] Berners-Lee, T., Masinter, L., and McCahill, M., "Uniform + Resource Locators (URL)," December 1994. + +[MULREL] E. Levinson, "The MIME Multipart/Related Content-type," + December 1995, RFC 1874. + + + + + +Levinson Standards Track [Page 4] + +RFC 2111 CID and MID URLs March 1997 + + +5. Acknowledgments + + The original concept of "mid" and "cid" URLs were part of the Tim + Berners-Lee's original vision of the World Wide Web. The ideas and + design have benefited greatly by discussions with Harald Alvestrand, + Dan Connolly, Roy Fielding, Larry Masinter, Jacob Palme, and others + in the MHTML working group. + +6. Author's Address + + Edward Levinson + 47 Clive Street + Metuchen, NJ 08840-1060 + USA + +1 908 549 3716 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Levinson Standards Track [Page 5] + diff --git a/lib/qCal/docs/rfc/rfc2119.txt b/lib/qCal/docs/rfc/rfc2119.txt new file mode 100644 index 0000000..e31fae4 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2119.txt @@ -0,0 +1,171 @@ + + + + + + +Network Working Group S. Bradner +Request for Comments: 2119 Harvard University +BCP: 14 March 1997 +Category: Best Current Practice + + + Key words for use in RFCs to Indicate Requirement Levels + +Status of this Memo + + This document specifies an Internet Best Current Practices for the + Internet Community, and requests discussion and suggestions for + improvements. Distribution of this memo is unlimited. + +Abstract + + In many standards track documents several words are used to signify + the requirements in the specification. These words are often + capitalized. This document defines these words as they should be + interpreted in IETF documents. Authors who follow these guidelines + should incorporate this phrase near the beginning of their document: + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL + NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and + "OPTIONAL" in this document are to be interpreted as described in + RFC 2119. + + Note that the force of these words is modified by the requirement + level of the document in which they are used. + +1. MUST This word, or the terms "REQUIRED" or "SHALL", mean that the + definition is an absolute requirement of the specification. + +2. MUST NOT This phrase, or the phrase "SHALL NOT", mean that the + definition is an absolute prohibition of the specification. + +3. SHOULD This word, or the adjective "RECOMMENDED", mean that there + may exist valid reasons in particular circumstances to ignore a + particular item, but the full implications must be understood and + carefully weighed before choosing a different course. + +4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that + there may exist valid reasons in particular circumstances when the + particular behavior is acceptable or even useful, but the full + implications should be understood and the case carefully weighed + before implementing any behavior described with this label. + + + + + +Bradner Best Current Practice [Page 1] + +RFC 2119 RFC Key Words March 1997 + + +5. MAY This word, or the adjective "OPTIONAL", mean that an item is + truly optional. One vendor may choose to include the item because a + particular marketplace requires it or because the vendor feels that + it enhances the product while another vendor may omit the same item. + An implementation which does not include a particular option MUST be + prepared to interoperate with another implementation which does + include the option, though perhaps with reduced functionality. In the + same vein an implementation which does include a particular option + MUST be prepared to interoperate with another implementation which + does not include the option (except, of course, for the feature the + option provides.) + +6. Guidance in the use of these Imperatives + + Imperatives of the type defined in this memo must be used with care + and sparingly. In particular, they MUST only be used where it is + actually required for interoperation or to limit behavior which has + potential for causing harm (e.g., limiting retransmisssions) For + example, they must not be used to try to impose a particular method + on implementors where the method is not required for + interoperability. + +7. Security Considerations + + These terms are frequently used to specify behavior with security + implications. The effects on security of not implementing a MUST or + SHOULD, or doing something the specification says MUST NOT or SHOULD + NOT be done may be very subtle. Document authors should take the time + to elaborate the security implications of not following + recommendations or requirements as most implementors will not have + had the benefit of the experience and discussion that produced the + specification. + +8. Acknowledgments + + The definitions of these terms are an amalgam of definitions taken + from a number of RFCs. In addition, suggestions have been + incorporated from a number of people including Robert Ullmann, Thomas + Narten, Neal McBurnett, and Robert Elz. + + + + + + + + + + + + +Bradner Best Current Practice [Page 2] + +RFC 2119 RFC Key Words March 1997 + + +9. Author's Address + + Scott Bradner + Harvard University + 1350 Mass. Ave. + Cambridge, MA 02138 + + phone - +1 617 495 3864 + + email - sob@harvard.edu + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Bradner Best Current Practice [Page 3] + diff --git a/lib/qCal/docs/rfc/rfc2234.txt b/lib/qCal/docs/rfc/rfc2234.txt new file mode 100644 index 0000000..edea302 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2234.txt @@ -0,0 +1,787 @@ + + + + + + +Network Working Group D. Crocker, Ed. +Request for Comments: 2234 Internet Mail Consortium +Category: Standards Track P. Overell + Demon Internet Ltd. + November 1997 + + + Augmented BNF for Syntax Specifications: ABNF + + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1997). All Rights Reserved. + +TABLE OF CONTENTS + + 1. INTRODUCTION .................................................. 2 + + 2. RULE DEFINITION ............................................... 2 + 2.1 RULE NAMING .................................................. 2 + 2.2 RULE FORM .................................................... 3 + 2.3 TERMINAL VALUES .............................................. 3 + 2.4 EXTERNAL ENCODINGS ........................................... 5 + + 3. OPERATORS ..................................................... 5 + 3.1 CONCATENATION RULE1 RULE2 ............................. 5 + 3.2 ALTERNATIVES RULE1 / RULE2 ................................... 6 + 3.3 INCREMENTAL ALTERNATIVES RULE1 =/ RULE2 .................... 6 + 3.4 VALUE RANGE ALTERNATIVES %C##-## ........................... 7 + 3.5 SEQUENCE GROUP (RULE1 RULE2) ................................. 7 + 3.6 VARIABLE REPETITION *RULE .................................... 8 + 3.7 SPECIFIC REPETITION NRULE .................................... 8 + 3.8 OPTIONAL SEQUENCE [RULE] ..................................... 8 + 3.9 ; COMMENT .................................................... 8 + 3.10 OPERATOR PRECEDENCE ......................................... 9 + + 4. ABNF DEFINITION OF ABNF ....................................... 9 + + 5. SECURITY CONSIDERATIONS ....................................... 10 + + + + +Crocker & Overell Standards Track [Page 1] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + 6. APPENDIX A - CORE ............................................. 11 + 6.1 CORE RULES ................................................... 11 + 6.2 COMMON ENCODING .............................................. 12 + + 7. ACKNOWLEDGMENTS ............................................... 12 + + 8. REFERENCES .................................................... 13 + + 9. CONTACT ....................................................... 13 + + 10. FULL COPYRIGHT STATEMENT ..................................... 14 + +1. INTRODUCTION + + Internet technical specifications often need to define a format + syntax and are free to employ whatever notation their authors deem + useful. Over the years, a modified version of Backus-Naur Form + (BNF), called Augmented BNF (ABNF), has been popular among many + Internet specifications. It balances compactness and simplicity, + with reasonable representational power. In the early days of the + Arpanet, each specification contained its own definition of ABNF. + This included the email specifications, RFC733 and then RFC822 which + have come to be the common citations for defining ABNF. The current + document separates out that definition, to permit selective + reference. Predictably, it also provides some modifications and + enhancements. + + The differences between standard BNF and ABNF involve naming rules, + repetition, alternatives, order-independence, and value ranges. + Appendix A (Core) supplies rule definitions and encoding for a core + lexical analyzer of the type common to several Internet + specifications. It is provided as a convenience and is otherwise + separate from the meta language defined in the body of this document, + and separate from its formal status. + +2. RULE DEFINITION + +2.1 Rule Naming + + The name of a rule is simply the name itself; that is, a sequence of + characters, beginning with an alphabetic character, and followed by + a combination of alphabetics, digits and hyphens (dashes). + + NOTE: Rule names are case-insensitive + + The names , , and all refer + to the same rule. + + + + +Crocker & Overell Standards Track [Page 2] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + Unlike original BNF, angle brackets ("<", ">") are not required. + However, angle brackets may be used around a rule name whenever their + presence will facilitate discerning the use of a rule name. This is + typically restricted to rule name references in free-form prose, or + to distinguish partial rules that combine into a string not separated + by white space, such as shown in the discussion about repetition, + below. + +2.2 Rule Form + + A rule is defined by the following sequence: + + name = elements crlf + + where is the name of the rule, is one or more rule + names or terminal specifications and is the end-of- line + indicator, carriage return followed by line feed. The equal sign + separates the name from the definition of the rule. The elements + form a sequence of one or more rule names and/or value definitions, + combined according to the various operators, defined in this + document, such as alternative and repetition. + + For visual ease, rule definitions are left aligned. When a rule + requires multiple lines, the continuation lines are indented. The + left alignment and indentation are relative to the first lines of the + ABNF rules and need not match the left margin of the document. + +2.3 Terminal Values + + Rules resolve into a string of terminal values, sometimes called + characters. In ABNF a character is merely a non-negative integer. + In certain contexts a specific mapping (encoding) of values into a + character set (such as ASCII) will be specified. + + Terminals are specified by one or more numeric characters with the + base interpretation of those characters indicated explicitly. The + following bases are currently defined: + + b = binary + + d = decimal + + x = hexadecimal + + + + + + + + +Crocker & Overell Standards Track [Page 3] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + Hence: + + CR = %d13 + + CR = %x0D + + respectively specify the decimal and hexadecimal representation of + [US-ASCII] for carriage return. + + A concatenated string of such values is specified compactly, using a + period (".") to indicate separation of characters within that value. + Hence: + + CRLF = %d13.10 + + ABNF permits specifying literal text string directly, enclosed in + quotation-marks. Hence: + + command = "command string" + + Literal text strings are interpreted as a concatenated set of + printable characters. + + NOTE: ABNF strings are case-insensitive and + the character set for these strings is us-ascii. + + Hence: + + rulename = "abc" + + and: + + rulename = "aBc" + + will match "abc", "Abc", "aBc", "abC", "ABc", "aBC", "AbC" and "ABC". + + To specify a rule which IS case SENSITIVE, + specify the characters individually. + + For example: + + rulename = %d97 %d98 %d99 + + or + + rulename = %d97.98.99 + + + + + +Crocker & Overell Standards Track [Page 4] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + will match only the string which comprises only lowercased + characters, abc. + +2.4 External Encodings + + External representations of terminal value characters will vary + according to constraints in the storage or transmission environment. + Hence, the same ABNF-based grammar may have multiple external + encodings, such as one for a 7-bit US-ASCII environment, another for + a binary octet environment and still a different one when 16-bit + Unicode is used. Encoding details are beyond the scope of ABNF, + although Appendix A (Core) provides definitions for a 7-bit US-ASCII + environment as has been common to much of the Internet. + + By separating external encoding from the syntax, it is intended that + alternate encoding environments can be used for the same syntax. + +3. OPERATORS + +3.1 Concatenation Rule1 Rule2 + + A rule can define a simple, ordered string of values -- i.e., a + concatenation of contiguous characters -- by listing a sequence of + rule names. For example: + + foo = %x61 ; a + + bar = %x62 ; b + + mumble = foo bar foo + + So that the rule matches the lowercase string "aba". + + LINEAR WHITE SPACE: Concatenation is at the core of the ABNF + parsing model. A string of contiguous characters (values) is + parsed according to the rules defined in ABNF. For Internet + specifications, there is some history of permitting linear white + space (space and horizontal tab) to be freelyPand + implicitlyPinterspersed around major constructs, such as + delimiting special characters or atomic strings. + + NOTE: This specification for ABNF does not + provide for implicit specification of linear white + space. + + Any grammar which wishes to permit linear white space around + delimiters or string segments must specify it explicitly. It is + often useful to provide for such white space in "core" rules that are + + + +Crocker & Overell Standards Track [Page 5] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + then used variously among higher-level rules. The "core" rules might + be formed into a lexical analyzer or simply be part of the main + ruleset. + +3.2 Alternatives Rule1 / Rule2 + + Elements separated by forward slash ("/") are alternatives. + Therefore, + + foo / bar + + will accept or . + + NOTE: A quoted string containing alphabetic + characters is special form for specifying alternative + characters and is interpreted as a non-terminal + representing the set of combinatorial strings with the + contained characters, in the specified order but with + any mixture of upper and lower case.. + +3.3 Incremental Alternatives Rule1 =/ Rule2 + + It is sometimes convenient to specify a list of alternatives in + fragments. That is, an initial rule may match one or more + alternatives, with later rule definitions adding to the set of + alternatives. This is particularly useful for otherwise- independent + specifications which derive from the same parent rule set, such as + often occurs with parameter lists. ABNF permits this incremental + definition through the construct: + + oldrule =/ additional-alternatives + + So that the rule set + + ruleset = alt1 / alt2 + + ruleset =/ alt3 + + ruleset =/ alt4 / alt5 + + is the same as specifying + + ruleset = alt1 / alt2 / alt3 / alt4 / alt5 + + + + + + + + +Crocker & Overell Standards Track [Page 6] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +3.4 Value Range Alternatives %c##-## + + A range of alternative numeric values can be specified compactly, + using dash ("-") to indicate the range of alternative values. Hence: + + DIGIT = %x30-39 + + is equivalent to: + + DIGIT = "0" / "1" / "2" / "3" / "4" / "5" / "6" / + + "7" / "8" / "9" + + Concatenated numeric values and numeric value ranges can not be + specified in the same string. A numeric value may use the dotted + notation for concatenation or it may use the dash notation to specify + one value range. Hence, to specify one printable character, between + end of line sequences, the specification could be: + + char-line = %x0D.0A %x20-7E %x0D.0A + +3.5 Sequence Group (Rule1 Rule2) + + Elements enclosed in parentheses are treated as a single element, + whose contents are STRICTLY ORDERED. Thus, + + elem (foo / bar) blat + + which matches (elem foo blat) or (elem bar blat). + + elem foo / bar blat + + matches (elem foo) or (bar blat). + + NOTE: It is strongly advised to use grouping + notation, rather than to rely on proper reading of + "bare" alternations, when alternatives consist of + multiple rule names or literals. + + Hence it is recommended that instead of the above form, the form: + + (elem foo) / (bar blat) + + be used. It will avoid misinterpretation by casual readers. + + The sequence group notation is also used within free text to set off + an element sequence from the prose. + + + + +Crocker & Overell Standards Track [Page 7] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +3.6 Variable Repetition *Rule + + The operator "*" preceding an element indicates repetition. The full + form is: + + *element + + where and are optional decimal values, indicating at least + and at most occurrences of element. + + Default values are 0 and infinity so that * allows any + number, including zero; 1* requires at least one; + 3*3 allows exactly 3 and 1*2 allows one or two. + +3.7 Specific Repetition nRule + + A rule of the form: + + element + + is equivalent to + + *element + + That is, exactly occurrences of . Thus 2DIGIT is a + 2-digit number, and 3ALPHA is a string of three alphabetic + characters. + +3.8 Optional Sequence [RULE] + + Square brackets enclose an optional element sequence: + + [foo bar] + + is equivalent to + + *1(foo bar). + +3.9 ; Comment + + A semi-colon starts a comment that continues to the end of line. + This is a simple way of including useful notes in parallel with the + specifications. + + + + + + + + +Crocker & Overell Standards Track [Page 8] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +3.10 Operator Precedence + + The various mechanisms described above have the following precedence, + from highest (binding tightest) at the top, to lowest and loosest at + the bottom: + + Strings, Names formation + Comment + Value range + Repetition + Grouping, Optional + Concatenation + Alternative + + Use of the alternative operator, freely mixed with concatenations can + be confusing. + + Again, it is recommended that the grouping operator be used to + make explicit concatenation groups. + +4. ABNF DEFINITION OF ABNF + + This syntax uses the rules provided in Appendix A (Core). + + rulelist = 1*( rule / (*c-wsp c-nl) ) + + rule = rulename defined-as elements c-nl + ; continues if next line starts + ; with white space + + rulename = ALPHA *(ALPHA / DIGIT / "-") + + defined-as = *c-wsp ("=" / "=/") *c-wsp + ; basic rules definition and + ; incremental alternatives + + elements = alternation *c-wsp + + c-wsp = WSP / (c-nl WSP) + + c-nl = comment / CRLF + ; comment or newline + + comment = ";" *(WSP / VCHAR) CRLF + + alternation = concatenation + *(*c-wsp "/" *c-wsp concatenation) + + + + +Crocker & Overell Standards Track [Page 9] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + concatenation = repetition *(1*c-wsp repetition) + + repetition = [repeat] element + + repeat = 1*DIGIT / (*DIGIT "*" *DIGIT) + + element = rulename / group / option / + char-val / num-val / prose-val + + group = "(" *c-wsp alternation *c-wsp ")" + + option = "[" *c-wsp alternation *c-wsp "]" + + char-val = DQUOTE *(%x20-21 / %x23-7E) DQUOTE + ; quoted string of SP and VCHAR + without DQUOTE + + num-val = "%" (bin-val / dec-val / hex-val) + + bin-val = "b" 1*BIT + [ 1*("." 1*BIT) / ("-" 1*BIT) ] + ; series of concatenated bit values + ; or single ONEOF range + + dec-val = "d" 1*DIGIT + [ 1*("." 1*DIGIT) / ("-" 1*DIGIT) ] + + hex-val = "x" 1*HEXDIG + [ 1*("." 1*HEXDIG) / ("-" 1*HEXDIG) ] + + prose-val = "<" *(%x20-3D / %x3F-7E) ">" + ; bracketed string of SP and VCHAR + without angles + ; prose description, to be used as + last resort + + +5. SECURITY CONSIDERATIONS + + Security is truly believed to be irrelevant to this document. + + + + + + + + + + + +Crocker & Overell Standards Track [Page 10] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +6. APPENDIX A - CORE + + This Appendix is provided as a convenient core for specific grammars. + The definitions may be used as a core set of rules. + +6.1 Core Rules + + Certain basic rules are in uppercase, such as SP, HTAB, CRLF, + DIGIT, ALPHA, etc. + + ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + + BIT = "0" / "1" + + CHAR = %x01-7F + ; any 7-bit US-ASCII character, + excluding NUL + + CR = %x0D + ; carriage return + + CRLF = CR LF + ; Internet standard newline + + CTL = %x00-1F / %x7F + ; controls + + DIGIT = %x30-39 + ; 0-9 + + DQUOTE = %x22 + ; " (Double Quote) + + HEXDIG = DIGIT / "A" / "B" / "C" / "D" / "E" / "F" + + HTAB = %x09 + ; horizontal tab + + LF = %x0A + ; linefeed + + LWSP = *(WSP / CRLF WSP) + ; linear white space (past newline) + + OCTET = %x00-FF + ; 8 bits of data + + SP = %x20 + + + +Crocker & Overell Standards Track [Page 11] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + + ; space + + VCHAR = %x21-7E + ; visible (printing) characters + + WSP = SP / HTAB + ; white space + +6.2 Common Encoding + + Externally, data are represented as "network virtual ASCII", namely + 7-bit US-ASCII in an 8-bit field, with the high (8th) bit set to + zero. A string of values is in "network byte order" with the + higher-valued bytes represented on the left-hand side and being sent + over the network first. + +7. ACKNOWLEDGMENTS + + The syntax for ABNF was originally specified in RFC 733. Ken L. + Harrenstien, of SRI International, was responsible for re-coding the + BNF into an augmented BNF that makes the representation smaller and + easier to understand. + + This recent project began as a simple effort to cull out the portion + of RFC 822 which has been repeatedly cited by non-email specification + writers, namely the description of augmented BNF. Rather than simply + and blindly converting the existing text into a separate document, + the working group chose to give careful consideration to the + deficiencies, as well as benefits, of the existing specification and + related specifications available over the last 15 years and therefore + to pursue enhancement. This turned the project into something rather + more ambitious than first intended. Interestingly the result is not + massively different from that original, although decisions such as + removing the list notation came as a surprise. + + The current round of specification was part of the DRUMS working + group, with significant contributions from Jerome Abela , Harald + Alvestrand, Robert Elz, Roger Fajman, Aviva Garrett, Tom Harsch, Dan + Kohn, Bill McQuillan, Keith Moore, Chris Newman , Pete Resnick and + Henning Schulzrinne. + + + + + + + + + + + +Crocker & Overell Standards Track [Page 12] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +8. REFERENCES + + [US-ASCII] Coded Character Set--7-Bit American Standard Code for + Information Interchange, ANSI X3.4-1986. + + [RFC733] Crocker, D., Vittal, J., Pogran, K., and D. Henderson, + "Standard for the Format of ARPA Network Text Message," RFC 733, + November 1977. + + [RFC822] Crocker, D., "Standard for the Format of ARPA Internet Text + Messages", STD 11, RFC 822, August 1982. + +9. CONTACT + + David H. Crocker Paul Overell + + Internet Mail Consortium Demon Internet Ltd + 675 Spruce Dr. Dorking Business Park + Sunnyvale, CA 94086 USA Dorking + Surrey, RH4 1HN + UK + + Phone: +1 408 246 8253 + Fax: +1 408 249 6205 + EMail: dcrocker@imc.org paulo@turnpike.com + + + + + + + + + + + + + + + + + + + + + + + + + + +Crocker & Overell Standards Track [Page 13] + +RFC 2234 ABNF for Syntax Specifications November 1997 + + +10. Full Copyright Statement + + Copyright (C) The Internet Society (1997). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Crocker & Overell Standards Track [Page 14] + diff --git a/lib/qCal/docs/rfc/rfc2279.txt b/lib/qCal/docs/rfc/rfc2279.txt new file mode 100644 index 0000000..3a3495c --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2279.txt @@ -0,0 +1,563 @@ + + + + + + +Network Working Group F. Yergeau +Request for Comments: 2279 Alis Technologies +Obsoletes: 2044 January 1998 +Category: Standards Track + + + UTF-8, a transformation format of ISO 10646 + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +Abstract + + ISO/IEC 10646-1 defines a multi-octet character set called the + Universal Character Set (UCS) which encompasses most of the world's + writing systems. Multi-octet characters, however, are not compatible + with many current applications and protocols, and this has led to the + development of a few so-called UCS transformation formats (UTF), each + with different characteristics. UTF-8, the object of this memo, has + the characteristic of preserving the full US-ASCII range, providing + compatibility with file systems, parsers and other software that rely + on US-ASCII values but are transparent to other values. This memo + updates and replaces RFC 2044, in particular addressing the question + of versions of the relevant standards. + +1. Introduction + + ISO/IEC 10646-1 [ISO-10646] defines a multi-octet character set + called the Universal Character Set (UCS), which encompasses most of + the world's writing systems. Two multi-octet encodings are defined, + a four-octet per character encoding called UCS-4 and a two-octet per + character encoding called UCS-2, able to address only the first 64K + characters of the UCS (the Basic Multilingual Plane, BMP), outside of + which there are currently no assignments. + + It is noteworthy that the same set of characters is defined by the + Unicode standard [UNICODE], which further defines additional + character properties and other application details of great interest + to implementors, but does not have the UCS-4 encoding. Up to the + + + +Yergeau Standards Track [Page 1] + +RFC 2279 UTF-8 January 1998 + + + present time, changes in Unicode and amendments to ISO/IEC 10646 have + tracked each other, so that the character repertoires and code point + assignments have remained in sync. The relevant standardization + committees have committed to maintain this very useful synchronism. + + The UCS-2 and UCS-4 encodings, however, are hard to use in many + current applications and protocols that assume 8 or even 7 bit + characters. Even newer systems able to deal with 16 bit characters + cannot process UCS-4 data. This situation has led to the development + of so-called UCS transformation formats (UTF), each with different + characteristics. + + UTF-1 has only historical interest, having been removed from ISO/IEC + 10646. UTF-7 has the quality of encoding the full BMP repertoire + using only octets with the high-order bit clear (7 bit US-ASCII + values, [US-ASCII]), and is thus deemed a mail-safe encoding + ([RFC2152]). UTF-8, the object of this memo, uses all bits of an + octet, but has the quality of preserving the full US-ASCII range: + US-ASCII characters are encoded in one octet having the normal US- + ASCII value, and any octet with such a value can only stand for an + US-ASCII character, and nothing else. + + UTF-16 is a scheme for transforming a subset of the UCS-4 repertoire + into pairs of UCS-2 values from a reserved range. UTF-16 impacts + UTF-8 in that UCS-2 values from the reserved range must be treated + specially in the UTF-8 transformation. + + UTF-8 encodes UCS-2 or UCS-4 characters as a varying number of + octets, where the number of octets, and the value of each, depend on + the integer value assigned to the character in ISO/IEC 10646. This + transformation format has the following characteristics (all values + are in hexadecimal): + + - Character values from 0000 0000 to 0000 007F (US-ASCII repertoire) + correspond to octets 00 to 7F (7 bit US-ASCII values). A direct + consequence is that a plain ASCII string is also a valid UTF-8 + string. + + - US-ASCII values do not appear otherwise in a UTF-8 encoded + character stream. This provides compatibility with file systems + or other software (e.g. the printf() function in C libraries) that + parse based on US-ASCII values but are transparent to other + values. + + - Round-trip conversion is easy between UTF-8 and either of UCS-4, + UCS-2. + + + + + +Yergeau Standards Track [Page 2] + +RFC 2279 UTF-8 January 1998 + + + - The first octet of a multi-octet sequence indicates the number of + octets in the sequence. + + - The octet values FE and FF never appear. + + - Character boundaries are easily found from anywhere in an octet + stream. + + - The lexicographic sorting order of UCS-4 strings is preserved. Of + course this is of limited interest since the sort order is not + culturally valid in either case. + + - The Boyer-Moore fast search algorithm can be used with UTF-8 data. + + - UTF-8 strings can be fairly reliably recognized as such by a + simple algorithm, i.e. the probability that a string of characters + in any other encoding appears as valid UTF-8 is low, diminishing + with increasing string length. + + UTF-8 was originally a project of the X/Open Joint + Internationalization Group XOJIG with the objective to specify a File + System Safe UCS Transformation Format [FSS-UTF] that is compatible + with UNIX systems, supporting multilingual text in a single encoding. + The original authors were Gary Miller, Greger Leijonhufvud and John + Entenmann. Later, Ken Thompson and Rob Pike did significant work for + the formal UTF-8. + + A description can also be found in Unicode Technical Report #4 and in + the Unicode Standard, version 2.0 [UNICODE]. The definitive + reference, including provisions for UTF-16 data within UTF-8, is + Annex R of ISO/IEC 10646-1 [ISO-10646]. + +2. UTF-8 definition + + In UTF-8, characters are encoded using sequences of 1 to 6 octets. + The only octet of a "sequence" of one has the higher-order bit set to + 0, the remaining 7 bits being used to encode the character value. In + a sequence of n octets, n>1, the initial octet has the n higher-order + bits set to 1, followed by a bit set to 0. The remaining bit(s) of + that octet contain bits from the value of the character to be + encoded. The following octet(s) all have the higher-order bit set to + 1 and the following bit set to 0, leaving 6 bits in each to contain + bits from the character to be encoded. + + The table below summarizes the format of these different octet types. + The letter x indicates bits available for encoding bits of the UCS-4 + character value. + + + + +Yergeau Standards Track [Page 3] + +RFC 2279 UTF-8 January 1998 + + + UCS-4 range (hex.) UTF-8 octet sequence (binary) + 0000 0000-0000 007F 0xxxxxxx + 0000 0080-0000 07FF 110xxxxx 10xxxxxx + 0000 0800-0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx + + 0001 0000-001F FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + 0020 0000-03FF FFFF 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + 0400 0000-7FFF FFFF 1111110x 10xxxxxx ... 10xxxxxx + + Encoding from UCS-4 to UTF-8 proceeds as follows: + + 1) Determine the number of octets required from the character value + and the first column of the table above. It is important to note + that the rows of the table are mutually exclusive, i.e. there is + only one valid way to encode a given UCS-4 character. + + 2) Prepare the high-order bits of the octets as per the second column + of the table. + + 3) Fill in the bits marked x from the bits of the character value, + starting from the lower-order bits of the character value and + putting them first in the last octet of the sequence, then the + next to last, etc. until all x bits are filled in. + + The algorithm for encoding UCS-2 (or Unicode) to UTF-8 can be + obtained from the above, in principle, by simply extending each + UCS-2 character with two zero-valued octets. However, pairs of + UCS-2 values between D800 and DFFF (surrogate pairs in Unicode + parlance), being actually UCS-4 characters transformed through + UTF-16, need special treatment: the UTF-16 transformation must be + undone, yielding a UCS-4 character that is then transformed as + above. + + Decoding from UTF-8 to UCS-4 proceeds as follows: + + 1) Initialize the 4 octets of the UCS-4 character with all bits set + to 0. + + 2) Determine which bits encode the character value from the number of + octets in the sequence and the second column of the table above + (the bits marked x). + + 3) Distribute the bits from the sequence to the UCS-4 character, + first the lower-order bits from the last octet of the sequence and + proceeding to the left until no x bits are left. + + If the UTF-8 sequence is no more than three octets long, decoding + can proceed directly to UCS-2. + + + +Yergeau Standards Track [Page 4] + +RFC 2279 UTF-8 January 1998 + + + NOTE -- actual implementations of the decoding algorithm above + should protect against decoding invalid sequences. For + instance, a naive implementation may (wrongly) decode the + invalid UTF-8 sequence C0 80 into the character U+0000, which + may have security consequences and/or cause other problems. See + the Security Considerations section below. + + A more detailed algorithm and formulae can be found in [FSS_UTF], + [UNICODE] or Annex R to [ISO-10646]. + +3. Versions of the standards + + ISO/IEC 10646 is updated from time to time by published amendments; + similarly, different versions of the Unicode standard exist: 1.0, 1.1 + and 2.0 as of this writing. Each new version obsoletes and replaces + the previous one, but implementations, and more significantly data, + are not updated instantly. + + In general, the changes amount to adding new characters, which does + not pose particular problems with old data. Amendment 5 to ISO/IEC + 10646, however, has moved and expanded the Korean Hangul block, + thereby making any previous data containing Hangul characters invalid + under the new version. Unicode 2.0 has the same difference from + Unicode 1.1. The official justification for allowing such an + incompatible change was that no implementations and no data + containing Hangul existed, a statement that is likely to be true but + remains unprovable. The incident has been dubbed the "Korean mess", + and the relevant committees have pledged to never, ever again make + such an incompatible change. + + New versions, and in particular any incompatible changes, have q + conseuences regarding MIME character encoding labels, to be discussed + in section 5. + +4. Examples + + The UCS-2 sequence "A." (0041, 2262, 0391, + 002E) may be encoded in UTF-8 as follows: + + 41 E2 89 A2 CE 91 2E + + The UCS-2 sequence representing the Hangul characters for the Korean + word "hangugo" (D55C, AD6D, C5B4) may be encoded as follows: + + ED 95 9C EA B5 AD EC 96 B4 + + + + + + +Yergeau Standards Track [Page 5] + +RFC 2279 UTF-8 January 1998 + + + The UCS-2 sequence representing the Han characters for the Japanese + word "nihongo" (65E5, 672C, 8A9E) may be encoded as follows: + + E6 97 A5 E6 9C AC E8 AA 9E + +5. MIME registration + + This memo is meant to serve as the basis for registration of a MIME + character set parameter (charset) [CHARSET-REG]. The proposed + charset parameter value is "UTF-8". This string labels media types + containing text consisting of characters from the repertoire of + ISO/IEC 10646 including all amendments at least up to amendment 5 + (Korean block), encoded to a sequence of octets using the encoding + scheme outlined above. UTF-8 is suitable for use in MIME content + types under the "text" top-level type. + + It is noteworthy that the label "UTF-8" does not contain a version + identification, referring generically to ISO/IEC 10646. This is + intentional, the rationale being as follows: + + A MIME charset label is designed to give just the information needed + to interpret a sequence of bytes received on the wire into a sequence + of characters, nothing more (see RFC 2045, section 2.2, in [MIME]). + As long as a character set standard does not change incompatibly, + version numbers serve no purpose, because one gains nothing by + learning from the tag that newly assigned characters may be received + that one doesn't know about. The tag itself doesn't teach anything + about the new characters, which are going to be received anyway. + + Hence, as long as the standards evolve compatibly, the apparent + advantage of having labels that identify the versions is only that, + apparent. But there is a disadvantage to such version-dependent + labels: when an older application receives data accompanied by a + newer, unknown label, it may fail to recognize the label and be + completely unable to deal with the data, whereas a generic, known + label would have triggered mostly correct processing of the data, + which may well not contain any new characters. + + Now the "Korean mess" (ISO/IEC 10646 amendment 5) is an incompatible + change, in principle contradicting the appropriateness of a version + independent MIME charset label as described above. But the + compatibility problem can only appear with data containing Korean + Hangul characters encoded according to Unicode 1.1 (or equivalently + ISO/IEC 10646 before amendment 5), and there is arguably no such data + to worry about, this being the very reason the incompatible change + was deemed acceptable. + + + + + +Yergeau Standards Track [Page 6] + +RFC 2279 UTF-8 January 1998 + + + In practice, then, a version-independent label is warranted, provided + the label is understood to refer to all versions after Amendment 5, + and provided no incompatible change actually occurs. Should + incompatible changes occur in a later version of ISO/IEC 10646, the + MIME charset label defined here will stay aligned with the previous + version until and unless the IETF specifically decides otherwise. + + It is also proposed to register the charset parameter value + "UNICODE-1-1-UTF-8", for the exclusive purpose of labelling text data + containing Hangul syllables encoded to UTF-8 without taking into + account Amendment 5 of ISO/IEC 10646 (i.e. using the pre-amendment 5 + code point assignments). Any other UTF-8 data SHOULD NOT use this + label, in particular data not containing any Hangul syllables, and it + is felt important to strongly recommend against creating any new + Hangul-containing data without taking Amendment 5 of ISO/IEC 10646 + into account. + +6. Security Considerations + + Implementors of UTF-8 need to consider the security aspects of how + they handle illegal UTF-8 sequences. It is conceivable that in some + circumstances an attacker would be able to exploit an incautious + UTF-8 parser by sending it an octet sequence that is not permitted by + the UTF-8 syntax. + + A particularly subtle form of this attack could be carried out + against a parser which performs security-critical validity checks + against the UTF-8 encoded form of its input, but interprets certain + illegal octet sequences as characters. For example, a parser might + prohibit the NUL character when encoded as the single-octet sequence + 00, but allow the illegal two-octet sequence C0 80 and interpret it + as a NUL character. Another example might be a parser which + prohibits the octet sequence 2F 2E 2E 2F ("/../"), yet permits the + illegal octet sequence 2F C0 AE 2E 2F. + +Acknowledgments + + The following have participated in the drafting and discussion of + this memo: + + James E. Agenbroad Andries Brouwer + Martin J. D|rst Ned Freed + David Goldsmith Edwin F. Hart + Kent Karlsson Markus Kuhn + Michael Kung Alain LaBonte + John Gardiner Myers Murray Sargent + Keld Simonsen Arnold Winkler + + + + +Yergeau Standards Track [Page 7] + +RFC 2279 UTF-8 January 1998 + + +Bibliography + + [CHARSET-REG] Freed, N., and J. Postel, "IANA Charset Registration + Procedures", BCP 19, RFC 2278, January 1998. + + [FSS_UTF] X/Open CAE Specification C501 ISBN 1-85912-082-2 28cm. + 22p. pbk. 172g. 4/95, X/Open Company Ltd., "File + System Safe UCS Transformation Format (FSS_UTF)", + X/Open Preleminary Specification, Document Number + P316. Also published in Unicode Technical Report #4. + + [ISO-10646] ISO/IEC 10646-1:1993. International Standard -- + Information technology -- Universal Multiple-Octet + Coded Character Set (UCS) -- Part 1: Architecture and + Basic Multilingual Plane. Five amendments and a + technical corrigendum have been published up to now. + UTF-8 is described in Annex R, published as Amendment + 2. UTF-16 is described in Annex Q, published as + Amendment 1. 17 other amendments are currently at + various stages of standardization. + + [MIME] Freed, N., and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) Part One: Format of Internet + Message Bodies", RFC 2045. N. Freed, N. Borenstein, + "Multipurpose Internet Mail Extensions (MIME) Part + Two: Media Types", RFC 2046. K. Moore, "MIME + (Multipurpose Internet Mail Extensions) Part Three: + Message Header Extensions for Non-ASCII Text", RFC + 2047. N. Freed, J. Klensin, J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: + Registration Procedures", RFC 2048. N. Freed, N. + Borenstein, " Multipurpose Internet Mail Extensions + (MIME) Part Five: Conformance Criteria and Examples", + RFC 2049. All November 1996. + + [RFC2152] Goldsmith, D., and M. Davis, "UTF-7: A Mail-safe + Transformation Format of Unicode", RFC 1642, Taligent + inc., May 1997. (Obsoletes RFC1642) + + [UNICODE] The Unicode Consortium, "The Unicode Standard -- + Version 2.0", Addison-Wesley, 1996. + + [US-ASCII] Coded Character Set--7-bit American Standard Code for + Information Interchange, ANSI X3.4-1986. + + + + + + + +Yergeau Standards Track [Page 8] + +RFC 2279 UTF-8 January 1998 + + +Author's Address + + Francois Yergeau + Alis Technologies + 100, boul. Alexis-Nihon + Suite 600 + Montreal QC H4M 2P2 + Canada + + Phone: +1 (514) 747-2547 + Fax: +1 (514) 747-2561 + EMail: fyergeau@alis.com + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Yergeau Standards Track [Page 9] + +RFC 2279 UTF-8 January 1998 + + +Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Yergeau Standards Track [Page 10] + diff --git a/lib/qCal/docs/rfc/rfc2425.txt b/lib/qCal/docs/rfc/rfc2425.txt new file mode 100644 index 0000000..2e37e24 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2425.txt @@ -0,0 +1,1851 @@ + + + + + + +Network Working Group T. Howes +Request for Comments: 2425 M. Smith +Category: Standards Track Netscape Communications Corp. + F. Dawson + Lotus Development Corporation + September 1998 + + + A MIME Content-Type for Directory Information + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +1. Abstract + + This document defines a MIME Content-Type for holding directory + information. The definition is independent of any particular + directory service or protocol. The text/directory Content-Type is + defined for holding a variety of directory information, for example, + name, or email address, or logo. The text/directory Content-Type can + also be used as the root body part in a multipart/related Content- + Type for handling more complicated situations, especially those in + which non-textual information that already has a natural MIME + representation, for example, a photograph or sound, is to be + represented. + + The text/directory Content-Type defines a general framework and + format for holding directory information in a simple "type:value" + form. We refer to "type" in this context meaning a property or + attribute with which the value is associated. Mechanisms are defined + to specify alternate languages, encodings and other meta-information. + This document also defines the procedure by which particular formats, + called profiles, for carrying application-specific information within + a text/directory Content-Type can be defined and registered, and the + conventions such formats must follow. It is expected that other + documents will be produced that define such formats for various + applications (e.g., white pages). + + + + + +Howes, et. al. Standards Track [Page 1] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this + document are to be interpreted as described in [RFC-2119]. + +2. Table of Contents + + Status of the Memo................................................ 1 + Copyright Notice.................................................. 1 + 1. Abstract...................................................... 1 + 2. Table of Contents............................................. 2 + 3. Need for a MIME Directory Type................................ 3 + 4. Overview...................................................... 4 + 5. The text/directory Content-Type............................... 4 + 5.1. MIME media type name........................................ 4 + 5.2. MIME subtype name........................................... 5 + 5.3. Required parameters......................................... 5 + 5.4. Optional parameters......................................... 5 + 5.5. Encoding considerations..................................... 5 + 5.6. Security considerations..................................... 6 + 5.7. Interoperability considerations............................. 6 + 5.8. Published specification..................................... 6 + 5.8.1. Line delimiting and folding............................... 6 + 5.8.2. ABNF content-type definition.............................. 7 + 5.8.3. Pre-defined Parameters.................................... 9 + 5.8.4. Pre-defined Value Types...................................11 + 5.9. Applications which use this media type......................14 + 5.10. Additional information.....................................14 + 5.11. Person & email address to contact for further information..14 + 5.12. Intended usage.............................................14 + 5.13. Author/Change controller...................................15 + 6. Predefined Types..............................................15 + 6.1. SOURCE Type Definition......................................15 + 6.2. NAME Type Definition........................................16 + 6.3. PROFILE Type Definition.....................................16 + 6.4. BEGIN Type Definition.......................................17 + 6.5. END Type Definition.........................................17 + 7. Use of the multipart/related Content-Type.....................18 + 8. Examples.......................................................18 + 8.1. Example 1...................................................19 + 8.2. Example 2...................................................19 + 8.3. Example 3...................................................20 + 8.4. Example 4...................................................21 + 9. Registration of new profiles..................................22 + 9.1. Define the profile..........................................22 + 9.2. Post the profile definition.................................23 + 9.3. Allow a comment period......................................23 + 9.4. Submit the profile for approval.............................23 + 10. Profile Change Control.......................................23 + + + +Howes, et. al. Standards Track [Page 2] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + 11. Registration of new types....................................24 + 11.1. Define the type............................................24 + 11.2. Post the type definition...................................25 + 11.3. Allow a comment period.....................................25 + 11.4. Submit the type for approval...............................25 + 12. Type Change Control..........................................25 + 13. Registration of new parameters...............................26 + 13.1. Define the parameter.......................................26 + 13.2. Post the parameter definition..............................27 + 13.3. Allow a comment period.....................................27 + 13.4. Submit the parameter for approval..........................27 + 14. Parameter Change Control.....................................28 + 15. Registration of new value types..............................28 + 15.1. Define the value type......................................28 + 15.2. Post the value type definition.............................29 + 15.3. Allow a comment period.....................................29 + 15.4. Submit the value type for approval.........................29 + 16. Security Considerations......................................30 + 17. Acknowledgements..............................................30 + 18. References....................................................30 + 19. Authors' Addresses...........................................32 + 20. Full Copyright Statement......................................33 + +3. Need for a MIME Directory Type + + For purposes of this document, a directory is a special-purpose + database that contains typed information. A directory usually + supports both read and search of the information it contains, and can + support creation and modification of the information as well. + Directory information is usually accessed far more often than it is + updated. Directories can be local or global in scope. They can be + distributed or centralized. The information they contain can be + replicated, with weak or strong consistency requirements. + + There are several situations in which users of Internet mail might + wish to exchange directory information: the email analogy of a + "business card" exchange; the conveyance of directory information to + a user having only email access to the Internet; the provision of + machine-parseable address information when purchasing goods or + services over the Internet; etc. As MIME [RFC-2045, RFC-2046] is + used increasingly by other protocols, most notably HTTP, it can also + be useful for these protocols to carry directory information in MIME + format. Such a format, for example, could be used to represent URC + (uniform resource characteristics) information about resources on the + World Wide Web, or to provide a rudimentary directory service over + HTTP. + + + + + +Howes, et. al. Standards Track [Page 3] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +4. Overview + + The scheme defined here for representing directory information in a + MIME Content-Type has two parts. First, the text/directory Content- + Type is defined for use in holding directory information within a + single body part, for example name, title, or email address. In its + simplest form, the format uses a "type:value" approach, which should + be easily parseable by existing MIME implementations and + understandable by users. More complicated situations can be + represented also. This document defines the general form the + information in the Content-Type should have, and the procedure by + which specific types and values (properties) for particular + applications can be defined. The framework is general enough to + handle information from any number of end directory services, + including LDAP [RFC-1777, RFC-1778], WHOIS++ [RFC-1835], and X.500 + [X500]. + + Directory entries can include far more than just textual information. + Some such information (e.g., an image or sound) overlaps with + predefined MIME Content-Types. In these cases it can be desirable to + include the information in its well-known MIME format. This situation + is handled by using a multipart/related Content-Type as defined in + [RFC-2112]. The root component of this type is a text/directory body + part specifying any in-line information, and for information + contained in other Content-Types, the Content-IDs (in URI form) of + those parts. + + In some applications, it can be useful to include a pointer (e.g, a + URI) to some directory information rather than the information + itself. This document defines a general mechanism for accomplishing + this. + +5. The text/directory Content-Type + + The text/directory Content-Type is used to hold basic directory + information and URIs referencing other information, including other + MIME body parts holding supplementary or non-textual directory + information, such as an image or sound. It is defined as follows, + using the MIME media type registration template from [RFC-2048]. + + To: ietf-types@uninett.no + Subject: Registration of MIME media type text/directory + +5.1. MIME media type name + + MIME media type name: text + + + + + +Howes, et. al. Standards Track [Page 4] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +5.2. MIME subtype name + + MIME subtype name: directory + +5.3. Required parameters + + Required parameters: charset + + The "charset" parameter is as defined in [RFC-2046] for other body + parts. It is used to identify the default character set used within + the body part. + +5.4. Optional parameters + + Optional parameters: profile + + The "profile" parameter is used to convey the type(s) of entity(ies) + to which the directory information pertains and the likely set of + information associated with the entity(ies). It is intended only as a + guide to applications interpreting the information contained within + the body part. It SHOULD NOT be used to exclude or require particular + pieces of information unless a profile definition specifically calls + for this behavior. Unless specifically forbidden by a particular + profile definition, a text/directory content type can contain + arbitrary attribute/value pairs. + + The value of the "profile" parameter is defined as follows. Profile + names are case insensitive (i.e., the profile name "vCard" is the + same as "VCARD" and "vcard" and "vcArD"). + + profile = x-name / iana-token + + x-name = "x-" 1*(ALPHA / DIGIT / "-") + ; Names beginning with "x-" or "X-" are + ; reserved for experimental use not intended for released + ; products, or for use in bilateral agreements. + + iana-token = + +5.5. Encoding considerations + + The default encoding is 8bit. Otherwise, as specified by the + Content-Transfer-Encoding header field. + + + + + + +Howes, et. al. Standards Track [Page 5] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +5.6. Security considerations + + Directory information can be public or it can be protected from + unauthorized access by the directory service in which it resides. + Once the information leaves its native service, there can be no + guarantee that the same care will be taken by all services handling + the information. Furthermore, this specification defines no access + control mechanism by which information can be protected, or by which + access control information can be conveyed. Note that the integrity + and privacy of a text/directory body part can be protected by + enclosing it within an appropriate MIME-based security mechanism. + +5.7. Interoperability considerations + + In order to make sense of directory information, applications must + share a common understanding of the types of information contained + within the Content-Type (the directory schema). This schema + information is not defined in this document, but rather in companion + documents (e.g., [MIME-VCARD]) that follow the requirements specified + in this document, or in bilateral agreements between communicating + parties. + +5.8. Published specification + + The text/directory Content-Type contains directory information, + typically pertaining to a single directory entity or group of + entities. The content consists of one or more lines in the format + given below. + +5.8.1. Line delimiting and folding + + Individual lines within the MIME text/directory Content Type body are + delimited by the [RFC-822] line break, which is a CRLF sequence + (ASCII decimal 13, followed by ASCII decimal 10). Long logical lines + of text can be split into a multiple-physical-line representation + using the following folding technique. + + A logical line MAY be continued on the next physical line anywhere + between two characters by inserting a CRLF immediately followed by a + single white space character (space, ASCII decimal 32, or horizontal + tab, ASCII decimal 9). At least one character must be present on the + folded line. Any sequence of CRLF followed immediately by a single + white space character is ignored (removed) when processing the + content type. For example the line: + + DESCRIPTION:This is a long description that exists on a long line. + + Can be represented as: + + + +Howes, et. al. Standards Track [Page 6] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + DESCRIPTION:This is a long description + that exists on a long line. + + It could also be represented as: + + DESCRIPTION:This is a long descrip + tion that exists o + n a long line. + + The process of moving from this folded multiple-line representation + of a type definition to its single line representation is called + unfolding. Unfolding is accomplished by regarding CRLF immediately + followed by a white space character (namely HTAB ASCII decimal 9 or + SPACE ASCII decimal 32) as equivalent to no characters at all (i.e., + the CRLF and single white space character are removed). + +5.8.2. ABNF content-type definition + + The following ABNF uses the notation of RFC 2234, which also defines + CRLF, WSP, DQUOTE, VCHAR, ALPHA, and DIGIT. After the unfolding of + any folded lines as described above, the syntax for a line of this + content type is as follows: + + contentline = [group "."] name *(";" param) ":" value CRLF + ; When parsing a content line, folded lines MUST first + ; be unfolded according to the unfolding procedure + ; described above. + ; When generating a content line, lines longer than 75 + ; characters SHOULD be folded according to the folding + ; procedure described above. + + group = 1*(ALPHA / DIGIT / "-") + + name = x-name / iana-token + + iana-token = 1*(ALPHA / DIGIT / "-") + ; identifier registered with IANA + + x-name = "x-" 1*(ALPHA / DIGIT / "-") + ; Names that begin with "x-" or "X-" are + ; reserved for experimental use, not intended for released + ; products, or for use in bilateral agreements. + + param = param-name "=" param-value *("," param-value) + + param-name = x-name / iana-token + + param-value = ptext / quoted-string + + + +Howes, et. al. Standards Track [Page 7] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + ptext = *SAFE-CHAR + + value = *VALUE-CHAR + / valuespec ; valuespec defined in section 5.8.4 + + quoted-string = DQUOTE *QSAFE-CHAR DQUOTE + + NON-ASCII = %x80-FF + ; use restricted by charset parameter + ; on outer MIME object (UTF-8 preferred) + + QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII + ; Any character except CTLs, DQUOTE + + SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E / NON-ASCII + ; Any character except CTLs, DQUOTE, ";", ":", "," + + VALUE-CHAR = WSP / VCHAR / NON-ASCII + ; any textual character + + A line that begins with a white space character is a continuation of + the previous line, as described above. The white space character and + immediately preceeding CRLF should be discarded when reconstructing + the original line. Note that this line-folding convention differs + from that found in RFC 822, in that the sequence found + anywhere in the content indicates a continued line and should be + removed. + + Various type names and the format of the corresponding values are + defined as specified in Section 11. Specifications MAY impose + ordering on the type constructs within a body part, though none is + required by default. The various x-name constructs are used for + bilaterally-agreed upon type names, parameter names and parameter + values, or for use in experimental settings. + + Type names and parameter names are case insensitive (e.g., the type + name "fn" is the same as "FN" and "Fn"). Parameter values MAY be case + sensitive or case insensitive, depending on their definition. + + The group construct is used to group related attributes together. + The group name is a syntactic convention used to indicate that all + type names prefaced with the same group name SHOULD be grouped + together when displayed by an application. It has no other + significance. Implementations that do not understand or support + grouping MAY simply strip off any text before a "." to the left of + the type name and present the types and values as normal. + + + + + +Howes, et. al. Standards Track [Page 8] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + Each attribute defined in the text/directory body MAY have multiple + values, if allowed in the definition of the profile in which the + attribute is used. The general rule for encoding multi-valued items + is to simply create a new content line for each value (including the + type name). However, it should be noted that some value types + support encoding multiple values in a single content line by + separating the values with a comma ",". This approach has been taken + for several of the content types defined below (date, time, integer, + float), for space-saving reasons. + +5.8.3. Pre-defined Parameters + + The following parameters and value types are defined for general use. + + predefined-param = encodingparm + / valuetypeparm + / languageparm + / contextparm + + encodingparm = "encoding" "=" encodingtype + + encodingtype = "b" ; from RFC 2047 + / iana-token ; registered as described in + ; section 15 of this document + + valuetypeparm = "value" "=" valuetype + + valuetype = "uri" ; genericurl from secion 5 of RFC 1738 + / "text" + / "date" + / "time" + / "date-time" ; date time + / "integer" + / "boolean" + / "float" + / x-name + / iana-token ; registered as described in + ; section 15 of this document + + languageparm = "language" "=" Language-Tag + ; Language-Tag is defined in section 2 of RFC 1766 + + contextparm = "context" "=" context + + context = x-name + / iana-token + + + + + +Howes, et. al. Standards Track [Page 9] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + The "language" type parameter is used to identify data in multiple + languages. There is no concept of "default" language, except as + specified by any "Content-Language" MIME header parameter that is + present. The value of the "language" type parameter is a language + tag as defined in Section 2 of [RFC-1766]. + + The "context" type parameter is used to identify a context (e.g., a + protocol) used in interpreting the value. This is used, for example, + in the "source" type, defined below. + + The "encoding" type parameter is used to specify an alternate + encoding for a value. If the value contains a CRLF, it must be + encoded, since CRLF is used to separate lines in the content-type + itself. Currently, only the "b" encoding is supported. + + The "b" encoding can also be useful for binary values that are mixed + with other text information in the body part (e.g., a certificate). + Using a per-value "b" encoding in this case leaves the other + information in a more readable form. The encoded base 64 value can be + split across multiple physical lines in the content type by using the + line folding technique described above. + + The Content-Transfer-Encoding header field is used to specify the + encoding used for the body part as a whole. The "encoding" type + parameter is used to specify an encoding for a particular value + (e.g., a certificate). In this case, the Content-Transfer-Encoding + header might specify "8bit", while the one certificate value might + specify an encoding of "b" via an "encoding=b" type parameter. + + The Content-Transfer-Encoding and the encodings of individual types + given by the "encoding" type parameter are independent of one + another. When encoding a text/directory body part for transmission, + individual type encodings are performed first, then the entire body + part is encoded according to the Content-Transfer-Encoding. When + decoding a text/directory body part, the Content-Transfer-Encoding is + decoded first, and then any individual types with an "encoding" type + parameter are decoded. + + The "value" parameter is optional, and is used to identify the value + type (data type) and format of the value. The use of these + predefined formats is encouraged even if the value parameter is not + explicity used. By defining a standard set of value types and their + formats, existing parsing and processing code can be leveraged. + + Including the value type explicitly as part of each property provides + an extra hint to keep parsing simple and support more generalized + applications. For example a search engine would not have to know the + particular value types for all of the items for which it is + + + +Howes, et. al. Standards Track [Page 10] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + searching. Because the value type is explicit in the definition, the + search engine could look for dates in any item type and provide + results that can still be interpreted. + +5.8.4. Pre-defined Value Types + + The format for values corresponding to the predefined valuetype + specifications given above are defined. + + valuespec = text-list + / genericurl ; from section 5 of RFC 1738 + / date-list + / time-list + / date-time-list + / boolean + / integer-list + / float-list + / iana-valuespec + + text-list = *TEXT-LIST-CHAR *("," *TEXT-LIST-CHAR) + + TEXT-LIST-CHAR = "\\" / "\," / "\n" + / + ; Backslashes, newlines, and commas must be encoded. + ; \n or \N can be used to encode a newline. + + date-list = date *("," date) + + time-list = time *("," time) + + date-time-list = date "T" time *("," date "T" time) + + boolean = "TRUE" / "FALSE" + + integer-list = integer *("," integer) + + integer = [sign] 1*DIGIT + + float-list = float *("," float) + + float = [sign] 1*DIGIT ["." 1*DIGIT] + + sign = "+" / "-" + + date = date-fullyear ["-"] date-month ["-"] date-mday + + date-fullyear = 4 DIGIT + + + + +Howes, et. al. Standards Track [Page 11] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + date-month = 2 DIGIT ;01-12 + + date-mday = 2 DIGIT ;01-28, 01-29, 01-30, 01-31 + ;based on month/year + + time = time-hour [":"] time-minute [":"] time-second [time-secfrac] + [time-zone] + + time-hour = 2 DIGIT ;00-23 + + time-minute = 2 DIGIT ;00-59 + + time-second = 2 DIGIT ;00-60 (leap second) + + time-secfrac = "," 1*DIGIT + + time-zone = "Z" / time-numzone + + time-numzome = sign time-hour [":"] time-minute + + iana-valuespec = + + Some specific notes on the value types and formats: + + "text": The "text" value type should be used to identify values that + contain human-readable text. The character set and language in which + the text is represented is controlled by the charset content-header + and the language type parameter and content-header. + + Examples for "text": + this is a text value + this is one value,this is another + this is a single value\, with a comma encoded + + A formatted text line break in a text value type MUST be represented + as the character sequence backslash (ASCII decimal 92) followed by a + Latin small letter n (ASCII decimal 110) or a Latin capital letter N + (ASCII decimal 78), that is "\n" or "\N". + + For example a multiple line DESCRIPTION value of: + + Mythical Manager + Hyjinx Software Division + BabsCo, Inc. + + could be represented as: + + + +Howes, et. al. Standards Track [Page 12] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + DESCRIPTION:Mythical Manager\nHyjinx Software Division\n + BabsCo\, Inc.\n + + demonstrating the \n literal formatted line break technique, the + CRLF-followed-by-space line folding technique, and the backslash + escape technique. + + "uri": The "uri" value type should be used to identify values that + are referenced by a URI (including a Content-ID URI), instead of + encoded in-line. These value references might be used if the value is + too large, or otherwise undesirable to include directly. The format + for the URI is as defined in RFC 1738. + + Examples for "uri": + http://www.foobar.com/my/picture.jpg + ldap://ldap.foobar.com/cn=babs%20jensen + + "date", "time", and "date-time": Each of these value types is based + on a subset of the definitions in ISO 8601 standard. Profiles MAY + place further restrictions on "date" and "time" values. Multiple + "date" and "time" values can be specified using the comma-separated + notation, unless restricted by a profile. + + Examples for "date": + 1985-04-12 + 1996-08-05,1996-11-11 + 19850412 + + Examples for "time": + 10:22:00 + 102200 + 10:22:00.33 + 10:22:00.33Z + 10:22:33,11:22:00 + 10:22:00-08:00 + + Examples for "date-time": + 1996-10-22T14:00:00Z + 1996-08-11T12:34:56Z + 19960811T123456Z + 1996-10-22T14:00:00Z,1996-08-11T12:34:56Z + + "boolean": The "boolean" value type is used to express boolen values. + These values are case insensitive. + + Examples: TRUE + false + True + + + +Howes, et. al. Standards Track [Page 13] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + "integer": The "integer" value type is used to express signed + integers in decimal format. If sign is not specified, the value is + assumed positive "+". Multiple "integer" values can be specified + using the comma-separated notation, unless restricted by a profile. + + Examples: 1234567890 + -1234556790 + +1234556790,432109876 + + "float": The "float" value type is used to express real numbers. If + sign is not specified, the value is assumed positive "+". Multiple + "float" values can be specified using the comma-separated notation, + unless restricted by a profile. + + Examples: 20.30 + 1000000.0000001 + 1.333,3.14 + +5.9. Applications which use this media type + + Applications which use this media type: Various + +5.10. Additional information + + Additional information: None + +5.11. Person & email address to contact for further information + + Tim Howes + Netscape Communications Corp. + 501 East Middlefield Rd. + Mountain View, CA 94041 + USA + howes@netscape.com + +1 415 937 3419 + +5.12. Intended usage + + Intended usage: COMMON + + + + + + + + + + + + +Howes, et. al. Standards Track [Page 14] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +5.13. Author/Change controller + + Tim Howes + Netscape Communications Corp. + 501 East Middlefield Rd. + Mountain View, CA 94041 + USA + howes@netscape.com + +1 415 937 3419 + + Mark Smith + Netscape Communications Corp. + 501 East Middlefield Rd. + Mountain View, CA 94041 + USA + mcs@netscape.com + +1 415 937 3477 + + Frank Dawson + Lotus Development Corporation + 6544 Battleford Drive + Raleigh, NC 27613-3502 + USA + frank_dawson@lotus.com + +1-919-676-9515 + +6. Predefined Types + + The following types are generally useful regardless of the profile + being carried and are defined below using the text/directory MIME + type registration template defined in Section 11.1 of this document. + These types MAY be included in any profile, unless explicitly + forbidden in the profile definition. + +6.1. SOURCE Type Definition + + To: ietf-mime-direct@imc.org + Subject: Registration of text/directory MIME type SOURCE + + Type name: SOURCE + + Type purpose: To identify the source of directory information + contained in the content type. + + Type encoding: 8bit + + Type valuetype: uri + + + + +Howes, et. al. Standards Track [Page 15] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + Type special notes: The SOURCE type is used to provide the means by + which applications knowledgable in the given directory service + protocol can obtain additional or more up-to-date information from + the directory service. It contains a URI as defined in [RFC-1738] + and/or other information referencing the directory entity or entities + to which the information pertains. When directory information is + available from more than one source, the sending entity can pick what + it considers to be the best source, or multiple SOURCE types can be + included. The interpretation of the value for a SOURCE type can + depend on the setting of the CONTEXT type parameter. The value of the + CONTEXT type parameter MUST be compatible with the value of the uri + prefix. + + Type example: + SOURCE;CONTEXT=LDAP:ldap://ldap.host/cn=Babs%20Jensen, + %20o=Babsco,%20c=US + +6.2. NAME Type Definition + + To: ietf-mime-direct@imc.org + Subject: Registration of text/directory MIME type NAME + + Type name: NAME + + Type purpose: To identify the displayable name of the directory + entity to which information in the content type pertains. + + Type encoding: 8bit + + Type valuetype: text + + Type special notes: The NAME type is used to convey the display name + of the entity to which the directory information pertains. + + Type example: + NAME:Babs Jensen's Contact Information + +6.3. PROFILE Type Definition + + To: ietf-mime-direct@imc.org + Subject: Registration of text/directory MIME type PROFILE + + Type name: PROFILE + + Type purpose: To identify the type of directory entity to which + information in the content type pertains. + + Type encoding: 8bit + + + +Howes, et. al. Standards Track [Page 16] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + Type valuetype: A profile name, registered as described in Section 9 + of this document or bilaterally agreed upon as described in Section + 5. + + Type special notes: The PROFILE type is used to convey the type of + the entity to which the directory information in the rest of the body + part pertains. It should be the same as the "profile" header + parameter, if present. + + Type example: + PROFILE:vCard + +6.4. BEGIN Type Definition + + To: ietf-mime-direct@imc.org + Subject: Registration of text/directory MIME type BEGIN + + Type name: BEGIN + + Type purpose: To denote the beginning of a syntactic entity within a + text/directory content-type. + + Type encoding: 8bit + + Type valuetype: text, containing a profile name, registered as + described in Section 9 of this document or bilaterally-agreed upon as + described in Section 5. + + Type special notes: The BEGIN type is used in conjunction with the + END type to delimit a profile containing a related set of properties + within an text/directory content-type. This construct can be used + instead of or in addition to wrapping separate sets of information + inside additional MIME headers. It is provided for applications that + wish to define content that can contain multiple entities within the + same text/directory content-type or to define content that can be + identifiable outside of a MIME environment. + + Type example: + BEGIN:VCARD + +6.5. END Type Definition + + To: ietf-mime-direct@imc.org + Subject: Registration of text/directory MIME type END + + Type name: END + + + + + +Howes, et. al. Standards Track [Page 17] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + Type purpose: To denote the end of a syntactic entity within a + text/directory content-type. + + Type encoding: 8bit + + Type valuetype: text, containing a profile name, registered as + described in Section 9 of this document or bilaterally-agreed upon as + described in Section 5. + + Type special notes: The END type is used in conjunction with the + BEGIN type to delimit a profile containing a related set of + properties within an text/directory content-type. This construct can + be used instead of or in addition to wrapping separate sets of + information inside additional MIME headers. It is provided for + applications that wish to define content that can contain multiple + entities within the same text/directory content-type or to define + content that can be identifiable outside of a MIME environment. + + Type example: + END: VCARD + +7. Use of the multipart/related Content-Type + + The multipart/related Content-Type can be used to hold directory + information comprised of both text and non-text information or + directory information that already has a natural MIME representation. + The root body part within the multipart/related body part is + specified as defined in [RFC-2112] by a "start" parameter, or it is + the first body part in the absence of such a parameter. The root + body part must have a Content-Type of "text/directory". This part + holds inline information and makes reference to subsequent body parts + holding additional text or non-text directory information via their + Content-ID URIs as explained in Section 5. + + The body parts referred to do not have to be in any particular order, + except as noted above for the root body part. + +8. Examples + + The following examples are for illustrative purposes only and are not + part of the definition. + + + + + + + + + + +Howes, et. al. Standards Track [Page 18] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +8.1. Example 1 + + The first example illustrates simple use of the text/directory + Content-Type. Note that no "profile" parameter is given, so an + application may not know what kind of directory entity the + information applies to. Note also the use of both hypothetical + official and bilaterally agreed upon types. + + From: Whomever@wherever.com + To: Someone@somewhere.com + Subject: whatever + MIME-Version: 1.0 + Message-ID: + Content-Type: text/directory + Content-ID: + + cn:Babs Jensen + cn:Barbara J Jensen + sn:Jensen + email:babs@umich.edu + phone:+1 313 747-4454 + x-id:1234567890 + +8.2. Example 2 + + The next example illustrates the use of the Quoted-Printable transfer + encoding defined in [RFC 2045] to include non-ASCII character in some + of the information returned, and the use of the optional "name" and + "source" types. It also illustrates the use of an "encoding" type + parameter to encode a certificate value in "b". A "vCard" profile + [MIME- VCARD] is used for the example. + +Content-Type: text/directory; + charset="iso-8859-1"; + profile="vCard" +Content-ID: +Content-Transfer-Encoding: Quoted-Printable + +begin:VCARD +source:ldap://cn=bjorn%20Jensen, o=university%20of%20Michigan, c=US +name:Bjorn Jensen +fn:Bj=F8rn Jensen +n:Jensen;Bj=F8rn +email;type=internet:bjorn@umich.edu +tel;type=work,voice,msg:+1 313 747-4454 +key;type=x509;encoding=B:dGhpcyBjb3VsZCBiZSAKbXkgY2VydGlmaWNhdGUK +end:VCARD + + + + +Howes, et. al. Standards Track [Page 19] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +8.3. Example 3 + + The next example illustrates the use of multi-valued type parameters, + the "language" type parameter, the "value" type parameter, folding of + long lines, the \n encoding for formatted lines, attribute grouping, + and the inline "b" encoding. A "vCard" profile [MIME-VCARD] is used + for the example. + +Content-Type: text/directory; profile="vcard"; charset=iso-8859-1 +Content-ID: +Content-Transfer-Encoding: Quoted-Printable + +begin:vcard +source:ldap://cn=Meister%20Berger,o=Universitaet%20Goerlitz,c=DE +name:Meister Berger +fn:Meister Berger +n:Berger;Meister +bday;value=date:1963-09-21 +o:Universit=E6t G=F6rlitz +title:Mayor +title;language=de;value=text:Burgermeister +note:The Mayor of the great city of + Goerlitz in the great country of Germany. +email;internet:mb@goerlitz.de +home.tel;type=fax,voice,msg:+49 3581 123456 +home.label:Hufenshlagel 1234\n + 02828 Goerlitz\n + Deutschland +key;type=X509;encoding=b:MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhvcNAQEEBQ + AwdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENvbW11bmljYXRpb25zI + ENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZvcm1hdGlvbiBTeXN0ZW1zMRwwGgYDVQQD + ExNyb290Y2EubmV0c2NhcGUuY29tMB4XDTk3MDYwNjE5NDc1OVoXDTk3MTIwMzE5NDc + 1OVowgYkxCzAJBgNVBAYTAlVTMSYwJAYDVQQKEx1OZXRzY2FwZSBDb21tdW5pY2F0aW + 9ucyBDb3JwLjEYMBYGA1UEAxMPVGltb3RoeSBBIEhvd2VzMSEwHwYJKoZIhvcNAQkBF + hJob3dlc0BuZXRzY2FwZS5jb20xFTATBgoJkiaJk/IsZAEBEwVob3dlczBcMA0GCSqG + SIb3DQEBAQUAA0sAMEgCQQC0JZf6wkg8pLMXHHCUvMfL5H6zjSk4vTTXZpYyrdN2dXc + oX49LKiOmgeJSzoiFKHtLOIboyludF90CgqcxtwKnAgMBAAGjNjA0MBEGCWCGSAGG+E + IBAQQEAwIAoDAfBgNVHSMEGDAWgBT84FToB/GV3jr3mcau+hUMbsQukjANBgkqhkiG9 + w0BAQQFAAOBgQBexv7o7mi3PLXadkmNP9LcIPmx93HGp0Kgyx1jIVMyNgsemeAwBM+M + SlhMfcpbTrONwNjZYW8vJDSoi//yrZlVt9bJbs7MNYZVsyF1unsqaln4/vy6Uawfg8V + UMk1U7jt8LYpo4YULU7UZHPYVUaSgVttImOHZIKi4hlPXBOhcUQ== +end:vcard + + + + + + + + + +Howes, et. al. Standards Track [Page 20] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +8.4. Example 4 + + The final example illustrates the use of the multipart/related + Content-Type to include non-textual directory data via the "uri" + encoding to refer to other body parts within the same message, or to + external values. Note that no "profile" parameter is given, so an + application may not know what kind of directory entity the + information applies to. Note also the use of both hypothetical + official and bilaterally agreed upon types. + +Content-Type: multipart/related; + boundary=woof; + type="text/directory"; + start="" +Content-ID: + +--woof +Content-Type: text/directory; charset="iso-8859-1" +Content-ID: +Content-Transfer-Encoding: Quoted-Printable + +source:ldap://cn=Bjorn%20Jensen,o=University%20of%20Michigan,c=US +cn:Bj=F8rn Jensen +sn:Jensen +email:bjorn@umich.edu +image;value=uri:cid:id6@host.com +image;value=uri;format=jpeg:ftp://some.host/some/path.jpg +sound;value=uri:cid:id7@host.com +phone:+1 313 747-4454 + +--woof +Content-Type: image/jpeg +Content-ID: + +<...image data...> + +--woof +Content-Type: message/external-body; + name="myvoice.au"; + site="myhost.com"; + access-type=ANON-FTP; + directory="pub/myname"; + mode="image" + +Content-Type: audio/basic +Content-ID: + +--woof-- + + + +Howes, et. al. Standards Track [Page 21] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +9. Registration of new profiles + + This section defines procedures by which new profiles are registered + with the IANA and made available to the Internet community. Note that + non-IANA profiles can be used by bilateral agreement, provided the + associated profile names follow the "X-" convention defined above. + + The procedures defined here are designed to allow public comment and + review of new profiles, while posing only a small impediment to the + definition of new profiles. + + Registration of a new profile is accomplished by the following steps. + +9.1. Define the profile + + A profile is defined by completing the following template. + + To: ietf-mime-direct@imc.org + Subject: Registration of text/directory MIME profile XXX + + Profile name: + + Profile purpose: + + Profile types: + + Profile special notes (optional): + + Intended usage: (one of COMMON, LIMITED USE or OBSOLETE) + + The explanation of what goes in each field in the template follows. + + Profile name: The name of the profile as it will appear in the + text/directory MIME Content-Type "profile" header parameter, or the + predefined "profile" type name. + + Profile purpose: The purpose of the profile (e.g., to represent + information about people, printers, documents, etc.). Give a short + but clear description. + + Profile types: The list of types associated with the profile. This + list of types is to be expected but not required in the profile, + unless otherwise noted in the profile definition. Other types not + mentioned in the profile definition MAY also be present. Note that + any new types referenced by the profile MUST be defined separately as + described in Section 10. + + + + + +Howes, et. al. Standards Track [Page 22] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + Profile special notes: Any special notes about the profile, how it is + to be used, etc. This section of the template can also be used to + define an ordering on the types that appear in the Content-Type, if + such an ordering is required. + +9.2. Post the profile definition + + The profile description must be posted to the new profile discussion + list, ietf-mime-direct@imc.org + +9.3. Allow a comment period + + Discussion on the new profile must be allowed to take place on the + list for a minimum of two weeks. Consensus must be reached on the + profile before proceeding to step 4. + +9.4. Submit the profile for approval + + Once the two-week comment period has elapsed, and the proposer is + convinced consensus has been reached on the profile, the registration + application should be submitted to the Profile Reviewer for approval. + The Profile Reviewer is appointed by the Application Area Directors + and can either accept or reject the profile registration. An accepted + registration is passed on by the Profile Reviewer to the IANA for + inclusion in the official IANA profile registry. The registration may + be rejected for any of the following reasons. 1) Insufficient comment + period; 2) Consensus not reached; 3) Technical deficiencies raised on + the list or elsewhere have not been addressed. The Profile Reviewer's + decision to reject a profile can be appealed by the proposer to the + IESG, or the objections raised can be addressed by the proposer and + the profile resubmitted. + +10. Profile Change Control + + Existing profiles can be changed using the same process by which they + were registered. + + Define the change + + Post the change + + Allow a comment period + + Submit the changed profile for approval + + + + + + + +Howes, et. al. Standards Track [Page 23] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + Note that the original author or any other interested party can + propose a change to an existing profile, but that such changes should + only be proposed when there are serious omissions or errors in the + published specification. The Profile Reviewer can object to a change + if it is not backwards compatible, but is not required to do so. + + Profile definitions can never be deleted from the IANA registry, but + profiles which are no longer believed to be useful can be declared + OBSOLETE by a change to their "intended use" field. + +11. Registration of new types + + This section defines procedures by which new types are registered + with the IANA. Note that non-IANA types can be used by bilateral + agreement, provided the associated types names follow the "X-" + convention defined above. + + The procedures defined here are designed to allow public comment and + review of new types, while posing only a small impediment to the + definition of new types. + + Registration of a new type is accomplished by the following steps. + +11.1. Define the type + + A type is defined by completing the following template. + + To: ietf-mime-direct@imc.org + Subject: Registration of text/directory MIME type XXX + + Type name: + + Type purpose: + + Type encoding: + + Type valuetype: + + Type special notes (optional): + + Intended usage: (one of COMMON, LIMITED USE or OBSOLETE) + + The meaning of each field in the template is as follows. + + Type name: The name of the type, as it will appear in the body of an + text/directory MIME Content-Type "type: value" line to the left of + the colon ":". + + + + +Howes, et. al. Standards Track [Page 24] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + Type purpose: The purpose of the type (e.g., to represent a name, + postal address, IP address, etc.). Give a short but clear + description. + + Type encoding: The default encoding a value of the type must have in + the body of a text/directory MIME Content-Type. + + Type valuetype: The format a value of the type must have in the body + of a text/directory MIME Content-Type. This description must be + precise and must not violate the general encoding rules defined in + section 5 of this document. + + Type special notes: Any special notes about the type, how it is to be + used, etc. + +11.2. Post the type definition + + The type description must be posted to the new type discussion list, + ietf-mime-direct@imc.org + +11.3. Allow a comment period + + Discussion on the new type must be allowed to take place on the list + for a minimum of two weeks. Consensus must be reached on the type + before proceeding to step 4. + +11.4. Submit the type for approval + + Once the two-week comment period has elapsed, and the proposer is + convinced consensus has been reached on the type, the registration + application should be submitted to the Profile Reviewer for approval. + The Profile Reviewer is appointed by the Application Area Directors + and can either accept or reject the type registration. An accepted + registration is passed on by the Profile Reviewer to the IANA for + inclusion in the official IANA profile registry. The registration can + be rejected for any of the following reasons. 1) Insufficient comment + period; 2) Consensus not reached; 3) Technical deficiencies raised on + the list or elsewhere have not been addressed. The Profile + Reviewer's decision to reject a type can be appealed by the proposer + to the IESG, or the objections raised can be addressed by the + proposer and the type resubmitted. + + + + + + + + + + +Howes, et. al. Standards Track [Page 25] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +12. Type Change Control + + Existing types can be changed using the same process by which they + were registered. + + Define the change + + Post the change + + Allow a comment period + + Submit the type for approval + + Note that the original author or any other interested party can + propose a change to an existing type, but that such changes should + only be proposed when there are serious omissions or errors in the + published specification. The Profile Reviewer can object to a change + if it is not backwards compatible, but is not required to do so. + + Type definitions can never be deleted from the IANA registry, but + types which are nolonger believed to be useful can be declared + OBSOLETE by a change to their "intended use" field. + +13. Registration of new parameters + + This section defines procedures by which new parameters are + registered with the IANA and made available to the Internet + community. Note that non-IANA parameters can be used by bilateral + agreement, provided the associated parameters names follow the "X-" + convention defined above. + + The procedures defined here are designed to allow public comment and + review of new parameters, while posing only a small impediment to the + definition of new parameters. + + Registration of a new parameter is accomplished by the following + steps. + +13.1. Define the parameter + + A parameter is defined by completing the following template. + + To: ietf-mime-direct@imc.org + Subject: Registration of text/directory MIME type parameter XXX + + Parameter name: + + Parameter purpose: + + + +Howes, et. al. Standards Track [Page 26] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + Parameter values: + + Parameter special notes (optional): + + Intended usage: (one of COMMON, LIMITED USE or OBSOLETE) + + The explanation of what goes in each field in the template follows. + + Parameter name: The name of the parameter as it will appear in the + text/directory MIME Content-Type. + + Parameter purpose: The purpose of the parameter (e.g., to represent + the format of an image, type of a phone number, etc.). Give a short + but clear description. If defining a general paramemter like "format" + or "type" keep in mind that other applications might wish to extend + its use. + + Parameter values: The list or description of values associated with + the parameter. + + Parameter special notes: Any special notes about the parameter, how + it is to be used, etc. + +13.2. Post the parameter definition + + The parameter description must be posted to the new parameter + discussion list, ietf-mime-direct@imc.org + +13.3. Allow a comment period + + Discussion on the new parameter must be allowed to take place on the + list for a minimum of two weeks. Consensus must be reached on the + parameter before proceeding to step 4. + +13.4. Submit the parameter for approval + + Once the two-week comment period has elapsed, and the proposer is + convinced consensus has been reached on the parameter, the + registration application should be submitted to the Profile Reviewer + for approval. The Profile Reviewer is appointed by the Application + Area Directors and can either accept or reject the parameter + registration. An accepted registration is passed on by the Profile + Reviewer to the IANA for inclusion in the official IANA parameter + registry. The registration can be rejected for any of the following + reasons. 1) Insufficient comment period; 2) Consensus not reached; 3) + Technical deficiencies raised on the list or elsewhere have not been + addressed. The Profile Reviewer's decision to reject a profile can be + appealed by the proposer to the IESG, or the objections raised can be + + + +Howes, et. al. Standards Track [Page 27] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + addressed by the proposer and the parameter registration resubmitted. + +14. Parameter Change Control + + Existing parameters can be changed using the same process by which + they were registered. + + Define the change + + Post the change + + Allow a comment period + + Submit the parameter for approval + + Note that the original author or any other interested party can + propose a change to an existing parameter, but that such changes + should only be proposed when there are serious omissions or errors in + the published specification. The Profile Reviewer can object to a + change if it is not backwards compatible, but is not required to do + so. + + Parameter definitions can never be deleted from the IANA registry, + but parameters which are nolonger believed to be useful can be + declared OBSOLETE by a change to their "intended use" field. + +15. Registration of new value types + + This section defines procedures by which new value types are + registered with the IANA and made available to the Internet + community. Note that non-IANA value types can be used by bilateral + agreement, provided the associated value types names follow the "X-" + convention defined above. + + The procedures defined here are designed to allow public comment and + review of new value types, while posing only a small impediment to + the definition of new value types. + + Registration of a new value types is accomplished by the following + steps. + +15.1. Define the value type + + A value type is defined by completing the following template. + + To: ietf-mime-direct@imc.org + Subject: Registration of text/directory MIME value type XXX + + + + +Howes, et. al. Standards Track [Page 28] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + value type name: + + value type purpose: + + value type format: + + value type special notes (optional): + + Intended usage: (one of COMMON, LIMITED USE or OBSOLETE) + + The explanation of what goes in each field in the template follows. + + value type name: The name of the value type as it will appear in the + text/directory MIME Content-Type. + + value type purpose: The purpose of the value type. Give a short but + clear description. + + value type format: The definition of the format for the value, + usually using ABNF grammar. + + value type special notes: Any special notes about the value type, how + it is to be used, etc. + +15.2. Post the value type definition + + The value type description must be posted to the new value type + discussion list, ietf-mime-direct@imc.org + +15.3. Allow a comment period + + Discussion on the new value type must be allowed to take place on the + list for a minimum of two weeks. Consensus must be reached before + proceeding to step 4. + +15.4. Submit the value type for approval + + Once the two-week comment period has elapsed, and the proposer is + convinced consensus has been reached on the value type, the + registration application should be submitted to the Profile Reviewer + for approval. The Profile Reviewer is appointed by the Application + Area Directors and can either accept or reject the value type + registration. An accepted registration should be passed on by the + Profile Reviewer to the IANA for inclusion in the official IANA value + type registry. The registration can be rejected for any of the + following reasons. 1) Insufficient comment period; 2) Consensus not + reached; 3) Technical deficiencies raised on the list or elsewhere + have not been addressed. The Profile Reviewer's decision to reject a + + + +Howes, et. al. Standards Track [Page 29] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + profile can be appealed by the proposer to the IESG, or the + objections raised can be addressed by the proposer and the value type + registration resubmitted. + +16. Security Considerations + + Internet mail is subject to many well known security attacks, + including monitoring, replay, and forgery. Care should be taken by + any directory service in allowing information to leave the scope of + the service itself, where any access controls can no longer be + guaranteed. Applications should also take care to display directory + data in a "safe" environment (e.g., PostScript-valued types). + +17. Acknowledgements + + The registration procedures defined here were shamelessly lifted from + the MIME registration RFC. + + The many valuable comments contributed by members of the IETF ASID + working group are gratefully acknowledged, as are the contributions + of the Versit Consortium. Chris Newman was especially helpful in + navigating the intricacies of ABNF lore. + +18. References + + [RFC-1777] Yeong, W., Howes, T., and S. Kille, "Lightweight + Directory Access Protocol", RFC 1777, March 1995. + + [RFC-1778] Howes, T., Kille, S., Yeong, W., and C. Robbins, "The + String Representation of Standard Attribute Syntaxes", + RFC 1778, March 1995. + + [RFC-822] Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", STD 11, RFC 822, August 1982. + + [RFC-2045] Borenstein, N., and N. Freed, "Multipurpose Internet + Mail Extensions (MIME) Part One: Format of Internet + Message Bodies", RFC 2045, November 1996. + + [RFC-2046] Moore, K., "Multipurpose Internet Mail Extensions (MIME) + Part Two: Media Types", RFC 2046, November 1996. + + [RFC-2048] Freed, N., Klensin, J., and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) Part Four: Registration + Procedures", RFC 2048, November 1996. + + [RFC-1766] Alvestrand, H., "Tags for the Identification of + Languages", RFC 1766, March 1995. + + + +Howes, et. al. Standards Track [Page 30] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + + [RFC-2112] Levinson, E., "The MIME Multipart/Related Content-type", + RFC 2112, March 1997. + + [X500] "Information Processing Systems - Open Systems + Interconnection - The Directory: Overview of Concepts, + Models and Services", ISO/IEC JTC 1/SC21, International + Standard 9594-1, 1988. + + [RFC-1835] Deutsch, P., Schoultz, R., Faltstrom, P., and C. Weider, + "Architecture of the WHOIS++ service", RFC 1835, August + 1995. + + [RFC-1738] Berners-Lee, T., Masinter, L., and M. McCahill, "Uniform + Resource Locators (URL)", RFC 1738, December 1994. + + [MIME-VCARD] Dawson, F., and T. Howes, "VCard MIME Directory + Profile", RFC 2426, September 1998. + + [VCARD] Internet Mail Consortium, "vCard - The Electronic + Business Card", Version 2.1, + http://www.imc.com/pdi/vcard-21.txt, September, 1996. + + [RFC-2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC-2234] Crocker, D., and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 2234, November 1997. + + + + + + + + + + + + + + + + + + + + + + + + +Howes, et. al. Standards Track [Page 31] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +19. Authors' Addresses + + Tim Howes + Netscape Communications Corp. + 501 East Middlefield Rd. + Mountain View, CA 94041 + USA + + Phone: +1.415.937.3419 + EMail: howes@netscape.com + + + Mark Smith + Netscape Communications Corp. + 501 East Middlefield Rd. + Mountain View, CA 94041 + USA + + Phone: +1.415.937.3477 + EMail: mcs@netscape.com + + + Frank Dawson + Lotus Development Corporation + 6544 Battleford Drive + Raleigh, NC 27613 + USA + + Phone: +1-919-676-9515 + EMail: frank_dawson@lotus.com + + + + + + + + + + + + + + + + + + + + + +Howes, et. al. Standards Track [Page 32] + +RFC 2425 MIME Content-Type for Directory Information September 1998 + + +20. Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Howes, et. al. Standards Track [Page 33] + diff --git a/lib/qCal/docs/rfc/rfc2426.txt b/lib/qCal/docs/rfc/rfc2426.txt new file mode 100644 index 0000000..a393a67 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2426.txt @@ -0,0 +1,2355 @@ + + + + + + +Network Working Group F. Dawson +Request for Comments: 2426 Lotus Development Corporation +Category: Standards Track T. Howes + Netscape Communications + September 1998 + + + vCard MIME Directory Profile + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +Abstract + + This memo defines the profile of the MIME Content-Type [MIME-DIR] for + directory information for a white-pages person object, based on a + vCard electronic business card. The profile definition is independent + of any particular directory service or protocol. The profile is + defined for representing and exchanging a variety of information + about an individual (e.g., formatted and structured name and delivery + addresses, email address, multiple telephone numbers, photograph, + logo, audio clips, etc.). The directory information used by this + profile is based on the attributes for the person object defined in + the X.520 and X.521 directory services recommendations. The profile + also provides the method for including a [VCARD] representation of a + white-pages directory entry within the MIME Content-Type defined by + the [MIME-DIR] document. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this + document are to be interpreted as described in [RFC 2119]. + + + + + + + + + + + +Dawson & Howes Standards Track [Page 1] + +RFC 2426 vCard MIME Directory Profile September 1998 + + +Table of Contents + + Overview.........................................................3 + 1. THE VCARD MIME DIRECTORY PROFILE REGISTRATION.................4 + 2. MIME DIRECTORY FEATURES.......................................5 + 2.1 PREDEFINED TYPE USAGE ......................................5 + 2.1.1 BEGIN and END Type ......................................5 + 2.1.2 NAME Type ...............................................5 + 2.1.3 PROFILE Type ............................................5 + 2.1.4 SOURCE Type .............................................5 + 2.2 PREDEFINED TYPE PARAMETER USAGE ............................6 + 2.3 PREDEFINED VALUE TYPE USAGE ................................6 + 2.4 EXTENSIONS TO THE PREDEFINED VALUE TYPES ...................6 + 2.4.1 BINARY ..................................................6 + 2.4.2 VCARD ...................................................6 + 2.4.3 PHONE-NUMBER ............................................7 + 2.4.4 UTC-OFFSET ..............................................7 + 2.5 STRUCTURED TYPE VALUES .....................................7 + 2.6 LINE DELIMITING AND FOLDING ................................8 + 3. VCARD PROFILE FEATURES........................................8 + 3.1 IDENTIFICATION TYPES .......................................8 + 3.1.1 FN Type Definition ......................................8 + 3.1.2 N Type Definition .......................................9 + 3.1.3 NICKNAME Type Definition ................................9 + 3.1.4 PHOTO Type Definition ..................................10 + 3.1.5 BDAY Type Definition ...................................11 + 3.2 DELIVERY ADDRESSING TYPES .................................11 + 3.2.1 ADR Type Definition ....................................11 + 3.2.2 LABEL Type Definition ..................................13 + 3.3 TELECOMMUNICATIONS ADDRESSING TYPES .......................13 + 3.3.1 TEL Type Definition ....................................14 + 3.3.2 EMAIL Type Definition ..................................15 + 3.3.3 MAILER Type Definition .................................15 + 3.4 GEOGRAPHICAL TYPES ........................................16 + 3.4.1 TZ Type Definition .....................................16 + 3.4.2 GEO Type Definition ....................................16 + 3.5 ORGANIZATIONAL TYPES ......................................17 + 3.5.1 TITLE Type Definition ..................................17 + 3.5.2 ROLE Type Definition ...................................18 + 3.5.3 LOGO Type Definition ...................................18 + 3.5.4 AGENT Type Definition ..................................19 + 3.5.5 ORG Type Definition ....................................20 + 3.6 EXPLANATORY TYPES .........................................20 + 3.6.1 CATEGORIES Type Definition .............................20 + 3.6.2 NOTE Type Definition ...................................21 + 3.6.3 PRODID Type Definition .................................21 + 3.6.4 REV Type Definition ....................................22 + 3.6.5 SORT-STRING Type Definition ............................22 + + + +Dawson & Howes Standards Track [Page 2] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + 3.6.6 SOUND Type Definition ..................................23 + 3.6.7 UID Type Definition ....................................24 + 3.6.8 URL Type Definition ....................................25 + 3.6.9 VERSION Type Definition ................................25 + 3.7 SECURITY TYPES ............................................25 + 3.7.1 CLASS Type Definition ..................................26 + 3.7.2 KEY Type Definition ....................................26 + 3.8 EXTENDED TYPES ............................................27 + 4. FORMAL GRAMMAR...............................................27 + 5. DIFFERENCES FROM VCARD V2.1..................................37 + 6. ACKNOWLEDGEMENTS.............................................39 + 7. AUTHORS' ADDRESSES...........................................39 + 8. SECURITY CONSIDERATIONS......................................39 + 9. REFERENCES...................................................40 + 10. FULL COPYRIGHT STATEMENT....................................42 + +Overview + + The [MIME-DIR] document defines a MIME Content-Type for holding + different kinds of directory information. The directory information + can be based on any of a number of directory schemas. This document + defines a [MIME-DIR] usage profile for conveying directory + information based on one such schema; that of the white-pages type of + person object. + + The schema is based on the attributes for the person object defined + in the X.520 and X.521 directory services recommendations. The schema + has augmented the basic attributes defined in the X.500 series + recommendation in order to provide for an electronic representation + of the information commonly found on a paper business card. This + schema was first defined in the [VCARD] document. Hence, this [MIME- + DIR] profile is referred to as the vCard MIME Directory Profile. + + A directory entry based on this usage profile can include traditional + directory, white-pages information such as the distinguished name + used to uniquely identify the entry, a formatted representation of + the name used for user-interface or presentation purposes, both the + structured and presentation form of the delivery address, various + telephone numbers and organizational information associated with the + entry. In addition, traditional paper business card information such + as an image of an organizational logo or identify photograph can be + included in this person object. + + The vCard MIME Directory Profile also provides support for + representing other important information about the person associated + with the directory entry. For instance, the date of birth of the + person; an audio clip describing the pronunciation of the name + associated with the directory entry, or some other application of the + + + +Dawson & Howes Standards Track [Page 3] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + digital sound; longitude and latitude geo-positioning information + related to the person associated with the directory entry; date and + time that the directory information was last updated; annotations + often written on a business card; Uniform Resource Locators (URL) for + a website; public key information. The profile also provides support + for non-standard extensions to the schema. This provides the + flexibility for implementations to augment the current capabilities + of the profile in a standardized way. More information about this + electronic business card format can be found in [VCARD]. + +1. The vCard Mime Directory Profile Registration + + This profile is identified by the following [MIME-DIR] registration + template information. Subsequent sections define the profile + definition. + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME profile VCARD + + Profile name: VCARD + + Profile purpose: To hold person object or white-pages type of + directory information. The person schema captured in the directory + entries is that commonly found in an electronic business card. + + Predefined MIME Directory value specifications used: uri, date, + date-time, float + + New value specifications: This profile places further constraints on + the [MIME-DIR] text value specification. In addition, it adds a + binary, phone-number, utc-offset and vcard value specifications. + + Predefined MIME Directory types used: SOURCE, NAME, PROFILE, BEGIN, + END. + + Predefined MIME Directory parameters used: ENCODING, VALUE, CHARSET, + LANGUAGE, CONTEXT. + + New types: FN, N, NICKNAME, PHOTO, BDAY, ADR, LABEL, TEL, EMAIL, + MAILER, TZ, GEO, TITLE, ROLE, LOGO, AGENT, ORG, CATEGORIES, NOTE, + PRODID, REV, SORT-STRING, SOUND, URL, UID, VERSION, CLASS, KEY + + New parameters: TYPE + + Profile special notes: The vCard object MUST contain the FN, N and + VERSION types. The type-grouping feature of [MIME-DIR] is supported + by this profile to group related vCard properties about a directory + + + +Dawson & Howes Standards Track [Page 4] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + entry. For example, vCard properties describing WORK or HOME related + characteristics can be grouped with a unique group label. + + The profile permits the use of non-standard types (i.e., those + identified with the prefix string "X-") as a flexible method for + implementations to extend the functionality currently defined within + this profile. + +2. MIME Directory Features + + The vCard MIME Directory Profile makes use of many of the features + defined by [MIME-DIR]. The following sections either clarify or + extend the content-type definition of [MIME-DIR]. + +2.1 Predefined Type Usage + + The vCard MIME Directory Profile uses the following predefined types + from [MIME-DIR]. + +2.1.1 BEGIN and END Type + + The content entity MUST begin with the BEGIN type with a value of + "VCARD". The content entity MUST end with the END type with a value + of "VCARD". + +2.1.2 NAME Type + + If the NAME type is present, then its value is the displayable, + presentation text associated with the source for the vCard, as + specified in the SOURCE type. + +2.1.3 PROFILE Type + + If the PROFILE type is present, then its value MUST be "VCARD". + +2.1.4 SOURCE Type + + If the SOURCE type is present, then its value provides information + how to find the source for the vCard. + + + + + + + + + + + + +Dawson & Howes Standards Track [Page 5] + +RFC 2426 vCard MIME Directory Profile September 1998 + + +2.2 Predefined Type Parameter Usage + + The vCard MIME Directory Profile uses the following predefined type + parameters as defined by [MIME-DIR]. + + - LANGUAGE + + - ENCODING + + - VALUE + +2.3 Predefined VALUE Type Usage + + The predefined data type values specified in [MIME-DIR] MUST NOT be + repeated in COMMA separated value lists except within the N, + NICKNAME, ADR and CATEGORIES value types. + + The text value type defined in [MIME-DIR] is further restricted such + that any SEMI-COLON character (ASCII decimal 59) in the value MUST be + escaped with the BACKSLASH character (ASCII decimal 92). + +2.4 Extensions To The Predefined VALUE Types + + The predefined data type values specified in [MIME-DIR] have been + extended by the vCard profile to include a number of value types that + are specific to this profile. + +2.4.1 BINARY + + The "binary" value type specifies that the type value is inline, + encoded binary data. This value type can be specified in the PHOTO, + LOGO, SOUND, and KEY types. + + If inline encoded binary data is specified, the ENCODING type + parameter MUST be used to specify the encoding format. The binary + data MUST be encoded using the "B" encoding format. Long lines of + encoded binary data SHOULD BE folded to 75 characters using the + folding method defined in [MIME-DIR]. + + The value type is defined by the following notation: + + binary = + +2.4.2 VCARD + + The "vcard" value type specifies that the type value is another + vCard. This value type can be specified in the AGENT type. The value + type is defined by this specification. Since each of the type + + + +Dawson & Howes Standards Track [Page 6] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + declarations with in the vcard value type are being specified within + a text value themselves, they MUST be terminated with the backslash + escape sequence "\n" or "\N", instead of the normal newline character + sequence CRLF. In addition, any COMMA character (ASCII decimal 44), + SEMI-COLON character (ASCII decimal 59) and COLON character (ASCII + decimal 58) MUST be escaped with the BACKSLASH character (ASCII + decimal 92). For example, with the AGENT type a value would be + specified as: + + AGENT:BEGIN:VCARD\nFN:Joe Friday\nTEL:+1-919-555-7878\n + TITLE:Area Administrator\, Assistant\n EMAIL\;TYPE=INTERN\n + ET:jfriday@host.com\nEND:VCARD\n + +2.4.3 PHONE-NUMBER + + The "phone-number" value type specifies that the type value is a + telephone number. This value type can be specified in the TEL type. + The value type is a text value that has the special semantics of a + telephone number as defined in [CCITT E.163] and [CCITT X.121]. + +2.4.4 UTC-OFFSET + + The "utc-offset" value type specifies that the type value is a signed + offset from UTC. This value type can be specified in the TZ type. + + The value type is an offset from Coordinated Universal Time (UTC). It + is specified as a positive or negative difference in units of hours + and minutes (e.g., +hh:mm). The time is specified as a 24-hour clock. + Hour values are from 00 to 23, and minute values are from 00 to 59. + Hour and minutes are 2-digits with high order zeroes required to + maintain digit count. The extended format for ISO 8601 UTC offsets + MUST be used. The extended format makes use of a colon character as a + separator of the hour and minute text fields. + + The value is defined by the following notation: + + time-hour = 2DIGIT ;00-23 + time-minute = 2DIGIT ;00-59 + utc-offset = ("+" / "-") time-hour ":" time-minute + +2.5 Structured Type Values + + Compound type values are delimited by a field delimiter, specified by + the SEMI-COLON character (ASCII decimal 59). A SEMI-COLON in a + component of a compound property value MUST be escaped with a + BACKSLASH character (ASCII decimal 92). + + + + + +Dawson & Howes Standards Track [Page 7] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Lists of values are delimited by a list delimiter, specified by the + COMMA character (ASCII decimal 44). A COMMA character in a value MUST + be escaped with a BACKSLASH character (ASCII decimal 92). + + This profile supports the type grouping mechanism defined in [MIME- + DIR]. Grouping of related types is a useful technique to communicate + common semantics concerning the properties of a vCard. + +2.6 Line Delimiting and Folding + + This profile supports the same line delimiting and folding methods + defined in [MIME-DIR]. Specifically, when parsing a content line, + folded lines must first be unfolded according to the unfolding + procedure described in [MIME-DIR]. After generating a content line, + lines longer than 75 characters SHOULD be folded according to the + folding procedure described in [MIME DIR]. + + Folding is done after any content encoding of a type value. Unfolding + is done before any decoding of a type value in a content line. + +3. vCard Profile Features + + The vCard MIME Directory Profile Type contains directory information, + typically pertaining to a single directory entry. The information is + described using an attribute schema that is tailored for capturing + personal contact information. The vCard can include attributes that + describe identification, delivery addressing, telecommunications + addressing, geographical, organizational, general explanatory and + security and access information about the particular object + associated with the vCard. + +3.1 Identification Types + + These types are used in the vCard profile to capture information + associated with the identification and naming of the person or + resource associated with the vCard. + +3.1.1 FN Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type FN + + Type name:FN + + Type purpose: To specify the formatted text corresponding to the name + of the object the vCard represents. + + + + +Dawson & Howes Standards Track [Page 8] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type encoding: 8bit + + Type value: A single text value. + + Type special notes: This type is based on the semantics of the X.520 + Common Name attribute. The property MUST be present in the vCard + object. + + Type example: + + FN:Mr. John Q. Public\, Esq. + +3.1.2 N Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type N + + Type name: N + + Type purpose: To specify the components of the name of the object the + vCard represents. + + Type encoding: 8bit + + Type value: A single structured text value. Each component can have + multiple values. + + Type special note: The structured type value corresponds, in + sequence, to the Family Name, Given Name, Additional Names, Honorific + Prefixes, and Honorific Suffixes. The text components are separated + by the SEMI-COLON character (ASCII decimal 59). Individual text + components can include multiple text values (e.g., multiple + Additional Names) separated by the COMMA character (ASCII decimal + 44). This type is based on the semantics of the X.520 individual name + attributes. The property MUST be present in the vCard object. + + Type example: + + N:Public;John;Quinlan;Mr.;Esq. + + N:Stevenson;John;Philip,Paul;Dr.;Jr.,M.D.,A.C.P. + +3.1.3 NICKNAME Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type NICKNAME + + + +Dawson & Howes Standards Track [Page 9] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type name: NICKNAME + + Type purpose: To specify the text corresponding to the nickname of + the object the vCard represents. + + Type encoding: 8bit + + Type value: One or more text values separated by a COMMA character + (ASCII decimal 44). + + Type special note: The nickname is the descriptive name given instead + of or in addition to the one belonging to a person, place, or thing. + It can also be used to specify a familiar form of a proper name + specified by the FN or N types. + + Type example: + + NICKNAME:Robbie + + NICKNAME:Jim,Jimmie + +3.1.4 PHOTO Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type PHOTO + + Type name: PHOTO + + Type purpose: To specify an image or photograph information that + annotates some aspect of the object the vCard represents. + + Type encoding: The encoding MUST be reset to "b" using the ENCODING + parameter in order to specify inline, encoded binary data. If the + value is referenced by a URI value, then the default encoding of 8bit + is used and no explicit ENCODING parameter is needed. + + Type value: A single value. The default is binary value. It can also + be reset to uri value. The uri value can be used to specify a value + outside of this MIME entity. + + Type special notes: The type can include the type parameter "TYPE" to + specify the graphic image format type. The TYPE parameter values MUST + be one of the IANA registered image formats or a non-standard image + format. + + + + + + +Dawson & Howes Standards Track [Page 10] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type example: + + PHOTO;VALUE=uri:http://www.abc.com/pub/photos + /jqpublic.gif + + + PHOTO;ENCODING=b;TYPE=JPEG:MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhvcN + AQEEBQAwdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENvbW11bm + ljYXRpb25zIENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZvcm1hdGlvbiBTeXN0 + <...remainder of "B" encoded binary data...> + +3.1.5 BDAY Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type BDAY + + Type name: BDAY + + Type purpose: To specify the birth date of the object the vCard + represents. + + Type encoding: 8bit + + Type value: The default is a single date value. It can also be reset + to a single date-time value. + + Type examples: + + BDAY:1996-04-15 + + BDAY:1953-10-15T23:10:00Z + + BDAY:1987-09-27T08:30:00-06:00 + +3.2 Delivery Addressing Types + + These types are concerned with information related to the delivery + addressing or label for the vCard object. + +3.2.1 ADR Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type ADR + + Type name: ADR + + + + +Dawson & Howes Standards Track [Page 11] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type purpose: To specify the components of the delivery address for + the vCard object. + + Type encoding: 8bit + + Type value: A single structured text value, separated by the + SEMI-COLON character (ASCII decimal 59). + + Type special notes: The structured type value consists of a sequence + of address components. The component values MUST be specified in + their corresponding position. The structured type value corresponds, + in sequence, to the post office box; the extended address; the street + address; the locality (e.g., city); the region (e.g., state or + province); the postal code; the country name. When a component value + is missing, the associated component separator MUST still be + specified. + + The text components are separated by the SEMI-COLON character (ASCII + decimal 59). Where it makes semantic sense, individual text + components can include multiple text values (e.g., a "street" + component with multiple lines) separated by the COMMA character + (ASCII decimal 44). + + The type can include the type parameter "TYPE" to specify the + delivery address type. The TYPE parameter values can include "dom" to + indicate a domestic delivery address; "intl" to indicate an + international delivery address; "postal" to indicate a postal + delivery address; "parcel" to indicate a parcel delivery address; + "home" to indicate a delivery address for a residence; "work" to + indicate delivery address for a place of work; and "pref" to indicate + the preferred delivery address when more than one address is + specified. These type parameter values can be specified as a + parameter list (i.e., "TYPE=dom;TYPE=postal") or as a value list + (i.e., "TYPE=dom,postal"). This type is based on semantics of the + X.520 geographical and postal addressing attributes. The default is + "TYPE=intl,postal,parcel,work". The default can be overridden to some + other set of values by specifying one or more alternate values. For + example, the default can be reset to "TYPE=dom,postal,work,home" to + specify a domestic delivery address for postal delivery to a + residence that is also used for work. + + Type example: In this example the post office box and the extended + address are absent. + + ADR;TYPE=dom,home,postal,parcel:;;123 Main + Street;Any Town;CA;91921-1234 + + + + + +Dawson & Howes Standards Track [Page 12] + +RFC 2426 vCard MIME Directory Profile September 1998 + + +3.2.2 LABEL Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type LABEL + + Type name: LABEL + + Type purpose: To specify the formatted text corresponding to delivery + address of the object the vCard represents. + + Type encoding: 8bit + + Type value: A single text value. + + Type special notes: The type value is formatted text that can be used + to present a delivery address label for the vCard object. The type + can include the type parameter "TYPE" to specify delivery label type. + The TYPE parameter values can include "dom" to indicate a domestic + delivery label; "intl" to indicate an international delivery label; + "postal" to indicate a postal delivery label; "parcel" to indicate a + parcel delivery label; "home" to indicate a delivery label for a + residence; "work" to indicate delivery label for a place of work; and + "pref" to indicate the preferred delivery label when more than one + label is specified. These type parameter values can be specified as a + parameter list (i.e., "TYPE=dom;TYPE=postal") or as a value list + (i.e., "TYPE=dom,postal"). This type is based on semantics of the + X.520 geographical and postal addressing attributes. The default is + "TYPE=intl,postal,parcel,work". The default can be overridden to some + other set of values by specifying one or more alternate values. For + example, the default can be reset to "TYPE=intl,post,parcel,home" to + specify an international delivery label for both postal and parcel + delivery to a residential location. + + Type example: A multi-line address label. + + LABEL;TYPE=dom,home,postal,parcel:Mr.John Q. Public\, Esq.\n + Mail Drop: TNE QB\n123 Main Street\nAny Town\, CA 91921-1234 + \nU.S.A. + +3.3 Telecommunications Addressing Types + + These types are concerned with information associated with the + telecommunications addressing of the object the vCard represents. + + + + + + + +Dawson & Howes Standards Track [Page 13] + +RFC 2426 vCard MIME Directory Profile September 1998 + + +3.3.1 TEL Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type TEL + + Type name: TEL + + Type purpose: To specify the telephone number for telephony + communication with the object the vCard represents. + + Type encoding: 8bit + + Type value: A single phone-number value. + + Type special notes: The value of this type is specified in a + canonical form in order to specify an unambiguous representation of + the globally unique telephone endpoint. This type is based on the + X.500 Telephone Number attribute. + + The type can include the type parameter "TYPE" to specify intended + use for the telephone number. The TYPE parameter values can include: + "home" to indicate a telephone number associated with a residence, + "msg" to indicate the telephone number has voice messaging support, + "work" to indicate a telephone number associated with a place of + work, "pref" to indicate a preferred-use telephone number, "voice" to + indicate a voice telephone number, "fax" to indicate a facsimile + telephone number, "cell" to indicate a cellular telephone number, + "video" to indicate a video conferencing telephone number, "pager" to + indicate a paging device telephone number, "bbs" to indicate a + bulletin board system telephone number, "modem" to indicate a MODEM + connected telephone number, "car" to indicate a car-phone telephone + number, "isdn" to indicate an ISDN service telephone number, "pcs" to + indicate a personal communication services telephone number. The + default type is "voice". These type parameter values can be specified + as a parameter list (i.e., "TYPE=work;TYPE=voice") or as a value list + (i.e., "TYPE=work,voice"). The default can be overridden to another + set of values by specifying one or more alternate values. For + example, the default TYPE of "voice" can be reset to a WORK and HOME, + VOICE and FAX telephone number by the value list + "TYPE=work,home,voice,fax". + + Type example: + + TEL;TYPE=work,voice,pref,msg:+1-213-555-1234 + + + + + + +Dawson & Howes Standards Track [Page 14] + +RFC 2426 vCard MIME Directory Profile September 1998 + + +3.3.2 EMAIL Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type EMAIL + + Type name: EMAIL + + Type purpose: To specify the electronic mail address for + communication with the object the vCard represents. + + Type encoding: 8bit + + Type value: A single text value. + + Type special notes: The type can include the type parameter "TYPE" to + specify the format or preference of the electronic mail address. The + TYPE parameter values can include: "internet" to indicate an Internet + addressing type, "x400" to indicate a X.400 addressing type or "pref" + to indicate a preferred-use email address when more than one is + specified. Another IANA registered address type can also be + specified. The default email type is "internet". A non-standard value + can also be specified. + + Type example: + + EMAIL;TYPE=internet:jqpublic@xyz.dom1.com + + EMAIL;TYPE=internet:jdoe@isp.net + + EMAIL;TYPE=internet,pref:jane_doe@abc.com + +3.3.3 MAILER Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type MAILER + + Type name: MAILER + + Type purpose: To specify the type of electronic mail software that is + used by the individual associated with the vCard. + + Type encoding: 8bit + + Type value: A single text value. + + + + + +Dawson & Howes Standards Track [Page 15] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type special notes: This information can provide assistance to a + correspondent regarding the type of data representation which can be + used, and how they can be packaged. This property is based on the + private MIME type X-Mailer that is generally implemented by MIME user + agent products. + + Type example: + + MAILER:PigeonMail 2.1 + +3.4 Geographical Types + + These types are concerned with information associated with + geographical positions or regions associated with the object the + vCard represents. + +3.4.1 TZ Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type TZ + + Type name: TZ + + Type purpose: To specify information related to the time zone of the + object the vCard represents. + + Type encoding: 8bit + + Type value: The default is a single utc-offset value. It can also be + reset to a single text value. + + Type special notes: The type value consists of a single value. + + Type examples: + + TZ:-05:00 + + TZ;VALUE=text:-05:00; EST; Raleigh/North America + ;This example has a single value, not a structure text value. + +3.4.2 GEO Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type GEO + + Type name: GEO + + + +Dawson & Howes Standards Track [Page 16] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type purpose: To specify information related to the global + positioning of the object the vCard represents. + + Type encoding: 8bit + + Type value: A single structured value consisting of two float values + separated by the SEMI-COLON character (ASCII decimal 59). + + Type special notes: This type specifies information related to the + global position of the object associated with the vCard. The value + specifies latitude and longitude, in that order (i.e., "LAT LON" + ordering). The longitude represents the location east and west of the + prime meridian as a positive or negative real number, respectively. + The latitude represents the location north and south of the equator + as a positive or negative real number, respectively. The longitude + and latitude values MUST be specified as decimal degrees and should + be specified to six decimal places. This will allow for granularity + within a meter of the geographical position. The text components are + separated by the SEMI-COLON character (ASCII decimal 59). The simple + formula for converting degrees-minutes-seconds into decimal degrees + is: + + decimal = degrees + minutes/60 + seconds/3600. + + Type example: + + GEO:37.386013;-122.082932 + +3.5 Organizational Types + + These types are concerned with information associated with + characteristics of the organization or organizational units of the + object the vCard represents. + +3.5.1 TITLE Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type TITLE + + Type name: TITLE + + Type purpose: To specify the job title, functional position or + function of the object the vCard represents. + + Type encoding: 8bit + + Type value: A single text value. + + + +Dawson & Howes Standards Track [Page 17] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type special notes: This type is based on the X.520 Title attribute. + + Type example: + + TITLE:Director\, Research and Development + +3.5.2 ROLE Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type ROLE + + Type name: ROLE + + Type purpose: To specify information concerning the role, occupation, + or business category of the object the vCard represents. + + Type encoding: 8bit + + Type value: A single text value. + + Type special notes: This type is based on the X.520 Business Category + explanatory attribute. This property is included as an organizational + type to avoid confusion with the semantics of the TITLE type and + incorrect usage of that type when the semantics of this type is + intended. + + Type example: + + ROLE:Programmer + +3.5.3 LOGO Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type LOGO + + Type name: LOGO + + Type purpose: To specify a graphic image of a logo associated with + the object the vCard represents. + + Type encoding: The encoding MUST be reset to "b" using the ENCODING + parameter in order to specify inline, encoded binary data. If the + value is referenced by a URI value, then the default encoding of 8bit + is used and no explicit ENCODING parameter is needed. + + + + + +Dawson & Howes Standards Track [Page 18] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type value: A single value. The default is binary value. It can also + be reset to uri value. The uri value can be used to specify a value + outside of this MIME entity. + + Type special notes: The type can include the type parameter "TYPE" to + specify the graphic image format type. The TYPE parameter values MUST + be one of the IANA registered image formats or a non-standard image + format. + + Type example: + + LOGO;VALUE=uri:http://www.abc.com/pub/logos/abccorp.jpg + + LOGO;ENCODING=b;TYPE=JPEG:MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhvcN + AQEEBQAwdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENvbW11bm + ljYXRpb25zIENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZvcm1hdGlvbiBTeXN0 + <...the remainder of "B" encoded binary data...> + +3.5.4 AGENT Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type AGENT + + Type name: AGENT + + Type purpose: To specify information about another person who will + act on behalf of the individual or resource associated with the + vCard. + + Type encoding: 8-bit + + Type value: The default is a single vcard value. It can also be reset + to either a single text or uri value. The text value can be used to + specify textual information. The uri value can be used to specify + information outside of this MIME entity. + + Type special notes: This type typically is used to specify an area + administrator, assistant, or secretary for the individual associated + with the vCard. A key characteristic of the Agent type is that it + represents somebody or something that is separately addressable. + + Type example: + + AGENT;VALUE=uri: + CID:JQPUBLIC.part3.960129T083020.xyzMail@host3.com + + + + + +Dawson & Howes Standards Track [Page 19] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + AGENT:BEGIN:VCARD\nFN:Susan Thomas\nTEL:+1-919-555- + 1234\nEMAIL\;INTERNET:sthomas@host.com\nEND:VCARD\n + +3.5.5 ORG Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type ORG + + Type name: ORG + + Type purpose: To specify the organizational name and units associated + with the vCard. + + Type encoding: 8bit + + Type value: A single structured text value consisting of components + separated the SEMI-COLON character (ASCII decimal 59). + + Type special notes: The type is based on the X.520 Organization Name + and Organization Unit attributes. The type value is a structured type + consisting of the organization name, followed by one or more levels + of organizational unit names. + + Type example: A type value consisting of an organizational name, + organizational unit #1 name and organizational unit #2 name. + + ORG:ABC\, Inc.;North American Division;Marketing + +3.6 Explanatory Types + + These types are concerned with additional explanations, such as that + related to informational notes or revisions specific to the vCard. + +3.6.1 CATEGORIES Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type CATEGORIES + + Type name: CATEGORIES + + Type purpose: To specify application category information about the + vCard. + + Type encoding: 8bit + + + + + +Dawson & Howes Standards Track [Page 20] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type value: One or more text values separated by a COMMA character + (ASCII decimal 44). + + Type example: + + CATEGORIES:TRAVEL AGENT + + CATEGORIES:INTERNET,IETF,INDUSTRY,INFORMATION TECHNOLOGY + +3.6.2 NOTE Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type NOTE + + Type name: NOTE + + Type purpose: To specify supplemental information or a comment that + is associated with the vCard. + + Type encoding: 8bit + + Type value: A single text value. + + Type special notes: The type is based on the X.520 Description + attribute. + + Type example: + + NOTE:This fax number is operational 0800 to 1715 + EST\, Mon-Fri. + +3.6.3 PRODID Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type PRODID + + Type name: PRODID + + Type purpose: To specify the identifier for the product that created + the vCard object. + + Type encoding: 8-bit + + Type value: A single text value. + + + + + +Dawson & Howes Standards Track [Page 21] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type special notes: Implementations SHOULD use a method such as that + specified for Formal Public Identifiers in ISO 9070 to assure that + the text value is unique. + + Type example: + + PRODID:-//ONLINE DIRECTORY//NONSGML Version 1//EN + +3.6.4 REV Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type REV + + Type name: REV + + Type purpose: To specify revision information about the current + vCard. + + Type encoding: 8-bit + + Type value: The default is a single date-time value. Can also be + reset to a single date value. + + Type special notes: The value distinguishes the current revision of + the information in this vCard for other renditions of the + information. + + Type example: + + REV:1995-10-31T22:27:10Z + + REV:1997-11-15 + +3.6.5 SORT-STRING Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type SORT-STRING + + Type Name: SORT-STRING + + Type purpose: To specify the family name or given name text to be + used for national-language-specific sorting of the FN and N types. + + Type encoding: 8bit + + Type value: A single text value. + + + +Dawson & Howes Standards Track [Page 22] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type special notes: The sort string is used to provide family name or + given name text that is to be used in locale- or national-language- + specific sorting of the formatted name and structured name types. + Without this information, sorting algorithms could incorrectly sort + this vCard within a sequence of sorted vCards. When this type is + present in a vCard, then this family name or given name value is used + for sorting the vCard. + + Type examples: For the case of family name sorting, the following + examples define common sort string usage with the FN and N types. + + FN:Rene van der Harten + N:van der Harten;Rene;J.;Sir;R.D.O.N. + SORT-STRING:Harten + + FN:Robert Pau Shou Chang + N:Pau;Shou Chang;Robert + SORT-STRING:Pau + + FN:Osamu Koura + N:Koura;Osamu + SORT-STRING:Koura + + FN:Oscar del Pozo + N:del Pozo Triscon;Oscar + SORT-STRING:Pozo + + FN:Chistine d'Aboville + N:d'Aboville;Christine + SORT-STRING:Aboville + +3.6.6 SOUND Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type SOUND + + Type name: SOUND + + Type purpose: To specify a digital sound content information that + annotates some aspect of the vCard. By default this type is used to + specify the proper pronunciation of the name type value of the vCard. + + Type encoding: The encoding MUST be reset to "b" using the ENCODING + parameter in order to specify inline, encoded binary data. If the + value is referenced by a URI value, then the default encoding of 8bit + is used and no explicit ENCODING parameter is needed. + + + + +Dawson & Howes Standards Track [Page 23] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type value: A single value. The default is binary value. It can also + be reset to uri value. The uri value can be used to specify a value + outside of this MIME entity. + + Type special notes: The type can include the type parameter "TYPE" to + specify the audio format type. The TYPE parameter values MUST be one + of the IANA registered audio formats or a non-standard audio format. + + Type example: + + SOUND;TYPE=BASIC;VALUE=uri:CID:JOHNQPUBLIC.part8. + 19960229T080000.xyzMail@host1.com + + SOUND;TYPE=BASIC;ENCODING=b:MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhvcN + AQEEBQAwdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENvbW11bm + ljYXRpb25zIENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZvcm1hdGlvbiBTeXN0 + <...the remainder of "B" encoded binary data...> + +3.6.7 UID Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type UID + + Type name: UID + + Type purpose: To specify a value that represents a globally unique + identifier corresponding to the individual or resource associated + with the vCard. + + Type encoding: 8bit + + Type value: A single text value. + + Type special notes: The type is used to uniquely identify the object + that the vCard represents. + + The type can include the type parameter "TYPE" to specify the format + of the identifier. The TYPE parameter value should be an IANA + registered identifier format. The value can also be a non-standard + format. + + Type example: + + UID:19950401-080045-40000F192713-0052 + + + + + + +Dawson & Howes Standards Track [Page 24] + +RFC 2426 vCard MIME Directory Profile September 1998 + + +3.6.8 URL Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type URL + + Type name: URL + + Type purpose: To specify a uniform resource locator associated with + the object that the vCard refers to. + + Type encoding: 8bit + + Type value: A single uri value. + + Type example: + + URL:http://www.swbyps.restaurant.french/~chezchic.html + +3.6.9 VERSION Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type VERSION + + Type name: VERSION + + Type purpose: To specify the version of the vCard specification used + to format this vCard. + + Type encoding: 8bit + + Type value: A single text value. + + Type special notes: The property MUST be present in the vCard object. + The value MUST be "3.0" if the vCard corresponds to this + specification. + + Type example: + + VERSION:3.0 + +3.7 Security Types + + These types are concerned with the security of communication pathways + or access to the vCard. + + + + + +Dawson & Howes Standards Track [Page 25] + +RFC 2426 vCard MIME Directory Profile September 1998 + + +3.7.1 CLASS Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type CLASS + + Type name: CLASS + + Type purpose: To specify the access classification for a vCard + object. + + Type encoding: 8bit + + Type value: A single text value. + + Type special notes: An access classification is only one component of + the general security model for a directory service. The + classification attribute provides a method of capturing the intent of + the owner for general access to information described by the vCard + object. + + Type examples: + + CLASS:PUBLIC + + CLASS:PRIVATE + + CLASS:CONFIDENTIAL + +3.7.2 KEY Type Definition + + To: ietf-mime-directory@imc.org + + Subject: Registration of text/directory MIME type KEY + + Type name: KEY + + Type purpose: To specify a public key or authentication certificate + associated with the object that the vCard represents. + + Type encoding: The encoding MUST be reset to "b" using the ENCODING + parameter in order to specify inline, encoded binary data. If the + value is a text value, then the default encoding of 8bit is used and + no explicit ENCODING parameter is needed. + + Type value: A single value. The default is binary. It can also be + reset to text value. The text value can be used to specify a text + key. + + + +Dawson & Howes Standards Track [Page 26] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + Type special notes: The type can also include the type parameter TYPE + to specify the public key or authentication certificate format. The + parameter type should specify an IANA registered public key or + authentication certificate format. The parameter type can also + specify a non-standard format. + + Type example: + + KEY;ENCODING=b:MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhvcNAQEEBQA + wdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENbW11bmljYX + Rpb25zIENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZvcm1hdGlvbiBTeXN0 + ZW1zMRwwGgYDVQQDExNyb290Y2EubmV0c2NhcGUuY29tMB4XDTk3MDYwNj + E5NDc1OVoXDTk3MTIwMzE5NDc1OVowgYkxCzAJBgNVBAYTAlVTMSYwJAYD + VQQKEx1OZXRzY2FwZSBDb21tdW5pY2F0aW9ucyBDb3JwLjEYMBYGA1UEAx + MPVGltb3RoeSBBIEhvd2VzMSEwHwYJKoZIhvcNAQkBFhJob3dlc0BuZXRz + Y2FwZS5jb20xFTATBgoJkiaJk/IsZAEBEwVob3dlczBcMA0GCSqGSIb3DQ + EBAQUAA0sAMEgCQQC0JZf6wkg8pLMXHHCUvMfL5H6zjSk4vTTXZpYyrdN2 + dXcoX49LKiOmgeJSzoiFKHtLOIboyludF90CgqcxtwKnAgMBAAGjNjA0MB + EGCWCGSAGG+EIBAQQEAwIAoDAfBgNVHSMEGDAWgBT84FToB/GV3jr3mcau + +hUMbsQukjANBgkqhkiG9w0BAQQFAAOBgQBexv7o7mi3PLXadkmNP9LcIP + mx93HGp0Kgyx1jIVMyNgsemeAwBM+MSlhMfcpbTrONwNjZYW8vJDSoi//y + rZlVt9bJbs7MNYZVsyF1unsqaln4/vy6Uawfg8VUMk1U7jt8LYpo4YULU7 + UZHPYVUaSgVttImOHZIKi4hlPXBOhcUQ== + +3.8 Extended Types + + The types defined by this document can be extended with private types + using the non-standard, private values mechanism defined in [RFC + 2045]. Non-standard, private types with a name starting with "X-" may + be defined bilaterally between two cooperating agents without outside + registration or standardization. + +4. Formal Grammar + + The following formal grammar is provided to assist developers in + building parsers for the vCard. + + This syntax is written according to the form described in RFC 2234, + but it references just this small subset of RFC 2234 literals: + + ;******************************************* + ; Commonly Used Literal Definition + ;******************************************* + + ALPHA = %x41-5A / %x61-7A + ; Latin Capital Letter A-Latin Capital Letter Z / + ; Latin Small Letter a-Latin Small Letter z + + + + +Dawson & Howes Standards Track [Page 27] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + CHAR = %x01-7F + ; Any C0 Controls and Basic Latin, excluding NULL from + ; Code Charts, pages 7-6 through 7-9 in [UNICODE] + + CR = %x0D + ; Carriage Return + + LF = %0A + ; Line Feed + + CRLF = CR LF + ; Internet standard newline + + ;CTL = %x00-1F / %x7F + ; Controls. Not used, but referenced in comments. + + DIGIT = %x30-39 + ; Digit Zero-Digit Nine + + DQUOTE = %x22 + ; Quotation Mark + + HTAB = %x09 + ; Horizontal Tabulation + + SP = %x20 + ; space + + VCHAR = %x21-7E + ; Visible (printing) characters + + WSP = SP / HTAB + ; White Space + + ;******************************************* + ; Basic vCard Definition + ;******************************************* + + vcard_entity = 1*(vcard) + + vcard = [group "."] "BEGIN" ":" "VCARD" 1*CRLF + 1*(contentline) + ;A vCard object MUST include the VERSION, FN and N types. + [group "."] "END" ":" "VCARD" 1*CRLF + + contentline = [group "."] name *(";" param ) ":" value CRLF + ; When parsing a content line, folded lines must first + ; be unfolded according to the unfolding procedure + + + +Dawson & Howes Standards Track [Page 28] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + ; described above. When generating a content line, lines + ; longer than 75 characters SHOULD be folded according to + ; the folding procedure described in [MIME DIR]. + + group = 1*(ALPHA / DIGIT / "-") + + name = iana-token / x-name + ; Parsing of the param and value is + ; based on the "name" or type identifier + ; as defined in ABNF sections below + + iana-token = 1*(ALPHA / DIGIT / "-") + ; vCard type or parameter identifier registered with IANA + + x-name = "X-" 1*(ALPHA / DIGIT / "-") + ; Reserved for non-standard use + + param = param-name "=" param-value *("," param-value) + + param-name = iana-token / x-name + + param-value = ptext / quoted-string + + ptext = *SAFE-CHAR + + value = *VALUE-CHAR + + quoted-string = DQUOTE QSAFE-CHAR DQUOTE + + NON-ASCII = %x80-FF + ; Use is restricted by CHARSET parameter + ; on outer MIME object (UTF-8 preferred) + + QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-ASCII + ; Any character except CTLs, DQUOTE + + SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E / NON-ASCII + ; Any character except CTLs, DQUOTE, ";", ":", "," + + VALUE-CHAR = WSP / VCHAR / NON-ASCII + ; Any textual character + + ;******************************************* + ; vCard Type Definition + ; + ; Provides type-specific definitions for how the + ; "value" and "param" are defined. + ;******************************************* + + + +Dawson & Howes Standards Track [Page 29] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + ;For name="NAME" + param = "" + ; No parameters allowed + + value = text-value + + ;For name="PROFILE" + param = "" + ; No parameters allowed + + value = text-value + ; Value MUST be the case insensitive value "VCARD + + ;For name="SOURCE" + param = source-param + ; No parameters allowed + + value = uri + + source-param = ("VALUE" "=" "uri") + / ("CONTEXT" "=" "word") + ; Parameter value specifies the protocol context + ; for the uri value. + / (x-name "=" *SAFE-CHAR) + + ;For name="FN" + ;This type MUST be included in a vCard object. + param = text-param + ; Text parameters allowed + + value = text-value + + ;For name="N" + ;This type MUST be included in a vCard object. + + param = text-param + ; Text parameters allowed + + value = n-value + + n-value = 0*4(text-value *("," text-value) ";") + text-value *("," text-value) + ; Family; Given; Middle; Prefix; Suffix. + ; Example: Public;John;Quincy,Adams;Reverend Dr. III + + ;For name="NICKNAME" + param = text-param + ; Text parameters allowed + + + +Dawson & Howes Standards Track [Page 30] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + value = text-list + + ;For name="PHOTO" + param = img-inline-param + ; Only image parameters allowed + + param =/ img-refer-param + ; Only image parameters allowed + + value = img-inline-value + ; Value and parameter MUST match + + value =/ img-refer-value + ; Value and parameter MUST match + + ;For name="BDAY" + param = ("VALUE" "=" "date") + ; Only value parameter allowed + + param =/ ("VALUE" "=" "date-time") + ; Only value parameter allowed + + value = date-value + ; Value MUST match value type + + value =/ date-time-value + ; Value MUST match value type + + ;For name="ADR" + param = adr-param / text-param + ; Only adr and text parameters allowed + + value = adr-value + + ;For name="LABEL" + param = adr-param / text-param + ; Only adr and text parameters allowed + + value = text-value + + ;For name="TEL" + param = tel-param + ; Only tel parameters allowed + + value = phone-number-value + + tel-param = "TYPE" "=" tel-type *("," tel-type) + + + + +Dawson & Howes Standards Track [Page 31] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + tel-type = "HOME" / "WORK" / "PREF" / "VOICE" / "FAX" / "MSG" + / "CELL" / "PAGER" / "BBS" / "MODEM" / "CAR" / "ISDN" + / "VIDEO" / "PCS" / iana-token / x-name + ; Values are case insensitive + + ;For name="EMAIL" + param = email-param + ; Only email parameters allowed + + value = text-value + + email-param = "TYPE" "=" email-type ["," "PREF"] + ; Value is case insensitive + + email-type = "INTERNET" / "X400" / iana-token / "X-" word + ; Values are case insensitive + + ;For name="MAILER" + param = text-param + ; Only text parameters allowed + + value = text-value + + ;For name="TZ" + param = "" + ; No parameters allowed + + value = utc-offset-value + + ;For name="GEO" + param = "" + ; No parameters allowed + + value = float-value ";" float-value + + ;For name="TITLE" + param = text-param + ; Only text parameters allowed + + value = text-value + + ;For name="ROLE" + param = text-param + ; Only text parameters allowed + + value = text-value + + ;For name="LOGO" + + + +Dawson & Howes Standards Track [Page 32] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + param = img-inline-param / img-refer-param + ; Only image parameters allowed + + value = img-inline-value / img-refer-value + ; Value and parameter MUST match + + ;For name="AGENT" + param = agent-inline-param + + param =/ agent-refer-param + + value = agent-inline-value + ; Value and parameter MUST match + + value =/ agent-refer-value + ; Value and parameter MUST match + + agent-inline-param = "" + ; No parameters allowed + + agent-refer-param = "VALUE" "=" "uri" + ; Only value parameter allowed + + agent-inline-value = text-value + ; Value MUST be a valid vCard object + + agent-refer-value = uri + ; URI MUST refer to image content of given type + + ;For name="ORG" + + param = text-param + ; Only text parameters allowed + + value = org-value + + org-value = *(text-value ";") text-value + ; First is Organization Name, remainder are Organization Units. + + ;For name="CATEGORIES" + param = text-param + ; Only text parameters allowed + + value = text-list + + ;For name="NOTE" + param = text-param + ; Only text parameters allowed + + + +Dawson & Howes Standards Track [Page 33] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + value = text-value + + ;For name="PRODID" + param = "" + ; No parameters allowed + + value = text-value + + ;For name="REV" + param = ["VALUE" =" "date-time"] + ; Only value parameters allowed. Values are case insensitive. + + param =/ "VALUE" =" "date" + ; Only value parameters allowed. Values are case insensitive. + + value = date-time-value + + value =/ date-value + + ;For name="SORT-STRING" + param = text-param + ; Only text parameters allowed + + value = text-value + + ;For name="SOUND" + param = snd-inline-param + ; Only sound parameters allowed + + param =/ snd-refer-param + ; Only sound parameters allowed + + value = snd-line-value + ; Value MUST match value type + + value =/ snd-refer-value + ; Value MUST match value type + + snd-inline-value = binary-value CRLF + ; Value MUST be "b" encoded audio content + + snd-inline-param = ("VALUE" "=" "binary"]) + / ("ENCODING" "=" "b") + / ("TYPE" "=" *SAFE-CHAR) + ; Value MUST be an IANA registered audio type + + snd-refer-value = uri + ; URI MUST refer to audio content of given type + + + +Dawson & Howes Standards Track [Page 34] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + snd-refer-param = ("VALUE" "=" "uri") + / ("TYPE" "=" word) + ; Value MUST be an IANA registered audio type + + ;For name="UID" + param = "" + ; No parameters allowed + + value = text-value + + ;For name="URL" + param = "" + ; No parameters allowed + + value = uri + + ;For name="VERSION" + ;This type MUST be included in a vCard object. + param = "" + ; No parameters allowed + + value = text-value + ; Value MUST be "3.0" + + ;For name="CLASS" + param = "" + ; No parameters allowed + + value = "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" + / iana-token / x-name + ; Value are case insensitive + + ;For name="KEY" + param = key-txt-param + ; Only value and type parameters allowed + + param =/ key-bin-param + ; Only value and type parameters allowed + + value = text-value + + value =/ binary-value + + key-txt-param = "TYPE" "=" keytype + + key-bin-param = ("TYPE" "=" keytype) + / ("ENCODING" "=" "b") + ; Value MUST be a "b" encoded key or certificate + + + +Dawson & Howes Standards Track [Page 35] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + keytype = "X509" / "PGP" / iana-token / x-name + ; Values are case insensitive + + ;For name="X-" non-standard type + param = text-param / (x-name "=" param-value) + ; Only text or non-standard parameters allowed + + value = text-value + + ;******************************************* + ; vCard Commonly Used Parameter Definition + ;******************************************* + + text-param = ("VALUE" "=" "ptext") + / ("LANGUAGE" "=" langval) + / (x-name "=" param-value) + + langval = + + img-inline-value = binary-value + ;Value MUST be "b" encoded image content + + img-inline-param + + img-inline-param = ("VALUE" "=" "binary") + / ("ENCODING" "=" "b") + / ("TYPE" "=" param-value + ;TYPE value MUST be an IANA registered image type + + img-refer-value = uri + ;URI MUST refer to image content of given type + + img-refer-param = ("VALUE" "=" "uri") + / ("TYPE" "=" param-value) + ;TYPE value MUST be an IANA registered image type + + adr-param = ("TYPE" "=" adr-type *("," adr-type)) + / (text-param) + + adr-type = "dom" / "intl" / "postal" / "parcel" / "home" + / "work" / "pref" / iana-type / x-name + + adr-value = 0*6(text-value ";") text-value + ; PO Box, Extended Address, Street, Locality, Region, Postal + ; Code, Country Name + + + + + + +Dawson & Howes Standards Track [Page 36] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + ;******************************************* + ; vCard Type Value Definition + ;******************************************* + + text-value-list = 1*text-value *("," 1*text-value) + + text-value = *(SAFE-CHAR / ":" / DQUOTE / ESCAPED-CHAR) + + ESCAPED-CHAR = "\\" / "\;" / "\," / "\n" / "\N") + ; \\ encodes \, \n or \N encodes newline + ; \; encodes ;, \, encodes , + + binary-value = + + date-value = + + time-value = + + date-time-value = + + phone-number-value = + + uri-value = + + utc-offset-value = ("+" / "-") time-hour ":" time-minute + time-hour = 2DIGIT ;00-23 + time-minute = 2DIGIT ;00-59 + +5. Differences From vCard v2.1 + + This specification has been reviewed by the IETF community. The + review process introduced a number of differences from the [VCARD] + version 2.1. These differences require that vCard objects conforming + to this specification have a different version number than a vCard + conforming to [VCARD]. The differences include the following: + + . The QUOTED-PRINTABLE inline encoding has been eliminated. + Only the "B" encoding of [RFC 2047] is an allowed value for + the ENCODING parameter. + + . The method for specifying CRLF character sequences in text + type values has been changed. The CRLF character sequence in + a text type value is specified with the backslash character + sequence "\n" or "\N". + + + + +Dawson & Howes Standards Track [Page 37] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + . Any COMMA or SEMICOLON in a text type value must be backslash + escaped. + + . VERSION value corresponding to this specification MUST be + "3.0". + + . The [MIME-DIR] predefined types of SOURCE, NAME and PROFILE + are allowed. + + . The [MIME-DIR] VALUE type parameter for value data typing is + allowed. In addition, there are extensions made to these type + values for additional value types used in this specification. + + . The [VCARD] CHARSET type parameter has been eliminated. + Character set can only be specified on the CHARSET parameter + on the Content-Type MIME header field. + + . The [VCARD] support for non-significant WSP character has + been eliminated. + + . The "TYPE=" prefix to parameter values is required. In + [VCARD] this was optional. + + . LOGO, PHOTO and SOUND multimedia formats MUST be either IANA + registered types or non-standard types. + + . Inline binary content must be "B" encoded and folded. A blank + line after the encoded binary content is no longer required. + + . TEL values can be identified as personal communication + services telephone numbers with the PCS type parameter value. + + . The CATEGORIES, CLASS, NICKNAME, PRODID and SORT-STRING types + have been added. + + . The VERSION, N and FN types MUST be specified in a vCard. + This identifies the version of the specification that the + object was formatted to. It also assures that every vCard + will include both a structured and formatted name that can be + used to identify the object. + + + + + + + + + + + +Dawson & Howes Standards Track [Page 38] + +RFC 2426 vCard MIME Directory Profile September 1998 + + +6. Acknowledgements + + The many valuable comments contributed by members of the IETF ASID + working group are gratefully acknowledged, as are the contributions + by Roland Alden, Stephen Bartlett, Alec Dun, Patrik Faltstrom, Daniel + Gurney, Bruce Johnston, Daniel Klaussen, Pete Miller, Keith Moore, + Vinod Seraphin, Michelle Watkins. Chris Newman was especially helpful + in navigating the intricacies of ABNF lore. + +7. Authors' Addresses + + BEGIN:vCard + VERSION:3.0 + FN:Frank Dawson + ORG:Lotus Development Corporation + ADR;TYPE=WORK,POSTAL,PARCEL:;;6544 Battleford Drive + ;Raleigh;NC;27613-3502;U.S.A. + TEL;TYPE=VOICE,MSG,WORK:+1-919-676-9515 + TEL;TYPE=FAX,WORK:+1-919-676-9564 + EMAIL;TYPE=INTERNET,PREF:Frank_Dawson@Lotus.com + EMAIL;TYPE=INTERNET:fdawson@earthlink.net + URL:http://home.earthlink.net/~fdawson + END:vCard + + + BEGIN:vCard + VERSION:3.0 + FN:Tim Howes + ORG:Netscape Communications Corp. + ADR;TYPE=WORK:;;501 E. Middlefield Rd.;Mountain View; + CA; 94043;U.S.A. + TEL;TYPE=VOICE,MSG,WORK:+1-415-937-3419 + TEL;TYPE=FAX,WORK:+1-415-528-4164 + EMAIL;TYPE=INTERNET:howes@netscape.com + END:vCard + +8. Security Considerations + + vCards can carry cryptographic keys or certificates, as described in + Section 3.7.2. + + Section 3.7.1 specifies a desired security classification policy for + a particular vCard. That policy is not enforced in any way. + + The vCard objects have no inherent authentication or privacy, but can + easily be carried by any security mechanism that transfers MIME + objects with authentication or privacy. In cases where threats of + "spoofed" vCard information is a concern, the vCard SHOULD BE + + + +Dawson & Howes Standards Track [Page 39] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + transported using one of these secure mechanisms. + + The information in a vCard may become out of date. In cases where the + vitality of data is important to an originator of a vCard, the "URL" + type described in section 3.6.8 SHOULD BE specified. In addition, the + "REV" type described in section 3.6.4 can be specified to indicate + the last time that the vCard data was updated. + +9. References + + [ISO 8601] ISO 8601:1988 - Data elements and interchange formats - + Information interchange - Representation of dates and + times - The International Organization for + Standardization, June, 1988. + + [ISO 8601 TC] ISO 8601, Technical Corrigendum 1 - Data elements and + interchange formats - Information interchange - + Representation of dates and times - The International + Organization for Standardization, May, 1991. + + [ISO 9070] ISO 9070, Information Processing - SGML support + facilities - Registration Procedures for Public Text + Owner Identifiers, April, 1991. + + [CCITT E.163] Recommendation E.163 - Numbering Plan for The + International Telephone Service, CCITT Blue Book, + Fascicle II.2, pp. 128-134, November, 1988. + + [CCITT X.121] Recommendation X.121 - International Numbering Plan for + Public Data Networks, CCITT Blue Book, Fascicle VIII.3, + pp. 317-332, November, 1988. + + [CCITT X.520] Recommendation X.520 - The Directory - Selected + Attribute Types, November 1988. + + [CCITT X.521] Recommendation X.521 - The Directory - Selected Object + Classes, November 1988. + + [MIME-DIR] Howes, T., Smith, M., and F. Dawson, "A MIME Content- + Type for Directory Information", RFC 2425, September + 1998. + + [RFC 1738] Berners-Lee, T., Masinter, L., and M. McCahill, + "Uniform Resource Locators (URL)", RFC 1738, December + 1994. + + [RFC 1766] Alvestrand, H., "Tags for the Identification of + Languages", RFC 1766, March 1995. + + + +Dawson & Howes Standards Track [Page 40] + +RFC 2426 vCard MIME Directory Profile September 1998 + + + [RFC 1872] Levinson, E., "The MIME Multipart/Related Content- + type", RFC 1872, December 1995. + + [RFC 2045] Freed, N., and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) - Part One: Format of Internet + Message Bodies", RFC 2045, November 1996. + + [RFC 2046] Freed, N., and N. Borenstein, "Multipurpose Internet + Mail Extensions (MIME) - Part Two: Media Types", RFC + 2046, November 1996. + + [RFC 2047] Moore, K., "Multipurpose Internet Mail Extensions + (MIME) - Part Three: Message Header Extensions for + Non-ASCII Text", RFC 2047, November 1996. + + [RFC 2048] Freed, N., Klensin, J., and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) - Part Four: + Registration Procedures", RFC 2048, January 1997. + + [RFC 2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC 2234] Crocker, D., and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 2234, November 1997. + + [UNICODE] "The Unicode Standard - Version 2.0", The Unicode + Consortium, July 1996. + + [VCARD] Internet Mail Consortium, "vCard - The Electronic + Business Card Version 2.1", + http://www.imc.org/pdi/vcard-21.txt, September 18, + 1996. + + + + + + + + + + + + + + + + + + + +Dawson & Howes Standards Track [Page 41] + +RFC 2426 vCard MIME Directory Profile September 1998 + + +10. Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Dawson & Howes Standards Track [Page 42] + diff --git a/lib/qCal/docs/rfc/rfc2445.txt b/lib/qCal/docs/rfc/rfc2445.txt new file mode 100644 index 0000000..70a3bcd --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2445.txt @@ -0,0 +1,8291 @@ + + + + + + +Network Working Group F. Dawson +Request for Comments: 2445 Lotus +Category: Standards Track D. Stenerson + Microsoft + November 1998 + + + Internet Calendaring and Scheduling Core Object Specification + (iCalendar) + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +Abstract + + There is a clear need to provide and deploy interoperable calendaring + and scheduling services for the Internet. Current group scheduling + and Personal Information Management (PIM) products are being extended + for use across the Internet, today, in proprietary ways. This memo + has been defined to provide the definition of a common format for + openly exchanging calendaring and scheduling information across the + Internet. + + This memo is formatted as a registration for a MIME media type per + [RFC 2048]. However, the format in this memo is equally applicable + for use outside of a MIME message content type. + + The proposed media type value is 'text/calendar'. This string would + label a media type containing calendaring and scheduling information + encoded as text characters formatted in a manner outlined below. + + This MIME media type provides a standard content type for capturing + calendar event, to-do and journal entry information. It also can be + used to convey free/busy time information. The content type is + suitable as a MIME message entity that can be transferred over MIME + based email systems, using HTTP or some other Internet transport. In + + + + + + +Dawson & Stenerson Standards Track [Page 1] + +RFC 2445 iCalendar November 1998 + + + addition, the content type is useful as an object for interactions + between desktop applications using the operating system clipboard, + drag/drop or file systems capabilities. + + This memo is based on the earlier work of the vCalendar specification + for the exchange of personal calendaring and scheduling information. + In order to avoid confusion with this referenced work, this memo is + to be known as the iCalendar specification. + + This memo defines the format for specifying iCalendar object methods. + An iCalendar object method is a set of usage constraints for the + iCalendar object. For example, these methods might define scheduling + messages that request an event be scheduled, reply to an event + request, send a cancellation notice for an event, modify or replace + the definition of an event, provide a counter proposal for an + original event request, delegate an event request to another + individual, request free or busy time, reply to a free or busy time + request, or provide similar scheduling messages for a to-do or + journal entry calendar component. The iCalendar Transport-indendent + Interoperability Protocol (iTIP) defined in [ITIP] is one such + scheduling protocol. + +Table of Contents + + 1 Introduction.....................................................5 + 2 Basic Grammar and Conventions....................................6 + 2.1 Formatting Conventions .......................................7 + 2.2 Related Memos ................................................8 + 2.3 International Considerations .................................8 + 3 Registration Information.........................................8 + 3.1 Content Type .................................................8 + 3.2 Parameters ...................................................9 + 3.3 Content Header Fields .......................................10 + 3.4 Encoding Considerations .....................................10 + 3.5 Security Considerations .....................................10 + 3.6 Interoperability Considerations .............................11 + 3.7 Applications Which Use This Media Type ......................11 + 3.8 Additional Information ......................................11 + 3.9 Magic Numbers ...............................................11 + 3.10 File Extensions ............................................11 + 3.11 Contact for Further Information: ...........................12 + 3.12 Intended Usage .............................................12 + 3.13 Authors/Change Controllers .................................12 + 4 iCalendar Object Specification..................................13 + 4.1 Content Lines ...............................................13 + 4.1.1 List and Field Separators ................................16 + 4.1.2 Multiple Values ..........................................16 + 4.1.3 Binary Content ...........................................16 + + + +Dawson & Stenerson Standards Track [Page 2] + +RFC 2445 iCalendar November 1998 + + + 4.1.4 Character Set ............................................17 + 4.2 Property Parameters .........................................17 + 4.2.1 Alternate Text Representation ............................18 + 4.2.2 Common Name ..............................................19 + 4.2.3 Calendar User Type .......................................20 + 4.2.4 Delegators ...............................................20 + 4.2.5 Delegatees ...............................................21 + 4.2.6 Directory Entry Reference ................................21 + 4.2.7 Inline Encoding ..........................................22 + 4.2.8 Format Type ..............................................23 + 4.2.9 Free/Busy Time Type ......................................23 + 4.2.10 Language ................................................24 + 4.2.11 Group or List Membership ................................25 + 4.2.12 Participation Status ....................................25 + 4.2.13 Recurrence Identifier Range .............................27 + 4.2.14 Alarm Trigger Relationship ..............................27 + 4.2.15 Relationship Type .......................................28 + 4.2.16 Participation Role ......................................29 + 4.2.17 RSVP Expectation ........................................29 + 4.2.18 Sent By .................................................30 + 4.2.19 Time Zone Identifier ....................................30 + 4.2.20 Value Data Types ........................................32 + 4.3 Property Value Data Types ...................................32 + 4.3.1 Binary ...................................................33 + 4.3.2 Boolean ..................................................33 + 4.3.3 Calendar User Address ....................................34 + 4.3.4 Date .....................................................34 + 4.3.5 Date-Time ................................................35 + 4.3.6 Duration .................................................37 + 4.3.7 Float ....................................................38 + 4.3.8 Integer ..................................................38 + 4.3.9 Period of Time ...........................................39 + 4.3.10 Recurrence Rule .........................................40 + 4.3.11 Text ....................................................45 + 4.3.12 Time ....................................................47 + 4.3.13 URI .....................................................49 + 4.3.14 UTC Offset ..............................................49 + 4.4 iCalendar Object ............................................50 + 4.5 Property ....................................................51 + 4.6 Calendar Components .........................................51 + 4.6.1 Event Component ..........................................52 + 4.6.2 To-do Component ..........................................55 + 4.6.3 Journal Component ........................................56 + 4.6.4 Free/Busy Component ......................................58 + 4.6.5 Time Zone Component ......................................60 + 4.6.6 Alarm Component ..........................................67 + 4.7 Calendar Properties .........................................73 + 4.7.1 Calendar Scale ...........................................73 + + + +Dawson & Stenerson Standards Track [Page 3] + +RFC 2445 iCalendar November 1998 + + + 4.7.2 Method ...................................................74 + 4.7.3 Product Identifier .......................................75 + 4.7.4 Version ..................................................76 + 4.8 Component Properties ........................................77 + 4.8.1 Descriptive Component Properties .........................77 + 4.8.1.1 Attachment ...........................................77 + 4.8.1.2 Categories ...........................................78 + 4.8.1.3 Classification .......................................79 + 4.8.1.4 Comment ..............................................80 + 4.8.1.5 Description ..........................................81 + 4.8.1.6 Geographic Position ..................................82 + 4.8.1.7 Location .............................................84 + 4.8.1.8 Percent Complete .....................................85 + 4.8.1.9 Priority .............................................85 + 4.8.1.10 Resources ...........................................87 + 4.8.1.11 Status ..............................................88 + 4.8.1.12 Summary .............................................89 + 4.8.2 Date and Time Component Properties .......................90 + 4.8.2.1 Date/Time Completed ..................................90 + 4.8.2.2 Date/Time End ........................................91 + 4.8.2.3 Date/Time Due ........................................92 + 4.8.2.4 Date/Time Start ......................................93 + 4.8.2.5 Duration .............................................94 + 4.8.2.6 Free/Busy Time .......................................95 + 4.8.2.7 Time Transparency ....................................96 + 4.8.3 Time Zone Component Properties ...........................97 + 4.8.3.1 Time Zone Identifier .................................97 + 4.8.3.2 Time Zone Name .......................................98 + 4.8.3.3 Time Zone Offset From ................................99 + 4.8.3.4 Time Zone Offset To .................................100 + 4.8.3.5 Time Zone URL .......................................101 + 4.8.4 Relationship Component Properties .......................102 + 4.8.4.1 Attendee ............................................102 + 4.8.4.2 Contact .............................................104 + 4.8.4.3 Organizer ...........................................106 + 4.8.4.4 Recurrence ID .......................................107 + 4.8.4.5 Related To ..........................................109 + 4.8.4.6 Uniform Resource Locator ............................110 + 4.8.4.7 Unique Identifier ...................................111 + 4.8.5 Recurrence Component Properties .........................112 + 4.8.5.1 Exception Date/Times ................................112 + 4.8.5.2 Exception Rule ......................................114 + 4.8.5.3 Recurrence Date/Times ...............................115 + 4.8.5.4 Recurrence Rule .....................................117 + 4.8.6 Alarm Component Properties ..............................126 + 4.8.6.1 Action ..............................................126 + 4.8.6.2 Repeat Count ........................................126 + 4.8.6.3 Trigger .............................................127 + + + +Dawson & Stenerson Standards Track [Page 4] + +RFC 2445 iCalendar November 1998 + + + 4.8.7 Change Management Component Properties ..................129 + 4.8.7.1 Date/Time Created ...................................129 + 4.8.7.2 Date/Time Stamp .....................................130 + 4.8.7.3 Last Modified .......................................131 + 4.8.7.4 Sequence Number .....................................131 + 4.8.8 Miscellaneous Component Properties ......................133 + 4.8.8.1 Non-standard Properties .............................133 + 4.8.8.2 Request Status ......................................134 + 5 iCalendar Object Examples......................................136 + 6 Recommended Practices..........................................140 + 7 Registration of Content Type Elements..........................141 + 7.1 Registration of New and Modified iCalendar Object Methods ..141 + 7.2 Registration of New Properties .............................141 + 7.2.1 Define the property .....................................142 + 7.2.2 Post the Property definition ............................143 + 7.2.3 Allow a comment period ..................................143 + 7.2.4 Submit the property for approval ........................143 + 7.3 Property Change Control ....................................143 + 8 References.....................................................144 + 9 Acknowledgments................................................145 + 10 Authors' and Chairs' Addresses................................146 + 11 Full Copyright Statement......................................148 + +1 Introduction + + The use of calendaring and scheduling has grown considerably in the + last decade. Enterprise and inter-enterprise business has become + dependent on rapid scheduling of events and actions using this + information technology. However, the longer term growth of + calendaring and scheduling, is currently limited by the lack of + Internet standards for the message content types that are central to + these knowledgeware applications. This memo is intended to progress + the level of interoperability possible between dissimilar calendaring + and scheduling applications. This memo defines a MIME content type + for exchanging electronic calendaring and scheduling information. The + Internet Calendaring and Scheduling Core Object Specification, or + iCalendar, allows for the capture and exchange of information + normally stored within a calendaring and scheduling application; such + as a Personal Information Manager (PIM) or a Group Scheduling + product. + + The iCalendar format is suitable as an exchange format between + applications or systems. The format is defined in terms of a MIME + content type. This will enable the object to be exchanged using + several transports, including but not limited to SMTP, HTTP, a file + system, desktop interactive protocols such as the use of a memory- + based clipboard or drag/drop interactions, point-to-point + asynchronous communication, wired-network transport, or some form of + + + +Dawson & Stenerson Standards Track [Page 5] + +RFC 2445 iCalendar November 1998 + + + unwired transport such as infrared might also be used. + + The memo also provides for the definition of iCalendar object methods + that will map this content type to a set of messages for supporting + calendaring and scheduling operations such as requesting, replying + to, modifying, and canceling meetings or appointments, to-dos and + journal entries. The iCalendar object methods can be used to define + other calendaring and scheduling operations such a requesting for and + replying with free/busy time data. Such a scheduling protocol is + defined in the iCalendar Transport-independent Interoperability + Protocol (iTIP) defined in [ITIP]. + + The memo also includes a formal grammar for the content type based on + the Internet ABNF defined in [RFC 2234]. This ABNF is required for + the implementation of parsers and to serve as the definitive + reference when ambiguities or questions arise in interpreting the + descriptive prose definition of the memo. + +2 Basic Grammar and Conventions + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY" and + "OPTIONAL" in this document are to be interoperated as described in + [RFC 2119]. + + This memo makes use of both a descriptive prose and a more formal + notation for defining the calendaring and scheduling format. + + The notation used in this memo is the ABNF notation of [RFC 2234]. + Readers intending on implementing this format defined in this memo + should be familiar with this notation in order to properly interpret + the specifications of this memo. + + All numeric and hexadecimal values used in this memo are given in + decimal notation. + + All names of properties, property parameters, enumerated property + values and property parameter values are case-insensitive. However, + all other property values are case-sensitive, unless otherwise + stated. + + Note: All indented editorial notes, such as this one, are + intended to provide the reader with additional information. The + information is not essential to the building of an + implementation conformant with this memo. The information is + provided to highlight a particular feature or characteristic of + the memo. + + + + +Dawson & Stenerson Standards Track [Page 6] + +RFC 2445 iCalendar November 1998 + + + The format for the iCalendar object is based on the syntax of the + [RFC 2425] content type. While the iCalendar object is not a profile + of the [RFC 2425] content type, it does reuse a number of the + elements from the [RFC 2425] specification. + +2.1 Formatting Conventions + + The mechanisms defined in this memo are defined in prose. Many of the + terms used to describe these have common usage that is different than + the standards usage of this memo. In order to reference within this + memo elements of the calendaring and scheduling model, core object + (this memo) or interoperability protocol [ITIP] some formatting + conventions have been used. Calendaring and scheduling roles are + referred to in quoted-strings of text with the first character of + each word in upper case. For example, "Organizer" refers to a role of + a "Calendar User" within the scheduling protocol defined by [ITIP]. + Calendar components defined by this memo are referred to with + capitalized, quoted-strings of text. All calendar components start + with the letter "V". For example, "VEVENT" refers to the event + calendar component, "VTODO" refers to the to-do calendar component + and "VJOURNAL" refers to the daily journal calendar component. + Scheduling methods defined by [ITIP] are referred to with + capitalized, quoted-strings of text. For example, "REQUEST" refers to + the method for requesting a scheduling calendar component be created + or modified, "REPLY" refers to the method a recipient of a request + uses to update their status with the "Organizer" of the calendar + component. + + The properties defined by this memo are referred to with capitalized, + quoted-strings of text, followed by the word "property". For example, + "ATTENDEE" property refers to the iCalendar property used to convey + the calendar address of a calendar user. Property parameters defined + by this memo are referred to with lowercase, quoted-strings of text, + followed by the word "parameter". For example, "value" parameter + refers to the iCalendar property parameter used to override the + default data type for a property value. Enumerated values defined by + this memo are referred to with capitalized text, either alone or + followed by the word "value". For example, the "MINUTELY" value can + be used with the "FREQ" component of the "RECUR" data type to specify + repeating components based on an interval of one minute or more. + + + + + + + + + + + +Dawson & Stenerson Standards Track [Page 7] + +RFC 2445 iCalendar November 1998 + + +2.2 Related Memos + + Implementers will need to be familiar with several other memos that, + along with this memo, form a framework for Internet calendaring and + scheduling standards. This memo, [ICAL], specifies a core + specification of objects, data types, properties and property + parameters. + + [ITIP] - specifies an interoperability protocol for scheduling + between different implementations; + + [IMIP] specifies an Internet email binding for [ITIP]. + + This memo does not attempt to repeat the specification of concepts or + definitions from these other memos. Where possible, references are + made to the memo that provides for the specification of these + concepts or definitions. + +2.3 International Considerations + + In the rest of this document, descriptions of characters are of the + form "character name (codepoint)", where "codepoint" is from the US- + ASCII character set. The "character name" is the authoritative + description; (codepoint) is a reference to that character in US-ASCII + or US-ASCII compatible sets (for example the ISO-8859-x family, UTF- + 8, ISO-2022-xx, KOI8-R). If a non-US-ASCII compatible character set + is used, appropriate code-point from that character set MUST be + chosen instead. Use of non-US-ASCII-compatible character sets is NOT + recommended. + +3 Registration Information + + The Calendaring and Scheduling Core Object Specification is intended + for use as a MIME content type. However, the implementation of the + memo is in no way limited solely as a MIME content type. + +3.1 Content Type + + The following text is intended to register this memo as the MIME + content type "text/calendar". + + To: ietf-types@uninett.no + + Subject: Registration of MIME content type text/calendar. + + MIME media type name: text + + MIME subtype name: calendar + + + +Dawson & Stenerson Standards Track [Page 8] + +RFC 2445 iCalendar November 1998 + + +3.2 Parameters + + Required parameters: none + + Optional parameters: charset, method, component and optinfo + + The "charset" parameter is defined in [RFC 2046] for other body + parts. It is used to identify the default character set used within + the body part. + + The "method" parameter is used to convey the iCalendar object method + or transaction semantics for the calendaring and scheduling + information. It also is an identifier for the restricted set of + properties and values that the iCalendar object consists of. The + parameter is to be used as a guide for applications interpreting the + information contained within the body part. It SHOULD NOT be used to + exclude or require particular pieces of information unless the + identified method definition specifically calls for this behavior. + Unless specifically forbidden by a particular method definition, a + text/calendar content type can contain any set of properties + permitted by the Calendaring and Scheduling Core Object + Specification. The "method" parameter MUST be the same value as that + specified in the "METHOD" component property in the iCalendar object. + If one is present, the other MUST also be present. + + The value for the "method" parameter is defined as follows: + + method = 1*(ALPHA / DIGIT / "-") + ; IANA registered iCalendar object method + + The "component" parameter conveys the type of iCalendar calendar + component within the body part. If the iCalendar object contains more + than one calendar component type, then multiple component parameters + MUST be specified. + + The value for the "component" parameter is defined as follows: + + component = ("VEVENT" / "VTODO" / "VJOURNAL" / "VFREEBUSY" + / "VTIMEZONE" / x-name / iana-token) + + The "optinfo" parameter conveys optional information about the + iCalendar object within the body part. This parameter can only + specify semantics already specified by the iCalendar object and that + can be otherwise determined by parsing the body part. In addition, + the optional information specified by this parameter MUST be + consistent with that information specified by the iCalendar object. + For example, it can be used to convey the "Attendee" response status + to a meeting request. The parameter value consists of a string value. + + + +Dawson & Stenerson Standards Track [Page 9] + +RFC 2445 iCalendar November 1998 + + + The parameter can be specified multiple times. + + This parameter MAY only specify semantics already specified by the + iCalendar object and that can be otherwise determined by parsing the + body part. + + The value for the "optinfo" parameter is defined as follows: + + optinfo = infovalue / qinfovalue + + infovalue = iana-token / x-name + + qinfovalue = DQUOTE (infovalue) DQUOTE + +3.3 Content Header Fields + + Optional content header fields: Any header fields defined by [RFC + 2045]. + +3.4 Encoding Considerations + + This MIME content type can contain 8bit characters, so the use of + quoted-printable or BASE64 MIME content-transfer-encodings might be + necessary when iCalendar objects are transferred across protocols + restricted to the 7bit repertoire. Note that a text valued property + in the content entity can also have content encoding of special + characters using a BACKSLASH character (US-ASCII decimal 92) + escapement technique. This means that content values can end up + encoded twice. + +3.5 Security Considerations + + SPOOFING - - In this memo, the "Organizer" is the only person + authorized to make changes to an existing "VEVENT", "VTODO", + "VJOURNAL" calendar component and redistribute the updates to the + "Attendees". An iCalendar object that maliciously changes or cancels + an existing "VEVENT", "VTODO" or "VJOURNAL" or "VFREEBUSY" calendar + component might be constructed by someone other than the "Organizer" + and sent to the "Attendees". In addition in this memo, other than the + "Organizer", an "Attendee" of a "VEVENT", "VTODO", "VJOURNAL" + calendar component is the only other person authorized to update any + parameter associated with their "ATTENDEE" property and send it to + the "Organizer". An iCalendar object that maliciously changes the + "ATTENDEE" parameters can be constructed by someone other than the + real "Attendee" and sent to the "Organizer". + + + + + + +Dawson & Stenerson Standards Track [Page 10] + +RFC 2445 iCalendar November 1998 + + + PROCEDURAL ALARMS - - An iCalendar object can be created that + contains a "VEVENT" and "VTODO" calendar component with "VALARM" + calendar components. The "VALARM" calendar component can be of type + PROCEDURE and can have an attachment containing some sort of + executable program. Implementations that incorporate these types of + alarms are subject to any virus or malicious attack that might occur + as a result of executing the attachment. + + ATTACHMENTS - - An iCalendar object can include references to Uniform + Resource Locators that can be programmed resources. + + Implementers and users of this memo should be aware of the network + security implications of accepting and parsing such information. In + addition, the security considerations observed by implementations of + electronic mail systems should be followed for this memo. + +3.6 Interoperability Considerations + + This MIME content type is intended to define a common format for + conveying calendaring and scheduling information between different + systems. It is heavily based on the earlier [VCAL] industry + specification. + +3.7 Applications Which Use This Media Type + + This content-type is designed for widespread use by Internet + calendaring and scheduling applications. In addition, applications in + the workflow and document management area might find this content- + type applicable. The [ITIP] and [IMIP] Internet protocols directly + use this content-type also. Future work on an Internet calendar + access protocol will utilize this content-type too. + +3.8 Additional Information + + This memo defines this content-type. + +3.9 Magic Numbers + + None. + +3.10 File Extensions + + The file extension of "ics" is to be used to designate a file + containing (an arbitrary set of) calendaring and scheduling + information consistent with this MIME content type. + + + + + + +Dawson & Stenerson Standards Track [Page 11] + +RFC 2445 iCalendar November 1998 + + + The file extension of "ifb" is to be used to designate a file + containing free or busy time information consistent with this MIME + content type. + + Macintosh file type codes: The file type code of "iCal" is to be used + in Apple MacIntosh operating system environments to designate a file + containing calendaring and scheduling information consistent with + this MIME media type. + + The file type code of "iFBf" is to be used in Apple MacIntosh + operating system environments to designate a file containing free or + busy time information consistent with this MIME media type. + +3.11 Contact for Further Information: + + Frank Dawson + 6544 Battleford Drive + Raleigh, NC 27613-3502 + 919-676-9515 (Telephone) + 919-676-9564 (Data/Facsimile) + Frank_Dawson@Lotus.com (Internet Mail) + + Derik Stenerson + One Microsoft Way + Redmond, WA 98052-6399 + 425-936-5522 (Telephone) + 425-936-7329 (Facsimile) + deriks@microsoft.com (Internet Mail) + +3.12 Intended Usage + + COMMON + +3.13 Authors/Change Controllers + + Frank Dawson + 6544 Battleford Drive + Raleigh, NC 27613-3502 + 919-676-9515 (Telephone) + 919-676-9564 (Data/Facsimile) + Frank_Dawson@Lotus.com (Internet Mail) + + Derik Stenerson + One Microsoft Way + Redmond, WA 98052-6399 + 425-936-5522 (Telephone) + 425-936-7329 (Facsimile) + deriks@microsoft.com (Internet Mail) + + + +Dawson & Stenerson Standards Track [Page 12] + +RFC 2445 iCalendar November 1998 + + +4 iCalendar Object Specification + + The following sections define the details of a Calendaring and + Scheduling Core Object Specification. This information is intended to + be an integral part of the MIME content type registration. In + addition, this information can be used independent of such content + registration. In particular, this memo has direct applicability for + use as a calendaring and scheduling exchange format in file-, memory- + or network-based transport mechanisms. + +4.1 Content Lines + + The iCalendar object is organized into individual lines of text, + called content lines. Content lines are delimited by a line break, + which is a CRLF sequence (US-ASCII decimal 13, followed by US-ASCII + decimal 10). + + Lines of text SHOULD NOT be longer than 75 octets, excluding the line + break. Long content lines SHOULD be split into a multiple line + representations using a line "folding" technique. That is, a long + line can be split between any two characters by inserting a CRLF + immediately followed by a single linear white space character (i.e., + SPACE, US-ASCII decimal 32 or HTAB, US-ASCII decimal 9). Any sequence + of CRLF followed immediately by a single linear white space character + is ignored (i.e., removed) when processing the content type. + + For example the line: + + DESCRIPTION:This is a long description that exists on a long line. + + Can be represented as: + + DESCRIPTION:This is a lo + ng description + that exists on a long line. + + The process of moving from this folded multiple line representation + to its single line representation is called "unfolding". Unfolding is + accomplished by removing the CRLF character and the linear white + space character that immediately follows. + + When parsing a content line, folded lines MUST first be unfolded + according to the unfolding procedure described above. When generating + a content line, lines longer than 75 octets SHOULD be folded + according to the folding procedure described above. + + + + + + +Dawson & Stenerson Standards Track [Page 13] + +RFC 2445 iCalendar November 1998 + + + The content information associated with an iCalendar object is + formatted using a syntax similar to that defined by [RFC 2425]. That + is, the content information consists of CRLF-separated content lines. + + The following notation defines the lines of content in an iCalendar + object: + + contentline = name *(";" param ) ":" value CRLF + ; This ABNF is just a general definition for an initial parsing + ; of the content line into its property name, parameter list, + ; and value string + + ; When parsing a content line, folded lines MUST first + ; be unfolded according to the unfolding procedure + ; described above. When generating a content line, lines + ; longer than 75 octets SHOULD be folded according to + ; the folding procedure described above. + + name = x-name / iana-token + + iana-token = 1*(ALPHA / DIGIT / "-") + ; iCalendar identifier registered with IANA + + x-name = "X-" [vendorid "-"] 1*(ALPHA / DIGIT / "-") + ; Reservered for experimental use. Not intended for use in + ; released products. + + vendorid = 3*(ALPHA / DIGIT) ;Vendor identification + + param = param-name "=" param-value + *("," param-value) + ; Each property defines the specific ABNF for the parameters + ; allowed on the property. Refer to specific properties for + ; precise parameter ABNF. + + param-name = iana-token / x-token + + param-value = paramtext / quoted-string + + paramtext = *SAFE-CHAR + + value = *VALUE-CHAR + + quoted-string = DQUOTE *QSAFE-CHAR DQUOTE + + NON-US-ASCII = %x80-F8 + ; Use restricted by charset parameter + ; on outer MIME object (UTF-8 preferred) + + + +Dawson & Stenerson Standards Track [Page 14] + +RFC 2445 iCalendar November 1998 + + + QSAFE-CHAR = WSP / %x21 / %x23-7E / NON-US-ASCII + ; Any character except CTLs and DQUOTE + + SAFE-CHAR = WSP / %x21 / %x23-2B / %x2D-39 / %x3C-7E + / NON-US-ASCII + ; Any character except CTLs, DQUOTE, ";", ":", "," + + VALUE-CHAR = WSP / %x21-7E / NON-US-ASCII + ; Any textual character + + CR = %x0D + ; carriage return + + LF = %x0A + ; line feed + + CRLF = CR LF + ; Internet standard newline + + CTL = %x00-08 / %x0A-1F / %x7F + ; Controls + + ALPHA = %x41-5A / %x61-7A ; A-Z / a-z + + DIGIT = %x30-39 + ; 0-9 + + DQUOTE = %x22 + ; Quotation Mark + + WSP = SPACE / HTAB + + SPACE = %x20 + + HTAB = %x09 + + The property value component of a content line has a format that is + property specific. Refer to the section describing each property for + a definition of this format. + + All names of properties, property parameters, enumerated property + values and property parameter values are case-insensitive. However, + all other property values are case-sensitive, unless otherwise + stated. + + + + + + + +Dawson & Stenerson Standards Track [Page 15] + +RFC 2445 iCalendar November 1998 + + +4.1.1 List and Field Separators + + Some properties and parameters allow a list of values. Values in a + list of values MUST be separated by a COMMA character (US-ASCII + decimal 44). There is no significance to the order of values in a + list. For those parameter values (such as those that specify URI + values) that are specified in quoted-strings, the individual quoted- + strings are separated by a COMMA character (US-ASCII decimal 44). + + Some property values are defined in terms of multiple parts. These + structured property values MUST have their value parts separated by a + SEMICOLON character (US-ASCII decimal 59). + + Some properties allow a list of parameters. Each property parameter + in a list of property parameters MUST be separated by a SEMICOLON + character (US-ASCII decimal 59). + + Property parameters with values containing a COLON, a SEMICOLON or a + COMMA character MUST be placed in quoted text. + + For example, in the following properties a SEMICOLON is used to + separate property parameters from each other, and a COMMA is used to + separate property values in a value list. + + ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT:MAILTO: + jsmith@host.com + + RDATE;VALUE=DATE:19970304,19970504,19970704,19970904 + +4.1.2 Multiple Values + + Some properties defined in the iCalendar object can have multiple + values. The general rule for encoding multi-valued items is to simply + create a new content line for each value, including the property + name. However, it should be noted that some properties support + encoding multiple values in a single property by separating the + values with a COMMA character (US-ASCII decimal 44). Individual + property definitions should be consulted for determining whether a + specific property allows multiple values and in which of these two + forms. + +4.1.3 Binary Content + + Binary content information in an iCalendar object SHOULD be + referenced using a URI within a property value. That is the binary + content information SHOULD be placed in an external MIME entity that + can be referenced by a URI from within the iCalendar object. In + applications where this is not feasible, binary content information + + + +Dawson & Stenerson Standards Track [Page 16] + +RFC 2445 iCalendar November 1998 + + + can be included within an iCalendar object, but only after first + encoding it into text using the "BASE64" encoding method defined in + [RFC 2045]. Inline binary contact SHOULD only be used in applications + whose special circumstances demand that an iCalendar object be + expressed as a single entity. A property containing inline binary + content information MUST specify the "ENCODING" property parameter. + Binary content information placed external to the iCalendar object + MUST be referenced by a uniform resource identifier (URI). + + The following example specifies an "ATTACH" property that references + an attachment external to the iCalendar object with a URI reference: + + ATTACH:http://xyz.com/public/quarterly-report.doc + + The following example specifies an "ATTACH" property with inline + binary encoded content information: + + ATTACH;FMTTYPE=image/basic;ENCODING=BASE64;VALUE=BINARY: + MIICajCCAdOgAwIBAgICBEUwDQYJKoZIhvcNAQEEBQAwdzELMAkGA1U + EBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlIENvbW11bmljYXRpb25zIE + <...remainder of "BASE64" encoded binary data...> + +4.1.4 Character Set + + There is not a property parameter to declare the character set used + in a property value. The default character set for an iCalendar + object is UTF-8 as defined in [RFC 2279]. + + The "charset" Content-Type parameter can be used in MIME transports + to specify any other IANA registered character set. + +4.2 Property Parameters + + A property can have attributes associated with it. These "property + parameters" contain meta-information about the property or the + property value. Property parameters are provided to specify such + information as the location of an alternate text representation for a + property value, the language of a text property value, the data type + of the property value and other attributes. + + Property parameter values that contain the COLON (US-ASCII decimal + 58), SEMICOLON (US-ASCII decimal 59) or COMMA (US-ASCII decimal 44) + character separators MUST be specified as quoted-string text values. + Property parameter values MUST NOT contain the DOUBLE-QUOTE (US-ASCII + decimal 22) character. The DOUBLE-QUOTE (US-ASCII decimal 22) + character is used as a delimiter for parameter values that contain + restricted characters or URI text. For example: + + + + +Dawson & Stenerson Standards Track [Page 17] + +RFC 2445 iCalendar November 1998 + + + DESCRIPTION;ALTREP="http://www.wiz.org":The Fall'98 Wild Wizards + Conference - - Las Vegas, NV, USA + + Property parameter values that are not in quoted strings are case + insensitive. + + The general property parameters defined by this memo are defined by + the following notation: + + parameter = altrepparam ; Alternate text representation + / cnparam ; Common name + / cutypeparam ; Calendar user type + / delfromparam ; Delegator + / deltoparam ; Delegatee + / dirparam ; Directory entry + / encodingparam ; Inline encoding + / fmttypeparam ; Format type + / fbtypeparam ; Free/busy time type + / languageparam ; Language for text + / memberparam ; Group or list membership + / partstatparam ; Participation status + / rangeparam ; Recurrence identifier range + / trigrelparam ; Alarm trigger relationship + / reltypeparam ; Relationship type + / roleparam ; Participation role + / rsvpparam ; RSVP expectation + / sentbyparam ; Sent by + / tzidparam ; Reference to time zone object + / valuetypeparam ; Property value data type + / ianaparam + ; Some other IANA registered iCalendar parameter. + / xparam + ; A non-standard, experimental parameter. + + ianaparam = iana-token "=" param-value *("," param-value) + + xparam =x-name "=" param-value *("," param-value) + +4.2.1 Alternate Text Representation + + Parameter Name: ALTREP + + Purpose: To specify an alternate text representation for the property + value. + + Format Definition: The property parameter is defined by the following + notation: + + + + +Dawson & Stenerson Standards Track [Page 18] + +RFC 2445 iCalendar November 1998 + + + altrepparam = "ALTREP" "=" DQUOTE uri DQUOTE + + Description: The parameter specifies a URI that points to an + alternate representation for a textual property value. A property + specifying this parameter MUST also include a value that reflects the + default representation of the text value. The individual URI + parameter values MUST each be specified in a quoted-string. + + Example: + + DESCRIPTION;ALTREP="CID:":Project + XYZ Review Meeting will include the following agenda items: (a) + Market Overview, (b) Finances, (c) Project Management + + The "ALTREP" property parameter value might point to a "text/html" + content portion. + + Content-Type:text/html + Content-Id: + + +

    Project XYZ Review Meeting will include the following + agenda items:

    1. Market + Overview
    2. Finances
    3. Project Management

    + + +4.2.2 Common Name + + Parameter Name: CN + + Purpose: To specify the common name to be associated with the + calendar user specified by the property. + + Format Definition: The property parameter is defined by the following + notation: + + cnparam = "CN" "=" param-value + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. The parameter specifies the common name to be + associated with the calendar user specified by the property. The + parameter value is text. The parameter value can be used for display + text to be associated with the calendar address specified by the + property. + + + + + + + +Dawson & Stenerson Standards Track [Page 19] + +RFC 2445 iCalendar November 1998 + + + Example: + + ORGANIZER;CN="John Smith":MAILTO:jsmith@host.com + +4.2.3 Calendar User Type + + Parameter Name: CUTYPE + + Purpose: To specify the type of calendar user specified by the + property. + + Format Definition: The property parameter is defined by the following + notation: + + cutypeparam = "CUTYPE" "=" + ("INDIVIDUAL" ; An individual + / "GROUP" ; A group of individuals + / "RESOURCE" ; A physical resource + / "ROOM" ; A room resource + / "UNKNOWN" ; Otherwise not known + / x-name ; Experimental type + / iana-token) ; Other IANA registered + ; type + ; Default is INDIVIDUAL + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. The parameter identifies the type of calendar + user specified by the property. If not specified on a property that + allows this parameter, the default is INDIVIDUAL. + + Example: + + ATTENDEE;CUTYPE=GROUP:MAILTO:ietf-calsch@imc.org + +4.2.4 Delegators + + Parameter Name: DELEGATED-FROM + + Purpose: To specify the calendar users that have delegated their + participation to the calendar user specified by the property. + + Format Definition: The property parameter is defined by the following + notation: + + delfromparam = "DELEGATED-FROM" "=" DQUOTE cal-address DQUOTE + *("," DQUOTE cal-address DQUOTE) + + + + + +Dawson & Stenerson Standards Track [Page 20] + +RFC 2445 iCalendar November 1998 + + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. This parameter can be specified on a property + that has a value type of calendar address. This parameter specifies + those calendar uses that have delegated their participation in a + group scheduled event or to-do to the calendar user specified by the + property. The value MUST be a MAILTO URI as defined in [RFC 1738]. + The individual calendar address parameter values MUST each be + specified in a quoted-string. + + Example: + + ATTENDEE;DELEGATED-FROM="MAILTO:jsmith@host.com":MAILTO: + jdoe@host.com + +4.2.5 Delegatees + + Parameter Name: DELEGATED-TO + + Purpose: To specify the calendar users to whom the calendar user + specified by the property has delegated participation. + + Format Definition: The property parameter is defined by the following + notation: + + deltoparam = "DELEGATED-TO" "=" DQUOTE cal-address DQUOTE + *("," DQUOTE cal-address DQUOTE) + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. This parameter specifies those calendar users + whom have been delegated participation in a group scheduled event or + to-do by the calendar user specified by the property. The value MUST + be a MAILTO URI as defined in [RFC 1738]. The individual calendar + address parameter values MUST each be specified in a quoted-string. + + Example: + + ATTENDEE;DELEGATED-TO="MAILTO:jdoe@host.com","MAILTO:jqpublic@ + host.com":MAILTO:jsmith@host.com + +4.2.6 Directory Entry Reference + + Parameter Name: DIR + + Purpose: To specify reference to a directory entry associated with + the calendar user specified by the property. + + Format Definition: The property parameter is defined by the following + notation: + + + +Dawson & Stenerson Standards Track [Page 21] + +RFC 2445 iCalendar November 1998 + + + dirparam = "DIR" "=" DQUOTE uri DQUOTE + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. The parameter specifies a reference to the + directory entry associated with the calendar user specified by the + property. The parameter value is a URI. The individual URI parameter + values MUST each be specified in a quoted-string. + + Example: + + ORGANIZER;DIR="ldap://host.com:6666/o=eDABC%20Industries,c=3DUS?? + (cn=3DBJim%20Dolittle)":MAILTO:jimdo@host1.com + +4.2.7 Inline Encoding + + Parameter Name: ENCODING + + Purpose: To specify an alternate inline encoding for the property + value. + + Format Definition: The property parameter is defined by the following + notation: + + encodingparam = "ENCODING" "=" + ("8BIT" + ; "8bit" text encoding is defined in [RFC 2045] + / "BASE64" + ; "BASE64" binary encoding format is defined in [RFC 2045] + / iana-token + ; Some other IANA registered iCalendar encoding type + / x-name) + ; A non-standard, experimental encoding type + + Description: The property parameter identifies the inline encoding + used in a property value. The default encoding is "8BIT", + corresponding to a property value consisting of text. The "BASE64" + encoding type corresponds to a property value encoded using the + "BASE64" encoding defined in [RFC 2045]. + + If the value type parameter is ";VALUE=BINARY", then the inline + encoding parameter MUST be specified with the value + ";ENCODING=BASE64". + + + + + + + + + +Dawson & Stenerson Standards Track [Page 22] + +RFC 2445 iCalendar November 1998 + + + Example: + + ATTACH;FMTYPE=IMAGE/JPEG;ENCODING=BASE64;VALUE=BINARY:MIICajC + CAdOgAwIBAgICBEUwDQYJKoZIhvcNAQEEBQAwdzELMAkGA1UEBhMCVVMxLDA + qBgNVBAoTI05ldHNjYXBlIENvbW11bmljYXRpb25zIENvcnBvcmF0aW9uMRw + <...remainder of "BASE64" encoded binary data...> + +4.2.8 Format Type + + Parameter Name: FMTTYPE + + Purpose: To specify the content type of a referenced object. + + Format Definition: The property parameter is defined by the following + notation: + + fmttypeparam = "FMTTYPE" "=" iana-token + ; A IANA registered content type + / x-name + ; A non-standard content type + + Description: This parameter can be specified on properties that are + used to reference an object. The parameter specifies the content type + of the referenced object. For example, on the "ATTACH" property, a + FTP type URI value does not, by itself, necessarily convey the type + of content associated with the resource. The parameter value MUST be + the TEXT for either an IANA registered content type or a non-standard + content type. + + Example: + + ATTACH;FMTTYPE=application/binary:ftp://domain.com/pub/docs/ + agenda.doc + +4.2.9 Free/Busy Time Type + + Parameter Name: FBTYPE + + Purpose: To specify the free or busy time type. + + Format Definition: The property parameter is defined by the following + notation: + + fbtypeparam = "FBTYPE" "=" ("FREE" / "BUSY" + / "BUSY-UNAVAILABLE" / "BUSY-TENTATIVE" + / x-name + ; Some experimental iCalendar data type. + / iana-token) + + + +Dawson & Stenerson Standards Track [Page 23] + +RFC 2445 iCalendar November 1998 + + + ; Some other IANA registered iCalendar data type. + + Description: The parameter specifies the free or busy time type. The + value FREE indicates that the time interval is free for scheduling. + The value BUSY indicates that the time interval is busy because one + or more events have been scheduled for that interval. The value + BUSY-UNAVAILABLE indicates that the time interval is busy and that + the interval can not be scheduled. The value BUSY-TENTATIVE indicates + that the time interval is busy because one or more events have been + tentatively scheduled for that interval. If not specified on a + property that allows this parameter, the default is BUSY. + + Example: The following is an example of this parameter on a FREEBUSY + property. + + FREEBUSY;FBTYPE=BUSY:19980415T133000Z/19980415T170000Z + +4.2.10 Language + + Parameter Name: LANGUAGE + + Purpose: To specify the language for text values in a property or + property parameter. + + Format Definition: The property parameter is defined by the following + notation: + + languageparam = "LANGUAGE" "=" language + + language = + + Description: This parameter can be specified on properties with a + text value type. The parameter identifies the language of the text in + the property or property parameter value. The value of the "language" + property parameter is that defined in [RFC 1766]. + + For transport in a MIME entity, the Content-Language header field can + be used to set the default language for the entire body part. + Otherwise, no default language is assumed. + + Example: + + SUMMARY;LANGUAGE=us-EN:Company Holiday Party + + LOCATION;LANGUAGE=en:Germany + LOCATION;LANGUAGE=no:Tyskland + + + + + +Dawson & Stenerson Standards Track [Page 24] + +RFC 2445 iCalendar November 1998 + + + The following example makes use of the Quoted-Printable encoding in + order to represent non-ASCII characters. + + LOCATION;LANGUAGE=da:K=F8benhavn + LOCATION;LANGUAGE=en:Copenhagen + +4.2.11 Group or List Membership + + Parameter Name: MEMBER + + Purpose: To specify the group or list membership of the calendar user + specified by the property. + + Format Definition: The property parameter is defined by the following + notation: + + memberparam = "MEMBER" "=" DQUOTE cal-address DQUOTE + *("," DQUOTE cal-address DQUOTE) + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. The parameter identifies the groups or list + membership for the calendar user specified by the property. The + parameter value either a single calendar address in a quoted-string + or a COMMA character (US-ASCII decimal 44) list of calendar + addresses, each in a quoted-string. The individual calendar address + parameter values MUST each be specified in a quoted-string. + + Example: + + ATTENDEE;MEMBER="MAILTO:ietf-calsch@imc.org":MAILTO:jsmith@host.com + + ATTENDEE;MEMBER="MAILTO:projectA@host.com","MAILTO:projectB@host. + com":MAILTO:janedoe@host.com + +4.2.12 Participation Status + + Parameter Name: PARTSTAT + + Purpose: To specify the participation status for the calendar user + specified by the property. + + Format Definition: The property parameter is defined by the following + notation: + + partstatparam = "PARTSTAT" "=" + ("NEEDS-ACTION" ; Event needs action + / "ACCEPTED" ; Event accepted + / "DECLINED" ; Event declined + + + +Dawson & Stenerson Standards Track [Page 25] + +RFC 2445 iCalendar November 1998 + + + / "TENTATIVE" ; Event tentatively + ; accepted + / "DELEGATED" ; Event delegated + / x-name ; Experimental status + / iana-token) ; Other IANA registered + ; status + ; These are the participation statuses for a "VEVENT". Default is + ; NEEDS-ACTION + partstatparam /= "PARTSTAT" "=" + ("NEEDS-ACTION" ; To-do needs action + / "ACCEPTED" ; To-do accepted + / "DECLINED" ; To-do declined + / "TENTATIVE" ; To-do tentatively + ; accepted + / "DELEGATED" ; To-do delegated + / "COMPLETED" ; To-do completed. + ; COMPLETED property has + ;date/time completed. + / "IN-PROCESS" ; To-do in process of + ; being completed + / x-name ; Experimental status + / iana-token) ; Other IANA registered + ; status + ; These are the participation statuses for a "VTODO". Default is + ; NEEDS-ACTION + + partstatparam /= "PARTSTAT" "=" + ("NEEDS-ACTION" ; Journal needs action + / "ACCEPTED" ; Journal accepted + / "DECLINED" ; Journal declined + / x-name ; Experimental status + / iana-token) ; Other IANA registered + ; status + ; These are the participation statuses for a "VJOURNAL". Default is + ; NEEDS-ACTION + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. The parameter identifies the participation + status for the calendar user specified by the property value. The + parameter values differ depending on whether they are associated with + a group scheduled "VEVENT", "VTODO" or "VJOURNAL". The values MUST + match one of the values allowed for the given calendar component. If + not specified on a property that allows this parameter, the default + value is NEEDS-ACTION. + + Example: + + ATTENDEE;PARTSTAT=DECLINED:MAILTO:jsmith@host.com + + + +Dawson & Stenerson Standards Track [Page 26] + +RFC 2445 iCalendar November 1998 + + +4.2.13 Recurrence Identifier Range + + Parameter Name: RANGE + + Purpose: To specify the effective range of recurrence instances from + the instance specified by the recurrence identifier specified by the + property. + + Format Definition: The property parameter is defined by the following + notation: + + rangeparam = "RANGE" "=" ("THISANDPRIOR" + ; To specify all instances prior to the recurrence identifier + / "THISANDFUTURE") + ; To specify the instance specified by the recurrence identifier + ; and all subsequent recurrence instances + + Description: The parameter can be specified on a property that + specifies a recurrence identifier. The parameter specifies the + effective range of recurrence instances that is specified by the + property. The effective range is from the recurrence identified + specified by the property. If this parameter is not specified an + allowed property, then the default range is the single instance + specified by the recurrence identifier value of the property. The + parameter value can be "THISANDPRIOR" to indicate a range defined by + the recurrence identified value of the property and all prior + instances. The parameter value can also be "THISANDFUTURE" to + indicate a range defined by the recurrence identifier and all + subsequent instances. + + Example: + + RECURRENCE-ID;RANGE=THISANDPRIOR:19980401T133000Z + +4.2.14 Alarm Trigger Relationship + + Parameter Name: RELATED + + Purpose: To specify the relationship of the alarm trigger with + respect to the start or end of the calendar component. + + Format Definition: The property parameter is defined by the following + notation: + + trigrelparam = "RELATED" "=" + ("START" ; Trigger off of start + / "END") ; Trigger off of end + + + + +Dawson & Stenerson Standards Track [Page 27] + +RFC 2445 iCalendar November 1998 + + + Description: The parameter can be specified on properties that + specify an alarm trigger with a DURATION value type. The parameter + specifies whether the alarm will trigger relative to the start or end + of the calendar component. The parameter value START will set the + alarm to trigger off the start of the calendar component; the + parameter value END will set the alarm to trigger off the end of the + calendar component. If the parameter is not specified on an allowable + property, then the default is START. + + Example: + + TRIGGER;RELATED=END:PT5M + +4.2.15 Relationship Type + + Parameter Name: RELTYPE + + Purpose: To specify the type of hierarchical relationship associated + with the calendar component specified by the property. + + Format Definition: The property parameter is defined by the following + notation: + + reltypeparam = "RELTYPE" "=" + ("PARENT" ; Parent relationship. Default. + / "CHILD" ; Child relationship + / "SIBLING ; Sibling relationship + / iana-token ; Some other IANA registered + ; iCalendar relationship type + / x-name) ; A non-standard, experimental + ; relationship type + + Description: This parameter can be specified on a property that + references another related calendar. The parameter specifies the + hierarchical relationship type of the calendar component referenced + by the property. The parameter value can be PARENT, to indicate that + the referenced calendar component is a superior of calendar + component; CHILD to indicate that the referenced calendar component + is a subordinate of the calendar component; SIBLING to indicate that + the referenced calendar component is a peer of the calendar + component. If this parameter is not specified on an allowable + property, the default relationship type is PARENT. + + Example: + + RELATED-TO;RELTYPE=SIBLING:<19960401-080045-4000F192713@host.com> + + + + + +Dawson & Stenerson Standards Track [Page 28] + +RFC 2445 iCalendar November 1998 + + +4.2.16 Participation Role + + Parameter Name: ROLE + + Purpose: To specify the participation role for the calendar user + specified by the property. + + Format Definition: The property parameter is defined by the following + notation: + + roleparam = "ROLE" "=" + ("CHAIR" ; Indicates chair of the + ; calendar entity + / "REQ-PARTICIPANT" ; Indicates a participant whose + ; participation is required + / "OPT-PARTICIPANT" ; Indicates a participant whose + ; participation is optional + / "NON-PARTICIPANT" ; Indicates a participant who is + ; copied for information + ; purposes only + / x-name ; Experimental role + / iana-token) ; Other IANA role + ; Default is REQ-PARTICIPANT + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. The parameter specifies the participation + role for the calendar user specified by the property in the group + schedule calendar component. If not specified on a property that + allows this parameter, the default value is REQ-PARTICIPANT. + + Example: + + ATTENDEE;ROLE=CHAIR:MAILTO:mrbig@host.com + +4.2.17 RSVP Expectation + + Parameter Name: RSVP + + Purpose: To specify whether there is an expectation of a favor of a + reply from the calendar user specified by the property value. + + Format Definition: The property parameter is defined by the following + notation: + + rsvpparam = "RSVP" "=" ("TRUE" / "FALSE") + ; Default is FALSE + + + + + +Dawson & Stenerson Standards Track [Page 29] + +RFC 2445 iCalendar November 1998 + + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. The parameter identifies the expectation of a + reply from the calendar user specified by the property value. This + parameter is used by the "Organizer" to request a participation + status reply from an "Attendee" of a group scheduled event or to-do. + If not specified on a property that allows this parameter, the + default value is FALSE. + + Example: + + ATTENDEE;RSVP=TRUE:MAILTO:jsmith@host.com + +4.2.18 Sent By + + Parameter Name: SENT-BY + + Purpose: To specify the calendar user that is acting on behalf of the + calendar user specified by the property. + + Format Definition: The property parameter is defined by the following + notation: + + sentbyparam = "SENT-BY" "=" DQUOTE cal-address DQUOTE + + Description: This parameter can be specified on properties with a + CAL-ADDRESS value type. The parameter specifies the calendar user + that is acting on behalf of the calendar user specified by the + property. The parameter value MUST be a MAILTO URI as defined in [RFC + 1738]. The individual calendar address parameter values MUST each be + specified in a quoted-string. + + Example: + + ORGANIZER;SENT-BY:"MAILTO:sray@host.com":MAILTO:jsmith@host.com + +4.2.19 Time Zone Identifier + + Parameter Name: TZID + + Purpose: To specify the identifier for the time zone definition for a + time component in the property value. + + Format Definition: This property parameter is defined by the + following notation: + + tzidparam = "TZID" "=" [tzidprefix] paramtext CRLF + + tzidprefix = "/" + + + +Dawson & Stenerson Standards Track [Page 30] + +RFC 2445 iCalendar November 1998 + + + Description: The parameter MUST be specified on the "DTSTART", + "DTEND", "DUE", "EXDATE" and "RDATE" properties when either a DATE- + TIME or TIME value type is specified and when the value is not either + a UTC or a "floating" time. Refer to the DATE-TIME or TIME value type + definition for a description of UTC and "floating time" formats. This + property parameter specifies a text value which uniquely identifies + the "VTIMEZONE" calendar component to be used when evaluating the + time portion of the property. The value of the TZID property + parameter will be equal to the value of the TZID property for the + matching time zone definition. An individual "VTIMEZONE" calendar + component MUST be specified for each unique "TZID" parameter value + specified in the iCalendar object. + + The parameter MUST be specified on properties with a DATE-TIME value + if the DATE-TIME is not either a UTC or a "floating" time. + + The presence of the SOLIDUS character (US-ASCII decimal 47) as a + prefix, indicates that this TZID represents a unique ID in a globally + defined time zone registry (when such registry is defined). + + Note: This document does not define a naming convention for time + zone identifiers. Implementers may want to use the naming + conventions defined in existing time zone specifications such as + the public-domain Olson database [TZ]. The specification of + globally unique time zone identifiers is not addressed by this + document and is left for future study. + + The following are examples of this property parameter: + + DTSTART;TZID=US-Eastern:19980119T020000 + + DTEND;TZID=US-Eastern:19980119T030000 + + The TZID property parameter MUST NOT be applied to DATE-TIME or TIME + properties whose time values are specified in UTC. + + The use of local time in a DATE-TIME or TIME value without the TZID + property parameter is to be interpreted as a local time value, + regardless of the existence of "VTIMEZONE" calendar components in the + iCalendar object. + + For more information see the sections on the data types DATE-TIME and + TIME. + + + + + + + + +Dawson & Stenerson Standards Track [Page 31] + +RFC 2445 iCalendar November 1998 + + +4.2.20 Value Data Types + + Parameter Name: VALUE + + Purpose: To explicitly specify the data type format for a property + value. + + Format Definition: The "VALUE" property parameter is defined by the + following notation: + + valuetypeparam = "VALUE" "=" valuetype + + valuetype = ("BINARY" + / "BOOLEAN" + / "CAL-ADDRESS" + / "DATE" + / "DATE-TIME" + / "DURATION" + / "FLOAT" + / "INTEGER" + / "PERIOD" + / "RECUR" + / "TEXT" + / "TIME" + / "URI" + / "UTC-OFFSET" + / x-name + ; Some experimental iCalendar data type. + / iana-token) + ; Some other IANA registered iCalendar data type. + + Description: The parameter specifies the data type and format of the + property value. The property values MUST be of a single value type. + For example, a "RDATE" property cannot have a combination of DATE- + TIME and TIME value types. + + If the property's value is the default value type, then this + parameter need not be specified. However, if the property's default + value type is overridden by some other allowable value type, then + this parameter MUST be specified. + +4.3 Property Value Data Types + + The properties in an iCalendar object are strongly typed. The + definition of each property restricts the value to be one of the + value data types, or simply value types, defined in this section. The + value type for a property will either be specified implicitly as the + default value type or will be explicitly specified with the "VALUE" + + + +Dawson & Stenerson Standards Track [Page 32] + +RFC 2445 iCalendar November 1998 + + + parameter. If the value type of a property is one of the alternate + valid types, then it MUST be explicitly specified with the "VALUE" + parameter. + +4.3.1 Binary + + Value Name: BINARY + + Purpose: This value type is used to identify properties that contain + a character encoding of inline binary data. For example, an inline + attachment of an object code might be included in an iCalendar + object. + + Formal Definition: The value type is defined by the following + notation: + + binary = *(4b-char) [b-end] + ; A "BASE64" encoded character string, as defined by [RFC 2045]. + + b-end = (2b-char "==") / (3b-char "=") + + b-char = ALPHA / DIGIT / "+" / "/" + + Description: Property values with this value type MUST also include + the inline encoding parameter sequence of ";ENCODING=BASE64". That + is, all inline binary data MUST first be character encoded using the + "BASE64" encoding method defined in [RFC 2045]. No additional content + value encoding (i.e., BACKSLASH character encoding) is defined for + this value type. + + Example: The following is an abridged example of a "BASE64" encoded + binary value data. + + ATTACH;VALUE=BINARY;ENCODING=BASE64:MIICajCCAdOgAwIBAgICBEUwDQY + JKoZIhvcNAQEEBQAwdzELMAkGA1UEBhMCVVMxLDAqBgNVBAoTI05ldHNjYXBlI + ENvbW11bmljYXRpb25zIENvcnBvcmF0aW9uMRwwGgYDVQQLExNJbmZv + <...remainder of "BASE64" encoded binary data...> + +4.3.2 Boolean + + Value Name: BOOLEAN + + Purpose: This value type is used to identify properties that contain + either a "TRUE" or "FALSE" Boolean value. + + Formal Definition: The value type is defined by the following + notation: + + + + +Dawson & Stenerson Standards Track [Page 33] + +RFC 2445 iCalendar November 1998 + + + boolean = "TRUE" / "FALSE" + + Description: These values are case insensitive text. No additional + content value encoding (i.e., BACKSLASH character encoding) is + defined for this value type. + + Example: The following is an example of a hypothetical property that + has a BOOLEAN value type: + + GIBBERISH:TRUE + +4.3.3 Calendar User Address + + Value Name: CAL-ADDRESS + + Purpose: This value type is used to identify properties that contain + a calendar user address. + + Formal Definition: The value type is as defined by the following + notation: + + cal-address = uri + + Description: The value is a URI as defined by [RFC 1738] or any other + IANA registered form for a URI. When used to address an Internet + email transport address for a calendar user, the value MUST be a + MAILTO URI, as defined by [RFC 1738]. No additional content value + encoding (i.e., BACKSLASH character encoding) is defined for this + value type. + + Example: + + ATTENDEE:MAILTO:jane_doe@host.com + +4.3.4 Date + + Value Name: DATE + + Purpose: This value type is used to identify values that contain a + calendar date. + + Formal Definition: The value type is defined by the following + notation: + + date = date-value + + date-value = date-fullyear date-month date-mday + date-fullyear = 4DIGIT + + + +Dawson & Stenerson Standards Track [Page 34] + +RFC 2445 iCalendar November 1998 + + + date-month = 2DIGIT ;01-12 + date-mday = 2DIGIT ;01-28, 01-29, 01-30, 01-31 + ;based on month/year + + Description: If the property permits, multiple "date" values are + specified as a COMMA character (US-ASCII decimal 44) separated list + of values. The format for the value type is expressed as the [ISO + 8601] complete representation, basic format for a calendar date. The + textual format specifies a four-digit year, two-digit month, and + two-digit day of the month. There are no separator characters between + the year, month and day component text. + + No additional content value encoding (i.e., BACKSLASH character + encoding) is defined for this value type. + + Example: The following represents July 14, 1997: + + 19970714 + +4.3.5 Date-Time + + Value Name: DATE-TIME + + Purpose: This value type is used to identify values that specify a + precise calendar date and time of day. + + Formal Definition: The value type is defined by the following + notation: + + date-time = date "T" time ;As specified in the date and time + ;value definitions + + Description: If the property permits, multiple "date-time" values are + specified as a COMMA character (US-ASCII decimal 44) separated list + of values. No additional content value encoding (i.e., BACKSLASH + character encoding) is defined for this value type. + + The "DATE-TIME" data type is used to identify values that contain a + precise calendar date and time of day. The format is based on the + [ISO 8601] complete representation, basic format for a calendar date + and time of day. The text format is a concatenation of the "date", + followed by the LATIN CAPITAL LETTER T character (US-ASCII decimal + 84) time designator, followed by the "time" format. + + The "DATE-TIME" data type expresses time values in three forms: + + The form of date and time with UTC offset MUST NOT be used. For + example, the following is not valid for a date-time value: + + + +Dawson & Stenerson Standards Track [Page 35] + +RFC 2445 iCalendar November 1998 + + + DTSTART:19980119T230000-0800 ;Invalid time format + + FORM #1: DATE WITH LOCAL TIME + + The date with local time form is simply a date-time value that does + not contain the UTC designator nor does it reference a time zone. For + example, the following represents Janurary 18, 1998, at 11 PM: + + DTSTART:19980118T230000 + + Date-time values of this type are said to be "floating" and are not + bound to any time zone in particular. They are used to represent the + same hour, minute, and second value regardless of which time zone is + currently being observed. For example, an event can be defined that + indicates that an individual will be busy from 11:00 AM to 1:00 PM + every day, no matter which time zone the person is in. In these + cases, a local time can be specified. The recipient of an iCalendar + object with a property value consisting of a local time, without any + relative time zone information, SHOULD interpret the value as being + fixed to whatever time zone the ATTENDEE is in at any given moment. + This means that two ATTENDEEs, in different time zones, receiving the + same event definition as a floating time, may be participating in the + event at different actual times. Floating time SHOULD only be used + where that is the reasonable behavior. + + In most cases, a fixed time is desired. To properly communicate a + fixed time in a property value, either UTC time or local time with + time zone reference MUST be specified. + + The use of local time in a DATE-TIME value without the TZID property + parameter is to be interpreted as floating time, regardless of the + existence of "VTIMEZONE" calendar components in the iCalendar object. + + FORM #2: DATE WITH UTC TIME + + The date with UTC time, or absolute time, is identified by a LATIN + CAPITAL LETTER Z suffix character (US-ASCII decimal 90), the UTC + designator, appended to the time value. For example, the following + represents January 19, 1998, at 0700 UTC: + + DTSTART:19980119T070000Z + + The TZID property parameter MUST NOT be applied to DATE-TIME + properties whose time values are specified in UTC. + + FORM #3: DATE WITH LOCAL TIME AND TIME ZONE REFERENCE + + + + + +Dawson & Stenerson Standards Track [Page 36] + +RFC 2445 iCalendar November 1998 + + + The date and local time with reference to time zone information is + identified by the use the TZID property parameter to reference the + appropriate time zone definition. TZID is discussed in detail in the + section on Time Zone. For example, the following represents 2 AM in + New York on Janurary 19, 1998: + + DTSTART;TZID=US-Eastern:19980119T020000 + + Example: The following represents July 14, 1997, at 1:30 PM in New + York City in each of the three time formats, using the "DTSTART" + property. + + DTSTART:19970714T133000 ;Local time + DTSTART:19970714T173000Z ;UTC time + DTSTART;TZID=US-Eastern:19970714T133000 ;Local time and time + ; zone reference + + A time value MUST ONLY specify 60 seconds when specifying the + periodic "leap second" in the time value. For example: + + COMPLETED:19970630T235960Z + +4.3.6 Duration + + Value Name: DURATION + + Purpose: This value type is used to identify properties that contain + a duration of time. + + Formal Definition: The value type is defined by the following + notation: + + dur-value = (["+"] / "-") "P" (dur-date / dur-time / dur-week) + + dur-date = dur-day [dur-time] + dur-time = "T" (dur-hour / dur-minute / dur-second) + dur-week = 1*DIGIT "W" + dur-hour = 1*DIGIT "H" [dur-minute] + dur-minute = 1*DIGIT "M" [dur-second] + dur-second = 1*DIGIT "S" + dur-day = 1*DIGIT "D" + + Description: If the property permits, multiple "duration" values are + specified by a COMMA character (US-ASCII decimal 44) separated list + of values. The format is expressed as the [ISO 8601] basic format for + the duration of time. The format can represent durations in terms of + weeks, days, hours, minutes, and seconds. + + + + +Dawson & Stenerson Standards Track [Page 37] + +RFC 2445 iCalendar November 1998 + + + No additional content value encoding (i.e., BACKSLASH character + encoding) are defined for this value type. + + Example: A duration of 15 days, 5 hours and 20 seconds would be: + + P15DT5H0M20S + + A duration of 7 weeks would be: + + P7W + +4.3.7 Float + + Value Name: FLOAT + + Purpose: This value type is used to identify properties that contain + a real number value. + + Formal Definition: The value type is defined by the following + notation: + + float = (["+"] / "-") 1*DIGIT ["." 1*DIGIT] + + Description: If the property permits, multiple "float" values are + specified by a COMMA character (US-ASCII decimal 44) separated list + of values. + + No additional content value encoding (i.e., BACKSLASH character + encoding) is defined for this value type. + + Example: + + 1000000.0000001 + 1.333 + -3.14 + +4.3.8 Integer + + Value Name:INTEGER + + Purpose: This value type is used to identify properties that contain + a signed integer value. + + Formal Definition: The value type is defined by the following + notation: + + integer = (["+"] / "-") 1*DIGIT + + + + +Dawson & Stenerson Standards Track [Page 38] + +RFC 2445 iCalendar November 1998 + + + Description: If the property permits, multiple "integer" values are + specified by a COMMA character (US-ASCII decimal 44) separated list + of values. The valid range for "integer" is -2147483648 to + 2147483647. If the sign is not specified, then the value is assumed + to be positive. + + No additional content value encoding (i.e., BACKSLASH character + encoding) is defined for this value type. + + Example: + + 1234567890 + -1234567890 + +1234567890 + 432109876 + +4.3.9 Period of Time + + Value Name: PERIOD + + Purpose: This value type is used to identify values that contain a + precise period of time. + + Formal Definition: The data type is defined by the following + notation: + + period = period-explicit / period-start + + period-explicit = date-time "/" date-time + ; [ISO 8601] complete representation basic format for a period of + ; time consisting of a start and end. The start MUST be before the + ; end. + + period-start = date-time "/" dur-value + ; [ISO 8601] complete representation basic format for a period of + ; time consisting of a start and positive duration of time. + + Description: If the property permits, multiple "period" values are + specified by a COMMA character (US-ASCII decimal 44) separated list + of values. There are two forms of a period of time. First, a period + of time is identified by its start and its end. This format is + expressed as the [ISO 8601] complete representation, basic format for + "DATE-TIME" start of the period, followed by a SOLIDUS character + (US-ASCII decimal 47), followed by the "DATE-TIME" of the end of the + period. The start of the period MUST be before the end of the period. + Second, a period of time can also be defined by a start and a + positive duration of time. The format is expressed as the [ISO 8601] + complete representation, basic format for the "DATE-TIME" start of + + + +Dawson & Stenerson Standards Track [Page 39] + +RFC 2445 iCalendar November 1998 + + + the period, followed by a SOLIDUS character (US-ASCII decimal 47), + followed by the [ISO 8601] basic format for "DURATION" of the period. + + Example: The period starting at 18:00:00 UTC, on January 1, 1997 and + ending at 07:00:00 UTC on January 2, 1997 would be: + + 19970101T180000Z/19970102T070000Z + + The period start at 18:00:00 on January 1, 1997 and lasting 5 hours + and 30 minutes would be: + + 19970101T180000Z/PT5H30M + + No additional content value encoding (i.e., BACKSLASH character + encoding) is defined for this value type. + +4.3.10 Recurrence Rule + + Value Name: RECUR + + Purpose: This value type is used to identify properties that contain + a recurrence rule specification. + + Formal Definition: The value type is defined by the following + notation: + + recur = "FREQ"=freq *( + + ; either UNTIL or COUNT may appear in a 'recur', + ; but UNTIL and COUNT MUST NOT occur in the same 'recur' + + ( ";" "UNTIL" "=" enddate ) / + ( ";" "COUNT" "=" 1*DIGIT ) / + + ; the rest of these keywords are optional, + ; but MUST NOT occur more than once + + ( ";" "INTERVAL" "=" 1*DIGIT ) / + ( ";" "BYSECOND" "=" byseclist ) / + ( ";" "BYMINUTE" "=" byminlist ) / + ( ";" "BYHOUR" "=" byhrlist ) / + ( ";" "BYDAY" "=" bywdaylist ) / + ( ";" "BYMONTHDAY" "=" bymodaylist ) / + ( ";" "BYYEARDAY" "=" byyrdaylist ) / + ( ";" "BYWEEKNO" "=" bywknolist ) / + ( ";" "BYMONTH" "=" bymolist ) / + ( ";" "BYSETPOS" "=" bysplist ) / + ( ";" "WKST" "=" weekday ) / + + + +Dawson & Stenerson Standards Track [Page 40] + +RFC 2445 iCalendar November 1998 + + + ( ";" x-name "=" text ) + ) + + freq = "SECONDLY" / "MINUTELY" / "HOURLY" / "DAILY" + / "WEEKLY" / "MONTHLY" / "YEARLY" + + enddate = date + enddate =/ date-time ;An UTC value + + byseclist = seconds / ( seconds *("," seconds) ) + + seconds = 1DIGIT / 2DIGIT ;0 to 59 + + byminlist = minutes / ( minutes *("," minutes) ) + + minutes = 1DIGIT / 2DIGIT ;0 to 59 + + byhrlist = hour / ( hour *("," hour) ) + + hour = 1DIGIT / 2DIGIT ;0 to 23 + + bywdaylist = weekdaynum / ( weekdaynum *("," weekdaynum) ) + + weekdaynum = [([plus] ordwk / minus ordwk)] weekday + + plus = "+" + + minus = "-" + + ordwk = 1DIGIT / 2DIGIT ;1 to 53 + + weekday = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA" + ;Corresponding to SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, + ;FRIDAY, SATURDAY and SUNDAY days of the week. + + bymodaylist = monthdaynum / ( monthdaynum *("," monthdaynum) ) + + monthdaynum = ([plus] ordmoday) / (minus ordmoday) + + ordmoday = 1DIGIT / 2DIGIT ;1 to 31 + + byyrdaylist = yeardaynum / ( yeardaynum *("," yeardaynum) ) + + yeardaynum = ([plus] ordyrday) / (minus ordyrday) + + ordyrday = 1DIGIT / 2DIGIT / 3DIGIT ;1 to 366 + + bywknolist = weeknum / ( weeknum *("," weeknum) ) + + + +Dawson & Stenerson Standards Track [Page 41] + +RFC 2445 iCalendar November 1998 + + + weeknum = ([plus] ordwk) / (minus ordwk) + + bymolist = monthnum / ( monthnum *("," monthnum) ) + + monthnum = 1DIGIT / 2DIGIT ;1 to 12 + + bysplist = setposday / ( setposday *("," setposday) ) + + setposday = yeardaynum + + Description: If the property permits, multiple "recur" values are + specified by a COMMA character (US-ASCII decimal 44) separated list + of values. The value type is a structured value consisting of a list + of one or more recurrence grammar parts. Each rule part is defined by + a NAME=VALUE pair. The rule parts are separated from each other by + the SEMICOLON character (US-ASCII decimal 59). The rule parts are not + ordered in any particular sequence. Individual rule parts MUST only + be specified once. + + The FREQ rule part identifies the type of recurrence rule. This rule + part MUST be specified in the recurrence rule. Valid values include + SECONDLY, to specify repeating events based on an interval of a + second or more; MINUTELY, to specify repeating events based on an + interval of a minute or more; HOURLY, to specify repeating events + based on an interval of an hour or more; DAILY, to specify repeating + events based on an interval of a day or more; WEEKLY, to specify + repeating events based on an interval of a week or more; MONTHLY, to + specify repeating events based on an interval of a month or more; and + YEARLY, to specify repeating events based on an interval of a year or + more. + + The INTERVAL rule part contains a positive integer representing how + often the recurrence rule repeats. The default value is "1", meaning + every second for a SECONDLY rule, or every minute for a MINUTELY + rule, every hour for an HOURLY rule, every day for a DAILY rule, + every week for a WEEKLY rule, every month for a MONTHLY rule and + every year for a YEARLY rule. + + The UNTIL rule part defines a date-time value which bounds the + recurrence rule in an inclusive manner. If the value specified by + UNTIL is synchronized with the specified recurrence, this date or + date-time becomes the last instance of the recurrence. If specified + as a date-time value, then it MUST be specified in an UTC time + format. If not present, and the COUNT rule part is also not present, + the RRULE is considered to repeat forever. + + The COUNT rule part defines the number of occurrences at which to + range-bound the recurrence. The "DTSTART" property value, if + + + +Dawson & Stenerson Standards Track [Page 42] + +RFC 2445 iCalendar November 1998 + + + specified, counts as the first occurrence. + + The BYSECOND rule part specifies a COMMA character (US-ASCII decimal + 44) separated list of seconds within a minute. Valid values are 0 to + 59. The BYMINUTE rule part specifies a COMMA character (US-ASCII + decimal 44) separated list of minutes within an hour. Valid values + are 0 to 59. The BYHOUR rule part specifies a COMMA character (US- + ASCII decimal 44) separated list of hours of the day. Valid values + are 0 to 23. + + The BYDAY rule part specifies a COMMA character (US-ASCII decimal 44) + separated list of days of the week; MO indicates Monday; TU indicates + Tuesday; WE indicates Wednesday; TH indicates Thursday; FR indicates + Friday; SA indicates Saturday; SU indicates Sunday. + + Each BYDAY value can also be preceded by a positive (+n) or negative + (-n) integer. If present, this indicates the nth occurrence of the + specific day within the MONTHLY or YEARLY RRULE. For example, within + a MONTHLY rule, +1MO (or simply 1MO) represents the first Monday + within the month, whereas -1MO represents the last Monday of the + month. If an integer modifier is not present, it means all days of + this type within the specified frequency. For example, within a + MONTHLY rule, MO represents all Mondays within the month. + + The BYMONTHDAY rule part specifies a COMMA character (ASCII decimal + 44) separated list of days of the month. Valid values are 1 to 31 or + -31 to -1. For example, -10 represents the tenth to the last day of + the month. + + The BYYEARDAY rule part specifies a COMMA character (US-ASCII decimal + 44) separated list of days of the year. Valid values are 1 to 366 or + -366 to -1. For example, -1 represents the last day of the year + (December 31st) and -306 represents the 306th to the last day of the + year (March 1st). + + The BYWEEKNO rule part specifies a COMMA character (US-ASCII decimal + 44) separated list of ordinals specifying weeks of the year. Valid + values are 1 to 53 or -53 to -1. This corresponds to weeks according + to week numbering as defined in [ISO 8601]. A week is defined as a + seven day period, starting on the day of the week defined to be the + week start (see WKST). Week number one of the calendar year is the + first week which contains at least four (4) days in that calendar + year. This rule part is only valid for YEARLY rules. For example, 3 + represents the third week of the year. + + Note: Assuming a Monday week start, week 53 can only occur when + Thursday is January 1 or if it is a leap year and Wednesday is + January 1. + + + +Dawson & Stenerson Standards Track [Page 43] + +RFC 2445 iCalendar November 1998 + + + The BYMONTH rule part specifies a COMMA character (US-ASCII decimal + 44) separated list of months of the year. Valid values are 1 to 12. + + The WKST rule part specifies the day on which the workweek starts. + Valid values are MO, TU, WE, TH, FR, SA and SU. This is significant + when a WEEKLY RRULE has an interval greater than 1, and a BYDAY rule + part is specified. This is also significant when in a YEARLY RRULE + when a BYWEEKNO rule part is specified. The default value is MO. + + The BYSETPOS rule part specifies a COMMA character (US-ASCII decimal + 44) separated list of values which corresponds to the nth occurrence + within the set of events specified by the rule. Valid values are 1 to + 366 or -366 to -1. It MUST only be used in conjunction with another + BYxxx rule part. For example "the last work day of the month" could + be represented as: + + RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1 + + Each BYSETPOS value can include a positive (+n) or negative (-n) + integer. If present, this indicates the nth occurrence of the + specific occurrence within the set of events specified by the rule. + + If BYxxx rule part values are found which are beyond the available + scope (ie, BYMONTHDAY=30 in February), they are simply ignored. + + Information, not contained in the rule, necessary to determine the + various recurrence instance start time and dates are derived from the + Start Time (DTSTART) entry attribute. For example, + "FREQ=YEARLY;BYMONTH=1" doesn't specify a specific day within the + month or a time. This information would be the same as what is + specified for DTSTART. + + BYxxx rule parts modify the recurrence in some manner. BYxxx rule + parts for a period of time which is the same or greater than the + frequency generally reduce or limit the number of occurrences of the + recurrence generated. For example, "FREQ=DAILY;BYMONTH=1" reduces the + number of recurrence instances from all days (if BYMONTH tag is not + present) to all days in January. BYxxx rule parts for a period of + time less than the frequency generally increase or expand the number + of occurrences of the recurrence. For example, + "FREQ=YEARLY;BYMONTH=1,2" increases the number of days within the + yearly recurrence set from 1 (if BYMONTH tag is not present) to 2. + + If multiple BYxxx rule parts are specified, then after evaluating the + specified FREQ and INTERVAL rule parts, the BYxxx rule parts are + applied to the current set of evaluated occurrences in the following + order: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, BYDAY, BYHOUR, + BYMINUTE, BYSECOND and BYSETPOS; then COUNT and UNTIL are evaluated. + + + +Dawson & Stenerson Standards Track [Page 44] + +RFC 2445 iCalendar November 1998 + + + Here is an example of evaluating multiple BYxxx rule parts. + + DTSTART;TZID=US-Eastern:19970105T083000 + RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9; + BYMINUTE=30 + + First, the "INTERVAL=2" would be applied to "FREQ=YEARLY" to arrive + at "every other year". Then, "BYMONTH=1" would be applied to arrive + at "every January, every other year". Then, "BYDAY=SU" would be + applied to arrive at "every Sunday in January, every other year". + Then, "BYHOUR=8,9" would be applied to arrive at "every Sunday in + January at 8 AM and 9 AM, every other year". Then, "BYMINUTE=30" + would be applied to arrive at "every Sunday in January at 8:30 AM and + 9:30 AM, every other year". Then, lacking information from RRULE, the + second is derived from DTSTART, to end up in "every Sunday in January + at 8:30:00 AM and 9:30:00 AM, every other year". Similarly, if the + BYMINUTE, BYHOUR, BYDAY, BYMONTHDAY or BYMONTH rule part were + missing, the appropriate minute, hour, day or month would have been + retrieved from the "DTSTART" property. + + No additional content value encoding (i.e., BACKSLASH character + encoding) is defined for this value type. + + Example: The following is a rule which specifies 10 meetings which + occur every other day: + + FREQ=DAILY;COUNT=10;INTERVAL=2 + + There are other examples specified in the "RRULE" specification. + +4.3.11 Text + + Value Name: TEXT + + Purpose This value type is used to identify values that contain human + readable text. + + Formal Definition: The character sets supported by this revision of + iCalendar are UTF-8 and US ASCII thereof. The applicability to other + character sets is for future work. The value type is defined by the + following notation. + + text = *(TSAFE-CHAR / ":" / DQUOTE / ESCAPED-CHAR) + ; Folded according to description above + + ESCAPED-CHAR = "\\" / "\;" / "\," / "\N" / "\n") + ; \\ encodes \, \N or \n encodes newline + ; \; encodes ;, \, encodes , + + + +Dawson & Stenerson Standards Track [Page 45] + +RFC 2445 iCalendar November 1998 + + + TSAFE-CHAR = %x20-21 / %x23-2B / %x2D-39 / %x3C-5B + %x5D-7E / NON-US-ASCII + ; Any character except CTLs not needed by the current + ; character set, DQUOTE, ";", ":", "\", "," + + Note: Certain other character sets may require modification of the + above definitions, but this is beyond the scope of this document. + + Description: If the property permits, multiple "text" values are + specified by a COMMA character (US-ASCII decimal 44) separated list + of values. + + The language in which the text is represented can be controlled by + the "LANGUAGE" property parameter. + + An intentional formatted text line break MUST only be included in a + "TEXT" property value by representing the line break with the + character sequence of BACKSLASH (US-ASCII decimal 92), followed by a + LATIN SMALL LETTER N (US-ASCII decimal 110) or a LATIN CAPITAL LETTER + N (US-ASCII decimal 78), that is "\n" or "\N". + + The "TEXT" property values may also contain special characters that + are used to signify delimiters, such as a COMMA character for lists + of values or a SEMICOLON character for structured values. In order to + support the inclusion of these special characters in "TEXT" property + values, they MUST be escaped with a BACKSLASH character. A BACKSLASH + character (US-ASCII decimal 92) in a "TEXT" property value MUST be + escaped with another BACKSLASH character. A COMMA character in a + "TEXT" property value MUST be escaped with a BACKSLASH character + (US-ASCII decimal 92). A SEMICOLON character in a "TEXT" property + value MUST be escaped with a BACKSLASH character (US-ASCII decimal + 92). However, a COLON character in a "TEXT" property value SHALL NOT + be escaped with a BACKSLASH character.Example: A multiple line value + of: + + Project XYZ Final Review + Conference Room - 3B + Come Prepared. + + would be represented as: + + Project XYZ Final Review\nConference Room - 3B\nCome Prepared. + + + + + + + + + +Dawson & Stenerson Standards Track [Page 46] + +RFC 2445 iCalendar November 1998 + + +4.3.12 Time + + Value Name: TIME + + Purpose: This value type is used to identify values that contain a + time of day. + + Formal Definition: The data type is defined by the following + notation: + + time = time-hour time-minute time-second [time-utc] + + time-hour = 2DIGIT ;00-23 + time-minute = 2DIGIT ;00-59 + time-second = 2DIGIT ;00-60 + ;The "60" value is used to account for "leap" seconds. + + time-utc = "Z" + + Description: If the property permits, multiple "time" values are + specified by a COMMA character (US-ASCII decimal 44) separated list + of values. No additional content value encoding (i.e., BACKSLASH + character encoding) is defined for this value type. + + The "TIME" data type is used to identify values that contain a time + of day. The format is based on the [ISO 8601] complete + representation, basic format for a time of day. The text format + consists of a two-digit 24-hour of the day (i.e., values 0-23), two- + digit minute in the hour (i.e., values 0-59), and two-digit seconds + in the minute (i.e., values 0-60). The seconds value of 60 MUST only + to be used to account for "leap" seconds. Fractions of a second are + not supported by this format. + + In parallel to the "DATE-TIME" definition above, the "TIME" data type + expresses time values in three forms: + + The form of time with UTC offset MUST NOT be used. For example, the + following is NOT VALID for a time value: + + 230000-0800 ;Invalid time format + + FORM #1 LOCAL TIME + + The local time form is simply a time value that does not contain the + UTC designator nor does it reference a time zone. For example, 11:00 + PM: + + 230000 + + + +Dawson & Stenerson Standards Track [Page 47] + +RFC 2445 iCalendar November 1998 + + + Time values of this type are said to be "floating" and are not bound + to any time zone in particular. They are used to represent the same + hour, minute, and second value regardless of which time zone is + currently being observed. For example, an event can be defined that + indicates that an individual will be busy from 11:00 AM to 1:00 PM + every day, no matter which time zone the person is in. In these + cases, a local time can be specified. The recipient of an iCalendar + object with a property value consisting of a local time, without any + relative time zone information, SHOULD interpret the value as being + fixed to whatever time zone the ATTENDEE is in at any given moment. + This means that two ATTENDEEs may participate in the same event at + different UTC times; floating time SHOULD only be used where that is + reasonable behavior. + + In most cases, a fixed time is desired. To properly communicate a + fixed time in a property value, either UTC time or local time with + time zone reference MUST be specified. + + The use of local time in a TIME value without the TZID property + parameter is to be interpreted as a local time value, regardless of + the existence of "VTIMEZONE" calendar components in the iCalendar + object. + + FORM #2: UTC TIME + + UTC time, or absolute time, is identified by a LATIN CAPITAL LETTER Z + suffix character (US-ASCII decimal 90), the UTC designator, appended + to the time value. For example, the following represents 07:00 AM + UTC: + + 070000Z + + The TZID property parameter MUST NOT be applied to TIME properties + whose time values are specified in UTC. + + FORM #3: LOCAL TIME AND TIME ZONE REFERENCE + + The local time with reference to time zone information form is + identified by the use the TZID property parameter to reference the + appropriate time zone definition. TZID is discussed in detail in the + section on Time Zone. + + Example: The following represents 8:30 AM in New York in Winter, five + hours behind UTC, in each of the three formats using the "X- + TIMEOFDAY" non-standard property: + + + + + + +Dawson & Stenerson Standards Track [Page 48] + +RFC 2445 iCalendar November 1998 + + + X-TIMEOFDAY:083000 + + X-TIMEOFDAY:133000Z + + X-TIMEOFDAY;TZID=US-Eastern:083000 + +4.3.13 URI + + Value Name: URI + + Purpose: This value type is used to identify values that contain a + uniform resource identifier (URI) type of reference to the property + value. + + Formal Definition: The data type is defined by the following + notation: + + uri = + + Description: This data type might be used to reference binary + information, for values that are large, or otherwise undesirable to + include directly in the iCalendar object. + + The URI value formats in RFC 1738, RFC 2111 and any other IETF + registered value format can be specified. + + Any IANA registered URI format can be used. These include, but are + not limited to, those defined in RFC 1738 and RFC 2111. + + When a property parameter value is a URI value type, the URI MUST be + specified as a quoted-string value. + + No additional content value encoding (i.e., BACKSLASH character + encoding) is defined for this value type. + + Example: The following is a URI for a network file: + + http://host1.com/my-report.txt + +4.3.14 UTC Offset + + Value Name: UTC-OFFSET + + Purpose: This value type is used to identify properties that contain + an offset from UTC to local time. + + Formal Definition: The data type is defined by the following + notation: + + + +Dawson & Stenerson Standards Track [Page 49] + +RFC 2445 iCalendar November 1998 + + + utc-offset = time-numzone ;As defined above in time data type + + time-numzone = ("+" / "-") time-hour time-minute [time- + second] + + Description: The PLUS SIGN character MUST be specified for positive + UTC offsets (i.e., ahead of UTC). The value of "-0000" and "-000000" + are not allowed. The time-second, if present, may not be 60; if + absent, it defaults to zero. + + No additional content value encoding (i.e., BACKSLASH character + encoding) is defined for this value type. + + Example: The following UTC offsets are given for standard time for + New York (five hours behind UTC) and Geneva (one hour ahead of UTC): + + -0500 + + +0100 + +4.4 iCalendar Object + + The Calendaring and Scheduling Core Object is a collection of + calendaring and scheduling information. Typically, this information + will consist of a single iCalendar object. However, multiple + iCalendar objects can be sequentially grouped together. The first + line and last line of the iCalendar object MUST contain a pair of + iCalendar object delimiter strings. The syntax for an iCalendar + object is as follows: + + icalobject = 1*("BEGIN" ":" "VCALENDAR" CRLF + icalbody + "END" ":" "VCALENDAR" CRLF) + + The following is a simple example of an iCalendar object: + + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:-//hacksw/handcal//NONSGML v1.0//EN + BEGIN:VEVENT + DTSTART:19970714T170000Z + DTEND:19970715T035959Z + SUMMARY:Bastille Day Party + END:VEVENT + END:VCALENDAR + + + + + + +Dawson & Stenerson Standards Track [Page 50] + +RFC 2445 iCalendar November 1998 + + +4.5 Property + + A property is the definition of an individual attribute describing a + calendar or a calendar component. A property takes the form defined + by the "contentline" notation defined in section 4.1.1. + + The following is an example of a property: + + DTSTART:19960415T133000Z + + This memo imposes no ordering of properties within an iCalendar + object. + + Property names, parameter names and enumerated parameter values are + case insensitive. For example, the property name "DUE" is the same as + "due" and "Due", DTSTART;TZID=US-Eastern:19980714T120000 is the same + as DtStart;TzID=US-Eastern:19980714T120000. + +4.6 Calendar Components + + The body of the iCalendar object consists of a sequence of calendar + properties and one or more calendar components. The calendar + properties are attributes that apply to the calendar as a whole. The + calendar components are collections of properties that express a + particular calendar semantic. For example, the calendar component can + specify an event, a to-do, a journal entry, time zone information, or + free/busy time information, or an alarm. + + The body of the iCalendar object is defined by the following + notation: + + icalbody = calprops component + + calprops = 2*( + + ; 'prodid' and 'version' are both REQUIRED, + ; but MUST NOT occur more than once + + prodid /version / + + ; 'calscale' and 'method' are optional, + ; but MUST NOT occur more than once + + calscale / + method / + + x-prop + + + + +Dawson & Stenerson Standards Track [Page 51] + +RFC 2445 iCalendar November 1998 + + + ) + + component = 1*(eventc / todoc / journalc / freebusyc / + / timezonec / iana-comp / x-comp) + + iana-comp = "BEGIN" ":" iana-token CRLF + + 1*contentline + + "END" ":" iana-token CRLF + + x-comp = "BEGIN" ":" x-name CRLF + + 1*contentline + + "END" ":" x-name CRLF + + An iCalendar object MUST include the "PRODID" and "VERSION" calendar + properties. In addition, it MUST include at least one calendar + component. Special forms of iCalendar objects are possible to publish + just busy time (i.e., only a "VFREEBUSY" calendar component) or time + zone (i.e., only a "VTIMEZONE" calendar component) information. In + addition, a complex iCalendar object is possible that is used to + capture a complete snapshot of the contents of a calendar (e.g., + composite of many different calendar components). More commonly, an + iCalendar object will consist of just a single "VEVENT", "VTODO" or + "VJOURNAL" calendar component. + +4.6.1 Event Component + + Component Name: "VEVENT" + + Purpose: Provide a grouping of component properties that describe an + event. + + Format Definition: A "VEVENT" calendar component is defined by the + following notation: + + eventc = "BEGIN" ":" "VEVENT" CRLF + eventprop *alarmc + "END" ":" "VEVENT" CRLF + + eventprop = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + class / created / description / dtstart / geo / + + + +Dawson & Stenerson Standards Track [Page 52] + +RFC 2445 iCalendar November 1998 + + + last-mod / location / organizer / priority / + dtstamp / seq / status / summary / transp / + uid / url / recurid / + + ; either 'dtend' or 'duration' may appear in + ; a 'eventprop', but 'dtend' and 'duration' + ; MUST NOT occur in the same 'eventprop' + + dtend / duration / + + ; the following are optional, + ; and MAY occur more than once + + attach / attendee / categories / comment / + contact / exdate / exrule / rstatus / related / + resources / rdate / rrule / x-prop + + ) + + Description: A "VEVENT" calendar component is a grouping of component + properties, and possibly including "VALARM" calendar components, that + represents a scheduled amount of time on a calendar. For example, it + can be an activity; such as a one-hour long, department meeting from + 8:00 AM to 9:00 AM, tomorrow. Generally, an event will take up time + on an individual calendar. Hence, the event will appear as an opaque + interval in a search for busy time. Alternately, the event can have + its Time Transparency set to "TRANSPARENT" in order to prevent + blocking of the event in searches for busy time. + + The "VEVENT" is also the calendar component used to specify an + anniversary or daily reminder within a calendar. These events have a + DATE value type for the "DTSTART" property instead of the default + data type of DATE-TIME. If such a "VEVENT" has a "DTEND" property, it + MUST be specified as a DATE value also. The anniversary type of + "VEVENT" can span more than one date (i.e, "DTEND" property value is + set to a calendar date after the "DTSTART" property value). + + The "DTSTART" property for a "VEVENT" specifies the inclusive start + of the event. For recurring events, it also specifies the very first + instance in the recurrence set. The "DTEND" property for a "VEVENT" + calendar component specifies the non-inclusive end of the event. For + cases where a "VEVENT" calendar component specifies a "DTSTART" + property with a DATE data type but no "DTEND" property, the events + non-inclusive end is the end of the calendar date specified by the + "DTSTART" property. For cases where a "VEVENT" calendar component + specifies a "DTSTART" property with a DATE-TIME data type but no + "DTEND" property, the event ends on the same calendar date and time + of day specified by the "DTSTART" property. + + + +Dawson & Stenerson Standards Track [Page 53] + +RFC 2445 iCalendar November 1998 + + + The "VEVENT" calendar component cannot be nested within another + calendar component. However, "VEVENT" calendar components can be + related to each other or to a "VTODO" or to a "VJOURNAL" calendar + component with the "RELATED-TO" property. + + Example: The following is an example of the "VEVENT" calendar + component used to represent a meeting that will also be opaque to + searches for busy time: + + BEGIN:VEVENT + UID:19970901T130000Z-123401@host.com + DTSTAMP:19970901T1300Z + DTSTART:19970903T163000Z + DTEND:19970903T190000Z + SUMMARY:Annual Employee Review + CLASS:PRIVATE + CATEGORIES:BUSINESS,HUMAN RESOURCES + END:VEVENT + + The following is an example of the "VEVENT" calendar component used + to represent a reminder that will not be opaque, but rather + transparent, to searches for busy time: + + BEGIN:VEVENT + UID:19970901T130000Z-123402@host.com + DTSTAMP:19970901T1300Z + DTSTART:19970401T163000Z + DTEND:19970402T010000Z + SUMMARY:Laurel is in sensitivity awareness class. + CLASS:PUBLIC + CATEGORIES:BUSINESS,HUMAN RESOURCES + TRANSP:TRANSPARENT + END:VEVENT + + The following is an example of the "VEVENT" calendar component used + to represent an anniversary that will occur annually. Since it takes + up no time, it will not appear as opaque in a search for busy time; + no matter what the value of the "TRANSP" property indicates: + + BEGIN:VEVENT + UID:19970901T130000Z-123403@host.com + DTSTAMP:19970901T1300Z + DTSTART:19971102 + SUMMARY:Our Blissful Anniversary + CLASS:CONFIDENTIAL + CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION + RRULE:FREQ=YEARLY + END:VEVENT + + + +Dawson & Stenerson Standards Track [Page 54] + +RFC 2445 iCalendar November 1998 + + +4.6.2 To-do Component + + Component Name: VTODO + + Purpose: Provide a grouping of calendar properties that describe a + to-do. + + Formal Definition: A "VTODO" calendar component is defined by the + following notation: + + todoc = "BEGIN" ":" "VTODO" CRLF + todoprop *alarmc + "END" ":" "VTODO" CRLF + + todoprop = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + class / completed / created / description / dtstamp / + dtstart / geo / last-mod / location / organizer / + percent / priority / recurid / seq / status / + summary / uid / url / + + ; either 'due' or 'duration' may appear in + ; a 'todoprop', but 'due' and 'duration' + ; MUST NOT occur in the same 'todoprop' + + due / duration / + + ; the following are optional, + ; and MAY occur more than once + attach / attendee / categories / comment / contact / + exdate / exrule / rstatus / related / resources / + rdate / rrule / x-prop + + ) + + Description: A "VTODO" calendar component is a grouping of component + properties and possibly "VALARM" calendar components that represent + an action-item or assignment. For example, it can be used to + represent an item of work assigned to an individual; such as "turn in + travel expense today". + + The "VTODO" calendar component cannot be nested within another + calendar component. However, "VTODO" calendar components can be + related to each other or to a "VTODO" or to a "VJOURNAL" calendar + component with the "RELATED-TO" property. + + + +Dawson & Stenerson Standards Track [Page 55] + +RFC 2445 iCalendar November 1998 + + + A "VTODO" calendar component without the "DTSTART" and "DUE" (or + "DURATION") properties specifies a to-do that will be associated with + each successive calendar date, until it is completed. + + Example: The following is an example of a "VTODO" calendar component: + + BEGIN:VTODO + UID:19970901T130000Z-123404@host.com + DTSTAMP:19970901T1300Z + DTSTART:19970415T133000Z + DUE:19970416T045959Z + SUMMARY:1996 Income Tax Preparation + CLASS:CONFIDENTIAL + CATEGORIES:FAMILY,FINANCE + PRIORITY:1 + STATUS:NEEDS-ACTION + END:VTODO + +4.6.3 Journal Component + + Component Name: VJOURNAL + + Purpose: Provide a grouping of component properties that describe a + journal entry. + + Formal Definition: A "VJOURNAL" calendar component is defined by the + following notation: + + journalc = "BEGIN" ":" "VJOURNAL" CRLF + jourprop + "END" ":" "VJOURNAL" CRLF + + jourprop = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + class / created / description / dtstart / dtstamp / + last-mod / organizer / recurid / seq / status / + summary / uid / url / + + ; the following are optional, + ; and MAY occur more than once + + attach / attendee / categories / comment / + contact / exdate / exrule / related / rdate / + rrule / rstatus / x-prop + + + + +Dawson & Stenerson Standards Track [Page 56] + +RFC 2445 iCalendar November 1998 + + + ) + + Description: A "VJOURNAL" calendar component is a grouping of + component properties that represent one or more descriptive text + notes associated with a particular calendar date. The "DTSTART" + property is used to specify the calendar date that the journal entry + is associated with. Generally, it will have a DATE value data type, + but it can also be used to specify a DATE-TIME value data type. + Examples of a journal entry include a daily record of a legislative + body or a journal entry of individual telephone contacts for the day + or an ordered list of accomplishments for the day. The "VJOURNAL" + calendar component can also be used to associate a document with a + calendar date. + + The "VJOURNAL" calendar component does not take up time on a + calendar. Hence, it does not play a role in free or busy time + searches - - it is as though it has a time transparency value of + TRANSPARENT. It is transparent to any such searches. + + The "VJOURNAL" calendar component cannot be nested within another + calendar component. However, "VJOURNAL" calendar components can be + related to each other or to a "VEVENT" or to a "VTODO" calendar + component, with the "RELATED-TO" property. + + Example: The following is an example of the "VJOURNAL" calendar + component: + + BEGIN:VJOURNAL + UID:19970901T130000Z-123405@host.com + DTSTAMP:19970901T1300Z + DTSTART;VALUE=DATE:19970317 + SUMMARY:Staff meeting minutes + DESCRIPTION:1. Staff meeting: Participants include Joe\, Lisa + and Bob. Aurora project plans were reviewed. There is currently + no budget reserves for this project. Lisa will escalate to + management. Next meeting on Tuesday.\n + 2. Telephone Conference: ABC Corp. sales representative called + to discuss new printer. Promised to get us a demo by Friday.\n + 3. Henry Miller (Handsoff Insurance): Car was totaled by tree. + Is looking into a loaner car. 654-2323 (tel). + END:VJOURNAL + + + + + + + + + + +Dawson & Stenerson Standards Track [Page 57] + +RFC 2445 iCalendar November 1998 + + +4.6.4 Free/Busy Component + + Component Name: VFREEBUSY + + Purpose: Provide a grouping of component properties that describe + either a request for free/busy time, describe a response to a request + for free/busy time or describe a published set of busy time. + + Formal Definition: A "VFREEBUSY" calendar component is defined by the + following notation: + + freebusyc = "BEGIN" ":" "VFREEBUSY" CRLF + fbprop + "END" ":" "VFREEBUSY" CRLF + + fbprop = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + contact / dtstart / dtend / duration / dtstamp / + organizer / uid / url / + + ; the following are optional, + ; and MAY occur more than once + + attendee / comment / freebusy / rstatus / x-prop + + ) + + Description: A "VFREEBUSY" calendar component is a grouping of + component properties that represents either a request for, a reply to + a request for free or busy time information or a published set of + busy time information. + + When used to request free/busy time information, the "ATTENDEE" + property specifies the calendar users whose free/busy time is being + requested; the "ORGANIZER" property specifies the calendar user who + is requesting the free/busy time; the "DTSTART" and "DTEND" + properties specify the window of time for which the free/busy time is + being requested; the "UID" and "DTSTAMP" properties are specified to + assist in proper sequencing of multiple free/busy time requests. + + When used to reply to a request for free/busy time, the "ATTENDEE" + property specifies the calendar user responding to the free/busy time + request; the "ORGANIZER" property specifies the calendar user that + originally requested the free/busy time; the "FREEBUSY" property + specifies the free/busy time information (if it exists); and the + + + +Dawson & Stenerson Standards Track [Page 58] + +RFC 2445 iCalendar November 1998 + + + "UID" and "DTSTAMP" properties are specified to assist in proper + sequencing of multiple free/busy time replies. + + When used to publish busy time, the "ORGANIZER" property specifies + the calendar user associated with the published busy time; the + "DTSTART" and "DTEND" properties specify an inclusive time window + that surrounds the busy time information; the "FREEBUSY" property + specifies the published busy time information; and the "DTSTAMP" + property specifies the date/time that iCalendar object was created. + + The "VFREEBUSY" calendar component cannot be nested within another + calendar component. Multiple "VFREEBUSY" calendar components can be + specified within an iCalendar object. This permits the grouping of + Free/Busy information into logical collections, such as monthly + groups of busy time information. + + The "VFREEBUSY" calendar component is intended for use in iCalendar + object methods involving requests for free time, requests for busy + time, requests for both free and busy, and the associated replies. + + Free/Busy information is represented with the "FREEBUSY" property. + This property provides a terse representation of time periods. One or + more "FREEBUSY" properties can be specified in the "VFREEBUSY" + calendar component. + + When present in a "VFREEBUSY" calendar component, the "DTSTART" and + "DTEND" properties SHOULD be specified prior to any "FREEBUSY" + properties. In a free time request, these properties can be used in + combination with the "DURATION" property to represent a request for a + duration of free time within a specified window of time. + + The recurrence properties ("RRULE", "EXRULE", "RDATE", "EXDATE") are + not permitted within a "VFREEBUSY" calendar component. Any recurring + events are resolved into their individual busy time periods using the + "FREEBUSY" property. + + Example: The following is an example of a "VFREEBUSY" calendar + component used to request free or busy time information: + + BEGIN:VFREEBUSY + ORGANIZER:MAILTO:jane_doe@host1.com + ATTENDEE:MAILTO:john_public@host2.com + DTSTART:19971015T050000Z + DTEND:19971016T050000Z + DTSTAMP:19970901T083000Z + END:VFREEBUSY + + + + + +Dawson & Stenerson Standards Track [Page 59] + +RFC 2445 iCalendar November 1998 + + + The following is an example of a "VFREEBUSY" calendar component used + to reply to the request with busy time information: + + BEGIN:VFREEBUSY + ORGANIZER:MAILTO:jane_doe@host1.com + ATTENDEE:MAILTO:john_public@host2.com + DTSTAMP:19970901T100000Z + FREEBUSY;VALUE=PERIOD:19971015T050000Z/PT8H30M, + 19971015T160000Z/PT5H30M,19971015T223000Z/PT6H30M + URL:http://host2.com/pub/busy/jpublic-01.ifb + COMMENT:This iCalendar file contains busy time information for + the next three months. + END:VFREEBUSY + + The following is an example of a "VFREEBUSY" calendar component used + to publish busy time information. + + BEGIN:VFREEBUSY + ORGANIZER:jsmith@host.com + DTSTART:19980313T141711Z + DTEND:19980410T141711Z + FREEBUSY:19980314T233000Z/19980315T003000Z + FREEBUSY:19980316T153000Z/19980316T163000Z + FREEBUSY:19980318T030000Z/19980318T040000Z + URL:http://www.host.com/calendar/busytime/jsmith.ifb + END:VFREEBUSY + +4.6.5 Time Zone Component + + Component Name: VTIMEZONE + + Purpose: Provide a grouping of component properties that defines a + time zone. + + Formal Definition: A "VTIMEZONE" calendar component is defined by the + following notation: + + timezonec = "BEGIN" ":" "VTIMEZONE" CRLF + + 2*( + + ; 'tzid' is required, but MUST NOT occur more + ; than once + + tzid / + + ; 'last-mod' and 'tzurl' are optional, + but MUST NOT occur more than once + + + +Dawson & Stenerson Standards Track [Page 60] + +RFC 2445 iCalendar November 1998 + + + last-mod / tzurl / + + ; one of 'standardc' or 'daylightc' MUST occur + ..; and each MAY occur more than once. + + standardc / daylightc / + + ; the following is optional, + ; and MAY occur more than once + + x-prop + + ) + + "END" ":" "VTIMEZONE" CRLF + + standardc = "BEGIN" ":" "STANDARD" CRLF + + tzprop + + "END" ":" "STANDARD" CRLF + + daylightc = "BEGIN" ":" "DAYLIGHT" CRLF + + tzprop + + "END" ":" "DAYLIGHT" CRLF + + tzprop = 3*( + + ; the following are each REQUIRED, + ; but MUST NOT occur more than once + + dtstart / tzoffsetto / tzoffsetfrom / + + ; the following are optional, + ; and MAY occur more than once + + comment / rdate / rrule / tzname / x-prop + + ) + + Description: A time zone is unambiguously defined by the set of time + measurement rules determined by the governing body for a given + geographic area. These rules describe at a minimum the base offset + from UTC for the time zone, often referred to as the Standard Time + offset. Many locations adjust their Standard Time forward or backward + by one hour, in order to accommodate seasonal changes in number of + + + +Dawson & Stenerson Standards Track [Page 61] + +RFC 2445 iCalendar November 1998 + + + daylight hours, often referred to as Daylight Saving Time. Some + locations adjust their time by a fraction of an hour. Standard Time + is also known as Winter Time. Daylight Saving Time is also known as + Advanced Time, Summer Time, or Legal Time in certain countries. The + following table shows the changes in time zone rules in effect for + New York City starting from 1967. Each line represents a description + or rule for a particular observance. + + Effective Observance Rule + + Date (Date/Time) Offset Abbreviation + + 1967-* last Sun in Oct, 02:00 -0500 EST + + 1967-1973 last Sun in Apr, 02:00 -0400 EDT + + 1974-1974 Jan 6, 02:00 -0400 EDT + + 1975-1975 Feb 23, 02:00 -0400 EDT + + 1976-1986 last Sun in Apr, 02:00 -0400 EDT + + 1987-* first Sun in Apr, 02:00 -0400 EDT + + Note: The specification of a global time zone registry is not + addressed by this document and is left for future study. + However, implementers may find the Olson time zone database [TZ] + a useful reference. It is an informal, public-domain collection + of time zone information, which is currently being maintained by + volunteer Internet participants, and is used in several + operating systems. This database contains current and historical + time zone information for a wide variety of locations around the + globe; it provides a time zone identifier for every unique time + zone rule set in actual use since 1970, with historical data + going back to the introduction of standard time. + + Interoperability between two calendaring and scheduling applications, + especially for recurring events, to-dos or journal entries, is + dependent on the ability to capture and convey date and time + information in an unambiguous format. The specification of current + time zone information is integral to this behavior. + + If present, the "VTIMEZONE" calendar component defines the set of + Standard Time and Daylight Saving Time observances (or rules) for a + particular time zone for a given interval of time. The "VTIMEZONE" + calendar component cannot be nested within other calendar components. + Multiple "VTIMEZONE" calendar components can exist in an iCalendar + object. In this situation, each "VTIMEZONE" MUST represent a unique + + + +Dawson & Stenerson Standards Track [Page 62] + +RFC 2445 iCalendar November 1998 + + + time zone definition. This is necessary for some classes of events, + such as airline flights, that start in one time zone and end in + another. + + The "VTIMEZONE" calendar component MUST be present if the iCalendar + object contains an RRULE that generates dates on both sides of a time + zone shift (e.g. both in Standard Time and Daylight Saving Time) + unless the iCalendar object intends to convey a floating time (See + the section "4.1.10.11 Time" for proper interpretation of floating + time). It can be present if the iCalendar object does not contain + such a RRULE. In addition, if a RRULE is present, there MUST be valid + time zone information for all recurrence instances. + + The "VTIMEZONE" calendar component MUST include the "TZID" property + and at least one definition of a standard or daylight component. The + standard or daylight component MUST include the "DTSTART", + "TZOFFSETFROM" and "TZOFFSETTO" properties. + + An individual "VTIMEZONE" calendar component MUST be specified for + each unique "TZID" parameter value specified in the iCalendar object. + + Each "VTIMEZONE" calendar component consists of a collection of one + or more sub-components that describe the rule for a particular + observance (either a Standard Time or a Daylight Saving Time + observance). The "STANDARD" sub-component consists of a collection of + properties that describe Standard Time. The "DAYLIGHT" sub-component + consists of a collection of properties that describe Daylight Saving + Time. In general this collection of properties consists of: + + - the first onset date-time for the observance + + - the last onset date-time for the observance, if a last onset + is known. + + - the offset to be applied for the observance + + - a rule that describes the day and time when the observance + takes effect + + - an optional name for the observance + + For a given time zone, there may be multiple unique definitions of + the observances over a period of time. Each observance is described + using either a "STANDARD" or "DAYLIGHT" sub-component. The collection + of these sub-components is used to describe the time zone for a given + period of time. The offset to apply at any given time is found by + locating the observance that has the last onset date and time before + the time in question, and using the offset value from that + + + +Dawson & Stenerson Standards Track [Page 63] + +RFC 2445 iCalendar November 1998 + + + observance. + + The top-level properties in a "VTIMEZONE" calendar component are: + + The mandatory "TZID" property is a text value that uniquely + identifies the VTIMZONE calendar component within the scope of an + iCalendar object. + + The optional "LAST-MODIFIED" property is a UTC value that specifies + the date and time that this time zone definition was last updated. + + The optional "TZURL" property is url value that points to a published + VTIMEZONE definition. TZURL SHOULD refer to a resource that is + accessible by anyone who might need to interpret the object. This + SHOULD NOT normally be a file: URL or other URL that is not widely- + accessible. + + The collection of properties that are used to define the STANDARD and + DAYLIGHT sub-components include: + + The mandatory "DTSTART" property gives the effective onset date and + local time for the time zone sub-component definition. "DTSTART" in + this usage MUST be specified as a local DATE-TIME value. + + The mandatory "TZOFFSETFROM" property gives the UTC offset which is + in use when the onset of this time zone observance begins. + "TZOFFSETFROM" is combined with "DTSTART" to define the effective + onset for the time zone sub-component definition. For example, the + following represents the time at which the observance of Standard + Time took effect in Fall 1967 for New York City: + + DTSTART:19671029T020000 + + TZOFFSETFROM:-0400 + + The mandatory "TZOFFSETTO " property gives the UTC offset for the + time zone sub-component (Standard Time or Daylight Saving Time) when + this observance is in use. + + The optional "TZNAME" property is the customary name for the time + zone. It may be specified multiple times, to allow for specifying + multiple language variants of the time zone names. This could be used + for displaying dates. + + If specified, the onset for the observance defined by the time zone + sub-component is defined by either the "RRULE" or "RDATE" property. + If neither is specified, only one sub-component can be specified in + the "VTIMEZONE" calendar component and it is assumed that the single + + + +Dawson & Stenerson Standards Track [Page 64] + +RFC 2445 iCalendar November 1998 + + + observance specified is always in effect. + + The "RRULE" property defines the recurrence rule for the onset of the + observance defined by this time zone sub-component. Some specific + requirements for the usage of RRULE for this purpose include: + + - If observance is known to have an effective end date, the + "UNTIL" recurrence rule parameter MUST be used to specify the + last valid onset of this observance (i.e., the UNTIL date-time + will be equal to the last instance generated by the recurrence + pattern). It MUST be specified in UTC time. + + - The "DTSTART" and the "TZOFFSETTO" properties MUST be used + when generating the onset date-time values (instances) from the + RRULE. + + Alternatively, the "RDATE" property can be used to define the onset + of the observance by giving the individual onset date and times. + "RDATE" in this usage MUST be specified as a local DATE-TIME value in + UTC time. + + The optional "COMMENT" property is also allowed for descriptive + explanatory text. + + Example: The following are examples of the "VTIMEZONE" calendar + component: + + This is an example showing time zone information for the Eastern + United States using "RDATE" property. Note that this is only suitable + for a recurring event that starts on or later than April 6, 1997 at + 03:00:00 EDT (i.e., the earliest effective transition date and time) + and ends no later than April 7, 1998 02:00:00 EST (i.e., latest valid + date and time for EST in this scenario). For example, this can be + used for a recurring event that occurs every Friday, 8am-9:00 AM, + starting June 1, 1997, ending December 31, 1997. + + BEGIN:VTIMEZONE + TZID:US-Eastern + LAST-MODIFIED:19870101T000000Z + BEGIN:STANDARD + DTSTART:19971026T020000 + RDATE:19971026T020000 + TZOFFSETFROM:-0400 + TZOFFSETTO:-0500 + TZNAME:EST + END:STANDARD + BEGIN:DAYLIGHT + DTSTART:19971026T020000 + + + +Dawson & Stenerson Standards Track [Page 65] + +RFC 2445 iCalendar November 1998 + + + RDATE:19970406T020000 + TZOFFSETFROM:-0500 + TZOFFSETTO:-0400 + TZNAME:EDT + END:DAYLIGHT + END:VTIMEZONE + + This is a simple example showing the current time zone rules for the + Eastern United States using a RRULE recurrence pattern. Note that + there is no effective end date to either of the Standard Time or + Daylight Time rules. This information would be valid for a recurring + event starting today and continuing indefinitely. + + BEGIN:VTIMEZONE + TZID:US-Eastern + LAST-MODIFIED:19870101T000000Z + TZURL:http://zones.stds_r_us.net/tz/US-Eastern + BEGIN:STANDARD + DTSTART:19671029T020000 + RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 + TZOFFSETFROM:-0400 + TZOFFSETTO:-0500 + TZNAME:EST + END:STANDARD + BEGIN:DAYLIGHT + DTSTART:19870405T020000 + RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 + TZOFFSETFROM:-0500 + TZOFFSETTO:-0400 + TZNAME:EDT + END:DAYLIGHT + END:VTIMEZONE + + This is an example showing a fictitious set of rules for the Eastern + United States, where the Daylight Time rule has an effective end date + (i.e., after that date, Daylight Time is no longer observed). + + BEGIN:VTIMEZONE + TZID:US--Fictitious-Eastern + LAST-MODIFIED:19870101T000000Z + BEGIN:STANDARD + DTSTART:19671029T020000 + RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 + TZOFFSETFROM:-0400 + TZOFFSETTO:-0500 + TZNAME:EST + END:STANDARD + + + + +Dawson & Stenerson Standards Track [Page 66] + +RFC 2445 iCalendar November 1998 + + + BEGIN:DAYLIGHT + DTSTART:19870405T020000 + RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=19980404T070000Z + TZOFFSETFROM:-0500 + TZOFFSETTO:-0400 + TZNAME:EDT + END:DAYLIGHT + END:VTIMEZONE + + This is an example showing a fictitious set of rules for the Eastern + United States, where the first Daylight Time rule has an effective + end date. There is a second Daylight Time rule that picks up where + the other left off. + + BEGIN:VTIMEZONE + TZID:US--Fictitious-Eastern + LAST-MODIFIED:19870101T000000Z + BEGIN:STANDARD + DTSTART:19671029T020000 + RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 + TZOFFSETFROM:-0400 + TZOFFSETTO:-0500 + TZNAME:EST + END:STANDARD + BEGIN:DAYLIGHT + DTSTART:19870405T020000 + RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4;UNTIL=19980404T070000Z + TZOFFSETFROM:-0500 + TZOFFSETTO:-0400 + TZNAME:EDT + END:DAYLIGHT + BEGIN:DAYLIGHT + DTSTART:19990424T020000 + RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=4 + TZOFFSETFROM:-0500 + TZOFFSETTO:-0400 + TZNAME:EDT + END:DAYLIGHT + END:VTIMEZONE + +4.6.6 Alarm Component + + Component Name: VALARM + + Purpose: Provide a grouping of component properties that define an + alarm. + + + + + +Dawson & Stenerson Standards Track [Page 67] + +RFC 2445 iCalendar November 1998 + + + Formal Definition: A "VALARM" calendar component is defined by the + following notation: + + alarmc = "BEGIN" ":" "VALARM" CRLF + (audioprop / dispprop / emailprop / procprop) + "END" ":" "VALARM" CRLF + + audioprop = 2*( + + ; 'action' and 'trigger' are both REQUIRED, + ; but MUST NOT occur more than once + + action / trigger / + + ; 'duration' and 'repeat' are both optional, + ; and MUST NOT occur more than once each, + ; but if one occurs, so MUST the other + + duration / repeat / + + ; the following is optional, + ; but MUST NOT occur more than once + + attach / + + ; the following is optional, + ; and MAY occur more than once + + x-prop + + ) + + + + dispprop = 3*( + + ; the following are all REQUIRED, + ; but MUST NOT occur more than once + + action / description / trigger / + + ; 'duration' and 'repeat' are both optional, + ; and MUST NOT occur more than once each, + ; but if one occurs, so MUST the other + + duration / repeat / + + ; the following is optional, + + + +Dawson & Stenerson Standards Track [Page 68] + +RFC 2445 iCalendar November 1998 + + + ; and MAY occur more than once + + *x-prop + + ) + + + + emailprop = 5*( + + ; the following are all REQUIRED, + ; but MUST NOT occur more than once + + action / description / trigger / summary + + ; the following is REQUIRED, + ; and MAY occur more than once + + attendee / + + ; 'duration' and 'repeat' are both optional, + ; and MUST NOT occur more than once each, + ; but if one occurs, so MUST the other + + duration / repeat / + + ; the following are optional, + ; and MAY occur more than once + + attach / x-prop + + ) + + + + procprop = 3*( + + ; the following are all REQUIRED, + ; but MUST NOT occur more than once + + action / attach / trigger / + + ; 'duration' and 'repeat' are both optional, + ; and MUST NOT occur more than once each, + ; but if one occurs, so MUST the other + + duration / repeat / + + + + +Dawson & Stenerson Standards Track [Page 69] + +RFC 2445 iCalendar November 1998 + + + ; 'description' is optional, + ; and MUST NOT occur more than once + + description / + + ; the following is optional, + ; and MAY occur more than once + + x-prop + + ) + + Description: A "VALARM" calendar component is a grouping of component + properties that is a reminder or alarm for an event or a to-do. For + example, it may be used to define a reminder for a pending event or + an overdue to-do. + + The "VALARM" calendar component MUST include the "ACTION" and + "TRIGGER" properties. The "ACTION" property further constrains the + "VALARM" calendar component in the following ways: + + When the action is "AUDIO", the alarm can also include one and only + one "ATTACH" property, which MUST point to a sound resource, which is + rendered when the alarm is triggered. + + When the action is "DISPLAY", the alarm MUST also include a + "DESCRIPTION" property, which contains the text to be displayed when + the alarm is triggered. + + When the action is "EMAIL", the alarm MUST include a "DESCRIPTION" + property, which contains the text to be used as the message body, a + "SUMMARY" property, which contains the text to be used as the message + subject, and one or more "ATTENDEE" properties, which contain the + email address of attendees to receive the message. It can also + include one or more "ATTACH" properties, which are intended to be + sent as message attachments. When the alarm is triggered, the email + message is sent. + + When the action is "PROCEDURE", the alarm MUST include one and only + one "ATTACH" property, which MUST point to a procedure resource, + which is invoked when the alarm is triggered. + + The "VALARM" calendar component MUST only appear within either a + "VEVENT" or "VTODO" calendar component. "VALARM" calendar components + cannot be nested. Multiple mutually independent "VALARM" calendar + components can be specified for a single "VEVENT" or "VTODO" calendar + component. + + + + +Dawson & Stenerson Standards Track [Page 70] + +RFC 2445 iCalendar November 1998 + + + The "TRIGGER" property specifies when the alarm will be triggered. + The "TRIGGER" property specifies a duration prior to the start of an + event or a to-do. The "TRIGGER" edge may be explicitly set to be + relative to the "START" or "END" of the event or to-do with the + "RELATED" parameter of the "TRIGGER" property. The "TRIGGER" property + value type can alternatively be set to an absolute calendar date and + time of day value. + + In an alarm set to trigger on the "START" of an event or to-do, the + "DTSTART" property MUST be present in the associated event or to-do. + In an alarm in a "VEVENT" calendar component set to trigger on the + "END" of the event, either the "DTEND" property MUST be present, or + the "DTSTART" and "DURATION" properties MUST both be present. In an + alarm in a "VTODO" calendar component set to trigger on the "END" of + the to-do, either the "DUE" property MUST be present, or the + "DTSTART" and "DURATION" properties MUST both be present. + + The alarm can be defined such that it triggers repeatedly. A + definition of an alarm with a repeating trigger MUST include both the + "DURATION" and "REPEAT" properties. The "DURATION" property specifies + the delay period, after which the alarm will repeat. The "REPEAT" + property specifies the number of additional repetitions that the + alarm will triggered. This repitition count is in addition to the + initial triggering of the alarm. Both of these properties MUST be + present in order to specify a repeating alarm. If one of these two + properties is absent, then the alarm will not repeat beyond the + initial trigger. + + The "ACTION" property is used within the "VALARM" calendar component + to specify the type of action invoked when the alarm is triggered. + The "VALARM" properties provide enough information for a specific + action to be invoked. It is typically the responsibility of a + "Calendar User Agent" (CUA) to deliver the alarm in the specified + fashion. An "ACTION" property value of AUDIO specifies an alarm that + causes a sound to be played to alert the user; DISPLAY specifies an + alarm that causes a text message to be displayed to the user; EMAIL + specifies an alarm that causes an electronic email message to be + delivered to one or more email addresses; and PROCEDURE specifies an + alarm that causes a procedure to be executed. The "ACTION" property + MUST specify one and only one of these values. + + In an AUDIO alarm, if the optional "ATTACH" property is included, it + MUST specify an audio sound resource. The intention is that the sound + will be played as the alarm effect. If an "ATTACH" property is + specified that does not refer to a sound resource, or if the + specified sound resource cannot be rendered (because its format is + unsupported, or because it cannot be retrieved), then the CUA or + other entity responsible for playing the sound may choose a fallback + + + +Dawson & Stenerson Standards Track [Page 71] + +RFC 2445 iCalendar November 1998 + + + action, such as playing a built-in default sound, or playing no sound + at all. + + In a DISPLAY alarm, the intended alarm effect is for the text value + of the "DESCRIPTION" property to be displayed to the user. + + In an EMAIL alarm, the intended alarm effect is for an email message + to be composed and delivered to all the addresses specified by the + "ATTENDEE" properties in the "VALARM" calendar component. The + "DESCRIPTION" property of the "VALARM" calendar component MUST be + used as the body text of the message, and the "SUMMARY" property MUST + be used as the subject text. Any "ATTACH" properties in the "VALARM" + calendar component SHOULD be sent as attachments to the message. + + In a PROCEDURE alarm, the "ATTACH" property in the "VALARM" calendar + component MUST specify a procedure or program that is intended to be + invoked as the alarm effect. If the procedure or program is in a + format that cannot be rendered, then no procedure alarm will be + invoked. If the "DESCRIPTION" property is present, its value + specifies the argument string to be passed to the procedure or + program. "Calendar User Agents" that receive an iCalendar object with + this category of alarm, can disable or allow the "Calendar User" to + disable, or otherwise ignore this type of alarm. While a very useful + alarm capability, the PROCEDURE type of alarm SHOULD be treated by + the "Calendar User Agent" as a potential security risk. + + Example: The following example is for a "VALARM" calendar component + that specifies an audio alarm that will sound at a precise time and + repeat 4 more times at 15 minute intervals: + + BEGIN:VALARM + TRIGGER;VALUE=DATE-TIME:19970317T133000Z + REPEAT:4 + DURATION:PT15M + ACTION:AUDIO + ATTACH;FMTTYPE=audio/basic:ftp://host.com/pub/sounds/bell-01.aud + END:VALARM + + The following example is for a "VALARM" calendar component that + specifies a display alarm that will trigger 30 minutes before the + scheduled start of the event or the due date/time of the to-do it is + associated with and will repeat 2 more times at 15 minute intervals: + + BEGIN:VALARM + TRIGGER:-PT30M + REPEAT:2 + DURATION:PT15M + ACTION:DISPLAY + + + +Dawson & Stenerson Standards Track [Page 72] + +RFC 2445 iCalendar November 1998 + + + DESCRIPTION:Breakfast meeting with executive\n + team at 8:30 AM EST. + END:VALARM + + The following example is for a "VALARM" calendar component that + specifies an email alarm that will trigger 2 days before the + scheduled due date/time of a to-do it is associated with. It does not + repeat. The email has a subject, body and attachment link. + + BEGIN:VALARM + TRIGGER:-P2D + ACTION:EMAIL + ATTENDEE:MAILTO:john_doe@host.com + SUMMARY:*** REMINDER: SEND AGENDA FOR WEEKLY STAFF MEETING *** + DESCRIPTION:A draft agenda needs to be sent out to the attendees + to the weekly managers meeting (MGR-LIST). Attached is a + pointer the document template for the agenda file. + ATTACH;FMTTYPE=application/binary:http://host.com/templates/agen + da.doc + END:VALARM + + The following example is for a "VALARM" calendar component that + specifies a procedural alarm that will trigger at a precise date/time + and will repeat 23 more times at one hour intervals. The alarm will + invoke a procedure file. + + BEGIN:VALARM + TRIGGER;VALUE=DATE-TIME:19980101T050000Z + REPEAT:23 + DURATION:PT1H + ACTION:PROCEDURE + ATTACH;FMTTYPE=application/binary:ftp://host.com/novo- + procs/felizano.exe + END:VALARM + +4.7 Calendar Properties + + The Calendar Properties are attributes that apply to the iCalendar + object, as a whole. These properties do not appear within a calendar + component. They SHOULD be specified after the "BEGIN:VCALENDAR" + property and prior to any calendar component. + +4.7.1 Calendar Scale + + Property Name: CALSCALE + + Purpose: This property defines the calendar scale used for the + calendar information specified in the iCalendar object. + + + +Dawson & Stenerson Standards Track [Page 73] + +RFC 2445 iCalendar November 1998 + + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: Property can be specified in an iCalendar object. The + default value is "GREGORIAN". + + Description: This memo is based on the Gregorian calendar scale. The + Gregorian calendar scale is assumed if this property is not specified + in the iCalendar object. It is expected that other calendar scales + will be defined in other specifications or by future versions of this + memo. + + Format Definition: The property is defined by the following notation: + + calscale = "CALSCALE" calparam ":" calvalue CRLF + + calparam = *(";" xparam) + + calvalue = "GREGORIAN" / iana-token + + Example: The following is an example of this property: + + CALSCALE:GREGORIAN + +4.7.2 Method + + Property Name: METHOD + + Purpose: This property defines the iCalendar object method associated + with the calendar object. + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: The property can be specified in an iCalendar object. + + Description: When used in a MIME message entity, the value of this + property MUST be the same as the Content-Type "method" parameter + value. This property can only appear once within the iCalendar + object. If either the "METHOD" property or the Content-Type "method" + parameter is specified, then the other MUST also be specified. + + No methods are defined by this specification. This is the subject of + other specifications, such as the iCalendar Transport-independent + + + +Dawson & Stenerson Standards Track [Page 74] + +RFC 2445 iCalendar November 1998 + + + Interoperability Protocol (iTIP) defined by [ITIP]. + + If this property is not present in the iCalendar object, then a + scheduling transaction MUST NOT be assumed. In such cases, the + iCalendar object is merely being used to transport a snapshot of some + calendar information; without the intention of conveying a scheduling + semantic. + + Format Definition: The property is defined by the following notation: + + method = "METHOD" metparam ":" metvalue CRLF + + metparam = *(";" xparam) + + metvalue = iana-token + + Example: The following is a hypothetical example of this property to + convey that the iCalendar object is a request for a meeting: + + METHOD:REQUEST + +4.7.3 Product Identifier + + Property Name: PRODID + + Purpose: This property specifies the identifier for the product that + created the iCalendar object. + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: The property MUST be specified once in an iCalendar + object. + + Description: The vendor of the implementation SHOULD assure that this + is a globally unique identifier; using some technique such as an FPI + value, as defined in [ISO 9070]. + + This property SHOULD not be used to alter the interpretation of an + iCalendar object beyond the semantics specified in this memo. For + example, it is not to be used to further the understanding of non- + standard properties. + + Format Definition: The property is defined by the following notation: + + prodid = "PRODID" pidparam ":" pidvalue CRLF + + + +Dawson & Stenerson Standards Track [Page 75] + +RFC 2445 iCalendar November 1998 + + + pidparam = *(";" xparam) + + pidvalue = text + ;Any text that describes the product and version + ;and that is generally assured of being unique. + + Example: The following is an example of this property. It does not + imply that English is the default language. + + PRODID:-//ABC Corporation//NONSGML My Product//EN + +4.7.4 Version + + Property Name: VERSION + + Purpose: This property specifies the identifier corresponding to the + highest version number or the minimum and maximum range of the + iCalendar specification that is required in order to interpret the + iCalendar object. + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property MUST be specified by an iCalendar object, + but MUST only be specified once. + + Description: A value of "2.0" corresponds to this memo. + + Format Definition: The property is defined by the following notation: + + version = "VERSION" verparam ":" vervalue CRLF + + verparam = *(";" xparam) + + vervalue = "2.0" ;This memo + / maxver + / (minver ";" maxver) + + minver =
    + ;Minimum iCalendar version needed to parse the iCalendar object + + maxver = + ;Maximum iCalendar version needed to parse the iCalendar object + + Example: The following is an example of this property: + + + + +Dawson & Stenerson Standards Track [Page 76] + +RFC 2445 iCalendar November 1998 + + + VERSION:2.0 + +4.8 Component Properties + + The following properties can appear within calendar components, as + specified by each component property definition. + +4.8.1 Descriptive Component Properties + + The following properties specify descriptive information about + calendar components. + +4.8.1.1 Attachment + + Property Name: ATTACH + + Purpose: The property provides the capability to associate a document + object with a calendar component. + + Value Type: The default value type for this property is URI. The + value type can also be set to BINARY to indicate inline binary + encoded content information. + + Property Parameters: Non-standard, inline encoding, format type and + value data type property parameters can be specified on this + property. + + Conformance: The property can be specified in a "VEVENT", "VTODO", + "VJOURNAL" or "VALARM" calendar components. + + Description: The property can be specified within "VEVENT", "VTODO", + "VJOURNAL", or "VALARM" calendar components. This property can be + specified multiple times within an iCalendar object. + + Format Definition: The property is defined by the following notation: + + attach = "ATTACH" attparam ":" uri CRLF + + attach =/ "ATTACH" attparam ";" "ENCODING" "=" "BASE64" + ";" "VALUE" "=" "BINARY" ":" binary + + attparam = *( + + ; the following is optional, + ; but MUST NOT occur more than once + + (";" fmttypeparam) / + + + + +Dawson & Stenerson Standards Track [Page 77] + +RFC 2445 iCalendar November 1998 + + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + Example: The following are examples of this property: + + ATTACH:CID:jsmith.part3.960817T083000.xyzMail@host1.com + + ATTACH;FMTTYPE=application/postscript:ftp://xyzCorp.com/pub/ + reports/r-960812.ps + +4.8.1.2 Categories + + Property Name: CATEGORIES + + Purpose: This property defines the categories for a calendar + component. + + Value Type: TEXT + + Property Parameters: Non-standard and language property parameters + can be specified on this property. + + Conformance: The property can be specified within "VEVENT", "VTODO" + or "VJOURNAL" calendar components. + + Description: This property is used to specify categories or subtypes + of the calendar component. The categories are useful in searching for + a calendar component of a particular type and category. Within the + "VEVENT", "VTODO" or "VJOURNAL" calendar components, more than one + category can be specified as a list of categories separated by the + COMMA character (US-ASCII decimal 44). + + Format Definition: The property is defined by the following notation: + + categories = "CATEGORIES" catparam ":" text *("," text) + CRLF + + catparam = *( + + ; the following is optional, + ; but MUST NOT occur more than once + + (";" languageparam ) / + + + + +Dawson & Stenerson Standards Track [Page 78] + +RFC 2445 iCalendar November 1998 + + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + Example: The following are examples of this property: + + CATEGORIES:APPOINTMENT,EDUCATION + + CATEGORIES:MEETING + +4.8.1.3 Classification + + Property Name: CLASS + + Purpose: This property defines the access classification for a + calendar component. + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: The property can be specified once in a "VEVENT", + "VTODO" or "VJOURNAL" calendar components. + + Description: An access classification is only one component of the + general security system within a calendar application. It provides a + method of capturing the scope of the access the calendar owner + intends for information within an individual calendar entry. The + access classification of an individual iCalendar component is useful + when measured along with the other security components of a calendar + system (e.g., calendar user authentication, authorization, access + rights, access role, etc.). Hence, the semantics of the individual + access classifications cannot be completely defined by this memo + alone. Additionally, due to the "blind" nature of most exchange + processes using this memo, these access classifications cannot serve + as an enforcement statement for a system receiving an iCalendar + object. Rather, they provide a method for capturing the intention of + the calendar owner for the access to the calendar component. + + Format Definition: The property is defined by the following notation: + + class = "CLASS" classparam ":" classvalue CRLF + + classparam = *(";" xparam) + + + +Dawson & Stenerson Standards Track [Page 79] + +RFC 2445 iCalendar November 1998 + + + classvalue = "PUBLIC" / "PRIVATE" / "CONFIDENTIAL" / iana-token + / x-name + ;Default is PUBLIC + + Example: The following is an example of this property: + + CLASS:PUBLIC + +4.8.1.4 Comment + + Property Name: COMMENT + + Purpose: This property specifies non-processing information intended + to provide a comment to the calendar user. + + Value Type: TEXT + + Property Parameters: Non-standard, alternate text representation and + language property parameters can be specified on this property. + + Conformance: This property can be specified in "VEVENT", "VTODO", + "VJOURNAL", "VTIMEZONE" or "VFREEBUSY" calendar components. + + Description: The property can be specified multiple times. + + Format Definition: The property is defined by the following notation: + + comment = "COMMENT" commparam ":" text CRLF + + commparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" altrepparam) / (";" languageparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + Example: The following is an example of this property: + + COMMENT:The meeting really needs to include both ourselves + and the customer. We can't hold this meeting without them. + As a matter of fact\, the venue for the meeting ought to be at + + + +Dawson & Stenerson Standards Track [Page 80] + +RFC 2445 iCalendar November 1998 + + + their site. - - John + + The data type for this property is TEXT. + +4.8.1.5 Description + + Property Name: DESCRIPTION + + Purpose: This property provides a more complete description of the + calendar component, than that provided by the "SUMMARY" property. + + Value Type: TEXT + + Property Parameters: Non-standard, alternate text representation and + language property parameters can be specified on this property. + + Conformance: The property can be specified in the "VEVENT", "VTODO", + "VJOURNAL" or "VALARM" calendar components. The property can be + specified multiple times only within a "VJOURNAL" calendar component. + + Description: This property is used in the "VEVENT" and "VTODO" to + capture lengthy textual decriptions associated with the activity. + + This property is used in the "VJOURNAL" calendar component to capture + one more textual journal entries. + + This property is used in the "VALARM" calendar component to capture + the display text for a DISPLAY category of alarm, to capture the body + text for an EMAIL category of alarm and to capture the argument + string for a PROCEDURE category of alarm. + + Format Definition: The property is defined by the following notation: + + description = "DESCRIPTION" descparam ":" text CRLF + + descparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" altrepparam) / (";" languageparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + + +Dawson & Stenerson Standards Track [Page 81] + +RFC 2445 iCalendar November 1998 + + + Example: The following is an example of the property with formatted + line breaks in the property value: + + DESCRIPTION:Meeting to provide technical review for "Phoenix" + design.\n Happy Face Conference Room. Phoenix design team + MUST attend this meeting.\n RSVP to team leader. + + The following is an example of the property with folding of long + lines: + + DESCRIPTION:Last draft of the new novel is to be completed + for the editor's proof today. + +4.8.1.6 Geographic Position + + Property Name: GEO + + Purpose: This property specifies information related to the global + position for the activity specified by a calendar component. + + Value Type: FLOAT. The value MUST be two SEMICOLON separated FLOAT + values. + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property can be specified in "VEVENT" or "VTODO" + calendar components. + + Description: The property value specifies latitude and longitude, in + that order (i.e., "LAT LON" ordering). The longitude represents the + location east or west of the prime meridian as a positive or negative + real number, respectively. The longitude and latitude values MAY be + specified up to six decimal places, which will allow for accuracy to + within one meter of geographical position. Receiving applications + MUST accept values of this precision and MAY truncate values of + greater precision. + + Values for latitude and longitude shall be expressed as decimal + fractions of degrees. Whole degrees of latitude shall be represented + by a two-digit decimal number ranging from 0 through 90. Whole + degrees of longitude shall be represented by a decimal number ranging + from 0 through 180. When a decimal fraction of a degree is specified, + it shall be separated from the whole number of degrees by a decimal + point. + + + + + + +Dawson & Stenerson Standards Track [Page 82] + +RFC 2445 iCalendar November 1998 + + + Latitudes north of the equator shall be specified by a plus sign (+), + or by the absence of a minus sign (-), preceding the digits + designating degrees. Latitudes south of the Equator shall be + designated by a minus sign (-) preceding the digits designating + degrees. A point on the Equator shall be assigned to the Northern + Hemisphere. + + Longitudes east of the prime meridian shall be specified by a plus + sign (+), or by the absence of a minus sign (-), preceding the digits + designating degrees. Longitudes west of the meridian shall be + designated by minus sign (-) preceding the digits designating + degrees. A point on the prime meridian shall be assigned to the + Eastern Hemisphere. A point on the 180th meridian shall be assigned + to the Western Hemisphere. One exception to this last convention is + permitted. For the special condition of describing a band of latitude + around the earth, the East Bounding Coordinate data element shall be + assigned the value +180 (180) degrees. + + Any spatial address with a latitude of +90 (90) or -90 degrees will + specify the position at the North or South Pole, respectively. The + component for longitude may have any legal value. + + With the exception of the special condition described above, this + form is specified in Department of Commerce, 1986, Representation of + geographic point locations for information interchange (Federal + Information Processing Standard 70-1): Washington, Department of + Commerce, National Institute of Standards and Technology. + + The simple formula for converting degrees-minutes-seconds into + decimal degrees is: + + decimal = degrees + minutes/60 + seconds/3600. + + Format Definition: The property is defined by the following notation: + + geo = "GEO" geoparam ":" geovalue CRLF + + geoparam = *(";" xparam) + + geovalue = float ";" float + ;Latitude and Longitude components + + Example: The following is an example of this property: + + GEO:37.386013;-122.082932 + + + + + + +Dawson & Stenerson Standards Track [Page 83] + +RFC 2445 iCalendar November 1998 + + +4.8.1.7 Location + + Property Name: LOCATION + + Purpose: The property defines the intended venue for the activity + defined by a calendar component. + + Value Type: TEXT + + Property Parameters: Non-standard, alternate text representation and + language property parameters can be specified on this property. + + Conformance: This property can be specified in "VEVENT" or "VTODO" + calendar component. + + Description: Specific venues such as conference or meeting rooms may + be explicitly specified using this property. An alternate + representation may be specified that is a URI that points to + directory information with more structured specification of the + location. For example, the alternate representation may specify + either an LDAP URI pointing to an LDAP server entry or a CID URI + pointing to a MIME body part containing a vCard [RFC 2426] for the + location. + + Format Definition: The property is defined by the following notation: + + location = "LOCATION locparam ":" text CRLF + + locparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" altrepparam) / (";" languageparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + Example: The following are some examples of this property: + + LOCATION:Conference Room - F123, Bldg. 002 + + LOCATION;ALTREP="http://xyzcorp.com/conf-rooms/f123.vcf": + Conference Room - F123, Bldg. 002 + + + +Dawson & Stenerson Standards Track [Page 84] + +RFC 2445 iCalendar November 1998 + + +4.8.1.8 Percent Complete + + Property Name: PERCENT-COMPLETE + + Purpose: This property is used by an assignee or delegatee of a to-do + to convey the percent completion of a to-do to the Organizer. + + Value Type: INTEGER + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property can be specified in a "VTODO" calendar + component. + + Description: The property value is a positive integer between zero + and one hundred. A value of "0" indicates the to-do has not yet been + started. A value of "100" indicates that the to-do has been + completed. Integer values in between indicate the percent partially + complete. + + When a to-do is assigned to multiple individuals, the property value + indicates the percent complete for that portion of the to-do assigned + to the assignee or delegatee. For example, if a to-do is assigned to + both individuals "A" and "B". A reply from "A" with a percent + complete of "70" indicates that "A" has completed 70% of the to-do + assigned to them. A reply from "B" with a percent complete of "50" + indicates "B" has completed 50% of the to-do assigned to them. + + Format Definition: The property is defined by the following notation: + + percent = "PERCENT-COMPLETE" pctparam ":" integer CRLF + + pctparam = *(";" xparam) + + Example: The following is an example of this property to show 39% + completion: + + PERCENT-COMPLETE:39 + +4.8.1.9 Priority + + Property Name: PRIORITY + + Purpose: The property defines the relative priority for a calendar + component. + + Value Type: INTEGER + + + +Dawson & Stenerson Standards Track [Page 85] + +RFC 2445 iCalendar November 1998 + + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: The property can be specified in a "VEVENT" or "VTODO" + calendar component. + + Description: The priority is specified as an integer in the range + zero to nine. A value of zero (US-ASCII decimal 48) specifies an + undefined priority. A value of one (US-ASCII decimal 49) is the + highest priority. A value of two (US-ASCII decimal 50) is the second + highest priority. Subsequent numbers specify a decreasing ordinal + priority. A value of nine (US-ASCII decimal 58) is the lowest + priority. + + A CUA with a three-level priority scheme of "HIGH", "MEDIUM" and + "LOW" is mapped into this property such that a property value in the + range of one (US-ASCII decimal 49) to four (US-ASCII decimal 52) + specifies "HIGH" priority. A value of five (US-ASCII decimal 53) is + the normal or "MEDIUM" priority. A value in the range of six (US- + ASCII decimal 54) to nine (US-ASCII decimal 58) is "LOW" priority. + + A CUA with a priority schema of "A1", "A2", "A3", "B1", "B2", ..., + "C3" is mapped into this property such that a property value of one + (US-ASCII decimal 49) specifies "A1", a property value of two (US- + ASCII decimal 50) specifies "A2", a property value of three (US-ASCII + decimal 51) specifies "A3", and so forth up to a property value of 9 + (US-ASCII decimal 58) specifies "C3". + + Other integer values are reserved for future use. + + Within a "VEVENT" calendar component, this property specifies a + priority for the event. This property may be useful when more than + one event is scheduled for a given time period. + + Within a "VTODO" calendar component, this property specified a + priority for the to-do. This property is useful in prioritizing + multiple action items for a given time period. + + Format Definition: The property is specified by the following + notation: + + priority = "PRIORITY" prioparam ":" privalue CRLF + ;Default is zero + + prioparam = *(";" xparam) + + privalue = integer ;Must be in the range [0..9] + ; All other values are reserved for future use + + + +Dawson & Stenerson Standards Track [Page 86] + +RFC 2445 iCalendar November 1998 + + + The following is an example of a property with the highest priority: + + PRIORITY:1 + + The following is an example of a property with a next highest + priority: + + PRIORITY:2 + + Example: The following is an example of a property with no priority. + This is equivalent to not specifying the "PRIORITY" property: + + PRIORITY:0 + +4.8.1.10 Resources + + Property Name: RESOURCES + + Purpose: This property defines the equipment or resources anticipated + for an activity specified by a calendar entity.. + + Value Type: TEXT + + Property Parameters: Non-standard, alternate text representation and + language property parameters can be specified on this property. + + Conformance: This property can be specified in "VEVENT" or "VTODO" + calendar component. + + Description: The property value is an arbitrary text. More than one + resource can be specified as a list of resources separated by the + COMMA character (US-ASCII decimal 44). + + Format Definition: The property is defined by the following notation: + + resources = "RESOURCES" resrcparam ":" text *("," text) CRLF + + resrcparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" altrepparam) / (";" languageparam) / + + ; the following is optional, + ; and MAY occur more than once + + + + + +Dawson & Stenerson Standards Track [Page 87] + +RFC 2445 iCalendar November 1998 + + + (";" xparam) + + ) + + Example: The following is an example of this property: + + RESOURCES:EASEL,PROJECTOR,VCR + + RESOURCES;LANGUAGE=fr:1 raton-laveur + +4.8.1.11 Status + + Property Name: STATUS + + Purpose: This property defines the overall status or confirmation for + the calendar component. + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property can be specified in "VEVENT", "VTODO" or + "VJOURNAL" calendar components. + + Description: In a group scheduled calendar component, the property is + used by the "Organizer" to provide a confirmation of the event to the + "Attendees". For example in a "VEVENT" calendar component, the + "Organizer" can indicate that a meeting is tentative, confirmed or + cancelled. In a "VTODO" calendar component, the "Organizer" can + indicate that an action item needs action, is completed, is in + process or being worked on, or has been cancelled. In a "VJOURNAL" + calendar component, the "Organizer" can indicate that a journal entry + is draft, final or has been cancelled or removed. + + Format Definition: The property is defined by the following notation: + + status = "STATUS" statparam] ":" statvalue CRLF + + statparam = *(";" xparam) + + statvalue = "TENTATIVE" ;Indicates event is + ;tentative. + / "CONFIRMED" ;Indicates event is + ;definite. + / "CANCELLED" ;Indicates event was + ;cancelled. + ;Status values for a "VEVENT" + + + +Dawson & Stenerson Standards Track [Page 88] + +RFC 2445 iCalendar November 1998 + + + statvalue =/ "NEEDS-ACTION" ;Indicates to-do needs action. + / "COMPLETED" ;Indicates to-do completed. + / "IN-PROCESS" ;Indicates to-do in process of + / "CANCELLED" ;Indicates to-do was cancelled. + ;Status values for "VTODO". + + statvalue =/ "DRAFT" ;Indicates journal is draft. + / "FINAL" ;Indicates journal is final. + / "CANCELLED" ;Indicates journal is removed. + ;Status values for "VJOURNAL". + + Example: The following is an example of this property for a "VEVENT" + calendar component: + + STATUS:TENTATIVE + + The following is an example of this property for a "VTODO" calendar + component: + + STATUS:NEEDS-ACTION + + The following is an example of this property for a "VJOURNAL" + calendar component: + + STATUS:DRAFT + +4.8.1.12 Summary + + Property Name: SUMMARY + + Purpose: This property defines a short summary or subject for the + calendar component. + + Value Type: TEXT + + Property Parameters: Non-standard, alternate text representation and + language property parameters can be specified on this property. + + Conformance: The property can be specified in "VEVENT", "VTODO", + "VJOURNAL" or "VALARM" calendar components. + + Description: This property is used in the "VEVENT", "VTODO" and + "VJOURNAL" calendar components to capture a short, one line summary + about the activity or journal entry. + + This property is used in the "VALARM" calendar component to capture + the subject of an EMAIL category of alarm. + + + + +Dawson & Stenerson Standards Track [Page 89] + +RFC 2445 iCalendar November 1998 + + + Format Definition: The property is defined by the following notation: + + summary = "SUMMARY" summparam ":" text CRLF + + summparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" altrepparam) / (";" languageparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + Example: The following is an example of this property: + + SUMMARY:Department Party + +4.8.2 Date and Time Component Properties + + The following properties specify date and time related information in + calendar components. + +4.8.2.1 Date/Time Completed + + Property Name: COMPLETED + + Purpose: This property defines the date and time that a to-do was + actually completed. + + Value Type: DATE-TIME + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: The property can be specified in a "VTODO" calendar + component. + + Description: The date and time MUST be in a UTC format. + + Format Definition: The property is defined by the following notation: + + completed = "COMPLETED" compparam ":" date-time CRLF + + + + +Dawson & Stenerson Standards Track [Page 90] + +RFC 2445 iCalendar November 1998 + + + compparam = *(";" xparam) + + Example: The following is an example of this property: + + COMPLETED:19960401T235959Z + +4.8.2.2 Date/Time End + + Property Name: DTEND + + Purpose: This property specifies the date and time that a calendar + component ends. + + Value Type: The default value type is DATE-TIME. The value type can + be set to a DATE value type. + + Property Parameters: Non-standard, value data type, time zone + identifier property parameters can be specified on this property. + + Conformance: This property can be specified in "VEVENT" or + "VFREEBUSY" calendar components. + + Description: Within the "VEVENT" calendar component, this property + defines the date and time by which the event ends. The value MUST be + later in time than the value of the "DTSTART" property. + + Within the "VFREEBUSY" calendar component, this property defines the + end date and time for the free or busy time information. The time + MUST be specified in the UTC time format. The value MUST be later in + time than the value of the "DTSTART" property. + + Format Definition: The property is defined by the following notation: + + dtend = "DTEND" dtendparam":" dtendval CRLF + + dtendparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" "VALUE" "=" ("DATE-TIME" / "DATE")) / + (";" tzidparam) / + + ; the following is optional, + ; and MAY occur more than once + + + + + + +Dawson & Stenerson Standards Track [Page 91] + +RFC 2445 iCalendar November 1998 + + + (";" xparam) + + ) + + + + dtendval = date-time / date + ;Value MUST match value type + + Example: The following is an example of this property: + + DTEND:19960401T235959Z + + DTEND;VALUE=DATE:19980704 + +4.8.2.3 Date/Time Due + + Property Name: DUE + + Purpose: This property defines the date and time that a to-do is + expected to be completed. + + Value Type: The default value type is DATE-TIME. The value type can + be set to a DATE value type. + + Property Parameters: Non-standard, value data type, time zone + identifier property parameters can be specified on this property. + + Conformance: The property can be specified once in a "VTODO" calendar + component. + + Description: The value MUST be a date/time equal to or after the + DTSTART value, if specified. + + Format Definition: The property is defined by the following notation: + + due = "DUE" dueparam":" dueval CRLF + + dueparam = *( + ; the following are optional, + ; but MUST NOT occur more than once + + (";" "VALUE" "=" ("DATE-TIME" / "DATE")) / + (";" tzidparam) / + + ; the following is optional, + ; and MAY occur more than once + + + + +Dawson & Stenerson Standards Track [Page 92] + +RFC 2445 iCalendar November 1998 + + + *(";" xparam) + + ) + + + + dueval = date-time / date + ;Value MUST match value type + + Example: The following is an example of this property: + + DUE:19980430T235959Z + +4.8.2.4 Date/Time Start + + Property Name: DTSTART + + Purpose: This property specifies when the calendar component begins. + + Value Type: The default value type is DATE-TIME. The time value MUST + be one of the forms defined for the DATE-TIME value type. The value + type can be set to a DATE value type. + + Property Parameters: Non-standard, value data type, time zone + identifier property parameters can be specified on this property. + + Conformance: This property can be specified in the "VEVENT", "VTODO", + "VFREEBUSY", or "VTIMEZONE" calendar components. + + Description: Within the "VEVENT" calendar component, this property + defines the start date and time for the event. The property is + REQUIRED in "VEVENT" calendar components. Events can have a start + date/time but no end date/time. In that case, the event does not take + up any time. + + Within the "VFREEBUSY" calendar component, this property defines the + start date and time for the free or busy time information. The time + MUST be specified in UTC time. + + Within the "VTIMEZONE" calendar component, this property defines the + effective start date and time for a time zone specification. This + property is REQUIRED within each STANDARD and DAYLIGHT part included + in "VTIMEZONE" calendar components and MUST be specified as a local + DATE-TIME without the "TZID" property parameter. + + Format Definition: The property is defined by the following notation: + + dtstart = "DTSTART" dtstparam ":" dtstval CRLF + + + +Dawson & Stenerson Standards Track [Page 93] + +RFC 2445 iCalendar November 1998 + + + dtstparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" "VALUE" "=" ("DATE-TIME" / "DATE")) / + (";" tzidparam) / + + ; the following is optional, + ; and MAY occur more than once + + *(";" xparam) + + ) + + + + dtstval = date-time / date + ;Value MUST match value type + + Example: The following is an example of this property: + + DTSTART:19980118T073000Z + +4.8.2.5 Duration + + Property Name: DURATION + + Purpose: The property specifies a positive duration of time. + + Value Type: DURATION + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: The property can be specified in "VEVENT", "VTODO", + "VFREEBUSY" or "VALARM" calendar components. + + Description: In a "VEVENT" calendar component the property may be + used to specify a duration of the event, instead of an explicit end + date/time. In a "VTODO" calendar component the property may be used + to specify a duration for the to-do, instead of an explicit due + date/time. In a "VFREEBUSY" calendar component the property may be + used to specify the interval of free time being requested. In a + "VALARM" calendar component the property may be used to specify the + delay period prior to repeating an alarm. + + Format Definition: The property is defined by the following notation: + + + +Dawson & Stenerson Standards Track [Page 94] + +RFC 2445 iCalendar November 1998 + + + duration = "DURATION" durparam ":" dur-value CRLF + ;consisting of a positive duration of time. + + durparam = *(";" xparam) + + Example: The following is an example of this property that specifies + an interval of time of 1 hour and zero minutes and zero seconds: + + DURATION:PT1H0M0S + + The following is an example of this property that specifies an + interval of time of 15 minutes. + + DURATION:PT15M + +4.8.2.6 Free/Busy Time + + Property Name: FREEBUSY + + Purpose: The property defines one or more free or busy time + intervals. + + Value Type: PERIOD. The date and time values MUST be in an UTC time + format. + + Property Parameters: Non-standard or free/busy time type property + parameters can be specified on this property. + + Conformance: The property can be specified in a "VFREEBUSY" calendar + component. + + Property Parameter: "FBTYPE" and non-standard parameters can be + specified on this property. + + Description: These time periods can be specified as either a start + and end date-time or a start date-time and duration. The date and + time MUST be a UTC time format. + + "FREEBUSY" properties within the "VFREEBUSY" calendar component + SHOULD be sorted in ascending order, based on start time and then end + time, with the earliest periods first. + + The "FREEBUSY" property can specify more than one value, separated by + the COMMA character (US-ASCII decimal 44). In such cases, the + "FREEBUSY" property values SHOULD all be of the same "FBTYPE" + property parameter type (e.g., all values of a particular "FBTYPE" + listed together in a single property). + + + + +Dawson & Stenerson Standards Track [Page 95] + +RFC 2445 iCalendar November 1998 + + + Format Definition: The property is defined by the following notation: + + freebusy = "FREEBUSY" fbparam ":" fbvalue + CRLF + + fbparam = *( + ; the following is optional, + ; but MUST NOT occur more than once + + (";" fbtypeparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + fbvalue = period *["," period] + ;Time value MUST be in the UTC time format. + + Example: The following are some examples of this property: + + FREEBUSY;FBTYPE=BUSY-UNAVAILABLE:19970308T160000Z/PT8H30M + + FREEBUSY;FBTYPE=FREE:19970308T160000Z/PT3H,19970308T200000Z/PT1H + + FREEBUSY;FBTYPE=FREE:19970308T160000Z/PT3H,19970308T200000Z/PT1H, + 19970308T230000Z/19970309T000000Z + +4.8.2.7 Time Transparency + + Property Name: TRANSP + + Purpose: This property defines whether an event is transparent or not + to busy time searches. + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property can be specified once in a "VEVENT" + calendar component. + + Description: Time Transparency is the characteristic of an event that + determines whether it appears to consume time on a calendar. Events + that consume actual time for the individual or resource associated + + + +Dawson & Stenerson Standards Track [Page 96] + +RFC 2445 iCalendar November 1998 + + + with the calendar SHOULD be recorded as OPAQUE, allowing them to be + detected by free-busy time searches. Other events, which do not take + up the individual's (or resource's) time SHOULD be recorded as + TRANSPARENT, making them invisible to free-busy time searches. + + Format Definition: The property is specified by the following + notation: + + transp = "TRANSP" tranparam ":" transvalue CRLF + + tranparam = *(";" xparam) + + transvalue = "OPAQUE" ;Blocks or opaque on busy time searches. + / "TRANSPARENT" ;Transparent on busy time searches. + ;Default value is OPAQUE + + Example: The following is an example of this property for an event + that is transparent or does not block on free/busy time searches: + + TRANSP:TRANSPARENT + + The following is an example of this property for an event that is + opaque or blocks on free/busy time searches: + + TRANSP:OPAQUE + +4.8.3 Time Zone Component Properties + + The following properties specify time zone information in calendar + components. + +4.8.3.1 Time Zone Identifier + + Property Name: TZID + + Purpose: This property specifies the text value that uniquely + identifies the "VTIMEZONE" calendar component. + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property MUST be specified in a "VTIMEZONE" + calendar component. + + + + + + +Dawson & Stenerson Standards Track [Page 97] + +RFC 2445 iCalendar November 1998 + + + Description: This is the label by which a time zone calendar + component is referenced by any iCalendar properties whose data type + is either DATE-TIME or TIME and not intended to specify a UTC or a + "floating" time. The presence of the SOLIDUS character (US-ASCII + decimal 47) as a prefix, indicates that this TZID represents an + unique ID in a globally defined time zone registry (when such + registry is defined). + + Note: This document does not define a naming convention for time + zone identifiers. Implementers may want to use the naming + conventions defined in existing time zone specifications such as + the public-domain Olson database [TZ]. The specification of + globally unique time zone identifiers is not addressed by this + document and is left for future study. + + Format Definition: This property is defined by the following + notation: + + tzid = "TZID" tzidpropparam ":" [tzidprefix] text CRLF + + tzidpropparam = *(";" xparam) + + ;tzidprefix = "/" + ; Defined previously. Just listed here for reader convenience. + + Example: The following are examples of non-globally unique time zone + identifiers: + + TZID:US-Eastern + + TZID:California-Los_Angeles + + The following is an example of a fictitious globally unique time zone + identifier: + + TZID:/US-New_York-New_York + +4.8.3.2 Time Zone Name + + Property Name: TZNAME + + Purpose: This property specifies the customary designation for a time + zone description. + + Value Type: TEXT + + Property Parameters: Non-standard and language property parameters + can be specified on this property. + + + +Dawson & Stenerson Standards Track [Page 98] + +RFC 2445 iCalendar November 1998 + + + Conformance: This property can be specified in a "VTIMEZONE" calendar + component. + + Description: This property may be specified in multiple languages; in + order to provide for different language requirements. + + Format Definition: This property is defined by the following + notation: + + tzname = "TZNAME" tznparam ":" text CRLF + + tznparam = *( + + ; the following is optional, + ; but MUST NOT occur more than once + + (";" languageparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + Example: The following are example of this property: + + TZNAME:EST + + The following is an example of this property when two different + languages for the time zone name are specified: + + TZNAME;LANGUAGE=en:EST + TZNAME;LANGUAGE=fr-CA:HNE + +4.8.3.3 Time Zone Offset From + + Property Name: TZOFFSETFROM + + Purpose: This property specifies the offset which is in use prior to + this time zone observance. + + Value Type: UTC-OFFSET + + Property Parameters: Non-standard property parameters can be + specified on this property. + + + + + +Dawson & Stenerson Standards Track [Page 99] + +RFC 2445 iCalendar November 1998 + + + Conformance: This property MUST be specified in a "VTIMEZONE" + calendar component. + + Description: This property specifies the offset which is in use prior + to this time observance. It is used to calculate the absolute time at + which the transition to a given observance takes place. This property + MUST only be specified in a "VTIMEZONE" calendar component. A + "VTIMEZONE" calendar component MUST include this property. The + property value is a signed numeric indicating the number of hours and + possibly minutes from UTC. Positive numbers represent time zones east + of the prime meridian, or ahead of UTC. Negative numbers represent + time zones west of the prime meridian, or behind UTC. + + Format Definition: The property is defined by the following notation: + + tzoffsetfrom = "TZOFFSETFROM" frmparam ":" utc-offset + CRLF + + frmparam = *(";" xparam) + + Example: The following are examples of this property: + + TZOFFSETFROM:-0500 + + TZOFFSETFROM:+1345 + +4.8.3.4 Time Zone Offset To + + Property Name: TZOFFSETTO + + Purpose: This property specifies the offset which is in use in this + time zone observance. + + Value Type: UTC-OFFSET + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property MUST be specified in a "VTIMEZONE" + calendar component. + + Description: This property specifies the offset which is in use in + this time zone observance. It is used to calculate the absolute time + for the new observance. The property value is a signed numeric + indicating the number of hours and possibly minutes from UTC. + Positive numbers represent time zones east of the prime meridian, or + ahead of UTC. Negative numbers represent time zones west of the prime + meridian, or behind UTC. + + + +Dawson & Stenerson Standards Track [Page 100] + +RFC 2445 iCalendar November 1998 + + + Format Definition: The property is defined by the following notation: + + tzoffsetto = "TZOFFSETTO" toparam ":" utc-offset CRLF + + toparam = *(";" xparam) + + Example: The following are examples of this property: + + TZOFFSETTO:-0400 + + TZOFFSETTO:+1245 + +4.8.3.5 Time Zone URL + + Property Name: TZURL + + Purpose: The TZURL provides a means for a VTIMEZONE component to + point to a network location that can be used to retrieve an up-to- + date version of itself. + + Value Type: URI + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property can be specified in a "VTIMEZONE" calendar + component. + + Description: The TZURL provides a means for a VTIMEZONE component to + point to a network location that can be used to retrieve an up-to- + date version of itself. This provides a hook to handle changes + government bodies impose upon time zone definitions. Retrieval of + this resource results in an iCalendar object containing a single + VTIMEZONE component and a METHOD property set to PUBLISH. + + Format Definition: The property is defined by the following notation: + + tzurl = "TZURL" tzurlparam ":" uri CRLF + + tzurlparam = *(";" xparam) + + Example: The following is an example of this property: + + TZURL:http://timezones.r.us.net/tz/US-California-Los_Angeles + + + + + + + +Dawson & Stenerson Standards Track [Page 101] + +RFC 2445 iCalendar November 1998 + + +4.8.4 Relationship Component Properties + + The following properties specify relationship information in calendar + components. + +4.8.4.1 Attendee + + Property Name: ATTENDEE + + Purpose: The property defines an "Attendee" within a calendar + component. + + Value Type: CAL-ADDRESS + + Property Parameters: Non-standard, language, calendar user type, + group or list membership, participation role, participation status, + RSVP expectation, delegatee, delegator, sent by, common name or + directory entry reference property parameters can be specified on + this property. + + Conformance: This property MUST be specified in an iCalendar object + that specifies a group scheduled calendar entity. This property MUST + NOT be specified in an iCalendar object when publishing the calendar + information (e.g., NOT in an iCalendar object that specifies the + publication of a calendar user's busy time, event, to-do or journal). + This property is not specified in an iCalendar object that specifies + only a time zone definition or that defines calendar entities that + are not group scheduled entities, but are entities only on a single + user's calendar. + + Description: The property MUST only be specified within calendar + components to specify participants, non-participants and the chair of + a group scheduled calendar entity. The property is specified within + an "EMAIL" category of the "VALARM" calendar component to specify an + email address that is to receive the email type of iCalendar alarm. + + The property parameter CN is for the common or displayable name + associated with the calendar address; ROLE, for the intended role + that the attendee will have in the calendar component; PARTSTAT, for + the status of the attendee's participation; RSVP, for indicating + whether the favor of a reply is requested; CUTYPE, to indicate the + type of calendar user; MEMBER, to indicate the groups that the + attendee belongs to; DELEGATED-TO, to indicate the calendar users + that the original request was delegated to; and DELEGATED-FROM, to + indicate whom the request was delegated from; SENT-BY, to indicate + whom is acting on behalf of the ATTENDEE; and DIR, to indicate the + URI that points to the directory information corresponding to the + attendee. These property parameters can be specified on an "ATTENDEE" + + + +Dawson & Stenerson Standards Track [Page 102] + +RFC 2445 iCalendar November 1998 + + + property in either a "VEVENT", "VTODO" or "VJOURNAL" calendar + component. They MUST not be specified in an "ATTENDEE" property in a + "VFREEBUSY" or "VALARM" calendar component. If the LANGUAGE property + parameter is specified, the identified language applies to the CN + parameter. + + A recipient delegated a request MUST inherit the RSVP and ROLE values + from the attendee that delegated the request to them. + + Multiple attendees can be specified by including multiple "ATTENDEE" + properties within the calendar component. + + Format Definition: The property is defined by the following notation: + + attendee = "ATTENDEE" attparam ":" cal-address CRLF + + attparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" cutypeparam) / (";"memberparam) / + (";" roleparam) / (";" partstatparam) / + (";" rsvpparam) / (";" deltoparam) / + (";" delfromparam) / (";" sentbyparam) / + (";"cnparam) / (";" dirparam) / + (";" languageparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + Example: The following are examples of this property's use for a to- + do: + + ORGANIZER:MAILTO:jsmith@host1.com + ATTENDEE;MEMBER="MAILTO:DEV-GROUP@host2.com": + MAILTO:joecool@host2.com + ATTENDEE;DELEGATED-FROM="MAILTO:immud@host3.com": + MAILTO:ildoit@host1.com + + The following is an example of this property used for specifying + multiple attendees to an event: + + + + + +Dawson & Stenerson Standards Track [Page 103] + +RFC 2445 iCalendar November 1998 + + + ORGANIZER:MAILTO:jsmith@host1.com + ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;CN=Henry Cabot + :MAILTO:hcabot@host2.com + ATTENDEE;ROLE=REQ-PARTICIPANT;DELEGATED-FROM="MAILTO:bob@host.com" + ;PARTSTAT=ACCEPTED;CN=Jane Doe:MAILTO:jdoe@host1.com + + The following is an example of this property with a URI to the + directory information associated with the attendee: + + ATTENDEE;CN=John Smith;DIR="ldap://host.com:6666/o=eDABC% + 20Industries,c=3DUS??(cn=3DBJim%20Dolittle)":MAILTO:jimdo@ + host1.com + + The following is an example of this property with "delegatee" and + "delegator" information for an event: + + ORGANIZER;CN=John Smith:MAILTO:jsmith@host.com + ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=TENTATIVE;DELEGATED-FROM= + "MAILTO:iamboss@host2.com";CN=Henry Cabot:MAILTO:hcabot@ + host2.com + ATTENDEE;ROLE=NON-PARTICIPANT;PARTSTAT=DELEGATED;DELEGATED-TO= + "MAILTO:hcabot@host2.com";CN=The Big Cheese:MAILTO:iamboss + @host2.com + ATTENDEE;ROLE=REQ-PARTICIPANT;PARTSTAT=ACCEPTED;CN=Jane Doe + :MAILTO:jdoe@host1.com + + Example: The following is an example of this property's use when + another calendar user is acting on behalf of the "Attendee": + + ATTENDEE;SENT-BY=MAILTO:jan_doe@host1.com;CN=John Smith:MAILTO: + jsmith@host1.com + +4.8.4.2 Contact + + Property Name: CONTACT + + Purpose: The property is used to represent contact information or + alternately a reference to contact information associated with the + calendar component. + + Value Type: TEXT + + Property Parameters: Non-standard, alternate text representation and + language property parameters can be specified on this property. + + Conformance: The property can be specified in a "VEVENT", "VTODO", + "VJOURNAL" or "VFREEBUSY" calendar component. + + + + +Dawson & Stenerson Standards Track [Page 104] + +RFC 2445 iCalendar November 1998 + + + Description: The property value consists of textual contact + information. An alternative representation for the property value can + also be specified that refers to a URI pointing to an alternate form, + such as a vCard [RFC 2426], for the contact information. + + Format Definition: The property is defined by the following notation: + + contact = "CONTACT" contparam ":" text CRLF + + contparam = *( + ; the following are optional, + ; but MUST NOT occur more than once + + (";" altrepparam) / (";" languageparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + Example: The following is an example of this property referencing + textual contact information: + + CONTACT:Jim Dolittle\, ABC Industries\, +1-919-555-1234 + + The following is an example of this property with an alternate + representation of a LDAP URI to a directory entry containing the + contact information: + + CONTACT;ALTREP="ldap://host.com:6666/o=3DABC%20Industries\, + c=3DUS??(cn=3DBJim%20Dolittle)":Jim Dolittle\, ABC Industries\, + +1-919-555-1234 + + The following is an example of this property with an alternate + representation of a MIME body part containing the contact + information, such as a vCard [RFC 2426] embedded in a [MIME-DIR] + content-type: + + CONTACT;ALTREP="CID=":Jim + Dolittle\, ABC Industries\, +1-919-555-1234 + + The following is an example of this property referencing a network + resource, such as a vCard [RFC 2426] object containing the contact + information: + + + + + +Dawson & Stenerson Standards Track [Page 105] + +RFC 2445 iCalendar November 1998 + + + CONTACT;ALTREP="http://host.com/pdi/jdoe.vcf":Jim + Dolittle\, ABC Industries\, +1-919-555-1234 + +4.8.4.3 Organizer + + Property Name: ORGANIZER + + Purpose: The property defines the organizer for a calendar component. + + Value Type: CAL-ADDRESS + + Property Parameters: Non-standard, language, common name, directory + entry reference, sent by property parameters can be specified on this + property. + + Conformance: This property MUST be specified in an iCalendar object + that specifies a group scheduled calendar entity. This property MUST + be specified in an iCalendar object that specifies the publication of + a calendar user's busy time. This property MUST NOT be specified in + an iCalendar object that specifies only a time zone definition or + that defines calendar entities that are not group scheduled entities, + but are entities only on a single user's calendar. + + Description: The property is specified within the "VEVENT", "VTODO", + "VJOURNAL calendar components to specify the organizer of a group + scheduled calendar entity. The property is specified within the + "VFREEBUSY" calendar component to specify the calendar user + requesting the free or busy time. When publishing a "VFREEBUSY" + calendar component, the property is used to specify the calendar that + the published busy time came from. + + The property has the property parameters CN, for specifying the + common or display name associated with the "Organizer", DIR, for + specifying a pointer to the directory information associated with the + "Organizer", SENT-BY, for specifying another calendar user that is + acting on behalf of the "Organizer". The non-standard parameters may + also be specified on this property. If the LANGUAGE property + parameter is specified, the identified language applies to the CN + parameter value. + + Format Definition: The property is defined by the following notation: + + organizer = "ORGANIZER" orgparam ":" + cal-address CRLF + + orgparam = *( + + ; the following are optional, + + + +Dawson & Stenerson Standards Track [Page 106] + +RFC 2445 iCalendar November 1998 + + + ; but MUST NOT occur more than once + + (";" cnparam) / (";" dirparam) / (";" sentbyparam) / + (";" languageparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + Example: The following is an example of this property: + + ORGANIZER;CN=John Smith:MAILTO:jsmith@host1.com + + The following is an example of this property with a pointer to the + directory information associated with the organizer: + + ORGANIZER;CN=JohnSmith;DIR="ldap://host.com:6666/o=3DDC%20Associ + ates,c=3DUS??(cn=3DJohn%20Smith)":MAILTO:jsmith@host1.com + + The following is an example of this property used by another calendar + user who is acting on behalf of the organizer, with responses + intended to be sent back to the organizer, not the other calendar + user: + + ORGANIZER;SENT-BY="MAILTO:jane_doe@host.com": + MAILTO:jsmith@host1.com + +4.8.4.4 Recurrence ID + + Property Name: RECURRENCE-ID + + Purpose: This property is used in conjunction with the "UID" and + "SEQUENCE" property to identify a specific instance of a recurring + "VEVENT", "VTODO" or "VJOURNAL" calendar component. The property + value is the effective value of the "DTSTART" property of the + recurrence instance. + + Value Type: The default value type for this property is DATE-TIME. + The time format can be any of the valid forms defined for a DATE-TIME + value type. See DATE-TIME value type definition for specific + interpretations of the various forms. The value type can be set to + DATE. + + + + + + +Dawson & Stenerson Standards Track [Page 107] + +RFC 2445 iCalendar November 1998 + + + Property Parameters: Non-standard property, value data type, time + zone identifier and recurrence identifier range parameters can be + specified on this property. + + Conformance: This property can be specified in an iCalendar object + containing a recurring calendar component. + + Description: The full range of calendar components specified by a + recurrence set is referenced by referring to just the "UID" property + value corresponding to the calendar component. The "RECURRENCE-ID" + property allows the reference to an individual instance within the + recurrence set. + + If the value of the "DTSTART" property is a DATE type value, then the + value MUST be the calendar date for the recurrence instance. + + The date/time value is set to the time when the original recurrence + instance would occur; meaning that if the intent is to change a + Friday meeting to Thursday, the date/time is still set to the + original Friday meeting. + + The "RECURRENCE-ID" property is used in conjunction with the "UID" + and "SEQUENCE" property to identify a particular instance of a + recurring event, to-do or journal. For a given pair of "UID" and + "SEQUENCE" property values, the "RECURRENCE-ID" value for a + recurrence instance is fixed. When the definition of the recurrence + set for a calendar component changes, and hence the "SEQUENCE" + property value changes, the "RECURRENCE-ID" for a given recurrence + instance might also change.The "RANGE" parameter is used to specify + the effective range of recurrence instances from the instance + specified by the "RECURRENCE-ID" property value. The default value + for the range parameter is the single recurrence instance only. The + value can also be "THISANDPRIOR" to indicate a range defined by the + given recurrence instance and all prior instances or the value can be + "THISANDFUTURE" to indicate a range defined by the given recurrence + instance and all subsequent instances. + + Format Definition: The property is defined by the following notation: + + recurid = "RECURRENCE-ID" ridparam ":" ridval CRLF + + ridparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" "VALUE" "=" ("DATE-TIME" / "DATE)) / + (";" tzidparam) / (";" rangeparam) / + + + +Dawson & Stenerson Standards Track [Page 108] + +RFC 2445 iCalendar November 1998 + + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + ridval = date-time / date + ;Value MUST match value type + + Example: The following are examples of this property: + + RECURRENCE-ID;VALUE=DATE:19960401 + + RECURRENCE-ID;RANGE=THISANDFUTURE:19960120T120000Z + +4.8.4.5 Related To + + Property Name: RELATED-TO + + Purpose: The property is used to represent a relationship or + reference between one calendar component and another. + + Value Type: TEXT + + Property Parameters: Non-standard and relationship type property + parameters can be specified on this property. + + Conformance: The property can be specified one or more times in the + "VEVENT", "VTODO" or "VJOURNAL" calendar components. + + Description: The property value consists of the persistent, globally + unique identifier of another calendar component. This value would be + represented in a calendar component by the "UID" property. + + By default, the property value points to another calendar component + that has a PARENT relationship to the referencing object. The + "RELTYPE" property parameter is used to either explicitly state the + default PARENT relationship type to the referenced calendar component + or to override the default PARENT relationship type and specify + either a CHILD or SIBLING relationship. The PARENT relationship + indicates that the calendar component is a subordinate of the + referenced calendar component. The CHILD relationship indicates that + the calendar component is a superior of the referenced calendar + component. The SIBLING relationship indicates that the calendar + component is a peer of the referenced calendar component. + + + + + +Dawson & Stenerson Standards Track [Page 109] + +RFC 2445 iCalendar November 1998 + + + Changes to a calendar component referenced by this property can have + an implicit impact on the related calendar component. For example, if + a group event changes its start or end date or time, then the + related, dependent events will need to have their start and end dates + changed in a corresponding way. Similarly, if a PARENT calendar + component is canceled or deleted, then there is an implied impact to + the related CHILD calendar components. This property is intended only + to provide information on the relationship of calendar components. It + is up to the target calendar system to maintain any property + implications of this relationship. + + Format Definition: The property is defined by the following notation: + + related = "RELATED-TO" [relparam] ":" text CRLF + + relparam = *( + + ; the following is optional, + ; but MUST NOT occur more than once + + (";" reltypeparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparm) + + ) + + The following is an example of this property: + + RELATED-TO: + + RELATED-TO:<19960401-080045-4000F192713-0052@host1.com> + +4.8.4.6 Uniform Resource Locator + + Property Name: URL + + Purpose: This property defines a Uniform Resource Locator (URL) + associated with the iCalendar object. + + Value Type: URI + + Property Parameters: Non-standard property parameters can be + specified on this property. + + + + + +Dawson & Stenerson Standards Track [Page 110] + +RFC 2445 iCalendar November 1998 + + + Conformance: This property can be specified once in the "VEVENT", + "VTODO", "VJOURNAL" or "VFREEBUSY" calendar components. + + Description: This property may be used in a calendar component to + convey a location where a more dynamic rendition of the calendar + information associated with the calendar component can be found. This + memo does not attempt to standardize the form of the URI, nor the + format of the resource pointed to by the property value. If the URL + property and Content-Location MIME header are both specified, they + MUST point to the same resource. + + Format Definition: The property is defined by the following notation: + + url = "URL" urlparam ":" uri CRLF + + urlparam = *(";" xparam) + + Example: The following is an example of this property: + + URL:http://abc.com/pub/calendars/jsmith/mytime.ics + +4.8.4.7 Unique Identifier + + Property Name: UID + + Purpose: This property defines the persistent, globally unique + identifier for the calendar component. + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: The property MUST be specified in the "VEVENT", "VTODO", + "VJOURNAL" or "VFREEBUSY" calendar components. + + Description: The UID itself MUST be a globally unique identifier. The + generator of the identifier MUST guarantee that the identifier is + unique. There are several algorithms that can be used to accomplish + this. The identifier is RECOMMENDED to be the identical syntax to the + [RFC 822] addr-spec. A good method to assure uniqueness is to put the + domain name or a domain literal IP address of the host on which the + identifier was created on the right hand side of the "@", and on the + left hand side, put a combination of the current calendar date and + time of day (i.e., formatted in as a DATE-TIME value) along with some + other currently unique (perhaps sequential) identifier available on + the system (for example, a process id number). Using a date/time + value on the left hand side and a domain name or domain literal on + + + +Dawson & Stenerson Standards Track [Page 111] + +RFC 2445 iCalendar November 1998 + + + the right hand side makes it possible to guarantee uniqueness since + no two hosts should be using the same domain name or IP address at + the same time. Though other algorithms will work, it is RECOMMENDED + that the right hand side contain some domain identifier (either of + the host itself or otherwise) such that the generator of the message + identifier can guarantee the uniqueness of the left hand side within + the scope of that domain. + + This is the method for correlating scheduling messages with the + referenced "VEVENT", "VTODO", or "VJOURNAL" calendar component. + + The full range of calendar components specified by a recurrence set + is referenced by referring to just the "UID" property value + corresponding to the calendar component. The "RECURRENCE-ID" property + allows the reference to an individual instance within the recurrence + set. + + This property is an important method for group scheduling + applications to match requests with later replies, modifications or + deletion requests. Calendaring and scheduling applications MUST + generate this property in "VEVENT", "VTODO" and "VJOURNAL" calendar + components to assure interoperability with other group scheduling + applications. This identifier is created by the calendar system that + generates an iCalendar object. + + Implementations MUST be able to receive and persist values of at + least 255 characters for this property. + + Format Definition: The property is defined by the following notation: + + uid = "UID" uidparam ":" text CRLF + + uidparam = *(";" xparam) + + Example: The following is an example of this property: + + UID:19960401T080045Z-4000F192713-0052@host1.com + +4.8.5 Recurrence Component Properties + + The following properties specify recurrence information in calendar + components. + +4.8.5.1 Exception Date/Times + + Property Name: EXDATE + + + + + +Dawson & Stenerson Standards Track [Page 112] + +RFC 2445 iCalendar November 1998 + + + Purpose: This property defines the list of date/time exceptions for a + recurring calendar component. + + Value Type: The default value type for this property is DATE-TIME. + The value type can be set to DATE. + + Property Parameters: Non-standard, value data type and time zone + identifier property parameters can be specified on this property. + + Conformance: This property can be specified in an iCalendar object + that includes a recurring calendar component. + + Description: The exception dates, if specified, are used in computing + the recurrence set. The recurrence set is the complete set of + recurrence instances for a calendar component. The recurrence set is + generated by considering the initial "DTSTART" property along with + the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained + within the iCalendar object. The "DTSTART" property defines the first + instance in the recurrence set. Multiple instances of the "RRULE" and + "EXRULE" properties can also be specified to define more + sophisticated recurrence sets. The final recurrence set is generated + by gathering all of the start date-times generated by any of the + specified "RRULE" and "RDATE" properties, and then excluding any + start date and times which fall within the union of start date and + times generated by any specified "EXRULE" and "EXDATE" properties. + This implies that start date and times within exclusion related + properties (i.e., "EXDATE" and "EXRULE") take precedence over those + specified by inclusion properties (i.e., "RDATE" and "RRULE"). Where + duplicate instances are generated by the "RRULE" and "RDATE" + properties, only one recurrence is considered. Duplicate instances + are ignored. + + The "EXDATE" property can be used to exclude the value specified in + "DTSTART". However, in such cases the original "DTSTART" date MUST + still be maintained by the calendaring and scheduling system because + the original "DTSTART" value has inherent usage dependencies by other + properties such as the "RECURRENCE-ID". + + Format Definition: The property is defined by the following notation: + + exdate = "EXDATE" exdtparam ":" exdtval *("," exdtval) CRLF + + exdtparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" "VALUE" "=" ("DATE-TIME" / "DATE")) / + + + +Dawson & Stenerson Standards Track [Page 113] + +RFC 2445 iCalendar November 1998 + + + (";" tzidparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + exdtval = date-time / date + ;Value MUST match value type + + Example: The following is an example of this property: + + EXDATE:19960402T010000Z,19960403T010000Z,19960404T010000Z + +4.8.5.2 Exception Rule + + Property Name: EXRULE + + Purpose: This property defines a rule or repeating pattern for an + exception to a recurrence set. + + Value Type: RECUR + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property can be specified in "VEVENT", "VTODO" or + "VJOURNAL" calendar components. + + Description: The exception rule, if specified, is used in computing + the recurrence set. The recurrence set is the complete set of + recurrence instances for a calendar component. The recurrence set is + generated by considering the initial "DTSTART" property along with + the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained + within the iCalendar object. The "DTSTART" defines the first instance + in the recurrence set. Multiple instances of the "RRULE" and "EXRULE" + properties can also be specified to define more sophisticated + recurrence sets. The final recurrence set is generated by gathering + all of the start date-times generated by any of the specified "RRULE" + and "RDATE" properties, and excluding any start date and times which + fall within the union of start date and times generated by any + specified "EXRULE" and "EXDATE" properties. This implies that start + date and times within exclusion related properties (i.e., "EXDATE" + and "EXRULE") take precedence over those specified by inclusion + + + + + +Dawson & Stenerson Standards Track [Page 114] + +RFC 2445 iCalendar November 1998 + + + properties (i.e., "RDATE" and "RRULE"). Where duplicate instances are + generated by the "RRULE" and "RDATE" properties, only one recurrence + is considered. Duplicate instances are ignored. + + The "EXRULE" property can be used to exclude the value specified in + "DTSTART". However, in such cases the original "DTSTART" date MUST + still be maintained by the calendaring and scheduling system because + the original "DTSTART" value has inherent usage dependencies by other + properties such as the "RECURRENCE-ID". + + Format Definition: The property is defined by the following notation: + + exrule = "EXRULE" exrparam ":" recur CRLF + + exrparam = *(";" xparam) + + Example: The following are examples of this property. Except every + other week, on Tuesday and Thursday for 4 occurrences: + + EXRULE:FREQ=WEEKLY;COUNT=4;INTERVAL=2;BYDAY=TU,TH + + Except daily for 10 occurrences: + + EXRULE:FREQ=DAILY;COUNT=10 + + Except yearly in June and July for 8 occurrences: + + EXRULE:FREQ=YEARLY;COUNT=8;BYMONTH=6,7 + +4.8.5.3 Recurrence Date/Times + + Property Name: RDATE + + Purpose: This property defines the list of date/times for a + recurrence set. + + Value Type: The default value type for this property is DATE-TIME. + The value type can be set to DATE or PERIOD. + + Property Parameters: Non-standard, value data type and time zone + identifier property parameters can be specified on this property. + + Conformance: The property can be specified in "VEVENT", "VTODO", + "VJOURNAL" or "VTIMEZONE" calendar components. + + + + + + + +Dawson & Stenerson Standards Track [Page 115] + +RFC 2445 iCalendar November 1998 + + + Description: This property can appear along with the "RRULE" property + to define an aggregate set of repeating occurrences. When they both + appear in an iCalendar object, the recurring events are defined by + the union of occurrences defined by both the "RDATE" and "RRULE". + + The recurrence dates, if specified, are used in computing the + recurrence set. The recurrence set is the complete set of recurrence + instances for a calendar component. The recurrence set is generated + by considering the initial "DTSTART" property along with the "RRULE", + "RDATE", "EXDATE" and "EXRULE" properties contained within the + iCalendar object. The "DTSTART" property defines the first instance + in the recurrence set. Multiple instances of the "RRULE" and "EXRULE" + properties can also be specified to define more sophisticated + recurrence sets. The final recurrence set is generated by gathering + all of the start date/times generated by any of the specified "RRULE" + and "RDATE" properties, and excluding any start date/times which fall + within the union of start date/times generated by any specified + "EXRULE" and "EXDATE" properties. This implies that start date/times + within exclusion related properties (i.e., "EXDATE" and "EXRULE") + take precedence over those specified by inclusion properties (i.e., + "RDATE" and "RRULE"). Where duplicate instances are generated by the + "RRULE" and "RDATE" properties, only one recurrence is considered. + Duplicate instances are ignored. + + Format Definition: The property is defined by the following notation: + + rdate = "RDATE" rdtparam ":" rdtval *("," rdtval) CRLF + + rdtparam = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" "VALUE" "=" ("DATE-TIME" / "DATE" / "PERIOD")) / + (";" tzidparam) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + rdtval = date-time / date / period + ;Value MUST match value type + + Example: The following are examples of this property: + + + + +Dawson & Stenerson Standards Track [Page 116] + +RFC 2445 iCalendar November 1998 + + + RDATE:19970714T123000Z + + RDATE;TZID=US-EASTERN:19970714T083000 + + RDATE;VALUE=PERIOD:19960403T020000Z/19960403T040000Z, + 19960404T010000Z/PT3H + + RDATE;VALUE=DATE:19970101,19970120,19970217,19970421 + 19970526,19970704,19970901,19971014,19971128,19971129,19971225 + +4.8.5.4 Recurrence Rule + + Property Name: RRULE + + Purpose: This property defines a rule or repeating pattern for + recurring events, to-dos, or time zone definitions. + + Value Type: RECUR + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property can be specified one or more times in + recurring "VEVENT", "VTODO" and "VJOURNAL" calendar components. It + can also be specified once in each STANDARD or DAYLIGHT sub-component + of the "VTIMEZONE" calendar component. + + Description: The recurrence rule, if specified, is used in computing + the recurrence set. The recurrence set is the complete set of + recurrence instances for a calendar component. The recurrence set is + generated by considering the initial "DTSTART" property along with + the "RRULE", "RDATE", "EXDATE" and "EXRULE" properties contained + within the iCalendar object. The "DTSTART" property defines the first + instance in the recurrence set. Multiple instances of the "RRULE" and + "EXRULE" properties can also be specified to define more + sophisticated recurrence sets. The final recurrence set is generated + by gathering all of the start date/times generated by any of the + specified "RRULE" and "RDATE" properties, and excluding any start + date/times which fall within the union of start date/times generated + by any specified "EXRULE" and "EXDATE" properties. This implies that + start date/times within exclusion related properties (i.e., "EXDATE" + and "EXRULE") take precedence over those specified by inclusion + properties (i.e., "RDATE" and "RRULE"). Where duplicate instances are + generated by the "RRULE" and "RDATE" properties, only one recurrence + is considered. Duplicate instances are ignored. + + + + + + +Dawson & Stenerson Standards Track [Page 117] + +RFC 2445 iCalendar November 1998 + + + The "DTSTART" and "DTEND" property pair or "DTSTART" and "DURATION" + property pair, specified within the iCalendar object defines the + first instance of the recurrence. When used with a recurrence rule, + the "DTSTART" and "DTEND" properties MUST be specified in local time + and the appropriate set of "VTIMEZONE" calendar components MUST be + included. For detail on the usage of the "VTIMEZONE" calendar + component, see the "VTIMEZONE" calendar component definition. + + Any duration associated with the iCalendar object applies to all + members of the generated recurrence set. Any modified duration for + specific recurrences MUST be explicitly specified using the "RDATE" + property. + + Format Definition: This property is defined by the following + notation: + + rrule = "RRULE" rrulparam ":" recur CRLF + + rrulparam = *(";" xparam) + + Example: All examples assume the Eastern United States time zone. + + Daily for 10 occurrences: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=DAILY;COUNT=10 + + ==> (1997 9:00 AM EDT)September 2-11 + + Daily until December 24, 1997: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=DAILY;UNTIL=19971224T000000Z + + ==> (1997 9:00 AM EDT)September 2-30;October 1-25 + (1997 9:00 AM EST)October 26-31;November 1-30;December 1-23 + + Every other day - forever: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=DAILY;INTERVAL=2 + ==> (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30; + October 2,4,6...20,22,24 + (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29; + Dec 1,3,... + + Every 10 days, 5 occurrences: + + + + +Dawson & Stenerson Standards Track [Page 118] + +RFC 2445 iCalendar November 1998 + + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 + + ==> (1997 9:00 AM EDT)September 2,12,22;October 2,12 + + Everyday in January, for 3 years: + + DTSTART;TZID=US-Eastern:19980101T090000 + RRULE:FREQ=YEARLY;UNTIL=20000131T090000Z; + BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA + or + RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1 + + ==> (1998 9:00 AM EDT)January 1-31 + (1999 9:00 AM EDT)January 1-31 + (2000 9:00 AM EDT)January 1-31 + + Weekly for 10 occurrences + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=WEEKLY;COUNT=10 + + ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21 + (1997 9:00 AM EST)October 28;November 4 + + Weekly until December 24, 1997 + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z + + ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21 + (1997 9:00 AM EST)October 28;November 4,11,18,25; + December 2,9,16,23 + Every other week - forever: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU + + ==> (1997 9:00 AM EDT)September 2,16,30;October 14 + (1997 9:00 AM EST)October 28;November 11,25;December 9,23 + (1998 9:00 AM EST)January 6,20;February + ... + + Weekly on Tuesday and Thursday for 5 weeks: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH + or + + + +Dawson & Stenerson Standards Track [Page 119] + +RFC 2445 iCalendar November 1998 + + + RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH + + ==> (1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25,30;October 2 + + Every other week on Monday, Wednesday and Friday until December 24, + 1997, but starting on Tuesday, September 2, 1997: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU; + BYDAY=MO,WE,FR + ==> (1997 9:00 AM EDT)September 2,3,5,15,17,19,29;October + 1,3,13,15,17 + (1997 9:00 AM EST)October 27,29,31;November 10,12,14,24,26,28; + December 8,10,12,22 + + Every other week on Tuesday and Thursday, for 8 occurrences: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH + + ==> (1997 9:00 AM EDT)September 2,4,16,18,30;October 2,14,16 + + Monthly on the 1st Friday for ten occurrences: + + DTSTART;TZID=US-Eastern:19970905T090000 + RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR + + ==> (1997 9:00 AM EDT)September 5;October 3 + (1997 9:00 AM EST)November 7;Dec 5 + (1998 9:00 AM EST)January 2;February 6;March 6;April 3 + (1998 9:00 AM EDT)May 1;June 5 + + Monthly on the 1st Friday until December 24, 1997: + + DTSTART;TZID=US-Eastern:19970905T090000 + RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR + + ==> (1997 9:00 AM EDT)September 5;October 3 + (1997 9:00 AM EST)November 7;December 5 + + Every other month on the 1st and last Sunday of the month for 10 + occurrences: + + DTSTART;TZID=US-Eastern:19970907T090000 + RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU + + ==> (1997 9:00 AM EDT)September 7,28 + (1997 9:00 AM EST)November 2,30 + + + +Dawson & Stenerson Standards Track [Page 120] + +RFC 2445 iCalendar November 1998 + + + (1998 9:00 AM EST)January 4,25;March 1,29 + (1998 9:00 AM EDT)May 3,31 + + Monthly on the second to last Monday of the month for 6 months: + + DTSTART;TZID=US-Eastern:19970922T090000 + RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO + + ==> (1997 9:00 AM EDT)September 22;October 20 + (1997 9:00 AM EST)November 17;December 22 + (1998 9:00 AM EST)January 19;February 16 + + Monthly on the third to the last day of the month, forever: + + DTSTART;TZID=US-Eastern:19970928T090000 + RRULE:FREQ=MONTHLY;BYMONTHDAY=-3 + + ==> (1997 9:00 AM EDT)September 28 + (1997 9:00 AM EST)October 29;November 28;December 29 + (1998 9:00 AM EST)January 29;February 26 + ... + + Monthly on the 2nd and 15th of the month for 10 occurrences: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15 + + ==> (1997 9:00 AM EDT)September 2,15;October 2,15 + (1997 9:00 AM EST)November 2,15;December 2,15 + (1998 9:00 AM EST)January 2,15 + + Monthly on the first and last day of the month for 10 occurrences: + + DTSTART;TZID=US-Eastern:19970930T090000 + RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1 + + ==> (1997 9:00 AM EDT)September 30;October 1 + (1997 9:00 AM EST)October 31;November 1,30;December 1,31 + (1998 9:00 AM EST)January 1,31;February 1 + + Every 18 months on the 10th thru 15th of the month for 10 + occurrences: + + DTSTART;TZID=US-Eastern:19970910T090000 + RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14, + 15 + + ==> (1997 9:00 AM EDT)September 10,11,12,13,14,15 + + + +Dawson & Stenerson Standards Track [Page 121] + +RFC 2445 iCalendar November 1998 + + + (1999 9:00 AM EST)March 10,11,12,13 + + Every Tuesday, every other month: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU + + ==> (1997 9:00 AM EDT)September 2,9,16,23,30 + (1997 9:00 AM EST)November 4,11,18,25 + (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31 + ... + + Yearly in June and July for 10 occurrences: + + DTSTART;TZID=US-Eastern:19970610T090000 + RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7 + ==> (1997 9:00 AM EDT)June 10;July 10 + (1998 9:00 AM EDT)June 10;July 10 + (1999 9:00 AM EDT)June 10;July 10 + (2000 9:00 AM EDT)June 10;July 10 + (2001 9:00 AM EDT)June 10;July 10 + Note: Since none of the BYDAY, BYMONTHDAY or BYYEARDAY components + are specified, the day is gotten from DTSTART + + Every other year on January, February, and March for 10 occurrences: + + DTSTART;TZID=US-Eastern:19970310T090000 + RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3 + + ==> (1997 9:00 AM EST)March 10 + (1999 9:00 AM EST)January 10;February 10;March 10 + (2001 9:00 AM EST)January 10;February 10;March 10 + (2003 9:00 AM EST)January 10;February 10;March 10 + + Every 3rd year on the 1st, 100th and 200th day for 10 occurrences: + + DTSTART;TZID=US-Eastern:19970101T090000 + RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200 + + ==> (1997 9:00 AM EST)January 1 + (1997 9:00 AM EDT)April 10;July 19 + (2000 9:00 AM EST)January 1 + (2000 9:00 AM EDT)April 9;July 18 + (2003 9:00 AM EST)January 1 + (2003 9:00 AM EDT)April 10;July 19 + (2006 9:00 AM EST)January 1 + + Every 20th Monday of the year, forever: + + + +Dawson & Stenerson Standards Track [Page 122] + +RFC 2445 iCalendar November 1998 + + + DTSTART;TZID=US-Eastern:19970519T090000 + RRULE:FREQ=YEARLY;BYDAY=20MO + + ==> (1997 9:00 AM EDT)May 19 + (1998 9:00 AM EDT)May 18 + (1999 9:00 AM EDT)May 17 + ... + + Monday of week number 20 (where the default start of the week is + Monday), forever: + + DTSTART;TZID=US-Eastern:19970512T090000 + RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO + + ==> (1997 9:00 AM EDT)May 12 + (1998 9:00 AM EDT)May 11 + (1999 9:00 AM EDT)May 17 + ... + + Every Thursday in March, forever: + + DTSTART;TZID=US-Eastern:19970313T090000 + RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH + + ==> (1997 9:00 AM EST)March 13,20,27 + (1998 9:00 AM EST)March 5,12,19,26 + (1999 9:00 AM EST)March 4,11,18,25 + ... + + Every Thursday, but only during June, July, and August, forever: + + DTSTART;TZID=US-Eastern:19970605T090000 + RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8 + + ==> (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31; + August 7,14,21,28 + (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30; + August 6,13,20,27 + (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29; + August 5,12,19,26 + ... + + Every Friday the 13th, forever: + + DTSTART;TZID=US-Eastern:19970902T090000 + EXDATE;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13 + + + + +Dawson & Stenerson Standards Track [Page 123] + +RFC 2445 iCalendar November 1998 + + + ==> (1998 9:00 AM EST)February 13;March 13;November 13 + (1999 9:00 AM EDT)August 13 + (2000 9:00 AM EDT)October 13 + ... + + The first Saturday that follows the first Sunday of the month, + forever: + + DTSTART;TZID=US-Eastern:19970913T090000 + RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13 + + ==> (1997 9:00 AM EDT)September 13;October 11 + (1997 9:00 AM EST)November 8;December 13 + (1998 9:00 AM EST)January 10;February 7;March 7 + (1998 9:00 AM EDT)April 11;May 9;June 13... + ... + + Every four years, the first Tuesday after a Monday in November, + forever (U.S. Presidential Election day): + + DTSTART;TZID=US-Eastern:19961105T090000 + RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4, + 5,6,7,8 + + ==> (1996 9:00 AM EST)November 5 + (2000 9:00 AM EST)November 7 + (2004 9:00 AM EST)November 2 + ... + + The 3rd instance into the month of one of Tuesday, Wednesday or + Thursday, for the next 3 months: + + DTSTART;TZID=US-Eastern:19970904T090000 + RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3 + + ==> (1997 9:00 AM EDT)September 4;October 7 + (1997 9:00 AM EST)November 6 + + The 2nd to last weekday of the month: + + DTSTART;TZID=US-Eastern:19970929T090000 + RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2 + + ==> (1997 9:00 AM EDT)September 29 + (1997 9:00 AM EST)October 30;November 27;December 30 + (1998 9:00 AM EST)January 29;February 26;March 30 + ... + + + + +Dawson & Stenerson Standards Track [Page 124] + +RFC 2445 iCalendar November 1998 + + + Every 3 hours from 9:00 AM to 5:00 PM on a specific day: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z + + ==> (September 2, 1997 EDT)09:00,12:00,15:00 + + Every 15 minutes for 6 occurrences: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6 + + ==> (September 2, 1997 EDT)09:00,09:15,09:30,09:45,10:00,10:15 + + Every hour and a half for 4 occurrences: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4 + + ==> (September 2, 1997 EDT)09:00,10:30;12:00;13:30 + + Every 20 minutes from 9:00 AM to 4:40 PM every day: + + DTSTART;TZID=US-Eastern:19970902T090000 + RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40 + or + RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16 + + ==> (September 2, 1997 EDT)9:00,9:20,9:40,10:00,10:20, + ... 16:00,16:20,16:40 + (September 3, 1997 EDT)9:00,9:20,9:40,10:00,10:20, + ...16:00,16:20,16:40 + ... + + An example where the days generated makes a difference because of + WKST: + + DTSTART;TZID=US-Eastern:19970805T090000 + RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO + + ==> (1997 EDT)Aug 5,10,19,24 + + changing only WKST from MO to SU, yields different results... + + DTSTART;TZID=US-Eastern:19970805T090000 + RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU + ==> (1997 EDT)August 5,17,19,31 + + + + +Dawson & Stenerson Standards Track [Page 125] + +RFC 2445 iCalendar November 1998 + + +4.8.6 Alarm Component Properties + + The following properties specify alarm information in calendar + components. + +4.8.6.1 Action + + Property Name: ACTION + + Purpose: This property defines the action to be invoked when an alarm + is triggered. + + Value Type: TEXT + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property MUST be specified once in a "VALARM" + calendar component. + + Description: Each "VALARM" calendar component has a particular type + of action associated with it. This property specifies the type of + action + + Format Definition: The property is defined by the following notation: + + action = "ACTION" actionparam ":" actionvalue CRLF + + actionparam = *(";" xparam) + + actionvalue = "AUDIO" / "DISPLAY" / "EMAIL" / "PROCEDURE" + / iana-token / x-name + + Example: The following are examples of this property in a "VALARM" + calendar component: + + ACTION:AUDIO + + ACTION:DISPLAY + + ACTION:PROCEDURE + +4.8.6.2 Repeat Count + + Property Name: REPEAT + + Purpose: This property defines the number of time the alarm should be + repeated, after the initial trigger. + + + +Dawson & Stenerson Standards Track [Page 126] + +RFC 2445 iCalendar November 1998 + + + Value Type: INTEGER + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property can be specified in a "VALARM" calendar + component. + + Description: If the alarm triggers more than once, then this property + MUST be specified along with the "DURATION" property. + + Format Definition: The property is defined by the following notation: + + repeatcnt = "REPEAT" repparam ":" integer CRLF + ;Default is "0", zero. + + repparam = *(";" xparam) + + Example: The following is an example of this property for an alarm + that repeats 4 additional times with a 5 minute delay after the + initial triggering of the alarm: + + REPEAT:4 + DURATION:PT5M + +4.8.6.3 Trigger + + Property Name: TRIGGER + + Purpose: This property specifies when an alarm will trigger. + + Value Type: The default value type is DURATION. The value type can be + set to a DATE-TIME value type, in which case the value MUST specify a + UTC formatted DATE-TIME value. + + Property Parameters: Non-standard, value data type, time zone + identifier or trigger relationship property parameters can be + specified on this property. The trigger relationship property + parameter MUST only be specified when the value type is DURATION. + + Conformance: This property MUST be specified in the "VALARM" calendar + component. + + Description: Within the "VALARM" calendar component, this property + defines when the alarm will trigger. The default value type is + DURATION, specifying a relative time for the trigger of the alarm. + The default duration is relative to the start of an event or to-do + that the alarm is associated with. The duration can be explicitly set + + + +Dawson & Stenerson Standards Track [Page 127] + +RFC 2445 iCalendar November 1998 + + + to trigger from either the end or the start of the associated event + or to-do with the "RELATED" parameter. A value of START will set the + alarm to trigger off the start of the associated event or to-do. A + value of END will set the alarm to trigger off the end of the + associated event or to-do. + + Either a positive or negative duration may be specified for the + "TRIGGER" property. An alarm with a positive duration is triggered + after the associated start or end of the event or to-do. An alarm + with a negative duration is triggered before the associated start or + end of the event or to-do. + + The "RELATED" property parameter is not valid if the value type of + the property is set to DATE-TIME (i.e., for an absolute date and time + alarm trigger). If a value type of DATE-TIME is specified, then the + property value MUST be specified in the UTC time format. If an + absolute trigger is specified on an alarm for a recurring event or + to-do, then the alarm will only trigger for the specified absolute + date/time, along with any specified repeating instances. + + If the trigger is set relative to START, then the "DTSTART" property + MUST be present in the associated "VEVENT" or "VTODO" calendar + component. If an alarm is specified for an event with the trigger set + relative to the END, then the "DTEND" property or the "DSTART" and + "DURATION' properties MUST be present in the associated "VEVENT" + calendar component. If the alarm is specified for a to-do with a + trigger set relative to the END, then either the "DUE" property or + the "DSTART" and "DURATION' properties MUST be present in the + associated "VTODO" calendar component. + + Alarms specified in an event or to-do which is defined in terms of a + DATE value type will be triggered relative to 00:00:00 UTC on the + specified date. For example, if "DTSTART:19980205, then the duration + trigger will be relative to19980205T000000Z. + + Format Definition: The property is defined by the following notation: + + trigger = "TRIGGER" (trigrel / trigabs) + + trigrel = *( + + ; the following are optional, + ; but MUST NOT occur more than once + + (";" "VALUE" "=" "DURATION") / + (";" trigrelparam) / + + ; the following is optional, + + + +Dawson & Stenerson Standards Track [Page 128] + +RFC 2445 iCalendar November 1998 + + + ; and MAY occur more than once + + (";" xparam) + ) ":" dur-value + + trigabs = 1*( + + ; the following is REQUIRED, + ; but MUST NOT occur more than once + + (";" "VALUE" "=" "DATE-TIME") / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) ":" date-time + + Example: A trigger set 15 minutes prior to the start of the event or + to-do. + + TRIGGER:-P15M + + A trigger set 5 minutes after the end of the event or to-do. + + TRIGGER;RELATED=END:P5M + + A trigger set to an absolute date/time. + + TRIGGER;VALUE=DATE-TIME:19980101T050000Z + +4.8.7 Change Management Component Properties + + The following properties specify change management information in + calendar components. + +4.8.7.1 Date/Time Created + + Property Name: CREATED + + Purpose: This property specifies the date and time that the calendar + information was created by the calendar user agent in the calendar + store. + + Note: This is analogous to the creation date and time for a file + in the file system. + + + + +Dawson & Stenerson Standards Track [Page 129] + +RFC 2445 iCalendar November 1998 + + + Value Type: DATE-TIME + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: The property can be specified once in "VEVENT", "VTODO" + or "VJOURNAL" calendar components. + + Description: The date and time is a UTC value. + + Format Definition: The property is defined by the following notation: + + created = "CREATED" creaparam ":" date-time CRLF + + creaparam = *(";" xparam) + + Example: The following is an example of this property: + + CREATED:19960329T133000Z + +4.8.7.2 Date/Time Stamp + + Property Name: DTSTAMP + + Purpose: The property indicates the date/time that the instance of + the iCalendar object was created. + + Value Type: DATE-TIME + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property MUST be included in the "VEVENT", "VTODO", + "VJOURNAL" or "VFREEBUSY" calendar components. + + Description: The value MUST be specified in the UTC time format. + + This property is also useful to protocols such as [IMIP] that have + inherent latency issues with the delivery of content. This property + will assist in the proper sequencing of messages containing iCalendar + objects. + + This property is different than the "CREATED" and "LAST-MODIFIED" + properties. These two properties are used to specify when the + particular calendar data in the calendar store was created and last + modified. This is different than when the iCalendar object + representation of the calendar service information was created or + last modified. + + + +Dawson & Stenerson Standards Track [Page 130] + +RFC 2445 iCalendar November 1998 + + + Format Definition: The property is defined by the following notation: + + dtstamp = "DTSTAMP" stmparam ":" date-time CRLF + + stmparam = *(";" xparam) + + Example: + + DTSTAMP:19971210T080000Z + +4.8.7.3 Last Modified + + Property Name: LAST-MODIFIED + + Purpose: The property specifies the date and time that the + information associated with the calendar component was last revised + in the calendar store. + + Note: This is analogous to the modification date and time for a + file in the file system. + + Value Type: DATE-TIME + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: This property can be specified in the "EVENT", "VTODO", + "VJOURNAL" or "VTIMEZONE" calendar components. + + Description: The property value MUST be specified in the UTC time + format. + + Format Definition: The property is defined by the following notation: + + last-mod = "LAST-MODIFIED" lstparam ":" date-time CRLF + + lstparam = *(";" xparam) + + Example: The following is are examples of this property: + + LAST-MODIFIED:19960817T133000Z + +4.8.7.4 Sequence Number + + Property Name: SEQUENCE + + Purpose: This property defines the revision sequence number of the + calendar component within a sequence of revisions. + + + +Dawson & Stenerson Standards Track [Page 131] + +RFC 2445 iCalendar November 1998 + + + Value Type: integer + + Property Parameters: Non-standard property parameters can be + specified on this property. + + Conformance: The property can be specified in "VEVENT", "VTODO" or + "VJOURNAL" calendar component. + + Description: When a calendar component is created, its sequence + number is zero (US-ASCII decimal 48). It is monotonically incremented + by the "Organizer's" CUA each time the "Organizer" makes a + significant revision to the calendar component. When the "Organizer" + makes changes to one of the following properties, the sequence number + MUST be incremented: + + . "DTSTART" + + . "DTEND" + + . "DUE" + + . "RDATE" + + . "RRULE" + + . "EXDATE" + + . "EXRULE" + + . "STATUS" + + In addition, changes made by the "Organizer" to other properties can + also force the sequence number to be incremented. The "Organizer" CUA + MUST increment the sequence number when ever it makes changes to + properties in the calendar component that the "Organizer" deems will + jeopardize the validity of the participation status of the + "Attendees". For example, changing the location of a meeting from one + locale to another distant locale could effectively impact the + participation status of the "Attendees". + + The "Organizer" includes this property in an iCalendar object that it + sends to an "Attendee" to specify the current version of the calendar + component. + + The "Attendee" includes this property in an iCalendar object that it + sends to the "Organizer" to specify the version of the calendar + component that the "Attendee" is referring to. + + + + +Dawson & Stenerson Standards Track [Page 132] + +RFC 2445 iCalendar November 1998 + + + A change to the sequence number is not the mechanism that an + "Organizer" uses to request a response from the "Attendees". The + "RSVP" parameter on the "ATTENDEE" property is used by the + "Organizer" to indicate that a response from the "Attendees" is + requested. + + Format Definition: This property is defined by the following + notation: + + seq = "SEQUENCE" seqparam ":" integer CRLF + ; Default is "0" + + seqparam = *(";" xparam) + + Example: The following is an example of this property for a calendar + component that was just created by the "Organizer". + + SEQUENCE:0 + + The following is an example of this property for a calendar component + that has been revised two different times by the "Organizer". + + SEQUENCE:2 + +4.8.8 Miscellaneous Component Properties + + The following properties specify information about a number of + miscellaneous features of calendar components. + +4.8.8.1 Non-standard Properties + + Property Name: Any property name with a "X-" prefix + + Purpose: This class of property provides a framework for defining + non-standard properties. + + Value Type: TEXT + + Property Parameters: Non-standard and language property parameters + can be specified on this property. + + Conformance: This property can be specified in any calendar + component. + + Description: The MIME Calendaring and Scheduling Content Type + provides a "standard mechanism for doing non-standard things". This + extension support is provided for implementers to "push the envelope" + on the existing version of the memo. Extension properties are + + + +Dawson & Stenerson Standards Track [Page 133] + +RFC 2445 iCalendar November 1998 + + + specified by property and/or property parameter names that have the + prefix text of "X-" (the two character sequence: LATIN CAPITAL LETTER + X character followed by the HYPEN-MINUS character). It is recommended + that vendors concatenate onto this sentinel another short prefix text + to identify the vendor. This will facilitate readability of the + extensions and minimize possible collision of names between different + vendors. User agents that support this content type are expected to + be able to parse the extension properties and property parameters but + can ignore them. + + At present, there is no registration authority for names of extension + properties and property parameters. The data type for this property + is TEXT. Optionally, the data type can be any of the other valid data + types. + + Format Definition: The property is defined by the following notation: + + x-prop = x-name *(";" xparam) [";" languageparam] ":" text CRLF + ; Lines longer than 75 octets should be folded + + Example: The following might be the ABC vendor's extension for an + audio-clip form of subject property: + + X-ABC-MMSUBJ;X-ABC-MMSUBJTYPE=wave:http://load.noise.org/mysubj.wav + +4.8.8.2 Request Status + + Property Name: REQUEST-STATUS + + Purpose: This property defines the status code returned for a + scheduling request. + + Value Type: TEXT + + Property Parameters: Non-standard and language property parameters + can be specified on this property. + + Conformance: The property can be specified in "VEVENT", "VTODO", + "VJOURNAL" or "VFREEBUSY" calendar component. + + Description: This property is used to return status code information + related to the processing of an associated iCalendar object. The data + type for this property is TEXT. + + The value consists of a short return status component, a longer + return status description component, and optionally a status-specific + data component. The components of the value are separated by the + SEMICOLON character (US-ASCII decimal 59). + + + +Dawson & Stenerson Standards Track [Page 134] + +RFC 2445 iCalendar November 1998 + + + The short return status is a PERIOD character (US-ASCII decimal 46) + separated 3-tuple of integers. For example, "3.1.1". The successive + levels of integers provide for a successive level of status code + granularity. + + The following are initial classes for the return status code. + Individual iCalendar object methods will define specific return + status codes for these classes. In addition, other classes for the + return status code may be defined using the registration process + defined later in this memo. + + |==============+===============================================| + | Short Return | Longer Return Status Description | + | Status Code | | + |==============+===============================================| + | 1.xx | Preliminary success. This class of status | + | | of status code indicates that the request has | + | | request has been initially processed but that | + | | completion is pending. | + |==============+===============================================| + | 2.xx | Successful. This class of status code | + | | indicates that the request was completed | + | | successfuly. However, the exact status code | + | | can indicate that a fallback has been taken. | + |==============+===============================================| + | 3.xx | Client Error. This class of status code | + | | indicates that the request was not successful.| + | | The error is the result of either a syntax or | + | | a semantic error in the client formatted | + | | request. Request should not be retried until | + | | the condition in the request is corrected. | + |==============+===============================================| + | 4.xx | Scheduling Error. This class of status code | + | | indicates that the request was not successful.| + | | Some sort of error occurred within the | + | | calendaring and scheduling service, not | + | | directly related to the request itself. | + |==============+===============================================| + + Format Definition: The property is defined by the following notation: + + rstatus = "REQUEST-STATUS" rstatparam ":" + statcode ";" statdesc [";" extdata] + + rstatparam = *( + + ; the following is optional, + ; but MUST NOT occur more than once + + + +Dawson & Stenerson Standards Track [Page 135] + +RFC 2445 iCalendar November 1998 + + + (";" languageparm) / + + ; the following is optional, + ; and MAY occur more than once + + (";" xparam) + + ) + + statcode = 1*DIGIT *("." 1*DIGIT) + ;Hierarchical, numeric return status code + + statdesc = text + ;Textual status description + + extdata = text + ;Textual exception data. For example, the offending property + ;name and value or complete property line. + + Example: The following are some possible examples of this property. + The COMMA and SEMICOLON separator characters in the property value + are BACKSLASH character escaped because they appear in a text value. + + REQUEST-STATUS:2.0;Success + + REQUEST-STATUS:3.1;Invalid property value;DTSTART:96-Apr-01 + + REQUEST-STATUS:2.8; Success\, repeating event ignored. Scheduled + as a single event.;RRULE:FREQ=WEEKLY\;INTERVAL=2 + + REQUEST-STATUS:4.1;Event conflict. Date/time is busy. + + REQUEST-STATUS:3.7;Invalid calendar user;ATTENDEE: + MAILTO:jsmith@host.com + +5 iCalendar Object Examples + + The following examples are provided as an informational source of + illustrative iCalendar objects consistent with this content type. + + The following example specifies a three-day conference that begins at + 8:00 AM EDT, September 18, 1996 and end at 6:00 PM EDT, September 20, + 1996. + + BEGIN:VCALENDAR PRODID:-//xyz Corp//NONSGML PDA Calendar Verson + 1.0//EN VERSION:2.0 BEGIN:VEVENT DTSTAMP:19960704T120000Z + UID:uid1@host.com ORGANIZER:MAILTO:jsmith@host.com + DTSTART:19960918T143000Z DTEND:19960920T220000Z STATUS:CONFIRMED + + + +Dawson & Stenerson Standards Track [Page 136] + +RFC 2445 iCalendar November 1998 + + + CATEGORIES:CONFERENCE SUMMARY:Networld+Interop Conference + DESCRIPTION:Networld+Interop Conference + and Exhibit\nAtlanta World Congress Center\n + Atlanta, Georgia END:VEVENT END:VCALENDAR + + The following example specifies a group scheduled meeting that begin + at 8:30 AM EST on March 12, 1998 and end at 9:30 AM EST on March 12, + 1998. The "Organizer" has scheduled the meeting with one or more + calendar users in a group. A time zone specification for Eastern + United States has been specified. + + BEGIN:VCALENDAR + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VTIMEZONE + TZID:US-Eastern + BEGIN:STANDARD + DTSTART:19981025T020000 + RDATE:19981025T020000 + TZOFFSETFROM:-0400 + TZOFFSETTO:-0500 + TZNAME:EST + END:STANDARD + BEGIN:DAYLIGHT + DTSTART:19990404T020000 + RDATE:19990404T020000 + TZOFFSETFROM:-0500 + TZOFFSETTO:-0400 + TZNAME:EDT + END:DAYLIGHT + END:VTIMEZONE + BEGIN:VEVENT + DTSTAMP:19980309T231000Z + UID:guid-1.host1.com + ORGANIZER;ROLE=CHAIR:MAILTO:mrbig@host.com + ATTENDEE;RSVP=TRUE;ROLE=REQ-PARTICIPANT;CUTYPE=GROUP: + MAILTO:employee-A@host.com + DESCRIPTION:Project XYZ Review Meeting + CATEGORIES:MEETING + CLASS:PUBLIC + CREATED:19980309T130000Z + SUMMARY:XYZ Project Review + DTSTART;TZID=US-Eastern:19980312T083000 + DTEND;TZID=US-Eastern:19980312T093000 + LOCATION:1CP Conference Room 4350 + END:VEVENT + END:VCALENDAR + + + + +Dawson & Stenerson Standards Track [Page 137] + +RFC 2445 iCalendar November 1998 + + + The following is an example of an iCalendar object passed in a MIME + message with a single body part consisting of a "text/calendar" + Content Type. + + TO:jsmith@host1.com + FROM:jdoe@host1.com + MIME-VERSION:1.0 + MESSAGE-ID: + CONTENT-TYPE:text/calendar + + BEGIN:VCALENDAR + METHOD:xyz + VERSION:2.0 + PRODID:-//ABC Corporation//NONSGML My Product//EN + BEGIN:VEVENT + DTSTAMP:19970324T1200Z + SEQUENCE:0 + UID:uid3@host1.com + ORGANIZER:MAILTO:jdoe@host1.com + ATTENDEE;RSVP=TRUE:MAILTO:jsmith@host1.com + DTSTART:19970324T123000Z + DTEND:19970324T210000Z + CATEGORIES:MEETING,PROJECT + CLASS:PUBLIC + SUMMARY:Calendaring Interoperability Planning Meeting + DESCRIPTION:Discuss how we can test c&s interoperability\n + using iCalendar and other IETF standards. + LOCATION:LDB Lobby + ATTACH;FMTTYPE=application/postscript:ftp://xyzCorp.com/pub/ + conf/bkgrnd.ps + END:VEVENT + END:VCALENDAR + + The following is an example of a to-do due on April 15, 1998. An + audio alarm has been specified to remind the calendar user at noon, + the day before the to-do is expected to be completed and repeat + hourly, four additional times. The to-do definition has been modified + twice since it was initially created. + + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:-//ABC Corporation//NONSGML My Product//EN + BEGIN:VTODO + DTSTAMP:19980130T134500Z + SEQUENCE:2 + UID:uid4@host1.com + ORGANIZER:MAILTO:unclesam@us.gov + ATTENDEE;PARTSTAT=ACCEPTED:MAILTO:jqpublic@host.com + + + +Dawson & Stenerson Standards Track [Page 138] + +RFC 2445 iCalendar November 1998 + + + DUE:19980415T235959 + STATUS:NEEDS-ACTION + SUMMARY:Submit Income Taxes + BEGIN:VALARM + ACTION:AUDIO + TRIGGER:19980403T120000 + ATTACH;FMTTYPE=audio/basic:http://host.com/pub/audio- + files/ssbanner.aud + REPEAT:4 + DURATION:PT1H + END:VALARM + END:VTODO + END:VCALENDAR + + The following is an example of a journal entry. + + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:-//ABC Corporation//NONSGML My Product//EN + BEGIN:VJOURNAL + DTSTAMP:19970324T120000Z + UID:uid5@host1.com + ORGANIZER:MAILTO:jsmith@host.com + STATUS:DRAFT + CLASS:PUBLIC + CATEGORY:Project Report, XYZ, Weekly Meeting + DESCRIPTION:Project xyz Review Meeting Minutes\n + Agenda\n1. Review of project version 1.0 requirements.\n2. + Definition + of project processes.\n3. Review of project schedule.\n + Participants: John Smith, Jane Doe, Jim Dandy\n-It was + decided that the requirements need to be signed off by + product marketing.\n-Project processes were accepted.\n + -Project schedule needs to account for scheduled holidays + and employee vacation time. Check with HR for specific + dates.\n-New schedule will be distributed by Friday.\n- + Next weeks meeting is cancelled. No meeting until 3/23. + END:VJOURNAL + END:VCALENDAR + + The following is an example of published busy time information. The + iCalendar object might be placed in the network resource + www.host.com/calendar/busytime/jsmith.ifb. + + BEGIN:VCALENDAR + VERSION:2.0 + PRODID:-//RDU Software//NONSGML HandCal//EN + BEGIN:VFREEBUSY + + + +Dawson & Stenerson Standards Track [Page 139] + +RFC 2445 iCalendar November 1998 + + + ORGANIZER:MAILTO:jsmith@host.com + DTSTART:19980313T141711Z + DTEND:19980410T141711Z + FREEBUSY:19980314T233000Z/19980315T003000Z + FREEBUSY:19980316T153000Z/19980316T163000Z + FREEBUSY:19980318T030000Z/19980318T040000Z + URL:http://www.host.com/calendar/busytime/jsmith.ifb + END:VFREEBUSY + END:VCALENDAR + +6 Recommended Practices + + These recommended practices should be followed in order to assure + consistent handling of the following cases for an iCalendar object. + + 1. Content lines longer than 75 octets SHOULD be folded. + + 2. A calendar entry with a "DTSTART" property but no "DTEND" + property does not take up any time. It is intended to represent + an event that is associated with a given calendar date and time + of day, such as an anniversary. Since the event does not take up + any time, it MUST NOT be used to record busy time no matter what + the value for the "TRANSP" property. + + 3. When the "DTSTART" and "DTEND", for "VEVENT", "VJOURNAL" and + "VFREEBUSY" calendar components, and "DTSTART" and "DUE", for + "VTODO" calendar components, have the same value data type (e.g., + DATE-TIME), they SHOULD specify values in the same time format + (e.g., UTC time format). + + 4. When the combination of the "RRULE" and "RDATE" properties on an + iCalendar object produces multiple instances having the same + start date/time, they should be collapsed to, and considered as, + a single instance. + + 5. When a calendar user receives multiple requests for the same + calendar component (e.g., REQUEST for a "VEVENT" calendar + component) as a result of being on multiple mailing lists + specified by "ATTENDEE" properties in the request, they SHOULD + respond to only one of the requests. The calendar user SHOULD + also specify (using the "MEMBER" parameter of the "ATTENDEE" + property) which mailing list they are a member of. + + 6. An implementation can truncate a "SUMMARY" property value to 255 + characters. + + + + + + +Dawson & Stenerson Standards Track [Page 140] + +RFC 2445 iCalendar November 1998 + + + 7. If seconds of the minute are not supported by an implementation, + then a value of "00" SHOULD be specified for the seconds + component in a time value. + + 8. If the value type parameter (VALUE=) contains an unknown value + type, it SHOULD be treated as TEXT. + + 9. TZURL values SHOULD NOT be specified as a FILE URI type. This URI + form can be useful within an organization, but is problematic in + the Internet. + + 10. Some possible English values for CATEGORIES property include + "ANNIVERSARY", "APPOINTMENT", "BUSINESS", "EDUCATION", + "HOLIDAY", "MEETING", "MISCELLANEOUS", "NON-WORKING HOURS", "NOT + IN OFFICE", "PERSONAL", "PHONE CALL", "SICK DAY", "SPECIAL + OCCASION", "TRAVEL", "VACATION". Categories can be specified in + any registered language. + + 11. Some possible English values for RESOURCES property include + "CATERING", "CHAIRS", "COMPUTER PROJECTOR", "EASEL", "OVERHEAD + PROJECTOR", "SPEAKER PHONE", "TABLE", "TV", "VCR", "VIDEO + PHONE", "VEHICLE". Resources can be specified in any registered + language. + +7 Registration of Content Type Elements + + This section provides the process for registration of MIME + Calendaring and Scheduling Content Type iCalendar object methods and + new or modified properties. + +7.1 Registration of New and Modified iCalendar Object Methods + + New MIME Calendaring and Scheduling Content Type iCalendar object + methods are registered by the publication of an IETF Request for + Comments (RFC). Changes to an iCalendar object method are registered + by the publication of a revision of the RFC defining the method. + +7.2 Registration of New Properties + + This section defines procedures by which new properties or enumerated + property values for the MIME Calendaring and Scheduling Content Type + can be registered with the IANA. Non-IANA properties can be used by + bilateral agreement, provided the associated properties names follow + the "X-" convention. + + The procedures defined here are designed to allow public comment and + review of new properties, while posing only a small impediment to the + definition of new properties. + + + +Dawson & Stenerson Standards Track [Page 141] + +RFC 2445 iCalendar November 1998 + + + Registration of a new property is accomplished by the following + steps. + +7.2.1 Define the property + + A property is defined by completing the following template. + + To: ietf-calendar@imc.org + + Subject: Registration of text/calendar MIME property XXX + + Property name: + + Property purpose: + + Property value type(s): + + Property parameter (s): + + Conformance: + + Description: + + Format definition: + + Examples: + + The meaning of each field in the template is as follows. + + Property name: The name of the property, as it will appear in the + body of an text/calendar MIME Content-Type "property: value" line to + the left of the colon ":". + + Property purpose: The purpose of the property (e.g., to indicate a + delegate for the event or to-do, etc.). Give a short but clear + description. + + Property value type (s): Any of the valid value types for the + property value needs to be specified. The default value type also + needs to be specified. If a new value type is specified, it needs to + be declared in this section. + + Property parameter (s): Any of the valid property parameters for the + property needs to be specified. + + Conformance: The calendar components that the property can appear in + needs to be specified. + + + + +Dawson & Stenerson Standards Track [Page 142] + +RFC 2445 iCalendar November 1998 + + + Description: Any special notes about the property, how it is to be + used, etc. + + Format definition: The ABNF for the property definition needs to be + specified. + + Examples: One or more examples of instances of the property needs to + be specified. + +7.2.2 Post the Property definition + + The property description MUST be posted to the new property + discussion list, ietf-calendar@imc.org. + +7.2.3 Allow a comment period + + Discussion on the new property MUST be allowed to take place on the + list for a minimum of two weeks. Consensus MUST be reached on the + property before proceeding to the next step. + +7.2.4 Submit the property for approval + + Once the two-week comment period has elapsed, and the proposer is + convinced consensus has been reached on the property, the + registration application should be submitted to the Method Reviewer + for approval. The Method Reviewer is appointed to the Application + Area Directors and can either accept or reject the property + registration. An accepted registration should be passed on by the + Method Reviewer to the IANA for inclusion in the official IANA method + registry. The registration can be rejected for any of the following + reasons. 1) Insufficient comment period; 2) Consensus not reached; 3) + Technical deficiencies raised on the list or elsewhere have not been + addressed. The Method Reviewer's decision to reject a property can be + appealed by the proposer to the IESG, or the objections raised can be + addressed by the proposer and the property resubmitted. + +7.3 Property Change Control + + Existing properties can be changed using the same process by which + they were registered. + + 1. Define the change + + 2. Post the change + + 3. Allow a comment period + + 4. Submit the property for approval + + + +Dawson & Stenerson Standards Track [Page 143] + +RFC 2445 iCalendar November 1998 + + + Note that the original author or any other interested party can + propose a change to an existing property, but that such changes + should only be proposed when there are serious omissions or errors in + the published memo. The Method Reviewer can object to a change if it + is not backward compatible, but is not required to do so. + + Property definitions can never be deleted from the IANA registry, but + properties which are no longer believed to be useful can be declared + OBSOLETE by a change to their "intended use" field. + +8 References + + [IMIP] Dawson, F., Mansour, S. and S. Silverberg, "iCalendar + Message-based Interoperability Protocol (IMIP)", RFC 2447, + November 1998. + + [ITIP] Silverberg, S., Mansour, S., Dawson, F. and R. Hopson, + "iCalendar Transport-Independent Interoperability Protocol + (iTIP) : Scheduling Events, Busy Time, To-dos and Journal + Entries", RFC 2446, November 1998. + + [ISO 8601] ISO 8601, "Data elements and interchange formats- + Information interchange--Representation of dates and + times", International Organization for Standardization, + June, 1988. + + [ISO 9070] ISO/IEC 9070, "Information Technology_SGML Support + Facilities--Registration Procedures for Public Text Owner + Identifiers", Second Edition, International Organization + for Standardization, April 1991. + + [RFC 822] Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", STD 11, RFC 822, August 1982. + + [RFC 1738] Berners-Lee, T., Masinter, L. and M. McCahill, "Uniform + Resource Locators (URL)", RFC 1738, December 1994. + + [RFC 1766] Alvestrand, H., "Tags for the Identification of + Languages", RFC 1766, March 1995. + + [RFC 2045] Freed, N. and N. Borenstein, " Multipurpose Internet Mail + Extensions (MIME) - Part One: Format of Internet Message + Bodies", RFC 2045, November 1996. + + [RFC 2046] Freed, N. and N. Borenstein, " Multipurpose Internet Mail + Extensions (MIME) - Part Two: Media Types", RFC 2046, + November 1996. + + + + +Dawson & Stenerson Standards Track [Page 144] + +RFC 2445 iCalendar November 1998 + + + [RFC 2048] Freed, N., Klensin, J. and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) - Part Four: Registration + Procedures", RFC 2048, January 1997. + + [RFC 2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [RFC 2234] Crocker, D. and P. Overell, "Augmented BNF for Syntax + Specifications: ABNF", RFC 2234, November 1997. + + [RFC 2279] Yergeau, F., "UTF-8, a transformation format of ISO + 10646", RFC 2279, January 1998. + + [RFC 2425] Howes, T., Smith, M. and F. Dawson, "A MIME Content-Type + for Directory Information", RFC 2425, September 1998. + + [RFC 2426] Dawson, F. and T. Howes, "vCard MIME Directory Profile", + RFC 2426, September 1998. + + [TZ] Olson, A.D., et al, Time zone code and data, + ftp://elsie.nci.nih.gov/pub/, updated periodically. + + [VCAL] Internet Mail Consortium, "vCalendar - The Electronic + Calendaring and Scheduling Exchange Format", + http://www.imc.org/pdi/vcal-10.txt, September 18, 1996. + +9 Acknowledgments + + A hearty thanks to the IETF Calendaring and Scheduling Working Group + and also the following individuals who have participated in the + drafting, review and discussion of this memo: + + Roland Alden, Harald T. Alvestrand, Eric Berman, Denis Bigorgne, John + Binici, Bill Bliss, Philippe Boucher, Steve Carter, Andre + Courtemanche, Dave Crocker, David Curley, Alec Dun, John Evans, Ross + Finlayson, Randell Flint, Ned Freed, Patrik Faltstrom, Chuck + Grandgent, Mark Handley, Steve Hanna, Paul B. Hill, Paul Hoffman, + Ross Hopson, Mark Horton, Daryl Huff, Bruce Kahn, C. Harald Koch, + Ryan Jansen, Don Lavange, Antoine Leca, Theodore Lorek, Steve + Mansour, Skip Montanaro, Keith Moore, Cecil Murray, Chris Newman, + John Noerenberg, Ralph Patterson, Pete Resnick, Keith Rhodes, Robert + Ripberger, John Rose, Doug Royer, Andras Salamar, Ted Schuh, Vinod + Seraphin, Derrick Shadel, Ken Shan, Andrew Shuman, Steve Silverberg, + William P. Spencer, John Sun, Mark Towfiq, Yvonne Tso, Robert Visnov, + James L. Weiner, Mike Weston, William Wyatt. + + + + + + +Dawson & Stenerson Standards Track [Page 145] + +RFC 2445 iCalendar November 1998 + + +10 Authors' and Chairs' Addresses + + The following address information is provided in a MIME-VCARD, + Electronic Business Card, format. + + The authors of this memo are: + + BEGIN:VCARD + VERSION:3.0 + N:Dawson;Frank + FN:Frank Dawson + ORG:Lotus Development Corporation + ADR;TYPE=WORK,POSTAL,PARCEL:;;6544 Battleford Drive; + Raleigh;NC;27613-3502;USA + TEL;TYPE=WORK,MSG:+1-919-676-9515 + TEL;TYPE=WORK,FAX:+1-919-676-9564 + EMAIL;TYPE=PREF,INTERNET:Frank_Dawson@Lotus.com + EMAIL;TYPE=INTERNET:fdawson@earthlink.net + URL:http://home.earthlink.net/~fdawson + END:VCARD + + BEGIN:VCARD + VERSION:3.0 + N:Stenerson;Derik + FN:Derik Stenerson + ORG:Microsoft Corporation + ADR;TYPE=WORK,POSTAL,PARCEL:;;One Microsoft Way; + Redmond;WA;98052-6399;USA + TEL;TYPE=WORK,MSG:+1-425-936-5522 + TEL;TYPE=WORK,FAX:+1-425-936-7329 + EMAIL;TYPE=INTERNET:deriks@Microsoft.com + END:VCARD + + The iCalendar object is a result of the work of the Internet + Engineering Task Force Calendaring and Scheduling Working Group. The + chairmen of that working group are: + + BEGIN:VCARD + VERSION:3.0 + N:Ganguly;Anik + FN:Anik Ganguly + ORG: Open Text Inc. + ADR;TYPE=WORK,POSTAL,PARCEL:;Suite 101;38777 West Six Mile Road; + Livonia;MI;48152;USA + TEL;TYPE=WORK,MSG:+1-734-542-5955 + EMAIL;TYPE=INTERNET:ganguly@acm.org + END:VCARD + + + + +Dawson & Stenerson Standards Track [Page 146] + +RFC 2445 iCalendar November 1998 + + + The co-chairman of that working group is: + + BEGIN:VCARD + VERSION:3.0 + N:Moskowitz;Robert + FN:Robert Moskowitz + EMAIL;TYPE=INTERNET:rgm-ietf@htt-consult.com + END:VCARD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Dawson & Stenerson Standards Track [Page 147] + +RFC 2445 iCalendar November 1998 + + +11. Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Dawson & Stenerson Standards Track [Page 148] + diff --git a/lib/qCal/docs/rfc/rfc2446.txt b/lib/qCal/docs/rfc/rfc2446.txt new file mode 100644 index 0000000..8e038a2 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2446.txt @@ -0,0 +1,6107 @@ + + + + + + +Network Working Group S. Silverberg +Request for Comments: 2446 Microsoft +Category: Standards Track S. Mansour + Netscape + F. Dawson + Lotus + R. Hopson + ON Technologies + November 1998 + + + iCalendar Transport-Independent Interoperability Protocol + (iTIP) + Scheduling Events, BusyTime, To-dos and Journal Entries + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +Abstract + + This document specifies how calendaring systems use iCalendar objects + to interoperate with other calendar systems. It does so in a general + way so as to allow multiple methods of communication between systems. + Subsequent documents specify interoperable methods of communications + between systems that use this protocol. + + The document outlines a model for calendar exchange that defines both + static and dynamic event, to-do, journal and free/busy objects. + Static objects are used to transmit information from one entity to + another without the expectation of continuity or referential + integrity with the original item. Dynamic objects are a superset of + static objects and will gracefully degrade to their static + counterparts for clients that only support static objects. + + This document specifies an Internet protocol based on the iCalendar + object specification that provides scheduling interoperability + between different calendar systems. The Internet protocol is called + the "iCalendar Transport-Independent Interoperability Protocol + (iTIP)". + + + +Silverberg, et. al. Standards Track [Page 1] + +RFC 2446 iTIP November 1998 + + + iTIP complements the iCalendar object specification by adding + semantics for group scheduling methods commonly available in current + calendar systems. These scheduling methods permit two or more + calendar systems to perform transactions such as publish, schedule, + reschedule, respond to scheduling requests, negotiation of changes or + cancel iCalendar-based calendar components. + + iTIP is defined independent of the particular transport used to + transmit the scheduling information. Companion memos to iTIP provide + bindings of the interoperability protocol to a number of Internet + protocols. + +Table of Contents + + 1 INTRODUCTION...................................................5 + 1.1 FORMATTING CONVENTIONS .....................................5 + 1.2 RELATED DOCUMENTS ..........................................6 + 1.3 ITIP ROLES AND TRANSACTIONS ................................6 + 2 INTEROPERABILITY MODELS........................................8 + 2.1 APPLICATION PROTOCOL .......................................9 + 2.1.1 Calendar Entry State ...................................9 + 2.1.2 Delegation .............................................9 + 2.1.3 Acting on Behalf of other Calendar Users ..............10 + 2.1.4 Component Revisions ...................................10 + 2.1.5 Message Sequencing ....................................11 + 3 APPLICATION PROTOCOL ELEMENTS.................................12 + 3.1 COMMON COMPONENT RESTRICTION TABLES .......................13 + 3.2 METHODS FOR VEVENT CALENDAR COMPONENTS ....................14 + 3.2.1 PUBLISH ...............................................15 + 3.2.2 REQUEST ...............................................17 + 3.2.2.1 Rescheduling an Event..............................19 + 3.2.2.2 Updating or Reconfirmation of an Event.............19 + 3.2.2.3 Delegating an Event to another CU..................19 + 3.2.2.4 Changing the Organizer.............................20 + 3.2.2.5 Sending on Behalf of the Organizer.................20 + 3.2.2.6 Forwarding to An Uninvited CU......................20 + 3.2.2.7 Updating Attendee Status...........................21 + 3.2.3 REPLY .................................................21 + 3.2.4 ADD ...................................................23 + 3.2.5 CANCEL ................................................25 + 3.2.6 REFRESH ...............................................26 + 3.2.7 COUNTER ...............................................28 + 3.2.8 DECLINECOUNTER ........................................29 + 3.3 METHODS FOR VFREEBUSY COMPONENTS ..........................31 + 3.3.1 PUBLISH ...............................................32 + 3.3.2 REQUEST ...............................................33 + 3.3.3 REPLY .................................................34 + 3.4 METHODS FOR VTODO COMPONENTS ..............................35 + + + +Silverberg, et. al. Standards Track [Page 2] + +RFC 2446 iTIP November 1998 + + + 3.4.1 PUBLISH ...............................................35 + 3.4.2 REQUEST ...............................................37 + 3.4.2.1 REQUEST for Rescheduling a VTODO...................39 + 3.4.2.2 REQUEST for Update or Reconfirmation of a VTODO....39 + 3.4.2.3 REQUEST for Delegating a VTODO.....................40 + 3.4.2.4 REQUEST Forwarded To An Uninvited Calendar User....40 + 3.4.2.5 REQUEST Updated Attendee Status....................41 + 3.4.3 REPLY .................................................41 + 3.4.4 ADD ...................................................43 + 3.4.5 CANCEL ................................................44 + 3.4.6 REFRESH ...............................................46 + 3.4.7 COUNTER ...............................................48 + 3.4.8 DECLINECOUNTER ........................................49 + 3.5 METHODS FOR VJOURNAL COMPONENTS ...........................50 + 3.5.1 PUBLISH ...............................................51 + 3.5.2 ADD ...................................................52 + 3.5.3 CANCEL ................................................53 + 3.6 STATUS REPLIES ............................................55 + 3.7 IMPLEMENTATION CONSIDERATIONS .............................57 + 3.7.1 Working With Recurrence Instances .....................57 + 3.7.2 Attendee Property Considerations ......................58 + 3.7.3 X-Tokens ..............................................59 + 4 EXAMPLES......................................................59 + 4.1 PUBLISHED EVENT EXAMPLES ..................................59 + 4.1.1 A Minimal Published Event .............................60 + 4.1.2 Changing A Published Event ............................60 + 4.1.3 Canceling A Published Event ...........................61 + 4.1.4 A Rich Published Event ................................62 + 4.1.5 Anniversaries or Events attached to entire days .......63 + 4.2 GROUP EVENT EXAMPLES ......................................63 + 4.2.1 A Group Event Request .................................64 + 4.2.2 Reply To A Group Event Request ........................65 + 4.2.3 Update An Event .......................................65 + 4.2.4 Countering an Event Proposal ..........................66 + 4.2.5 Delegating an Event ...................................68 + 4.2.6 Delegate Accepts the Meeting ..........................70 + 4.2.7 Delegate Declines the Meeting .........................71 + 4.2.8 Forwarding an Event Request ...........................72 + 4.2.9 Cancel A Group Event ..................................72 + 4.2.10 Removing Attendees ...................................74 + 4.2.11 Replacing the Organizer ..............................75 + 4.3 BUSY TIME EXAMPLES ........................................76 + 4.3.1 Request Busy Time .....................................77 + 4.3.2 Reply To A Busy Time Request ..........................77 + 4.4 RECURRING EVENT AND TIME ZONE EXAMPLES ....................78 + 4.4.1 A Recurring Event Spanning Time Zones .................78 + 4.4.2 Modify A Recurring Instance ...........................79 + 4.4.3 Cancel an Instance ....................................81 + + + +Silverberg, et. al. Standards Track [Page 3] + +RFC 2446 iTIP November 1998 + + + 4.4.4 Cancel Recurring Event ................................81 + 4.4.5 Change All Future Instances ...........................82 + 4.4.6 Add A New Instance To A Recurring Event ...............82 + 4.4.7 Add A New Series of Instances To A Recurring Event ....83 + 4.4.8 Counter An Instance Of A Recurring Event ..............87 + 4.4.9 Error Reply To A Request ..............................88 + 4.5 GROUP TO-DO EXAMPLES ......................................89 + 4.5.1 A VTODO Request .......................................90 + 4.5.2 A VTODO Reply .........................................90 + 4.5.3 A VTODO Request for Updated Status ....................91 + 4.5.4 A Reply: Percent-Complete .............................91 + 4.5.5 A Reply: Completed ....................................92 + 4.5.6 An Updated VTODO Request ..............................92 + 4.5.7 Recurring VTODOs ......................................92 + 4.5.7.1 Request for a Recurring VTODO......................93 + 4.5.7.2 Calculating due dates in recurring VTODOs..........93 + 4.5.7.3 Replying to an instance of a recurring VTODO.......93 + 4.6 JOURNAL EXAMPLES ..........................................94 + 4.7 OTHER EXAMPLES ............................................94 + 4.7.1 Event Refresh .........................................94 + 4.7.2 Bad RECURRENCE-ID .....................................95 + 5 APPLICATION PROTOCOL FALLBACKS................................97 + 5.1 PARTIAL IMPLEMENTATION ....................................97 + 5.1.1 Event-Related Fallbacks ...............................97 + 5.1.2 Free/Busy-Related Fallbacks ...........................99 + 5.1.3 To-Do-Related Fallbacks ...............................99 + 5.1.4 Journal-Related Fallbacks ............................101 + 5.2 LATENCY ISSUES ...........................................102 + 5.2.1 Cancellation of an Unknown Calendar Component. .......102 + 5.2.2 Unexpected Reply from an Unknown Delegate ............103 + 5.3 SEQUENCE NUMBER ..........................................103 + 6 SECURITY CONSIDERATIONS......................................103 + 6.1 SECURITY THREATS .........................................103 + 6.1.1 Spoofing the "Organizer" .............................103 + 6.1.2 Spoofing the "Attendee" ..............................103 + 6.1.3 Unauthorized Replacement of the Organizer ............104 + 6.1.4 Eavesdropping ........................................104 + 6.1.5 Flooding a Calendar ..................................104 + 6.1.6 Procedural Alarms ....................................104 + 6.1.7 Unauthorized REFRESH Requests ........................104 + 6.2 RECOMMENDATIONS ..........................................104 + 6.2.1 Use of [RFC-1847] to secure iTIP transactions ........105 + 6.2.2 Implementation Controls ..............................105 + 7 ACKNOWLEDGMENTS..............................................106 + 8 BIBLIOGRAPHY.................................................106 + 9 AUTHORS' ADDRESSES...........................................107 + 10 FULL COPYRIGHT STATEMENT....................................109 + + + + +Silverberg, et. al. Standards Track [Page 4] + +RFC 2446 iTIP November 1998 + + +1 Introduction + + This document specifies how calendaring systems use iCalendar objects + to interoperate with other calendar systems. In particular, it + specifies how to schedule events, to-dos, or daily journal entries. + It further specifies how to search for available busy time + information. It does so in a general way so as to allow multiple + methods of communication between systems. Subsequent documents + specify transport bindings between systems that use this protocol. + + This protocol is based on messages sent from an originator to one or + more recipients. For certain types of messages, a recipient may + reply, in order to update their status and may also return + transaction/request status information. The protocol supports the + ability for the message originator to modify or cancel the original + message. The protocol also supports the ability for recipients to + suggest changes to the originator of a message. The elements of the + protocol also define the user roles for its transactions. + +1.1 Formatting Conventions + + In order to refer to elements of the calendaring and scheduling + model, core object or interoperability protocol defined in [iCAL] and + [iTIP] several formatting conventions have been utilized. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this + document are to be interpreted as described in [RFC-2119]. + + Calendaring and scheduling roles are referred to in quoted-strings of + text with the first character of each word in upper case. For + example, "Organizer" refers to a role of a "Calendar User" (CU) + within the scheduling protocol defined by [iTIP]. Calendar components + defined by [iCAL] are referred to with capitalized, quoted-strings of + text. All calendar components start with the letter "V". For example, + "VEVENT" refers to the event calendar component, "VTODO" refers to + the to-do calendar component and "VJOURNAL" refers to the daily + journal calendar component. Scheduling methods defined by [iTIP] are + referred to with capitalized, quoted-strings of text. For example, + "REQUEST" refers to the method for requesting a scheduling calendar + component be created or modified, "REPLY" refers to the method a + recipient of a request uses to update their status with the + "Organizer" of the calendar component. + + Properties defined by [iCAL] are referred to with capitalized, + quoted-strings of text, followed by the word "property". For example, + "ATTENDEE" property refers to the iCalendar property used to convey + the calendar address of a "Calendar User". Property parameters + + + +Silverberg, et. al. Standards Track [Page 5] + +RFC 2446 iTIP November 1998 + + + defined by this memo are referred to with lower case, quoted-strings + of text, followed by the word "parameter". For example, "value" + parameter refers to the iCalendar property parameter used to override + the default data type for a property value. Enumerated values defined + by this memo are referred to with capitalized text, either alone or + followed by the word "value". + + In tables, the quoted-string text is specified without quotes in + order to minimize the table length. + +1.2 Related Documents + + Implementers will need to be familiar with several other memos that, + along with this one, describe the Internet calendaring and scheduling + standards. This document, [iTIP], specifies an interoperability + protocol for scheduling between different implementations. The + related documents are: + + [iCAL] - specifies the objects, data types, properties and + property parameters used in the protocols, along with the + methods for representing and encoding them; + + [iMIP] specifies an Internet email binding for [iTIP]. + + This memo does not attempt to repeat the specification of concepts or + definitions from these other memos. Where possible, references are + made to the memo that provides for the specification of these + concepts or definitions. + +1.3 ITIP Roles and Transactions + + ITIP defines methods for exchanging [iCAL] objects for the purposes + of group calendaring and scheduling between "Calendar Users" (CUs). + CUs take on one of two roles in iTIP. The CU who initiates an + exchange takes on the role of "Organizer". For example, the CU who + proposes a group meeting is the "Organizer". The CUs asked to + participate in the group meeting by the "Organizer" take on the role + of "Attendee". Note that "role" is also a descriptive parameter to + the _ATTENDEE_ property. Its use is to convey descriptive context to + an "Attendee" such as "chair", "req-participant" or "non-participant" + and has nothing to do with the calendaring workflow. + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 6] + +RFC 2446 iTIP November 1998 + + + The ITIP methods are listed below and their usage and semantics are + defined in section 3 of this document. + + +================+==================================================+ + | Method | Description | + |================+==================================================| + | PUBLISH | Used to publish a calendar entry to one or more | + | | Calendar Users. There is no interactivity | + | | between the publisher and any other calendar | + | | user. An example might include a baseball team | + | | publishing its schedule to the public. | + | | | + | REQUEST | Used to schedule a calendar entry with other | + | | Calendar Users. Requests are interactive in that | + | | they require the receiver to respond using | + | | the Reply methods. Meeting Requests, Busy | + | | Time requests and the assignment of VTODOs to | + | | other Calendar Users are all examples. | + | | Requests are also used by the "Organizer" to | + | | update the status of a calendar entry. | + | | | + | REPLY | A Reply is used in response to a Request to | + | | convey "Attendee" status to the "Organizer". | + | | Replies are commonly used to respond to meeting | + | | and task requests. | + | | | + | ADD | Add one or more instances to an existing | + | | VEVENT, VTODO, or VJOURNAL. | + | | | + | CANCEL | Cancel one or more instances of an existing | + | | VEVENT, VTODO, or VJOURNAL. | + | | | + | REFRESH | The Refresh method is used by an "Attendee" to | + | | request the latest version of a calendar entry. | + | | | + | COUNTER | The Counter method is used by an "Attendee" to | + | | negotiate a change in the calendar entry. | + | | Examples include the request to change a | + | | proposed Event time or change the due date for a | + | | VTODO. | + | | | + | DECLINE- | Used by the "Organizer" to decline the proposed | + | COUNTER | counter-proprosal. | + +================+==================================================+ + + + + + + + +Silverberg, et. al. Standards Track [Page 7] + +RFC 2446 iTIP November 1998 + + + Group scheduling in iTIP is accomplished using the set of "request" + and "response" methods described above. The following table shows the + methods broken down by who can send them. + + +================+==================================================+ + | Originator | Methods | + |================+==================================================| + | Organizer | PUBLISH, REQUEST, ADD, CANCEL, DECLINECOUNTER | + | | | + | Attendee | REPLY, REFRESH, COUNTER | + | | REQUEST only when delegating | + +================+==================================================+ + + Note that for some calendar component types, the allowable methods + are a subset of the above set. + +2 Interoperability Models + + There are two distinct protocols relevant to interoperability: an + "Application Protocol" and a "Transport Protocol". The Application + Protocol defines the content of the iCalendar objects sent between + sender and receiver to accomplish the scheduling transactions listed + above. The Transport Protocol defines how the iCalendar objects are + sent between the sender and receiver. This document focuses on the + Application Protocol. Binding documents such as [iMIP] focus on the + Transport Protocol. + + The connection between Sender and Receiver in the diagram below + refers to the Application Protocol. The iCalendar objects passed from + the Sender to the Receiver are presented in Section 3, Application + Protocol Elements. + + +----------+ +----------+ + | | iTIP | | + | Sender |<-------------------->| Receiver | + | | | | + +----------+ +----------+ + + There are several variations of this diagram in which the Sender and + Receiver take on various roles of a "Calendar User Agent" (CUA) or a + "Calendar Service" (CS). + + The architecture of iTIP is depicted in the diagram below. An + application written to this specification may work with bindings for + the store-and-forward transport, the real time transport, or both. + Also note that iTIP could be bound to other transports. + + + + + +Silverberg, et. al. Standards Track [Page 8] + +RFC 2446 iTIP November 1998 + + + +------------------------------------------+ + | iTIP | + +------------------------------------------+ + |Real-time | Store-and-Fwd | Other | + |Transport | Transport | Transports... | + +------------------------------------------+ + +2.1 Application Protocol + + In the iTIP model, a calendar entry is created and managed by an + "Organizer". The "Organizer" interacts with other CUs by sending one + or more of the iTIP messages listed above. "Attendees" use the + "REPLY" method to communicate their status. "Attendees" do not make + direct changes to the master calendar entry. They can, however, use + the "COUNTER" method to suggest changes to the "Organizer". In any + case, the "Organizer" has complete control over the master calendar + entry. + +2.1.1 Calendar Entry State + + There are two distinct states relevant to calendar entries: the + overall state of the entry and the state associated with an + "Attendee" to that entry. + + The state of an entry is defined by the "STATUS" property and is + controlled by the "Organizer." There is no default value for the + "STATUS" property. The "Organizer" sets the "STATUS" property to the + appropriate value for each calendar entry. + + The state of a particular "Attendee" relative to an entry is defined + by the "partstat" parameter in the "ATTENDEE" property for each + "Attendee". When an "Organizer" issues the initial entry, "Attendee" + status is unknown. The "Organizer" specifies this by setting the + "partstat" parameter to "NEEDS-ACTION". Each "Attendee" modifies + their "ATTENDEE" property "partstat" parameter to an appropriate + value as part of a "REPLY" message sent back to the "Organizer". + +2.1.2 Delegation + + Delegation is defined as the process by which an "Attendee" grants + another CU (or several CUs) the right to attend on their behalf. The + "Organizer" is made aware of this change because the delegating + "Attendee" informs the "Organizer". These steps are detailed in the + REQUEST method section. + + + + + + + +Silverberg, et. al. Standards Track [Page 9] + +RFC 2446 iTIP November 1998 + + +2.1.3 Acting on Behalf of other Calendar Users + + In many organizations one user will act on behalf of another to + organize and/or respond to meeting requests. ITIP provides two + mechanisms that support these activities. + + First, the "Organizer" is treated as a special entity, separate from + "Attendees". All responses from "Attendees" flow to the "Organizer", + making it easy to separate a calendar user organizing a meeting from + calendar users attending the meeting. Additionally, iCalendar + provides descriptive roles for each "Attendee". For instance, a role + of "chair" may be ascribed to one or more "Attendees". The "chair" + and the "Organizer" may or may not be the same calendar user. This + maps well to scenarios where an assistant may manage meeting + logistics for another individual who chairs a meeting. + + Second, a "sent-by" parameter may be specified in either the + "Organizer" or "Attendee" properties. When specified, the "sent-by" + parameter indicates that the responding CU acted on behalf of the + specified "Attendee" or "Organizer". + +2.1.4 Component Revisions + + The "SEQUENCE" property is used by the "Organizer" to indicate + revisions to the calendar component. The rules for incrementing the + "SEQUENCE" number are defined in [iCAL]. For clarity, these rules are + paraphrased here in terms of how they are applied in [iTIP]. For a + given "UID" in a calendar component: + + . For the "PUBLISH" and "REQUEST" methods, the "SEQUENCE" property + value is incremented according to the rules defined in [iCAL]. + + . The "SEQUENCE" property value MUST be incremented each time the + "Organizer" uses the "ADD" or "CANCEL" methods. + + . The "SEQUENCE" property value MUST NOT be incremented when using + "REPLY", "REFRESH", "COUNTER", "DECLINECOUNTER", or when sending a + delegation "REQUEST". + + In some circumstances the "Organizer" may not have received responses + to the final revision sent out. In this situation, the "Organizer" + may wish to send an update "REQUEST", and set "RSVP=TRUE" for all + "Attendees", so that current responses can be collected. + + + + + + + + +Silverberg, et. al. Standards Track [Page 10] + +RFC 2446 iTIP November 1998 + + + The value of the "SEQUENCE" property contained in a response from an + "Attendee" may not always match the "Organizer's" revision. + Implementations may choose to have the CUA indicate to the CU that + the response is to an entry that has been revised and allow the CU to + decide whether or not to accept the response. + +2.1.5 Message Sequencing + + CUAs that handle the [iTIP] application protocol must often correlate + a component in a calendar store with a component received in the + [iTIP] message. For example, an event may be updated with a later + revision of the same event. To accomplish this, a CUA must correlate + the version of the event already in its calendar store with the + version sent in the [iTIP] message. In addition to this correlation, + there are several factors that can cause [iTIP] messages to arrive in + an unexpected order. That is, an "Organizer" could receive a reply + to an earlier revision of a component AFTER receiving a reply to a + later revision. + + To maximize interoperability and to handle messages that arrive in an + unexpected order, use the following rules: + + 1. The primary key for referencing a particular iCalendar component + is the "UID" property value. To reference an instance of a + recurring component, the primary key is composed of the "UID" and + the "RECURRENCE-ID" properties. + + 2. The secondary key for referencing a component is the "SEQUENCE" + property value. For components where the "UID" is the same, the + component with the highest numeric value for the "SEQUENCE" + property obsoletes all other revisions of the component with + lower values. + + 3. "Attendees" send "REPLY" messages to the "Organizer". For + replies where the "UID" property value is the same, the value of + the "SEQUENCE" property indicates the revision of the component + to which the "Attendee" is replying. The reply with the highest + numeric value for the "SEQUENCE" property obsoletes all other + replies with lower values. + + 4. In situations where the "UID" and "SEQUENCE" properties match, + the "DTSTAMP" property is used as the tie-breaker. The component + with the latest "DTSTAMP" overrides all others. Similarly, for + "Attendee" responses where the "UID" property values match and + the "SEQUENCE" property values match, the response with the + latest "DTSTAMP" overrides all others. + + + + + +Silverberg, et. al. Standards Track [Page 11] + +RFC 2446 iTIP November 1998 + + + Hence, CUAs must persist the following component properties: "UID", + "RECURRENCE-ID", "SEQUENCE", and "DTSTAMP". Furthermore, for each + "ATTENDEE" property of a component CUAs must persist the "SEQUENCE" + and "DTSTAMP" property values associated with the "Attendee's" + response. + +3 Application Protocol Elements + + ITIP messages are "text/calendar" MIME entities that contain + calendaring and scheduling information. The particular type of [iCAL] + message is referred to as the "method type". Each method type is + identified by a "METHOD" property specified as part of the + "text/calendar" content type. The table below shows various + combinations of calendar components and the method types that this + memo supports. + + +=================================================+ + | | VEVENT | VTODO | VJOURNAL | VFREEBUSY | + |=================================================| + |Publish | Yes | Yes | Yes | Yes | + |Request | Yes | Yes | No | Yes | + |Refresh | Yes | Yes | No | No | + |Cancel | Yes | Yes | Yes | No | + |Add | Yes | Yes | Yes | No | + |Reply | Yes | Yes | No | Yes | + |Counter | Yes | Yes | No | No | + |Decline- | | | | | + |Counter | Yes | Yes | No | No | + +=================================================+ + + Each method type is defined in terms of its associated components and + properties. Some components and properties are required, some are + optional and others are excluded. The restrictions are expressed in + this document using a simple "restriction table". The first column + indicates the name of a component or property. Properties of the + iCalendar object are not indented. Properties of a component are + indented. The second column contains "MUST" if the component or + property must be present, "MAY" if the component or property is + optional, and "NOT" if the component or property must not be present. + Entries in the second column sometimes contain comments for further + clarification. + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 12] + +RFC 2446 iTIP November 1998 + + +3.1 Common Component Restriction Tables + + The restriction table below applies to properties of the iCalendar + object. That is, the properties at the outermost scope. The presence + column uses the following values to assert whether a property is + required, is optional and the number of times it may appear in the + iCalendar object. + + Presence Value Description + -------------------------------------------------------------- + 1 One instance MUST be present + 1+ At least one instance MUST be present + 0 Instances of this property Must NOT be present + 0+ Multiple instances MAY be present + 0 or 1 Up to 1 instance of this property MAY be present + --------------------------------------------------------------- + + The tables also call out "X-PROPERTY" and "X-COMPONENT" to show + where vendor-specific properties and components can appear. The + tables do not lay out the restrictions of property parameters. Those + restrictions are defined in [iCAL]. + + Component/Property Presence + ------------------- ---------------------------------------------- + CALSCALE 0 or 1 + PRODID 1 + VERSION 1 Value MUST be "2.0" + X-PROPERTY 0+ + + + DateTime values MAY refer to a "VTIMEZONE" component. The property + restrictions in the table below apply to any "VTIMEZONE" component in + an ITIP message. + + Component/Property Presence + ------------------- ---------------------------------------------- + VTIMEZONE 0+ MUST be present if any date/time refers + to timezone + DAYLIGHT 0+ MUST be one or more of either STANDARD or + DAYLIGHT + COMMENT 0 or 1 + DTSTART 1 MUST be local time format + RDATE 0+ if present RRULE MUST NOT be present + RRULE 0+ if present RDATE MUST NOT be present + TZNAME 0 or 1 + TZOFFSET 1 + TZOFFSETFROM 1 + TZOFFSETTO 1 + + + +Silverberg, et. al. Standards Track [Page 13] + +RFC 2446 iTIP November 1998 + + + X-PROPERTY 0+ + LAST-MODIFIED 0 or 1 + STANDARD 0+ MUST be one or more of either STANDARD or + DAYLIGHT + COMMENT 0 or 1 + DTSTART 1 MUST be local time format + RDATE 0+ if present RRULE MUST NOT be present + RRULE 0+ if present RDATE MUST NOT be present + TZNAME 0 or 1 + TZOFFSETFROM 1 + TZOFFSETTO 1 + X-PROPERTY 0+ + TZID 1 + TZURL 0 or 1 + X-PROPERTY 0+ + + The property restrictions in the table below apply to any "VALARM" + component in an ITIP message. + + Component/Property Presence + ------------------- ---------------------------------------------- + VALARM 0+ + ACTION 1 + ATTACH 0+ + DESCRIPTION 0 or 1 + DURATION 0 or 1 if present REPEAT MUST be present + REPEAT 0 or 1 if present DURATION MUST be present + SUMMARY 0 or 1 + TRIGGER 1 + X-PROPERTY 0+ + +3.2 Methods for VEVENT Calendar Components + + This section defines the property set restrictions for the method + types that are applicable to the "VEVENT" calendar component. Each + method is defined using a table that clarifies the property + constraints that define the particular method. + + + + + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 14] + +RFC 2446 iTIP November 1998 + + + The following summarizes the methods that are defined for the + "VEVENT" calendar component. + + +================+==================================================+ + | Method | Description | + |================+==================================================| + | PUBLISH | Post notification of an event. Used primarily as | + | | a method of advertising the existence of an | + | | event. | + | | | + | REQUEST | Make a request for an event. This is an explicit | + | | invitation to one or more "Attendees". Event | + | | Requests are also used to update or change an | + | | existing event. Clients that cannot handle | + | | REQUEST may degrade the event to view it as an | + | | PUBLISH. | + | | | + | REPLY | Reply to an event request. Clients may set their | + | | status ("partstat") to ACCEPTED, DECLINED, | + | | TENTATIVE, or DELEGATED. | + | | | + | ADD | Add one or more instances to an existing event. | + | | | + | CANCEL | Cancel one or more instances of an existing | + | | event. | + | | | + | REFRESH | A request is sent to an "Organizer" by an | + | | "Attendee" asking for the latest version of an | + | | event to be resent to the requester. | + | | | + | COUNTER | Counter a REQUEST with an alternative proposal, | + | | Sent by an "Attendee" to the "Organizer". | + | | | + | DECLINECOUNTER | Decline a counter proposal. Sent to an | + | | "Attendee" by the "Organizer". | + +================+==================================================+ + +3.2.1 PUBLISH + + The "PUBLISH" method in a "VEVENT" calendar component is an + unsolicited posting of an iCalendar object. Any CU may add published + components to their calendar. The "Organizer" MUST be present in a + published iCalendar component. "Attendees" MUST NOT be present. Its + expected usage is for encapsulating an arbitrary event as an + iCalendar object. The "Organizer" may subsequently update (with + another "PUBLISH" method), add instances to (with an "ADD" method), + or cancel (with a "CANCEL" method) a previously published "VEVENT" + calendar component. + + + +Silverberg, et. al. Standards Track [Page 15] + +RFC 2446 iTIP November 1998 + + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST equal "PUBLISH" +VEVENT 1+ + DTSTAMP 1 + DTSTART 1 + ORGANIZER 1 + SUMMARY 1 Can be null. + UID 1 + RECURRENCE-ID 0 or 1 only if referring to an instance of a + recurring calendar component. Otherwise + it MUST NOT be present. + SEQUENCE 0 or 1 MUST be present if value is greater than + 0, MAY be present if 0 + ATTACH 0+ + CATEGORIES 0 or 1 This property may contain a list of + values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 Can be null + DTEND 0 or 1 if present DURATION MUST NOT be present + DURATION 0 or 1 if present DTEND MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PRIORITY 0 or 1 + RDATE 0+ + RELATED-TO 0+ + RESOURCES 0 or 1 This property MAY contain a list of values + RRULE 0+ + STATUS 0 or 1 MAY be one of TENTATIVE/CONFIRMED/CANCELLED + TRANSP 0 or 1 + URL 0 or 1 + X-PROPERTY 0+ + + ATTENDEE 0 + REQUEST-STATUS 0 + +VALARM 0+ +VFREEBUSY 0 +VJOURNAL 0 + + + +Silverberg, et. al. Standards Track [Page 16] + +RFC 2446 iTIP November 1998 + + +VTODO 0 +VTIMEZONE 0+ MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +3.2.2 REQUEST + + The "REQUEST" method in a "VEVENT" component provides the following + scheduling functions: + + . Invite "Attendees" to an event; + . Reschedule an existing event; + . Response to a REFRESH request; + . Update the details of an existing event, without rescheduling it; + . Update the status of "Attendees" of an existing event, without + rescheduling it; + . Reconfirm an existing event, without rescheduling it; + . Forward a "VEVENT" to another uninvited CU. + . For an existing "VEVENT" calendar component, delegate the role of + "Attendee" to another CU; + . For an existing "VEVENT" calendar component, changing the role of + "Organizer" to another CU. + + The "Organizer" originates the "REQUEST". The recipients of the + "REQUEST" method are the CUs invited to the event, the "Attendees". + "Attendees" use the "REPLY" method to convey attendance status to the + "Organizer". + + The "UID" and "SEQUENCE" properties are used to distinguish the + various uses of the "REQUEST" method. If the "UID" property value in + the "REQUEST" is not found on the recipient's calendar, then the + "REQUEST" is for a new "VEVENT" calendar component. If the "UID" + property value is found on the recipient's calendar, then the + "REQUEST" is for a rescheduling, an update, or a reconfirm of the + "VEVENT" calendar component. + + For the "REQUEST" method, multiple "VEVENT" components in a single + iCalendar object are only permitted when for components with the same + "UID" property. That is, a series of recurring events may have + instance-specific information. In this case, multiple "VEVENT" + components are needed to express the entire series. + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 17] + +RFC 2446 iTIP November 1998 + + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +----------------------------------------------------------------- +METHOD 1 MUST be "REQUEST" +VEVENT 1+ All components MUST have the same UID + ATTENDEE 1+ + DTSTAMP 1 + DTSTART 1 + ORGANIZER 1 + SEQUENCE 0 or 1 MUST be present if value is greater than 0, + MAY be present if 0 + SUMMARY 1 Can be null + UID 1 + + ATTACH 0+ + CATEGORIES 0 or 1 This property may contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 Can be null + DTEND 0 or 1 if present DURATION MUST NOT be present + DURATION 0 or 1 if present DTEND MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PRIORITY 0 or 1 + RDATE 0+ + RECURRENCE-ID 0 or 1 only if referring to an instance of a + recurring calendar component. Otherwise it + MUST NOT be present. + RELATED-TO 0+ + REQUEST-STATUS 0+ + RESOURCES 0 or 1 This property MAY contain a list of values + RRULE 0+ + STATUS 0 or 1 MAY be one of TENTATIVE/CONFIRMED + TRANSP 0 or 1 + URL 0 or 1 + X-PROPERTY 0+ + +VALARM 0+ +VTIMEZONE 0+ MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + + + +Silverberg, et. al. Standards Track [Page 18] + +RFC 2446 iTIP November 1998 + + +VFREEBUSY 0 +VJOURNAL 0 +VTODO 0 + +3.2.2.1 Rescheduling an Event + + The "REQUEST" method may be used to reschedule an event. A + rescheduled event involves a change to the existing event in terms of + its time or recurrence intervals and possibly the location or + description. If the recipient CUA of a "REQUEST" method finds that + the "UID" property value already exists on the calendar, but that the + "SEQUENCE" (or "DTSTAMP") property value in the "REQUEST" method is + greater than the value for the existing event, then the "REQUEST" + method describes a rescheduling of the event. + +3.2.2.2 Updating or Reconfirmation of an Event + + The "REQUEST" method may be used to update or reconfirm an event. An + update to an existing event does not involve changes to the time or + recurrence intervals, and might not involve a change to the location + or description for the event. If the recipient CUA of a "REQUEST" + method finds that the "UID" property value already exists on the + calendar and that the "SEQUENCE" property value in the "REQUEST" is + the same as the value for the existing event, then the "REQUEST" + method describes an update of the event details, but no rescheduling + of the event. + + The update "REQUEST" method is the appropriate response to a + "REFRESH" method sent from an "Attendee" to the "Organizer" of an + event. + + The "Organizer" of an event may also send unsolicited "REQUEST" + methods. The unsolicited "REQUEST" methods may be used to update the + details of the event without rescheduling it, to update the + "partstat" parameter of "Attendees", or to reconfirm the event. + +3.2.2.3 Delegating an Event to another CU + + Some calendar and scheduling systems allow "Attendees" to delegate + their presence at an event to another calendar user. ITIP supports + this concept using the following workflow. Any "Attendee" may + delegate their right to participate in a calendar VEVENT to another + CU. The implication is that the delegate participates in lieu of the + original "Attendee"; NOT in addition to the "Attendee". The delegator + MUST notify the "Organizer" of this action using the steps outlined + below. Implementations may support or restrict delegation as they + see fit. For instance, some implementations may restrict a delegate + from delegating a "REQUEST" to another CU. + + + +Silverberg, et. al. Standards Track [Page 19] + +RFC 2446 iTIP November 1998 + + + The "Delegator" of an event forwards the existing "REQUEST" to the + "Delegate". The "REQUEST" method MUST include an "ATTENDEE" property + with the calendar address of the "Delegate". The "Delegator" MUST + also send a "REPLY" method to the "Organizer" with the "Delegator's" + "ATTENDEE" property "partstat" parameter value set to "delegated". In + addition, the "delegated-to" parameter MUST be included with the + calendar address of the "Delegate". + + In response to the request, the "Delegate" MUST send a "REPLY" method + to the "Organizer" and optionally, to the "Delegator". The "REPLY" + method " SHOULD include the "ATTENDEE" property with the "delegated- + from" parameter value of the "Delegator's" calendar address. + + The "Delegator" may continue to receive updates to the event even + though they will not be attending. This is accomplished by the + "Delegator" setting their "role" attribute to " NON-PARTICIPANT" in + the "REPLY" to the "Organizer" + +3.2.2.4 Changing the Organizer + + The situation may arise where the "Organizer" of a VEVENT is no + longer able to perform the "Organizer" role and abdicates without + passing on the "Organizer" role to someone else. When this occurs the + "Attendees" of the VEVENT may use out-of-band mechanisms to + communicate the situation and agree upon a new "Organizer". The new + "Organizer" should then send out a new "REQUEST" with a modified + version of the VEVENT in which the "SEQUENCE" number has been + incremented and value of the "ORGANIZER" property has been changed to + the calendar address of the new "Organizer". + +3.2.2.5 Sending on Behalf of the Organizer + + There are a number of scenarios that support the need for a calendar + user to act on behalf of the "Organizer" without explicit role + changing. This might be the case if the CU designated as "Organizer" + was sick or unable to perform duties associated with that function. + In these cases iTIP supports the notion of one CU acting on behalf of + another. Using the "sent-by" parameter, a calendar user could send an + updated "VEVENT" REQUEST. In the case where one CU sends on behalf of + another CU, the "Attendee" responses are still directed back towards + the CU designated as "Organizer". + +3.2.2.6 Forwarding to An Uninvited CU + + An "Attendee" invited to an event may invite another uninvited CU to + the event. The invited "Attendee" accomplishes this by forwarding the + original "REQUEST" method to the uninvited CU. The "Organizer" + decides whether or not the uninvited CU is added to the attendee + + + +Silverberg, et. al. Standards Track [Page 20] + +RFC 2446 iTIP November 1998 + + + list. If the "Organizer" decides not to add the uninvited CU no + further action is required, however the "Organizer" MAY send the + uninvited CU a "CANCEL" message. If the "Organizer" decides to add + an uninvited CU, a new "ATTENDEE" property is added for the uninvited + CU with its property parameters set as the "Organizer" deems + appropriate. When forwarding a "REQUEST" to another CU, the + forwarding "Attendee" MUST NOT make changes to the VEVENT property + set. + +3.2.2.7 Updating Attendee Status + + The "Organizer" of an event may also request updated status from one + or more "Attendees. The "Organizer" sends a "REQUEST" method to the + "Attendee" and sets the "ATTENDEE;RSVP=TRUE" property parameter. The + "SEQUENCE" property for the event is not changed from its previous + value. A recipient will determine that the only change in the + "REQUEST" is that their "RSVP" property parameter indicates a request + for updated status. The recipient SHOULD respond with a "REPLY" + method indicating their current status with respect to the "REQUEST". + +3.2.3 REPLY + + The "REPLY" method in a "VEVENT" calendar component is used to + respond (e.g., accept or decline) to a "REQUEST" or to reply to a + delegation "REQUEST". When used to provide a delegation response, the + "Delegator" SHOULD include the calendar address of the "Delegate" on + the "delegated-to" property parameter of the "Delegator's" "ATTENDEE" + property. The "Delegate" SHOULD include the calendar address of the + "Delegator" on the "delegated-from" property parameter of the + "Delegate's" "ATTENDEE" property. + + The "REPLY" method may also be used to respond to an unsuccessful + "REQUEST" method. Depending on the value of the "REQUEST-STATUS" + property no scheduling action may have been performed. + + The "Organizer" of an event may receive the "REPLY" method from a CU + not in the original "REQUEST". For example, a "REPLY" may be received + from a "Delegate" to an event. In addition, the "REPLY" method may be + received from an unknown CU (a "Party Crasher"). This uninvited + "Attendee" may be accepted, or the "Organizer" may cancel the event + for the uninvited "Attendee" by sending a "CANCEL" method to the + uninvited "Attendee". + + An "Attendee" can include a message to the "Organizer" using the + "COMMENT" property. For example, if the user indicates tentative + acceptance and wants to let the "Organizer" know why, the reason can + be expressed in the "COMMENT" property value. + + + + +Silverberg, et. al. Standards Track [Page 21] + +RFC 2446 iTIP November 1998 + + + The "Organizer" may also receive a "REPLY" from one CU on behalf of + another. Like the scenario enumerated above for the "Organizer", + "Attendees" may have another CU respond on their behalf. This is done + using the "sent-by" parameter. + + The optional properties listed in the table below (those listed as + "0+" or "0 or 1") MUST NOT be changed from those of the original + request. If property changes are desired the COUNTER message must be + used. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "REPLY" +VEVENT 1+ All components MUST have the same UID + ATTENDEE 1 MUST be the address of the Attendee + replying. + DTSTAMP 1 + ORGANIZER 1 + RECURRENCE-ID 0 or 1 only if referring to an instance of a + recurring calendar component. Otherwise + it must NOT be present. + UID 1 MUST be the UID of the original REQUEST + + SEQUENCE 0 or 1 MUST if non-zero, MUST be the sequence + number of the original REQUEST. MAY be + present if 0. + + ATTACH 0+ + CATEGORIES 0 or 1 This property may contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 + DTEND 0 or 1 if present DURATION MUST NOT be present + DTSTART 0 or 1 + DURATION 0 or 1 if present DTEND MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PRIORITY 0 or 1 + RDATE 0+ + RELATED-TO 0+ + + + +Silverberg, et. al. Standards Track [Page 22] + +RFC 2446 iTIP November 1998 + + + RESOURCES 0 or 1 This property MAY contain a list of values + REQUEST-STATUS 0+ + RRULE 0+ + STATUS 0 or 1 + SUMMARY 0 or 1 + TRANSP 0 or 1 + URL 0 or 1 + X-PROPERTY 0+ + +VTIMEZONE 0 or 1 MUST be present if any date/time refers + to a timezone +X-COMPONENT 0+ + +VALARM 0 +VFREEBUSY 0 +VJOURNAL 0 +VTODO 0 + +3.2.4 ADD + + The "ADD" method in a "VEVENT" calendar component is used to add one + or more instances to an existing "VEVENT". Unlike the "REQUEST" + method, when using issuing an "ADD" method, the "Organizer" does not + send the full "VEVENT" description; only the new instance(s). The + "ADD" method is especially useful if there are instance-specific + properties to be preserved in a recurring "VEVENT". For instance, an + "Organizer" may have originally scheduled a weekly Thursday meeting. + At some point, several instances changed. Location or start time may + have changed, or some instances may have unique "DESCRIPTION" + properties. The "ADD" method allows the "Organizer" to add new + instances to an existing event using a single ITIP message without + redefining the entire recurring pattern. + + The "UID" must be that of the existing event. If the "UID" property + value in the "ADD" is not found on the recipient's calendar, then the + recipient SHOULD send a "REFRESH" to the "Organizer" in order to be + updated with the latest version of the "VEVENT". If an "Attendee" + implementation does not support the "ADD" method it should respond + with a "REQUEST-STATUS" value of 3.14 and ask for a "REFRESH". + + This method type is an iCalendar object that conforms to the + following property constraints: + + + + + + + + + +Silverberg, et. al. Standards Track [Page 23] + +RFC 2446 iTIP November 1998 + + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "ADD" +VEVENT 1 + DTSTAMP 1 + DTSTART 1 + ORGANIZER 1 + SEQUENCE 1 MUST be greater than 0 + SUMMARY 1 Can be null + UID 1 MUST match that of the original event + + ATTACH 0+ + ATTENDEE 0+ + CATEGORIES 0 or 1 This property MAY contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 Can be null + DTEND 0 or 1 if present DURATION MUST NOT be present + DURATION 0 or 1 if present DTEND MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PRIORITY 0 or 1 + RDATE 0+ + RELATED-TO 0+ + RESOURCES 0 or 1 This property MAY contain a list of values + RRULE 0+ + STATUS 0 or 1 MAY be one of TENTATIVE/CONFIRMED + TRANSP 0 or 1 + URL 0 or 1 + X-PROPERTY 0+ + + RECURRENCE-ID 0 + REQUEST-STATUS 0 + +VALARM 0+ +VTIMEZONE 0+ MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +VFREEBUSY 0 +VTODO 0 +VJOURNAL 0 + + + + +Silverberg, et. al. Standards Track [Page 24] + +RFC 2446 iTIP November 1998 + + +3.2.5 CANCEL + + The "CANCEL" method in a "VEVENT" calendar component is used to send + a cancellation notice of an existing event request to the + "Attendees". The message is sent by the "Organizer" of the event. For + a recurring event, either the whole event or instances of an event + may be cancelled. To cancel the complete range of recurring event, + the "UID" property value for the event MUST be specified and a + "RECURRENCE-ID" MUST NOT be specified in the "CANCEL" method. In + order to cancel an individual instance of the event, the + "RECURRENCE-ID" property value for the event MUST be specified in the + "CANCEL" method. + + There are two options for canceling a sequence of instances of a + recurring "VEVENT" calendar component: + + (a) the "RECURRENCE-ID" property for an instance in the sequence MUST + be specified with the "RANGE" property parameter value of + THISANDPRIOR (or THISANDFUTURE) to indicate cancellation of the + specified "VEVENT" calendar component and all instances before + (or after); or + + (b) individual recurrence instances may be cancelled by specifying + multiple "RECURRENCE-ID" properties corresponding to the + instances to be cancelled. + + When a "VEVENT" is cancelled, the "SEQUENCE" property value MUST be + incremented. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "CANCEL" + +VEVENT 1+ All must have the same UID + ATTENDEE 0+ MUST include all "Attendees" being removed + the event. MUST include all "Attendees" if + the entire event is cancelled. + DTSTAMP 1 + ORGANIZER 1 + SEQUENCE 1 + UID 1 MUST be the UID of the original REQUEST + + COMMENT 0 or 1 + ATTACH 0+ + CATEGORIES 0 or 1 This property may contain a list of values + + + +Silverberg, et. al. Standards Track [Page 25] + +RFC 2446 iTIP November 1998 + + + CLASS 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 + DTEND 0 or 1 if present DURATION MUST NOT be present + DTSTART 0 or 1 + DURATION 0 or 1 if present DTEND MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PRIORITY 0 or 1 + RDATE 0+ + RECURRENCE-ID 0 or 1 MUST be present if referring to one or + more or more recurring instances. + Otherwise it MUST NOT be present + RELATED-TO 0+ + RESOURCES 0 or 1 + RRULE 0+ + STATUS 0 or 1 MUST be set to CANCELLED. If uninviting + specific "Attendees" then MUST NOT be + included. + SUMMARY 0 or 1 + TRANSP 0 or 1 + URL 0 or 1 + X-PROPERTY 0+ + REQUEST-STATUS 0 + +VTIMEZONE 0+ MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +VTODO 0 +VJOURNAL 0 +VFREEBUSY 0 +VALARM 0 + +3.2.6 REFRESH + + The "REFRESH" method in a "VEVENT" calendar component is used by + "Attendees" of an existing event to request an updated description + from the event "Organizer". The "REFRESH" method must specify the + "UID" property of the event to update. A recurrence instance of an + event may be requested by specifying the "RECURRENCE-ID" property + corresponding to the associated event. The "Organizer" responds with + the latest description and version of the event. + + + + +Silverberg, et. al. Standards Track [Page 26] + +RFC 2446 iTIP November 1998 + + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "REFRESH" + +VEVENT 1 + ATTENDEE 1 MUST be the address of requestor + DTSTAMP 1 + ORGANIZER 1 + UID 1 MUST be the UID associated with original + REQUEST + COMMENT 0 or 1 + RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a + recurring calendar component. Otherwise + it must NOT be present. + X-PROPERTY 0+ + + ATTACH 0 + CATEGORIES 0 + CLASS 0 + CONTACT 0 + CREATED 0 + DESCRIPTION 0 + DTEND 0 + DTSTART 0 + DURATION 0 + EXDATE 0 + EXRULE 0 + GEO 0 + LAST-MODIFIED 0 + LOCATION 0 + PRIORITY 0 + RDATE 0 + RELATED-TO 0 + REQUEST-STATUS 0 + RESOURCES 0 + RRULE 0 + SEQUENCE 0 + STATUS 0 + SUMMARY 0 + TRANSP 0 + URL 0 + +X-COMPONENT 0+ + +VTODO 0 + + + +Silverberg, et. al. Standards Track [Page 27] + +RFC 2446 iTIP November 1998 + + +VJOURNAL 0 +VFREEBUSY 0 +VTIMEZONE 0 +VALARM 0 + +3.2.7 COUNTER + + The "COUNTER" method for a "VEVENT" calendar component is used by an + "Attendee" of an existing event to submit to the "Organizer" a + counter proposal to the event description. The "Attendee" sends this + message to the "Organizer" of the event. + + The counter proposal is an iCalendar object consisting of a VEVENT + calendar component describing the complete description of the + alternate event. + + The "Organizer" rejects the counter proposal by sending the + "Attendee" a VEVENT "DECLINECOUNTER" method. The "Organizer" accepts + the counter proposal by rescheduling the event as described in + section 3.2.2.1 Rescheduling an Event. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "COUNTER" + +VEVENT 1 + DTSTAMP 1 + DTSTART 1 + ORGANIZER 1 MUST be the "Organizer" of the original + event + SEQUENCE 1 MUST be present if value is greater than 0, + MAY be present if 0 + SUMMARY 1 Can be null + UID 1 MUST be the UID associated with the REQUEST + being countered + + ATTACH 0+ + ATTENDEE 0+ Can also be used to propose other + "Attendees" + CATEGORIES 0 or 1 This property may contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 + + + +Silverberg, et. al. Standards Track [Page 28] + +RFC 2446 iTIP November 1998 + + + DTEND 0 or 1 if present DURATION MUST NOT be present + DURATION 0 or 1 if present DTEND MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PRIORITY 0 or 1 + RDATE 0+ + RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a + recurring calendar component. Otherwise it + MUST NOT be present. + RELATED-TO 0+ + REQUEST-STATUS 0+ + RESOURCES 0 or 1 This property may contain a list of values + RRULE 0+ + STATUS 0 or 1 Value must be one of CONFIRMED/TENATIVE/ + CANCELLED + TRANSP 0 or 1 + URL 0 or 1 + X-PROPERTY 0+ + +VALARM 0+ +VTIMEZONE 0+ MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +VTODO 0 +VJOURNAL 0 +VFREEBUSY 0 + +3.2.8 DECLINECOUNTER + + The "DECLINECOUNTER" method in a "VEVENT" calendar component is used + by the "Organizer" of an event to reject a counter proposal submitted + by an "Attendee". The "Organizer" must send the "DECLINECOUNTER" + message to the "Attendee" that sent the "COUNTER" method to the + "Organizer". + + This method type is an iCalendar object that conforms to the + following property constraints: + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 29] + +RFC 2446 iTIP November 1998 + + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "DECLINECOUNTER" + +VEVENT 1 + DTSTAMP 1 + ORGANIZER 1 + UID 1 MUST, same UID specified in original + REQUEST and subsequent COUNTER + COMMENT 0 or 1 + RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a + recurring calendar component. Otherwise it + MUST NOT be present. + REQUEST-STATUS 0+ + SEQUENCE 0 OR 1 MUST be present if value is greater than 0, + MAY be present if 0 + X-PROPERTY 0+ + ATTACH 0 + ATTENDEE 0 + CATEGORIES 0 + CLASS 0 + CONTACT 0 + CREATED 0 + DESCRIPTION 0 + DTEND 0 + DTSTART 0 + DURATION 0 + EXDATE 0 + EXRULE 0 + GEO 0 + LAST-MODIFIED 0 + LOCATION 0 + PRIORITY 0 + RDATE 0 + RELATED-TO 0 + RESOURCES 0 + RRULE 0 + STATUS 0 + SUMMARY 0 + TRANSP 0 + URL 0 + +X-COMPONENT 0+ +VTODO 0 +VJOURNAL 0 +VFREEBUSY 0 +VTIMEZONE 0 +VALARM 0 + + + +Silverberg, et. al. Standards Track [Page 30] + +RFC 2446 iTIP November 1998 + + +3.3 Methods For VFREEBUSY Components + + This section defines the property set for the methods that are + applicable to the "VFREEBUSY" calendar component. Each of the methods + is defined using a restriction table. + + This document only addresses the transfer of busy time information. + Applications desiring free time information MUST infer this from + available busy time information. + + The busy time information within the iCalendar object MAY be grouped + into more than one "VFREEBUSY" calendar component. This capability + allows busy time periods to be grouped according to some common + periodicity, such as a calendar week, month, or year. In this case, + each "VFREEBUSY" calendar component MUST include the "ATTENDEE", + "DTSTART" and "DTEND" properties in order to specify the source of + the busy time information and the date and time interval over which + the busy time information covers. + + The "FREEBUSY" property value MAY include a list of values, separated + by the COMMA character ([US-ASCII] decimal 44). Alternately, multiple + busy time periods MAY be specified with multiple instances of the + "FREEBUSY" property. Both forms MUST be supported by implementations + conforming to this document. Duplicate busy time periods SHOULD NOT + be specified in an iCalendar object. However, two different busy time + periods MAY overlap. + + "FREEBUSY" properties should be sorted such that their values are in + ascending order, based on the start time, and then the end time, with + the earliest periods first. For example, today's busy time + information should appear after yesterday's busy time information. + And the busy time for this half-hour should appear after the busy + time for earlier today. + + Since events may span a day boundary, free busy time period may also + span a day boundary. Individual "A" requests busy time from + individuals "B", "C" and "D". Individual "B" and "C" replies with + busy time data to individual "A". Individual "D" does not support + busy time requests and does not reply with any data. If the transport + binding supports exception messages, then individual "D" returns an + "unsupported capability" message to individual "A4.34.3". + + The following summarizes the methods that are defined for the + "VFREEBUSY" calendar component. + + + + + + + +Silverberg, et. al. Standards Track [Page 31] + +RFC 2446 iTIP November 1998 + + + |================|==================================================| + | Method | Description | + |================|==================================================| + | PUBLISH | Publish unsolicited busy time data. | + | REQUEST | Request busy time data. | + | REPLY | Reply to a busy time request. | + |================|==================================================| + +3.3.1 PUBLISH + + The "PUBLISH" method in a "VFREEBUSY" calendar component is used to + publish busy time data. The method may be sent from one CU to any + other. The purpose of the method is to provide a message for sending + unsolicited busy time data. That is, the busy time data is not being + sent as a "REPLY" to the receipt of a "REQUEST" method. + + The "ATTENDEE" property must be specified in the busy time + information. The value is the CU address of the originator of the + busy time information. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "PUBLISH" + +VFREEBUSY 1+ + DTSTAMP 1 + DTSTART 1 DateTime values must be in UTC + DTEND 1 DateTime values must be in UTC + FREEBUSY 1+ MUST be BUSYTIME. Multiple instances are + allowed. Multiple instances must be sorted + in ascending order + ORGANIZER 1 MUST contain the address of originator of + busy time data. + + COMMENT 0 or 1 + CONTACT 0+ + X-PROPERTY 0+ + URL 0 or 1 Specifies busy time URL + + ATTENDEE 0 + DURATION 0 + REQUEST-STATUS 0 + UID 0 + +X-COMPONENT 0+ + + + +Silverberg, et. al. Standards Track [Page 32] + +RFC 2446 iTIP November 1998 + + +VEVENT 0 +VTODO 0 +VJOURNAL 0 +VTIMEZONE 0 +VALARM 0 + +3.3.2 REQUEST + + The "REQUEST" method in a "VFREEBUSY" calendar component is used to + ask a "Calendar User" for their busy time information. The request + may be for a busy time information bounded by a specific date and + time interval. + + This message only permits requests for busy time information. The + message is sent from a "Calendar User" requesting the busy time + information to one or more intended recipients. + + If the originator of the "REQUEST" method is not authorized to make a + busy time request on the recipient's calendar system, then an + exception message SHOULD be returned in a "REPLY" method, but no busy + time data need be returned. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "REQUEST" + +VFREEBUSY 1 + ATTENDEE 1+ contain the address of the calendar store + DTEND 1 DateTime values must be in UTC + DTSTAMP 1 + DTSTART 1 DateTime values must be in UTC + ORGANIZER 1 MUST be the request originator's address + UID 1 + COMMENT 0 or 1 + CONTACT 0+ + X-PROPERTY 0+ + + FREEBUSY 0 + DURATION 0 + REQUEST-STATUS 0 + URL 0 + +X-COMPONENT 0+ +VALARM 0 +VEVENT 0 + + + +Silverberg, et. al. Standards Track [Page 33] + +RFC 2446 iTIP November 1998 + + +VTODO 0 +VJOURNAL 0 +VTIMEZONE 0 + +3.3.3 REPLY + + The "REPLY" method in a "VFREEBUSY" calendar component is used to + respond to a busy time request. The method is sent by the recipient + of a busy time request to the originator of the request. + + The "REPLY" method may also be used to respond to an unsuccessful + "REQUEST" method. Depending on the "REQUEST-STATUS" value, no busy + time information may be returned. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "REPLY" + +VFREEBUSY 1 + ATTENDEE 1 (address of recipient replying) + DTSTAMP 1 + DTEND 1 DateTime values must be in UTC + DTSTART 1 DateTime values must be in UTC + FREEBUSY 1+ (values MUST all be of the same data + type. Multiple instances are allowed. + Multiple instances MUST be sorted in + ascending order. Values MAY NOT overlap) + ORGANIZER 1 MUST be the request originator's address + UID 1 + + COMMENT 0 or 1 + CONTACT 0+ + REQUEST-STATUS 0+ + URL 0 or 1 (specifies busy time URL) + X-PROPERTY 0+ + DURATION 0 + SEQUENCE 0 + +X-COMPONENT 0+ +VALARM 0 +VEVENT 0 +VTODO 0 +VJOURNAL 0 +VTIMEZONE 0 + + + + +Silverberg, et. al. Standards Track [Page 34] + +RFC 2446 iTIP November 1998 + + +3.4 Methods For VTODO Components + + This section defines the property set for the methods that are + applicable to the "VTODO" calendar component. Each of the methods is + defined using a restriction table that specifies the property + constraints that define the particular method. + + The following summarizes the methods that are defined for the "VTODO" + calendar component. + + +================+==================================================+ + | Method | Description | + |================+==================================================| + | PUBLISH | Post notification of a VTODO. Used primarily as | + | | a method of advertising the existence of a VTODO.| + | | | + | REQUEST | Assign a VTODO. This is an explicit assignment to| + | | one or more Calendar Users. The REQUEST method | + | | is also used to update or change an existing | + | | VTODO. Clients that cannot handle REQUEST MAY | + | | degrade the method to treat it as a PUBLISH. | + | | | + | REPLY | Reply to a VTODO request. Attendees MAY set | + | | PARTSTAT to ACCEPTED, DECLINED, TENTATIVE, | + | | DELEGATED, PARTIAL, and COMPLETED. | + | | | + | ADD | Add one or more instances to an existing to-do. | + | | | + | CANCEL | Cancel one or more instances of an existing | + | | to-do. | + | | | + | REFRESH | A request sent to a VTODO Organizer asking for | + | | the latest version of a VTODO. | + | | | + | COUNTER | Counter a REQUEST with an alternative proposal. | + | | | + | DECLINECOUNTER | Decline a counter proposal by an Attendee. | + +================+==================================================+ + +3.4.1 PUBLISH + + The "PUBLISH" method in a "VTODO" calendar component has no + associated response. It is simply a posting of an iCalendar object + that maybe added to a calendar. It MUST have an "Organizer". It MUST + NOT have "Attendees". Its expected usage is for encapsulating an + arbitrary "VTODO" calendar component as an iCalendar object. The + "Organizer" MAY subsequently update (with another "PUBLISH" method), + add instances to (with an "ADD" method), or cancel (with a "CANCEL" + + + +Silverberg, et. al. Standards Track [Page 35] + +RFC 2446 iTIP November 1998 + + + method) a previously published "VTODO" calendar component. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "PUBLISH" +VTODO 1+ + DTSTAMP 1 + DTSTART 1 + ORGANIZER 1 + PRIORITY 1 + SEQUENCE 0 or 1 MUST be present if value is greater than + 0, MAY be present if 0 + SUMMARY 1 Can be null. + UID 1 + + ATTACH 0+ + CATEGORIES 0 or 1 This property may contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 Can be null + DUE 0 or 1 If present DURATION MUST NOT be present + DURATION 0 or 1 If present DUE MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PERCENT-COMPLETE 0 or 1 + RDATE 0+ + RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a + recurring calendar component. Otherwise + it MUST NOT be present. + + RELATED-TO 0+ + RESOURCES 0 or 1 This property may contain a list of values + RRULE 0+ +STATUS 0 or 1 MAY be one of COMPLETED/NEEDS ACTION/IN- + PROCESS/CANCELLED + URL 0 or 1 + X-PROPERTY 0+ + + ATTENDEE 0 + REQUEST-STATUS 0 + + + +Silverberg, et. al. Standards Track [Page 36] + +RFC 2446 iTIP November 1998 + + +VTIMEZONE 0+ MUST be present if any date/time refers to + a timezone +VALARM 0+ +X-COMPONENT 0+ + +VFREEBUSY 0 +VEVENT 0 +VJOURNAL 0 + +3.4.2 REQUEST + + The "REQUEST" method in a "VTODO" calendar component provides the + following scheduling functions: + + . Assign a to-do to one or more "Calendar Users"; + . Reschedule an existing to-do; + . Update the details of an existing to-do, without rescheduling + it; + . Update the completion status of "Attendees" of an existing + to-do, without rescheduling it; + . Reconfirm an existing to-do, without rescheduling it; + . Delegate/reassign an existing to-do to another "Calendar User". + + The assigned "Calendar Users" are identified in the "VTODO" calendar + component by individual "ATTENDEE;ROLE=REQ-PARTICIPANT" property + value sequences. + + The originator of a "REQUEST" is the "Organizer" of the to-do. The + recipient of a "REQUEST" is the "Calendar User" assigned the to-do. + The "Attendee" uses the "REPLY" method to convey their acceptance and + completion status to the "Organizer" of the "REQUEST". + + The "UID", "SEQUENCE", and "DTSTAMP" properties are used to + distinguish the various uses of the "REQUEST" method. If the "UID" + property value in the "REQUEST" is not found on the recipient's + calendar, then the "REQUEST" is for a new to-do. If the "UID" + property value is found on the recipient's calendar, then the + "REQUEST" is a rescheduling, an update, or a reconfirm of the "VTODO" + calendar object. + + If the "Organizer" of the "REQUEST" method is not authorized to make + a to-do request on the "Attendee's" calendar system, then an + exception is returned in the "REQUEST-STATUS" property of a + subsequent "REPLY" method, but no scheduling action is performed. + + For the "REQUEST" method, multiple "VTODO" components in a single + iCalendar object are only permitted when for components with the same + "UID" property. That is, a series of recurring events may have + + + +Silverberg, et. al. Standards Track [Page 37] + +RFC 2446 iTIP November 1998 + + + instance-specific information. In this case, multiple "VTODO" + components are needed to express the entire series. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "REQUEST" +VTODO 1+ All components must have the same UID + ATTENDEE 1+ + DTSTAMP 1 + DTSTART 1 + ORGANIZER 1 + PRIORITY 1 + SEQUENCE 0 or 1 MUST be present if value is greater than + 0, MAY be present if 0 + SUMMARY 1 Can be null. + UID 1 + + ATTACH 0+ + CATEGORIES 0 or 1 This property may contain a list of + values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 Can be null + DUE 0 or 1 If present DURATION MUST NOT be present + DURATION 0 or 1 If present DUE MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PERCENT-COMPLETE 0 or 1 + RDATE 0+ + RECURRENCE-ID 0 or 1 present if referring to an instance of a + recurring calendar component. Otherwise + it MUST NOT be present. + RELATED-TO 0+ + RESOURCES 0 or 1 This property may contain a list of + values + RRULE 0+ + STATUS 0 or 1 MAY be one of COMPLETED/NEEDS ACTION/IN- + PROCESS + URL 0 or 1 + X-PROPERTY 0+ + + + +Silverberg, et. al. Standards Track [Page 38] + +RFC 2446 iTIP November 1998 + + + REQUEST-STATUS 0 + +VALARM 0+ + +VTIMEZONE 0+ MUST be present if any date/time refers + to a timezone +X-COMPONENT 0+ + +VEVENT 0 +VFREEBUSY 0 +VJOURNAL 0 + +3.4.2.1 REQUEST for Rescheduling a VTODO + + The "REQUEST" method may be used to reschedule a "VTODO" calendar + component. + + Rescheduling a "VTODO" calendar component involves a change to the + existing "VTODO" calendar component in terms of its start or due time + or recurrence intervals and possibly the description. If the + recipient CUA of a "REQUEST" method finds that the "UID" property + value already exists on the calendar, but that the "SEQUENCE" + property value in the "REQUEST" is greater than the value for the + existing VTODO, then the "REQUEST" method describes a rescheduling of + the "VTODO" calendar component. + +3.4.2.2 REQUEST for Update or Reconfirmation of a VTODO + + The "REQUEST" method may be used to update or reconfirm a "VTODO" + calendar component. Reconfirmation is merely an update of "Attendee" + completion status or overall "VTODO" calendar component status. + + An update to an existing "VTODO" calendar component does not involve + changes to the start or due time or recurrence intervals, nor + generally to the description for the "VTODO" calendar component. If + the recipient CUA of a "REQUEST" method finds that the "UID" property + value already exists on the calendar and that the "SEQUENCE" property + value in the "REQUEST" is the same as the value for the existing + event, then the "REQUEST" method describes an update of the "VTODO" + calendar component details, but not a rescheduling of the "VTODO" + calendar component. + + The update "REQUEST" is the appropriate response to a "REFRESH" + method sent from an "Attendee" to the "Organizer" of a "VTODO" + calendar component. + + Unsolicited "REQUEST" methods MAY be sent by the "Organizer" of a + "VTODO" calendar component. The unsolicited "REQUEST" methods are + + + +Silverberg, et. al. Standards Track [Page 39] + +RFC 2446 iTIP November 1998 + + + used to update the details of the "VTODO" (without rescheduling it or + updating the completion status of "Attendees") or the "VTODO" + calendar component itself (i.e., reconfirm the "VTODO"). + +3.4.2.3 REQUEST for Delegating a VTODO + + The "REQUEST" method is also used to delegate or reassign ownership + of a "VTODO" calendar component to another "Calendar User". For + example, it may be used to delegate an "Attendee's" role (i.e. + "chair", or "participant") for a "VTODO" calendar component. The + "REQUEST" method is sent by one of the "Attendees" of an existing + + "VTODO" calendar component to some other individual. An "Attendee" of + a "VTODO" calendar component MUST NOT delegate to the "Organizer" of + the event. + + For the purposes of this description, the "Attendee" delegating the + "VTODO" calendar component is referred to as the "Delegator". The + "Attendee" receiving the delegation request is referred to as the + "Delegate". + + The "Delegator" of a "VTODO" calendar component MUST forward the + existing "REQUEST" method for a "VTODO" calendar component to the + "Delegate". The "VTODO" calendar component description MUST include + the "Delegator's" up-to-date "VTODO" calendar component definition. + The "REQUEST" method MUST also include an "ATTENDEE" property with + the calendar address of the "Delegate". The "Delegator" MUST also + send a "REPLY" method back to the "Organizer" with the "Delegator's" + "Attendee" property "partstat" parameter value set to "DELEGATED". In + addition, the "delegated-to" parameter MUST be included with the + calendar address of the "Delegate". A response to the delegation + "REQUEST" is sent from the "Delegate" to the "Organizer" and + optionally, to the "Delegator". The "REPLY" method from the + "Delegate" SHOULD include the "ATTENDEE" property with their calendar + address and the "delegated-from" parameter with the value of the + "Delegator's" calendar address. + + The delegation "REQUEST" method MUST assign a value for the "RSVP" + property parameter associated with the "Delegator's" "Attendee" + property to that of the "Delegate's" "ATTENDEE" property. For example + if the "Delegator's" "ATTENDEE" property specifies "RSVP=TRUE", then + the "Delegate's" "ATTENDEE" property MUST specify "RSVP=TRUE". + +3.4.2.4 REQUEST Forwarded To An Uninvited Calendar User + + An "Attendee" assigned a "VTODO" calendar component may send the + "VTODO" calendar component to another new CU, not previously + associated with the "VTODO" calendar component. The current + + + +Silverberg, et. al. Standards Track [Page 40] + +RFC 2446 iTIP November 1998 + + + "Attendee" assigned the "VTODO" calendar component does this by + forwarding the original "REQUEST" method to the new CU. The new CU + can send a "REPLY" to the "Organizer" of the "VTODO" calendar + component. The reply contains an "ATTENDEE" property for the new CU. + + The "Organizer" ultimately decides whether or not the new CU becomes + part of the to-do and is not obligated to do anything with a "REPLY" + from a new (uninvited) CU. If the "Organizer" does not want the new + CU to be part of the to-do, the new "ATTENDEE" property is not added + to the "VTODO" calendar component. The "Organizer" MAY send the CU a + "CANCEL" message to indicate that they will not be added to the to- + do. If the "Organizer" decides to add the new CU, the new "ATTENDEE" + property is added to the "VTODO" calendar component. Furthermore, the + "Organizer" is free to change any "ATTENDEE" property parameter from + the values supplied by the new CU to something the "Organizer" + considers appropriate. + +3.4.2.5 REQUEST Updated Attendee Status + + An "Organizer" of a "VTODO" may request an updated status from one or + more "Attendees". The "Organizer" sends a "REQUEST" method to the + "Attendee" with the "ATTENDEE;RSVP=TRUE" property sequence. The + "SEQUENCE" property for the "VTODO" is not changed from its previous + value. A recipient determines that the only change in the "REQUEST" + is that their "RSVP" property parameter indicates a request for an + updated status. The recipient SHOULD respond with a "REPLY" method + indicating their current status with respect to the "REQUEST". + +3.4.3 REPLY + + The "REPLY" method in a "VTODO" calendar component is used to respond + (e.g., accept or decline) to a request or to reply to a delegation + request. It is also used by an "Attendee" to update their completion + status. When used to provide a delegation response, the "Delegator" + MUST include the calendar address of the "Delegate" in the + "delegated-to" parameter of the "Delegator's" "ATTENDEE" property. + The "Delegate" MUST include the calendar address of the "Delegator" + on the "delegated-from" parameter of the "Delegate's" "ATTENDEE" + property. + + The "REPLY" method MAY also be used to respond to an unsuccessful + "VTODO" calendar component "REQUEST" method. Depending on the + "REQUEST-STATUS" value, no scheduling action may have been performed. + + The "Organizer" of a "VTODO" calendar component MAY receive a "REPLY" + method from a "Calendar User" not in the original "REQUEST". For + example, a "REPLY" method MAY be received from a "Delegate" of a + "VTODO" calendar component. In addition, the "REPLY" method MAY be + + + +Silverberg, et. al. Standards Track [Page 41] + +RFC 2446 iTIP November 1998 + + + received from an unknown "Calendar User", having been forwarded the + "REQUEST" by an original "Attendee" of the "VTODO" calendar + component. This uninvited "Attendee" MAY be accepted, or the + "Organizer" MAY cancel the "VTODO" calendar component for the + uninvited "Attendee" by sending them a "CANCEL" method. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- --------------------------------------------- +METHOD 1 MUST be "REPLY" +VTODO 1+ All component MUST have the same UID + ATTENDEE 1+ + DTSTAMP 1 + ORGANIZER 1 + REQUEST-STATUS 1+ + UID 1 MUST must be the address of the replying + attendee + + ATTACH 0+ + CATEGORIES 0 or 1 This property may contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 + DTSTART 0 or 1 + DUE 0 or 1 If present DURATION MUST NOT be present + DURATION 0 or 1 If present DUE MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PERCENT-COMPLETE 0 or 1 + PRIORITY 0 or 1 + RDATE 0+ + RELATED-TO 0+ + RESOURCES 0 or 1 This property may contain a list of values + RRULE 0+ + RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a + Recurring calendar component. Otherwise it + MUST NOT be present + SEQUENCE 0 or 1 MUST be the sequence number of + the original REQUEST if greater than 0. + MAY be present if 0. + STATUS 0 or 1 + + + +Silverberg, et. al. Standards Track [Page 42] + +RFC 2446 iTIP November 1998 + + + SUMMARY 0 or 1 Can be null + URL 0 or 1 + X-PROPERTY 0+ + +VTIMEZONE 0 or 1 MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +VALARM 0 +VEVENT 0 +VFREEBUSY 0 + +3.4.4 ADD + + The "ADD" method in a "VTODO" calendar component is used to add one + or more instances to an existing to-do. + + If the "UID" property value in the "ADD" is not found on the + recipient's calendar, then the recipient SHOULD send a "REFRESH" to + the "Organizer" in order to be updated with the latest version of the + "VTODO". If an "Attendee" implementation does not support the "ADD" + method it should respond with a "REQUEST-STATUS" value of 5.3 and ask + for a "REFRESH". + + The "SEQUENCE" property value is incremented as the sequence of to- + dos has changed. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "ADD" +VTODO 1 + DTSTAMP 1 + ORGANIZER 1 + PRIORITY 1 + SEQUENCE 1 MUST be greater than 0 + SUMMARY 1 Can be null. + UID 1 MUST match that of the original to-do + + ATTACH 0+ + ATTENDEE 0+ + CATEGORIES 0 or 1 This property may contain a list of + values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + + + +Silverberg, et. al. Standards Track [Page 43] + +RFC 2446 iTIP November 1998 + + + CREATED 0 or 1 + DESCRIPTION 0 or 1 Can be null + DTSTART 0 or 1 + DUE 0 or 1 If present DURATION MUST NOT be present + DURATION 0 or 1 If present DUE MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PERCENT-COMPLETE 0 or 1 + RDATE 0+ + RELATED-TO 0+ + RESOURCES 0 or 1 This property may contain a list of + values + RRULE 0+ + STATUS 0 or 1 MAY be one of COMPLETED/NEEDS ACTION/IN- + PROCESS + URL 0 or 1 + X-PROPERTY 0+ + + RECURRENCE-ID 0 + REQUEST-STATUS 0 + +VALARM 0+ +VTIMEZONE 0+ MUST be present if any date/time refers + to a timezone +X-COMPONENT 0+ + +VEVENT 0 +VJOURNAL 0 +VFREEBUSY 0 + +3.4.5 CANCEL + + The "CANCEL" method in a "VTODO" calendar component is used to send a + cancellation notice of an existing "VTODO" calendar request to the + "Attendees". The message is sent by the "Organizer" of a "VTODO" + calendar component to the "Attendees" of the "VTODO" calendar + component. For a recurring "VTODO" calendar component, either the + whole "VTODO" calendar component or instances of a "VTODO" calendar + component may be cancelled. To cancel the complete range of a + recurring "VTODO" calendar component, the "UID" property value for + the "VTODO" calendar component MUST be specified and a "RECURRENCE- + ID" MUST NOT be specified in the "CANCEL" method. In order to cancel + an individual instance of a recurring "VTODO" calendar component, the + "RECURRENCE-ID" property value for the "VTODO" calendar component + MUST be specified in the "CANCEL" method. + + + +Silverberg, et. al. Standards Track [Page 44] + +RFC 2446 iTIP November 1998 + + + There are two options for canceling a sequence of instances of a + recurring "VTODO" calendar component: + + (a) the "RECURRENCE-ID" property for an instance in the sequence MUST + be specified with the "RANGE" property parameter value of + THISANDPRIOR (or THISANDFUTURE) to indicate cancellation of the + specified "VTODO" calendar component and all instances before (or + after); or + + (b) individual recurrence instances may be cancelled by specifying + multiple "RECURRENCE-ID" properties corresponding to the + instances to be cancelled. + + When a "VTODO" is cancelled, the "SEQUENCE" property value MUST be + incremented. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- --------------------------------------------- +METHOD 1 MUST be "CANCEL" +VTODO 1 + ATTENDEE 0+ include all "Attendees" being removed from + the todo. MUST include all "Attendees" if + the entire todo is cancelled. + UID 1 MUST echo original UID + DTSTAMP 1 + ORGANIZER 1 + SEQUENCE 1 + + ATTACH 0+ + CATEGORIES 0 or 1 This property MAY contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 + DTSTART 0 or 1 + DUE 0 or 1 If present DURATION MUST NOT be present + DURATION 0 or 1 If present DUE MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PERCENT-COMPLETE 0 or 1 + RDATE 0+ + + + +Silverberg, et. al. Standards Track [Page 45] + +RFC 2446 iTIP November 1998 + + + RECURRENCE-ID 0 or 1 MUST only if referring to one or more + instances of a recurring calendar + component. Otherwise it MUST NOT be + present. + RELATED-TO 0+ + RESOURCES 0 or 1 This property MAY contain a list of values + RRULE 0+ + PRIORITY 0 or 1 + STATUS 0 or 1 If present it MUST be set to "CANCELLED". + MUST NOT be used if purpose is to remove + "ATTENDEES" rather than cancel the entire + VTODO. + URL 0 or 1 + X-PROPERTY 0+ + + REQUEST-STATUS 0 + +VTIMEZONE 0 or 1 MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +VALARM 0 +VEVENT 0 +VFREEBUSY 0 + +3.4.6 REFRESH + + The "REFRESH" method in a "VTODO" calendar component is used by + "Attendees" of an existing "VTODO" calendar component to request an + updated description from the "Organizer" of the "VTODO" calendar + component. The "Organizer" of the "VTODO" calendar component MAY use + this method to request an updated status from the "Attendees". The + "REFRESH" method MUST specify the "UID" property corresponding to the + "VTODO" calendar component needing update. + + A refresh of a recurrence instance of a "VTODO" calendar component + may be requested by specifying the "RECURRENCE-ID" property + corresponding to the associated "VTODO" calendar component. The + "Organizer" responds with the latest description and rendition of the + "VTODO" calendar component. In most cases this will be a REQUEST + unless the "VTODO" has been cancelled, in which case the ORGANIZER + MUST send a "CANCEL". This method is intended to facilitate machine + processing of requests for updates to a "VTODO" calendar component. + + This method type is an iCalendar object that conforms to the + following property constraints: + + + + + +Silverberg, et. al. Standards Track [Page 46] + +RFC 2446 iTIP November 1998 + + +Component/Property Presence +------------------- --------------------------------------------- +METHOD 1 MUST be "REFRESH" +VTODO 1 + ATTENDEE 1 + DTSTAMP 1 + UID 1 MUST echo original UID + + RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a + Recurring calendar component. Otherwise it + MUST NOT be present + X-PROPERTY 0+ + + ATTACH 0 + CATEGORIES 0 + CLASS 0 + COMMENT 0 + CONTACT 0 + CREATED 0 + DESCRIPTION 0 + DTSTART 0 + DUE 0 + DURATION 0 + EXDATE 0 + EXRULE 0 + GEO 0 + LAST-MODIFIED 0 + LOCATION 0 + ORGANIZER 0 + PERCENT-COMPLETE 0 + PRIORITY 0 + RDATE 0 + RELATED-TO 0 + REQUEST-STATUS 0 + RESOURCES 0 + RRULE 0 + SEQUENCE 0 + STATUS 0 + URL 0 + +X-COMPONENT 0+ + +VALARM 0 +VEVENT 0 +VFREEBUSY 0 +VTIMEZONE 0 + + + + + +Silverberg, et. al. Standards Track [Page 47] + +RFC 2446 iTIP November 1998 + + +3.4.7 COUNTER + + The "COUNTER" method in a "VTODO" calendar component is used by an + "Attendee" of an existing "VTODO" calendar component to submit to the + "Organizer" a counter proposal for the "VTODO" calendar component. + The "Attendee" sends the message to the "Organizer" of the "VTODO" + calendar component. + + The counter proposal is an iCalendar object consisting of a "VTODO" + calendar component describing the complete description of the + alternate "VTODO" calendar component. + + The "Organizer" rejects the counter proposal by sending the + "Attendee" a "DECLINECOUNTER" method. The "Organizer" accepts the + counter proposal by sending all of the "Attendees" of the "VTODO" + calendar component a "REQUEST" method rescheduling the "VTODO" + calendar component. In the latter case, the "Organizer" SHOULD reset + the individual "RSVP" property parameter values to TRUE on each + "ATTENDEE" property; in order to force a response by the "Attendees". + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "COUNTER" +VTODO 1 + ATTENDEE 1+ + DTSTAMP 1 + ORGANIZER 1 + PRIORITY 1 + SUMMARY 1 Can be null + UID 1 + + ATTACH 0+ + CATEGORIES 0 or 1 This property MAY contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 Can be null + DTSTART 0 or 1 + DUE 0 or 1 If present DURATION MUST NOT be present + DURATION 0 or 1 If present DUE MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + + + +Silverberg, et. al. Standards Track [Page 48] + +RFC 2446 iTIP November 1998 + + + LOCATION 0 or 1 + PERCENT-COMPLETE 0 or 1 + RDATE 0+ + RECURRENCE-ID 0 or 1 MUST only 3.5if referring to an instance of a + recurring calendar component. Otherwise it + MUST NOT be present. + RELATED-TO 0+ + REQUEST-STATUS 0+ + RESOURCES 0 or 1 This property MAY contain a list of values + RRULE 0 or 1 + SEQUENCE 0 or 1 MUST echo the original SEQUENCE number. + MUST be present if non-zero. MAY be present + if zero. + STATUS 0 or 1 MAY be one of COMPLETED/NEEDS ACTION/IN- + PROCESS/CANCELLED + URL 0 or 1 + X-PROPERTY 0+ + + +VALARM 0+ +VTIMEZONE 0 or 1 MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +VEVENT 0 +VFREEBUSY 0 + +3.4.8 DECLINECOUNTER + + The "DECLINECOUNTER" method in a "VTODO" calendar component is used + by an "Organizer" of "VTODO" calendar component to reject a counter + proposal offered by one of the "Attendees". The "Organizer" sends the + message to the "Attendee" that sent the "COUNTER" method to the + "Organizer". + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- --------------------------------------------- +METHOD 1 MUST be "DECLINECOUNTER" + +VTODO 1 + ATTENDEE 1+ MUST for all attendees + DTSTAMP 1 + ORGANIZER 1 + SEQUENCE 1 MUST echo the original SEQUENCE number + UID 1 MUST echo original UID + + + +Silverberg, et. al. Standards Track [Page 49] + +RFC 2446 iTIP November 1998 + + + ATTACH 0+ + CATEGORIES 0 or 1 This property may contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 + DTSTART 0 or 1 + DUE 0 or 1 If present DURATION MUST NOT be present + DURATION 0 or 1 If present DUE MUST NOT be present + EXDATE 0+ + EXRULE 0+ + GEO 0 or 1 + LAST-MODIFIED 0 or 1 + LOCATION 0 or 1 + PERCENT-COMPLETE 0 or 1 + PRIORITY 0 or 1 + RDATE 0+ + RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a + recurring calendar component. Otherwise + it MUST NOT be present. + RELATED-TO 0+ + REQUEST-STATUS 0+ + RESOURCES 0 or 1 This property MAY contain a list of values + RRULE 0+ + STATUS 0 or 1 MAY be one of COMPLETED/NEEDS ACTION/IN- + PROCESS + URL 0 or 1 + X-PROPERTY 0+ + +VTIMEZONE 0+ MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +VALARM 0 +VEVENT 0 +VFREEBUSY 0 + +3.5 Methods For VJOURNAL Components + + This section defines the property set for the methods that are + applicable to the "VJOURNAL" calendar component. + + The following summarizes the methods that are defined for the + "VJOURNAL" calendar component. + + + + + + +Silverberg, et. al. Standards Track [Page 50] + +RFC 2446 iTIP November 1998 + + + +================+==================================================+ + | Method | Description | + |================+==================================================| + | PUBLISH | Post a journal entry. Used primarily as a method | + | | of advertising the existence of a journal entry. | + | | | + | ADD | Add one or more instances to an existing journal | + | | entry. | + | | | + | CANCEL | Cancel one or more instances of an existing | + | | journal entry. | + +================+==================================================+ + +3.5.1 PUBLISH + + The "PUBLISH" method in a "VJOURNAL" calendar component has no + associated response. It is simply a posting of an iCalendar object + that may be added to a calendar. It MUST have an "Organizer". It MUST + NOT have "Attendees". The expected usage is for encapsulating an + + arbitrary journal entry as an iCalendar object. The "Organizer" MAY + subsequently update (with another "PUBLISH" method) or cancel (with a + "CANCEL" method) a previously published journal entry. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "PUBLISH" +VJOURNAL 1+ + DESCRIPTION 1 Can be null. + DTSTAMP 1 + DTSTART 1 + ORGANIZER 1 + UID 1 + + ATTACH 0+ + CATEGORIES 0 or 1 This property MAY contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + EXDATE 0+ + EXRULE 0+ + LAST-MODIFIED 0 or 1 + RDATE 0+ + RECURRENCE-ID 0 or 1 MUST only if referring to an instance of a + + + +Silverberg, et. al. Standards Track [Page 51] + +RFC 2446 iTIP November 1998 + + + recurring calendar component. Otherwise + it MUST NOT be present. + RELATED-TO 0+ + RRULE 0+ + SEQUENCE 0 or 1 MUST echo the original SEQUENCE number. + MUST be present if non-zero. MAY be + present if zero. + STATUS 0 or 1 MAY be one of DRAFT/FINAL/CANCELLED + SUMMARY 0 or 1 Can be null + URL 0 or 1 + X-PROPERTY 0+ + + ATTENDEE 0 + +VALARM 0+ +VTIMEZONE 0+ MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +VEVENT 0 +VFREEBUSY 0 +VTODO 0 + +3.5.2 ADD + + The "ADD" method in a "VJOURNAL" calendar component is used to add + one or more instances to an existing "VJOURNAL" entry. There is no + response to the "Organizer". + + If the "UID" property value in the "ADD" is not found on the + recipient's calendar, then the recipient MAY treat the "ADD" as a + "PUBLISH". + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- ---------------------------------------------- +METHOD 1 MUST be "ADD" +VJOURNAL 1 + DESCRIPTION 1 Can be null. + DTSTAMP 1 + DTSTART 1 + ORGANIZER 1 + SEQUENCE 1 MUST be greater than 0 + UID 1 MUST match that of the original journal + + ATTACH 0+ + + + +Silverberg, et. al. Standards Track [Page 52] + +RFC 2446 iTIP November 1998 + + + CATEGORIES 0 or 1 This property MAY contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + EXDATE 0+ + EXRULE 0+ + LAST-MODIFIED 0 or 1 + RDATE 0+ + RELATED-TO 0+ + RRULE 0+ + STATUS 0 or 1 MAY be one of DRAFT/FINAL/CANCELLED + SUMMARY 0 or 1 Can be null + URL 0 or 1 + X-PROPERTY 0+ + + ATTENDEE 0 + RECURRENCE-ID 0 + +VALARM 0+ +VTIMEZONE 0 or 1 MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ + +VEVENT 0 +VFREEBUSY 0 +VTODO 0 + +3.5.3 CANCEL + + The "CANCEL" method in a "VJOURNAL" calendar component is used to + send a cancellation notice of an existing journal entry. The message + is sent by the "Organizer" of a journal entry. For a recurring + journal entry, either the whole journal entry or instances of a + journal entry may be cancelled. To cancel the complete range of a + recurring journal entry, the "UID" property value for the journal + entry MUST be specified and a "RECURRENCE-ID" property MUST NOT be + specified in the "CANCEL" method. In order to cancel an individual + instance of the journal entry, the "RECURRENCE-ID" property value for + the journal entry MUST be specified in the "CANCEL" method. + + There are two options for canceling a sequence of instances of a + recurring "VJOURNAL" calendar component: + + + + + + + + +Silverberg, et. al. Standards Track [Page 53] + +RFC 2446 iTIP November 1998 + + + (a) the "RECURRENCE-ID" property for an instance in the sequence MUST + be specified with the "RANGE" property parameter value of + THISANDPRIOR (or THISANDFUTURE) to indicate cancellation of the + specified "VTODO" calendar component and all instances before (or + after); or + + (b) individual recurrence instances may be cancelled by specifying + multiple "RECURRENCE-ID" properties corresponding to the + instances to be cancelled. + + When a "VJOURNAL" is cancelled, the "SEQUENCE" property value MUST be + incremented. + + This method type is an iCalendar object that conforms to the + following property constraints: + +Component/Property Presence +------------------- --------------------------------------------- +METHOD 1 MUST be "CANCEL" +VJOURNAL 1+ All MUST have the same UID + DTSTAMP 1 + ORGANIZER 1 + SEQUENCE 1 + UID 1 MUST be the UID of the original REQUEST + + ATTACH 0+ + ATTENDEE 0+ + CATEGORIES 0 or 1 This property MAY contain a list of values + CLASS 0 or 1 + COMMENT 0 or 1 + CONTACT 0+ + CREATED 0 or 1 + DESCRIPTION 0 or 1 + DTSTART 0 or 1 + EXDATE 0+ + EXRULE 0+ + LAST-MODIFIED 0 or 1 + RDATE 0+ + RECURRENCE-ID 0 or 1 only if referring to an instance of a + recurring calendar component. Otherwise + it MUST NOT be present. + RELATED-TO 0+ + RRULE 0+ + STATUS 0 or 1 MAY be present, must be "CANCELLED" if + present + SUMMARY 0 or 1 + URL 0 or 1 + X-PROPERTY 0+ + + + +Silverberg, et. al. Standards Track [Page 54] + +RFC 2446 iTIP November 1998 + + + REQUEST-STATUS 0 + +VTIMEZONE 0+ MUST be present if any date/time refers to + a timezone +X-COMPONENT 0+ +VALARM 0 +VEVENT 0 +VFREEBUSY 0 +VTODO 0 + +3.6 Status Replies + + The "REQUEST-STATUS" property may include the following values: + +|==============+============================+=========================| +| Short Return | Longer Return Status | Offending Data | +| Status Code | Description | | +|==============+============================+=========================| +| 2.0 | Success. | None. | +|==============+============================+=========================| +| 2.1 | Success but fallback taken | Property name and value | +| | on one or more property | MAY be specified. | +| | values. | | +|==============+============================+=========================| +| 2.2 | Success, invalid property | Property name MAY be | +| | ignored. | specified. | +|==============+============================+=========================| +| 2.3 | Success, invalid property | Property parameter name | +| | parameter ignored. | and value MAY be | +| | | specified. | +|==============+============================+=========================| +| 2.4 | Success, unknown non- | Non-standard property | +| | standard property ignored. | name MAY be specified. | +|==============+============================+=========================| +| 2.5 | Success, unknown non | Property and non- | +| | standard property value | standard value MAY be | +| | ignored. | specified. | +|==============+============================+=========================| +| 2.6 | Success, invalid calendar | Calendar component | +| | component ignored. | sentinel (e.g., BEGIN: | +| | | ALARM) MAY be | +| | | specified. | +|==============+============================+=========================| +| 2.7 | Success, request forwarded | Original and forwarded | +| | to Calendar User. | caluser addresses MAY | +| | | be specified. | +|==============+============================+=========================| +| 2.8 | Success, repeating event | RRULE or RDATE property | + + + +Silverberg, et. al. Standards Track [Page 55] + +RFC 2446 iTIP November 1998 + + +| | ignored. Scheduled as a | name and value MAY be | +| | single component. | specified. | +|==============+============================+=========================| +| 2.9 | Success, truncated end date| DTEND property value | +| | time to date boundary. | MAY be specified. | +|==============+============================+=========================| +| 2.10 | Success, repeating VTODO | RRULE or RDATE property | +| | ignored. Scheduled as a | name and value MAY be | +| | single VTODO. | specified. | +|==============+============================+=========================| +| 2.11 | Success, unbounded RRULE | RRULE property name and | +| | clipped at some finite | value MAY be specified. | +| | number of instances | Number of instances MAY | +| | | also be specified. | +|==============+============================+=========================| +| 3.0 | Invalid property name. | Property name MAY be | +| | | specified. | +|==============+============================+=========================| +| 3.1 | Invalid property value. | Property name and value | +| | | MAY be specified. | +|==============+============================+=========================| +| 3.2 | Invalid property parameter.| Property parameter name | +| | | and value MAY be | +| | | specified. | +|==============+============================+=========================| +| 3.3 | Invalid property parameter | Property parameter name | +| | value. | and value MAY be | +| | | specified. | +|==============+============================+=========================| +| 3.4 | Invalid calendar component | Calendar component | +| | sequence. | sentinel MAY be | +| | | specified (e.g., BEGIN: | +| | | VTIMEZONE). | +|==============+============================+=========================| +| 3.5 | Invalid date or time. | Date/time value(s) MAY | +| | | be specified. | +|==============+============================+=========================| +| 3.6 | Invalid rule. | Rule value MAY be | +| | | specified. | +|==============+============================+=========================| +| 3.7 | Invalid Calendar User. | Attendee property value | +| | |MAY be specified. | +|==============+============================+=========================| +| 3.8 | No authority. | METHOD and Attendee | +| | | property values MAY be | +| | | specified. | +|==============+============================+=========================| + + + + +Silverberg, et. al. Standards Track [Page 56] + +RFC 2446 iTIP November 1998 + + +| 3.9 | Unsupported version. | VERSION property name | +| | | and value MAY be | +| | | specified. | +|==============+============================+=========================| +| 3.10 | Request entity too large. | None. | +|==============+============================+=========================| +| 3.11 | Required component or | Component or property | +| | property missing. | name MAY be specified. | +|==============+============================+=========================| +| 3.12 | Unknown component or | Component or property | +| | property found | name MAY be specified | +|==============+============================+=========================| +| 3.13 | Unsupported component or | Component or property | +| | property found | name MAY be specified | +|==============+============================+=========================| +| 3.14 | Unsupported capability | Method or action MAY | +| | | be specified | +|==============+============================+=========================| +| 4.0 | Event conflict. Date/time | DTSTART and DTEND | +| | is busy. | property name and values| +| | | MAY be specified. | +|==============+============================+=========================| +| 5.0 | Request MAY supported. | Method property value | +| | | MAY be specified. | +|==============+============================+=========================| +| 5.1 | Service unavailable. | ATTENDEE property value | +| | | MAY be specified. | +|==============+============================+=========================| +| 5.2 | Invalid calendar service. | ATTENDEE property value | +| | | MAY be specified. | +|==============+============================+=========================| +| 5.3 | No scheduling support for | ATTENDEE property value | +| | user. | MAY be specified. | +|==============+============================+=========================| + +3.7 Implementation Considerations + +3.7.1 Working With Recurrence Instances + + iCalendar includes a recurrence grammar to represent recurring + events. The benefit of such a grammar is the ability to represent a + number of events in a single object. However, while this simplifies + creation of a recurring event, meeting instances still need to be + referenced. For instance, an "Attendee" may decline the third + instance of a recurring Friday event. Similarly, the "Organizer" may + change the time or location to a single instance of the recurring + event. + + + + +Silverberg, et. al. Standards Track [Page 57] + +RFC 2446 iTIP November 1998 + + + Since implementations may elect to store recurring events as either a + single event object or a collection of discreet, related event + objects, the protocol is designed so that each recurring instance may + be both referenced and versioned. Hence, implementations that choose + to maintain per-instance properties (such as "ATTENDEE" property + "partstat" parameter) may do so. However, the protocol does not + require per-instance recognition unless the instance itself must be + renegotiated. + + The scenarios for recurrence instance referencing are listed below. + For purposes of simplification a change to an event refers to a + "trigger property." That is, a property that has a substantive + effect on the meeting itself such as start time, location, due date + (for "VTODO" calendar component components) and possibly description. + + "Organizer" initiated actions: + + . "Organizer" deletes or changes a single instance of a recurring + event + . "Organizer" makes changes that affect all future instances + . "Organizer" makes changes that affect all previous instances + . "Organizer" deletes or modifies a previously changed instance + + "Attendee" initiated actions: + + . "Attendee" changes status for a particular recurrence instance + . "Attendee" sends Event-Counter for a particular recurrence + instance + + An instance of a recurring event is assigned a unique identification, + "RECURRENCE-ID" property, when that instance is renegotiated. + Negotiation may be necessary when a substantive change to the event + or to-do has be made (such as changing the start time, end time, due + date or location). The "Organizer" can identify a specific recurrence + instance using the "RECURRENCE-ID" property. The property value is + equal to the date/time of the instance. If the "Organizer" wishes to + change the "DTSTART", the original "DTSTART" value is used for + "RECURRENCE-ID" property and the new "DTSTART" and "DTEND" values + reflect the change. Note that after the change has occurred, the + "RECURRENCE-ID" has changed to the new "DTSTART" value. + +3.7.2 Attendee Property Considerations + + The "ORGANIZER" property is required on published events, to-dos, and + journal entries for two reasons. First, only the "Organizer" is + allowed to update and redistribute an event or to-do component. It + follows that the "ORGANIZER" property MUST be present in the event, + to-do, or journal entry component so that the CUA has a basis for + + + +Silverberg, et. al. Standards Track [Page 58] + +RFC 2446 iTIP November 1998 + + + authorizing an update. Second, it is prudent to provide a point of + contact for anyone who receives a published component in case of + problems. + + There are valid [RFC-822] addresses that represent groups. Sending + email to such an address results in mail being sent to multiple + recipients. Such an address may be used as the value of an + "ATTENDEE" property. Thus, it is possible that the recipient of a + "REQUEST" does not appear explicitly in the list. + + It is recommended that the general approach to finding a "Calendar + User" in an attendee list be as follows: + + 1. Search for the "Calendar User" in the attendee list where + "TYPE=INDIVIDUAL" + + 2. Failing (1) look for attendees where "TYPE=GROUP" or + 'TYPE=UNKNOWN". The CUA then determines if the "Calendar User" + is a member of one of these groups. If so, the "REPLY" method + sent to the "Organizer" MUST contain a new "ATTENDEE" property in + which: + . the "type" property parameter is set to INDIVIDUAL + . the "member" property parameter is set to the name of the + group + + 3. Failing (2) the CUA MAY ignore or accept the request as the + "Calendar User" wishes. + +3.7.3 X-Tokens + + To make iCalendar objects extensible, new property types MAY be + inserted into components. These properties are called X-Tokens as + they are prefixed with "X-". A client is not required to make sense + of X-Tokens. Clients are not required to save X-Tokens or use them + in replies. + +4 Examples + +4.1 Published Event Examples + + In the calendaring and scheduling context, publication refers to the + one way transfer of event information. Consumers of published events + simply incorporate the event into a calendar. No reply is expected. + Individual "A" publishes an event. Individual "B" reads the event and + incorporates it into their calendar. Events are published in several + ways including: embedding the event as an object in a web page, e- + mailing the event to a distribution list, and posting the event to a + newsgroup. + + + +Silverberg, et. al. Standards Track [Page 59] + +RFC 2446 iTIP November 1998 + + + The table below illustrates the sequence of events between the + publisher and the consumers of a published event. + + +-------------------------------------------------------------------+ + | Action | "Organizer" | + +---------------------------------+---------------------------------+ + | Publish an event | "A" sends or posts a PUBLISH | + | | message | + +---------------------------------+---------------------------------+ + | "B" reads a published event | | + +---------------------------------+---------------------------------+ + | Publish an updated event | "A" sends or posts a PUBLISH | + | | message | + +---------------------------------+---------------------------------+ + | "B" reads the updated event | | + +---------------------------------+---------------------------------+ + | Cancel a published event | "A" sends or posts a CANCEL | + | | message | + +---------------------------------+---------------------------------+ + | "B" reads the canceled event | | + | publication | | + +---------------------------------+---------------------------------+ + +4.1.1 A Minimal Published Event + + The iCalendar object below describes a single event that begins on + July 1, 1997 at 20:00 UTC. This event contains the minimum set of + properties for a "PUBLISH" for a "VEVENT" calendar component. + + BEGIN:VCALENDAR + METHOD:PUBLISH + PRODID:-//ACME/DesktopCalendar//EN + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:mailto:a@example.com + DTSTART:19970701T200000Z + DTSTAMP:19970611T190000Z + SUMMARY:ST. PAUL SAINTS -VS- DULUTH-SUPERIOR DUKES + UID:0981234-1234234-23@example.com + END:VEVENT + END:VCALENDAR + +4.1.2 Changing A Published Event + + The iCalendar object below describes an update to the event described + in 4.1.1, the time has been changed, an end time has been added, and + the sequence number has been adjusted. + + + + +Silverberg, et. al. Standards Track [Page 60] + +RFC 2446 iTIP November 1998 + + + BEGIN:VCALENDAR + METHOD:PUBLISH + VERSION:2.0 + PRODID:-//ACME/DesktopCalendar//EN + BEGIN:VEVENT + ORGANIZER:mailto:a@example.com + DTSTAMP:19970612T190000Z + DTSTART:19970701T210000Z + DTEND:19970701T230000Z + SEQUENCE:1 + UID:0981234-1234234-23@example.com + SUMMARY:ST. PAUL SAINTS -VS- DULUTH-SUPERIOR DUKES + END:VEVENT + END:VCALENDAR + + The "UID" property is used by the client to identify the event. The + "SEQUENCE" property indicates that this is a change to the event. The + event with a matching UID and sequence number 0 is superseded by this + event. + + The "SEQUENCE" property provides a reliable way to distinguish + different versions of the same event. Each time an event is + published, its sequence number is incremented. If a client receives + an event with a sequence number 5 and finds it has the same event + with sequence number 2, the event SHOULD be updated. However, if the + client received an event with sequence number 2 and finds it already + has sequence number 5 of the same event, the event MUST NOT be + updated. + +4.1.3 Canceling A Published Event + + The iCalendar object below cancels the event described in 4.1.1. This + cancels the event with "SEQUENCE" property of 0, 1, and 2. + + BEGIN:VCALENDAR + METHOD:CANCEL + VERSION:2.0 + PRODID:-//ACME/DesktopCalendar//EN + BEGIN:VEVENT + ORGANIZER:mailto:a@example.com + COMMENT:DUKES forfeit the game + SEQUENCE:2 + UID:0981234-1234234-23@example.com + DTSTAMP:19970613T190000Z + END:VEVENT + END:VCALENDAR + + + + + +Silverberg, et. al. Standards Track [Page 61] + +RFC 2446 iTIP November 1998 + + +4.1.4 A Rich Published Event + + This example describes the same event as in 4.1.1, but in much + greater detail. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:PUBLISH + SCALE:GREGORIAN + VERSION:2.0 + BEGIN:VTIMEZONE + TZID:America-Chicago + TZURL:http://zones.stds_r_us.net/tz/America-Chicago + BEGIN:STANDARD + DTSTART:19671029T020000 + RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 + TZOFFSETFROM:-0500 + TZOFFSETTO:-0600 + TZNAME:CST + END:STANDARD + BEGIN:DAYLIGHT + DTSTART:19870405T020000 + RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 + TZOFFSETFROM:-0600 + TZOFFSETTO:-0500 + TZNAME:CDT + END:DAYLIGHT + END:VTIMEZONE + BEGIN:VEVENT + ORGANIZER:mailto:a@example.com + ATTACH:http://www.dukes.com/ + CATEGORIES:SPORTS EVENT,ENTERTAINMENT + CLASS:PRIVATE + DESCRIPTION:MIDWAY STADIUM\n + Big time game. MUST see.\n + Expected duration:2 hours\n + DTEND;TZID=America-Chicago:19970701T180000 + DTSTART;TZID=America-Chicago:19970702T160000 + DTSTAMP:19970614T190000Z + STATUS:CONFIRMED + LOCATION;VALUE=URI:http://www.midwaystadium.com/ + PRIORITY:2 + RESOURCES:SCOREBOARD + SEQUENCE:3 + SUMMARY:ST. PAUL SAINTS -VS- DULUTH-SUPERIOR DUKES + UID:0981234-1234234-23@example.com + RELATED-TO:0981234-1234234-14@example.com + BEGIN:VALARM + + + +Silverberg, et. al. Standards Track [Page 62] + +RFC 2446 iTIP November 1998 + + + TRIGGER:-PT2H + ACTION:DISPLAY + DESCRIPTION:You should be leaving for the game now. + END:VALARM + BEGIN:VALARM + TRIGGER:-PT30M + ACTION:AUDIO + END:VALARM + END:VEVENT + END:VCALENDAR + + The "RELATED-TO" field contains the "UID" property of a related + calendar event. The "SEQUENCE" property 3 indicates that this event + supersedes versions 0, 1, and 2. + +4.1.5 Anniversaries or Events attached to entire days + + This example demonstrates the use of the "value" parameter to tie a + "VEVENT" to day rather than a specific time. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:PUBLISH + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:mailto:a@example.com + DTSTAMP:19970614T190000Z + UID:0981234-1234234-23@example.com + DTSTART;VALUE=DATE:19970714 + RRULE:FREQ=YEARLY;INTERVAL=1 + SUMMARY: Bastille Day + END:VEVENT + END:VCALENDAR + +4.2 Group Event Examples + + Group events are distinguished from published events in that they + have "Attendees" and that there is interaction between the + "Attendees" and the "Organizer" with respect to the event. Individual + "A" requests a meeting between individuals "A", "B", "C" and "D". + Individual "B" confirms attendance to the meeting. Individual "C" + declines attendance. Individual "D" tentatively confirms attendance. + The following table illustrates the the message flow between these + individuals. A, the CU scheduling the meeting, is referenced as the + "Organizer". + + + + + + +Silverberg, et. al. Standards Track [Page 63] + +RFC 2446 iTIP November 1998 + + ++---------------------------------------------------------------------+ +| Action | "Organizer" | Attendee | ++---------------------------------------------------------------------+ +| Initiate a meeting | "A" sends a REQUEST | | +| request | message to "B", "C",| | +| | and "D" | | ++---------------------------------------------------------------------+ +| Accept the meeting | | "B" sends a REPLY | +| request | | message to "A" with its | +| | | ATTENDEE "partstat" para-| +| | | set to "accepted" | ++---------------------------------------------------------------------+ +| Decline the meeting| | "C" sends a REPLY | +| request | | message to "A" with its | +| | | ATTENDEE "partstat" para-| +| | | set to "declined" | ++---------------------------------------------------------------------+ +| Tentatively accept | | "D" sends a REPLY | +| the meeting request| | message to "A" with its | +| | | ATTENDEE "partstat" para-| +| | | set to "tentative" | ++---------------------------------------------------------------------+ +| Confirm meeting | "A" sends a REQUEST | | +| status with | message to "B" and | | +| attendees | "D" with updated | | +| | information. | | ++---------------------------------------------------------------------+ + +4.2.1 A Group Event Request + + A sample meeting request is sent from "A" to "B", "C", and "D". _E_ + is also sent a copy of the request but is not expected to attend and + need not reply. "E" illustrates how CUAs might implement an "FYI" + type feature. Note the use of the "role" parameter. The default value + for the "role" parameter is "req-participant" and it need not be + enumerated. In this case we are using the value "non-participant" to + indicate "E" is a non-attending CU. The parameter is not needed on + other "Attendees" since "participant" is the default value. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED;CN=BIG A:Mailto:A@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL;CN=B:Mailto:B@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL;CN=C:Mailto:C@example.com + + + +Silverberg, et. al. Standards Track [Page 64] + +RFC 2446 iTIP November 1998 + + + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL;CN=Hal:Mailto:D@example.com + ATTENDEE;RSVP=FALSE;TYPE=ROOM:conf_Big@example.com + ATTENDEE;ROLE=NON-PARTICIPANT;RSVP=FALSE:Mailto:E@example.com + DTSTAMP:19970611T190000Z + DTSTART:19970701T200000Z + DTEND:19970701T2000000Z + SUMMARY:Conference + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:0 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + +4.2.2 Reply To A Group Event Request + + Attendee "B" accepts the meeting. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REPLY + VERSION:2.0 + BEGIN:VEVENT + ATTENDEE;PARTSTAT=ACCEPTED:Mailto:B@example.com + ORGANIZER:MAILTO:A@example.com + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:0 + REQUEST-STATUS:2.0;Success + DTSTAMP:19970612T190000Z + END:VEVENT + END:VCALENDAR + + "B" could have declined the meeting or indicated tentative acceptance + by setting the "ATTENDEE" "partstat" parameter to "declined" or + "tentative", respectively. Also, "REQUEST-STATUS" is not required in + successful transactions. + +4.2.3 Update An Event + + The event is moved to a different time. The combination of the "UID" + property (unchanged) and the "SEQUENCE" (bumped to 1) properties + indicate the update. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + + + +Silverberg, et. al. Standards Track [Page 65] + +RFC 2446 iTIP November 1998 + + + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:B@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:C@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL;CN=Hal:Mailto:D@example.com + ATTENDEE;ROLE=NON-PARTICIPANT;RSVP=FALSE; + CUTYPE=ROOM:Mailto:Conf@example.com + ATTENDEE;ROLE=NON-PARTICIPANT;RSVP=FALSE:Mailto:E@example.com + DTSTART:19970701T180000Z + DTEND:19970701T190000Z + SUMMARY:Phone Conference + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:1 + DTSTAMP:19970613T190000Z + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + +4.2.4 Countering an Event Proposal + + "A" sends a "REQUEST" to "B" and "C". "B" makes a counter-proposal to + "A" to change the time and location. + + "A" sends the following "REQUEST": + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:B@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:C@example.com + DTSTART:19970701T190000Z + DTEND:19970701T200000Z + SUMMARY:Discuss the Merits of the election results + LOCATION:Green Conference Room + UID:calsrv.example.com-873970198738777a@example.com + SEQUENCE:0 + DTSTAMP:19970611T190000Z + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + "B" sends "COUNTER" to "A", requesting changes to time and place. "B" + uses the "COMMENT" property to communicate a rationale for the + change. Note that the "SEQUENCE" property is NOT incremented on a + "COUNTER". + + + +Silverberg, et. al. Standards Track [Page 66] + +RFC 2446 iTIP November 1998 + + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:COUNTER + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:B@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:C@example.com + DTSTART:19970701T160000Z + DTEND:19970701T190000Z + DTSTAMP:19970612T190000Z + SUMMARY:Discuss the Merits of the election results + LOCATION:Green Conference Room + COMMENT:This time works much better and I think the big conference + room is too big + UID:calsrv.example.com-873970198738777a@example.com + SEQUENCE:0 + DTSTAMP:19970611T190000Z + END:VEVENT + END:VCALENDAR + + "A" accepts the changes from "B". To accept a counter-proposal, the + "Organizer" sends a new event "REQUEST" with an incremented sequence + number. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:B@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:C@example.com + DTSTAMP:19970613T190000Z + DTSTART:19970701T160000Z + DTEND:19970701T190000Z + SUMMARY:Discuss the Merits of the election results - changed to + meet B's schedule + LOCATION:Green Conference Room + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:1 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + Instead, "A" rejects "B's" counter proposal + + + +Silverberg, et. al. Standards Track [Page 67] + +RFC 2446 iTIP November 1998 + + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:DECLINECOUNTER + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:B@example.com + COMMENT:Sorry, I cannot change this meeting time + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:0 + DTSTAMP:19970614T190000Z + END:VEVENT + END:VCALENDAR + +4.2.5 Delegating an Event + + When delegating an event request to another "Calendar User", the + "Delegator" must both update the "Organizer" with a "REPLY" and send + a request to the "Delegate". There is currently no protocol + limitation to delegation depth. It is possible for the original + + delegate to delegate the meeting to someone else, and so on. When a + request is delegated from one CUA to another there are a number of + responsibilities required of the "Delegator". The "Delegator" MUST: + + . Send a "REPLY" to the "Organizer" with the following updates: + . The "Delegator's" "ATTENDEE" property "partstat" parameter set + to "delegated" and the "delegated-to" parameter is set to the + address of the "Delegate" + . Add an additional "ATTENDEE" property for the "Delegate" with + the "delegated-from" property parameter set to the "Delegator" + . Indicate whether they want to continue to receive updates when + the "Organizer" sends out updated versions of the event. + Setting the "rsvp" property parameter to "TRUE" will cause the + updates to be sent, setting it to "FALSE" causes no further + updates to be sent. Note that in either case, if the "Delegate" + declines the invitation the "Delegator" will be notified. + . The "Delegator" MUST also send a copy of the original "REQUEST" + method to the "Delegate". + + It is not required that the "Delegate" include the "Delegator" in + their "REPLY" method. However, it is strongly advised since this will + inform the "Delegator" whether the "Delegate" plans to attend the + meeting. [Editors note: How so?] If the "Delegate" declines the + meeting, the "Delegator" may elect to delegate the "REQUEST" to + another CUA. The process is the same. + + + + + +Silverberg, et. al. Standards Track [Page 68] + +RFC 2446 iTIP November 1998 + + ++---------------------------------------------------------------------+ +| Action | "Organizer" | Attendee | ++---------------------------------------------------------------------+ +| Initiate a meeting | "A" sends a REQUEST | | +| request | message to "B" and | | +| | "C" | | ++---------------------------------------------------------------------+ +| Delegate: | | "C" sends a REPLY to "A" | +| "C" delegates to | | with the ATTENDEE. | +| "E" | | "partstat" parameter set | +| | | to "delegated" and with a| +| | | new "ATTENDEE" property | +| | | for "E". "E's" ATTENDEE | +| | | "delegated-from" param | +| | | is set to "C". "C's" | +| | | ATTENDEE "delegated-to" | +| | | param is set to "E". | +| | | "C" sends REQUEST message| +| | | to "E" with the original | +| | | meeting request | +| | | information. The | +| | | "partstat" property | +| | | parameter for "C" is set | +| | | to "delegated" and the | +| | | "delegated-to" | +| | | parameter is set to | +| | | the address of "E". An | +| | | "ATTENDEE" property is | +| | | added for "E" and the | +| | | "delegated-from" | +| | | parameter is set to | +| | | the address of "C". | ++---------------------------------------------------------------------+ +| Confirm meeting | | "E" sends REPLY message | +| attendance | | to "A" and optionally "C"| +| | | with its "partstat" | +| | | property parameter set | +| | | to "ACCEPTED" | ++---------------------------------------------------------------------+ +| Optional: | "A" sends REQUEST | | +| Redistribute | message to "B", "C" | | +| meeting to | and "E". | | +| attendees | | | ++---------------------------------------------------------------------+ + + + + + + + +Silverberg, et. al. Standards Track [Page 69] + +RFC 2446 iTIP November 1998 + + + "C" responds to the "Organizer". + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REPLY + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:MAILTO:A@Example.com + ATTENDEE;PARTSTAT=DELEGATED;DELEGATED- + TO="Mailto:E@example.com":Mailto:C@example.com + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:0 + REQUEST-STATUS:2.0;Success + DTSTAMP:19970611T190000Z + END:VEVENT + END:VCALENDAR + + Attendee "C" delegates presence at the meeting to "E". + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE;PARTSTAT=DELEGATED;DELEGATED- + TO="Mailto:E@example.com":Mailto:C@example.com + ATTENDEE;RSVP=TRUE; + DELEGATED-FROM="Mailto:C@example.com":Mailto:E@example.com + DTSTART:19970701T180000Z + DTEND:19970701T200000Z + SUMMARY:Phone Conference + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:0 + STATUS:CONFIRMED + DTSTAMP:19970611T190000Z + END:VEVENT + END:VCALENDAR + +4.2.6 Delegate Accepts the Meeting + + To accept a delegated meeting, the delegate, "E", sends the following + message to "A" and "C": + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REPLY + VERSION:2.0 + + + +Silverberg, et. al. Standards Track [Page 70] + +RFC 2446 iTIP November 1998 + + + BEGIN:VEVENT + ORGANIZER:MAILTO:A@Example.com + ATTENDEE;PARTSTAT=ACCEPTED;DELEGATED- + FROM="Mailto:C@example.com":Mailto:E@example.com + ATTENDEE;PARTSTAT=DELEGATED; + DELEGATED-TO="Mailto:E@example.com":Mailto:C@example.com + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:0 + REQUEST-STATUS:2.0;Success + DTSTAMP:19970614T190000Z + END:VEVENT + END:VCALENDAR + +4.2.7 Delegate Declines the Meeting + + In this example the "Delegate" declines the meeting request and sets + the "ATTENDEE" property "partstat" parameter to "DECLINED". The + "Organizer" SHOULD resend the "REQUEST" to "C" with the "partstat" + parameter of the "Delegate" set to "declined". This lets the + "Delegator" know that the "Delegate" has declined and provides an + opportunity to the "Delegator" to either accept the request or + delegate it to another CU. + + Response from "E" to "A" and "C". Note the use of the "COMMENT" + property "E" uses to indicate why the delegation was declined. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REPLY + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:MAILTO:A@Example.com + ATTENDEE;PARTSTAT=DELEGATED; + DELEGATED-TO="Mailto:E@example.com":Mailto:C@example.com + ATTENDEE;PARTSTAT=DECLINED; + DELEGATED-FROM="Mailto:C@example.com":Mailto:E@example.com + COMMENT:Sorry, I will be out of town at that time. + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:0 + REQUEST-STATUS:2.0;Success + DTSTAMP:19970614T190000Z + END:VEVENT + END:VCALENDAR + + "A" resends the "REQUEST" method to "C". "A" may also wish to express + the fact that the item was delegated in the "COMMENT" property. + + + + + +Silverberg, et. al. Standards Track [Page 71] + +RFC 2446 iTIP November 1998 + + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:MAILTO:A@Example.com + ATTENDEE;PARTSTAT=DECLINED; + DELEGATED-FROM="Mailto:C@example.com":Mailto:E@example.com + ATTENDEE;RSVP=TRUE:Mailto:C@example.com + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:0 + SUMMARY:Phone Conference + DTSTART:19970701T180000Z + DTEND:19970701T200000Z + DTSTAMP:19970614T200000Z + COMMENT:DELEGATE (ATTENDEE Mailto:E@example.com) DECLINED YOUR + INVITATION + END:VEVENT + END:VCALENDAR + +4.2.8 Forwarding an Event Request + + The protocol does not prevent an "Attendee" from "forwarding" an + "VEVENT" calendar component to other "Calendar Users". Forwarding + differs from delegation in that the forwarded "Calendar User" (often + referred to as a "Party Crasher") does not replace the forwarding + "Calendar User". Implementations are not required to add the "Party + Crasher" to the "Attendee" list and hence there is no guarantee that + a "Party Crasher" will receive additional updates to the Event. The + forwarding "Calendar User" SHOULD NOT add the "Party Crasher" to the + attendee list. The "Organizer" MAY add the forwarded "Calendar User" + to the attendee list. + +4.2.9 Cancel A Group Event + + Individual "A" requests a meeting between individuals "A", "B", "C", + and "D". Individual "B" declines attendance to the meeting. + Individual "A" decides to cancel the meeting. The following table + illustrates the sequence of messages that would be exchanged between + these individuals. + + Messages related to a previously canceled event ("SEQUENCE" property + value is less than the "SEQUENCE" property value of the "CANCEL" + message) MUST be ignored. + + + + + + + +Silverberg, et. al. Standards Track [Page 72] + +RFC 2446 iTIP November 1998 + + + +--------------------------------------------------------------------+ + | Action | "Organizer" | "Attendee" | + +--------------------------------------------------------------------+ + | Initiate a meeting | "A" sends a REQUEST | | + | request | message to "B", "C",| | + | | and "D" | | + +--------------------------------------------------------------------+ + | Decline the meeting| | "B" sends a "REPLY" | + | request | | message to "A" with its | + | | | "partstat" para- | + | | | set to "declined". | + +--------------------------------------------------------------------+ + | Cancel the meeting | "A" sends a CANCEL | | + | | message to "B", "C" | | + | | and "D" | | + +--------------------------------------------------------------------+ + + The example shows how "A" cancels the event. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:CANCEL + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE;TYPE=INDIVIDUAL;Mailto:A@example.com + ATTENDEE;TYPE=INDIVIDUAL:Mailto:B@example.com + ATTENDEE;TYPE=INDIVIDUAL:Mailto:C@example.com + ATTENDEE;TYPE=INDIVIDUAL:Mailto:D@example.com + COMMENT:Mr. B cannot attend. It's raining. Lets cancel. + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:1 + STATUS:CANCELLED + DTSTAMP:19970613T190000Z + END:VEVENT + END:VCALENDAR + + + + + + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 73] + +RFC 2446 iTIP November 1998 + + +4.2.10 Removing Attendees + + "A" wants to remove "B" from a meeting. This is done by sending a + "CANCEL" to "B" and removing "B" from the attendee list in the master + copy of the event. + + +--------------------------------------------------------------------+ + | Action | "Organizer" | "Attendee" | + +--------------------------------------------------------------------+ + | Remove an "B" | "A" sends a CANCEL | | + | as an "Attendee" | message to "B" | | + +--------------------------------------------------------------------+ + | Update the master | "A" sends the | | + | copy of the event | updated event to | | + | | the remaining | | + | | "Attendees" | | + +--------------------------------------------------------------------+ + + The original meeting includes "A", "B", "C", and "D". The example + below shows the "CANCEL" that "A" sends to "B". Note that in the + example below the "STATUS" property is omitted. This is used when the + meeting itself is cancelled and not when the intent is to remove an + "Attendee" from the Event. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:CANCEL + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE:mailto:B@example.com + COMMENT:You're off the hook for this meeting + UID:calsrv.example.com-873970198738777@example.com + DTSTAMP:19970613T193000Z + SEQUENCE:1 + END:VEVENT + END:VCALENDAR + + The updated master copy of the event is shown below. The "Organizer" + MAY resend the updated event to the remaining "Attendees". Note that + "B" has been removed. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + + + +Silverberg, et. al. Standards Track [Page 74] + +RFC 2446 iTIP November 1998 + + + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;TYPE=INDIVIDUAL:Mailto:C@example.com + ATTENDEE;TYPE=INDIVIDUAL:Mailto:D@example.com + ATTENDEE;TYPE=ROOM:CR_Big@example.com + ATTENDEE;ROLE=NON-PARTICIPANT; + RSVP=FALSE:Mailto:E@example.com + DTSTAMP:19970611T190000Z + DTSTART:19970701T200000Z + DTEND:19970701T203000Z + SUMMARY:Phone Conference + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:2 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + +4.2.11 Replacing the Organizer + + The scenario for this example begins with "A" as the "Organizer" for + a recurring meeting with "B", "C", and "D". "A" receives a new job + offer in another country and drops out of touch. "A" left no + forwarding address or way to be reached. Using out-of-band + communication, the other "Attendees" eventually learn what has + happened and reach an agreement that "B" should become the new + "Organizer" for the meeting. To do this, "B" sends out a new version + of the event and the other "Attendees" agree to accept "B" as the new + "Organizer". "B" also removes "A" from the event. + + When the "Organizer" is replaced, the "SEQUENCE" property value MUST + be incremented. + + This is the message "B" sends to "C" and "D" + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:B@example.com + ATTENDEE;ROLE=CHAIR;STATUS=ACCEPTED:Mailto:B@example.com + ATTENDEE;TYPE=INDIVIDUAL:Mailto:C@example.com + ATTENDEE;TYPE=INDIVIDUAL:Mailto:D@example.com + DTSTAMP:19970611T190000Z + DTSTART:19970701T200000Z + DTEND:19970701T203000Z + RRULE:FREQ=WEEKLY + SUMMARY:Phone Conference + UID:123456@example.com + + + +Silverberg, et. al. Standards Track [Page 75] + +RFC 2446 iTIP November 1998 + + + SEQUENCE:1 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + +4.3 Busy Time Examples + + Busy time objects can be used in several ways. First, a CU may + request busy time from another CU for a specific range of time. That + request can be answered with a busy time Reply. Additionally, a CU + may simply publish their busy time for a given interval and point + other CUs to the published location. The following examples outline + both scenarios. + + Individual "A" publishes busy time for one week. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + VERSION:2.0 + METHOD:PUBLISH + BEGIN:VFREEBUSY + DTSTAMP:19980101T124100Z + ORGANIZER:MAILTO:A@Example.com + DTSTART:19980101T124200Z + DTEND:19980107T124200Z + FREEBUSY:19980101T180000Z/19980101T190000Z + FREEBUSY:19980103T020000Z/19980103T050000Z + FREEBUSY:19980107T020000Z/19980107T050000Z + FREEBUSY:19980113T000000Z/19980113T010000Z + FREEBUSY:19980115T190000Z/19980115T200000Z + FREEBUSY:19980115T220000Z/19980115T230000Z + FREEBUSY:19980116T013000Z/19980116T043000Z + END:VFREEBUSY + END:VCALENDAR + + Individual "A" requests busy time from individuals "B", "C". + Individual "B" and "C" replies with busy time data to individual "A". + The following table illustrates the sequence of messages that would + be exchanged between these individuals. + + + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 76] + +RFC 2446 iTIP November 1998 + + ++--------------------------------------------------------------------+ +| Action | "Organizer" | Attendee | ++--------------------------------------------------------------------+ +| Initiate a busy | "A" sends "REQUEST" | | +| time request | message to "B" and | | +| | and "C" | | ++--------------------------------------------------------------------+ +| Reply to the "BUSY"| | "B" sends a "REPLY" | +| request with "BUSY"| | message to "A" with | +| time data | | busy time data | ++--------------------------------------------------------------------+ + +4.3.1 Request Busy Time + + "A" sends a "BUSY-REQUEST" to "B" and "C" for busy time + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VFREEBUSY + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR:Mailto:A@example.com + ATTENDEE:Mailto:B@example.com + ATTENDEE:Mailto:C@example.com + DTSTAMP:19970613T190000Z + DTSTART:19970701T080000Z + DTEND:19970701T200000 + UID:calsrv.example.com-873970198738777@example.com + END:VFREEBUSY + END:VCALENDAR + +4.3.2 Reply To A Busy Time Request + + "B" sends a "REPLY" method type of a "VFREEBUSY" calendar component + to "A" + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REPLY + VERSION:2.0 + BEGIN:VFREEBUSY + ORGANIZER:MAILTO:A@example.com + ATTENDEE:Mailto:B@example.com + DTSTART:19970701T080000Z + DTEND:19970701T200000Z + UID:calsrv.example.com-873970198738777@example.com + FREEBUSY:19970701T090000Z/PT1H,19970701T140000Z/PT30M + + + +Silverberg, et. al. Standards Track [Page 77] + +RFC 2446 iTIP November 1998 + + + DTSTAMP:19970613T190030Z + END:VFREEBUSY + END:VCALENDAR + + "B" is busy from 09:00 to 10:00 and from 14:00 to 14:30. + +4.4 Recurring Event and Time Zone Examples + +4.4.1 A Recurring Event Spanning Time Zones + + This event describes a weekly phone conference. The "Attendees" are + each in a different time zone. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VTIMEZONE + TZID:America-SanJose + TZURL:http://zones.stds_r_us.net/tz/America-SanJose + BEGIN:STANDARD + DTSTART:19671029T020000 + RRULE:FREQ=YEARLY;BYDAY=-1SU;BYMONTH=10 + TZOFFSETFROM:-0700 + TZOFFSETTO:-0800 + TZNAME:PST + END:STANDARD + BEGIN:DAYLIGHT + DTSTART:19870405T020000 + RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=4 + TZOFFSETFROM:-0800 + TZOFFSETTO:-0700 + TZNAME:PDT + END:DAYLIGHT + END:VTIMEZONE + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED;TYPE=INDIVIDUAL:A@example.COM + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:B@example.fr + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:c@example.jp + DTSTAMP:19970613T190030Z + DTSTART;TZID=America-SanJose:19970701T140000 + DTEND;TZID=America-SanJose:19970701T150000 + RRULE:FREQ=WEEKLY;INTERVAL=20;WKST=SU;BYDAY=TU + RDATE;TZID=America-SanJose:19970910T140000 + EXDATE;TZID=America-SanJose:19970909T140000 + EXDATE;TZID=America-SanJose:19971028T140000 + SUMMARY:Weekly Phone Conference + + + +Silverberg, et. al. Standards Track [Page 78] + +RFC 2446 iTIP November 1998 + + + UID:calsrv.example.com-873970198738777@example.com + SEQUENCE:0 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + The first two components of this iCalendar object are the time zone + components. The "DTSTART" date coincides with the first instance of + the RRULE property. + + The recurring meeting is defined in a particular time zone, + presumably that of the originator. The client for each "Attendee" has + the responsibility of determining the recurrence time in the + "Attendee's" time zone. + + The repeating event starts on Tuesday, July 1, 1997 at 2:00pm PDT. + "Attendee" B@example.fr is in France where the local time on this + date is 9 hours ahead of PDT or 23:00. "Attendee" C@example.jp is in + Japan where local time is 8 hours ahead of UTC or Wednesday, July 2 + at 06:00. The event repeats weekly on Tuesdays (in PST/PDT). The + "RRULE" property results in 20 instances. The last instance falls on + Tuesday, November 11, 1997 2:00pm PDT. The "RDATE" property adds + another instance: WED, 10-SEP-1997 2:00 PM PST. + + There are two exceptions to this recurring appointment. The first one + is: + + TUE, 09-SEP-1997 23:00 GMT + TUE, 09-SEP-1997 14:00 PDT + WED, 10-SEP-1997 06:00 JST + + and the second is: + + TUE, 28-OCT-1997 23:00 GMT + TUE, 28-OCT-1997 14:00 PST + WED, 29-OCT-1997 06:00 JST + +4.4.2 Modify A Recurring Instance + + In this example the "Organizer" issues a recurring meeting. Later the + "Organizer" changes an instance of the event by changing the + "DTSTART" property. Note the use of "RECURRENCE-ID" property and + "SEQUENCE" property in the second request. + + Original Request: + + BEGIN:VCALENDAR + METHOD:REQUEST + + + +Silverberg, et. al. Standards Track [Page 79] + +RFC 2446 iTIP November 1998 + + + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:guid-1@host1.com + SEQUENCE:0 + RRULE:FREQ=MONTHLY;BYMONTHDAY=1;UNTIL=19980901T210000Z + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE:Mailto:B@example.com + ATTENDEE:Mailto:C@example.com + ATTENDEE:Mailto:D@example.com + DESCRIPTION:IETF-C&S Conference Call + CLASS:PUBLIC + SUMMARY:IETF Calendaring Working Group Meeting + DTSTART:19970601T210000Z + DTEND:19970601T220000Z + LOCATION:Conference Call + DTSTAMP:19970526T083000Z + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + The event request below is to change the time of a specific instance. + This changes the July 1st instance to July 3rd. + + BEGIN:VCALENDAR + METHOD:REQUEST + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:guid-1@host1com + RECURRENCE-ID:19970701T210000Z + SEQUENCE:1 + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE:Mailto:B@example.com + ATTENDEE:Mailto:C@example.com + ATTENDEE:Mailto:D@example.com + DESCRIPTION:IETF-C&S Conference Call + CLASS:PUBLIC + SUMMARY:IETF Calendaring Working Group Meeting + DTSTART:19970703T210000Z + DTEND:19970703T220000Z + LOCATION:Conference Call + DTSTAMP:19970626T093000Z + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + + +Silverberg, et. al. Standards Track [Page 80] + +RFC 2446 iTIP November 1998 + + +4.4.3 Cancel an Instance + + In this example the "Organizer" of a recurring event deletes the + August 1st instance. + + BEGIN:VCALENDAR + METHOD:CANCEL + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:guid-1@host1.com + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE:Mailto:B@example.com + ATTENDEE:Mailto:C@example.com + ATTENDEE:Mailto:D@example.com + RECURRENCE-ID:19970801T210000Z + SEQUENCE:2 + STATUS:CANCELLED + DTSTAMP:19970721T093000Z + END:VEVENT + END:VCALENDAR + +4.4.4 Cancel Recurring Event + + In this example the "Organizer" wishes to cancel the entire recurring + event and any exceptions. + + BEGIN:VCALENDAR + METHOD:CANCEL + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:guid-1@host1.com + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE:Mailto:B@example.com + ATTENDEE:Mailto:C@example.com + ATTENDEE:Mailto:D@example.com + DTSTAMP:19970721T103000Z + STATUS:CANCELLED + SEQUENCE:3 + END:VEVENT + END:VCALENDAR + + + + + + + +Silverberg, et. al. Standards Track [Page 81] + +RFC 2446 iTIP November 1998 + + +4.4.5 Change All Future Instances + + This example changes the meeting location from a conference call to + Seattle starting September 1 and extends to all future instances. + + BEGIN:VCALENDAR + METHOD:REQUEST + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:guid-1@host1.com + RECURRENCE-ID;THISANDFUTURE:19970901T210000Z + SEQUENCE:3 + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + ATTENDEE;RSVP=TRUE:Mailto:C@example.com + ATTENDEE;RSVP=TRUE:Mailto:D@example.com + DESCRIPTION:IETF-C&S Discussion + CLASS:PUBLIC + SUMMARY:IETF Calendaring Working Group Meeting + DTSTART:19970901T210000Z + DTEND:19970901T220000Z + LOCATION:Building 32, Microsoft, Seattle, WA + DTSTAMP:19970526T083000Z + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + +4.4.6 Add A New Instance To A Recurring Event + + This example adds a one-time additional instance to the recurring + event. "Organizer" adds a second July meeting on the 15th. + + BEGIN:VCALENDAR + METHOD:ADD + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:123456789@host1.com + SEQUENCE:4 + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + ATTENDEE;RSVP=TRUE:Mailto:C@example.com + ATTENDEE;RSVP=TRUE:Mailto:D@example.com + DESCRIPTION:IETF-C&S Conference Call + CLASS:PUBLIC + + + +Silverberg, et. al. Standards Track [Page 82] + +RFC 2446 iTIP November 1998 + + + SUMMARY:IETF Calendaring Working Group Meeting + DTSTART:19970715T210000Z + DTEND:19970715T220000Z + LOCATION:Conference Call + DTSTAMP:19970629T093000Z + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + +4.4.7 Add A New Series of Instances To A Recurring Event + + The scenario for this example involves an ongoing meeting, originally + set up to occur every Tuesday. The "Organizer" later decides that + the meetings need to be on Tuesdays and Thursdays, but does not want + to reschedule the entire meeting or lose any of the per-instance + information already collected. + + The original event: + + BEGIN:VCALENDAR + METHOD:REQUEST + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:123456789@host1.com + SEQUENCE:0 + RRULE:WKST=SU;BYDAY=TU;FREQ=WEEKLY + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + SUMMARY:Review Accounts + DTSTART:19980303T210000Z + DTEND:19980303T220000Z + LOCATION:The White Room + DTSTAMP:19980301T093000Z + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + Assume that many other updates happen to this event and that a lot of + instance-specific information exists in the recurring series. The + "SEQUENCE" property value is 7 for the next update. Now the + "Organizer" wants to add Thursdays to the event: + + BEGIN:VCALENDAR + METHOD:ADD + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + + + +Silverberg, et. al. Standards Track [Page 83] + +RFC 2446 iTIP November 1998 + + + BEGIN:VEVENT + UID:123456789@host1.com + SEQUENCE:7 + RRULE:WKST=SU;BYDAY=TH;FREQ=WEEKLY + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + SUMMARY:Review Accounts + DTSTART:19980303T210000Z + DTEND:19980303T220000Z + DTSTAMP:19980303T193000Z + LOCATION:The Usual conference room + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + Alternatively, if the "Organizer" is not concerned with per-instance + updates, the entire event can be rescheduled using a "REQUEST". This + is done by using the "UID" of the event to reschedule and including + the modified "RRULE". Note, that since this is an entire rescheduling + of the event, any instance-specific information will be lost. + + BEGIN:VCALENDAR + METHOD:REQUEST + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:123456789@host1.com + SEQUENCE:7 + RRULE:WKST=SU;BYDAY=TU,TH;FREQ=WEEKLY + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + SUMMARY:Review Accounts + DTSTART:19980303T210000Z + DTEND:19980303T220000Z + DTSTAMP:19980303T193000Z + LOCATION:The White Room + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + The next series of examples illustrate how an "Organizer" would + respond to a "REFRESH" submitted by an "Attendee" after a series of + instance-specific modifications. To convey all instance-specific + changes, the "Organizer" must provide the latest event description + and the relevant instances. The first three examples show the history + including the initial "VEVENT" request and subsequent instance + + + +Silverberg, et. al. Standards Track [Page 84] + +RFC 2446 iTIP November 1998 + + + changes and finally the "REFRESH". + + Original Request: + + BEGIN:VCALENDAR + METHOD:REQUEST + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:123456789@host1.com + SEQUENCE:0 + RDATE:19980304T180000Z + RDATE:19980311T180000Z + RDATE:19980318T180000Z + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + SUMMARY:Review Accounts + DTSTART:19980304T180000Z + DTEND:19980304T200000Z + DTSTAMP:19980303T193000Z + LOCATION:Conference Room A + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + Organizer changes 2nd instance Location and Time: + + BEGIN:VCALENDAR + METHOD:REQUEST + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:123456789@host1.com + SEQUENCE:1 + RECURRENCE-ID:19980311T180000Z + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + SUMMARY:Review Accounts + DTSTART:19980311T160000Z + DTEND:19980311T180000Z + DTSTAMP:19980306T193000Z + LOCATION:The Small conference room + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + + + +Silverberg, et. al. Standards Track [Page 85] + +RFC 2446 iTIP November 1998 + + + Organizer adds a 4th instance of the meeting using the "ADD" method + + BEGIN:VCALENDAR + METHOD:ADD + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:123456789@host1.com + SEQUENCE:2 + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + SUMMARY:Review Accounts + DTSTART:19980315T180000Z + DTEND:19980315T200000Z + DTSTAMP:19980307T193000Z + LOCATION:Conference Room A + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + If "B" requests a "REFRESH", "A" responds with the following to + capture all instance-specific data. In this case both the initial + request and an additional "VEVENT" that specifies the instance- + specific data are included. Because these are both of the same type + (they are both "VEVENTS"), they can be conveyed in the same iCalendar + object. + + BEGIN:VCALENDAR + METHOD:REQUEST + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:123456789@host1.com + SEQUENCE:2 + RDATE:19980304T180000Z + RDATE:19980311T160000Z + RDATE:19980315T180000Z + Error! Bookmark not defined. + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + SUMMARY:Review Accounts + DTSTART:19980304T180000Z + DTEND:19980304T200000Z + DTSTAMP:19980303T193000Z + LOCATION:Conference Room A + STATUS:CONFIRMED + + + +Silverberg, et. al. Standards Track [Page 86] + +RFC 2446 iTIP November 1998 + + + END:VEVENT + + BEGIN:VEVENT + Error! Bookmark not defined. + SEQUENCE:2 + RECURRENCE-ID:19980311T160000Z + Error! Bookmark not defined. + ATTENDEE;ROLE=CHAIR;Error! Bookmark not defined. + ATTENDEE;Error! Bookmark not defined. + SUMMARY:Review Accounts + DTSTART:19980311T160000Z + DTEND:19980304T180000Z + DTSTAMP:19980306T193000Z + LOCATION:The Small conference room + STATUS:CONFIRMED + END:VEVENT + + END:VCALENDAR + +4.4.8 Counter An Instance Of A Recurring Event + + In this example one of the "Attendees" counters the "DTSTART" + property of the proposed second July meeting. + + BEGIN:VCALENDAR + METHOD:COUNTER + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:guid-1@host1.com + RECURRENCE-ID:19970715T210000Z + SEQUENCE:4 + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;RSVP=TRUE:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + ATTENDEE;RSVP=TRUE:Mailto:C@example.com + ATTENDEE;RSVP=TRUE:Mailto:D@example.com + DESCRIPTION:IETF-C&S Conference Call + CLASS:PUBLIC + SUMMARY:IETF Calendaring Working Group Meeting + DTSTART:19970715T220000Z + DTEND:19970715T230000Z + LOCATION:Conference Call + COMMENT:May we bump this by an hour? I have a conflict + DTSTAMP:19970629T094000Z + END:VEVENT + END:VCALENDAR + + + + +Silverberg, et. al. Standards Track [Page 87] + +RFC 2446 iTIP November 1998 + + +4.4.9 Error Reply To A Request + + The following example illustrates a scenario where a meeting is + proposed containing an unsupported property and a bad property. + + Original Request + + BEGIN:VCALENDAR + METHOD:REQUEST + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:guid-1@host1.com + SEQUENCE:0 + RRULE:FREQ=MONTHLY;BYMONTHDAY=1 + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + ATTENDEE;RSVP=TRUE:Mailto:C@example.com + ATTENDEE;RSVP=TRUE:Mailto:D@example.com + DESCRIPTION:IETF-C&S Conference Call + CLASS:PUBLIC + SUMMARY:IETF Calendaring Working Group Meeting + DTSTART:19970601T210000Z + DTEND:19970601T220000Z + DTSTAMP:19970602T094000Z + LOCATION:Conference Call + STATUS:CONFIRMED + FOO:BAR + END:VEVENT + END:VCALENDAR + + Response from "B" to indicate that RRULE is not supported and an + unrecognized property was encountered + + BEGIN:VCALENDAR + PRODID:-//RDU Software//NONSGML HandCal//EN + METHOD:REPLY + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE:Mailto:B@example.com + REQUEST-STATUS:2.8;Repeating event ignored. Scheduled as a single + event;RRULE + REQUEST-STATUS:3.0;Invalid Property Name;FOO + UID:guid-1@host1.com + SEQUENCE:0 + DTSTAMP:19970603T094000Z + + + +Silverberg, et. al. Standards Track [Page 88] + +RFC 2446 iTIP November 1998 + + + END:VEVENT + END:VCALENDAR + +4.5 Group To-do Examples + + Individual "A" creates a group task in which individuals "A", "B", + "C" and "D" will participate. Individual "B" confirms acceptance of + the task. Individual "C" declines the task. Individual "D" + tentatively accepts the task. The following table illustrates the + sequence of messages that would be exchanged between these + individuals. Individual "A" then issues a "REQUEST" method to obtain + the status of the to-do from each participant. The response indicates + the individual "Attendee's" completion status. The table below + illustrates the message flow. + ++--------------------------------------------------------------------+ +| Action | "Organizer" | Attendee | ++--------------------------------------------------------------------+ +| Initiate a to-do | "A" sends a REQUEST | | +| request | message to "B", "C",| | +| | and "D" | | ++--------------------------------------------------------------------+ +| Accept the to-do | | "B" sends a "REPLY" | +| request | | message to "A" with its | +| | | "partstat" paramater | +| | | set to "accepted". | ++--------------------------------------------------------------------+ +| Decline the to-do | | "C" sends a REPLY | +| request | | message to "A" with its | +| | | "partstat" parameter | +| | | set to "declined". | ++--------------------------------------------------------------------+ +| Tentatively accept | | "D" sends a REPLY | +| the to-do request | | message to "A" with its | +| | | "partstat" parameter | +| | | set to "tentative". | ++--------------------------------------------------------------------+ +| Check attendee | "A" sends a REQUEST | | +| completion status | message to "B" and | | +| | "D" with current | | +| | information. | | ++--------------------------------------------------------------------+ +| Attendee indicates | | "B" sends a "REPLY" | +| percent complete | | message indicating | +| | | percent complete | + + + + + + +Silverberg, et. al. Standards Track [Page 89] + +RFC 2446 iTIP November 1998 + + ++--------------------------------------------------------------------+ +| Attendee indicates | | "D" sends a "REPLY" | +| completion | | message indicating | +| | | completion | ++--------------------------------------------------------------------+ + +4.5.1 A VTODO Request + + A sample "REQUEST" for a "VTODO" calendar component that "A" sends to + "B", "C", and "D". + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VTODO + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR:Mailto:A@example.com + ATTENDEE;RSVP=TRUE:Mailto:B@example.com + ATTENDEE;RSVP=TRUE:Mailto:C@example.com + ATTENDEE;RSVP=TRUE:Mailto:D@example.com + DTSTART:19970701T170000Z + DUE:19970722T170000Z + PRIORITY:1 + SUMMARY:Create the requirements document + UID:calsrv.example.com-873970198738777-00@example.com + SEQUENCE:0 + DTSTAMP:19970717T200000Z + STATUS:Needs Action + END:VTODO + END:VCALENDAR + +4.5.2 A VTODO Reply + + "B" accepts the to-do. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REPLY + VERSION:2.0 + BEGIN:VTODO + ORGANIZER:Mailto:A@example.com + ATTENDEE;PARTSTAT=ACCEPTED:Mailto:B@example.com + UID:calsrv.example.com-873970198738777-00@example.com + COMMENT:I'll send you my input by e-mail + SEQUENCE:0 + DTSTAMP:19970717T203000Z + REQUEST-STATUS:2.0;Success + + + +Silverberg, et. al. Standards Track [Page 90] + +RFC 2446 iTIP November 1998 + + + END:VTODO + END:VCALENDAR + + "B" could have declined the TODO or indicated tentative acceptance by + setting the "partstat" property parameter sequence to "declined" or + "tentative", respectively. + +4.5.3 A VTODO Request for Updated Status + + "A" requests status from all "Attendees". + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VTODO + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR:Mailto:A@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:B@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:D@example.com + UID:calsrv.example.com-873970198738777-00@example.com + SUMMARY:Create the requirements document + PRIORITY:1 + SEQUENCE:0 + STATUS:IN-PROCESS + DTSTART:19970701T170000Z + DTSTAMP:19970717T230000Z + END:VTODO + END:VCALENDAR + +4.5.4 A Reply: Percent-Complete + + A reply indicating the task being worked on and that "B" is 75% + complete with "B's" part of the assignment. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REPLY + VERSION:2.0 + BEGIN:VTODO + ORGANIZER:MAILTO:A@example.com + ATTENDEE;PARTSTAT=IN-PROCESS:Mailto:B@example.com + PERCENT-COMPLETE:75 + UID:calsrv.example.com-873970198738777-00@example.com + DTSTAMP:19970717T233000Z + SEQUENCE:0 + END:VTODO + END:VCALENDAR + + + +Silverberg, et. al. Standards Track [Page 91] + +RFC 2446 iTIP November 1998 + + +4.5.5 A Reply: Completed + + A reply indicating that "D" completed "D's" part of the assignment. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REPLY + VERSION:2.0 + BEGIN:VTODO + ORGANIZER:MAILTO:A@example.com + ATTENDEE;PARTSTAT=COMPLETED:Mailto:D@example.com + UID:calsrv.example.com-873970198738777-00@example.com + DTSTAMP:19970717T233000Z + SEQUENCE:0 + END:VTODO + END:VCALENDAR + +4.5.6 An Updated VTODO Request + + Organizer "A" resends the "VTODO" calendar component. "A" sets the + overall completion for the to-do at 40%. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VTODO + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE;PARTSTAT=ACCEPTED;TYPE=INDIVIDUAL:Mailto:B@example.com + ATTENDEE;PARTSTAT=IN-PROCESS;TYPE=INDIVIDUAL:Mailto:D@example.com + DTSTART:19970701T170000Z + DUE:19970722T170000Z + PRIORITY:1 + SUMMARY:Create the requirements document + UID:calsrv.example.com-873970198738777-00@example.com + SEQUENCE:1 + DTSTAMP:19970718T100000Z + STATUS:IN-PROGRESS + PERCENT-COMPLETE:40 + END:VTODO + END:VCALENDAR + +4.5.7 Recurring VTODOs + + The following examples relate to recurring "VTODO" calendar + components. + + + + +Silverberg, et. al. Standards Track [Page 92] + +RFC 2446 iTIP November 1998 + + +4.5.7.1 Request for a Recurring VTODO + + In this example "A" sends a recurring "VTODO" calendar component to + "B" and "D". + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VTODO + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR:Mailto:A@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:B@example.com + ATTENDEE;RSVP=TRUE;TYPE=INDIVIDUAL:Mailto:D@example.com + RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR + DTSTART:19980101T100000-0700 + DUE:19980103T100000-0700 + SUMMARY:Send Status Reports to Area Managers + UID:calsrv.example.com-873970198738777-00@example.com + SEQUENCE:0 + DTSTAMP:19970717T200000Z + STATUS:NEEDS ACTION + PRIORITY:1 + END:VTODO + END:VCALENDAR + +4.5.7.2 Calculating due dates in recurring VTODOs + + The due date in a recurring "VTODO" calendar component is either a + fixed interval specified in the "REQUEST" method or specified using + the "RECURRENCE-ID" property. The former is calculated by applying + the difference between "DTSTART" and "DUE" properties and applying it + to each of the start of each recurring instance. Hence, if the + initial "VTODO" calendar component specifies a "DTSTART" property + value of "19970701T190000Z" and a "DUE" property value of + "19970801T190000Z" the interval of one day which is applied to each + recurring instance of the "VTODO" calendar component to determine the + "DUE" date of the instance. + +4.5.7.3 Replying to an instance of a recurring VTODO + + In this example "B" updates "A" on a single instance of the "VTODO" + calendar component. + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REPLY + VERSION:2.0 + + + +Silverberg, et. al. Standards Track [Page 93] + +RFC 2446 iTIP November 1998 + + + BEGIN:VTODO + ATTENDEE;PARTSTAT=IN-PROCESS:Mailto:B@example.com + PERCENT-COMPLETE:75 + UID:calsrv.example.com-873970198738777-00@example.com + DTSTAMP:19970717T233000Z + RECURRENCE-ID:19980101T170000Z + SEQUENCE:1 + END:VTODO + END:VCALENDAR + +4.6 Journal Examples + + The iCalendar object below describes a single journal entry for + October 2, 1997. The "RELATED-TO" property references the phone + conference event for which minutes were taken. + + BEGIN:VCALENDAR + METHOD:PUBLISH + PRODID:-//ACME/DesktopCalendar//EN + VERSION:2.0 + BEGIN:VJOURNAL + DTSTART:19971002T200000Z + ORGANIZER:MAILTO:A@Example.com + SUMMARY:Phone conference minutes + DESCRIPTION:The editors meeting was held on October 1, 1997. + Details are in the attached document. + UID:0981234-1234234-2410@example.com + RELATED-TO:0981234-1234234-2402-35@example.com + ATTACH:ftp://ftp.example.com/pub/ed/minutes100197.txt + END:VJOURNAL + END:VCALENDAR + +4.7 Other Examples + +4.7.1 Event Refresh + + Refresh the event with "UID" property value of "guid-1- + 12345@host1.com": + + BEGIN:VCALENDAR + PRODID:-//RDU Software//NONSGML HandCal//EN + METHOD:REFRESH + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE:Mailto:B@example.com + ATTENDEE:Mailto:C@example.com + + + +Silverberg, et. al. Standards Track [Page 94] + +RFC 2446 iTIP November 1998 + + + ATTENDEE:Mailto:D@example.com + UID: guid-1-12345@host1.com + DTSTAMP:19970603T094000 + END:VEVENT + END:VCALENDAR + +4.7.2 Bad RECURRENCE-ID + + Component instances are identified by the combination of "UID", + "RECURRENCE-ID", and "SEQUENCE". When an "Organizer" sends a request + to an "Attendee", there are three cases in which an instance cannot + be found. They are: + + 1. The component with the referenced "UID" and "RECURRENCE-ID" has + been found but the "SEQUENCE" number in the calendar store does + not match that of the ITIP message. + + 2. The component with the referenced "UID" has been found, the + "SEQUENCE" numbers match, but the "RECURRENCE-ID" cannot be + found. + + 3. The "UID" and "SEQUENCE" numbers are found but the CUA does not + support recurrences. + + In case (1), two things can happen. If the "SEQUENCE" number of the + "Attendee's" instance is larger than that in the "Organizer's" + message then the "Attendee" is receiving an out-of-sequence message + and MUST ignore it. If the "SEQUENCE" number of the "Attendee's" + instance is smaller, then the "Organizer" is sending out a newer + version of the component and the "Attendee's" version needs to be + updated. Since one or more updates have been missed, the "Attendee" + SHOULD send a "REFRESH" message to the "Organizer" to get an updated + version of the event. + + In case (2), something has gone wrong. Both the "Organizer" and the + "Attendee" should have the same instances, but the "Attendee" does + not have the referenced instance. In this case the "Attendee" SHOULD + send a "REFRESH" to the "Organizer" to get an updated version of the + event. + + In case (3), the limitations of the "Attendee's" CUA makes it + impossible to match an instance other than the single instance + scheduled. In this case, the "Attendee" need not send a "REFRESH" to + the "Organizer". + + The example below shows a sequence in which an "Attendee" sends a + "REFRESH" to the "Organizer". + + + + +Silverberg, et. al. Standards Track [Page 95] + +RFC 2446 iTIP November 1998 + + ++--------------------------------------------------------------------+ +| Action | "Organizer" | Attendee | ++--------------------------------------------------------------------+ +| Update an instance | "A" sends "REQUEST"| | +| request | message to "B" | | ++--------------------------------------------------------------------+ +| Attendee requests | | "B" sends a "REFRESH" | +| refresh because | | message to "A" | +| "RECURRENCE-ID" was| | | +| not found | | | ++--------------------------------------------------------------------+ +| Refresh the entire | "A" sends the | | +| Event | latest copy of the | | +| | Event to "B" | | ++--------------------------------------------------------------------+ +| Attendee handles | | "B" updates to the | +| the request and | | latest copy of the | +| updates the | | meeting. | +| instance | | | ++--------------------------------------------------------------------+ + + Request from "A": + + BEGIN:VCALENDAR + METHOD:REQUEST + PRODID:-//RDU Software//NONSGML HandCal//EN + VERSION:2.0 + BEGIN:VEVENT + UID:acme-12345@host1.com + SEQUENCE:3 + RRULE:FREQ=WEEKLY + RDATE;VALUE=PERIOD:19970819T210000Z/199700819T220000Z + ORGANIZER:Mailto:A@example.com + ATTENDEE;ROLE=CHAIR;PARTSTAT=ACCEPTED:Mailto:A@example.com + ATTENDEE:Mailto:B@example.com + DESCRIPTION:IETF-C&S Conference Call + SUMMARY:IETF Calendaring Working Group Meeting + DTSTART:19970801T210000Z + DTEND:19970801T220000Z + RECURRENCE-ID:19970809T210000Z + DTSTAMP:19970726T083000 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + "B" has the event with "UID" property "acme-12345@host1.com" but + "B's" "SEQUENCE" property value is "1" and the event does not have an + instance at the specified recurrence time. This means that "B" has + + + +Silverberg, et. al. Standards Track [Page 96] + +RFC 2446 iTIP November 1998 + + + missed at least one update and needs a new copy of the event. "B" + requests the latest copy of the event with the following refresh + message: + + BEGIN:VCALENDAR + PRODID:-//RDU Software//NONSGML HandCal//EN + METHOD:REFRESH + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:Mailto:A@example.com + ATTENDEE:Mailto:B@example.com + UID:acme-12345@host1.com + DTSTAMP:19970603T094000 + END:VEVENT + END:VCALENDAR + +5 Application Protocol Fallbacks + +5.1 Partial Implementation + + Applications that support this memo are not required to support the + entire protocol. The following describes how methods and properties + SHOULD "fallback" in applications that do not support the complete + protocol. If a method or property is not addressed in this section, + it may be ignored. + +5.1.1 Event-Related Fallbacks + +Method Fallback +-------------- ----------------------------------------------------- +PUBLISH Required +REQUEST PUBLISH +REPLY Required +ADD Required +CANCEL Required +REFRESH Required +COUNTER Reply with Not Supported +DECLINECOUNTER Required if EVENT-COUNTER is implemented; otherwise + reply with Not Supported + +iCalendar +Property Fallback +-------------- ----------------------------------------------------- +CALSCALE Ignore; assume GREGORIAN +PRODID Ignore +METHOD Required as described in the Method list above +VERSION Ignore + + + + +Silverberg, et. al. Standards Track [Page 97] + +RFC 2446 iTIP November 1998 + + +Event-Related +Components Fallback +-------------- ----------------------------------------------------- +VALARM Reply with Not Supported +VTIMEZONE Required if any DateTime value refers to a time zone. + +Component +Property Fallback +-------------- ----------------------------------------------------- +ATTACH Ignore +ATTENDEE Required if EVENT-REQUEST is not implemented; + otherwise reply with Not Supported +CATEGORIES Ignore +CLASS Ignore +COMMENT Ignore +COMPLETED Ignore +nCONTACT Ignore +CREATED Ignore +DESCRIPTION Required +DURATION Reply with Not Supported +DTSTAMP Required +DTSTART Required +DTEND Required +EXDATE Ignore +EXRULE Ignore Reply with Not Supported. If implemented, + VTIMEZONE MUST also be implemented. +GEO Ignore +LAST-MODIFIED Ignore +LOCATION Required +ORGANIZER Ignore +PRIORITY Ignore +RELATED-TO Ignore +RDATE Ignore +RRULE Ignore. The first instance occurs on the DTStart + property. If implemented, VTIMEZONE MUST also be + implemented. +RECURRENCE-ID Required if RRULE is implemented; otherwise Ignore +REQUEST-STATUS Required +RESOURCES Ignore +SEQUENCE Required +STATUS Ignore +SUMMARY Ignore +TRANSP Required if FREEBUSY is implemented; otherwise Ignore +URL Ignore +UID Required +X- Ignore + + + + + +Silverberg, et. al. Standards Track [Page 98] + +RFC 2446 iTIP November 1998 + + +5.1.2 Free/Busy-Related Fallbacks + +Method Fallback +-------------- ----------------------------------------------------- +PUBLISH Implementations MAY ignore the METHOD type. The + REQUEST-STATUS "3.14;Unsupported capability" MUST be + returned. +REQUEST Implementations MAY ignore the METHOD type. The + REQUEST-STATUS "3.14;Unsupported capability" MUST be + returned. +REPLY Implementations MAY ignore the METHOD type. The + REQUEST-STATUS "3.14;Unsupported capability" MUST be + returned. + + +iCalendar +Property Fallback +-------------- ----------------------------------------------------- +CALSCALE Ignore; assume GREGORIAN. +PRODID Ignore +METHOD Required as described in the Method list above. +VERSION Ignore + + +Component +Property Fallback +-------------- ----------------------------------------------------- +COMMENT Ignore +CONTACT Ignore +DTEND Required +DTSTAMP Required +DTSTART Required +DURATION Required +FREEBUSY Required +ORGANIZER Ignore +REQUEST-STATUS Ignore +UID Required +URL Ignore +X- Ignore + +5.1.3 To-Do-Related Fallbacks + +Method Fallback +-------------- ----------------------------------------------------- +PUBLISH Required +REQUEST PUBLISH +REPLY Required +ADD Required + + + +Silverberg, et. al. Standards Track [Page 99] + +RFC 2446 iTIP November 1998 + + +CANCEL Required +REFRESH Required +COUNTER Reply with Not Supported +DECLINECOUNTER Required if VTODO - COUNTER is implemented; otherwise + reply with Not Supported + +iCalendar +Property Fallback +-------------- ----------------------------------------------------- +CALSCALE Ignore; assume GREGORIAN. +PRODID Ignore +METHOD Required as described in the Method list above. +VERSION Ignore + + +To-Do-Related +Components Fallback +-------------- ----------------------------------------------------- +VALARM Reply with Not Supported +VTIMEZONE Required if any DateTime value refers to a time zone. + + +Component +Property Fallback +-------------- ----------------------------------------------------- +ATTACH Ignore +ATTENDEE Required if REQUEST is not implemented; otherwise + ignore +CATEGORIES Ignore +CLASS Ignore +COMMENT Ignore +COMPLETED Required +CONTACT Ignore +CREATED Ignore +DESCRIPTION Required +DUE Required +DURATION Ignore Reply with Not Supported +DTSTAMP Required +DTSTART Required +EXDATE Ignore Reply with Not Supported +EXRULE Ignore Reply with Not Supported. If implemented, + VTIMEZONE MUST also be implemented. +LAST-MODIFIED Ignore +LOCATION Ignore +ORGANIZER Ignore +PERCENT-COMPLETE Ignore +PRIORITY Required +RECURRENCE-ID Ignore + + + +Silverberg, et. al. Standards Track [Page 100] + +RFC 2446 iTIP November 1998 + + +RELATED-TO Ignore +REQUEST-STATUS Ignore +RDATE Ignore +RRULE Ignore. The first instance occurs on the DTSTART + property. If implemented, VTIMEZONE MUST also be + implemented. +RESOURCES Ignore +SEQUENCE Required +STATUS Required +SUMMARY Ignore +URL Ignore +UID Required +X- Ignore + +5.1.4 Journal-Related Fallbacks + + +Method Fallback +-------------- ----------------------------------------------------- +PUBLISH Implementations MAY ignore the METHOD type. The + REQUEST-STATUS "3.14;Unsupported capability" MUST be + returned. +ADD Implementations MAY ignore the METHOD type. The + REQUEST-STATUS "3.14;Unsupported capability" MUST be + returned. +CANCEL Implementations MAY ignore the METHOD type. The + REQUEST-STATUS "3.14;Unsupported capability" MUST be + returned. + + +iCalendar +Property Fallback +-------------- ----------------------------------------------------- +CALSCALE Ignore; assume GREGORIAN. +PRODID Ignore +METHOD Required as described in the Method list above. +VERSION Ignore + + +Journal-Related +Components Fallback +-------------- ----------------------------------------------------- +VTIMEZONE Required if any DateTime value refers to a time zone. + + + + + + + + +Silverberg, et. al. Standards Track [Page 101] + +RFC 2446 iTIP November 1998 + + +Component +Property Fallback +-------------- ----------------------------------------------------- +ATTACH Ignore +ATTENDEE Required if JOURNAL-REQUEST is implemented; otherwise + ignore +CATEGORIES Ignore +CLASS Ignore +COMMENT Ignore +CONTACT Ignore +CREATED Ignore +DESCRIPTION Required +DTSTAMP Required +DTSTART Required +EXDATE Ignore +EXRULE Ignore Reply with Not Supported. If implemented, + VTIMEZONE MUST also be implemented. +LAST-MODIFIED Ignore +ORGANIZER Ignore +RECURRENCE-ID Ignore +RELATED-TO Ignore +RDATE Ignore. +RRULE Ignore. The first instance occurs on the DTSTART + property. If implemented, VTIMEZONE MUST also be + implemented. +SEQUENCE Required +STATUS Ignore +SUMMARY Required +URL Ignore +UID Required +X- Ignore + +5.2 Latency Issues + + With a store-and-forward transport, it is possible for events to + arrive out of sequence. That is, a "CANCEL" method may be received + prior to receiving the associated "REQUEST" for the calendar + component. This section discusses a few of these scenarios. + +5.2.1 Cancellation of an Unknown Calendar Component. + + When a "CANCEL" method is received before the original "REQUEST" + method the calendar will be unable to correlate the "UID" property of + the cancellation with an existing calendar component. It is suggested + that messages that can not be correlated that also contain non-zero + sequence numbers be held and not discarded. Implementations MAY age + them out if no other messages arrive with the same "UID" property + value and a lower sequence number. + + + +Silverberg, et. al. Standards Track [Page 102] + +RFC 2446 iTIP November 1998 + + +5.2.2 Unexpected Reply from an Unknown Delegate + + When an "Attendee" delegates an item to another CU they MUST send a + "REPLY" method to the "Organizer" using the "ATTENDEE" properties to + indicate that the request was delegated and to whom. Hence, it is + possible for an "Organizer" to receive an "REPLY" from a CU not + listed as one of the original "Attendees". The resolution is left to + the implementation but it is expected that the calendaring software + will either accept the reply or hold it until the related "REPLY" + method is received from the "Delegator". If the version of the + "REPLY" method is out of date the "Organizer" SHOULD treat the + message as a "REFRESH" message and update the delegate with the + correct version. + +5.3 Sequence Number + + Under some conditions, a CUA may receive requests and replies with + the same "SEQUENCE" property value. The "DTSTAMP" property is + utilized as a tie-breaker when two items with the same "SEQUENCE" + property value are evaluated. + +6 Security Considerations + + ITIP is an abstract transport protocol which will be bound to a + real-time transport, a store-and-forward transport, and perhaps other + transports. The transport protocol will be responsible for providing + facilities for authentication and encryption using standard Internet + mechanisms that are mutually understood between the sender and + receiver. + +6.1 Security Threats + +6.1.1 Spoofing the "Organizer" + + In iTIP, the "Organizer" (or someone working on the "Organizer's" + behalf) is the only person authorized to make changes to an existing + "VEVENT", "VTODO", "VJOURNAL" calendar component and republish it or + redistribute updates to the "Attendees". An iCalendar object that + maliciously changes or cancels an existing "VEVENT", "VTODO" or + "VJOURNAL" calendar component may be constructed by someone other + than the "Organizer" and republished or sent to the "Attendees". + +6.1.2 Spoofing the "Attendee" + + In iTIP, an "Attendee" of a "VEVENT" or "VTODO" calendar component + (or someone working on the "Attendee's" behalf) is the only person + authorized to update any parameter associated with their "ATTENDEE" + property and send it to the "Organizer". An iCalendar object that + + + +Silverberg, et. al. Standards Track [Page 103] + +RFC 2446 iTIP November 1998 + + + maliciously changes the "ATTENDEE" parameters may be constructed by + someone other than the real "Attendee" and sent to the "Organizer". + +6.1.3 Unauthorized Replacement of the Organizer + + There will be circumstances when "Attendees" of an event or to-do + decide, using out-of-band mechanisms, that the "Organizer" must be + replaced. When the new "Organizer" sends out the updated "VEVENT" or + "VTODO" the "Attendee's" CUA will detect that the "Organizer" has + been changed, but it has no way of knowing whether or not the change + was mutually agreed upon. + +6.1.4 Eavesdropping + + The iCalendar object is constructed with human-readable clear text. + Any information contained in an iCalendar object may be read and/or + changed by unauthorized persons while the object is in transit. + +6.1.5 Flooding a Calendar + + Implementations MAY provide a means to automatically incorporate + "REQUEST" methods into a calendar. This presents the opportunity for + a calendar to be flooded with requests, which effectively block all + the calendar's free time. + +6.1.6 Procedural Alarms + + The "REQUEST" methods for "VEVENT" and "VTODO" calendar components + MAY contain "VALARM" calendar components. The "VALARM" calendar + component may be of type "PROCEDURE" and MAY have an attachment + containing an executable program. Implementations that incorporate + these types of alarms are subject to any virus or malicious attack + that may occur as a result of executing the attachment. + +6.1.7 Unauthorized REFRESH Requests + + It is possible for an "Organizer" to receive a "REFRESH" request from + someone who is not an "Attendee" of an event or to-do. Only + "Attendee's" of an event or to-do are authorized to receive replies + to "REFRESH" requests. Replying to such requests to anyone who is not + an "Attendee" may be a security problem. + +6.2 Recommendations + + For an application where the information is sensitive or critical and + the network is subject is subject to a high probability of attack, + iTIP transactions SHOULD be encrypted. This may be accomplished using + public key technology, specifically Security Multiparts for MIME + + + +Silverberg, et. al. Standards Track [Page 104] + +RFC 2446 iTIP November 1998 + + + [RFC-1847] in the iTIP transport binding. This helps mitigate the + threats of spoofing, eavesdropping and malicious changes in transit. + +6.2.1 Use of [RFC-1847] to secure iTIP transactions + + iTIP transport bindings MUST provide a mechanism based on Security + Multiparts for MIME [RFC-1847] to enable authentication of the + sender's identity, and privacy and integrity of the data being + transmitted. This allows the receiver of a signed iCalendar object to + verify the identity of the sender. This sender may then be correlated + to an "ATTENDEE" property in the iCalendar object. If the correlation + is made and the sender is authorized to make the requested change or + update then the operation may proceed. It also allows the message to + be encrypted to prevent unauthorized reading of the message contents + in transit. iTIP transport binding documents describe this process in + detail. + + Implementations MAY provide controls for users to disable this + capability. + +6.2.2 Implementation Controls + + The threat of unauthorized replacement of the "Organizer" SHOULD be + mitigated by a calendar system that uses this protocol by providing + controls or alerts that make "Calendar Users" aware of such + "Organizer" changes and allowing them to decide whether or not the + request should be honored. + + The threat of flooding a calendar SHOULD be mitigated by a calendar + system that uses this protocol by providing controls that may be used + to limit the acceptable sources for iTIP transactions, and perhaps + the size of messages and volume of traffic, by source. + + The threat of malicious procedural alarms SHOULD be mitigated by a + calendar system that uses this protocol by providing controls that + may be used to disallow procedural alarms in iTIP transactions and/or + remove all alarms from the object before delivery to the recipient. + + The threat of unauthorized "REFRESH" requests SHOULD be mitigated by + a calendar system that uses this protocol by providing controls or + alerts that allow "Calendar User" to decide whether or not the + request should be honored. An implementation MAY decide to maintain, + for audit or historical purposes, "Calendar Users" who were part of + an attendee list and who were subsequently uninvited. Similar + controls or alerts should be provided when a "REFRESH" request is + received from these "Calendar Users" as well. + + + + + +Silverberg, et. al. Standards Track [Page 105] + +RFC 2446 iTIP November 1998 + + +7 Acknowledgments + + A hearty thanks to the following individuals who have participated in + the drafting, review and discussion of this memo: + + Anik Ganguly, Dan Hickman, Paul Hill, Daryl Huff, Bruce Kahn, Antoine + Leca, Bob Mahoney, John Noerenberg, Leo Parker, John Rose, Doug + Royer, Vinod Seraphin, Richard Shusterman, Derik Stenerson, John Sun, + Alexander Taler, Kevin Tsurutome. + +8 Bibliography + + [iCAL] Dawson, F. and D. Stenerson, "Internet Calendaring and + Scheduling Core Object Specification - iCalendar", RFC + 2445, November 1998. + + [iMIP] Dawson, F., Mansour, S. and S. Silverberg, "iCalendar + Message-Based Interoperability Protocol - iMIP", RFC 2447, + November 1998. + + [RFC-2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + [US-ASCII] Coded Character Set--7-bit American Standard Code for + Information Interchange, ANSI X3.4-1986. + + + + + + + + + + + + + + + + + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 106] + +RFC 2446 iTIP November 1998 + + +9 Authors' Addresses + + The following address information is provided in a vCard v3.0, + Electronic Business Card, format. + + The authors of this memo are: + + BEGIN:VCARD + VERSION:3.0 + N:Dawson;Frank + FN:Frank Dawson + ORG:Lotus Development Corporation + ADR;WORK;POSTAL;PARCEL:;;6544 Battleford Drive;Raleigh;NC;27613- + 3502;USA + TEL;TYPE=WORK,MSG:+1-919-676-9515 + TEL;TYPE=WORK,FAX:+1-919-676-9564 + EMAIL;TYPE=PREF,INTERNET:Frank_Dawson@Lotus.com + EMAIL;TYPE=INTERNET:fdawson@earthlink.net + URL:http://home.earthlink.net/~fdawson + END:VCARD + + BEGIN:VCARD + VERSION:3.0 + N:Hopson;Ross + FN:Ross Hopson + ORG:On Technology, Inc. + ADR;TYPE=WORK,POSTAL,PARCEL:;Suite 1600;434 Fayetteville St. + Mall\, Two Hannover Square;Raleigh;NC;27601 + TEL;TYPE=WORK,MSG:+1-919-890-4036 + TEL;TYPE=WORK,FAX:+1-919-890-4100 + EMAIL;TYPE=INTERNET:rhopson@on.com + END:VCARD + + BEGIN:VCARD + VERSION:3.0 + N:Mansour;Steve + FN:Steve Mansour + ORG:Netscape Communications Corporation + ADR;TYPE=WORK,POSTAL,PARCEL:;;501 East Middlefield Road;Mountain + View;CA;94043;USA + TEL;TYPE=WORK,MSG:+1-650-937-2378 + TEL;TYPE=WORK,FAX:+1-650-937-2103 + EMAIL;TYPE=INTERNET:sman@netscape.com + END:VCARD + + + + + + + +Silverberg, et. al. Standards Track [Page 107] + +RFC 2446 iTIP November 1998 + + + BEGIN:VCARD + VERSION:3.0 + N:Silverberg;Steve + FN:Steve Silverberg + ORG:Microsoft Corporation + ADR;TYPE=WORK,POSTAL,PARCEL:;;One Microsoft Way; + Redmond;WA;98052-6399;USA + TEL;TYPE=WORK,MSG:+1-425-936-9277 + TEL;TYPE=WORK,FAX:+1-425-936-8019 + EMAIL;INTERNET:stevesil@Microsoft.com + END:VCARD + + The iCalendar object is a result of the work of the Internet + Engineering Task Force Calendaring and scheduling Working Group. The + chairman of that working group is: + + BEGIN:VCARD + VERSION:3.0 + N:Ganguly;Anik + FN:Anik Ganguly + ORG:Open Text Inc. + ADR;TYPE=WORK,POSTAL,PARCEL:;Suite 101;38777 West Six Mile Road; + Livonia;MI;48152;USA + TEL;TYPE=WORK,MSG:+1-734-542-5955 + EMAIL;TYPE=INTERNET:ganguly@acm.org + END:VCARD + + The co-chairman of that working group is: + + BEGIN:VCARD + VERSION:3.0 + N:Moskowitz;Robert + FN:Robert Moskowitz + NICKNAME:Bob + EMAIL; TYPE=INTERNET:rgm-ietf@htt-consult.com + END:VCARD + + + + + + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 108] + +RFC 2446 iTIP November 1998 + + +10. Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Silverberg, et. al. Standards Track [Page 109] + diff --git a/lib/qCal/docs/rfc/rfc2447.txt b/lib/qCal/docs/rfc/rfc2447.txt new file mode 100644 index 0000000..c4f9790 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc2447.txt @@ -0,0 +1,1011 @@ + + + + + + +Network Working Group F. Dawson +Request for Comments: 2447 Lotus +Category: Standards Track S. Mansour + Netscape + S. Silverberg + Microsoft + November 1998 + + + iCalendar Message-Based Interoperability Protocol + (iMIP) + +Status of this Memo + + This document specifies an Internet standards track protocol for the + Internet community, and requests discussion and suggestions for + improvements. Please refer to the current edition of the "Internet + Official Protocol Standards" (STD 1) for the standardization state + and status of this protocol. Distribution of this memo is unlimited. + +Copyright Notice + + Copyright (C) The Internet Society (1998). All Rights Reserved. + +Abstract + + This document, [iMIP], specifies a binding from the iCalendar + Transport-independent Interoperability Protocol (iTIP) to Internet + email-based transports. Calendaring entries defined by the iCalendar + Object Model [iCAL] are composed using constructs from [RFC-822], + [RFC-2045], [RFC-2046], [RFC-2047], [RFC-2048] and [RFC-2049]. + + This document is based on discussions within the Internet Engineering + Task Force (IETF) Calendaring and Scheduling (CALSCH) working group. + More information about the IETF CALSCH working group activities can + be found on the IMC web site at http://www.imc.org, the IETF web site + at http://www.ietf.org/html.charters/calsch-charter.html. Refer to + the references within this document for further information on how to + access these various documents. + + + + + + + + + + + + +Dawson, et. al. Standards Track [Page 1] + +RFC 2447 iMIP November 1998 + + +Table of Contents + + 1 INTRODUCTION........................................................2 + 1.1 RELATED MEMOS ...................................................2 + 1.2 FORMATTING CONVENTIONS ..........................................3 + 1.3 TERMINOLOGY .....................................................4 + 2 MIME MESSAGE FORMAT BINDING.........................................4 + 2.1 MIME MEDIA TYPE .................................................4 + 2.2 SECURITY ........................................................4 + 2.2.1 Authorization ...............................................4 + 2.2.2 Authentication ..............................................5 + 2.2.3 Confidentiality .............................................5 + 2.3 [RFC-822] ADDRESSES .............................................5 + 2.4 CONTENT TYPE ....................................................5 + 2.5 CONTENT-TRANSFER-ENCODING .......................................6 + 2.6 CONTENT-DISPOSITION .............................................6 + 3 SECURITY CONSIDERATIONS.............................................7 + 4 EXAMPLES............................................................8 + 4.1 SINGLE COMPONENT WITH AN ATTACH PROPERTY ........................8 + 4.2 USING MULTIPART ALTERNATIVE FOR LOW FIDELITY CLIENTS ............8 + 4.3 SINGLE COMPONENT WITH AN ATTACH PROPERTY AND INLINE ATTACHMENT ..9 + 4.4 MULTIPLE SIMILAR COMPONENTS ....................................10 + 4.5 MULTIPLE MIXED COMPONENTS ......................................11 + 4.6 DETAILED COMPONENTS WITH AN ATTACH PROPERTY ....................13 + 5 RECOMMENDED PRACTICES..............................................14 + 5.1 USE OF CONTENT AND MESSAGE IDS .................................14 + 6 BIBLIOGRAPHY.......................................................15 + 7 AUTHORS' ADDRESSES.................................................16 + 8 FULL COPYRIGHT STATEMENT...........................................18 + +1 Introduction + + This binding document provides the transport specific information + necessary convey iCalendar Transport-independent Interoperability + Protocol (iTIP) over MIME as defined in [RFC-822] and [RFC-2045]. + +1.1 Related Memos + + Implementers will need to be familiar with several other memos that, + along with this memo, form a framework for Internet calendaring and + scheduling standards. + + This document, [iMIP], specifies an Internet email binding for iTIP. + + [iCAL] - specifies a core specification of objects, data types, + properties and property parameters; + + + + + +Dawson, et. al. Standards Track [Page 2] + +RFC 2447 iMIP November 1998 + + + [iTIP] - specifies an interoperability protocol for scheduling + between different implementations; + + This memo does not attempt to repeat the specification of concepts or + definitions from these other memos. Where possible, references are + made to the memo that provides for the specification of these + concepts or definitions. + +1.2 Formatting Conventions + + The mechanisms defined in this memo are defined in prose. In order to + refer to elements of the calendaring and scheduling model, core + object or interoperability protocol defined in [iCAL] and [iTIP] some + formatting conventions have been used. + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY" and "OPTIONAL" in this + document are to be interpreted as described in [RFC-2119]. + + Calendaring and scheduling roles are referred to in quoted-strings of + text with the first character of each word in upper case. For + example, "Organizer" refers to a role of a "Calendar User" within the + scheduling protocol defined by [iTIP]. + + Calendar components defined by [iCAL] are referred to with + capitalized, quoted-strings of text. All calendar components start + with the letter "V". For example, "VEVENT" refers to the event + calendar component, "VTODO" refers to the to-do calendar component + and "VJOURNAL" refers to the daily journal calendar component. + + Scheduling methods defined by [iTIP] are referred to with + capitalized, quoted-strings of text. For example, "REQUEST" refers to + the method for requesting a scheduling calendar component be created + or modified, "REPLY" refers to the method a recipient of a request + uses to update their status with the "Organizer" of the calendar + component. + + Properties defined by [iCAL] are referred to with capitalized, + quoted-strings of text, followed by the word "property". For example, + "ATTENDEE" property refers to the iCalendar property used to convey + the calendar address of a calendar user. + + Property parameters defined by [iCAL] are referred to with lower + case, quoted-strings of text, followed by the word "parameter". For + example, "value" parameter refers to the iCalendar property parameter + used to override the default data type for a property value. + + + + + +Dawson, et. al. Standards Track [Page 3] + +RFC 2447 iMIP November 1998 + + +1.3 Terminology + + The email terms used in this memo are defined in [RFC-822] and [RFC- + 2045]. The calendaring and scheduling terms used in this memo are + defined in [iCAL] and [iTIP]. + +2 MIME Message Format Binding + + This section defines the message binding to the MIME electronic mail + transport. + + The sections below refer to the "originator" and the "respondent" of + an iMIP message. Typically, the originator is the "Organizer" of an + event. The respondent is an "Attendee" of the event. + + The [RFC-822] "Reply-To" header typically contains the email address + of the originator or respondent of an event. However, this cannot be + guaranteed as Mail User Agents (MUA) are not required to enforce iMIP + semantics. + +2.1 MIME Media Type + + A MIME entity containing content information formatted according to + this document will be referenced as a "text/calendar" content type. + It is assumed that this content type will be transported through a + MIME electronic mail transport. + +2.2 Security + + This section addresses several aspects of security including + Authentication, Authorization and Confidentiality. Authentication and + confidentiality can be achieved using [RFC-1847] that specifies the + Security Multiparts for MIME. This framework defines new content + types and subtypes of multipart: signed and encrypted. Each contains + two body parts: one for the protected data and another for the + control information necessary to remove the protection. + +2.2.1 Authorization + + In [iTIP] messages, only the "Organizer" is authorized to modify or + cancel calendar entries they organize. That is, spoof@xyz.com is not + allowed to modify or cancel a meeting that was organized by + a@example.com. Furthermore, only the respondent has the authorization + to indicate their status to the "Organizer". That is, the "Organizer" + must ignore an [iTIP] message from spoof@xyz.com that declines a + meeting invitation for b@example.com. + + + + + +Dawson, et. al. Standards Track [Page 4] + +RFC 2447 iMIP November 1998 + + + Implementations of iMIP SHOULD verify the authenticity of the creator + of an iCalendar object before taking any action. The methods for + doing this are presented later in this document. + + [RFC-1847] Message flow in iTIP supports someone working on behalf of + a "Calendar User" through use of the "sent-by" parameter that is + associated with the "ATTENDEE" and "ORGANIZER" properties. However, + there is no mechanism to verify whether or not a "Calendar User" has + authorized someone to work on their behalf. It is left to + implementations to provide mechanisms for the "Calendar Users" to + make that decision. + +2.2.2 Authentication + + Authentication can be performed using an implementation of [RFC-1847] + "multipart/signed" that supports public/private key certificates. + Authentication is possible only on messages that have been signed. + Authenticating an unsigned message may not be reliable. + +2.2.3 Confidentiality + + To ensure confidentiality using iMIP implementations should utilize + [RFC-1847]-compliant encryption. The protocol does not restrict a + "Calendar User Agent" (CUA) from forwarding iCalendar objects to + other users or agents. + +2.3 [RFC-822] Addresses + + The calendar address specified within the "ATTENDEE" property in an + iCalendar object MUST be a fully qualified, [RFC-822] address + specification for the corresponding "Organizer" or "Attendee" of the + "VEVENT" or "VTODO". + + Because [iTIP] does not preclude "Attendees" from forwarding + "VEVENTS" or "VTODOS" to others, the [RFC-822] "Sender" value may not + equal that of the "Organizer". Additionally, the "Organizer" or + "Attendee" cannot be reliably inferred by the [RFC-822] "Sender" or + "Reply-to" values of an iMIP message. The relevant address MUST be + ascertained by opening the "text/calendar" MIME body part and + examining the "ATTENDEE" and "ORGANIZER" properties. + +2.4 Content Type + + A MIME body part containing content information that conforms to this + document MUST have an [RFC-2045] "Content-Type" value of + "text/calendar". The [RFC-2045] "Content-Type" header field must also + include the type parameter "method". The value MUST be the same as + the value of the "METHOD" calendar property within the iCalendar + + + +Dawson, et. al. Standards Track [Page 5] + +RFC 2447 iMIP November 1998 + + + object. This means that a MIME message containing multiple iCalendar + objects with different method values must be further encapsulated + with a "multipart/mixed" MIME entity. This will allow each of the + iCalendar objects to be encapsulated within their own "text/calendar" + MIME entity. + + A "charset" parameter MUST be present if the iCalendar object + contains characters that are not part of the US-ASCII character set. + [RFC-2046] discusses the selection of an appropriate "charset" value. + + The optional "component" parameter defines the iCalendar component + type contained within the iCalendar object. + + The following is an example of this header field with a value that + indicates an event message. + + Content-Type:text/calendar; method=request; charset=UTF-8; + component=vevent + + The "text/calendar" content type allows for the scheduling message + type to be included in a MIME message with other content information + (i.e., "multipart/mixed") or included in a MIME message with a + clear-text, human-readable form of the scheduling message (i.e., + "multipart/alternative"). + + In order to permit the information in the scheduling message to be + understood by MIME user agents (UA) that do not support the + "text/calendar" content type, scheduling messages SHOULD be sent with + an alternative, human-readable form of the information. + +2.5 Content-Transfer-Encoding + + Note that the default character set for iCalendar objects is UTF-8. A + transfer encoding SHOULD be used for iCalendar objects containing any + characters that are not part of the US-ASCII character set. + +2.6 Content-Disposition + + The handling of a MIME part should be based on its [RFC-2045] + "Content-Type". However, this is not guaranteed to work in all + environments. Some environments handle MIME attachments based on + their file type or extension. To operate correctly in these + environments, implementations may wish to include a "Content- + Disposition" property to define a file name. + + + + + + + +Dawson, et. al. Standards Track [Page 6] + +RFC 2447 iMIP November 1998 + + +3 Security Considerations + + The security threats that applications must address when implementing + iTIP are detailed in [iTIP]. Two spoofing threats are identified: + Spoofing the "Organizer", and Spoofing an "Attendee". To address + these threats, the originator of an iCalendar object must be + authenticated by a recipient. Once authenticated, a determination can + be made as to whether or not the originator is authorized to perform + the requested operation. Compliant applications MUST support signing + and encrypting text/calendar attachments using a mechanism based on + Security Multiparts for MIME [RFC-1847] to facilitate the + authentication the originator of the iCalendar object. + Implementations MAY provide a means for users to disable signing and + encrypting. The steps are described below: + + 1. The iCalendar object MUST be signed by the "Organizer" sending an + update or the "Attendee" sending a reply. + + 2. Using the [RFC-1847]-compliant security mechanism, determine who + signed the iCalendar object. This is the "signer". Note that the + signer is not necessarily the person sending an e-mail message since + an e-mail message can be forwarded. + + 3. Correlate the signer to an "ATTENDEE" property in the iCalendar + object. If the signer cannot be correlated to an "ATTENDEE" property, + ignore the message. + + 4. Determine whether or not the "ATTENDEE" is authorized to perform + the operation as defined by [iTIP]. If the conditions are not met, + ignore the message. + + 5. If all the above conditions are met, the message can be processed. + + To address the confidentiality security threats, signed iMIP messages + SHOULD be encrypted by a mechanism based on Security Multiparts for + MIME [RFC-1847]. + + It is possible to receive iMIP messages sent by someone working on + behalf of another "Calendar User". This is determined by examining + the "sent-by" parameter in the relevant "ORGANIZER" or "ATTENDEE" + property. [iCAL] and [iTIP] provide no mechanism to verify that a + "Calendar User" has authorized someone else to work on their behalf. + To address this security issue, implementations MUST provide + mechanisms for the "Calendar Users" to make that decision before + applying changes from someone working on behalf of a "Calendar User". + + + + + + +Dawson, et. al. Standards Track [Page 7] + +RFC 2447 iMIP November 1998 + + +4 Examples + +4.1 Single Component With An ATTACH Property + + This minimal message shows how an iCalendar object references an + attachment. The attachment is accessible via its URL. + + From: sman@netscape.com + To: stevesil@microsoft.com + Subject: Phone Conference + Mime-Version: 1.0 + Content-Type:text/calendar; method=REQUEST; charset=US-ASCII + Content-Transfer-Encoding: 7bit + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:mailto:sman@netscape.com + ATTENDEE;ROLE=CHAIR;ATTSTAT=ACCEPTED:mailto:sman@netscape.com + ATTENDEE;RSVP=YES:mailto:stevesil@microsoft.com + DTSTAMP:19970611T190000Z + DTSTART:19970701T210000Z + DTEND:19970701T230000Z + SUMMARY:Phone Conference + DESCRIPTION:Please review the attached document. + UID:calsvr.example.com-873970198738777 + ATTACH:ftp://ftp.bar.com/pub/docs/foo.doc + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + +4.2 Using Multipart Alternative for Low Fidelity Clients + + This example shows how a client can emit a multipart message that + includes both a plain text version as well as the full iCalendar + object. Clients that do not support text/calendar will still be + capable of rendering the plain text representation. + + From: foo1@example.com + To: foo2@example.com + Subject: Phone Conference + Mime-Version: 1.0 + Content-Type: multipart/alternative;boundary="01BD3665.3AF0D360" + + --01BD3665.3AF0D360 + Content-Type: text/plain;charset=us-ascii + + + +Dawson, et. al. Standards Track [Page 8] + +RFC 2447 iMIP November 1998 + + + Content-Transfer-Encoding: 7bit + + This is an alternative representation of a TEXT/CALENDAR MIME Object + When: 7/1/1997 10:00AM PDT - 7/1/97 10:30AM PDT + Where: + Organizer: foo1@example.com + Summary: Phone Conference + + --01BD3665.3AF0D360 + Content-Type:text/calendar; method=REQUEST; charset=US-ASCII + Content-Transfer-Encoding: 7bit + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:mailto:foo1@example.com + ATTENDEE;ROLE=CHAIR;ATTSTAT=ACCEPTED:mailto:foo1@example.com + ATTENDEE;RSVP=YES;TYPE=INDIVIDUAL:mailto:foo2@example.com + DTSTAMP:19970611T190000Z + DTSTART:19970701T170000Z + DTEND:19970701T173000Z + SUMMARY:Phone Conference + UID:calsvr.example.com-8739701987387771 + SEQUENCE:0 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + --01BD3665.3AF0D360 + +4.3 Single Component With An ATTACH Property and Inline Attachment + + This example shows how a message containing an iCalendar object + references an attached document. The reference is made using a + Content-id (CID). Thus, the iCalendar object and the document are + packaged in a multipart/related encapsulation. + + From: foo1@example.com + To: foo2@example.com + Subject: Phone Conference + Mime-Version: 1.0 + Content-Type: multipart/related; boundary="boundary-example-1"; + type=text/calendar + + --boundary-example-1 + + + + +Dawson, et. al. Standards Track [Page 9] + +RFC 2447 iMIP November 1998 + + + Content-Type:text/calendar; method=REQUEST; charset=US-ASCII + Content-Transfer-Encoding: 7bit + Content-Disposition: attachment; filename="event.vcs" + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:mailto:foo1@example.com + ATTENDEE;ROLE=CHAIR;ATTSTAT=ACCEPTED:mailto:foo1@example.com + ATTENDEE;RSVP=YES;TYPE=INDIVIDUAL:mailto:foo2@example.com + DTSTAMP:19970611T190000Z + DTSTART:19970701T180000Z + DTEND:19970701T183000Z + SUMMARY:Phone Conference + UID:calsvr.example.com-8739701987387771 + ATTACH:cid:123456789@example.com + SEQUENCE:0 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + --boundary-example-1 + Content-Type: application/msword; name="FieldReport.doc" + Content-Transfer-Encoding: base64 + Content-Disposition: inline; filename="FieldReport.doc" + Content-ID: <123456789@example.com> + + 0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAPgADAP7/CQAGAAAAAAAAAAABAAAARAAAAAAA + AAAAEAAAQAAAAAEAAAD+////AAAAAEUAAAD///////////////////////////////// + + --boundary-example-1-- + +4.4 Multiple Similar Components + + Multiple iCalendar components of the same type can be included in the + iCalendar object when the METHOD is the same for each component. + + From: foo1@example.com + To: foo2@example.com + Subject: Summer Company Holidays + Mime-Version: 1.0 + Content-Type:text/calendar; method=PUBLISH; charset=US-ASCII + Content-Transfer-Encoding: 7bit + Content-Disposition: attachment; filename="event.vcs" + + + + + +Dawson, et. al. Standards Track [Page 10] + +RFC 2447 iMIP November 1998 + + + BEGIN:VCALENDAR + PRODID:-//ACME/DESKTOPCALENDAR//EN + METHOD:PUBLISH + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:MAILTO:FOO1@EXAMPLE.COM + DTSTAMP:19970611T150000Z + DTSTART:19970701T150000Z + DTEND:19970701T230000Z + SUMMARY:Company Picnic + DESCRIPTION:Food and drink will be provided + UID:CALSVR.EXAMPLE.COM-873970198738777-1 + SEQUENCE:0 + STATUS:CONFIRMED + END:VEVENT + BEGIN:VEVENT + ORGANIZER:MAILTO:FOO1@EXAMPLE.COM + DTSTAMP:19970611T190000Z + DTSTART:19970715T150000Z + DTEND:19970715T230000Z + SUMMARY:Company Bowling Tournament + DESCRIPTION:We have 10 lanes reserved + UID:CALSVR.EXAMPLE.COM-873970198738777-2 + SEQUENCE:0 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + +4.5 Multiple Mixed Components + + Different component types must be encapsulated in separate iCalendar + objects. + + From: foo1@example.com + To: foo2@example.com + Subject: Phone Conference + Mime-Version: 1.0 + Content-Type:multipart/mixed;boundary="--FEE3790DC7E35189CA67CE2C" + + This is a multi-part message in MIME format. + + ----FEE3790DC7E35189CA67CE2C + Content-Type:text/calendar; method=REQUEST; charset=US-ASCII + Content-Transfer-Encoding: 7bit + Content-Disposition: attachment; filename="event1.vcs" + + + + + + +Dawson, et. al. Standards Track [Page 11] + +RFC 2447 iMIP November 1998 + + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:mailto:foo1@example.com + ATTENDEE;ROLE=CHAIR;ATTSTAT=ACCEPTED:mailto:foo1@example.com + ATTENDEE;RSVP=YES;TYPE=INDIVIDUAL:mailto:foo2@example.com + DTSTAMP:19970611T190000Z + DTSTART:19970701T210000Z + DTEND:19970701T230000Z + SUMMARY:Phone Conference + DESCRIPTION:Discuss what happened at the last meeting + UID:calsvr.example.com-8739701987387772 + SEQUENCE:0 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + ----FEE3790DC7E35189CA67CE2C + Content-Type:text/calendar; method=REQUEST; charset=US-ASCII + Content-Transfer-Encoding:7bit + Content-Disposition: attachment; filename="todo1.vcs" + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + METHOD:REQUEST + VERSION:2.0 + BEGIN:VTODO + DUE:19970701T090000-0700 + ORGANIZER:mailto:foo1@example.com + ATTENDEE;ROLE=CHAIR;ATTSTAT=ACCEPTED:mailto:foo1@example.com + ATTENDEE;RSVP=YES:mailto:foo2@example.com + SUMMARY:Phone Conference + DESCRIPTION:Discuss a new location for the company picnic + UID:calsvr.example.com-td-8739701987387773 + SEQUENCE:0 + STATUS:NEEDS ACTION + END:VEVENT + END:VCALENDAR + + ----FEE3790DC7E35189CA67CE2C + + + + + + + + + +Dawson, et. al. Standards Track [Page 12] + +RFC 2447 iMIP November 1998 + + +4.6 Detailed Components With An ATTACH Property + + This example shows the format of a message containing a group meeting + between three individuals. The multipart/related encapsulation is + used because the iCalendar object contains an ATTACH property that + uses a CID to reference the attachment. + + From: foo1@example.com + MIME-Version: 1.0 + To: foo2@example.com,foo3@example.com + Subject: REQUEST - Phone Conference + Content-Type:multipart/related;boundary="--FEE3790DC7E35189CA67CE2C" + + ----FEE3790DC7E35189CA67CE2C + Content-Type: multipart/alternative; + boundary="--00FEE3790DC7E35189CA67CE2C00" + + ----00FEE3790DC7E35189CA67CE2C00 + Content-Type: text/plain; charset=us-ascii + Content-Transfer-Encoding: 7bit + + When: 7/1/1997 10:00PM PDT- 7/1/97 10:30 PM PDT + Where: + Organizer: foo1@example.com + Summary: Let's discuss the attached document + + ----00FEE3790DC7E35189CA67CE2C00 + + Content-Type:text/calendar; method=REQUEST; charset=US-ASCII; + Component=vevent + Content-Transfer-Encoding: 7bit + Content-Disposition: attachment; filename="event.vcs" + + BEGIN:VCALENDAR + PRODID:-//ACME/DesktopCalendar//EN + PROFILE:REQUEST + PROFILE-VERSION:1.0 + VERSION:2.0 + BEGIN:VEVENT + ORGANIZER:foo1@example.com + ATTENDEE;ROLE=CHAIR;ATTSTAT=ACCEPTED:foo1@example.com + ATTENDEE;RSVP=YES;TYPE=INDIVIDUAL:mailto:foo2@example.com + ATTENDEE;RSVP=YES;TYPE=INDIVIDUAL:mailto:foo3@example.com + DTSTAMP:19970611T190000Z + DTSTART:19970621T170000Z + DTEND:199706211T173000Z + SUMMARY:Let's discuss the attached document + UID:calsvr.example.com-873970198738777-8aa + + + +Dawson, et. al. Standards Track [Page 13] + +RFC 2447 iMIP November 1998 + + + ATTACH:cid:calsvr.example.com-12345aaa + SEQUENCE:0 + STATUS:CONFIRMED + END:VEVENT + END:VCALENDAR + + ----00FEE3790DC7E35189CA67CE2C00 + + ----FEE3790DC7E35189CA67CE2C + Content-Type: application/msword; name="FieldReport.doc" + Content-Transfer-Encoding: base64 + Content-Disposition: inline; filename="FieldReport.doc" + Content-ID: + + + R0lGODdhTAQZAJEAAFVVVd3d3e4AAP///ywAAAAATAQZAAAC/5yPOSLhD6OctNqLs94XqAG + 4kiW5omm6sq27gvH8kzX9o1y+s73/g8MCofEovGITCoxKMbyCR16cNSq9YrNarfcrvdriIH + 5LL5jE6rxc3G+v2cguf0uv2Oz+v38L7/DxgoOKjURnjIIbe3yNjo+AgZWYVIWWl5iZnJY6J. + + ----FEE3790DC7E35189CA67CE2C + +5 Recommended Practices + + This section outlines a series of recommended practices when using a + messaging transport to exchange iCalendar objects. + +5.1 Use of Content and Message IDs + + The [iCAL] specification makes frequent use of the URI for data types + in properties such as "DESCRIPTION", "ATTACH", "CONTACT" and others. + Two forms of URIs are Message ID (MID) and Content ID (CID). These + are defined in [RFC-2111]. Although [RFC-2111] allows referencing + messages or MIME body parts in other MIME entities or stores, it is + strongly recommended that iMIP implementations include all referenced + messages and body parts in a single MIME entity. Simply put, if an + iCalendar object contains CID or MID references to other messages or + body parts, implementations should ensure that these messages and/or + body parts are transmitted with the iCalendar object. If they are not + there is no guarantee that the receiving "CU" will have the access or + the authorization to view those objects. + + + + + + + + + + + +Dawson, et. al. Standards Track [Page 14] + +RFC 2447 iMIP November 1998 + + +6 Bibliography + + [CHST] Character Sets, ftp://ftp.isi.edu/in- + notes/iana/assignments/character-sets + + [iCAL] Dawson, F. and D. Stenerson, "Internet Calendaring and + Scheduling Core Object Specification - iCalendar", RFC + 2445, November 1998. + + [iTIP] Silverberg, S., Mansour, S., Dawson, F. and R. Hopson, + "iCalendar Transport-Independent Interoperability Protocol + (iTIP): Scheduling Events, Busy Time, To-dos and Journal + Entries", RFC 2446, November 1998. + + [RFC-822] Crocker, D., "Standard for the Format of ARPA Internet + Text Messages", STD 11, RFC 822, August 1982. + + [RFC-1847] Galvin, J., Murphy, S., Crocker, S. and N. Freed, + "Security Multiparts for MIME: Multipart/Signed and + Multipart/Encrypted", RFC 1847, October 1995. + + [RFC-2045] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) - Part One: Format of Internet Message + Bodies", RFC 2045, November 1996. + + [RFC-2046] Freed, N. and N. Borenstein, "Multipurpose Internet Mail + Extensions (MIME) - Part Two: Media Types", RFC 2046, + November 1996. + + [RFC-2047] Moore, K., "Multipurpose Internet Mail Extensions (MIME) - + Part Three: Message Header Extensions for Non-ASCII Text", + RFC 2047, November 1996. + + [RFC-2048] Freed, N., Klensin, J. and J. Postel, "Multipurpose + Internet Mail Extensions (MIME) - Part Four: Registration + Procedures", RFC 2048, January 1997. + + [RFC-2111] Levinson, E., "Content-ID and Message-ID Uniform Resource + Locators", RFC 2111, March 1997. + + [RFC-2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, March 1997. + + + + + + + + + +Dawson, et. al. Standards Track [Page 15] + +RFC 2447 iMIP November 1998 + + +7 Authors' Addresses + + The following address information is provided in a vCard v3.0, + Electronic Business Card, format. + + BEGIN:VCARD + VERSION:3.0 + N:Dawson;Frank + FN:Frank Dawson + ORG:Lotus Development Corporation + ADR;TYPE=WORK,POSTAL,PARCEL:;;6544 Battleford + Drive;Raleigh;NC;27613-3502;USA + TEL;TYPE=WORK,MSG:+1-919-676-9515 + TEL;TYPE=WORK,FAX:+1-919-676-9564 + EMAIL;TYPE=INTERNET:fdawson@earthlink.net + URL:http://home.earthlink.net/~fdawson + END:VCARD + + BEGIN:VCARD + VERSION:3.0 + N:Mansour;Steve + FN:Steve Mansour + ORG:Netscape Communications Corporation + ADR;TYPE=WORK,POSTAL,PARCEL:;;501 East Middlefield Road;Mountain + View;CA;94043;USA + TEL;TYPE=WORK,MSG:+1-650-937-2378 + TEL;TYPE=WORK,FAX:+1-650-937-2103 + EMAIL;TYPE=INTERNET:sman@netscape.com + END:VCARD + + BEGIN:VCARD + VERSION:3.0 + N:Silverberg;Steve + FN:Steve Silverberg + ORG:Microsoft Corporation + ADR;TYPE=WORK,POSTAL,PARCEL:;;One Microsoft Way; + Redmond;WA;98052-6399;USA + TEL;TYPE=WORK,MSG:+1-425-936-9277 + TEL;TYPE=WORK,FAX:+1-425-936-8019 + EMAIL;TYPE=INTERNET:stevesil@Microsoft.com + END:VCARD + + + + + + + + + + +Dawson, et. al. Standards Track [Page 16] + +RFC 2447 iMIP November 1998 + + + The iCalendar Object is a result of the work of the Internet + Engineering Task Force Calendaring and scheduling Working Group. The + chairmen of that working group are: + + BEGIN:VCARD + VERSION:3.0 + N:Ganguly;Anik + FN:Anik Ganguly + ORG:Open Text Inc. + ADR;TYPE=WORK,POSTAL,PARCEL:;Suite 101;38777 West Six Mile Road; + Livonia;MI;48152;USA + TEL;TYPE=WORK,MSG:+1-734-542-5955 + EMAIL;TYPE=INTERNET:ganguly@acm.org + END:VCARD + + BEGIN:VCARD + VERSION:3.0 + N:Moskowitz;Robert + FN:Robert Moskowitz + EMAIL;TYPE=INTERNET:rgm-ietf@htt-consult.com + END:VCARD + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Dawson, et. al. Standards Track [Page 17] + +RFC 2447 iMIP November 1998 + + +8. Full Copyright Statement + + Copyright (C) The Internet Society (1998). All Rights Reserved. + + This document and translations of it may be copied and furnished to + others, and derivative works that comment on or otherwise explain it + or assist in its implementation may be prepared, copied, published + and distributed, in whole or in part, without restriction of any + kind, provided that the above copyright notice and this paragraph are + included on all such copies and derivative works. However, this + document itself may not be modified in any way, such as by removing + the copyright notice or references to the Internet Society or other + Internet organizations, except as needed for the purpose of + developing Internet standards in which case the procedures for + copyrights defined in the Internet Standards process must be + followed, or as required to translate it into languages other than + English. + + The limited permissions granted above are perpetual and will not be + revoked by the Internet Society or its successors or assigns. + + This document and the information contained herein is provided on an + "AS IS" basis and THE INTERNET SOCIETY AND THE INTERNET ENGINEERING + TASK FORCE DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING + BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION + HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. + + + + + + + + + + + + + + + + + + + + + + + + +Dawson, et. al. Standards Track [Page 18] + diff --git a/lib/qCal/docs/rfc/rfc822.txt b/lib/qCal/docs/rfc/rfc822.txt new file mode 100644 index 0000000..35b09a3 --- /dev/null +++ b/lib/qCal/docs/rfc/rfc822.txt @@ -0,0 +1,2901 @@ + + + + + + + RFC # 822 + + Obsoletes: RFC #733 (NIC #41952) + + + + + + + + + + + + + STANDARD FOR THE FORMAT OF + + ARPA INTERNET TEXT MESSAGES + + + + + + + August 13, 1982 + + + + + + + Revised by + + David H. Crocker + + + Dept. of Electrical Engineering + University of Delaware, Newark, DE 19711 + Network: DCrocker @ UDel-Relay + + + + + + + + + + + + + + + + Standard for ARPA Internet Text Messages + + + TABLE OF CONTENTS + + + PREFACE .................................................... ii + + 1. INTRODUCTION ........................................... 1 + + 1.1. Scope ............................................ 1 + 1.2. Communication Framework .......................... 2 + + 2. NOTATIONAL CONVENTIONS ................................. 3 + + 3. LEXICAL ANALYSIS OF MESSAGES ........................... 5 + + 3.1. General Description .............................. 5 + 3.2. Header Field Definitions ......................... 9 + 3.3. Lexical Tokens ................................... 10 + 3.4. Clarifications ................................... 11 + + 4. MESSAGE SPECIFICATION .................................. 17 + + 4.1. Syntax ........................................... 17 + 4.2. Forwarding ....................................... 19 + 4.3. Trace Fields ..................................... 20 + 4.4. Originator Fields ................................ 21 + 4.5. Receiver Fields .................................. 23 + 4.6. Reference Fields ................................. 23 + 4.7. Other Fields ..................................... 24 + + 5. DATE AND TIME SPECIFICATION ............................ 26 + + 5.1. Syntax ........................................... 26 + 5.2. Semantics ........................................ 26 + + 6. ADDRESS SPECIFICATION .................................. 27 + + 6.1. Syntax ........................................... 27 + 6.2. Semantics ........................................ 27 + 6.3. Reserved Address ................................. 33 + + 7. BIBLIOGRAPHY ........................................... 34 + + + APPENDIX + + A. EXAMPLES ............................................... 36 + B. SIMPLE FIELD PARSING ................................... 40 + C. DIFFERENCES FROM RFC #733 .............................. 41 + D. ALPHABETICAL LISTING OF SYNTAX RULES ................... 44 + + + August 13, 1982 - i - RFC #822 + + + + + Standard for ARPA Internet Text Messages + + + PREFACE + + + By 1977, the Arpanet employed several informal standards for + the text messages (mail) sent among its host computers. It was + felt necessary to codify these practices and provide for those + features that seemed imminent. The result of that effort was + Request for Comments (RFC) #733, "Standard for the Format of ARPA + Network Text Message", by Crocker, Vittal, Pogran, and Henderson. + The specification attempted to avoid major changes in existing + software, while permitting several new features. + + This document revises the specifications in RFC #733, in + order to serve the needs of the larger and more complex ARPA + Internet. Some of RFC #733's features failed to gain adequate + acceptance. In order to simplify the standard and the software + that follows it, these features have been removed. A different + addressing scheme is used, to handle the case of inter-network + mail; and the concept of re-transmission has been introduced. + + This specification is intended for use in the ARPA Internet. + However, an attempt has been made to free it of any dependence on + that environment, so that it can be applied to other network text + message systems. + + The specification of RFC #733 took place over the course of + one year, using the ARPANET mail environment, itself, to provide + an on-going forum for discussing the capabilities to be included. + More than twenty individuals, from across the country, partici- + pated in the original discussion. The development of this + revised specification has, similarly, utilized network mail-based + group discussion. Both specification efforts greatly benefited + from the comments and ideas of the participants. + + The syntax of the standard, in RFC #733, was originally + specified in the Backus-Naur Form (BNF) meta-language. Ken L. + Harrenstien, of SRI International, was responsible for re-coding + the BNF into an augmented BNF that makes the representation + smaller and easier to understand. + + + + + + + + + + + + + August 13, 1982 - ii - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 1. INTRODUCTION + + 1.1. SCOPE + + This standard specifies a syntax for text messages that are + sent among computer users, within the framework of "electronic + mail". The standard supersedes the one specified in ARPANET + Request for Comments #733, "Standard for the Format of ARPA Net- + work Text Messages". + + In this context, messages are viewed as having an envelope + and contents. The envelope contains whatever information is + needed to accomplish transmission and delivery. The contents + compose the object to be delivered to the recipient. This stan- + dard applies only to the format and some of the semantics of mes- + sage contents. It contains no specification of the information + in the envelope. + + However, some message systems may use information from the + contents to create the envelope. It is intended that this stan- + dard facilitate the acquisition of such information by programs. + + Some message systems may store messages in formats that + differ from the one specified in this standard. This specifica- + tion is intended strictly as a definition of what message content + format is to be passed BETWEEN hosts. + + Note: This standard is NOT intended to dictate the internal for- + mats used by sites, the specific message system features + that they are expected to support, or any of the charac- + teristics of user interface programs that create or read + messages. + + A distinction should be made between what the specification + REQUIRES and what it ALLOWS. Messages can be made complex and + rich with formally-structured components of information or can be + kept small and simple, with a minimum of such information. Also, + the standard simplifies the interpretation of differing visual + formats in messages; only the visual aspect of a message is + affected and not the interpretation of information within it. + Implementors may choose to retain such visual distinctions. + + The formal definition is divided into four levels. The bot- + tom level describes the meta-notation used in this document. The + second level describes basic lexical analyzers that feed tokens + to higher-level parsers. Next is an overall specification for + messages; it permits distinguishing individual fields. Finally, + there is definition of the contents of several structured fields. + + + + August 13, 1982 - 1 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 1.2. COMMUNICATION FRAMEWORK + + Messages consist of lines of text. No special provisions + are made for encoding drawings, facsimile, speech, or structured + text. No significant consideration has been given to questions + of data compression or to transmission and storage efficiency, + and the standard tends to be free with the number of bits con- + sumed. For example, field names are specified as free text, + rather than special terse codes. + + A general "memo" framework is used. That is, a message con- + sists of some information in a rigid format, followed by the main + part of the message, with a format that is not specified in this + document. The syntax of several fields of the rigidly-formated + ("headers") section is defined in this specification; some of + these fields must be included in all messages. + + The syntax that distinguishes between header fields is + specified separately from the internal syntax for particular + fields. This separation is intended to allow simple parsers to + operate on the general structure of messages, without concern for + the detailed structure of individual header fields. Appendix B + is provided to facilitate construction of these parsers. + + In addition to the fields specified in this document, it is + expected that other fields will gain common use. As necessary, + the specifications for these "extension-fields" will be published + through the same mechanism used to publish this document. Users + may also wish to extend the set of fields that they use + privately. Such "user-defined fields" are permitted. + + The framework severely constrains document tone and appear- + ance and is primarily useful for most intra-organization communi- + cations and well-structured inter-organization communication. + It also can be used for some types of inter-process communica- + tion, such as simple file transfer and remote job entry. A more + robust framework might allow for multi-font, multi-color, multi- + dimension encoding of information. A less robust one, as is + present in most single-machine message systems, would more + severely constrain the ability to add fields and the decision to + include specific fields. In contrast with paper-based communica- + tion, it is interesting to note that the RECEIVER of a message + can exercise an extraordinary amount of control over the + message's appearance. The amount of actual control available to + message receivers is contingent upon the capabilities of their + individual message systems. + + + + + + August 13, 1982 - 2 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 2. NOTATIONAL CONVENTIONS + + This specification uses an augmented Backus-Naur Form (BNF) + notation. The differences from standard BNF involve naming rules + and indicating repetition and "local" alternatives. + + 2.1. RULE NAMING + + Angle brackets ("<", ">") are not used, in general. The + name of a rule is simply the name itself, rather than "". + Quotation-marks enclose literal text (which may be upper and/or + lower case). Certain basic rules are in uppercase, such as + SPACE, TAB, CRLF, DIGIT, ALPHA, etc. Angle brackets are used in + rule definitions, and in the rest of this document, whenever + their presence will facilitate discerning the use of rule names. + + 2.2. RULE1 / RULE2: ALTERNATIVES + + Elements separated by slash ("/") are alternatives. There- + fore "foo / bar" will accept foo or bar. + + 2.3. (RULE1 RULE2): LOCAL ALTERNATIVES + + Elements enclosed in parentheses are treated as a single + element. Thus, "(elem (foo / bar) elem)" allows the token + sequences "elem foo elem" and "elem bar elem". + + 2.4. *RULE: REPETITION + + The character "*" preceding an element indicates repetition. + The full form is: + + *element + + indicating at least and at most occurrences of element. + Default values are 0 and infinity so that "*(element)" allows any + number, including zero; "1*element" requires at least one; and + "1*2element" allows one or two. + + 2.5. [RULE]: OPTIONAL + + Square brackets enclose optional elements; "[foo bar]" is + equivalent to "*1(foo bar)". + + 2.6. NRULE: SPECIFIC REPETITION + + "(element)" is equivalent to "*(element)"; that is, + exactly occurrences of (element). Thus 2DIGIT is a 2-digit + number, and 3ALPHA is a string of three alphabetic characters. + + + August 13, 1982 - 3 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 2.7. #RULE: LISTS + + A construct "#" is defined, similar to "*", as follows: + + #element + + indicating at least and at most elements, each separated + by one or more commas (","). This makes the usual form of lists + very easy; a rule such as '(element *("," element))' can be shown + as "1#element". Wherever this construct is used, null elements + are allowed, but do not contribute to the count of elements + present. That is, "(element),,(element)" is permitted, but + counts as only two elements. Therefore, where at least one ele- + ment is required, at least one non-null element must be present. + Default values are 0 and infinity so that "#(element)" allows any + number, including zero; "1#element" requires at least one; and + "1#2element" allows one or two. + + 2.8. ; COMMENTS + + A semi-colon, set off some distance to the right of rule + text, starts a comment that continues to the end of line. This + is a simple way of including useful notes in parallel with the + specifications. + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 4 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3. LEXICAL ANALYSIS OF MESSAGES + + 3.1. GENERAL DESCRIPTION + + A message consists of header fields and, optionally, a body. + The body is simply a sequence of lines containing ASCII charac- + ters. It is separated from the headers by a null line (i.e., a + line with nothing preceding the CRLF). + + 3.1.1. LONG HEADER FIELDS + + Each header field can be viewed as a single, logical line of + ASCII characters, comprising a field-name and a field-body. + For convenience, the field-body portion of this conceptual + entity can be split into a multiple-line representation; this + is called "folding". The general rule is that wherever there + may be linear-white-space (NOT simply LWSP-chars), a CRLF + immediately followed by AT LEAST one LWSP-char may instead be + inserted. Thus, the single line + + To: "Joe & J. Harvey" , JJV @ BBN + + can be represented as: + + To: "Joe & J. Harvey" , + JJV@BBN + + and + + To: "Joe & J. Harvey" + , JJV + @BBN + + and + + To: "Joe & + J. Harvey" , JJV @ BBN + + The process of moving from this folded multiple-line + representation of a header field to its single line represen- + tation is called "unfolding". Unfolding is accomplished by + regarding CRLF immediately followed by a LWSP-char as + equivalent to the LWSP-char. + + Note: While the standard permits folding wherever linear- + white-space is permitted, it is recommended that struc- + tured fields, such as those containing addresses, limit + folding to higher-level syntactic breaks. For address + fields, it is recommended that such folding occur + + + August 13, 1982 - 5 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + between addresses, after the separating comma. + + 3.1.2. STRUCTURE OF HEADER FIELDS + + Once a field has been unfolded, it may be viewed as being com- + posed of a field-name followed by a colon (":"), followed by a + field-body, and terminated by a carriage-return/line-feed. + The field-name must be composed of printable ASCII characters + (i.e., characters that have values between 33. and 126., + decimal, except colon). The field-body may be composed of any + ASCII characters, except CR or LF. (While CR and/or LF may be + present in the actual text, they are removed by the action of + unfolding the field.) + + Certain field-bodies of headers may be interpreted according + to an internal syntax that some systems may wish to parse. + These fields are called "structured fields". Examples + include fields containing dates and addresses. Other fields, + such as "Subject" and "Comments", are regarded simply as + strings of text. + + Note: Any field which has a field-body that is defined as + other than simply is to be treated as a struc- + tured field. + + Field-names, unstructured field bodies and structured + field bodies each are scanned by their own, independent + "lexical" analyzers. + + 3.1.3. UNSTRUCTURED FIELD BODIES + + For some fields, such as "Subject" and "Comments", no struc- + turing is assumed, and they are treated simply as s, as + in the message body. Rules of folding apply to these fields, + so that such field bodies which occupy several lines must + therefore have the second and successive lines indented by at + least one LWSP-char. + + 3.1.4. STRUCTURED FIELD BODIES + + To aid in the creation and reading of structured fields, the + free insertion of linear-white-space (which permits folding + by inclusion of CRLFs) is allowed between lexical tokens. + Rather than obscuring the syntax specifications for these + structured fields with explicit syntax for this linear-white- + space, the existence of another "lexical" analyzer is assumed. + This analyzer does not apply for unstructured field bodies + that are simply strings of text, as described above. The + analyzer provides an interpretation of the unfolded text + + + August 13, 1982 - 6 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + composing the body of the field as a sequence of lexical sym- + bols. + + These symbols are: + + - individual special characters + - quoted-strings + - domain-literals + - comments + - atoms + + The first four of these symbols are self-delimiting. Atoms + are not; they are delimited by the self-delimiting symbols and + by linear-white-space. For the purposes of regenerating + sequences of atoms and quoted-strings, exactly one SPACE is + assumed to exist, and should be used, between them. (Also, in + the "Clarifications" section on "White Space", below, note the + rules about treatment of multiple contiguous LWSP-chars.) + + So, for example, the folded body of an address field + + ":sysmail"@ Some-Group. Some-Org, + Muhammed.(I am the greatest) Ali @(the)Vegas.WBA + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 7 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + is analyzed into the following lexical symbols and types: + + :sysmail quoted string + @ special + Some-Group atom + . special + Some-Org atom + , special + Muhammed atom + . special + (I am the greatest) comment + Ali atom + @ atom + (the) comment + Vegas atom + . special + WBA atom + + The canonical representations for the data in these addresses + are the following strings: + + ":sysmail"@Some-Group.Some-Org + + and + + Muhammed.Ali@Vegas.WBA + + Note: For purposes of display, and when passing such struc- + tured information to other systems, such as mail proto- + col services, there must be NO linear-white-space + between s that are separated by period (".") or + at-sign ("@") and exactly one SPACE between all other + s. Also, headers should be in a folded form. + + + + + + + + + + + + + + + + + + + August 13, 1982 - 8 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.2. HEADER FIELD DEFINITIONS + + These rules show a field meta-syntax, without regard for the + particular type or internal syntax. Their purpose is to permit + detection of fields; also, they present to higher-level parsers + an image of each field as fitting on one line. + + field = field-name ":" [ field-body ] CRLF + + field-name = 1* + + field-body = field-body-contents + [CRLF LWSP-char field-body] + + field-body-contents = + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 9 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.3. LEXICAL TOKENS + + The following rules are used to define an underlying lexical + analyzer, which feeds tokens to higher level parsers. See the + ANSI references, in the Bibliography. + + ; ( Octal, Decimal.) + CHAR = ; ( 0-177, 0.-127.) + ALPHA = + ; (101-132, 65.- 90.) + ; (141-172, 97.-122.) + DIGIT = ; ( 60- 71, 48.- 57.) + CTL = ; ( 177, 127.) + CR = ; ( 15, 13.) + LF = ; ( 12, 10.) + SPACE = ; ( 40, 32.) + HTAB = ; ( 11, 9.) + <"> = ; ( 42, 34.) + CRLF = CR LF + + LWSP-char = SPACE / HTAB ; semantics = SPACE + + linear-white-space = 1*([CRLF] LWSP-char) ; semantics = SPACE + ; CRLF => folding + + specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted- + / "," / ";" / ":" / "\" / <"> ; string, to use + / "." / "[" / "]" ; within a word. + + delimiters = specials / linear-white-space / comment + + text = atoms, specials, + CR & bare LF, but NOT ; comments and + including CRLF> ; quoted-strings are + ; NOT recognized. + + atom = 1* + + quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or + ; quoted chars. + + qtext = , ; => may be folded + "\" & CR, and including + linear-white-space> + + domain-literal = "[" *(dtext / quoted-pair) "]" + + + + + August 13, 1982 - 10 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + dtext = may be folded + "]", "\" & CR, & including + linear-white-space> + + comment = "(" *(ctext / quoted-pair / comment) ")" + + ctext = may be folded + ")", "\" & CR, & including + linear-white-space> + + quoted-pair = "\" CHAR ; may quote any char + + phrase = 1*word ; Sequence of words + + word = atom / quoted-string + + + 3.4. CLARIFICATIONS + + 3.4.1. QUOTING + + Some characters are reserved for special interpretation, such + as delimiting lexical tokens. To permit use of these charac- + ters as uninterpreted data, a quoting mechanism is provided. + To quote a character, precede it with a backslash ("\"). + + This mechanism is not fully general. Characters may be quoted + only within a subset of the lexical constructs. In particu- + lar, quoting is limited to use within: + + - quoted-string + - domain-literal + - comment + + Within these constructs, quoting is REQUIRED for CR and "\" + and for the character(s) that delimit the token (e.g., "(" and + ")" for a comment). However, quoting is PERMITTED for any + character. + + Note: In particular, quoting is NOT permitted within atoms. + For example when the local-part of an addr-spec must + contain a special character, a quoted string must be + used. Therefore, a specification such as: + + Full\ Name@Domain + + is not legal and must be specified as: + + "Full Name"@Domain + + + August 13, 1982 - 11 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.4.2. WHITE SPACE + + Note: In structured field bodies, multiple linear space ASCII + characters (namely HTABs and SPACEs) are treated as + single spaces and may freely surround any symbol. In + all header fields, the only place in which at least one + LWSP-char is REQUIRED is at the beginning of continua- + tion lines in a folded field. + + When passing text to processes that do not interpret text + according to this standard (e.g., mail protocol servers), then + NO linear-white-space characters should occur between a period + (".") or at-sign ("@") and a . Exactly ONE SPACE should + be used in place of arbitrary linear-white-space and comment + sequences. + + Note: Within systems conforming to this standard, wherever a + member of the list of delimiters is allowed, LWSP-chars + may also occur before and/or after it. + + Writers of mail-sending (i.e., header-generating) programs + should realize that there is no network-wide definition of the + effect of ASCII HT (horizontal-tab) characters on the appear- + ance of text at another network host; therefore, the use of + tabs in message headers, though permitted, is discouraged. + + 3.4.3. COMMENTS + + A comment is a set of ASCII characters, which is enclosed in + matching parentheses and which is not within a quoted-string + The comment construct permits message originators to add text + which will be useful for human readers, but which will be + ignored by the formal semantics. Comments should be retained + while the message is subject to interpretation according to + this standard. However, comments must NOT be included in + other cases, such as during protocol exchanges with mail + servers. + + Comments nest, so that if an unquoted left parenthesis occurs + in a comment string, there must also be a matching right + parenthesis. When a comment acts as the delimiter between a + sequence of two lexical symbols, such as two atoms, it is lex- + ically equivalent with a single SPACE, for the purposes of + regenerating the sequence, such as when passing the sequence + onto a mail protocol server. Comments are detected as such + only within field-bodies of structured fields. + + If a comment is to be "folded" onto multiple lines, then the + syntax for folding must be adhered to. (See the "Lexical + + + August 13, 1982 - 12 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Analysis of Messages" section on "Folding Long Header Fields" + above, and the section on "Case Independence" below.) Note + that the official semantics therefore do not "see" any + unquoted CRLFs that are in comments, although particular pars- + ing programs may wish to note their presence. For these pro- + grams, it would be reasonable to interpret a "CRLF LWSP-char" + as being a CRLF that is part of the comment; i.e., the CRLF is + kept and the LWSP-char is discarded. Quoted CRLFs (i.e., a + backslash followed by a CR followed by a LF) still must be + followed by at least one LWSP-char. + + 3.4.4. DELIMITING AND QUOTING CHARACTERS + + The quote character (backslash) and characters that delimit + syntactic units are not, generally, to be taken as data that + are part of the delimited or quoted unit(s). In particular, + the quotation-marks that define a quoted-string, the + parentheses that define a comment and the backslash that + quotes a following character are NOT part of the quoted- + string, comment or quoted character. A quotation-mark that is + to be part of a quoted-string, a parenthesis that is to be + part of a comment and a backslash that is to be part of either + must each be preceded by the quote-character backslash ("\"). + Note that the syntax allows any character to be quoted within + a quoted-string or comment; however only certain characters + MUST be quoted to be included as data. These characters are + the ones that are not part of the alternate text group (i.e., + ctext or qtext). + + The one exception to this rule is that a single SPACE is + assumed to exist between contiguous words in a phrase, and + this interpretation is independent of the actual number of + LWSP-chars that the creator places between the words. To + include more than one SPACE, the creator must make the LWSP- + chars be part of a quoted-string. + + Quotation marks that delimit a quoted string and backslashes + that quote the following character should NOT accompany the + quoted-string when the string is passed to processes that do + not interpret data according to this specification (e.g., mail + protocol servers). + + 3.4.5. QUOTED-STRINGS + + Where permitted (i.e., in words in structured fields) quoted- + strings are treated as a single symbol. That is, a quoted- + string is equivalent to an atom, syntactically. If a quoted- + string is to be "folded" onto multiple lines, then the syntax + for folding must be adhered to. (See the "Lexical Analysis of + + + August 13, 1982 - 13 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Messages" section on "Folding Long Header Fields" above, and + the section on "Case Independence" below.) Therefore, the + official semantics do not "see" any bare CRLFs that are in + quoted-strings; however particular parsing programs may wish + to note their presence. For such programs, it would be rea- + sonable to interpret a "CRLF LWSP-char" as being a CRLF which + is part of the quoted-string; i.e., the CRLF is kept and the + LWSP-char is discarded. Quoted CRLFs (i.e., a backslash fol- + lowed by a CR followed by a LF) are also subject to rules of + folding, but the presence of the quoting character (backslash) + explicitly indicates that the CRLF is data to the quoted + string. Stripping off the first following LWSP-char is also + appropriate when parsing quoted CRLFs. + + 3.4.6. BRACKETING CHARACTERS + + There is one type of bracket which must occur in matched pairs + and may have pairs nested within each other: + + o Parentheses ("(" and ")") are used to indicate com- + ments. + + There are three types of brackets which must occur in matched + pairs, and which may NOT be nested: + + o Colon/semi-colon (":" and ";") are used in address + specifications to indicate that the included list of + addresses are to be treated as a group. + + o Angle brackets ("<" and ">") are generally used to + indicate the presence of a one machine-usable refer- + ence (e.g., delimiting mailboxes), possibly including + source-routing to the machine. + + o Square brackets ("[" and "]") are used to indicate the + presence of a domain-literal, which the appropriate + name-domain is to use directly, bypassing normal + name-resolution mechanisms. + + 3.4.7. CASE INDEPENDENCE + + Except as noted, alphabetic strings may be represented in any + combination of upper and lower case. The only syntactic units + + + + + + + + + August 13, 1982 - 14 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + which requires preservation of case information are: + + - text + - qtext + - dtext + - ctext + - quoted-pair + - local-part, except "Postmaster" + + When matching any other syntactic unit, case is to be ignored. + For example, the field-names "From", "FROM", "from", and even + "FroM" are semantically equal and should all be treated ident- + ically. + + When generating these units, any mix of upper and lower case + alphabetic characters may be used. The case shown in this + specification is suggested for message-creating processes. + + Note: The reserved local-part address unit, "Postmaster", is + an exception. When the value "Postmaster" is being + interpreted, it must be accepted in any mixture of + case, including "POSTMASTER", and "postmaster". + + 3.4.8. FOLDING LONG HEADER FIELDS + + Each header field may be represented on exactly one line con- + sisting of the name of the field and its body, and terminated + by a CRLF; this is what the parser sees. For readability, the + field-body portion of long header fields may be "folded" onto + multiple lines of the actual field. "Long" is commonly inter- + preted to mean greater than 65 or 72 characters. The former + length serves as a limit, when the message is to be viewed on + most simple terminals which use simple display software; how- + ever, the limit is not imposed by this standard. + + Note: Some display software often can selectively fold lines, + to suit the display terminal. In such cases, sender- + provided folding can interfere with the display + software. + + 3.4.9. BACKSPACE CHARACTERS + + ASCII BS characters (Backspace, decimal 8) may be included in + texts and quoted-strings to effect overstriking. However, any + use of backspaces which effects an overstrike to the left of + the beginning of the text or quoted-string is prohibited. + + + + + + August 13, 1982 - 15 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 3.4.10. NETWORK-SPECIFIC TRANSFORMATIONS + + During transmission through heterogeneous networks, it may be + necessary to force data to conform to a network's local con- + ventions. For example, it may be required that a CR be fol- + lowed either by LF, making a CRLF, or by , if the CR is + to stand alone). Such transformations are reversed, when the + message exits that network. + + When crossing network boundaries, the message should be + treated as passing through two modules. It will enter the + first module containing whatever network-specific transforma- + tions that were necessary to permit migration through the + "current" network. It then passes through the modules: + + o Transformation Reversal + + The "current" network's idiosyncracies are removed and + the message is returned to the canonical form speci- + fied in this standard. + + o Transformation + + The "next" network's local idiosyncracies are imposed + on the message. + + ------------------ + From ==> | Remove Net-A | + Net-A | idiosyncracies | + ------------------ + || + \/ + Conformance + with standard + || + \/ + ------------------ + | Impose Net-B | ==> To + | idiosyncracies | Net-B + ------------------ + + + + + + + + + + + + August 13, 1982 - 16 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 4. MESSAGE SPECIFICATION + + 4.1. SYNTAX + + Note: Due to an artifact of the notational conventions, the syn- + tax indicates that, when present, some fields, must be in + a particular order. Header fields are NOT required to + occur in any particular order, except that the message + body must occur AFTER the headers. It is recommended + that, if present, headers be sent in the order "Return- + Path", "Received", "Date", "From", "Subject", "Sender", + "To", "cc", etc. + + This specification permits multiple occurrences of most + fields. Except as noted, their interpretation is not + specified here, and their use is discouraged. + + The following syntax for the bodies of various fields should + be thought of as describing each field body as a single long + string (or line). The "Lexical Analysis of Message" section on + "Long Header Fields", above, indicates how such long strings can + be represented on more than one line in the actual transmitted + message. + + message = fields *( CRLF *text ) ; Everything after + ; first null line + ; is message body + + fields = dates ; Creation time, + source ; author id & one + 1*destination ; address required + *optional-field ; others optional + + source = [ trace ] ; net traversals + originator ; original mail + [ resent ] ; forwarded + + trace = return ; path to sender + 1*received ; receipt tags + + return = "Return-path" ":" route-addr ; return address + + received = "Received" ":" ; one per relay + ["from" domain] ; sending host + ["by" domain] ; receiving host + ["via" atom] ; physical path + *("with" atom) ; link/mail protocol + ["id" msg-id] ; receiver msg id + ["for" addr-spec] ; initial form + + + August 13, 1982 - 17 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + ";" date-time ; time received + + originator = authentic ; authenticated addr + [ "Reply-To" ":" 1#address] ) + + authentic = "From" ":" mailbox ; Single author + / ( "Sender" ":" mailbox ; Actual submittor + "From" ":" 1#mailbox) ; Multiple authors + ; or not sender + + resent = resent-authentic + [ "Resent-Reply-To" ":" 1#address] ) + + resent-authentic = + = "Resent-From" ":" mailbox + / ( "Resent-Sender" ":" mailbox + "Resent-From" ":" 1#mailbox ) + + dates = orig-date ; Original + [ resent-date ] ; Forwarded + + orig-date = "Date" ":" date-time + + resent-date = "Resent-Date" ":" date-time + + destination = "To" ":" 1#address ; Primary + / "Resent-To" ":" 1#address + / "cc" ":" 1#address ; Secondary + / "Resent-cc" ":" 1#address + / "bcc" ":" #address ; Blind carbon + / "Resent-bcc" ":" #address + + optional-field = + / "Message-ID" ":" msg-id + / "Resent-Message-ID" ":" msg-id + / "In-Reply-To" ":" *(phrase / msg-id) + / "References" ":" *(phrase / msg-id) + / "Keywords" ":" #phrase + / "Subject" ":" *text + / "Comments" ":" *text + / "Encrypted" ":" 1#2word + / extension-field ; To be defined + / user-defined-field ; May be pre-empted + + msg-id = "<" addr-spec ">" ; Unique message id + + + + + + + August 13, 1982 - 18 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + extension-field = + + + user-defined-field = + + + 4.2. FORWARDING + + Some systems permit mail recipients to forward a message, + retaining the original headers, by adding some new fields. This + standard supports such a service, through the "Resent-" prefix to + field names. + + Whenever the string "Resent-" begins a field name, the field + has the same semantics as a field whose name does not have the + prefix. However, the message is assumed to have been forwarded + by an original recipient who attached the "Resent-" field. This + new field is treated as being more recent than the equivalent, + original field. For example, the "Resent-From", indicates the + person that forwarded the message, whereas the "From" field indi- + cates the original author. + + Use of such precedence information depends upon partici- + pants' communication needs. For example, this standard does not + dictate when a "Resent-From:" address should receive replies, in + lieu of sending them to the "From:" address. + + Note: In general, the "Resent-" fields should be treated as con- + taining a set of information that is independent of the + set of original fields. Information for one set should + not automatically be taken from the other. The interpre- + tation of multiple "Resent-" fields, of the same type, is + undefined. + + In the remainder of this specification, occurrence of legal + "Resent-" fields are treated identically with the occurrence of + + + + + + + + + August 13, 1982 - 19 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + fields whose names do not contain this prefix. + + 4.3. TRACE FIELDS + + Trace information is used to provide an audit trail of mes- + sage handling. In addition, it indicates a route back to the + sender of the message. + + The list of known "via" and "with" values are registered + with the Network Information Center, SRI International, Menlo + Park, California. + + 4.3.1. RETURN-PATH + + This field is added by the final transport system that + delivers the message to its recipient. The field is intended + to contain definitive information about the address and route + back to the message's originator. + + Note: The "Reply-To" field is added by the originator and + serves to direct replies, whereas the "Return-Path" + field is used to identify a path back to the origina- + tor. + + While the syntax indicates that a route specification is + optional, every attempt should be made to provide that infor- + mation in this field. + + 4.3.2. RECEIVED + + A copy of this field is added by each transport service that + relays the message. The information in the field can be quite + useful for tracing transport problems. + + The names of the sending and receiving hosts and time-of- + receipt may be specified. The "via" parameter may be used, to + indicate what physical mechanism the message was sent over, + such as Arpanet or Phonenet, and the "with" parameter may be + used to indicate the mail-, or connection-, level protocol + that was used, such as the SMTP mail protocol, or X.25 tran- + sport protocol. + + Note: Several "with" parameters may be included, to fully + specify the set of protocols that were used. + + Some transport services queue mail; the internal message iden- + tifier that is assigned to the message may be noted, using the + "id" parameter. When the sending host uses a destination + address specification that the receiving host reinterprets, by + + + August 13, 1982 - 20 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + expansion or transformation, the receiving host may wish to + record the original specification, using the "for" parameter. + For example, when a copy of mail is sent to the member of a + distribution list, this parameter may be used to record the + original address that was used to specify the list. + + 4.4. ORIGINATOR FIELDS + + The standard allows only a subset of the combinations possi- + ble with the From, Sender, Reply-To, Resent-From, Resent-Sender, + and Resent-Reply-To fields. The limitation is intentional. + + 4.4.1. FROM / RESENT-FROM + + This field contains the identity of the person(s) who wished + this message to be sent. The message-creation process should + default this field to be a single, authenticated machine + address, indicating the AGENT (person, system or process) + entering the message. If this is not done, the "Sender" field + MUST be present. If the "From" field IS defaulted this way, + the "Sender" field is optional and is redundant with the + "From" field. In all cases, addresses in the "From" field + must be machine-usable (addr-specs) and may not contain named + lists (groups). + + 4.4.2. SENDER / RESENT-SENDER + + This field contains the authenticated identity of the AGENT + (person, system or process) that sends the message. It is + intended for use when the sender is not the author of the mes- + sage, or to indicate who among a group of authors actually + sent the message. If the contents of the "Sender" field would + be completely redundant with the "From" field, then the + "Sender" field need not be present and its use is discouraged + (though still legal). In particular, the "Sender" field MUST + be present if it is NOT the same as the "From" Field. + + The Sender mailbox specification includes a word sequence + which must correspond to a specific agent (i.e., a human user + or a computer program) rather than a standard address. This + indicates the expectation that the field will identify the + single AGENT (person, system, or process) responsible for + sending the mail and not simply include the name of a mailbox + from which the mail was sent. For example in the case of a + shared login name, the name, by itself, would not be adequate. + The local-part address unit, which refers to this agent, is + expected to be a computer system term, and not (for example) a + generalized person reference which can be used outside the + network text message context. + + + August 13, 1982 - 21 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Since the critical function served by the "Sender" field is + identification of the agent responsible for sending mail and + since computer programs cannot be held accountable for their + behavior, it is strongly recommended that when a computer pro- + gram generates a message, the HUMAN who is responsible for + that program be referenced as part of the "Sender" field mail- + box specification. + + 4.4.3. REPLY-TO / RESENT-REPLY-TO + + This field provides a general mechanism for indicating any + mailbox(es) to which responses are to be sent. Three typical + uses for this feature can be distinguished. In the first + case, the author(s) may not have regular machine-based mail- + boxes and therefore wish(es) to indicate an alternate machine + address. In the second case, an author may wish additional + persons to be made aware of, or responsible for, replies. A + somewhat different use may be of some help to "text message + teleconferencing" groups equipped with automatic distribution + services: include the address of that service in the "Reply- + To" field of all messages submitted to the teleconference; + then participants can "reply" to conference submissions to + guarantee the correct distribution of any submission of their + own. + + Note: The "Return-Path" field is added by the mail transport + service, at the time of final deliver. It is intended + to identify a path back to the orginator of the mes- + sage. The "Reply-To" field is added by the message + originator and is intended to direct replies. + + 4.4.4. AUTOMATIC USE OF FROM / SENDER / REPLY-TO + + For systems which automatically generate address lists for + replies to messages, the following recommendations are made: + + o The "Sender" field mailbox should be sent notices of + any problems in transport or delivery of the original + messages. If there is no "Sender" field, then the + "From" field mailbox should be used. + + o The "Sender" field mailbox should NEVER be used + automatically, in a recipient's reply message. + + o If the "Reply-To" field exists, then the reply should + go to the addresses indicated in that field and not to + the address(es) indicated in the "From" field. + + + + + August 13, 1982 - 22 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + o If there is a "From" field, but no "Reply-To" field, + the reply should be sent to the address(es) indicated + in the "From" field. + + Sometimes, a recipient may actually wish to communicate with + the person that initiated the message transfer. In such + cases, it is reasonable to use the "Sender" address. + + This recommendation is intended only for automated use of + originator-fields and is not intended to suggest that replies + may not also be sent to other recipients of messages. It is + up to the respective mail-handling programs to decide what + additional facilities will be provided. + + Examples are provided in Appendix A. + + 4.5. RECEIVER FIELDS + + 4.5.1. TO / RESENT-TO + + This field contains the identity of the primary recipients of + the message. + + 4.5.2. CC / RESENT-CC + + This field contains the identity of the secondary (informa- + tional) recipients of the message. + + 4.5.3. BCC / RESENT-BCC + + This field contains the identity of additional recipients of + the message. The contents of this field are not included in + copies of the message sent to the primary and secondary reci- + pients. Some systems may choose to include the text of the + "Bcc" field only in the author(s)'s copy, while others may + also include it in the text sent to all those indicated in the + "Bcc" list. + + 4.6. REFERENCE FIELDS + + 4.6.1. MESSAGE-ID / RESENT-MESSAGE-ID + + This field contains a unique identifier (the local-part + address unit) which refers to THIS version of THIS message. + The uniqueness of the message identifier is guaranteed by the + host which generates it. This identifier is intended to be + machine readable and not necessarily meaningful to humans. A + message identifier pertains to exactly one instantiation of a + particular message; subsequent revisions to the message should + + + August 13, 1982 - 23 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + each receive new message identifiers. + + 4.6.2. IN-REPLY-TO + + The contents of this field identify previous correspon- + dence which this message answers. Note that if message iden- + tifiers are used in this field, they must use the msg-id + specification format. + + 4.6.3. REFERENCES + + The contents of this field identify other correspondence + which this message references. Note that if message identif- + iers are used, they must use the msg-id specification format. + + 4.6.4. KEYWORDS + + This field contains keywords or phrases, separated by + commas. + + 4.7. OTHER FIELDS + + 4.7.1. SUBJECT + + This is intended to provide a summary, or indicate the + nature, of the message. + + 4.7.2. COMMENTS + + Permits adding text comments onto the message without + disturbing the contents of the message's body. + + 4.7.3. ENCRYPTED + + Sometimes, data encryption is used to increase the + privacy of message contents. If the body of a message has + been encrypted, to keep its contents private, the "Encrypted" + field can be used to note the fact and to indicate the nature + of the encryption. The first parameter indicates the + software used to encrypt the body, and the second, optional + is intended to aid the recipient in selecting the + proper decryption key. This code word may be viewed as an + index to a table of keys held by the recipient. + + Note: Unfortunately, headers must contain envelope, as well + as contents, information. Consequently, it is neces- + sary that they remain unencrypted, so that mail tran- + sport services may access them. Since names, + addresses, and "Subject" field contents may contain + + + August 13, 1982 - 24 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + sensitive information, this requirement limits total + message privacy. + + Names of encryption software are registered with the Net- + work Information Center, SRI International, Menlo Park, Cali- + fornia. + + 4.7.4. EXTENSION-FIELD + + A limited number of common fields have been defined in + this document. As network mail requirements dictate, addi- + tional fields may be standardized. To provide user-defined + fields with a measure of safety, in name selection, such + extension-fields will never have names that begin with the + string "X-". + + Names of Extension-fields are registered with the Network + Information Center, SRI International, Menlo Park, California. + + 4.7.5. USER-DEFINED-FIELD + + Individual users of network mail are free to define and + use additional header fields. Such fields must have names + which are not already used in the current specification or in + any definitions of extension-fields, and the overall syntax of + these user-defined-fields must conform to this specification's + rules for delimiting and folding fields. Due to the + extension-field publishing process, the name of a user- + defined-field may be pre-empted + + Note: The prefatory string "X-" will never be used in the + names of Extension-fields. This provides user-defined + fields with a protected set of names. + + + + + + + + + + + + + + + + + + + August 13, 1982 - 25 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 5. DATE AND TIME SPECIFICATION + + 5.1. SYNTAX + + date-time = [ day "," ] date time ; dd mm yy + ; hh:mm:ss zzz + + day = "Mon" / "Tue" / "Wed" / "Thu" + / "Fri" / "Sat" / "Sun" + + date = 1*2DIGIT month 2DIGIT ; day month year + ; e.g. 20 Jun 82 + + month = "Jan" / "Feb" / "Mar" / "Apr" + / "May" / "Jun" / "Jul" / "Aug" + / "Sep" / "Oct" / "Nov" / "Dec" + + time = hour zone ; ANSI and Military + + hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] + ; 00:00:00 - 23:59:59 + + zone = "UT" / "GMT" ; Universal Time + ; North American : UT + / "EST" / "EDT" ; Eastern: - 5/ - 4 + / "CST" / "CDT" ; Central: - 6/ - 5 + / "MST" / "MDT" ; Mountain: - 7/ - 6 + / "PST" / "PDT" ; Pacific: - 8/ - 7 + / 1ALPHA ; Military: Z = UT; + ; A:-1; (J not used) + ; M:-12; N:+1; Y:+12 + / ( ("+" / "-") 4DIGIT ) ; Local differential + ; hours+min. (HHMM) + + 5.2. SEMANTICS + + If included, day-of-week must be the day implied by the date + specification. + + Time zone may be indicated in several ways. "UT" is Univer- + sal Time (formerly called "Greenwich Mean Time"); "GMT" is per- + mitted as a reference to Universal Time. The military standard + uses a single character for each zone. "Z" is Universal Time. + "A" indicates one hour earlier, and "M" indicates 12 hours ear- + lier; "N" is one hour later, and "Y" is 12 hours later. The + letter "J" is not used. The other remaining two forms are taken + from ANSI standard X3.51-1975. One allows explicit indication of + the amount of offset from UT; the other uses common 3-character + strings for indicating time zones in North America. + + + August 13, 1982 - 26 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 6. ADDRESS SPECIFICATION + + 6.1. SYNTAX + + address = mailbox ; one addressee + / group ; named list + + group = phrase ":" [#mailbox] ";" + + mailbox = addr-spec ; simple address + / phrase route-addr ; name & addr-spec + + route-addr = "<" [route] addr-spec ">" + + route = 1#("@" domain) ":" ; path-relative + + addr-spec = local-part "@" domain ; global address + + local-part = word *("." word) ; uninterpreted + ; case-preserved + + domain = sub-domain *("." sub-domain) + + sub-domain = domain-ref / domain-literal + + domain-ref = atom ; symbolic reference + + 6.2. SEMANTICS + + A mailbox receives mail. It is a conceptual entity which + does not necessarily pertain to file storage. For example, some + sites may choose to print mail on their line printer and deliver + the output to the addressee's desk. + + A mailbox specification comprises a person, system or pro- + cess name reference, a domain-dependent string, and a name-domain + reference. The name reference is optional and is usually used to + indicate the human name of a recipient. The name-domain refer- + ence specifies a sequence of sub-domains. The domain-dependent + string is uninterpreted, except by the final sub-domain; the rest + of the mail service merely transmits it as a literal string. + + 6.2.1. DOMAINS + + A name-domain is a set of registered (mail) names. A name- + domain specification resolves to a subordinate name-domain + specification or to a terminal domain-dependent string. + Hence, domain specification is extensible, permitting any + number of registration levels. + + + August 13, 1982 - 27 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + Name-domains model a global, logical, hierarchical addressing + scheme. The model is logical, in that an address specifica- + tion is related to name registration and is not necessarily + tied to transmission path. The model's hierarchy is a + directed graph, called an in-tree, such that there is a single + path from the root of the tree to any node in the hierarchy. + If more than one path actually exists, they are considered to + be different addresses. + + The root node is common to all addresses; consequently, it is + not referenced. Its children constitute "top-level" name- + domains. Usually, a service has access to its own full domain + specification and to the names of all top-level name-domains. + + The "top" of the domain addressing hierarchy -- a child of the + root -- is indicated by the right-most field, in a domain + specification. Its child is specified to the left, its child + to the left, and so on. + + Some groups provide formal registration services; these con- + stitute name-domains that are independent logically of + specific machines. In addition, networks and machines impli- + citly compose name-domains, since their membership usually is + registered in name tables. + + In the case of formal registration, an organization implements + a (distributed) data base which provides an address-to-route + mapping service for addresses of the form: + + person@registry.organization + + Note that "organization" is a logical entity, separate from + any particular communication network. + + A mechanism for accessing "organization" is universally avail- + able. That mechanism, in turn, seeks an instantiation of the + registry; its location is not indicated in the address specif- + ication. It is assumed that the system which operates under + the name "organization" knows how to find a subordinate regis- + try. The registry will then use the "person" string to deter- + mine where to send the mail specification. + + The latter, network-oriented case permits simple, direct, + attachment-related address specification, such as: + + user@host.network + + Once the network is accessed, it is expected that a message + will go directly to the host and that the host will resolve + + + August 13, 1982 - 28 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + the user name, placing the message in the user's mailbox. + + 6.2.2. ABBREVIATED DOMAIN SPECIFICATION + + Since any number of levels is possible within the domain + hierarchy, specification of a fully qualified address can + become inconvenient. This standard permits abbreviated domain + specification, in a special case: + + For the address of the sender, call the left-most + sub-domain Level N. In a header address, if all of + the sub-domains above (i.e., to the right of) Level N + are the same as those of the sender, then they do not + have to appear in the specification. Otherwise, the + address must be fully qualified. + + This feature is subject to approval by local sub- + domains. Individual sub-domains may require their + member systems, which originate mail, to provide full + domain specification only. When permitted, abbrevia- + tions may be present only while the message stays + within the sub-domain of the sender. + + Use of this mechanism requires the sender's sub-domain + to reserve the names of all top-level domains, so that + full specifications can be distinguished from abbrevi- + ated specifications. + + For example, if a sender's address is: + + sender@registry-A.registry-1.organization-X + + and one recipient's address is: + + recipient@registry-B.registry-1.organization-X + + and another's is: + + recipient@registry-C.registry-2.organization-X + + then ".registry-1.organization-X" need not be specified in the + the message, but "registry-C.registry-2" DOES have to be + specified. That is, the first two addresses may be abbrevi- + ated, but the third address must be fully specified. + + When a message crosses a domain boundary, all addresses must + be specified in the full format, ending with the top-level + name-domain in the right-most field. It is the responsibility + of mail forwarding services to ensure that addresses conform + + + August 13, 1982 - 29 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + with this requirement. In the case of abbreviated addresses, + the relaying service must make the necessary expansions. It + should be noted that it often is difficult for such a service + to locate all occurrences of address abbreviations. For exam- + ple, it will not be possible to find such abbreviations within + the body of the message. The "Return-Path" field can aid + recipients in recovering from these errors. + + Note: When passing any portion of an addr-spec onto a process + which does not interpret data according to this stan- + dard (e.g., mail protocol servers). There must be NO + LWSP-chars preceding or following the at-sign or any + delimiting period ("."), such as shown in the above + examples, and only ONE SPACE between contiguous + s. + + 6.2.3. DOMAIN TERMS + + A domain-ref must be THE official name of a registry, network, + or host. It is a symbolic reference, within a name sub- + domain. At times, it is necessary to bypass standard mechan- + isms for resolving such references, using more primitive + information, such as a network host address rather than its + associated host name. + + To permit such references, this standard provides the domain- + literal construct. Its contents must conform with the needs + of the sub-domain in which it is interpreted. + + Domain-literals which refer to domains within the ARPA Inter- + net specify 32-bit Internet addresses, in four 8-bit fields + noted in decimal, as described in Request for Comments #820, + "Assigned Numbers." For example: + + [10.0.3.19] + + Note: THE USE OF DOMAIN-LITERALS IS STRONGLY DISCOURAGED. It + is permitted only as a means of bypassing temporary + system limitations, such as name tables which are not + complete. + + The names of "top-level" domains, and the names of domains + under in the ARPA Internet, are registered with the Network + Information Center, SRI International, Menlo Park, California. + + 6.2.4. DOMAIN-DEPENDENT LOCAL STRING + + The local-part of an addr-spec in a mailbox specification + (i.e., the host's name for the mailbox) is understood to be + + + August 13, 1982 - 30 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + whatever the receiving mail protocol server allows. For exam- + ple, some systems do not understand mailbox references of the + form "P. D. Q. Bach", but others do. + + This specification treats periods (".") as lexical separators. + Hence, their presence in local-parts which are not quoted- + strings, is detected. However, such occurrences carry NO + semantics. That is, if a local-part has periods within it, an + address parser will divide the local-part into several tokens, + but the sequence of tokens will be treated as one uninter- + preted unit. The sequence will be re-assembled, when the + address is passed outside of the system such as to a mail pro- + tocol service. + + For example, the address: + + First.Last@Registry.Org + + is legal and does not require the local-part to be surrounded + with quotation-marks. (However, "First Last" DOES require + quoting.) The local-part of the address, when passed outside + of the mail system, within the Registry.Org domain, is + "First.Last", again without quotation marks. + + 6.2.5. BALANCING LOCAL-PART AND DOMAIN + + In some cases, the boundary between local-part and domain can + be flexible. The local-part may be a simple string, which is + used for the final determination of the recipient's mailbox. + All other levels of reference are, therefore, part of the + domain. + + For some systems, in the case of abbreviated reference to the + local and subordinate sub-domains, it may be possible to + specify only one reference within the domain part and place + the other, subordinate name-domain references within the + local-part. This would appear as: + + mailbox.sub1.sub2@this-domain + + Such a specification would be acceptable to address parsers + which conform to RFC #733, but do not support this newer + Internet standard. While contrary to the intent of this stan- + dard, the form is legal. + + Also, some sub-domains have a specification syntax which does + not conform to this standard. For example: + + sub-net.mailbox@sub-domain.domain + + + August 13, 1982 - 31 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + uses a different parsing sequence for local-part than for + domain. + + Note: As a rule, the domain specification should contain + fields which are encoded according to the syntax of + this standard and which contain generally-standardized + information. The local-part specification should con- + tain only that portion of the address which deviates + from the form or intention of the domain field. + + 6.2.6. MULTIPLE MAILBOXES + + An individual may have several mailboxes and wish to receive + mail at whatever mailbox is convenient for the sender to + access. This standard does not provide a means of specifying + "any member of" a list of mailboxes. + + A set of individuals may wish to receive mail as a single unit + (i.e., a distribution list). The construct permits + specification of such a list. Recipient mailboxes are speci- + fied within the bracketed part (":" - ";"). A copy of the + transmitted message is to be sent to each mailbox listed. + This standard does not permit recursive specification of + groups within groups. + + While a list must be named, it is not required that the con- + tents of the list be included. In this case, the
    + serves only as an indication of group distribution and would + appear in the form: + + name:; + + Some mail services may provide a group-list distribution + facility, accepting a single mailbox reference, expanding it + to the full distribution list, and relaying the mail to the + list's members. This standard provides no additional syntax + for indicating such a service. Using the address + alternative, while listing one mailbox in it, can mean either + that the mailbox reference will be expanded to a list or that + there is a group with one member. + + 6.2.7. EXPLICIT PATH SPECIFICATION + + At times, a message originator may wish to indicate the + transmission path that a message should follow. This is + called source routing. The normal addressing scheme, used in + an addr-spec, is carefully separated from such information; + the portion of a route-addr is provided for such occa- + sions. It specifies the sequence of hosts and/or transmission + + + August 13, 1982 - 32 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + services that are to be traversed. Both domain-refs and + domain-literals may be used. + + Note: The use of source routing is discouraged. Unless the + sender has special need of path restriction, the choice + of transmission route should be left to the mail tran- + sport service. + + 6.3. RESERVED ADDRESS + + It often is necessary to send mail to a site, without know- + ing any of its valid addresses. For example, there may be mail + system dysfunctions, or a user may wish to find out a person's + correct address, at that site. + + This standard specifies a single, reserved mailbox address + (local-part) which is to be valid at each site. Mail sent to + that address is to be routed to a person responsible for the + site's mail system or to a person with responsibility for general + site operation. The name of the reserved local-part address is: + + Postmaster + + so that "Postmaster@domain" is required to be valid. + + Note: This reserved local-part must be matched without sensi- + tivity to alphabetic case, so that "POSTMASTER", "postmas- + ter", and even "poStmASteR" is to be accepted. + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 33 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + 7. BIBLIOGRAPHY + + + ANSI. "USA Standard Code for Information Interchange," X3.4. + American National Standards Institute: New York (1968). Also + in: Feinler, E. and J. Postel, eds., "ARPANET Protocol Hand- + book", NIC 7104. + + ANSI. "Representations of Universal Time, Local Time Differen- + tials, and United States Time Zone References for Information + Interchange," X3.51-1975. American National Standards Insti- + tute: New York (1975). + + Bemer, R.W., "Time and the Computer." In: Interface Age (Feb. + 1979). + + Bennett, C.J. "JNT Mail Protocol". Joint Network Team, Ruther- + ford and Appleton Laboratory: Didcot, England. + + Bhushan, A.K., Pogran, K.T., Tomlinson, R.S., and White, J.E. + "Standardizing Network Mail Headers," ARPANET Request for + Comments No. 561, Network Information Center No. 18516; SRI + International: Menlo Park (September 1973). + + Birrell, A.D., Levin, R., Needham, R.M., and Schroeder, M.D. + "Grapevine: An Exercise in Distributed Computing," Communica- + tions of the ACM 25, 4 (April 1982), 260-274. + + Crocker, D.H., Vittal, J.J., Pogran, K.T., Henderson, D.A. + "Standard for the Format of ARPA Network Text Message," + ARPANET Request for Comments No. 733, Network Information + Center No. 41952. SRI International: Menlo Park (November + 1977). + + Feinler, E.J. and Postel, J.B. ARPANET Protocol Handbook, Net- + work Information Center No. 7104 (NTIS AD A003890). SRI + International: Menlo Park (April 1976). + + Harary, F. "Graph Theory". Addison-Wesley: Reading, Mass. + (1969). + + Levin, R. and Schroeder, M. "Transport of Electronic Messages + through a Network," TeleInformatics 79, pp. 29-33. North + Holland (1979). Also as Xerox Palo Alto Research Center + Technical Report CSL-79-4. + + Myer, T.H. and Henderson, D.A. "Message Transmission Protocol," + ARPANET Request for Comments, No. 680, Network Information + Center No. 32116. SRI International: Menlo Park (1975). + + + August 13, 1982 - 34 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + NBS. "Specification of Message Format for Computer Based Message + Systems, Recommended Federal Information Processing Standard." + National Bureau of Standards: Gaithersburg, Maryland + (October 1981). + + NIC. Internet Protocol Transition Workbook. Network Information + Center, SRI-International, Menlo Park, California (March + 1982). + + Oppen, D.C. and Dalal, Y.K. "The Clearinghouse: A Decentralized + Agent for Locating Named Objects in a Distributed Environ- + ment," OPD-T8103. Xerox Office Products Division: Palo Alto, + CA. (October 1981). + + Postel, J.B. "Assigned Numbers," ARPANET Request for Comments, + No. 820. SRI International: Menlo Park (August 1982). + + Postel, J.B. "Simple Mail Transfer Protocol," ARPANET Request + for Comments, No. 821. SRI International: Menlo Park (August + 1982). + + Shoch, J.F. "Internetwork naming, addressing and routing," in + Proc. 17th IEEE Computer Society International Conference, pp. + 72-79, Sept. 1978, IEEE Cat. No. 78 CH 1388-8C. + + Su, Z. and Postel, J. "The Domain Naming Convention for Internet + User Applications," ARPANET Request for Comments, No. 819. + SRI International: Menlo Park (August 1982). + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 35 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + APPENDIX + + + A. EXAMPLES + + A.1. ADDRESSES + + A.1.1. Alfred Neuman + + A.1.2. Neuman@BBN-TENEXA + + These two "Alfred Neuman" examples have identical seman- + tics, as far as the operation of the local host's mail sending + (distribution) program (also sometimes called its "mailer") + and the remote host's mail protocol server are concerned. In + the first example, the "Alfred Neuman" is ignored by the + mailer, as "Neuman@BBN-TENEXA" completely specifies the reci- + pient. The second example contains no superfluous informa- + tion, and, again, "Neuman@BBN-TENEXA" is the intended reci- + pient. + + Note: When the message crosses name-domain boundaries, then + these specifications must be changed, so as to indicate + the remainder of the hierarchy, starting with the top + level. + + A.1.3. "George, Ted" + + This form might be used to indicate that a single mailbox + is shared by several users. The quoted string is ignored by + the originating host's mailer, because "Shared@Group.Arpanet" + completely specifies the destination mailbox. + + A.1.4. Wilt . (the Stilt) Chamberlain@NBA.US + + The "(the Stilt)" is a comment, which is NOT included in + the destination mailbox address handed to the originating + system's mailer. The local-part of the address is the string + "Wilt.Chamberlain", with NO space between the first and second + words. + + A.1.5. Address Lists + + Gourmets: Pompous Person , + Childs@WGBH.Boston, Galloping Gourmet@ + ANT.Down-Under (Australian National Television), + Cheapie@Discount-Liquors;, + Cruisers: Port@Portugal, Jones@SEA;, + Another@Somewhere.SomeOrg + + + August 13, 1982 - 36 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + This group list example points out the use of comments and the + mixing of addresses and groups. + + A.2. ORIGINATOR ITEMS + + A.2.1. Author-sent + + George Jones logs into his host as "Jones". He sends + mail himself. + + From: Jones@Group.Org + + or + + From: George Jones + + A.2.2. Secretary-sent + + George Jones logs in as Jones on his host. His secre- + tary, who logs in as Secy sends mail for him. Replies to the + mail should go to George. + + From: George Jones + Sender: Secy@Other-Group + + A.2.3. Secretary-sent, for user of shared directory + + George Jones' secretary sends mail for George. Replies + should go to George. + + From: George Jones + Sender: Secy@Other-Group + + Note that there need not be a space between "Jones" and the + "<", but adding a space enhances readability (as is the case + in other examples. + + A.2.4. Committee activity, with one author + + George is a member of a committee. He wishes to have any + replies to his message go to all committee members. + + From: George Jones + Sender: Jones@Host + Reply-To: The Committee: Jones@Host.Net, + Smith@Other.Org, + Doe@Somewhere-Else; + + Note that if George had not included himself in the + + + August 13, 1982 - 37 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + enumeration of The Committee, he would not have gotten an + implicit reply; the presence of the "Reply-to" field SUPER- + SEDES the sending of a reply to the person named in the "From" + field. + + A.2.5. Secretary acting as full agent of author + + George Jones asks his secretary (Secy@Host) to send a + message for him in his capacity as Group. He wants his secre- + tary to handle all replies. + + From: George Jones + Sender: Secy@Host + Reply-To: Secy@Host + + A.2.6. Agent for user without online mailbox + + A friend of George's, Sarah, is visiting. George's + secretary sends some mail to a friend of Sarah in computer- + land. Replies should go to George, whose mailbox is Jones at + Registry. + + From: Sarah Friendly + Sender: Secy-Name + Reply-To: Jones@Registry. + + A.2.7. Agent for member of a committee + + George's secretary sends out a message which was authored + jointly by all the members of a committee. Note that the name + of the committee cannot be specified, since names are + not permitted in the From field. + + From: Jones@Host, + Smith@Other-Host, + Doe@Somewhere-Else + Sender: Secy@SHost + + + + + + + + + + + + + + + August 13, 1982 - 38 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + A.3. COMPLETE HEADERS + + A.3.1. Minimum required + + Date: 26 Aug 76 1429 EDT Date: 26 Aug 76 1429 EDT + From: Jones@Registry.Org or From: Jones@Registry.Org + Bcc: To: Smith@Registry.Org + + Note that the "Bcc" field may be empty, while the "To" field + is required to have at least one address. + + A.3.2. Using some of the additional fields + + Date: 26 Aug 76 1430 EDT + From: George Jones + Sender: Secy@SHOST + To: "Al Neuman"@Mad-Host, + Sam.Irving@Other-Host + Message-ID: + + A.3.3. About as complex as you're going to get + + Date : 27 Aug 76 0932 PDT + From : Ken Davis + Subject : Re: The Syntax in the RFC + Sender : KSecy@Other-Host + Reply-To : Sam.Irving@Reg.Organization + To : George Jones , + Al.Neuman@MAD.Publisher + cc : Important folk: + Tom Softwood , + "Sam Irving"@Other-Host;, + Standard Distribution: + /main/davis/people/standard@Other-Host, + "standard.dist.3"@Tops-20-Host>; + Comment : Sam is away on business. He asked me to handle + his mail for him. He'll be able to provide a + more accurate explanation when he returns + next week. + In-Reply-To: , George's message + X-Special-action: This is a sample of user-defined field- + names. There could also be a field-name + "Special-action", but its name might later be + preempted + Message-ID: <4231.629.XYzi-What@Other-Host> + + + + + + + August 13, 1982 - 39 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + B. SIMPLE FIELD PARSING + + Some mail-reading software systems may wish to perform only + minimal processing, ignoring the internal syntax of structured + field-bodies and treating them the same as unstructured-field- + bodies. Such software will need only to distinguish: + + o Header fields from the message body, + + o Beginnings of fields from lines which continue fields, + + o Field-names from field-contents. + + The abbreviated set of syntactic rules which follows will + suffice for this purpose. It describes a limited view of mes- + sages and is a subset of the syntactic rules provided in the main + part of this specification. One small exception is that the con- + tents of field-bodies consist only of text: + + B.1. SYNTAX + + + message = *field *(CRLF *text) + + field = field-name ":" [field-body] CRLF + + field-name = 1* + + field-body = *text [CRLF LWSP-char field-body] + + + B.2. SEMANTICS + + Headers occur before the message body and are terminated by + a null line (i.e., two contiguous CRLFs). + + A line which continues a header field begins with a SPACE or + HTAB character, while a line beginning a field starts with a + printable character which is not a colon. + + A field-name consists of one or more printable characters + (excluding colon, space, and control-characters). A field-name + MUST be contained on one line. Upper and lower case are not dis- + tinguished when comparing field-names. + + + + + + + + August 13, 1982 - 40 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + C. DIFFERENCES FROM RFC #733 + + The following summarizes the differences between this stan- + dard and the one specified in Arpanet Request for Comments #733, + "Standard for the Format of ARPA Network Text Messages". The + differences are listed in the order of their occurrence in the + current specification. + + C.1. FIELD DEFINITIONS + + C.1.1. FIELD NAMES + + These now must be a sequence of printable characters. They + may not contain any LWSP-chars. + + C.2. LEXICAL TOKENS + + C.2.1. SPECIALS + + The characters period ("."), left-square bracket ("["), and + right-square bracket ("]") have been added. For presentation + purposes, and when passing a specification to a system that + does not conform to this standard, periods are to be contigu- + ous with their surrounding lexical tokens. No linear-white- + space is permitted between them. The presence of one LWSP- + char between other tokens is still directed. + + C.2.2. ATOM + + Atoms may not contain SPACE. + + C.2.3. SPECIAL TEXT + + ctext and qtext have had backslash ("\") added to the list of + prohibited characters. + + C.2.4. DOMAINS + + The lexical tokens and have been + added. + + C.3. MESSAGE SPECIFICATION + + C.3.1. TRACE + + The "Return-path:" and "Received:" fields have been specified. + + + + + + August 13, 1982 - 41 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + C.3.2. FROM + + The "From" field must contain machine-usable addresses (addr- + spec). Multiple addresses may be specified, but named-lists + (groups) may not. + + C.3.3. RESENT + + The meta-construct of prefacing field names with the string + "Resent-" has been added, to indicate that a message has been + forwarded by an intermediate recipient. + + C.3.4. DESTINATION + + A message must contain at least one destination address field. + "To" and "CC" are required to contain at least one address. + + C.3.5. IN-REPLY-TO + + The field-body is no longer a comma-separated list, although a + sequence is still permitted. + + C.3.6. REFERENCE + + The field-body is no longer a comma-separated list, although a + sequence is still permitted. + + C.3.7. ENCRYPTED + + A field has been specified that permits senders to indicate + that the body of a message has been encrypted. + + C.3.8. EXTENSION-FIELD + + Extension fields are prohibited from beginning with the char- + acters "X-". + + C.4. DATE AND TIME SPECIFICATION + + C.4.1. SIMPLIFICATION + + Fewer optional forms are permitted and the list of three- + letter time zones has been shortened. + + C.5. ADDRESS SPECIFICATION + + + + + + + August 13, 1982 - 42 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + C.5.1. ADDRESS + + The use of quoted-string, and the ":"-atom-":" construct, have + been removed. An address now is either a single mailbox + reference or is a named list of addresses. The latter indi- + cates a group distribution. + + C.5.2. GROUPS + + Group lists are now required to to have a name. Group lists + may not be nested. + + C.5.3. MAILBOX + + A mailbox specification may indicate a person's name, as + before. Such a named list no longer may specify multiple + mailboxes and may not be nested. + + C.5.4. ROUTE ADDRESSING + + Addresses now are taken to be absolute, global specifications, + independent of transmission paths. The construct has + been provided, to permit explicit specification of transmis- + sion path. RFC #733's use of multiple at-signs ("@") was + intended as a general syntax for indicating routing and/or + hierarchical addressing. The current standard separates these + specifications and only one at-sign is permitted. + + C.5.5. AT-SIGN + + The string " at " no longer is used as an address delimiter. + Only at-sign ("@") serves the function. + + C.5.6. DOMAINS + + Hierarchical, logical name-domains have been added. + + C.6. RESERVED ADDRESS + + The local-part "Postmaster" has been reserved, so that users can + be guaranteed at least one valid address at a site. + + + + + + + + + + + August 13, 1982 - 43 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + D. ALPHABETICAL LISTING OF SYNTAX RULES + + address = mailbox ; one addressee + / group ; named list + addr-spec = local-part "@" domain ; global address + ALPHA = + ; (101-132, 65.- 90.) + ; (141-172, 97.-122.) + atom = 1* + authentic = "From" ":" mailbox ; Single author + / ( "Sender" ":" mailbox ; Actual submittor + "From" ":" 1#mailbox) ; Multiple authors + ; or not sender + CHAR = ; ( 0-177, 0.-127.) + comment = "(" *(ctext / quoted-pair / comment) ")" + CR = ; ( 15, 13.) + CRLF = CR LF + ctext = may be folded + ")", "\" & CR, & including + linear-white-space> + CTL = ; ( 177, 127.) + date = 1*2DIGIT month 2DIGIT ; day month year + ; e.g. 20 Jun 82 + dates = orig-date ; Original + [ resent-date ] ; Forwarded + date-time = [ day "," ] date time ; dd mm yy + ; hh:mm:ss zzz + day = "Mon" / "Tue" / "Wed" / "Thu" + / "Fri" / "Sat" / "Sun" + delimiters = specials / linear-white-space / comment + destination = "To" ":" 1#address ; Primary + / "Resent-To" ":" 1#address + / "cc" ":" 1#address ; Secondary + / "Resent-cc" ":" 1#address + / "bcc" ":" #address ; Blind carbon + / "Resent-bcc" ":" #address + DIGIT = ; ( 60- 71, 48.- 57.) + domain = sub-domain *("." sub-domain) + domain-literal = "[" *(dtext / quoted-pair) "]" + domain-ref = atom ; symbolic reference + dtext = may be folded + "]", "\" & CR, & including + linear-white-space> + extension-field = + + + + August 13, 1982 - 44 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + field = field-name ":" [ field-body ] CRLF + fields = dates ; Creation time, + source ; author id & one + 1*destination ; address required + *optional-field ; others optional + field-body = field-body-contents + [CRLF LWSP-char field-body] + field-body-contents = + + field-name = 1* + group = phrase ":" [#mailbox] ";" + hour = 2DIGIT ":" 2DIGIT [":" 2DIGIT] + ; 00:00:00 - 23:59:59 + HTAB = ; ( 11, 9.) + LF = ; ( 12, 10.) + linear-white-space = 1*([CRLF] LWSP-char) ; semantics = SPACE + ; CRLF => folding + local-part = word *("." word) ; uninterpreted + ; case-preserved + LWSP-char = SPACE / HTAB ; semantics = SPACE + mailbox = addr-spec ; simple address + / phrase route-addr ; name & addr-spec + message = fields *( CRLF *text ) ; Everything after + ; first null line + ; is message body + month = "Jan" / "Feb" / "Mar" / "Apr" + / "May" / "Jun" / "Jul" / "Aug" + / "Sep" / "Oct" / "Nov" / "Dec" + msg-id = "<" addr-spec ">" ; Unique message id + optional-field = + / "Message-ID" ":" msg-id + / "Resent-Message-ID" ":" msg-id + / "In-Reply-To" ":" *(phrase / msg-id) + / "References" ":" *(phrase / msg-id) + / "Keywords" ":" #phrase + / "Subject" ":" *text + / "Comments" ":" *text + / "Encrypted" ":" 1#2word + / extension-field ; To be defined + / user-defined-field ; May be pre-empted + orig-date = "Date" ":" date-time + originator = authentic ; authenticated addr + [ "Reply-To" ":" 1#address] ) + phrase = 1*word ; Sequence of words + + + + + August 13, 1982 - 45 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + qtext = , ; => may be folded + "\" & CR, and including + linear-white-space> + quoted-pair = "\" CHAR ; may quote any char + quoted-string = <"> *(qtext/quoted-pair) <">; Regular qtext or + ; quoted chars. + received = "Received" ":" ; one per relay + ["from" domain] ; sending host + ["by" domain] ; receiving host + ["via" atom] ; physical path + *("with" atom) ; link/mail protocol + ["id" msg-id] ; receiver msg id + ["for" addr-spec] ; initial form + ";" date-time ; time received + + resent = resent-authentic + [ "Resent-Reply-To" ":" 1#address] ) + resent-authentic = + = "Resent-From" ":" mailbox + / ( "Resent-Sender" ":" mailbox + "Resent-From" ":" 1#mailbox ) + resent-date = "Resent-Date" ":" date-time + return = "Return-path" ":" route-addr ; return address + route = 1#("@" domain) ":" ; path-relative + route-addr = "<" [route] addr-spec ">" + source = [ trace ] ; net traversals + originator ; original mail + [ resent ] ; forwarded + SPACE = ; ( 40, 32.) + specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted- + / "," / ";" / ":" / "\" / <"> ; string, to use + / "." / "[" / "]" ; within a word. + sub-domain = domain-ref / domain-literal + text = atoms, specials, + CR & bare LF, but NOT ; comments and + including CRLF> ; quoted-strings are + ; NOT recognized. + time = hour zone ; ANSI and Military + trace = return ; path to sender + 1*received ; receipt tags + user-defined-field = + + word = atom / quoted-string + + + + + August 13, 1982 - 46 - RFC #822 + + + + Standard for ARPA Internet Text Messages + + + zone = "UT" / "GMT" ; Universal Time + ; North American : UT + / "EST" / "EDT" ; Eastern: - 5/ - 4 + / "CST" / "CDT" ; Central: - 6/ - 5 + / "MST" / "MDT" ; Mountain: - 7/ - 6 + / "PST" / "PDT" ; Pacific: - 8/ - 7 + / 1ALPHA ; Military: Z = UT; + <"> = ; ( 42, 34.) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + August 13, 1982 - 47 - RFC #822 + diff --git a/lib/qCal/lib/autoload.php b/lib/qCal/lib/autoload.php new file mode 100644 index 0000000..ee8f124 --- /dev/null +++ b/lib/qCal/lib/autoload.php @@ -0,0 +1,15 @@ + \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Component.php b/lib/qCal/lib/qCal/Component.php new file mode 100644 index 0000000..7e28ffd --- /dev/null +++ b/lib/qCal/lib/qCal/Component.php @@ -0,0 +1,523 @@ + '-// Some Property Id//', + * 'someotherproperty' => null, + * qCal_Property_Version(2.0), + * ), array( + * qCal_Component_Daylight(), + * )); + */ + public function __construct($properties = array(), $components = array()) { + + foreach ($components as $component) { + // if value is an array, then each value inside of it will be a component + if ($component instanceof qCal_Component) { + $this->attach($component); + } + else throw new qCal_Exception_InvalidComponent('The second argument is optional, but if provided, must be an array of components'); + } + foreach ($properties as $name => $value) { + // if value is an array, then each value inside of it will be a property + if (is_array($value)) { + foreach ($value as $val) { + if ($val instanceof qCal_Property) { + $this->addProperty($val); + } else { + $this->addProperty($name, $val); + } + } + } else { + if ($value instanceof qCal_Property) { + $this->addProperty($value); + } else { + $this->addProperty($name, $value); + } + } + } + // I think it would make more sense to do validation at render time. That way you don't have + // to have all of the required components and properties when you instantiate. Also, that way + // components don't need to be aware of eachother until render time (or until validate() is called + // explicitly). @todo + // $this->validate(); + + } + /** + * @todo (lazy load functionality) Check that this is a valid component. This method is sort of lazy-loaded. It only gets called + * if the user has requested data that requires validation and the component has not been validated already. + * @todo Shouldn't this loop over children and validate them too? Maybe optionally? + */ + public function validate() { + + // if we're missing any required properties and they have no default, throw an exception + $properties = array(); + foreach ($this->getProperties() as $property) { + if (is_array($property)) { + foreach ($property as $prop) { + $properties[] = $prop->getName(); + } + } else { + $properties[] = $property->getName(); + } + } + $missing = array_diff($this->requiredProperties, array_unique($properties)); + foreach ($missing as $propertyname) { + // the property factory will throw an exception if it's passed a null value for a property with no default + try { + $property = qCal_Property::factory($propertyname, null); + $this->addProperty($property); + } catch (qCal_Exception_InvalidPropertyValue $e) { + // if that's the case, catch the exception and throw a missing property exception + throw new qCal_Exception_MissingProperty($this->getName() . " component requires " . $propertyname . " property"); + } + } + // this allows per-component validation :) + $this->doValidation(); + + } + /** + * Returns the component name + * @return string + */ + public function getName() { + + return $this->name; + + } + /** + * Returns true if this component can be attached to $component + * I'm sure there's a better way to do this, but this works for now + */ + public function canAttachTo(qCal_Component $component) { + + if (in_array($component->getName(), $this->allowedComponents)) return true; + + } + /** + * Attach a component to this component (alarm inside event for example) + * @todo There may be an issue with the way this is done. When parsing a file, if a component + * or property with a tzid comes before its corresponding vtimezone component, an exception + * will be thrown. I'm don't think the RFC specifies that requirement (that timezone components + * must come before their corresponding tzids) + * @todo Sub-components such as Vevent need to be able to access the main vcalendar object + * for several reasons. + * - If a vtodo has a tzid, it needs to be able to determine that the corresponding + * vtimezone component is available. + * - If components need to relate to eachother, they can only find eachother through + * the main vcalendar object. + * - Freebusy time can only be determined by polling all components in the main vcalendar + * object. + * - More to come probably + */ + public function attach(qCal_Component $component) { + + if (!$component->canAttachTo($this)) { + throw new qCal_Exception_InvalidComponent($component->getName() . ' cannot be attached to ' . $this->getName()); + } + $component->setParent($this); + // make sure if a timezone is requested that it is available... + $timezones = $this->getTimezones(); + $tzids = array_keys($timezones); + // we only need to check if tzid exists if we are attaching something other than a timezone... + if (!($component instanceof qCal_Component_Vtimezone)) { + foreach ($component->getProperties() as $pname => $properties) { + $pname = strtoupper($pname); // probably redundant... + foreach ($properties as $property) { + switch ($pname) { + case "TZID": + $tzid = $property->getValue(); + if (!array_key_exists($tzid, $tzids)) { + throw new qCal_Exception_MissingComponent('TZID "' . $tzid . '" not defined'); + } + break; + } + $params = $property->getParams(); + foreach ($params as $param => $val) { + $param = strtoupper($param); // probably redundant... + switch ($param) { + case "TZID": + $tzid = $val; + if (!array_key_exists($tzid, $tzids)) { + throw new qCal_Exception_MissingComponent('TZID "' . $tzid . '" not defined'); + } + break; + } + } + } + } + } + $this->children[$component->getName()][] = $component; + + } + /** + * Set the parent of this component + * @todo I'm not sure this will suffice. See the attach method for reasoning behind this. + */ + public function setParent(qCal_Component $component) { + + $this->parent = $component; + + } + /** + * Get the parent of this component (if there is one) + */ + public function getParent() { + + return $this->parent; + + } + /** + * The only thing I need this for so far is the parser, but it may come in handy for the facade as well + */ + static public function factory($name, $properties = array()) { + + if (empty($name)) return false; + // capitalize + $component = ucfirst(strtolower($name)); + $className = "qCal_Component_" . $component; + $fileName = str_replace("_", DIRECTORY_SEPARATOR, $className) . ".php"; + qCal_Loader::loadFile($fileName); + $class = new $className($properties); + return $class; + + } + + /** + * I'm not sure how this should work. Not sure if it should be setProperty, + * addProperty, both? Because properties on some components can be set multiple + * times, while some properties have multiple values. :( I am trying to consider + * a case where somebody needs to open a calendar, change a few properties on a + * component (change event time for instance). I think the way I'll handle properties + * that can be set multiple times is I'll create a method do delete properties based + * on values, parameters, etc. since they don't really have IDs. So I tihnk I'll go + * with addProperty :) + */ + public function addProperty($property, $value = null, $params = array()) { + + if (!($property instanceof qCal_Property)) { + $property = qCal_Property::factory($property, $value, $params); + } + if (!$property->of($this)) { + throw new qCal_Exception_InvalidProperty($this->getName() . " component does not allow " . $property->getName() . " property"); + } + if (!$property->allowMultiple()) { + unset($this->properties[$property->getName()]); + } + $this->properties[$property->getName()][] = $property; + + } + /** + * Returns property of this component by name + * + * @todo Since the same property can appear in a component more than once, this method + * doesn't make that much sense unless it returns all of the instances of the property + * @return array of qCal_Property + */ + public function getProperty($name) { + + $name = strtoupper($name); + if ($this->hasProperty($name)) { + return $this->properties[$name]; + } + + } + + /** + * Returns true if this component contains a property of $name + * + * @return boolean + */ + public function hasProperty($name) { + + $name = strtoupper($name); + return isset($this->properties[$name]); + + } + + /** + * Returns true if this component contains a property of $name + * + * @return boolean + */ + public function hasComponent($name) { + + $name = strtoupper($name); + return isset($this->children[$name]); + + } + /** + * Returns the child component requested + */ + public function getComponent($name) { + + $name = strtoupper($name); + if ($this->hasComponent($name)) { + return $this->children[$name]; + } + + } + + /* + public function clearProperties() { + + $this->properties = array(); + + } + + public function clearChildren() { + + $this->children = array(); + + } + */ + + public function getProperties() { + + return $this->properties; + + } + + public function getChildren() { + + return $this->children; + + } + + /** + * Gets the parent-most component in the tree. I would really like to come up + * with a cleaner way to access other components from within a component, but oh well. + */ + public function getRootComponent() { + + $parent = $this; + while (!($parent instanceof qCal_Component_Vcalendar)) { + if (!$parent->getParent()) break; + $parent = $parent->getParent(); + } + return $parent; + + } + + /** + * Renders the calendar, by default in icalendar format. If you pass + * in a renderer, it will use that instead + * + * @return mixed Depends on the renderer + * @todo Would it make more sense to pass the component to the renderer, or the renderer + * to the component? I'm not sure components should know about rendering. + */ + public function render(qCal_Renderer $renderer = null) { + + $this->validate(); + if (is_null($renderer)) $renderer = new qCal_Renderer_iCalendar(); + return $renderer->render($this); + + } + + /** + * Output the icalendar component as a string (render it) + */ + public function __toString() { + + return $this->render(); + + } + + /** + * getFreeBusyTime + * Looks through all of the data in the calendar and returns a qCal_Component_Vfreebusy object + * with free/busy time from $startdate to $enddate. The component will contain all components, but some + * may have their transparency set to "transparent". + * @todo This cannot be finished until recurring events are finished, since free/busy does not allow + * recurrence rules, each instance of a recurrence would need to be calculated out and passed into the free/busy + * component, so that the component would contain concrete instances of each event recurrence. + */ + public function getFreeBusyTime() { + + $root = $this->getRootComponent(); + foreach ($root->getChildren() as $children) { + foreach ($children as $child) { + // now get the object's free/busy time + } + } + + } + /** + * getTimeZones + */ + public function getTimezones() { + + $tzs = array(); + $root = $this->getRootComponent(); + foreach ($root->getChildren() as $children) { + foreach ($children as $child) { + // if the child is a vtimezone, add it to the results + // @todo make sure that tzid is available, throw exception otherwise + if ($child instanceof qCal_Component_Vtimezone) { + $tzid = $child->getTzid(); + $tzid = strtoupper($tzid); + $tzs[$tzid] = $child; + } + } + } + return $tzs; + + } + /** + * Get a specific timezone by tzid + * @param string The timezone identifier + */ + public function getTimezone($tzid) { + + $tzid = strtoupper($tzid); + $root = $this->getRootComponent(); + $timezones = $root->getTimezones(); + if (array_key_exists($tzid, $timezones)) { + return $timezones[$tzid]; + } + return false; + + } + /** + * Allows for components to get and set property values by calling + * qCal_Component::getPropertyName() and qCal_Component::setPropertyName('2.0') where propertyName is the property name + * to be set and $val is the property value. + * This is just a convenience facade, it isn't going to be used within the library as much as by end-users + * @todo I can't decided whether to maybe get rid of the facade methods at least for now since some properties + * can potentially return multiple values and that makes the interface inconsistent + */ + public function __call($method, $params) { + + $firstthree = substr($method, 0, 3); + $name = substr($method, 3); + if ($firstthree == "get") { + // if property is allowed multiple times, an array is returned, otherwise just the one component + if ($this->hasProperty($name)) { + $property = $this->getProperty($name); + if (!$property[0]->allowMultiple()) { + return $property[0]; + } else { + return $property; + } + } + } elseif ($firstthree == "set") { + $value = isset($params[0]) ? $params[0] : null; + $params = isset($params[1]) ? $params[1] : array(); + $property = qCal_Property::factory($name, $value, $params); + $this->addProperty($property); + } elseif ($firstthree == "add") { + // add property type + $property = qCal_Property::factory($name, $params); + $this->addProperty($property); + return $this; + } + // throw exception here? + // throw new qCal_Exception(); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Component/Daylight.php b/lib/qCal/lib/qCal/Component/Daylight.php new file mode 100644 index 0000000..ea361a0 --- /dev/null +++ b/lib/qCal/lib/qCal/Component/Daylight.php @@ -0,0 +1,367 @@ +getAction(); + switch(strtoupper($action->getValue())) { + case "AUDIO": + // action, trigger (already covered by parent constructor) + // attach can only occur once + $attach = $this->getProperty('ATTACH'); + if (count($attach) > 1) { + throw new qCal_Exception_InvalidProperty('VALARM audio component can contain one and only one ATTACH property'); + } + break; + case "DISPLAY": + // action, trigger, description + if (!$this->hasProperty('DESCRIPTION')) { + throw new qCal_Exception_MissingProperty("DISPLAY VALARM component requires DESCRIPTION property"); + } + break; + case "EMAIL": + // action, description, trigger, summary + if (!$this->hasProperty('DESCRIPTION')) { + throw new qCal_Exception_MissingProperty("EMAIL VALARM component requires DESCRIPTION property"); + } + if (!$this->hasProperty('SUMMARY')) { + throw new qCal_Exception_MissingProperty("EMAIL VALARM component requires SUMMARY property"); + } + break; + case "PROCEDURE": + // action, attach, trigger + $attach = $this->getProperty('ATTACH'); + if (count($attach) > 1) { + throw new qCal_Exception_InvalidProperty('VALARM procedure component can contain one and only one ATTACH property'); + } + if (count($attach) < 1) { + throw new qCal_Exception_MissingProperty("PROCEDURE VALARM component requires ATTACH property"); + } + break; + } + if ($this->hasProperty('DURATION')) { + if (!$this->hasProperty('REPEAT')) { + throw new qCal_Exception_MissingProperty("VALARM component with a DURATION property requires a REPEAT property"); + } + } + if ($this->hasProperty('REPEAT')) { + if (!$this->hasProperty('DURATION')) { + throw new qCal_Exception_MissingProperty("VALARM component with a REPEAT property requires a DURATION property"); + } + } + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Component/Vcalendar.php b/lib/qCal/lib/qCal/Component/Vcalendar.php new file mode 100644 index 0000000..4478e94 --- /dev/null +++ b/lib/qCal/lib/qCal/Component/Vcalendar.php @@ -0,0 +1,59 @@ +getProperties(); + $propnames = array_keys($properties); + if (in_array('DTEND', $propnames) && in_array('DURATION', $propnames)) { + throw new qCal_Exception_InvalidProperty('DTEND and DURATION cannot both occur in the same VEVENT component'); + } + if (in_array('DTSTART', $propnames)) { + $dtstart = $this->getProperty('dtstart'); + $dtstart = $dtstart[0]; + // check that if dtstart is a DATE that dtend is a DATE + if ($dtstart->getType() == 'DATE') { + if (in_array('DTEND', $propnames)) { + $dtend = $this->getProperty('dtend'); + $dtend = $dtend[0]; + if ($dtend->getType() != 'DATE') { + throw new qCal_Exception_InvalidProperty('If DTSTART property is specified as a DATE property, so must DTEND'); + } + } + } + // check that dtstart comes before dtend + if (in_array('DTEND', $propnames)) { + $dtend = $this->getProperty('dtend'); + $dtend = $dtend[0]; + $startdate = strtotime($dtstart->getValue()); + $enddate = strtotime($dtend->getValue()); + if ($startdate > $enddate) { + throw new qCal_Exception_InvalidProperty('DTSTART property must come before DTEND'); + } + } + } + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Component/Vfreebusy.php b/lib/qCal/lib/qCal/Component/Vfreebusy.php new file mode 100644 index 0000000..003a67d --- /dev/null +++ b/lib/qCal/lib/qCal/Component/Vfreebusy.php @@ -0,0 +1,137 @@ +getChildren(); + if (!array_key_exists('DAYLIGHT', $children) && !array_key_exists('STANDARD', $children)) { + throw new qCal_Exception_MissingComponent('Either a STANDARD or DAYLIGHT component is required within a VTIMEZONE component'); + } + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Component/Vtodo.php b/lib/qCal/lib/qCal/Component/Vtodo.php new file mode 100644 index 0000000..702ede2 --- /dev/null +++ b/lib/qCal/lib/qCal/Component/Vtodo.php @@ -0,0 +1,81 @@ + "january", + 2 => "february", + 3 => "march", + 4 => "april", + 5 => "may", + 6 => "june", + 7 => "july", + 8 => "august", + 9 => "september", + 10 => "october", + 11 => "november", + 12 => "december", + ); + /** + * @var array The month in a two-dimensional array (picture a calendar) + */ + protected $monthMap = array(); + /** + * Class constructor + * @param int The year of this date + * @param int The month of this date + * @param int The day of this date + */ + public function __construct($year = null, $month = null, $day = null, $rollover = false) { + + $this->setDate($year, $month, $day, $rollover); + + } + /** + * Set the date of this class + * The date defaults to now. If any part of the date is missing, it will default to whatever "now"'s + * date portion is. For instance, if the year provided is 2006 and no other portion is given, it will + * default to today's month and day, but in the year 2006. If, for any reason the date defaults to a + * nonsensical date, an exception will be thrown. For instance, if you specify the year as 2006, and + * the current date is february 29th, an exception will be thrown because the 29th of February does not + * exist in 2006. + * @param int The year of this date + * @param int The month of this date + * @param int The day of this date + * @throws qCal_Date_Exception_InvalidDate + */ + protected function setDate($year = null, $month = null, $day = null, $rollover = false) { + + $now = getdate(); + if (is_null($year)) { + $year = $now['year']; + } + if (is_null($month)) { + $month = $now['mon']; + } + if (is_null($day)) { + $day = $now['mday']; + } + + $this->date = gmmktime(0, 0, 0, $month, $day, $year); + $this->dateArray = self::gmgetdate($this->date); + if (!$rollover) { + if ($this->dateArray["mday"] != $day || $this->dateArray["mon"] != $month || $this->dateArray["year"] != $year) { + throw new qCal_DateTime_Exception_InvalidDate("Invalid date specified for qCal_Date: \"{$month}/{$day}/{$year}\""); + } + } + + // @todo Look into how much more efficient it might be to call date() only once and then break apart the result... + $formatString = "d|D|j|l|N|S|w|z|W|F|m|M|n|t|L|o|y|Y|c|r|U"; + $keys = explode("|", $formatString); + $vals = explode("|", gmdate($formatString, $this->date)); + $this->dateArray = array_merge($this->dateArray, array_combine($keys, $vals)); + return $this; + + } + /** + * This is a factory method. It allows you to create a date by string or by another date object (to make a copy) + */ + public static function factory($date) { + + if (is_integer($date)) { + // @todo Handle timestamps + } + if (is_string($date)) { + if (!$timestamp = strtotime($date)) { + // if unix timestamp can't be created throw an exception + throw new qCal_Date_Exception_InvalidDate("Invalid or ambiguous date string passed to qCal_Date::factory()"); + } + } + + $date = self::gmgetdate($timestamp); + $newdate = gmmktime(0, 0, 0, $date['mon'], $date['mday'], $date['year']); + $newdate = self::gmgetdate($newdate); + return new qCal_Date($newdate['year'], $newdate['mon'], $newdate['mday']); + + } + /** + * Set the format that should be used when calling either __toString() or format() without an argument. + * @param string $format + */ + public function setFormat($format) { + + $this->format = (string) $format; + return $this; + + } + /** + * Formats the date according to either the existing $this->format, or if the $format arg is passed + * in, it uses that. + * @param string The format that is to be used (according to php's date function). Only date-related metacharacters work. + */ + public function format($format) { + + $escape = false; + $meta = str_split($format); + $output = array(); + foreach($meta as $char) { + if ($char == '\\') { + $escape = true; + continue; + } + if (!$escape && array_key_exists($char, $this->dateArray)) { + $output[] = $this->dateArray[$char]; + } else { + $output[] = $char; + } + // reset this to false after every iteration that wasn't "continued" + $escape = false; + } + return implode($output); + + } + + /** + * Getters + * The next dozen or so methods are just your standard getters for things such as month, day, year, week day, etc. + */ + + /** + * Get the month (number) of this date + * @return integer A number between 1 and 12 inclusively + */ + public function getMonth() { + + return $this->dateArray["mon"]; + + } + /** + * Get the month of this date + * @return string The actual name of the month, capitalized + */ + public function getMonthName() { + + return $this->dateArray["month"]; + + } + /** + * Get the day of the month + * @return integer A number between 1 and 31 inclusively + */ + public function getDay() { + + return $this->dateArray["mday"]; + + } + /** + * Get the day of the year + * @return integer A number between 0 and 365 inclusively + */ + public function getYearDay($startFromOne = false) { + + $yearDay = $this->dateArray["yday"] + (integer) $startFromOne; + return $yearDay; + + } + /** + * Find how many days until the end of the year. + * For instance, if the date is December 25th, there are 6 days until the end of the year + */ + public function getNumDaysUntilEndOfYear() { + + $yearday = $this->getYearDay(true); + return $this->getNumDaysInYear() - $yearday; + + } + /** + * Get how many months until the end of the year + * @todo This is really rudimentary. There is more to this, but this works for now... + */ + public function getNumMonthsUntilEndOfYear() { + + return 12 - $this->getMonth(); + + } + /** + * Get the amount of days in the year (365 unless it is a leap-year, then it's 366) + */ + public function getNumDaysInYear() { + + return ($this->isLeapYear()) ? 366 : 365; + + } + /** + * Return the first day of the month as a qCal_Date object + * @return qCal_Date The first day of the month + */ + public function getFirstDayOfMonth() { + + return new qCal_Date($this->getYear(), $this->getMonth(), 1); + + } + /** + * Return the last day of the month as a qCal_Date object + * @return qCal_Date The last day of the month + */ + public function getLastDayOfMonth() { + + $lastday = $this->format("t"); + return new qCal_Date($this->getYear(), $this->getMonth(), $lastday); + + } + /** + * Get the number of days until the end of the month + */ + public function getNumDaysUntilEndOfMonth() { + + return $this->getNumDaysInMonth() - $this->getDay(); + + } + /** + * Get the year + * @return integer The year of this date, for example 1999 + */ + public function getYear() { + + return $this->dateArray["year"]; + + } + /** + * Get the day of the week + * @return integer A number between 0 (for Sunday) and 6 (for Saturday). + */ + public function getWeekDay() { + + return $this->dateArray["wday"]; + + } + /** + * Get the day of the week + * @return string The actual name of the day of the week, capitalized + */ + public function getWeekDayName() { + + return $this->dateArray["weekday"]; + + } + /** + * Get the amount of days in the current month of this year + * @return integer The number of days in the month + */ + public function getNumDaysInMonth() { + + return $this->dateArray["t"]; + + } + /** + * Get the week of the year + * @return integer The week of the year (0-51 I think) + * @todo This is not accurate if the week start isn't monday. I need to adjust for that + */ + public function getWeekOfYear() { + + return $this->dateArray["W"]; + + } + /** + * Get how many weeks until the end of the year + * @todo This is really rudimentary. There is more to this, but this works for now... + */ + public function getWeeksUntilEndOfYear() { + + return 52 - $this->getWeekOfYear(); + + } + /** + * Determine if this is a leap year + */ + public function isLeapYear() { + + return (boolean) $this->dateArray["L"]; + + } + /** + * Get a unix timestamp for the date + * @return integer The amount of seconds since unix epoch (January 1, 1970 UTC) + */ + public function getUnixTimestamp() { + + return $this->dateArray[0]; + + } + + /** + * Date magic + * This component is capable of doing some really convenient things with dates. + * It is capable of determining things such as how many days until the end of the year, + * which monday of the month it is (ie: third monday in february), etc. + */ + + /** + * Determine the number or Tuesdays (or whatever day of the week this date is) since the + * beginning or end of the month. + * @param integer $xth A positive or negative number that determines which weekday of the month we want + * @param string|integer $weekday Either Sunday-Saturday or 0-6 to specify the weekday we want + * @param string|integer $month Either January-December or 1-12 to specify the month we want + * @param integer $year A valid year to specify which year we want + */ + public function getXthWeekdayOfMonth($xth, $weekday = null, $month = null, $year = null) { + + $negpos = substr($xth, 0, 1); + if ($negpos == "+" || $negpos == "-") { + $xth = (integer) substr($xth, 1); + } else { + $negpos = "+"; + } + + if (is_null($weekday)) { + $weekday = $this->getWeekday(); + } + + if (ctype_digit((string) $weekday)) { + if (!array_key_exists($weekday, $this->weekdays)) { + throw new qCal_Date_Exception_InvalidWeekday("\"$weekday\" is not a valid weekday."); + } + } else { + $weekday = strtolower($weekday); + if (!in_array($weekday, $this->weekdays)) { + throw new qCal_Date_Exception_InvalidWeekday("\"$weekday\" is not a valid weekday."); + } + $wdays = array_flip($this->weekdays); + $weekday = $wdays[$weekday]; + } + + if (is_null($month)) { + $month = $this->getMonth(); + } + + if (ctype_digit((string) $month)) { + if (!array_key_exists($month, $this->months)) { + throw new qCal_Date_Exception_InvalidMonth("\"$month\" is not a valid month."); + } + } else { + $month = strtolower($month); + if (!in_array($month, $this->months)) { + throw new qCal_Date_Exception_InvalidMonth("\"$month\" is not a valid month."); + } + $mons = array_flip($this->months); + $month = $mons[$month]; + } + + if (is_null($year)) { + $year = $this->getYear(); + } + + if (!ctype_digit((string) $year) || strlen($year) != 4) { + throw new qCal_Date_Exception_InvalidYear("\"$year\" is not a valid year."); + } + + // now, using the year, month and numbered weekday, we need to find the actual day of the month... + $firstofmonth = new qCal_Date($year, $month, 1); + $numdaysinmonth = $firstofmonth->getNumDaysInMonth(); + $numweekdays = 0; // the number of weekdays that have occurred (in the loop) + $foundday = false; + if ($negpos == "+") { + $day = 1; + $wday = $firstofmonth->getWeekday(); + // while we are in the current month, loop + while ($day <= $numdaysinmonth) { + // if the specified weekday == the current week day in the loop + if ($weekday == $wday) { + $numweekdays++; + if ($numweekdays == $xth) { + // break out of the loop, we've found the right day! yay! + $foundday = $day; + break; + } + } + if ($wday == 6) $wday = 0; // reset to Sunday after Saturday + else $wday++; + $day++; + } + } else { + $day = $numdaysinmonth; + $lastofmonth = $firstofmonth->getLastDayOfMonth(); + $wday = $lastofmonth->getWeekday(); + while ($day >= 1) { + if ($weekday == $wday) { + $numweekdays++; + if ($numweekdays == $xth) { + // break out of the loop, we've found the right day! yay! + $foundday = $day; + break; + } + } + if ($wday == 0) $wday = 6; // reset to Saturday after Sunday + else $wday--; + $day--; + } + } + + if ($foundday && checkdate($month, $day, $year)) { + $date = new qCal_Date($year, $month, $day); + } else { + if ($day == 32) { + throw new qCal_DateTime_Exception_InvalidDate("You have specified an incorrect number of days for qCal_Date::getXthWeekdayOfMonth()"); + } else { + throw new qCal_DateTime_Exception_InvalidDate("You have entered an invalid date."); + } + } + + return $date; + + } + /** + * Determine the number or Tuesdays (or whatever day of the week this date is) since the + * beginning or end of the year. + */ + public function getXthWeekdayOfYear($xth, $weekday = null, $year = null) { + + $negpos = substr($xth, 0, 1); + if ($negpos == "+" || $negpos == "-") { + $xth = (integer) substr($xth, 1); + } else { + $negpos = "+"; + } + + if (is_null($weekday)) { + $weekday = $this->getWeekday(); + } + + if (ctype_digit((string) $weekday)) { + if (!array_key_exists($weekday, $this->weekdays)) { + throw new qCal_Date_Exception_InvalidWeekday("\"$weekday\" is not a valid weekday."); + } + } else { + $weekday = strtolower($weekday); + if (!in_array($weekday, $this->weekdays)) { + throw new qCal_Date_Exception_InvalidWeekday("\"$weekday\" is not a valid weekday."); + } + $wdays = array_flip($this->weekdays); + $weekday = $wdays[$weekday]; + } + + if (is_null($year)) { + $year = $this->getYear(); + } + + if (!ctype_digit((string) $year) || strlen($year) != 4) { + throw new qCal_Date_Exception_InvalidYear("\"$year\" is not a valid year."); + } + + // now find the specified day by counting either forwards or backwards to the day in question + $firstofyear = new qCal_Date($year, 1, 1); + $numdaysinyear = ($firstofyear->isLeapYear()) ? 366 : 365; + $numweekdays = 0; // the number of weekdays that have occurred within the loop + $found = false; // whether or not the specified day has been found + if ($negpos == "+") { + // count forward + // loop over every day of every month looking for the right one + $day = 1; + $wday = $firstofyear->getWeekDay(); + while ($day <= $numdaysinyear) { + // if the specified weekday == the current week day in the loop + if ($weekday == $wday) { + $numweekdays++; + if ($numweekdays == $xth) { + // break out of the loop, we've found the right day! yay! + $found = $day; + break; + } + } + if ($wday == 6) $wday = 0; // reset to Sunday after Saturday + else $wday++; + $day++; + } + } else { + // count backward + $lastofyear = new qCal_Date($year, 12, 31); + // count forward + // loop over every day of every month looking for the right one + $day = $numdaysinyear; + $wday = $lastofyear->getWeekDay(); + while ($day >= 1) { + // if the specified weekday == the current week day in the loop + if ($weekday == $wday) { + $numweekdays++; + if ($numweekdays == $xth) { + // break out of the loop, we've found the right day! yay! + $found = $day; + break; + } + } + if ($wday == 0) $wday = 6; // reset to Saturday after Sunday + else $wday--; + $day--; + } + } + + // @todo: Can't use checkdate here, so find another validation method... + if ($found) { + $date = new qCal_Date($year, 1, $found, true); // takes advantage of the rollover feature :) + } else { + throw new qCal_DateTime_Exception_InvalidDate("You have specified an incorrect number of days for qCal_Date::getXthWeekdayOfYear()"); + } + + return $date; + + } + + /** + * Magic methods + */ + + /** + * Output the date as a string. Options are as follows: + * @return string The formatted date + */ + public function __toString() { + + return $this->format($this->format); + + } + + /** + * Static methods + */ + + /** + * Because PHP does not provide a gmgetdate() function, I borrowed this one from the + * comments on the getdate() function page on php.net + * @param integer The timestamp to use to create the date + */ + public static function gmgetdate($timestamp = null) { + + $k = array('seconds','minutes','hours','mday','wday','mon','year','yday','weekday','month',0); + return(array_combine($k, split(":", gmdate('s:i:G:j:w:n:Y:z:l:F:U', is_null($timestamp) ? time() : $timestamp)))); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/DateTime.php b/lib/qCal/lib/qCal/DateTime.php new file mode 100644 index 0000000..3de5929 --- /dev/null +++ b/lib/qCal/lib/qCal/DateTime.php @@ -0,0 +1,190 @@ +setDate($date); + $this->setTime($time); + + } + /** + * Generate a datetime object via string + * @todo Should this accept qCal_Date and qCal_DateTime objects? + */ + public static function factory($datetime, $timezone = null) { + + if (is_null($timezone) || !($timezone instanceof qCal_Timezone)) { + // @todo Make sure this doesn't cause any issues + // detect if we're working with a UTC string like "19970101T180000Z", where the Z means use UTC time + if (strtolower(substr($datetime, -1)) == "z") { + $timezone = "UTC"; + } + $timezone = qCal_Timezone::factory($timezone); + } + // get the default timezone so we can set it back to it later + $tz = date_default_timezone_get(); + // set the timezone to GMT temporarily + date_default_timezone_set("GMT"); + + // handles unix timestamp + if (is_integer($datetime) || ctype_digit((string) $datetime)) { + $timestamp = $datetime; + } else { + // handles just about any string representation of date/time (strtotime) + if (is_string($datetime) || empty($datetime)) { + if (!$timestamp = strtotime($datetime)) { + // if unix timestamp can't be created throw an exception + throw new qCal_DateTime_Exception("Invalid or ambiguous date/time string passed to qCal_DateTime::factory()"); + } + } + } + + if (!isset($timestamp)) { + throw new qCal_DateTime_Exception("Could not generate a qCal_DateTime object."); + } + + list($year, $month, $day, $hour, $minute, $second) = explode("|", gmdate("Y|m|d|H|i|s", $timestamp)); + + // set the timezone back to what it was + date_default_timezone_set($tz); + + return new qCal_DateTime($year, $month, $day, $hour, $minute, $second, $timezone); + + } + /** + * Set the date component + */ + protected function setDate(qCal_Date $date) { + + $this->date = $date; + + } + /** + * Set the time component + */ + protected function setTime(qCal_Time $time) { + + $this->time = $time; + + } + /** + * Get time portion as object + */ + public function getTime() { + + return $this->time; + + } + /** + * Get date portion as object + */ + public function getDate() { + + return $this->date; + + } + /** + * Get unix timestamp + */ + public function getUnixTimestamp($useOffset = true) { + + return $this->date->getUnixTimestamp() + $this->time->getTimestamp($useOffset); + + } + /** + * Set the format to use when outputting as a string + */ + public function setFormat($format) { + + $this->format = (string) $format; + return $this; + + } + /** + * Format the date/time using PHP's date() function's meta-characters + * @todo It's obvious I need to find a better solution to formatting since I have repeated this method + * in three classes now... + */ + public function format($format) { + + $escape = false; + $meta = str_split($format); + $output = array(); + foreach($meta as $char) { + if ($char == '\\') { + $escape = true; + continue; + } + if (!$escape && $this->convertChar($char) != $char) { + $output[] = $this->convertChar($char); + } else { + $output[] = $char; + } + // reset this to false after every iteration that wasn't "continued" + $escape = false; + } + return implode($output); + + } + /** + * convert character + */ + protected function convertChar($char) { + + $char = $this->date->format($char); + $char = $this->time->format($char); + $char = $this->time->getTimezone()->format($char); + return $char; + + } + /** + * Output date/time as string + */ + public function __toString() { + + return $this->format($this->format); + + } + /** + * Get date/time as UTC + */ + public function getUtc($humanReadable = false) { + + if ($humanReadable) return gmdate('Y-m-d', $this->date->getUnixTimestamp()) . gmdate('\TH:i:s\Z', $this->time->getTimestamp()); + else return gmdate('Ymd', $this->date->getUnixTimestamp()) . gmdate('\THis\Z', $this->time->getTimestamp()); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/DateTime/Duration.php b/lib/qCal/lib/qCal/DateTime/Duration.php new file mode 100644 index 0000000..36cc1de --- /dev/null +++ b/lib/qCal/lib/qCal/DateTime/Duration.php @@ -0,0 +1,115 @@ + 604800, 'D' => 86400, 'H' => 3600, 'M' => 60, 'S' => 1); + /** + * Duration in seconds + */ + protected $duration; + /** + * If this is negative, this will be a minus symbol. Positive doesn't need a sign, so it is just null + */ + protected $sign; + /** + * Constructor + */ + public function __construct($duration = null) { + + $this->setDuration($duration); + + } + /** + * Set duration - accepts an integer (amount of seconds) or an icalendar-formatted duration string + */ + public function setDuration($duration) { + + $duration = strtoupper($duration); + // if plus or minus precedes number, remove it set in class + if (preg_match("/^[+-]/", (string) $duration, $matches)) { + if ($matches[0] == "-") $this->sign = "-"; + $duration = str_split($duration); + array_shift($duration); + $duration = implode("", $duration); + } + if (ctype_digit($duration)) { + $this->duration = $duration; + } else { + // convert value to duration in seconds + preg_match('/^P([0-9]+[W])?([0-9]+[D])?T?([0-9]+[H])?([0-9]+[M])?([0-9]+[S])?$/i', $duration, $matches); + // remove first element (which is just entire the matched string) + array_shift($matches); + $seconds = 0; + foreach ($matches as $duration) { + if (empty($duration)) continue; + $seconds += $this->calculateSeconds($duration); + } + $this->duration = $seconds; + } + return $this; + + } + /** + * Pass in a string like "15W" or "1D" and this will return how many seconds are in it + */ + protected function calculateSeconds($duration) { + + $amnt = preg_replace("/[^0-9]/i", "", $duration); + $inc = preg_replace("/[^A-Z]/i", "", $duration); + return $this->durations[$inc] * $amnt; + + } + /** + * Converts seconds to an icalendar-formatted duration string + */ + public function toICal() { + + $total = $this->duration; + $return = "P"; + // this is why order is important when defining $this->durations + foreach ($this->durations as $dur => $amnt) { + // how many "weeks" are in the value? + $quotient = (int) ($total / $amnt); + // get the remainder of the division + $remainder = $total - ($quotient*$amnt); + // now if we got a whole number as quotient, add this duration to the return string + if ($quotient) { + // if this is the first "time" duration, add the required T char + if ($dur == "H" || $dur == "M" || $dur == "S") { + if (!strpos($return, "T")) $return .= "T"; + } + $return .= $quotient . $dur; + } + $total = $remainder; + } + return $this->sign . $return; + + } + /** + * @todo Should this be the string representation? I dont really know. + */ + public function __toString() { + + return $this->toICal(); + + } + /** + * Get duration in seconds + */ + public function getSeconds() { + + return (integer) $this->sign . $this->duration; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/DateTime/Exception.php b/lib/qCal/lib/qCal/DateTime/Exception.php new file mode 100644 index 0000000..a688c0d --- /dev/null +++ b/lib/qCal/lib/qCal/DateTime/Exception.php @@ -0,0 +1,7 @@ +start = $start; + $this->end = $end; + if ($this->getSeconds() < 0) { + throw new qCal_DateTime_Exception_InvalidPeriod("The start date must come before the end date."); + } + + } + /** + * Converts to how many seconds between the two. because this is the smallest increment + * used in this class, seconds are used to determine other increments + */ + public function getSeconds() { + + return $this->end->getUnixTimestamp() - $this->start->getUnixTimestamp(); + + } + /** + * Returns start date + */ + public function getStart() { + + return $this->start; + + } + /** + * Returns end date + */ + public function getEnd() { + + return $this->end; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/DateTime/Recur.php b/lib/qCal/lib/qCal/DateTime/Recur.php new file mode 100644 index 0000000..8e7877b --- /dev/null +++ b/lib/qCal/lib/qCal/DateTime/Recur.php @@ -0,0 +1,452 @@ + 'Monday', + 'TU' => 'Tuesday', + 'WE' => 'Wednesday', + 'TH' => 'Thursday', + 'FR' => 'Friday', + 'SA' => 'Saturday', + 'SU' => 'Sunday', + ); + /** + * @var qCal_Date The start date/time of the recurrence + */ + protected $dtstart; + /** + * @var string frequency of the recurrence + */ + protected $freq; + /** + * @var qCal_Date The date/time which the recurrence ends + */ + protected $until; + /** + * @var integer The amount of recurrences + */ + protected $count; + /** + * @var integer Interval of recurrence (for every 3 days, "3" would be the interval) + */ + protected $interval; + /** + * @var integer|array An integer between 0 and 59 (for multiple, set as an array) + */ + protected $bysecond; + /** + * @var integer|array An integer between 0 and 59 (or an array of integers for multiple) + */ + protected $byminute; + /** + * @var integer|array An integer or array of integers between 0 and 23 + */ + protected $byhour; + /** + * @var string If present, represents the nth occurrence of a specific day within monthly or yearly + * so it can be something like +1MO (or simply 1MO) for the first monday within the month, whereas + * -1MO for the last monday of the month. Or it can be simply MO to represent every monday within the month + */ + protected $byday; + /** + * @var integer|array An integer or array of integers. -31 to -1 or 1 to 31. -10 would mean the tenth to last + * day of the month. [1,5,-5] would be the 1st, 5th, and 5th to last days of the month + */ + protected $bymonthday; + /** + * @var integer|array An integer or array of integers. -366 to -1 or 1 to 366. -306 represents the 306th to last + * day of the year (March 1st) + */ + protected $byyearday; + /** + * @var integer|array An integer or array of integers. -53 to -1 or 1 to 53. Only valid for yearly rules. + * 3 represents the third week of the year. + */ + protected $byweekno; + /** + * @var integer|array An integer or array of integers. 1 to 12. 3 would represent March + */ + protected $bymonth; + /** + * @var integer If present, it indicates the nth occurrence of the specific occurrence within the set of + * events specified by this recurrence rule + */ + protected $bysetpos; + /** + * @var string Must be one of the weekdays specified above (2 char). Specifies the day on which the work week + * starts. This is significant when a weekly rule has an interval greater than 1 and a byday rule part is specified. + * This is also significant when in a yearly rule when a byweekno rule part is specified. Defaults to "MO" + */ + protected $wkst = "MO"; + /** + * Constructor + * @param $freq string Must be one of the freqtypes specified above. + * @throws qCal_Date_Exception_InvalidRecur if a frequency other than those specified above is passed in + */ + public function __construct($dtstart = null) { + + $this->dtstart = is_null($dtstart) ? null : qCal_DateTime::factory($dtstart); + + } + /** + * Specifies the date when the recurrence stops, inclusively. If not present, and there is no count specified, + * then the recurrence goes on "forever". + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param $until string|qCal_Date|DateTime If time is specified, it must be UTC + * @throws qCal_Date_Exception_InvalidRecur + * @return self + */ + public function until($until = null) { + + if (is_null($until)) return $this->until; + if ($this->count()) throw new qCal_DateTime_Exception_InvalidRecur('A recurrence count and an until date cannot both be specified'); + $this->until = qCal_DateTime::factory($until); + return $this; + + } + /** + * Specifies the amount of recurrences before the recurrence ends. If neither this nor "until" is specified, + * the recurrence repeats "forever". + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param $count integer The amount of recurrences before it stops + * @throws qCal_Date_Exception_InvalidRecur + * @return self + */ + public function count($count = null) { + + if (is_null($count)) return $this->count; + if ($this->until()) throw new qCal_DateTime_Exception_InvalidRecur('A recurrence count and an until date cannot both be specified'); + $this->count = (integer) $count; + return $this; + + } + /** + * Specifies the start of the work-week, which is Monday by default + */ + public function wkst($wkst = null) { + + if (is_null($wkst)) return $this->wkst; + $abbrs = array_keys($this->weekdays); + if (!in_array($wkst, $abbrs)) throw new qCal_DateTime_Exception_InvalidRecur('"' . $wkst . '" is not a valid week day, must be one of the following: ' . implode(', ', $abbrs)); + $this->wkst = $wkst; + // @todo I wonder if re-sorting the weekdays array would help me in any way... + + } + /** + * Specifies the interval of recurrences + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param $interval integer The interval of recurrences, for instance every "3" days + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function interval($interval = null) { + + if (is_null($interval)) return $this->interval; + $this->interval = (integer) $interval; + return $this; + + } + /** + * Specifies a rule which will happen on every nth second. + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param $second integer|array Can be an integer (or array of ints) between 0 and 59 + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function bySecond($second = null) { + + if (is_null($second)) return $this->bysecond; + if (!is_array($second)) $second = array($second); + $this->bysecond = $second; + return $this; + + } + /** + * Specifies a rule which will happen on every nth minute + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param $minute integer|array Can be an integer (or array of ints) between 0 and 59 + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function byMinute($minute = null) { + + if (is_null($minute)) return $this->byminute; + if (!is_array($minute)) $minute = array($minute); + $this->byminute = $minute; + return $this; + + } + /** + * Specifies a rule which will happen on every nth hour + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param $hour integer|array Can be an integer (or array of ints) between 0 and 23 + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function byHour($hour = null) { + + if (is_null($hour)) return $this->byhour; + if (!is_array($hour)) $hour = array($hour); + $this->byhour = $hour; + return $this; + + } + /** + * Specifies a rule which will happen on whichever day is specified. For instance, "MO" would + * mean every monday. + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * Sets $this->byday into an array of arrays like array('SU' => 1) for '1SU' and array('SU' => 0) for 'SU' + * @param $day string|array Must be one of the 2-char week days specified above. Can be preceded by + * a positive or negative integer to represent, for instance, the third monday of the month (3MO) or second to last + * Sunday of the month (-2SU) + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function byDay($day = null) { + + if (is_null($day)) { + $ret = array(); + foreach ($this->byday as $val) { + $num = (current($val) == 0) ? "" : current($val); + $ret[] = $num . key($val); + } + return $ret; + } + if (!is_array($day)) $day = array($day); + $days = array(); + foreach ($day as $d) { + // optional plus or minus followed by a series of digits as group 1 + // two-character week day as group 2 + if (preg_match('/^([+-]?[0-9]+)?(MO|TU|WE|TH|FR|SA|SU)$/', $d, $matches)) { + $num = ($matches[1] == "") ? "0" : $matches[1]; + $wday = $matches[2]; + if (substr($num, 0, 1) == "+") { + $num = substr($num, 1); + } + $days[] = array($wday => $num); + } + } + $this->byday = $days; + return $this; + + } + /** + * Specifies a rule which will happen on the month days specified. For instance, 23 would mean the 23rd of every month. + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param integer|array Must be between 1 and 31 or -31 to 1 (or an array of those values) + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function byMonthDay($monthday = null) { + + if (is_null($monthday)) return $this->bymonthday; + if (!is_array($monthday)) $monthday = array($monthday); + $this->bymonthday = $monthday; + return $this; + + } + /** + * Specifies a rule which will happen on the nth day of the year + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param integer|array Must be between 1 and 366 or -366 to -1. + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function byYearDay($yearday = null) { + + if (is_null($yearday)) return $this->byyearday; + if (!is_array($yearday)) $yearday = array($yearday); + $this->byyearday = $yearday; + return $this; + + } + /** + * Specifies a rule which will happen on the nth week of the year + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param integer|array Must be between 1 and 53 or -53 to -1. + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function byWeekNo($weekno = null) { + + if (is_null($weekno)) return $this->byweekno; + if (!is_array($weekno)) $weekno = array($weekno); + $this->byweekno = $weekno; + return $this; + + } + /** + * Specifies a rule which will happen on the nth month of the year + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @param integer|array Must be between 1 and 12 + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function byMonth($month = null) { + + if (is_null($month)) return $this->bymonth; + if (!is_array($month)) $month = array($month); + $this->bymonth = $month; + return $this; + + } + /** + * Indicates the nth occurrence of the specific occurrence within the set of + * events specified by the rule. + * This is a getter as well as a setter (if no arg is supplied, it is a getter) + * @todo I don't really understand how this works... :( Figure out wtf it is for. + * @throws qCal_DateTime_Exception_InvalidRecur + * @return self + */ + public function bySetPos($setpos = null) { + + if (is_null($setpos)) return $this->bysetpos; + $this->bysetpos = (integer) $setpos; + return $this; + + } + /** + * Factory method generates the correct recur type based on the string it is passed: "yearly, weekly, etc." + * @param string The frequency type of recurrence rule you want to generate + * @param mixed The start date/time for the recurrence. Accepts anything qCal_Date accepts + */ + static public function factory($freq, $start) { + + $freq = ucfirst(strtolower($freq)); + $className = "qCal_DateTime_Recur_" . $freq; + $fileName = str_replace("_", DIRECTORY_SEPARATOR, $className) . ".php"; + qCal_Loader::loadFile($fileName); + $class = new $className($start); + return $class; + + } + /** + * Fetches instances of the recurrence rule in the given time period. Because recurrences + * could potentially go on forever, there is no way to fetch ALL instances of a recurrence rule + * other than providing a date range that spans the entire length of the recurrence. + * + * The way this will need to work is, depending on the frequency, I will find all possible + * occurrence of the rule. For instance, if this is a "monthly" rule, I'll find out which month + * to start in, then find all occurrence possible. Then narrow down by the other rules I guess. + * + * @idea Maybe I should build classes for each of the frequency types. That way I could loop over + * the object and get methods like qCal_DateTime_Recur_Monthly::isNthDay('SU') to find out what sunday + * of the month it is... stuff like that... I dunno... ? + * + * @throws qCal_DateTime_Exception_InvalidRecur + * @todo The giant switch in this method is a glaring code smell, but it works for now. I will refactor + * after version 0.1 and remove the switch (probably will implement qCal_DateTime_Recur_Yearly, qCal_DateTime_Recur_Monthly, etc.) + */ + public function getRecurrences($start, $end) { + + $start = qCal_DateTime::factory($start); + $end = qCal_DateTime::factory($end); + if ($start->getUnixTimestamp() > $end->getUnixTimestamp()) throw new qCal_DateTime_Exception_InvalidRecur('Start date must come before end date'); + if (!$this->interval) throw new qCal_DateTime_Exception_InvalidRecur('You must specify an interval'); + + $rules = array( + 'bymonth' => array(), + 'byweekno' => array(), + 'byyearday' => array(), + 'byday' => array(), + ); + + // byMonth rules + if (is_array($this->bymonth)) { + foreach ($this->bymonth as $bymonth) { + $rules['bymonth'][] = new qCal_DateTime_Recur_Rule_ByMonth($bymonth); + } + } + + // byWeekNo rules + if (is_array($this->byweekno)) { + foreach ($this->byweekno as $byweekno) { + $rules['byweekno'][] = new qCal_DateTime_Recur_Rule_ByWeekNo($byweekno); + } + } + + // byYearDay rules + if (is_array($this->byyearday)) { + foreach ($this->byyearday as $byyearday) { + $rules['byyearday'][] = new qCal_DateTime_Recur_Rule_ByYearDay($byyearday); + } + } + + // byMonthDay rules (these get applied to bymonth rules) + if (is_array($this->bymonthday)) { + foreach ($this->bymonthday as $bymonthday) { + $bmdrule = new qCal_DateTime_Recur_Rule_ByMonthDay($bymonthday); + foreach ($rules['bymonth'] as $bymonth) { + $bymonth->attach($bmdrule); + } + } + } + + // byDay rules (these get applied to bymonth rules if they exist, otherwise simply to year) + if (is_array($this->byday)) { + foreach ($this->byday as $byday) { + $bdrule = new qCal_DateTime_Recur_Rule_ByDay($byday); + if (is_array($rules['bymonth']) && !empty($rules['bymonth'])) { + foreach ($rules['bymonth'] as $bymonth) { + $bymonth->attach($bdrule); + } + } else { + $rules['byday'][] = $bdrule; + } + } + } + + // byHour rules (these get applied to each rule above) + if (is_array($this->byhour)) { + foreach ($this->byhour as $byhour) { + $bhrule = new qCal_DateTime_Recur_Rule_ByHour($byhour); + foreach ($rules as $type => $ruleset) { + foreach ($ruleset as $rule) { + $rule->attach($bhrule); + } + } + } + } + + // byMinute rules (these get applied to each rule above) + if (is_array($this->byminute)) { + foreach ($this->byminute as $byminute) { + $bmrule = new qCal_DateTime_Recur_Rule_ByMinute($byminute); + foreach ($rules as $type => $ruleset) { + foreach ($ruleset as $rule) { + $rule->attach($bmrule); + } + } + } + } + + // bySecond rules (these get applied to each rule above) + if (is_array($this->bysecond)) { + foreach ($this->bysecond as $bysecond) { + $bsrule = new qCal_DateTime_Recur_Rule_BySecond($bysecond); + foreach ($rules as $type => $ruleset) { + foreach ($ruleset as $rule) { + $rule->attach($bsrule); + } + } + } + } + + return $this->doGetRecurrences($rules, $start, $end); + + } + /** + * Each type of rule needs to determine its recurrences so this is left abstract + * to be implemented by children. + */ + abstract protected function doGetRecurrences($rules, $start, $end); + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/DateTime/Recur/Daily.php b/lib/qCal/lib/qCal/DateTime/Recur/Daily.php new file mode 100644 index 0000000..5f6ce19 --- /dev/null +++ b/lib/qCal/lib/qCal/DateTime/Recur/Daily.php @@ -0,0 +1,10 @@ +value = $value; + + } + /** + * Attach rules to this rule. For instance, if this is a byMonth rule, then + * we can attach byDay rules like "-1SU" for the last Sunday of the month. + */ + public function attach(qCal_DateTime_Recur_Rule $rule) { + + $this->rules[] = $rule; + + } + /** + * Creates the recurrences for this rule. Left to children to do this. + */ + abstract public function getRecurrences(); + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/DateTime/Recur/Rule/ByDay.php b/lib/qCal/lib/qCal/DateTime/Recur/Rule/ByDay.php new file mode 100644 index 0000000..0f92132 --- /dev/null +++ b/lib/qCal/lib/qCal/DateTime/Recur/Rule/ByDay.php @@ -0,0 +1,10 @@ +format('d'); + $smonth = $start->format('m'); + $syear = $start->format('Y'); + + // end day, year, and month + $eday = $end->format('d'); + $emonth = $end->format('m'); + $eyear = $end->format('Y'); + + // loop over years, by increment + $year = $syear; + while ($year <= $eyear) { + + // if byMonth is specified... + if (count($this->byMonth())) { + // loop over each month + for ($month = 1; $month <= 12; $month++) { + // if this is the start year still and we haven't reached the start month, skip ahead + if ($year == $syear && $month < $smonth) { + continue; + } + // if this is the end year and we have passed the end month, break out of loop + if ($year == $eyear && $month > $emonth) { + break; + } + // if this is not one of the bymonths, continue as well + if (!in_array($month, $this->byMonth())) { + continue; + } + // now we need to loop over each day of the month to look for byday or bymonthday + $thismonth = new qCal_Date(); // used to determine total days in the current month + $thismonth->setDate($year, $month, 1); + $weekdays = array( + 'MO' => 0, + 'TU' => 0, + 'WE' => 0, + 'TH' => 0, + 'FR' => 0, + 'SA' => 0, + 'SU' => 0, + ); + // @todo For now this only allows 1SU, SU, but not -1SU (no negatives for now) + for ($day = 1; $day <= $thismonth->format('t'); $day++) { + $alreadyadded = false; + $date = new qCal_Date; + $date->setDate($year, $month, $day); + $date->setTime(0, 0, 0); + $wdname = strtoupper(substr($date->format('l'), 0, 2)); + // keep track of how many of each day of the week have gone by + $weekdays[$wdname]++; + // if byDay is specified... + // @todo this is inconsistent, I don't use the getter here because of its special functionality. + // I need to either remove the special functionality or not use getters elsewhere in this method + $byday = $this->byday; + if (count($byday)) { + // by day is broken into an array of arrays like array('TH' => 0), array('FR' => 1), array('MO' => -2) etc. + // with zero meaning every instance of that particular day should be included and number meaning the Nth of that day + foreach ($byday as $val) { + // if at least one of this wday has gone by... + $num = current($val); + if ($weekdays[$wdname] > 0) { + // check if it is the right week day and if a digit is specified (like 1SU) that it is checked as well + if ($wdname == key($val) && ($weekdays[$wdname] == $num || $num == 0)) { + $recurrences[] = $date; + $alreadyadded = true; + } + } + } + } + + // if byMonthDay is specified... + if (count($this->byMonthDay())) { + foreach ($this->byMonthDay() as $mday) { + // only add this day if it hasn't been added already + if ($mday == $day && !$alreadyadded) { + $recurrences[] = $date; + } + } + } + + // now loop over each hour and add hours + if (count($this->byHour())) { + $hourrecurrences = array(); + foreach ($this->byHour() as $hour) { + $new = new qCal_Date(); + $new = $new->copy($date); + $new->setTime($hour, 0, 0); + $hourrecurrences[] = $new; + } + } + + // now loop over byHours and add byMinutes + if (count($this->byMinute())) { + if (!isset($minuterecurrences)) $minuterecurrences = array(); + foreach ($this->byMinute() as $minute) { + $new = new qCal_Date(); + $new = $new->copy($date); + $new->setTime(0, $minute, 0); + } + } + + // now loop over byMinutes and add bySeconds + + } + } + } + + // if in the first year we don't find an instance, don't do the interval, just increment a year + if ($year == $syear && count($recurrences)) $year += $this->interval(); + else ($year++); + } + + // now loop over weeks to get byWeekNo + + foreach ($recurrences as $date) { + // pr($date->format("r")); + } + // exit; + + return $recurrences; + // for bymonth, it would make the most sense to loop over each month until the specified one + // is found. Then loop over each day to find its sub-rules. + + // for byweekno, it would make the most sense to loop over each week until the specified one + // is found. Then apply any sub-rules (actually I'm not sure how byhour and its ilk would be applied in this situation... need to read the rfc) + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/DateTime/Timezone.php b/lib/qCal/lib/qCal/DateTime/Timezone.php new file mode 100644 index 0000000..ac7cb51 --- /dev/null +++ b/lib/qCal/lib/qCal/DateTime/Timezone.php @@ -0,0 +1,62 @@ +format($this->format); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Exception.php b/lib/qCal/lib/qCal/Exception.php new file mode 100644 index 0000000..b87fc56 --- /dev/null +++ b/lib/qCal/lib/qCal/Exception.php @@ -0,0 +1,11 @@ +options = array( + 'searchpath' => get_include_path(), + ); + $this->options = array_merge($this->options, $options); + + } + /** + * @todo What should this accept? filename? actual string content? either? + * @todo Maybe even create a parse() for raw string and a parseFile() for a file name? + */ + public function parse($content, $lexer = null) { + + if (is_null($lexer)) { + $lexer = new qCal_Parser_Lexer_iCalendar($content); + } + $this->lexer = $lexer; + return $this->doParse($this->lexer->tokenize()); + + } + /** + * Parse a file. The searchpath defaults to the include path. Also, if the filename + * provided is an absolute path, the searchpath is not used. This is determined by + * either the file starting with a forward slash, or a drive letter (for Windows) + * @todo Throw an exception if file doesn't exist + * @todo I'm not really sure that it should default to the include path. That's not really what the include path is for, is it? + * @todo Test for path starting with a drive letter for windows (or find a better way to detect that) + */ + public function parseFile($filename) { + + // @todo This is hacky... but it works + if (substr($filename, 0, 1) == '/' || substr($filename, 0, 3) == 'C:\\') { + if (file_exists($filename)) { + $content = file_get_contents($filename); + return $this->parse($content); + } + } else { + $paths = explode(PATH_SEPARATOR, $this->options['searchpath']); + foreach ($paths as $path) { + $fname = $path . DIRECTORY_SEPARATOR . $filename; + if (file_exists($fname)) { + $content = file_get_contents($fname); + return $this->parse($content); + } + } + } + throw new qCal_Exception_FileNotFound('File cannot be found: "' . $filename . '"'); + + } + /** + * Override doParse in a child class if necessary + */ + protected function doParse($tokens) { + + $properties = array(); + foreach ($tokens['properties'] as $propertytoken) { + $params = array(); + foreach ($propertytoken['params'] as $paramtoken) { + $params[$paramtoken['param']] = $paramtoken['value']; + } + try { + $properties[] = qCal_Property::factory($propertytoken['property'], $propertytoken['value'], $params); + } catch (qCal_Exception $e) { + // @todo There should be a better way of determining what went wrong during parsing/lexing than this + // do nothing... + // pr($e); + } + } + $component = qCal_Component::factory($tokens['component'], $properties); + foreach ($tokens['children'] as $child) { + $childcmpnt = $this->doParse($child); + $component->attach($childcmpnt); + } + return $component; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Parser/Lexer.php b/lib/qCal/lib/qCal/Parser/Lexer.php new file mode 100644 index 0000000..2de249a --- /dev/null +++ b/lib/qCal/lib/qCal/Parser/Lexer.php @@ -0,0 +1,34 @@ +content = $content; + + } + /** + * Tokenize content into tokens that can be used to build iCalendar objects + */ + abstract public function tokenize(); + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Parser/Lexer/iCalendar.php b/lib/qCal/lib/qCal/Parser/Lexer/iCalendar.php new file mode 100644 index 0000000..adbad57 --- /dev/null +++ b/lib/qCal/lib/qCal/Parser/Lexer/iCalendar.php @@ -0,0 +1,121 @@ +line_terminator = chr(13) . chr(10); + + } + /** + * Return a list of tokens (to be fed to the parser) + * @returns array tokens + */ + public function tokenize() { + + $lines = $this->unfold($this->content); + // loop through chunks of input text by separating by properties and components + // and create tokens for each one, creating a multi-dimensional array of tokens to return + $stack = array(); + foreach ($lines as $line) { + // begin a component + if (preg_match('#^BEGIN:([a-z]+)$#i', $line, $matches)) { + // create new array representing the new component + $array = array( + 'component' => $matches[1], + 'properties' => array(), + 'children' => array(), + ); + $stack[] = $array; + } elseif (strpos($line, "END:") === 0) { + // end component, pop the stack + $child = array_pop($stack); + if (empty($stack)) { + $tokens = $child; + } else { + $parent =& $stack[count($stack)-1]; + array_push($parent['children'], $child); + } + } else { + // continue component + if (preg_match('#^([^:]+):"?([^\n]+)?"?$#i', $line, $matches)) { + // @todo What do I do with empty values? + $value = isset($matches[2]) ? $matches[2] : ""; + $component =& $stack[count($stack)-1]; + // if line is a property line, start a new property, but first determine if there are any params + $property = $matches[1]; + $params = array(); + $propparts = explode(";", $matches[1]); + if (count($propparts) > 1) { + foreach ($propparts as $key => $part) { + // the first one is the property name + if ($key == 0) { + $property = $part; + } else { + // the rest are params + // @todo Quoted param values need to be taken care of... + list($paramname, $paramvalue) = explode("=", $part, 2); + $params[] = array( + 'param' => $paramname, + 'value' => $paramvalue, + ); + } + } + } + $proparray = array( + 'property' => $property, + 'value' => $value, + 'params' => $params, + ); + $component['properties'][] = $proparray; + } + } + } + return $tokens; + + } + /** + * Unfold the file before trying to parse it + */ + protected function unfold($content) { + + $return = array(); + $lines = explode($this->line_terminator, $content); + foreach ($lines as $line) { + $checkempty = trim($line); + if (empty($checkempty)) continue; + $chr1 = substr($line, 0, 1); + $therest = substr($line, 1); + // if character 1 is a whitespace character... (tab or space) + if ($chr1 == chr(9) || $chr1 == chr(32)) { + $return[count($return)-1] .= $therest; + } else { + $return[] = $line; + } + } + return $return; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Property.php b/lib/qCal/lib/qCal/Property.php new file mode 100644 index 0000000..2814397 --- /dev/null +++ b/lib/qCal/lib/qCal/Property.php @@ -0,0 +1,289 @@ +type) + * @todo Determine if there can be multiple params of the same name + */ + public function __construct($value = null, $params = array()) { + + if (is_null($this->name)) $this->name = $this->getPropertyNameFromClassName(get_class($this)); + foreach ($params as $pname => $pval) { + $this->setParam($pname, $pval); + } + // this must be set after parameters because the VALUE parameter can affect it + $this->setValue($value); + + } + /** + * Generates a qCal_Property class based on property name, params, and value + * which can come directly from an icalendar file + * @todo I need a way to detect INVALID properties as they are being parsed. This + * way there can be an option to NOT stop on errors. To just log and then continue. + */ + static public function factory($name, $value, $params = array()) { + + $className = self::getClassNameFromPropertyName($name); + $fileName = str_replace("_", DIRECTORY_SEPARATOR, $className) . ".php"; + try { + qCal_Loader::loadFile($fileName); + $class = new $className($value, $params); + } catch (qCal_Exception_InvalidFile $e) { + // if there is no class available for this property, check if it is non-standard + $xname = strtoupper(substr($name, 0, 2)); + // non-standard property + if ($xname == "X-") { + $class = new qCal_Property_NonStandard($value, $params, $name); + } else { + // if it's not a non-standard property, rethrow + throw $e; + } + } + return $class; + + } + /** + * Returns the property name (formatted and exactly to spec) + * @return string + */ + public function getName() { + + return $this->name; + + } + /** + * Returns the property value (as a string) + * If you want the actual object, use getValueObject() + * I wish I could just pass the object back and have php do some overloading magicness, but + * it doesn't know how :( + * @return string + */ + public function getValue() { + + return $this->value->__toString(); + + } + /** + * Just returns getValue() + */ + public function __toString() { + + return $this->getValue(); + + } + /** + * Returns raw value object (or for multi-value, an array) + * @return string + */ + public function getValueObject() { + + return $this->value; + + } + /** + * Sets the property value + * @param mixed + */ + public function setValue($value) { + + // if value sent is null and this property doesn't have a default value, + // the property can't be created, so throw an invalidpropertyvalue exception + if (is_null($value)) { + if ($this->default === false) { + // this is caught by factory and reported as a conformance error + throw new qCal_Exception_InvalidPropertyValue($this->getName() . ' property must have a value'); + } else { + $value = $this->default; + } + } + $this->value = $this->convertValue($value); + return $this; + + } + /** + * Converts a value into whatever internal storage mechanism the property uses + */ + protected function convertValue($value) { + + return qCal_Value::factory($this->getType(), $value); + + } + /** + * Returns the property type + * @return string + */ + public function getType() { + + return $this->type; + + } + /** + * Check if this is a property of a certain component. Some properties + * can only be set on certain Components. This method looks inside this + * property's $allowedComponents and returns true if $component is allowed + * + * @return boolean True if this is a property of $component, false otherwise + * @param qCal_Component The component we're evaluating + **/ + public function of(qCal_Component $component) { + + return in_array($component->getName(), $this->allowedComponents); + + } + /** + * Retreive the value of a parameter + * + * @return mixed parameter value + */ + public function getParam($name) { + + if (isset($this->params[strtoupper($name)])) { + return $this->params[strtoupper($name)]; + } + + } + /** + * Returns an array of all params + */ + public function getParams() { + + return $this->params; + + } + /** + * Set the value of a parameter + */ + public function setParam($name, $value) { + + $name = strtoupper($name); + // if value param has been passed in, change the type of this property to its value + if ($name == "VALUE") { + $value = strtoupper($value); + $this->type = $value; + } + $this->params[$name] = $value; + return $this; + + } + /** + * Determine's this property's name from the class name by adding a dash after + * every capital letter and upper-casing + * + * @return string The RFC property name + * @todo This method is flawed. The class name XLvFoo gets converted to X-L-VFOO when + * it should be X-LV-FOO + **/ + protected function getPropertyNameFromClassName($classname) { + + // determine the property name by class name + $parts = explode("_", $classname); + end($parts); + // find where capital letters are and insert dash + $chars = str_split(current($parts)); + // make a copy @todo Why make a copy? + $newchars = $chars; + foreach ($chars as $pos => $char) { + // don't add a dash for the first letter + if (!$pos) continue; + $num = ord($char); + // if character is a capital letter + if ($num >= 65 && $num <= 90) { + // insert dash + array_splice($newchars, $pos, 0, '-'); + } + } + return strtoupper(implode("", $newchars)); + + } + /** + * Determine's this property's class name from the property name + * + * @return string The property class name + **/ + protected function getClassNameFromPropertyName($name) { + + // remove dashes, capitalize properly + $parts = explode("-", $name); + $property = ""; + foreach ($parts as $part) $property .= trim(ucfirst(strtolower($part))); + // get the class, and instantiate + $className = "qCal_Property_" . $property; + return $className; + + } + /** + * Is this property allowed to be specified multiple times in a component? + * @return boolean + */ + public function allowMultiple() { + + return (boolean) $this->allowMultiple; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Property/Action.php b/lib/qCal/lib/qCal/Property/Action.php new file mode 100644 index 0000000..3606d1e --- /dev/null +++ b/lib/qCal/lib/qCal/Property/Action.php @@ -0,0 +1,51 @@ +":Jim + * Dolittle\, ABC Industries\, +1-919-555-1234 + * + * The following is an example of this property referencing a network + * resource, such as a vCard [RFC 2426] object containing the contact + * information: + * + * CONTACT;ALTREP="http://host.com/pdi/jdoe.vcf":Jim + * Dolittle\, ABC Industries\, +1-919-555-1234 + */ +class qCal_Property_Contact extends qCal_Property { + + protected $type = 'TEXT'; + protected $allowedComponents = array('VEVENT','VTODO','VJOURNAL','VFREEBUSY'); + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Property/Created.php b/lib/qCal/lib/qCal/Property/Created.php new file mode 100644 index 0000000..7feb552 --- /dev/null +++ b/lib/qCal/lib/qCal/Property/Created.php @@ -0,0 +1,45 @@ +value as $value) { + $return[] = $value->__toString(); + } + return implode(chr(44), $return); + + } + /** + * Sets the value of this property. Overwrites any previous values. Use addValue to + * add rather than overwrite. + * @todo I'm not sure I like how this is done. Eventually I will come back to it. + */ + public function setValue($value) { + + if (!is_array($value)) { + $value = array($value); + } + // parent::setValue($value); + $this->value = array(); + foreach ($value as $val) { + $this->value[] = $this->convertValue($val); + } + return $this; + + } + /** + * Add a value to the array of values (rather than overwrite) + */ + public function addValue($value) { + + $this->value[] = $this->convertValue($value); + return $this; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Property/NonStandard.php b/lib/qCal/lib/qCal/Property/NonStandard.php new file mode 100644 index 0000000..a0b203a --- /dev/null +++ b/lib/qCal/lib/qCal/Property/NonStandard.php @@ -0,0 +1,70 @@ +name = strtoupper($name); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Property/Organizer.php b/lib/qCal/lib/qCal/Property/Organizer.php new file mode 100644 index 0000000..24ea907 --- /dev/null +++ b/lib/qCal/lib/qCal/Property/Organizer.php @@ -0,0 +1,89 @@ + + * + * RELATED-TO:<19960401-080045-4000F192713-0052@host1.com> + */ +class qCal_Property_RecurrenceId extends qCal_Property { + + protected $type = 'TEXT'; + protected $allowedComponents = array('VEVENT','VTODO','VJOURNAL'); + protected $allowMultiple = true; + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Property/Repeat.php b/lib/qCal/lib/qCal/Property/Repeat.php new file mode 100644 index 0000000..f77d655 --- /dev/null +++ b/lib/qCal/lib/qCal/Property/Repeat.php @@ -0,0 +1,47 @@ + (1997 9:00 AM EDT)September 2-11 + * + * Daily until December 24, 1997: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=DAILY;UNTIL=19971224T000000Z + * + * ==> (1997 9:00 AM EDT)September 2-30;October 1-25 + * (1997 9:00 AM EST)October 26-31;November 1-30;December 1-23 + * + * Every other day - forever: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=DAILY;INTERVAL=2 + * ==> (1997 9:00 AM EDT)September2,4,6,8...24,26,28,30; + * October 2,4,6...20,22,24 + * (1997 9:00 AM EST)October 26,28,30;November 1,3,5,7...25,27,29; + * Dec 1,3,... + * + * Every 10 days, 5 occurrences: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=DAILY;INTERVAL=10;COUNT=5 + * + * ==> (1997 9:00 AM EDT)September 2,12,22;October 2,12 + * + * Everyday in January, for 3 years: + * + * DTSTART;TZID=US-Eastern:19980101T090000 + * RRULE:FREQ=YEARLY;UNTIL=20000131T090000Z; + * BYMONTH=1;BYDAY=SU,MO,TU,WE,TH,FR,SA + * or + * RRULE:FREQ=DAILY;UNTIL=20000131T090000Z;BYMONTH=1 + * + * ==> (1998 9:00 AM EDT)January 1-31 + * (1999 9:00 AM EDT)January 1-31 + * (2000 9:00 AM EDT)January 1-31 + * + * Weekly for 10 occurrences + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=WEEKLY;COUNT=10 + * + * ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21 + * (1997 9:00 AM EST)October 28;November 4 + * + * Weekly until December 24, 1997 + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=WEEKLY;UNTIL=19971224T000000Z + * + * ==> (1997 9:00 AM EDT)September 2,9,16,23,30;October 7,14,21 + * (1997 9:00 AM EST)October 28;November 4,11,18,25; + * December 2,9,16,23 + * Every other week - forever: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=WEEKLY;INTERVAL=2;WKST=SU + * + * ==> (1997 9:00 AM EDT)September 2,16,30;October 14 + * (1997 9:00 AM EST)October 28;November 11,25;December 9,23 + * (1998 9:00 AM EST)January 6,20;February + * ... + * + * Weekly on Tuesday and Thursday for 5 weeks: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=WEEKLY;UNTIL=19971007T000000Z;WKST=SU;BYDAY=TU,TH + * or + * RRULE:FREQ=WEEKLY;COUNT=10;WKST=SU;BYDAY=TU,TH + * + * ==> (1997 9:00 AM EDT)September 2,4,9,11,16,18,23,25,30;October 2 + * + * Every other week on Monday, Wednesday and Friday until December 24, + * 1997, but starting on Tuesday, September 2, 1997: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=WEEKLY;INTERVAL=2;UNTIL=19971224T000000Z;WKST=SU; + * BYDAY=MO,WE,FR + * ==> (1997 9:00 AM EDT)September 2,3,5,15,17,19,29;October + * 1,3,13,15,17 + * (1997 9:00 AM EST)October 27,29,31;November 10,12,14,24,26,28; + * December 8,10,12,22 + * + * Every other week on Tuesday and Thursday, for 8 occurrences: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=8;WKST=SU;BYDAY=TU,TH + * + * ==> (1997 9:00 AM EDT)September 2,4,16,18,30;October 2,14,16 + * + * Monthly on the 1st Friday for ten occurrences: + * + * DTSTART;TZID=US-Eastern:19970905T090000 + * RRULE:FREQ=MONTHLY;COUNT=10;BYDAY=1FR + * + * ==> (1997 9:00 AM EDT)September 5;October 3 + * (1997 9:00 AM EST)November 7;Dec 5 + * (1998 9:00 AM EST)January 2;February 6;March 6;April 3 + * (1998 9:00 AM EDT)May 1;June 5 + * + * Monthly on the 1st Friday until December 24, 1997: + * + * DTSTART;TZID=US-Eastern:19970905T090000 + * RRULE:FREQ=MONTHLY;UNTIL=19971224T000000Z;BYDAY=1FR + * + * ==> (1997 9:00 AM EDT)September 5;October 3 + * (1997 9:00 AM EST)November 7;December 5 + * + * Every other month on the 1st and last Sunday of the month for 10 + * occurrences: + * + * DTSTART;TZID=US-Eastern:19970907T090000 + * RRULE:FREQ=MONTHLY;INTERVAL=2;COUNT=10;BYDAY=1SU,-1SU + * + * ==> (1997 9:00 AM EDT)September 7,28 + * (1997 9:00 AM EST)November 2,30 + * (1998 9:00 AM EST)January 4,25;March 1,29 + * (1998 9:00 AM EDT)May 3,31 + * + * Monthly on the second to last Monday of the month for 6 months: + * + * DTSTART;TZID=US-Eastern:19970922T090000 + * RRULE:FREQ=MONTHLY;COUNT=6;BYDAY=-2MO + * + * ==> (1997 9:00 AM EDT)September 22;October 20 + * (1997 9:00 AM EST)November 17;December 22 + * (1998 9:00 AM EST)January 19;February 16 + * + * Monthly on the third to the last day of the month, forever: + * + * DTSTART;TZID=US-Eastern:19970928T090000 + * RRULE:FREQ=MONTHLY;BYMONTHDAY=-3 + * + * ==> (1997 9:00 AM EDT)September 28 + * (1997 9:00 AM EST)October 29;November 28;December 29 + * (1998 9:00 AM EST)January 29;February 26 + * ... + * + * Monthly on the 2nd and 15th of the month for 10 occurrences: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=2,15 + * + * ==> (1997 9:00 AM EDT)September 2,15;October 2,15 + * (1997 9:00 AM EST)November 2,15;December 2,15 + * (1998 9:00 AM EST)January 2,15 + * + * Monthly on the first and last day of the month for 10 occurrences: + * + * DTSTART;TZID=US-Eastern:19970930T090000 + * RRULE:FREQ=MONTHLY;COUNT=10;BYMONTHDAY=1,-1 + * + * ==> (1997 9:00 AM EDT)September 30;October 1 + * (1997 9:00 AM EST)October 31;November 1,30;December 1,31 + * (1998 9:00 AM EST)January 1,31;February 1 + * + * Every 18 months on the 10th thru 15th of the month for 10 + * occurrences: + * + * DTSTART;TZID=US-Eastern:19970910T090000 + * RRULE:FREQ=MONTHLY;INTERVAL=18;COUNT=10;BYMONTHDAY=10,11,12,13,14, + * 15 + * + * ==> (1997 9:00 AM EDT)September 10,11,12,13,14,15 + * (1999 9:00 AM EST)March 10,11,12,13 + * + * Every Tuesday, every other month: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=MONTHLY;INTERVAL=2;BYDAY=TU + * + * ==> (1997 9:00 AM EDT)September 2,9,16,23,30 + * (1997 9:00 AM EST)November 4,11,18,25 + * (1998 9:00 AM EST)January 6,13,20,27;March 3,10,17,24,31 + * ... + * + * Yearly in June and July for 10 occurrences: + * + * DTSTART;TZID=US-Eastern:19970610T090000 + * RRULE:FREQ=YEARLY;COUNT=10;BYMONTH=6,7 + * ==> (1997 9:00 AM EDT)June 10;July 10 + * (1998 9:00 AM EDT)June 10;July 10 + * (1999 9:00 AM EDT)June 10;July 10 + * (2000 9:00 AM EDT)June 10;July 10 + * (2001 9:00 AM EDT)June 10;July 10 + * Note: Since none of the BYDAY, BYMONTHDAY or BYYEARDAY components + * are specified, the day is gotten from DTSTART + * + * Every other year on January, February, and March for 10 occurrences: + * + * DTSTART;TZID=US-Eastern:19970310T090000 + * RRULE:FREQ=YEARLY;INTERVAL=2;COUNT=10;BYMONTH=1,2,3 + * + * ==> (1997 9:00 AM EST)March 10 + * (1999 9:00 AM EST)January 10;February 10;March 10 + * (2001 9:00 AM EST)January 10;February 10;March 10 + * (2003 9:00 AM EST)January 10;February 10;March 10 + * + * Every 3rd year on the 1st, 100th and 200th day for 10 occurrences: + * + * DTSTART;TZID=US-Eastern:19970101T090000 + * RRULE:FREQ=YEARLY;INTERVAL=3;COUNT=10;BYYEARDAY=1,100,200 + * + * ==> (1997 9:00 AM EST)January 1 + * (1997 9:00 AM EDT)April 10;July 19 + * (2000 9:00 AM EST)January 1 + * (2000 9:00 AM EDT)April 9;July 18 + * (2003 9:00 AM EST)January 1 + * (2003 9:00 AM EDT)April 10;July 19 + * (2006 9:00 AM EST)January 1 + * + * Every 20th Monday of the year, forever: + * DTSTART;TZID=US-Eastern:19970519T090000 + * RRULE:FREQ=YEARLY;BYDAY=20MO + * + * ==> (1997 9:00 AM EDT)May 19 + * (1998 9:00 AM EDT)May 18 + * (1999 9:00 AM EDT)May 17 + * ... + * + * Monday of week number 20 (where the default start of the week is + * Monday), forever: + * + * DTSTART;TZID=US-Eastern:19970512T090000 + * RRULE:FREQ=YEARLY;BYWEEKNO=20;BYDAY=MO + * + * ==> (1997 9:00 AM EDT)May 12 + * (1998 9:00 AM EDT)May 11 + * (1999 9:00 AM EDT)May 17 + * ... + * + * Every Thursday in March, forever: + * + * DTSTART;TZID=US-Eastern:19970313T090000 + * RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=TH + * + * ==> (1997 9:00 AM EST)March 13,20,27 + * (1998 9:00 AM EST)March 5,12,19,26 + * (1999 9:00 AM EST)March 4,11,18,25 + * ... + * + * Every Thursday, but only during June, July, and August, forever: + * + * DTSTART;TZID=US-Eastern:19970605T090000 + * RRULE:FREQ=YEARLY;BYDAY=TH;BYMONTH=6,7,8 + * + * ==> (1997 9:00 AM EDT)June 5,12,19,26;July 3,10,17,24,31; + * August 7,14,21,28 + * (1998 9:00 AM EDT)June 4,11,18,25;July 2,9,16,23,30; + * August 6,13,20,27 + * (1999 9:00 AM EDT)June 3,10,17,24;July 1,8,15,22,29; + * August 5,12,19,26 + * ... + * + * Every Friday the 13th, forever: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * EXDATE;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=MONTHLY;BYDAY=FR;BYMONTHDAY=13 + * + * ==> (1998 9:00 AM EST)February 13;March 13;November 13 + * (1999 9:00 AM EDT)August 13 + * (2000 9:00 AM EDT)October 13 + * ... + * + * The first Saturday that follows the first Sunday of the month, + * forever: + * + * DTSTART;TZID=US-Eastern:19970913T090000 + * RRULE:FREQ=MONTHLY;BYDAY=SA;BYMONTHDAY=7,8,9,10,11,12,13 + * + * ==> (1997 9:00 AM EDT)September 13;October 11 + * (1997 9:00 AM EST)November 8;December 13 + * (1998 9:00 AM EST)January 10;February 7;March 7 + * (1998 9:00 AM EDT)April 11;May 9;June 13... + * ... + * + * Every four years, the first Tuesday after a Monday in November, + * forever (U.S. Presidential Election day): + * + * DTSTART;TZID=US-Eastern:19961105T090000 + * RRULE:FREQ=YEARLY;INTERVAL=4;BYMONTH=11;BYDAY=TU;BYMONTHDAY=2,3,4, + * 5,6,7,8 + * + * ==> (1996 9:00 AM EST)November 5 + * (2000 9:00 AM EST)November 7 + * (2004 9:00 AM EST)November 2 + * ... + * + * The 3rd instance into the month of one of Tuesday, Wednesday or + * Thursday, for the next 3 months: + * + * DTSTART;TZID=US-Eastern:19970904T090000 + * RRULE:FREQ=MONTHLY;COUNT=3;BYDAY=TU,WE,TH;BYSETPOS=3 + * + * ==> (1997 9:00 AM EDT)September 4;October 7 + * (1997 9:00 AM EST)November 6 + * + * The 2nd to last weekday of the month: + * + * DTSTART;TZID=US-Eastern:19970929T090000 + * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-2 + * + * ==> (1997 9:00 AM EDT)September 29 + * (1997 9:00 AM EST)October 30;November 27;December 30 + * (1998 9:00 AM EST)January 29;February 26;March 30 + * ... + * + * Every 3 hours from 9:00 AM to 5:00 PM on a specific day: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=HOURLY;INTERVAL=3;UNTIL=19970902T170000Z + * + * ==> (September 2, 1997 EDT)09:00,12:00,15:00 + * + * Every 15 minutes for 6 occurrences: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=MINUTELY;INTERVAL=15;COUNT=6 + * + * ==> (September 2, 1997 EDT)09:00,09:15,09:30,09:45,10:00,10:15 + * + * Every hour and a half for 4 occurrences: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=MINUTELY;INTERVAL=90;COUNT=4 + * + * ==> (September 2, 1997 EDT)09:00,10:30;12:00;13:30 + * + * Every 20 minutes from 9:00 AM to 4:40 PM every day: + * + * DTSTART;TZID=US-Eastern:19970902T090000 + * RRULE:FREQ=DAILY;BYHOUR=9,10,11,12,13,14,15,16;BYMINUTE=0,20,40 + * or + * RRULE:FREQ=MINUTELY;INTERVAL=20;BYHOUR=9,10,11,12,13,14,15,16 + * + * ==> (September 2, 1997 EDT)9:00,9:20,9:40,10:00,10:20, + * ... 16:00,16:20,16:40 + * (September 3, 1997 EDT)9:00,9:20,9:40,10:00,10:20, + * ...16:00,16:20,16:40 + * ... + * + * An example where the days generated makes a difference because of + * WKST: + * + * DTSTART;TZID=US-Eastern:19970805T090000 + * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=MO + * + * ==> (1997 EDT)Aug 5,10,19,24 + * + * changing only WKST from MO to SU, yields different results... + * + * DTSTART;TZID=US-Eastern:19970805T090000 + * RRULE:FREQ=WEEKLY;INTERVAL=2;COUNT=4;BYDAY=TU,SU;WKST=SU + * ==> (1997 EDT)August 5,17,19,31 + */ +class qCal_Property_Rrule extends qCal_Property { + + protected $type = 'RECUR'; + protected $allowedComponents = array('VEVENT','VTODO','VJOURNAL','VTIMEZONE','DAYLIGHT','STANDARD'); + protected $allowMultiple = true; + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Property/Sequence.php b/lib/qCal/lib/qCal/Property/Sequence.php new file mode 100644 index 0000000..f2380a9 --- /dev/null +++ b/lib/qCal/lib/qCal/Property/Sequence.php @@ -0,0 +1,94 @@ + + * ;Minimum iCalendar version needed to parse the iCalendar object + * + * maxver = + * ;Maximum iCalendar version needed to parse the iCalendar object + * + * Example: The following is an example of this property: + * + * VERSION:2.0 + */ +class qCal_Property_Version extends qCal_Property { + + protected $type = 'TEXT'; + protected $allowedComponents = array('VCALENDAR'); + protected $default = "2.0"; + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Renderer.php b/lib/qCal/lib/qCal/Renderer.php new file mode 100644 index 0000000..aedf7ac --- /dev/null +++ b/lib/qCal/lib/qCal/Renderer.php @@ -0,0 +1,6 @@ +getName() . self::LINE_ENDING; + foreach ($component->getProperties() as $property) { + if (is_array($property)) { + foreach ($property as $prop) { + $return .= $this->renderProperty($prop); + } + } else { + $return .= $this->renderProperty($property); + } + } + foreach ($component->getChildren() as $children) { + if (is_array($children)) { + foreach ($children as $child) { + $return .= $this->render($child); + } + } else { + $return .= $this->render($children); + } + } + return $return . "END:" . $component->getName() . self::LINE_ENDING; + + } + /** + * Renders a property in accordance with rfc 2445 + * @todo $proptype is created below and never used... wtf? + */ + protected function renderProperty(qCal_Property $property) { + + $propval = $property->getValue(); + $params = $property->getParams(); + $paramreturn = ""; + foreach ($params as $paramname => $paramval) { + $paramreturn .= $this->renderParam($paramname, $paramval); + } + // if property has a "value" param, then use it as the type instead + $proptype = isset($params['VALUE']) ? $params['VALUE'] : $property->getType(); + if ($property instanceof qCal_Property_MultiValue) { + $values = array(); + foreach ($property->getValue() as $value) { + $values[] = $this->renderValue($property->getValue(), $proptype); + } + $value = implode(chr(44), $values); + } else { + $value = $this->renderValue($property->getValue(), $proptype); + } + $content = $property->getName() . $paramreturn . ":" . $value . self::LINE_ENDING; + return $this->fold($content); + + } + /** + * Renders a value + */ + protected function renderValue($value, $type) { + + switch(strtoupper($type)) { + case "TEXT": + $value = str_replace(",", "\,", $value); + break; + } + return $value; + + } + /** + * Renders a parameter + * RFC 2445 says if paramval contains COLON (US-ASCII decimal + * 58), SEMICOLON (US-ASCII decimal 59) or COMMA (US-ASCII decimal 44) + * character separators MUST be specified as quoted-string text values + */ + protected function renderParam($name, $value) { + + $invchars = array(chr(58),chr(59),chr(44)); + $quote = false; + foreach ($invchars as $char) { + if (strstr($value, $char)) { + $quote = true; + break; + } + } + if ($quote) $value = '"' . $value . '"'; + return ";" . $name . "=" . $value; + + } + + /** + * Text cannot exceed 75 octets. This method will "fold" long lines in accordance with RFC 2445 + * @todo Make sure this is multi-byte safe + * @todo The file I downloaded from google used this same folding method (long lines went to 76) + * so until I see any different, I'm going to keep it at 76. + */ + protected function fold($data) { + + if (strlen($data) == (self::FOLD_LENGTH + strlen(self::LINE_ENDING))) return $data; + $apart = str_split($data, self::FOLD_LENGTH); + return implode(self::LINE_ENDING . " ", $apart); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Time.php b/lib/qCal/lib/qCal/Time.php new file mode 100644 index 0000000..0a4be94 --- /dev/null +++ b/lib/qCal/lib/qCal/Time.php @@ -0,0 +1,198 @@ +setTimezone($timezone) + ->setTime($hour, $minute, $second, $rollover); + + } + /** + * Set the time + * @access protected This class is immutable, so this is protected. Only the constructor calls it. + */ + protected function setTime($hour = null, $minute = null, $second = null, $rollover = null) { + + if (is_null($hour)) { + $hour = gmdate("H"); + } + if (is_null($minute)) { + $minute = gmdate("i"); + } + if (is_null($second)) { + $second = gmdate("s"); + } + if (is_null($rollover)) $rollover = false; + if (!$rollover) { + if ($hour > 23 || $minute > 59 || $second > 59) { + throw new qCal_DateTime_Exception_InvalidTime(sprintf("Invalid time specified for qCal_Time: \"%02d:%02d:%02d\"", $hour, $minute, $second)); + } + } + // since PHP is incapable of storing a time without a date, we use the first day of + // the unix epoch so that we only have the amount of seconds since the zero of unix epoch + // we only use gm here because we don't want the server's timezone to interfere + $time = gmmktime($hour, $minute, $second, 1, 1, 1970); + $this->time = $time; + $formatString = "a|A|B|g|G|h|H|i|s|u"; + $keys = explode("|", $formatString); + $vals = explode("|", gmdate($formatString, $this->getTimestamp(false))); + $this->timeArray = array_merge($this->timeArray, array_combine($keys, $vals)); + return $this; + + } + /** + * Set the timezone + */ + protected function setTimezone($timezone) { + + if (is_null($timezone) || !($timezone instanceof qCal_Timezone)) { + $timezone = qCal_Timezone::factory($timezone); + } + $this->timezone = $timezone; + return $this; + + } + /** + * Get the timezone + */ + public function getTimezone() { + + return $this->timezone; + + } + /** + * Generate a qCal_Time object via a string or a number of other methods + */ + public static function factory($time, $timezone = null) { + + if (is_null($timezone) || !($timezone instanceof qCal_Timezone)) { + $timezone = qCal_Timezone::factory($timezone); + } + // get the default timezone so we can set it back to it later + $tz = date_default_timezone_get(); + // set the timezone to GMT temporarily + date_default_timezone_set("GMT"); + + if (is_integer($time)) { + // @todo Handle timestamps + // @maybe not... + } + if (is_string($time)) { + if ($time == "now") { + $time = new qCal_Time(null, null, null, $timezone); + } else { + $tstring = "01/01/1970 $time"; + if (!$timestamp = strtotime($tstring)) { + // if unix timestamp can't be created throw an exception + throw new qCal_DateTime_Exception_InvalidTime("Invalid or ambiguous time string passed to qCal_Time::factory()"); + } + list($hour, $minute, $second) = explode(":", gmdate("H:i:s", $timestamp)); + $time = new qCal_Time($hour, $minute, $second, $timezone); + } + } + + // set the timezone back to what it was + date_default_timezone_set($tz); + + return $time; + + } + /** + * Get the hour + */ + public function getHour() { + + return $this->timeArray['G']; + + } + /** + * Get the minute + */ + public function getMinute() { + + return $this->timeArray['i']; + + } + /** + * Get the second + */ + public function getSecond() { + + return $this->timeArray['s']; + + } + /** + * Get the timestamp + */ + public function getTimestamp($useOffset = true) { + + $time = ($useOffset) ? + $this->time - $this->getTimezone()->getOffsetSeconds() : + $this->time; + return $time; + + } + /** + * Set the format to use when outputting as a string + */ + public function setFormat($format) { + + $this->format = (string) $format; + return $this; + + } + /** + * Output the object using PHP's date() function's meta-characters + */ + public function format($format) { + + $escape = false; + $meta = str_split($format); + $output = array(); + foreach($meta as $char) { + if ($char == '\\') { + $escape = true; + continue; + } + if (!$escape && array_key_exists($char, $this->timeArray)) { + $output[] = $this->timeArray[$char]; + } else { + $output[] = $char; + } + // reset this to false after every iteration that wasn't "continued" + $escape = false; + } + return implode($output); + + } + /** + * Output the object as a string + */ + public function __toString() { + + return $this->format($this->format); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Timezone.php b/lib/qCal/lib/qCal/Timezone.php new file mode 100644 index 0000000..a1d9c22 --- /dev/null +++ b/lib/qCal/lib/qCal/Timezone.php @@ -0,0 +1,224 @@ +setName($name); + $this->setOffsetSeconds($offset); + if (is_null($abbreviation)) $abbreviation = $name; + $this->setAbbreviation($abbreviation); + $this->setIsDaylightSavings($daylightsavings); + $this->formatArray = array( + 'e' => $this->getName(), + 'I' => (integer) $this->isDaylightSavings(), + 'O' => $this->getOffsetHours(), + 'P' => $this->getOffset(), + 'T' => $this->getAbbreviation(), + 'Z' => $this->getOffsetSeconds(), + ); + + } + public function setName($name) { + + $this->name = (string) $name; + + } + public function setOffsetSeconds($offset) { + + $this->offsetSeconds = (integer) $offset; + + } + public function setAbbreviation($abbreviation) { + + $this->abbreviation = (string) $abbreviation; + + } + public function setIsDaylightSavings($daylightSavings = null) { + + $this->isDaylightSavings = (boolean) $daylightSavings; + + } + /** + * Generate a timezone from either an array of parameters, or a timezone + * name such as "America/Los_Angeles". + * @link http://php.net/manual/en/timezones.php A directory of valid timezones + * @todo This method is FUGLY. Rewrite it and make it make sense. This is sort of nonsensical. + */ + public static function factory($timezone = null) { + + if (is_array($timezone)) { + // remove anything irrelevant + $vals = array_intersect_key($timezone, array_flip(array('name','offsetSeconds','abbreviation','isDaylightSavings'))); + if (!array_key_exists("name", $vals)) { + // @todo throw an exception or something + } + if (!array_key_exists("offsetSeconds", $vals)) { + // @todo throw an exception or something + } + $name = $vals['name']; + $offsetSeconds = $vals['offsetSeconds']; + $abbreviation = (array_key_exists('abbreviation', $vals)) ? $vals['abbreviation'] : null; + $isDaylightSavings = (array_key_exists('isDaylightSavings', $vals)) ? $vals['isDaylightSavings'] : null; + $timezone = new qCal_Timezone($name, $offsetSeconds, $abbreviation, $isDaylightSavings); + } else { + // get the timezone information out of the string + $defaultTz = date_default_timezone_get(); + + if (is_null($timezone)) $timezone = $defaultTz; + + // if the timezone being set is invalid, we will get a PHP notice, so error is suppressed here + // @todo It would be more clean and probably more efficient to use php's error handling to throw an exception here... + if (is_string($timezone)) { + @date_default_timezone_set($timezone); + // if the function above didn't work, this will be true + if (date_default_timezone_get() != $timezone) { + // if the timezone requested is registered, use it + if (array_key_exists($timezone, self::$timezones)) { + $timezone = self::$timezones[$timezone]; + } else { + // otherwise, throw an exception + throw new qCal_DateTime_Exception_InvalidTimezone("'$timezone' is not a valid timezone."); + } + } else { + // if the timezone specified was a valid (native php) timezone, use it + $name = date("e"); + $offset = date("Z"); + $abbr = date("T"); + $ds = date("I"); + $timezone = new qCal_Timezone($name, $offset, $abbr, $ds); + } + } + + // now set it back to what it was... + date_default_timezone_set($defaultTz); + } + return $timezone; + + } + + public static function register(qCal_Timezone $timezone) { + + self::$timezones[$timezone->getName()] = $timezone; + + } + + public static function unregister($timezone) { + + unset(self::$timezones[(string) $timezone]); + + } + + public function getName() { + + return $this->name; + + } + + public function getOffset() { + + $seconds = $this->getOffsetSeconds(); + $negpos = "+"; + if ($seconds < 0) { + $negpos = "-"; + } + $hours = (integer) ($seconds / 60 / 60); + $minutes = $hours * 60; + $minutes = ($seconds / 60) - $minutes; + return sprintf("%s%02d:%02d", $negpos, abs($hours), abs($minutes)); + + } + + public function getOffsetHours() { + + $seconds = $this->getOffsetSeconds(); + $negpos = "+"; + if ($seconds < 0) { + $negpos = "-"; + } + $hours = (integer) ($seconds / 60 / 60); + $minutes = $hours * 60; + $minutes = ($seconds / 60) - $minutes; + return sprintf("%s%02d%02d", $negpos, abs($hours), abs($minutes)); + + } + + public function getOffsetSeconds() { + + return $this->offsetSeconds; + + } + + public function getAbbreviation() { + + return $this->abbreviation; + + } + + public function isDaylightSavings() { + + return $this->isDaylightSavings; + + } + + /** + * Set the format that should be used when calling either __toString() or format() without an argument. + * @param string $format + */ + public function setFormat($format) { + + $this->format = (string) $format; + return $this; + + } + + public function format($format) { + + $escape = false; + $meta = str_split($format); + $output = array(); + foreach($meta as $char) { + if ($char == '\\') { + $escape = true; + continue; + } + if (!$escape && array_key_exists($char, $this->formatArray)) { + $output[] = $this->formatArray[$char]; + } else { + $output[] = $char; + } + // reset this to false after every iteration that wasn't "continued" + $escape = false; + } + return implode($output); + + } + + public function __toString() { + + return $this->format($this->format); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Value.php b/lib/qCal/lib/qCal/Value.php new file mode 100644 index 0000000..3e3563c --- /dev/null +++ b/lib/qCal/lib/qCal/Value.php @@ -0,0 +1,98 @@ +setValue($value); + + } + /** + * A factory for data type objects. Pass in a type and a value, and it will return the value + * casted to the proper type + */ + public static function factory($type, $value) { + + // remove dashes, capitalize properly + $parts = explode("-", $type); + $type = ""; + foreach ($parts as $part) $type .= trim(ucfirst(strtolower($part))); + // get the class, and instantiate + $className = "qCal_Value_" . $type; + $class = new $className($value); + return $class; + + } + /** + * Sets the value of this object. The beauty of using inheritence here is that I can store + * the value however I want for any value type, and then on __toString() I can return it how + * iCalendar specifies :) + */ + public function setValue($value) { + + $this->value = $this->doCast($value); + return $this; + + } + /** + * Returns raw value (as it is stored) + */ + public function getValue() { + + return $this->value; + + } + /** + * Casts $value to this data type + */ + public function cast($value) { + + return $this->doCast($value); + + } + /** + * Returns the value as a string + */ + public function __toString() { + + return $this->toString($this->value); + + } + /** + * Converts from native format to a string, __toString() calls this internally + */ + protected function toString($value) { + + return (string) $value; + + } + /** + * This is left to be implemented by children classes, basically they + * implement this method to cast any input into their data type (from a string) + * @todo Change the name of this to something more appropriate, maybe toNative or something + */ + abstract protected function doCast($value); + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Value/Binary.php b/lib/qCal/lib/qCal/Value/Binary.php new file mode 100644 index 0000000..fb8b393 --- /dev/null +++ b/lib/qCal/lib/qCal/Value/Binary.php @@ -0,0 +1,69 @@ + + * + * qCal_DataType_Binary + * This object defines any binary object that may be attached to an + * icalendar file. + */ +class qCal_Value_Binary extends qCal_Value { + + /** + * When the value of a binary property is requested, it will be returned as a base64 encoded string + * @todo Base64 is the only encoding supported by this standard, but the encoding=base64 parameter must be + * provided regardless. + */ + protected function toString($value) { + + return base64_encode($value); + + } + /** + * Binary can be store as-is I believe, so don't change it + */ + protected function doCast($value) { + + return $value; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Value/Boolean.php b/lib/qCal/lib/qCal/Value/Boolean.php new file mode 100644 index 0000000..3d47a55 --- /dev/null +++ b/lib/qCal/lib/qCal/Value/Boolean.php @@ -0,0 +1,47 @@ +format('Ymd'); + + } + /** + * This converts to a qCal_Date for internal storage + */ + protected function doCast($value) { + + $date = qCal_Date::factory($value); + return $date; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Value/DateTime.php b/lib/qCal/lib/qCal/Value/DateTime.php new file mode 100644 index 0000000..4f138ce --- /dev/null +++ b/lib/qCal/lib/qCal/Value/DateTime.php @@ -0,0 +1,134 @@ +format('Ymd\THis'); + + } + /** + * This converts to a qCal_Date for internal storage + */ + protected function doCast($value) { + + // @todo This may be the wrong place to do this... + if ($value instanceof qCal_DateTime) { + return $value; + } + $date = qCal_DateTime::factory($value); + return $date; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Value/Duration.php b/lib/qCal/lib/qCal/Value/Duration.php new file mode 100644 index 0000000..b0a86b0 --- /dev/null +++ b/lib/qCal/lib/qCal/Value/Duration.php @@ -0,0 +1,67 @@ +toICal(); + + } + /** + * Convert to internal representation + */ + protected function doCast($value) { + + return new qCal_DateTime_Duration($value); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Value/Float.php b/lib/qCal/lib/qCal/Value/Float.php new file mode 100644 index 0000000..56474ca --- /dev/null +++ b/lib/qCal/lib/qCal/Value/Float.php @@ -0,0 +1,44 @@ +getUnixTimestamp() + $duration->getSeconds()); // @todo This needs to be updated once qCal_DateTime accepts timestamps + } + return new qCal_DateTime_Period($start, $end); + + } + /** + * Convert to string - this converts to string into the UTC/UTC format + */ + protected function toString($value) { + + return $value->getStart()->getUtc() . "/" + . $value->getEnd()->getUtc(); + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Value/Recur.php b/lib/qCal/lib/qCal/Value/Recur.php new file mode 100644 index 0000000..97f6c5c --- /dev/null +++ b/lib/qCal/lib/qCal/Value/Recur.php @@ -0,0 +1,279 @@ +format('His'); + + } + /** + * This converts to a qCal_Date for internal storage + */ + protected function doCast($value) { + + $date = qCal_Time::factory($value); + return $date; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Value/Uri.php b/lib/qCal/lib/qCal/Value/Uri.php new file mode 100644 index 0000000..03c3edc --- /dev/null +++ b/lib/qCal/lib/qCal/Value/Uri.php @@ -0,0 +1,56 @@ + + * + * Description: This data type might be used to reference binary + * information, for values that are large, or otherwise undesirable to + * include directly in the iCalendar object. + * + * The URI value formats in RFC 1738, RFC 2111 and any other IETF + * registered value format can be specified. + * + * Any IANA registered URI format can be used. These include, but are + * not limited to, those defined in RFC 1738 and RFC 2111. + * + * When a property parameter value is a URI value type, the URI MUST be + * specified as a quoted-string value. + * + * No additional content value encoding (i.e., BACKSLASH character + * encoding) is defined for this value type. + * + * Example: The following is a URI for a network file: + * + * http://host1.com/my-report.txt + */ +class qCal_Value_Uri extends qCal_Value { + + protected function toString($value) { + + return (string) $value; + + } + /** + * @todo: implement this + */ + protected function doCast($value) { + + return $value; + + } + +} \ No newline at end of file diff --git a/lib/qCal/lib/qCal/Value/UtcOffset.php b/lib/qCal/lib/qCal/Value/UtcOffset.php new file mode 100644 index 0000000..9d03553 --- /dev/null +++ b/lib/qCal/lib/qCal/Value/UtcOffset.php @@ -0,0 +1,50 @@ + 'foo', + * 'dtend' => 'bar', + * 'location' => 'foobar', + * // etc... + * )); + * $event->addProperty(new qCal_Property_Attendee('john.stamos@gmail.com', array( + * 'CUTYPE' => 'INDIVIDUAL', + * 'ROLE' => 'REQ-PARTICIPANT', + * 'PARTSTAT' => 'ACCEPTED', + * 'CN' => 'John Stamos' + * ))); + * $event->addProperty(new qCal_Property_Attendee('rebecca.stamos@gmail.com', array( + * 'CUTYPE' => 'INDIVIDUAL', + * 'ROLE' => 'REQ-PARTICIPANT', + * 'PARTSTAT' => 'ACCEPTED', + * 'CN' => 'Rebecca Stamos' + * ))); + * $attendees = $event->getProperty('attendee'); + * $john = $attendees->current(); + * $rebecca = $attendees->next(); + * $dtstart = $event->getProperty('dtstart'); + * $dtstart = $dtstart->current()->getValue(); + * // there's also this + * $dtstart = $dtstart->getDtstart(); // facade methods + */ +class UnitTestCase_Component extends UnitTestCase { + + /** + * A nice simple test to start things off... + */ + public function testClassTypes() { + + $property = new Mock_qCal_Property; + $component = new Mock_qCal_Component; + $this->assertTrue($property instanceof qCal_Property); + $this->assertTrue($component instanceof qCal_Component); + + } + /** + * Test that validation happens at render time + */ + public function testValidationHappensAtRenderTime() { + + $valarm = new qCal_Component_Valarm(array( + )); + $this->expectException(new qCal_Exception_MissingProperty('VALARM component requires ACTION property')); + $valarm->render(); + + } + /** + * Test facade methods + */ + public function testFacadeMethods() { + + $calendar = new qCal_Component_Vcalendar(); + $calendar->setProdId('// Test //'); + $this->assertEqual($calendar->getProdid(), '// Test //'); + // try something that has multiple instances + $event = new qCal_Component_Vevent(); + $event->addAttendee('luke.visinoni@gmail.com'); + $event->addAttendee('john.stamos@gmail.com'); + $attendees = $event->getAttendee(); + $this->assertEqual(count($attendees), 2); + + } + /** + * These are examples from other icalendar libraries I've found in various other languages + */ + public function testExamplesFromOtherLibraries() { + + $cal = new qCal; + $cal->addProperty('prodid', '-//My calendar product//mxm.dk//'); + $cal->addProperty('version', '2.0'); + // $prodid = $cal->getProperty('prodid'); + // $this->assertEqual($prodid[0]->getValue(), '-//My calendar product//mxm.dk//'); + // $version = $cal->getProperty('version'); + // $this->assertEqual($version[0]->getValue(), '2.0'); + + } + + /** + * Test that non-standard properties can be set on a component. + */ + public function testNonStandardProperties() { + + $cal = new qCal(array('X-IS-FOO' => array('foo','bar'))); + $xisfoos = $cal->getProperty('x-is-foo'); + $this->assertEqual($xisfoos[0]->getValue(), 'foo'); + $this->assertEqual($xisfoos[1]->getValue(), 'bar'); + + } + /** + * Test that if there is a class for a non-standard property available, it will be used + * instaead of the qCal_Property_NonStandard class + * @todo This should probably be in the property unit test case + * @todo I'm not sure I like how this turned out. What if somebody wants their non-standard + * class to be named "Lv_Property_XLvFoo"? Should this be so constricting? + */ + public function testClassIsUsedInsteadOfNonStandardClassIfAvailable() { + + $calendar = new qCal_Component_Vtodo(); + $calendar->addProperty('X-LV-FOO', 'bar'); + $xlvfoo = $calendar->getProperty('X-LV-FOO'); + $this->assertIsA($xlvfoo[0], 'qCal_Property_XLvFoo'); + + } + /** + * ATTACHING COMPONENTS + */ + /** + * only certain components can be attached to eachother + */ + public function testInvalidAttaching() { + + $this->expectException(new qCal_Exception_InvalidComponent('VCALENDAR cannot be attached to VEVENT')); + // calendars cannot be attached to anything (except perhaps other calendars) + $cal = new qCal; + $event = new qCal_Component_Vevent(); + $event->attach($cal); + + } + /** + * Component constructor should accept an array of properties + */ + public function testConstructorAcceptsInitializingArray() { + + $cal = new qCal(array( + 'version' => '2.0', + 'prodid' => '-//foo/bar//NONFOO v1.0//EN' + )); + $properties = array_keys($cal->getProperties()); + $this->assertEqual($properties, array('VERSION','PRODID')); + + } + /** + * Component constructor should accept an array of properties and + * if it needs several instances of the same property it should be able to accept + * an array inside of the array. + */ + public function testConstructorAcceptsInitializingArrayOfArrays() { + + $journal = new qCal_Component_Vjournal(array( + 'attach' => array( + 'http://www.example.com/foo/bar.mp3', + 'http://www.example.com/foo/baz.mp3' + ) + )); + $this->assertEqual(count($journal->getProperty('attach')), 2); + + } + /** + * The factory method is used in the parser. It may eventually be used in the facade methods as well + * The factory should accept the name of the component as the first param and the properties as the second + * It should also be completely case-insensitive + */ + public function testFactoryMethod() { + + $component = qCal_Component::factory('VALARM', array('action' => 'audio', 'TriggER' => 'P1w3Dt2H3M45S')); + $this->assertIsA($component, 'qCal_Component_Valarm'); + + } + /** + * Test that children can access their parents + * @todo I'm not sure that this is even necessary. See comments for qCal_Component::attach() for reasoning + * behind this. + */ + public function testAccessToParent() { + + $vtodo = new qCal_Component_Vtodo(array( + 'summary' => 'Foo', + 'description' => 'Foobar' + )); + $valarm = new qCal_Component_Valarm(array( + 'trigger' => '-P15M', + 'action' => 'display', + 'summary' => 'Foo', + 'description' => 'Foobar' + )); + $this->assertNull($valarm->getParent()); + $vtodo->attach($valarm); + $this->assertIdentical($valarm->getParent(), $vtodo); + + } + /** + * Test that all components have access to their root component + */ + public function testComponentsHaveAccessToRootComponent() { + + $cal = new qCal_Component_Vcalendar(); + $todo = new qCal_Component_Vtodo(); + $alarm = new qCal_Component_Valarm(); + $todo->attach($alarm); + $cal->attach($todo); + $this->assertIdentical($alarm->getRootComponent(), $cal); + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Component/Alarm.php b/lib/qCal/tests/UnitTestCase/Component/Alarm.php new file mode 100644 index 0000000..fa00a6d --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Component/Alarm.php @@ -0,0 +1,344 @@ +expectException(new qCal_Exception_MissingProperty('VALARM component requires ACTION property')); + $component = new qCal_Component_Valarm(); + $component->validate(); + // test that trigger is required to initialize an alarm + $this->expectException(new qCal_Exception_MissingProperty('VALARM component requires TRIGGER property')); + $component = new qCal_Component_Valarm('AUDIO'); + $component->validate(); + + } + /** + * Test the various types of alarms that are possible + */ + public function testAudioAlarm() { + + // audio alarm + $this->expectException(new qCal_Exception_MissingProperty('VALARM component requires TRIGGER property')); + $alarm = new qCal_Component_Valarm(array( + 'action' => 'audio', + //'trigger' => '15m' + )); + $alarm->validate(); + + } + /** + * Test the various types of alarms that are possible + */ + public function testDisplayAlarm() { + + // display alarm + $this->expectException(new qCal_Exception_MissingProperty('DISPLAY VALARM component requires DESCRIPTION property')); + $alarm = new qCal_Component_Valarm(array( + 'action' => 'display', + 'trigger' => 'P1W3DT2H3M45S', + //'description' => 'Feed your fish' + )); + $alarm->validate(); + + } + /** + * Test the various types of alarms that are possible + */ + public function testEmailAlarm() { + + // email alarm + $this->expectException(new qCal_Exception_MissingProperty('EMAIL VALARM component requires DESCRIPTION property')); + $alarm = new qCal_Component_Valarm(array( + 'action' => 'email', + 'trigger' => 'P1W3DT2H3M45S', + 'summary' => 'Feed your fish!', + //'description' => 'Don\'t forget to feed your poor fishy, Pedro V' + )); + $alarm->validate(); + + } + /** + * Test the various types of alarms that are possible + */ + public function testProcedureAlarm() { + + // email alarm + $this->expectException(new qCal_Exception_MissingProperty('PROCEDURE VALARM component requires ATTACH property')); + $alarm = new qCal_Component_Valarm(array( + 'action' => 'procedure', + 'trigger' => 'P1W3DT2H3M45S', + //'attach' => 'http://www.somewebsite.com/387592/alarm/5/', + )); + $alarm->validate(); + + } + /** + * ; 'action' and 'trigger' are both REQUIRED, + * ; but MUST NOT occur more than once + */ + public function testActionAndTriggerRequiredButCannotOccurMoreThanOnce() { + + $alarm = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'trigger' => 'p15m' + )); + // @todo Should this throw an exception since display requires description? + $alarm->addProperty('action', 'display'); + $action = $alarm->getProperty('action'); + $this->assertEqual(count($action), 1); + $alarm->addProperty('trigger', 'p30d'); + $trigger = $alarm->getProperty('trigger'); + $this->assertEqual(count($trigger), 1); + + } + /** + * ; 'duration' and 'repeat' are both optional, + * ; and MUST NOT occur more than once each, + * ; but if one occurs, so MUST the other + */ + public function testIfDurationOccursSoMustRepeat() { + + $this->expectException(new qCal_Exception_MissingProperty('VALARM component with a DURATION property requires a REPEAT property')); + $alarm = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'duration' => 'p30m', + 'trigger' => 'p20d' + )); + $alarm->validate(); + $this->expectException(new qCal_Exception_MissingProperty('VALARM component with a REPEAT property requires a DURATION property')); + $alarm2 = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'repeat' => 'p30m', + 'trigger' => 'p20d' + )); + $alarm2->validate(); + + } + /** + * ; the following are optional, + * ; and MAY occur more than once + * + * attach / x-prop + * + * The RFC specifies these as examples: + * ATTACH:CID:jsmith.part3.960817T083000.xyzMail@host1.com + * + * ATTACH;FMTTYPE=application/postscript:ftp://xyzCorp.com/pub/ + * reports/r-960812.ps + * @todo I'm not sure how the first one is suppose to work... :( + */ + public function testAttachAndNonStandardCanOccurMultipleTimes() { + + $alarm = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'trigger' => 'P1M' + )); + $attach1 = new qCal_Property_Attach('ftp://xyzCorp.com/pub/reports/r-960812.ps', array( + 'fmttype' => 'application/postscript' + )); + $alarm->addProperty($attach1); + $attach2 = new qCal_Property_Attach('ftp://xyzCorp.com/pub/reports/r-960813.ps', array( + 'fmttype' => 'application/postscript' + )); + $alarm->addProperty($attach2); + $attaches = $alarm->getProperty('attach'); + $this->assertEqual(count($attaches), 2); + + // now try non-standard properties + $alarm2 = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'trigger' => 'P1M' + )); + $ns1 = new qCal_Property_NonStandard('foobar', array( + 'x-foo' => 'baz' + ), 'x-lv-email'); + $alarm2->addProperty($ns1); + $ns2 = new qCal_Property_NonStandard('luke.visinoni@gmail.com', array( + 'altrep' => 'lvisinoni@foobar.com' + ), 'x-lv-email'); + $alarm2->addProperty($ns2); + $ns = $alarm2->getProperty('x-lv-email'); + $this->assertEqual(count($ns), 2); + + } + /** + * ; 'description' is optional, + * ; and MUST NOT occur more than once + */ + public function testDescriptionIsOptionalAndCannotOccurMoreThanOnce() { + + $alarm = new qCal_Component_Valarm(array( + 'action' => 'procedure', + 'trigger' => 'P15M', + 'attach' => 'http://www.example.com/foo' + )); + $alarm->addProperty(new qCal_Property_Description('This is a description')); + $alarm->addProperty(new qCal_Property_Description('This is another description')); + $this->assertEqual(count($alarm->getProperty('description')), 1); + + } + /** + * When the action is "AUDIO", the alarm can also include one and only + * one "ATTACH" property, which MUST point to a sound resource, which is + * rendered when the alarm is triggered. + * @todo I'm still not really sure when validation should occur. I called it manually here. + */ + public function testAudioAlarmCanIncludeOneAndOnlyOneAttachProperty() { + + $this->expectException(new qCal_Exception_InvalidProperty('VALARM audio component can contain one and only one ATTACH property')); + $alarm = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'trigger' => 'P45Y', + 'attach' => 'http://www.example.com/foobar.mp3' + )); + $alarm->addProperty('attach', 'http://www.example.com/boofar.mp3'); + $alarm->validate(); + + } + /** + * When the action is "PROCEDURE", the alarm MUST include one and only + * one "ATTACH" property, which MUST point to a procedure resource, + * which is invoked when the alarm is triggered. + */ + public function testProcedureAlarmCanIncludeOneAndOnlyOneAttachProperty() { + + $this->expectException(new qCal_Exception_InvalidProperty('VALARM procedure component can contain one and only one ATTACH property')); + $alarm = new qCal_Component_Valarm(array( + 'action' => 'procedure', + 'trigger' => 'P45Y', + 'attach' => 'http://www.example.com/foobar.mp3' + )); + $alarm->addProperty('attach', 'http://www.example.com/boofar.mp3'); + $alarm->validate(); + + } + /** + * The "VALARM" calendar component MUST only appear within either a + * "VEVENT" or "VTODO" calendar component. + */ + public function testValarmCanOnlyAppearInVeventOrVtodo() { + + $alarm = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'trigger' => 'P15M' + )); + $alarm2 = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'trigger' => 'P25M' + )); + $journal = new qCal_Component_Vjournal(array( + 'summary' => 'Some silly entry', + 'description' => 'Some silly description' + )); + $this->expectException(new qCal_Exception_InvalidComponent('VALARM cannot be attached to VJOURNAL')); + $journal->attach($alarm); + + } + /** + * "VALARM" calendar components + * cannot be nested. Multiple mutually independent "VALARM" calendar + * components can be specified for a single "VEVENT" or "VTODO" calendar + * component. + */ + public function testValarmCannotBeNestedAndCanOccurMultipleTimes() { + + $alarm = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'trigger' => 'P15M' + )); + $alarm2 = new qCal_Component_Valarm(array( + 'action' => 'audio', + 'trigger' => 'P25M' + )); + $todo = new qCal_Component_Vtodo(); + $this->expectException(new qCal_Exception_InvalidComponent('VALARM cannot be attached to VALARM')); + $alarm2->attach($alarm); + $todo->attach($alarm); + $todo->attach($alarm2); + + } + /** + * In an alarm set to trigger on the "START" of an event or to-do, the + * "DTSTART" property MUST be present in the associated event or to-do. + * In an alarm in a "VEVENT" calendar component set to trigger on the + * "END" of the event, either the "DTEND" property MUST be present, or + * the "DTSTART" and "DURATION" properties MUST both be present. In an + * alarm in a "VTODO" calendar component set to trigger on the "END" of + * the to-do, either the "DUE" property MUST be present, or the + * "DTSTART" and "DURATION" properties MUST both be present. + * + * @todo I don't know how this should work. Does the associated event or + * todo have to be related with the relatedto property? I need to work out + * how related components should work before I try to write a test for this. + * After reading the "related-to" property, I realize that I'll need to have + * some kind of framework for related components because when components are + * related, certain properties should change their related components when + * they are changed. For instance, if an alarm is related to an event, and the + * event start date is updated, the alarm's trigger should be changed as well. + * + * I just realized after typing all of the above that alarms are nested within + * their parent components, so I can test it that way. The above is still true + * for related components though. + * + * @todo After typing what I just typed above, I realize again that I don't know + * how to test this. How do I know whether or not an alarm is set to trigger on + * the start of an event if the event doesn't have a dtstart? + */ + public function testAlarmTriggerWithParentComponent() { + /* + $todo = new qCal_Component_Vtodo(array( + + )); + $alarm = new qCal_Component_Valarm(array( + + )); + $this->expectException(new qCal_Exception_MissingProperty('')); + */ + } + /** + * The alarm can be defined such that it triggers repeatedly. A + * definition of an alarm with a repeating trigger MUST include both the + * "DURATION" and "REPEAT" properties. The "DURATION" property specifies + * the delay period, after which the alarm will repeat. The "REPEAT" + * property specifies the number of additional repetitions that the + * alarm will triggered. This repitition count is in addition to the + * initial triggering of the alarm. Both of these properties MUST be + * present in order to specify a repeating alarm. If one of these two + * properties is absent, then the alarm will not repeat beyond the + * initial trigger. + * + * @todo I'm not sure how to test this. + */ + + /** + * In an EMAIL alarm, the intended alarm effect is for an email message + * to be composed and delivered to all the addresses specified by the + * "ATTENDEE" properties in the "VALARM" calendar component. The + * "DESCRIPTION" property of the "VALARM" calendar component MUST be + * used as the body text of the message, and the "SUMMARY" property MUST + * be used as the subject text. Any "ATTACH" properties in the "VALARM" + * calendar component SHOULD be sent as attachments to the message. + */ + public function testEmailAlarmShouldBeCapableOfFindingAllAttendeesAndAttachments() { + + + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Component/Calendar.php b/lib/qCal/tests/UnitTestCase/Component/Calendar.php new file mode 100644 index 0000000..2f9a89f --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Component/Calendar.php @@ -0,0 +1,108 @@ +expectException(new qCal_Exception_Conformance('PRODID property must be specified for component "VCALENDAR"')); + $component = new qCal_Component_Calendar(); + */ + + } + /** + * Make sure only valid components may be set on calendar + */ + public function testCalendarPropertyConformance() { + + $this->expectException(new qCal_Exception_InvalidProperty("VCALENDAR component does not allow PERCENT-COMPLETE property")); + $component = new qCal_Component_Vcalendar(); + $percentComplete = new qCal_Property_PercentComplete(35); + $component->addProperty($percentComplete); + + } + /** + * Tests that defaults get set correctly when instantiating + **/ + public function testCalendarInitializeDefaults() { + + $component = new qCal_Component_Vcalendar(); + $component->validate(); + // test calendar defaults. eventually there will be convenience methods + // that will allow you to do $component->prodid() to get and set + $prodid = $component->getProperty('prodid'); + $this->assertEqual($prodid[0]->getValue(), '-//Luke Visinoni//qCal v0.1//EN'); + $version = $component->getProperty('version'); + $this->assertEqual($version[0]->getValue(), '2.0'); + + // I commented this out because as of right now I Don't need a component factory + // do it through factory too + //$component = qCal_Component::factory('VCALENDAR'); + //$this->assertEqual($component->getProperty('prodid')->getValue(), '-//Luke Visinoni//qCal v0.1//EN'); + //$this->assertEqual($component->getProperty('version')->getValue(), '2.0'); + + } + /** + * Test that you can pass in name/value parts, property objects, or a combination, and that + * the name portion is case insensitive + */ + public function testCalendarInitializeAcceptsMixedArray() { + + // name/value pairs + $properties = array( + 'prodid' => '// Test //', + 'version' => '3.1' + ); + $calendar = new qCal_Component_Vcalendar($properties); + $prodid = $calendar->getProperty('prodid'); + $this->assertEqual($prodid[0]->getValue(), '// Test //'); + $version = $calendar->getProperty('version'); + $this->assertEqual($version[0]->getValue(), '3.1'); + + // property objects + $properties = array( + new qCal_Property_Version('4.0'), + new qCal_Property_Prodid('// Test //') + ); + $calendar = new qCal_Component_Vcalendar($properties); + $prodid = $calendar->getProperty('prodid'); + $this->assertEqual($prodid[0]->getValue(), '// Test //'); + $version = $calendar->getProperty('version'); + $this->assertEqual($version[0]->getValue(), '4.0'); + + // combination of property objects and name/value + $properties = array( + new qCal_Property_Version('4.0'), + 'prodid' => '// Test //', + ); + $calendar = new qCal_Component_Vcalendar($properties); + $prodid = $calendar->getProperty('prodid'); + $this->assertEqual($prodid[0]->getValue(), '// Test //'); + $version = $calendar->getProperty('version'); + $this->assertEqual($version[0]->getValue(), '4.0'); + + // @todo what happens if the same property is passed in multiple times, and that isn't allowed? + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Component/Event.php b/lib/qCal/tests/UnitTestCase/Component/Event.php new file mode 100644 index 0000000..eec2a49 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Component/Event.php @@ -0,0 +1,154 @@ +expectException(new qCal_Exception_InvalidProperty('DTEND and DURATION cannot both occur in the same VEVENT component')); + $event = new qCal_Component_Vevent(array( + 'dtend' => '20090101T040000Z', + 'duration' => 'P1D' + )); + $event->validate(); + + } + /** + * Description: A "VEVENT" calendar component is a grouping of component + * properties, and possibly including "VALARM" calendar components, that + * represents a scheduled amount of time on a calendar. For example, it + * can be an activity; such as a one-hour long, department meeting from + * 8:00 AM to 9:00 AM, tomorrow. Generally, an event will take up time + * on an individual calendar. Hence, the event will appear as an opaque + * interval in a search for busy time. Alternately, the event can have + * its Time Transparency set to "TRANSPARENT" in order to prevent + * blocking of the event in searches for busy time. + * + * Example: The following is an example of the "VEVENT" calendar + * component used to represent a meeting that will also be opaque to + * searches for busy time: + * + * BEGIN:VEVENT + * UID:19970901T130000Z-123401@host.com + * DTSTAMP:19970901T1300Z + * DTSTART:19970903T163000Z + * DTEND:19970903T190000Z + * SUMMARY:Annual Employee Review + * CLASS:PRIVATE + * CATEGORIES:BUSINESS,HUMAN RESOURCES + * END:VEVENT + * + * The following is an example of the "VEVENT" calendar component used + * to represent a reminder that will not be opaque, but rather + * transparent, to searches for busy time: + * + * BEGIN:VEVENT + * UID:19970901T130000Z-123402@host.com + * DTSTAMP:19970901T1300Z + * DTSTART:19970401T163000Z + * DTEND:19970402T010000Z + * SUMMARY:Laurel is in sensitivity awareness class. + * CLASS:PUBLIC + * CATEGORIES:BUSINESS,HUMAN RESOURCES + * TRANSP:TRANSPARENT + * END:VEVENT + * + * The following is an example of the "VEVENT" calendar component used + * to represent an anniversary that will occur annually. Since it takes + * up no time, it will not appear as opaque in a search for busy time; + * no matter what the value of the "TRANSP" property indicates: + * + * BEGIN:VEVENT + * UID:19970901T130000Z-123403@host.com + * DTSTAMP:19970901T1300Z + * DTSTART:19971102 + * SUMMARY:Our Blissful Anniversary + * CLASS:CONFIDENTIAL + * CATEGORIES:ANNIVERSARY,PERSONAL,SPECIAL OCCASION + * RRULE:FREQ=YEARLY + * END:VEVENT + * + */ + public function zzztestMeetingThatWillBeOpaqueToSearchesForBusyTime() { + + $event = new qCal_Component_Vevent(array( + 'uid' => '19970901T130000Z-123401@host.com', + 'dtstamp' => '19970901T1300Z', + 'dtstart' => '19970903T163000Z', + 'dtend' => '19970903T190000Z', + 'summary' => 'Annual Employee Review', + 'class' => 'PRIVATE', + 'categories' => array('BUSINESS','HUMAN RESOURCES', 'SOMETHING, COOL'), + )); + $cal = new qCal(); + $cal->attach($event); + $freebusytime = $cal->getFreeBusyTime(); + + } + /** + * The "VEVENT" is also the calendar component used to specify an + * anniversary or daily reminder within a calendar. These events have a + * DATE value type for the "DTSTART" property instead of the default + * data type of DATE-TIME. If such a "VEVENT" has a "DTEND" property, it + * MUST be specified as a DATE value also. The anniversary type of + * "VEVENT" can span more than one date (i.e, "DTEND" property value is + * set to a calendar date after the "DTSTART" property value). + */ + public function testVeventDtstartDateWithDtendDate() { + + $this->expectException(new qCal_Exception_InvalidProperty('If DTSTART property is specified as a DATE property, so must DTEND')); + $dtstart = new qCal_Property_Dtstart('09/09/2009', array('value' => 'date')); + $dtend = new qCal_Property_Dtend('09/19/2009'); + $event = new qCal_Component_Vevent(array( + 'uid' => '20090909T130000Z-123401@host.com', + 'dtstart' => $dtstart, + 'dtend' => $dtend + )); + $event->validate(); + + } + public function testVeventDtstartMustComeBeforeDtend() { + + $this->expectException(new qCal_Exception_InvalidProperty('DTSTART property must come before DTEND')); + $event = new qCal_Component_Vevent(array( + 'dtstart' => new qCal_Property_Dtstart('09/09/2009'), + 'dtend' => new qCal_Property_Dtend('09/08/2009') + )); + $event->validate(); + + } + /** + * The "DTSTART" property for a "VEVENT" specifies the inclusive start + * of the event. For recurring events, it also specifies the very first + * instance in the recurrence set. The "DTEND" property for a "VEVENT" + * calendar component specifies the non-inclusive end of the event. For + * cases where a "VEVENT" calendar component specifies a "DTSTART" + * property with a DATE data type but no "DTEND" property, the events + * non-inclusive end is the end of the calendar date specified by the + * "DTSTART" property. For cases where a "VEVENT" calendar component + * specifies a "DTSTART" property with a DATE-TIME data type but no + * "DTEND" property, the event ends on the same calendar date and time + * of day specified by the "DTSTART" property. + */ + public function zzztestRecurringEvent() { + + // test it! + + } + /** + * The "VEVENT" calendar component cannot be nested within another + * calendar component. However, "VEVENT" calendar components can be + * related to each other or to a "VTODO" or to a "VJOURNAL" calendar + * component with the "RELATED-TO" property. + */ + public function zzztestVeventCannotBeNestedButCanBeRelatedToVeventOrVtodoOrVjournal() { + + // test it! + + } + +} +?> \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Component/Timezone.php b/lib/qCal/tests/UnitTestCase/Component/Timezone.php new file mode 100644 index 0000000..34244e5 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Component/Timezone.php @@ -0,0 +1,327 @@ +expectException(new qCal_Exception_MissingProperty('VTIMEZONE component requires TZID property')); + $component = new qCal_Component_Vtimezone(); + $component->validate(); + + } + /** + * ; 'tzid' is required, but MUST NOT occur more + * ; than once + */ + public function testTzidIsRequiredButMustNotOccurMoreThanOnce() { + + $standard = new qCal_Component_Standard(array( + 'tzoffsetto' => '-0500', + 'tzoffsetfrom' => '-0400', + 'dtstart' => '19971026T020000' + )); + $tz = new qCal_Component_Vtimezone(array( + 'tzid' => 'California-Los_Angeles', + ), array($standard)); + $tz->addProperty(new qCal_Property_Tzid('New_York-New_York')); + $tzid = $tz->getProperty('tzid'); + $this->assertEqual(count($tzid), 1); + $this->assertEqual($tzid[0]->getValue(), 'New_York-New_York'); + + } + /** + * ; 'last-mod' and 'tzurl' are optional, + * but MUST NOT occur more than once + */ + public function testLastModAndTzurlMustNotOccurMoreThanOnce() { + + $standard = new qCal_Component_Standard(array( + 'tzoffsetto' => '-0500', + 'tzoffsetfrom' => '-0400', + 'dtstart' => '19971026T020000' + )); + $tz = new qCal_Component_Vtimezone(array( + 'tzid' => 'California-Los_Angeles', + 'last-modified' => qCal_DateTime::factory("now", "America/Los_Angeles"), + 'tzurl' => 'http://www.example.com/tz1', + ), array($standard)); + $newtime = time(); + $tz->addProperty('last-modified', $newtime); + $tz->addProperty(new qCal_Property_Tzurl('http://www.example.com/tz2')); + $tzlm = $tz->getProperty('last-modified'); + $tzurl = $tz->getProperty('tzurl'); + $this->assertEqual(count($tzlm), 1); + $this->assertEqual(count($tzurl), 1); + //@todo this probably isn't right... gmdate shouldn't work here... but I don't know for sure... + $this->assertEqual($tzlm[0]->getValue(), gmdate('Ymd\THis', $newtime)); + $this->assertEqual($tzurl[0]->getValue(), 'http://www.example.com/tz2'); + + } + /** + * The "VTIMEZONE" calendar component MUST include the "TZID" property + * and at least one definition of a standard or daylight component. The + * standard or daylight component MUST include the "DTSTART", + * "TZOFFSETFROM" and "TZOFFSETTO" properties. + */ + public function testOneOfStandardOrDaylightMustOccurAndMayOccurMoreThanOnce() { + + $this->expectException(new qCal_Exception_MissingComponent('Either a STANDARD or DAYLIGHT component is required within a VTIMEZONE component')); + $tz = new qCal_Component_Vtimezone(array( + 'tzid' => 'US-Eastern', + ), array( + // $standard + )); + $tz->validate(); + $standard = new qCal_Component_Standard(array( + 'tzoffsetto' => '-0500', + 'tzoffsetfrom' => '-0400', + 'dtstart' => '19971026T020000' + )); + $standard->validate(); + $standard2 = new qCal_Component_Standard(array( + 'tzoffsetto' => '-0600', + 'tzoffsetfrom' => '-0500', + 'dtstart' => '19981026T020000' + )); + $standard2->validate(); + $tz->attach($standard); + $tz->attach($standard2); + $tz->validate(); // shouldn't throw an exception now that standard was attached + $chidren = $tz->getChildren(); + $this->assertEqual(count($children), 2); + + } + /** + * The vcalendar component should be capable of retrieving all of the available time zones + */ + public function testGetTimeZonesFromCalendar() { + + $cal = new qCal_Component_Vcalendar; + $useastern = new qCal_Component_Vtimezone(array( + 'tzid' => 'US-Eastern', + )); + // fake us eastern timezone + $useastern->attach(new qCal_Component_Standard(array( + 'dtstart' => qCal_DateTime::factory('20090913T000000Z'), + 'offsetto' => new qCal_Property_Tzoffsetto('0200'), + 'offsetfrom' => new qCal_Property_Tzoffsetfrom('0400'), + ))); + $uswestern = new qCal_Component_Vtimezone(array( + 'tzid' => 'US-Western', + )); + // fake us western timezone + $uswestern->attach(new qCal_Component_Standard(array( + 'dtstart' => qCal_DateTime::factory('20090913T000000Z'), + 'offsetto' => new qCal_Property_Tzoffsetto('0100'), + 'offsetfrom' => new qCal_Property_Tzoffsetfrom('0300'), + ))); + $cal->attach($useastern); + $cal->attach($uswestern); + $timezones = $cal->getTimeZones(); + $this->assertEqual(count($timezones), 2); + $this->assertIdentical($timezones['US-EASTERN'], $useastern); + $this->assertIdentical($timezones['US-WESTERN'], $uswestern); + + } + /** + * Timezones should be accessible individually by getTimezone() + */ + public function testGetTimezone() { + + $cal = new qCal_Component_Vcalendar; + $useastern = new qCal_Component_Vtimezone(array( + 'tzid' => 'US-Eastern', + )); + // fake us eastern timezone + $useastern->attach(new qCal_Component_Standard(array( + 'dtstart' => qCal_DateTime::factory('20090913T000000Z'), + 'offsetto' => new qCal_Property_Tzoffsetto('0200'), + 'offsetfrom' => new qCal_Property_Tzoffsetfrom('0400'), + ))); + $uswestern = new qCal_Component_Vtimezone(array( + 'tzid' => 'US-Western', + )); + // fake us western timezone + $uswestern->attach(new qCal_Component_Standard(array( + 'dtstart' => qCal_DateTime::factory('20090913T000000Z'), + 'offsetto' => new qCal_Property_Tzoffsetto('0100'), + 'offsetfrom' => new qCal_Property_Tzoffsetfrom('0300'), + ))); + $cal->attach($useastern); + $cal->attach($uswestern); + $this->assertIdentical($cal->getTimezone('us-eastern'), $useastern); + + } + /** + * An individual "VTIMEZONE" calendar component MUST be specified for + * each unique "TZID" parameter value specified in the iCalendar object. + * @todo Finish this when you are more sure how timezones will work + */ + public function testEachTzidParameterMustHaveCorrespondingVTimezone() { + + $cal = new qCal_Component_Vcalendar(); + $todo1 = new qCal_Component_Vtodo(array( + 'summary' => 'Make the monkey wash the cat', + 'description' => 'Make the monkey wash the cat with a cloth. Make sure to also video-tape it.', + new qCal_Property_Dtstart('20090815T050000', array('tzid' => 'US-Eastern')), + )); + $todo2 = new qCal_Component_Vtodo(array( + 'summary' => 'Make the cat wash the monkey', + 'description' => 'Make the cat wash the monkey with a sponge. Make sure to audio-tape it.', + new qCal_Property_Dtstart('20090816T050000', array('tzid' => 'US-Pacific')), + )); + $this->expectException(new qCal_Exception_MissingComponent('TZID "US-Eastern" not defined')); + $cal->attach($todo1); + $cal->attach($todo2); + // $cal->validate(); + + // $this->expectException(new qCal_Exception_MissingComponent('TZID "US-Pacific" not defined')); + + } + /** + * Each "VTIMEZONE" calendar component consists of a collection of one + * or more sub-components that describe the rule for a particular + * observance (either a Standard Time or a Daylight Saving Time + * observance). The "STANDARD" sub-component consists of a collection of + * properties that describe Standard Time. The "DAYLIGHT" sub-component + * consists of a collection of properties that describe Daylight Saving + * Time. In general this collection of properties consists of: + * + * - the first onset date-time for the observance + * + * - the last onset date-time for the observance, if a last onset + * is known. + * + * - the offset to be applied for the observance + * + * - a rule that describes the day and time when the observance + * takes effect + * + * - an optional name for the observance + * + * For a given time zone, there may be multiple unique definitions of + * the observances over a period of time. Each observance is described + * using either a "STANDARD" or "DAYLIGHT" sub-component. The collection + * of these sub-components is used to describe the time zone for a given + * period of time. The offset to apply at any given time is found by + * locating the observance that has the last onset date and time before + * the time in question, and using the offset value from that + * observance. + */ + public function zzztestAllTheStuffAbove() { + + // this cannot be tested until the recurrence property is unit tested and working... + $tz = new qCal_Component_Vtimezone(array( + 'tzid' => 'America/Los_Angeles' + ), array( + new qCal_Component_Standard(array( + 'dtstart' => '19701101T020000', + 'tzoffsetfrom' => '-0800', + 'tzoffsetto' => '-0700', + 'tzname' => 'PST', + new qCal_Property_Rrule('', array( + 'freq' => 'yearly', + 'bymonth' => '3', + 'byday' => '2su' + )), + new qCal_Property_Rrule('', array( + 'freq' => 'monthly', + 'bymonth' => '3', + 'byday' => '1su' + )) + )), + new qCal_Component_Daylight(array( + 'dtstart' => '19701101T020000', + 'tzoffsetfrom' => '-0800', + 'tzoffsetto' => '-0700', + 'tzname' => 'PDT', + new qCal_Property_Rrule('', array( + 'freq' => 'yearly', + 'bymonth' => '11', + 'byday' => '1su' + )) + )), + )); + + } + /** + * The optional "TZURL" property is url value that points to a published + * VTIMEZONE definition. TZURL SHOULD refer to a resource that is + * accessible by anyone who might need to interpret the object. This + * SHOULD NOT normally be a file: URL or other URL that is not widely- + * accessible. + * @todo WTF does "should not normally be a file" mean?? + */ + public function zzztestTzurlPropertyIsUrl() { + + // @todo Finish this when you can + + } + /** + * The collection of properties that are used to define the STANDARD and + * DAYLIGHT sub-components include: + * + * The mandatory "DTSTART" property gives the effective onset date and + * local time for the time zone sub-component definition. "DTSTART" in + * this usage MUST be specified as a local DATE-TIME value. + * + * The mandatory "TZOFFSETFROM" property gives the UTC offset which is + * in use when the onset of this time zone observance begins. + * "TZOFFSETFROM" is combined with "DTSTART" to define the effective + * onset for the time zone sub-component definition. For example, the + * following represents the time at which the observance of Standard + * Time took effect in Fall 1967 for New York City: + * + * DTSTART:19671029T020000 + * + * TZOFFSETFROM:-0400 + * + * The mandatory "TZOFFSETTO " property gives the UTC offset for the + * time zone sub-component (Standard Time or Daylight Saving Time) when + * this observance is in use. + * + * The optional "TZNAME" property is the customary name for the time + * zone. It may be specified multiple times, to allow for specifying + * multiple language variants of the time zone names. This could be used + * for displaying dates. + * + * If specified, the onset for the observance defined by the time zone + * sub-component is defined by either the "RRULE" or "RDATE" property. + * If neither is specified, only one sub-component can be specified in + * the "VTIMEZONE" calendar component and it is assumed that the single + * observance specified is always in effect. + * + * The "RRULE" property defines the recurrence rule for the onset of the + * observance defined by this time zone sub-component. Some specific + * requirements for the usage of RRULE for this purpose include: + * + * - If observance is known to have an effective end date, the + * "UNTIL" recurrence rule parameter MUST be used to specify the + * last valid onset of this observance (i.e., the UNTIL date-time + * will be equal to the last instance generated by the recurrence + * pattern). It MUST be specified in UTC time. + * + * - The "DTSTART" and the "TZOFFSETTO" properties MUST be used + * when generating the onset date-time values (instances) from the + * RRULE. + * + * Alternatively, the "RDATE" property can be used to define the onset + * of the observance by giving the individual onset date and times. + * "RDATE" in this usage MUST be specified as a local DATE-TIME value in + * UTC time. + * + * The optional "COMMENT" property is also allowed for descriptive + * explanatory text. + */ + public function zzztestStandardAndDaylightConformance() { + + // @todo This needs to be done, but it should probably go in its own unit test case + + } + +} +?> \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Database.php b/lib/qCal/tests/UnitTestCase/Database.php new file mode 100644 index 0000000..e145b46 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Database.php @@ -0,0 +1,62 @@ +testpath = TESTFILE_PATH . '/db'; + if (!file_exists($this->testpath)) mkdir($this->testpath, 0777); + $db = $this->testpath . DIRECTORY_SEPARATOR . 'sqlitedb'; + */ + // $this->db = new SQLiteDatabase($this->testpath . DIRECTORY_SEPARATOR . 'sqlitedb'); + $this->db = new SQLiteDatabase(":memory:"); // in-memory database might be the perfect solution :) + $createtable = <<db->queryExec($createtable); + + } + /** + * Delete test files + */ + public function tearDown() { + + $this->db->queryExec("DROP TABLE foo"); + /* + $dir = dir($this->testpath); + while (false !== ($entry = $dir->read())) { + if ($entry != "." && $entry != "..") unlink($this->testpath . DIRECTORY_SEPARATOR . $entry); + } + rmdir($this->testpath); + */ + + } + public function testInitializeDatabase() { + + $this->db->queryExec("INSERT INTO foo (id, bar, baz) VALUES (null, 'baz', 1)"); + $this->db->queryExec("INSERT INTO foo (id, bar, baz) VALUES (null, 'boo', 25)"); + $this->db->queryExec("INSERT INTO foo (id, bar, baz) VALUES (null, 'billowbop', 50)"); + $result = $this->db->query("SELECT * FROM foo"); + $this->assertEqual(count($result->fetchAll(SQLITE_ASSOC)), 3); + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Date.php b/lib/qCal/tests/UnitTestCase/Date.php new file mode 100644 index 0000000..8b2a8e6 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Date.php @@ -0,0 +1,253 @@ +assertEqual($exception->getDate(), $date); + $this->expectException($exception); + throw $exception; + + }*/ + /** + * The object should default to the current date and time + */ + public function testDateDefaultsToNow() { + + $date = new qCal_Date(); + $now = time(); + $this->assertEqual($date->getMonth(), date("m", $now)); + $this->assertEqual($date->getDay(), date("d", $now)); + $this->assertEqual($date->getYear(), date("Y", $now)); + + // make sure that if only a portion of the date is given, the rest default to now + $date2 = new qCal_Date(2006); + $this->assertEqual($date2->getMonth(), date("m", $now)); + $this->assertEqual($date2->getDay(), date("d", $now)); + $this->assertEqual($date2->getYear(), "2006"); + + $date3 = new qCal_Date(2006, 5); + $this->assertEqual($date3->getDay(), date("d", $now)); + $this->assertEqual($date3->getMonth(), "5"); + $this->assertEqual($date3->getYear(), "2006"); + + } + + public function testInvalidDateThrowsException() { + + $this->expectException(new qCal_DateTime_Exception_InvalidDate("Invalid date specified for qCal_Date: \"1/35/2009\"")); + $date = new qCal_Date(2009, 1, 35); + + } + /** + * The same instantiation as was done in the test above will not throw an exception, but instead + * will just roll over the extra four days into the next month if you specify the fourth argument as true + */ + public function testDateRollover() { + + $date = new qCal_Date(2009, 1, 35, true); + $this->assertEqual($date->getMonth(), 2); + $this->assertEqual($date->getDay(), 4); + $this->assertEqual($date->getYear(), 2009); + + // make sure year can roll over too + $date2 = new qCal_Date(2009, 12, 41, true); + $this->assertEqual($date2->getMonth(), 1); + $this->assertEqual($date2->getDay(), 10); + $this->assertEqual($date2->getYear(), 2010); + + } + /** + * @todo I wish I could set the server's date to leap-year so that when the qCal_Date + * class calls time() it would be leap-year. Then I could test this properly... + */ + public function testInvalidLeapYear() { + + // you cannot specify a leap-year for a date that is not a leap-year + $this->expectException(new qCal_DateTime_Exception_InvalidDate("Invalid date specified for qCal_Date: \"2/29/2009\"")); + $date = new qCal_Date(2009, 2, 29); + + } + + public function testSetDateByString() { + + // test that something like "tomorrow" works + $tomorrow = qCal_Date::factory("tomorrow"); + // coming soon! + // $this->assertEqual($tomorrow->getDay()); + + } + /** + * Calling __toString will output the date in the format specified by calling setFormat() + * You can use any of the date-related meta characters from php's date() function. Time-related + * formatting will not work. + */ + public function testToString() { + + $date = new qCal_Date(2009, 12, 7); + // format defaults to m/d/Y + $this->assertEqual($date->__toString(), '12/07/2009'); + // european format + $date->setFormat('d/m/Y'); + $this->assertEqual($date->__toString(), '07/12/2009'); + // no leading zeros + $date->setFormat('n/j/Y'); + $this->assertEqual($date->__toString(), '12/7/2009'); + // two-digit year + $date->setFormat('n/j/y'); + $this->assertEqual($date->__toString(), '12/7/09'); + + // time-related date meta-characters do not work + $date->setFormat('m/d/Y h:i:sa'); + $this->assertEqual($date->__toString(), '12/07/2009 h:i:sa'); + + // you can escape meta-characters with a backslash + $date->setFormat('\m\d\ymdy'); + $this->assertEqual($date->__toString(), 'mdy120709'); + + } + /** + * The date object has many getters which allow for you to determine things like day of the week, + * day of the year, etc. The following tests those getters. + */ + public function testGetters() { + + $date = new qCal_Date(2009, 4, 23); + + /** + * Month + */ + $this->assertEqual($date->getMonth(), 4); + $this->assertEqual($date->getMonthName(), "April"); + $this->assertEqual($date->getNumDaysInMonth(), 30); + + /** + * Day + */ + $this->assertEqual($date->getDay(), 23); + $this->assertEqual($date->getYearDay(), 112); + $this->assertEqual($date->getFirstDayOfMonth()->__toString(), "04/01/2009"); + $this->assertEqual($date->getFirstDayOfMonth()->format("l"), "Wednesday"); + $this->assertEqual($date->getLastDayOfMonth()->__toString(), "04/30/2009"); + $this->assertEqual($date->getLastDayOfMonth()->format("l"), "Thursday"); + // find the xth weekday (mon-sun) of the month + $this->assertEqual($date->getXthWeekdayOfMonth(2)->__toString(), "04/09/2009"); // find the second Thursday of the month (Because 4/23/2009 was on a Thursday, the weekday defaults to that. The year defaults to 2009 for basically the same reason) + $this->assertEqual($date->getXthWeekdayOfMonth(2, "Monday")->__toString(), "04/13/2009"); // find the second monday of the month (month defaults to april because that's what $date is currently set to) + $this->assertEqual($date->getXthWeekdayOfMonth(2, "Monday", "January")->__toString(), "01/12/2009"); // find the second monday in January (year defaults to 2009) + $this->assertEqual($date->getXthWeekdayOfMonth(2, "Monday", "January", 2008)->__toString(), "01/14/2008"); // find the second Monday in January, 2008 + // now try negatives and positives + $this->assertEqual($date->getXthWeekdayOfMonth(-2)->__toString(), "04/23/2009"); // get the second to last Thursday of the month + $this->assertEqual($date->getXthWeekdayOfMonth("-2")->__toString(), "04/23/2009"); // get the second to last Thursday of the month + $this->assertEqual($date->getXthWeekdayOfMonth(+2)->__toString(), "04/09/2009"); // surprisingly, this works... interesting... + $this->assertEqual($date->getXthWeekdayOfMonth("+2")->__toString(), "04/09/2009"); + // we can also use numbers instead of spelling out the names of weekdays and months. For the weekday part, use 0 for Sunday through 6 for Saturday (the same as PHP's date function's "w" metacharacter) + $this->assertEqual($date->getXthWeekdayOfMonth(2, 1)->__toString(), "04/13/2009"); // second monday + $this->assertEqual($date->getXthWeekdayOfMonth(2, 1, 1)->__toString(), "01/12/2009"); // second monday in january + $this->assertEqual($date->getXthWeekdayOfMonth(-2, 1)->__toString(), "04/20/2009"); // second to last monday in april + + + /** + * Year + */ + $this->assertEqual($date->getYear(), 2009); + + /** + * Week + */ + $this->assertEqual($date->getWeekDay(), 4); + $this->assertEqual($date->getWeekDayName(), "Thursday"); + $this->assertEqual($date->getWeekOfYear(), 17); + + /** + * Unix Timestamp + */ + $this->assertEqual($date->getUnixTimestamp(), gmmktime(0, 0, 0, 4, 23, 2009)); + + } + /** + * Test that an exception is thrown if there is a request for an non-existant weekday in the month + */ + public function testInvalidXthWeekday() { + + $this->expectException(new qCal_DateTime_Exception_InvalidDate("You have specified an incorrect number of days for qCal_Date::getXthWeekdayOfMonth()")); + $date = new qCal_Date(2010, 1, 1); + $tenth_tuesday = $date->getXthWeekdayOfMonth(10, "Tuesday"); + + + } + /** + * Any of the setters in the object will return the object itself + */ + public function testFluidMethods() { + + $date = new qCal_Date(2009, 12, 17); + $this->assertEqual($date->setFormat("m/d/Y")->__toString(), "12/17/2009"); + + } + + /** + * Test that date can determine if it is in a leap year + */ + public function testIsLeapyear() { + + $date = new qCal_Date(2009, 4, 23); + $this->assertFalse($date->isLeapYear()); + $date2 = new qCal_Date(2008, 4, 23); + $this->assertTrue($date2->isLeapYear()); + + } + + /** + * Test that you can determine the amount of days in the year (usually 365, but 366 on leap-year) + */ + public function testNumDaysInYear() { + + $date = new qCal_Date(2009, 4, 23); + $this->assertEqual($date->getNumDaysInYear(), 365); + $date2 = new qCal_Date(2008, 4, 23); + $this->assertTrue($date2->getNumDaysInYear(), 366); + + } + + /** + * The following are methods that test the date component's ability to do "date magic". + * It tests things such as the date component's ability to determine if this is the 2nd + * monday of the month, or the 2nd to last monday of the month. Or how many days from the + * end of the year it is. Or whether it is the 2nd Tuesday of the year. Or whether + */ + + /** + * This method tests that the date component is capable of determining of the date is the + * Xth Xday of the month. For instance, it can determine if this date is the third Sunday + * of the month or the second to last Tuesday of the month. + * + * Internally, maybe the date component should do some of this kind of stuff and cache it + * so that it doesn't have to actually do anything when a method like this is called. + */ + /*public function test_Is_Xth_Xday_Of_The_Month() { + + $date = new qCal_Date(2009, 12, 15); + $this->assertTrue($date->isXthWeekdayOfMonth("Tuesday", 3)); + $this->assertTrue($date->isXthWeekdayOfMonth("Tuesday", -3)); + + }*/ + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/DateTime.php b/lib/qCal/tests/UnitTestCase/DateTime.php new file mode 100644 index 0000000..91b7283 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/DateTime.php @@ -0,0 +1,134 @@ +assertEqual($datetime->getDate()->getYear(), $year); + $this->assertEqual($datetime->getDate()->getMonth(), $month); + $this->assertEqual($datetime->getDate()->getDay(), $day); + $this->assertEqual($datetime->getTime()->getHour(), $hour); + $this->assertEqual($datetime->getTime()->getMinute(), $minute); + $this->assertEqual($datetime->getTime()->getSecond(), $second); + $this->assertEqual($datetime->getTime()->getTimezone()->getName(), $timezone); + + } + /** + * Test the factory method + */ + public function testFactoryMethod() { + + $datetime = qCal_DateTime::factory("03/20/1990 10:00:00pm"); + $this->assertEqual($datetime->getDate()->getYear(), 1990); + $this->assertEqual($datetime->getDate()->getMonth(), 3); + $this->assertEqual($datetime->getDate()->getDay(), 20); + $this->assertEqual($datetime->getTime()->getHour(), 22); + $this->assertEqual($datetime->getTime()->getMinute(), 0); + $this->assertEqual($datetime->getTime()->getSecond(), 0); + + } + /** + * Factory should accept unix timestamps + FIGURE OUT HOW TIMEZONES SHOULD WORK HERE... + + public function testFactoryAcceptsUnixTimestamps() { + + $dt = qCal_DateTime::factory(1262603182, "GMT"); + $this->assertEqual($dt->__toString(), "2010-01-04T11:06:22"); + + $dt2 = qCal_DateTime::factory("1262603182", "GMT"); + $this->assertEqual($dt2->__toString(), "2010-01-04T11:06:22"); + + $dt3 = qCal_DateTime::factory("1262603182", "America/Los_Angeles"); + pre(date("m-d-Y H:i:s", 1262603182)); + //$this->assertEqual($dt3->__toString(), "2010-01-04T03:06:22"); + + } + */ + /** + * Test that date/time can be converted to timestamp + */ + public function testTimestampConversion() { + + $datetime = qCal_DateTime::factory("03/20/1993 01:00:00pm", "America/Los_Angeles"); + // this will result in the time specified above plus 8 hours because the tz offset is -8 + $this->assertEqual(gmdate("g:i:sa", $datetime->getUnixTimestamp(true)), "9:00:00pm"); + // this will result in the time specified above + $this->assertEqual(gmdate("g:i:sa", $datetime->getUnixTimestamp(false)), "1:00:00pm"); + + $defaultTz = date_default_timezone_get(); + + date_default_timezone_set("America/Los_Angeles"); + // this will result in the time specified above because date adjusts the timestamp that is returned + $this->assertEqual(date("g:i", $datetime->getUnixTimestamp()), "1:00"); + + date_default_timezone_set("GMT"); + $this->assertEqual(date("H:i", $datetime->getUnixTimestamp()), "21:00"); + + date_default_timezone_set($defaultTz); + + } + /** + * Test string output + */ + public function testStringOutput() { + + $dt = new qCal_DateTime(2000, 10, 1, 5, 0, 0, "America/Los_Angeles"); + $this->assertEqual($dt->__toString(), "2000-10-01T05:00:00-08:00"); + + } + /** + * Test that format method allows date() function's meta-characters + */ + public function testDateTimeFormat() { + + $dt = new qCal_DateTime(2000, 10, 1, 5, 0, 0); + $this->assertEqual($dt->format("m/d/Y H:i:s"), "10/01/2000 05:00:00"); + + } + /** + * Test conversion to UTC + * @todo The entire process for UTC conversion is hacky at best. Fix it up in the next release. + */ + public function testUTCConversion() { + + $datetime = qCal_DateTime::factory("2/22/1988 5:52am", "America/Denver"); // February 22, 1988 at 5:52am Mountain Standard Time (-7 hours) + // UTC is GMT time, which means that the result of this should be the time specified plus seven hours + $this->assertEqual($datetime->getUtc(), "19880222T125200Z"); + + } + /** + * @todo I need to add a test so that something like "19970101T180000Z", when passed to qCal_DateTime::factory() + * knows to assign the GMT timezone to it because the "Z" stands for UTC + */ + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Parser.php b/lib/qCal/tests/UnitTestCase/Parser.php new file mode 100644 index 0000000..f07c1db --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Parser.php @@ -0,0 +1,313 @@ +testpath = TESTFILE_PATH . DIRECTORY_SEPARATOR . 'testpath'; + if (!file_exists($this->testpath)) mkdir($this->testpath, 0777); + else { + @chmod($this->testpath, 0777); + } + $file = $this->testpath . DIRECTORY_SEPARATOR . 'foo.ics'; + $content = file_get_contents(TESTFILE_PATH . DIRECTORY_SEPARATOR . 'simple.ics'); + file_put_contents($file, $content); + + } + /** + * Delete test files + */ + public function tearDown() { + + $dir = dir($this->testpath); + if (is_resource($dir->handle)) { + while (false !== ($entry = $dir->read())) { + if ($entry != "." && $entry != "..") { + @chmod($this->testpath . DIRECTORY_SEPARATOR . $entry, 0777); + @unlink($this->testpath . DIRECTORY_SEPARATOR . $entry); + } + } + @rmdir($this->testpath); + } + + } + public function testParserAcceptsRawData() { + + $data = file_get_contents(TESTFILE_PATH . DIRECTORY_SEPARATOR . 'simple.ics'); + $parser = new qCal_Parser(array( + // options! + )); + $ical = $parser->parse($data); + $this->assertIsA($ical, 'qCal_Component_Vcalendar'); + + } + /** + * Parses a file + */ + public function testParserAcceptsFilename() { + + $parser = new qCal_Parser(array( + // options! + )); + $ical = $parser->parseFile(TESTFILE_PATH . DIRECTORY_SEPARATOR . 'simple.ics'); + $this->assertIsA($ical, 'qCal_Component_Vcalendar'); + + } + /** + * The parser accepts an array of options in its constructor. One option is "searchpath", + * which, if provided, will be used to search for the file that is provided in parseFile() + * If no searchpath is provided, it will use the include path. Paths should be separated by + * PATH_SEPARATOR. + * @todo I think I want to change this to "search_path" instead of "searchpath" + */ + public function testParserAcceptsSearchPath() { + + $paths = array($this->testpath); + $parser = new qCal_Parser(array( + 'searchpath' => implode(PATH_SEPARATOR, $paths) + )); + $ical = $parser->parseFile('foo.ics'); + $this->assertIsA($ical, 'qCal_Component_Vcalendar'); + + } + public function testFileNotFound() { + + $filename = "foobar.ics"; + $this->expectException(new qCal_Exception_FileNotFound('File cannot be found: "' . $filename . '"')); + $parser = new qCal_Parser(); + $parser->parseFile($filename); + + } + + /** + * Tell the parser to ignore validation errors (things like valarm missing its action property) + * @todo I will implement this if there is a need for it. + public function testIgnoreValidationErrors() { + + $invalidcal = << 'off' + )); + $ical = $parser->parse($invalidcal); + + } + */ + /** + * Test that it is possible to not use property defaults. For instance, CALSCALE defaults to "GREGORIAN", but if + * defaults are turned off, it shouldn't default to anything. + * @todo I will implement this if there is a need for it + + public function testNoDefaults() { + + } + */ + public function testInitParser() { + + $parser = new qCal_Parser(array( + + )); + // $ical = $parser->parse(TESTFILE_PATH . '/lvisinoni.ics'); + // pre($ical->render()); + + } + + /** + public function testParserNoValidationOption() { + + $parser = new qCal_Parser(array( + 'validation' => 'off' + )); + $calendarcontent = <<parse($calendarcontent); + + } + **/ + + + + + + + + + + + + + + + + + + + + + + + public function hideOldCode() { + + // I hid the old test code for now, I'll add it back later + $oldcode = <<parse($data); + $this->assertIsA($ical, 'qCal_Component'); + + } + + public function NOSHOWtestParser() { + + $parser = new qCal_Parser_iCal('./files/simple.ics'); + $calendar = $parser->parse(); // now we have an iterable collection of event, todo, etc objects in $calendar + + } + + public function NOSHOWtestGenerateCalendar() { + + $calendar = qCal::factory(); // generate a calendar object + $calendar->attach(new qCal_Component_Event); // add an event + $calendar->attach(new qCal_Component_Journal); // add a journal + $todo = new qCal_Component_Todo(); // create a todo + // this is a facade + // what it does is call: + /** + * $summary = new qCal_Property_Summary('I eat peas'); + * $todo->addProperty($summary); + * // we may also need a + */ + $todo->summary('I eat peas'); // summarize todo + $calendar->attach($todo); // add a todo to calendar + $calendar->prodId('//this is cool//'); // give the calendar a product id + $calendar->version('2.1'); // tell calendar what version it is + $event = new qCal_Component_Event; // create a new event + $event->start('3-11-2009 9:00'); // starts on march 3 09 + $event->end('3-11-2009 12:00'); // ends at 12 same day + /** + * qCal_Date_Rule represents a series of dates. It is used to define event recurrence, + * dates to filter by (with qCal_Filter), and probably other things in the future, and may + * be useful elsewhere than this library. + * @todo compe up with a better name for it + */ + $rule = new qCal_Date_Rule(); // create a date recurrence rule + $rule->until(2010); // will recur until 2010 + //$rule->count(55); // will occur 55 times + $rule->exclude('11-3-2009','2011'); // exclude nov 3 2009 and all of 2011 + $rule->include('11-11-2009'); + $rule->frequency('weekly'); + $rule->interval('2'); // every other week + $rule->byDay('TU','TH'); // on tuesdays and thursdays + // $rule->by + $event->recurs($rule); // apply this rule to the recur + + } + + public function NOSHOWtestDateRecurrence() { + + $pattern = new DatePattern(); + $pattern->until(1995); + // count() and until() cannot both be used + // $pattern->count(50); // repeat pattern 50 times + $pattern->frequency('yearly'); + $pattern->byMonth(4); + $pattern->byMonthWeek(3); + $pattern->byDay('tuesday'); + + // accepts either a date (11/5/2001), a date range (1992-1993) or another DatePattern object + // may not be possible to allow include() to include a pattern... not sure... find out. + $pattern->except($except); + $pattern->include($include); + + } + + public function NOSHOWtestFilterCalendar() { + + $calendar = qCal::import('calendar.ics'); // imports calendar information from calendar file + $filter = new qCal_Filter(); + $dates = new qCal_Date_Rule(); + /** + * I'm not sure if this will all work - I need to play around with it and see if this interface + * is possible - I think it will work as long as include/exclude are evaluated separate from the recurrence type stuff below + */ + $dates->includeRange(2007, 2009); // grab dates from 2007-2009 + $dates->include('2008', '11-23-2006'); // include all of 08 and nov 11 of 2006 + $dates->excludeRange('9-2006','10-2006'); // exclude september to october 2006 + $dates->exclude('4-23-2006', 'april 2007'); // exclude april 23 2006 and all of april 2007 + /** + * Recurrence rules can be used as well (hopefully) + */ + $dates->frequency('monthly'); // monthly + $dates->interval(2); // every other month + $dates->byMonthDay(2,3,5,7); // on the 2nd, 3rd, 4th, and 7th + $filter->add($dates); + /** + * Can also filter by type (this is a facade that in the background would instantiate qCal_Filter_ByType + * and pass the second arg to the constructor) + */ + $filter->add('ByType', array('VEVENT','VTODO','VJOURNAL')); + $components = $filter->filter($calendar); // returns matches + + } + + /** + * Property names, parameter names and enumerated parameter values are + * case insensitive. For example, the property name "DUE" is the same as + * "due" and "Due", DTSTART;TZID=US-Eastern:19980714T120000 is the same + * as DtStart;TzID=US-Eastern:19980714T120000. + */ + public function testPropertyNamesAndParamValuesAreCaseInsensitive() { + + + + } +OLDCODE; + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Property.php b/lib/qCal/tests/UnitTestCase/Property.php new file mode 100644 index 0000000..0761301 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Property.php @@ -0,0 +1,33 @@ + 'end')); + $this->assertEqual($property->getParam('related'), 'end'); + + } + + /** + * Test that passing in the VALUE parameter effectively changes the type + */ + public function testValueParamChangesPropertyType() { + + $property = new qCal_Property_Attach("SOME DATA"); + $this->assertEqual($property->getType(), "URI"); + $property->setParam("value", "binary"); + $this->assertEqual($property->getType(), "BINARY"); + $property = new qCal_Property_Attach("SOME DATA", array('value' => 'binary')); + $this->assertEqual($property->getType(), "BINARY"); + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Recur.php b/lib/qCal/tests/UnitTestCase/Recur.php new file mode 100644 index 0000000..f00ef97 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Recur.php @@ -0,0 +1,353 @@ +interval(1); + $this->expectException(new qCal_DateTime_Exception_InvalidRecur('Start date must come before end date')); + $dates = $recur->getRecurrences('08/01/2009', '07/01/2009'); + + } + + public function testGetInstancesRequiresInterval() { + + $recur = new qCal_DateTime_Recur_Yearly; + $this->expectException(new qCal_DateTime_Exception_InvalidRecur('You must specify an interval')); + $dates = $recur->getRecurrences('08/01/2009', '09/01/2009'); + + } + + public function testGetters() { + + $recur = new qCal_DateTime_Recur_Yearly; + $recur->count(10) + ->byMonth(2) + ->byDay('TU'); + $this->assertEqual($recur->count(), 10); + $this->assertEqual($recur->byMonth(), array(2)); + $this->assertEqual($recur->byDay(), array('TU')); + $this->assertEqual($recur->byMonthDay(), null); + + } + + public function testSetWeekworkStart() { + + $recur = new qCal_DateTime_Recur_Minutely; + $recur->wkst('SU'); // set the work week start to Sunday + $this->assertEqual($recur->wkst(), 'SU'); + // invalid work day should throw an exception + $this->expectException(new qCal_DateTime_Exception_InvalidRecur('"FOO" is not a valid week day, must be one of the following: MO, TU, WE, TH, FR, SA, SU')); + $recur->wkst('FOO'); + + } + + public function testCanHaveCountOrUntilButNotBoth() { + + $rule = new qCal_DateTime_Recur_Hourly; + $this->expectException(new qCal_DateTime_Exception_InvalidRecur('A recurrence count and an until date cannot both be specified')); + $rule->count(10) + ->until('02/12/2009'); + + } + + /** + * Within the recur object, we use objects to compare the current date with + * rule modifiers. This tests their functionality. + */ + // public function testLoopHelpers() { + // + // // let's assume we want a recurrence every 30 seconds starting from 01/01/2000 at 12:00am + // // and ending at 01/01/2000 at 12:05:15am + // $sInstances = array(); + // $secondly = new qCal_Date_Recur_Looper_Secondly('01/01/2000 12:00am'); + // while($secondly->onOrBefore('01/01/2000 12:05:15am')) { + // // get current instance + // $sInstances[] = $secondly->getInstance(); + // // increment by 30 seconds + // $secondly->increment(30); + // } + // + // // make sure onOrBefore works right + // $on = new qCal_Date('01/01/2000 12:00am'); + // $after = new qCal_Date('01/01/2000 12:00:01am'); + // $before = new qCal_Date('12/31/1999 11:59:59pm'); + // $sly = new qCal_Date_Recur_Looper_Secondly('01/01/2000 12:00am'); + // $this->assertTrue($sly->onOrBefore($on)); + // $this->assertTrue($sly->onOrBefore($after)); + // $this->assertFalse($sly->onOrBefore($before)); + // + // $this->assertEqual($sInstances, array( + // new qCal_Date('01/01/2000 12:00am'), + // new qCal_Date('01/01/2000 12:00:30am'), + // new qCal_Date('01/01/2000 12:01am'), + // new qCal_Date('01/01/2000 12:01:30am'), + // new qCal_Date('01/01/2000 12:02am'), + // new qCal_Date('01/01/2000 12:02:30am'), + // new qCal_Date('01/01/2000 12:03am'), + // new qCal_Date('01/01/2000 12:03:30am'), + // new qCal_Date('01/01/2000 12:04am'), + // new qCal_Date('01/01/2000 12:04:30am'), + // new qCal_Date('01/01/2000 12:05am'), + // )); + // + // // let's assume we want a recurrence every 20 minutes starting from 01/01/2000 at 12:00am + // // and ending at 01/01/2000 at 3:50:15am + // $minInstances = array(); + // $minutely = new qCal_Date_Recur_Looper_Minutely('01/01/2000 12:00am'); + // while($minutely->onOrBefore('01/01/2000 3:50:15am')) { + // // get current instance + // $minInstances[] = $minutely->getInstance(); + // // increment by 30 seconds + // $minutely->increment(20); + // } + // + // // make sure onOrBefore works right + // $on = new qCal_Date('01/01/2000 12:00:20am'); + // $after = new qCal_Date('01/01/2000 12:02:00am'); + // $before = new qCal_Date('12/31/1999 11:59:59pm'); + // $mly = new qCal_Date_Recur_Looper_Minutely('01/01/2000 12:00am'); + // $this->assertTrue($mly->onOrBefore($on)); + // $this->assertTrue($mly->onOrBefore($after)); + // $this->assertFalse($mly->onOrBefore($before)); + // + // $this->assertEqual($minInstances, array( + // new qCal_Date('01/01/2000 12:00am'), + // new qCal_Date('01/01/2000 12:20am'), + // new qCal_Date('01/01/2000 12:40am'), + // new qCal_Date('01/01/2000 01:00am'), + // new qCal_Date('01/01/2000 01:20am'), + // new qCal_Date('01/01/2000 01:40am'), + // new qCal_Date('01/01/2000 02:00am'), + // new qCal_Date('01/01/2000 02:20am'), + // new qCal_Date('01/01/2000 02:40am'), + // new qCal_Date('01/01/2000 03:00am'), + // new qCal_Date('01/01/2000 03:20am'), + // new qCal_Date('01/01/2000 03:40am'), + // )); + // + // + // // let's assume we want a recurrence every other hour starting from 01/01/2000 at 12:00am + // // and ending at 01/01/2000 at 6:00 am + // $hrInstances = array(); + // $hourly = new qCal_Date_Recur_Looper_Hourly('01/01/2000 12:00am'); + // while($hourly->onOrBefore('01/01/2000 6:00am')) { + // // get current instance + // $hrInstances[] = $hourly->getInstance(); + // // increment by 2 hours + // $hourly->increment(2); + // } + // + // // make sure onOrBefore works right + // $on = new qCal_Date('01/01/2000 12:45:20am'); + // $after = new qCal_Date('01/01/2000 1:02:00am'); + // $before = new qCal_Date('12/31/1999 11:59:59pm'); + // $hly = new qCal_Date_Recur_Looper_Hourly('01/01/2000 12:00am'); + // $this->assertTrue($hly->onOrBefore($on)); + // $this->assertTrue($hly->onOrBefore($after)); + // $this->assertFalse($hly->onOrBefore($before)); + // + // $this->assertEqual($hrInstances, array( + // new qCal_Date('01/01/2000 12:00am'), + // new qCal_Date('01/01/2000 02:00am'), + // new qCal_Date('01/01/2000 04:00am'), + // new qCal_Date('01/01/2000 06:00am'), + // )); + // + // // let's assume we want a recurrence every three days starting from 01/01/2000 at 9:00am + // // and ending at 01/27/2000 at 6:00 am + // // @todo This kind of presents a problem... daily rules probably shouldn't really have a time component, + // // daily rules should represent whole days rather than days at a certain time. my qCal_Date object requires + // // a time because it just extends the built-in DateTime class. I need to figure out how I want to deal with this. + // $dInstances = array(); + // $daily = new qCal_Date_Recur_Looper_Daily('01/01/2000'); + // while($daily->onOrBefore('01/27/2000')) { + // // get current instance + // $dInstances[] = $daily->getInstance(); + // // increment by 3 days + // $daily->increment(3); + // } + // + // // make sure onOrBefore works right + // $on = new qCal_Date('01/01/2000 12:45:20am'); + // $after = new qCal_Date('01/02/2000 1:02:00am'); + // $before = new qCal_Date('12/31/1999 11:59:59pm'); + // $dly = new qCal_Date_Recur_Looper_Daily('01/01/2000 12:00am'); + // $this->assertTrue($dly->onOrBefore($on)); + // $this->assertTrue($dly->onOrBefore($after)); + // $this->assertFalse($dly->onOrBefore($before)); + // + // $this->assertEqual($dInstances, array( + // new qCal_Date('01/01/2000'), + // new qCal_Date('01/04/2000'), + // new qCal_Date('01/07/2000'), + // new qCal_Date('01/10/2000'), + // new qCal_Date('01/13/2000'), + // new qCal_Date('01/16/2000'), + // new qCal_Date('01/19/2000'), + // new qCal_Date('01/22/2000'), + // new qCal_Date('01/25/2000'), + // )); + // + // // let's assume we want a recurrence every week starting from 01/01/2000 + // // and ending at 02/02/2000 + // // @todo This has the same issues as daily does with times being attached when they shouldn't be + // $wInstances = array(); + // $weekly = new qCal_Date_Recur_Looper_Weekly('01/01/2000'); + // while($weekly->onOrBefore('02/02/2000')) { + // // get current instance + // $wInstances[] = $weekly->getInstance(); + // // increment by a week + // $weekly->increment(1); + // } + // + // // make sure onOrBefore works right + // $on = new qCal_Date('01/03/2000 12:45:20am'); + // $after = new qCal_Date('01/09/2000 1:02:00am'); + // $before = new qCal_Date('12/31/1999 11:59:59pm'); + // $wly = new qCal_Date_Recur_Looper_Weekly('01/01/2000 12:00am'); + // $this->assertTrue($wly->onOrBefore($on)); + // $this->assertTrue($wly->onOrBefore($after)); + // $this->assertFalse($wly->onOrBefore($before)); + // + // $this->assertEqual($wInstances, array( + // new qCal_Date('01/01/2000'), + // new qCal_Date('01/08/2000'), + // new qCal_Date('01/15/2000'), + // new qCal_Date('01/22/2000'), + // new qCal_Date('01/29/2000'), + // )); + // + // // let's assume we want a recurrence every 6 months starting from 01/01/2000 + // // and ending at 02/08/2004 + // // @todo This has the same issues as daily does with times being attached when they shouldn't be + // $mInstances = array(); + // $monthly = new qCal_Date_Recur_Looper_Monthly('01/01/2000'); + // while($monthly->onOrBefore('02/08/2004')) { + // // get current instance + // $mInstances[] = $monthly->getInstance(); + // // increment by 6 months + // $monthly->increment(6); + // } + // + // // make sure onOrBefore works right + // $on = new qCal_Date('01/03/2000 12:45:20am'); + // $after = new qCal_Date('02/01/2000 1:02:00am'); + // $before = new qCal_Date('12/31/1999 11:59:59pm'); + // $mly = new qCal_Date_Recur_Looper_Monthly('01/01/2000 12:00am'); + // $this->assertTrue($mly->onOrBefore($on)); + // $this->assertTrue($mly->onOrBefore($after)); + // $this->assertFalse($mly->onOrBefore($before)); + // + // $this->assertEqual($mInstances, array( + // new qCal_Date('01/01/2000'), + // new qCal_Date('07/01/2000'), + // new qCal_Date('01/01/2001'), + // new qCal_Date('07/01/2001'), + // new qCal_Date('01/01/2002'), + // new qCal_Date('07/01/2002'), + // new qCal_Date('01/01/2003'), + // new qCal_Date('07/01/2003'), + // new qCal_Date('01/01/2004'), + // )); + // + // // let's assume we want a recurrence every year starting from 01/01/2000 + // // and ending at 02/08/2004 + // // @todo This has the same issues as daily does with times being attached when they shouldn't be + // $yInstances = array(); + // $yearly = new qCal_Date_Recur_Looper_Yearly('01/01/2000'); + // while($yearly->onOrBefore('02/08/2004')) { + // // get current instance + // $yInstances[] = $yearly->getInstance(); + // // increment by 6 months + // $yearly->increment(1); + // } + // + // // make sure onOrBefore works right + // $on = new qCal_Date('01/03/2000 12:45:20am'); + // $after = new qCal_Date('02/01/2000 1:02:00am'); + // $before = new qCal_Date('12/31/1999 11:59:59pm'); + // $yly = new qCal_Date_Recur_Looper_Yearly('01/01/2000 12:00am'); + // $this->assertTrue($yly->onOrBefore($on)); + // $this->assertTrue($yly->onOrBefore($after)); + // $this->assertFalse($yly->onOrBefore($before)); + // + // $this->assertEqual($yInstances, array( + // new qCal_Date('01/01/2000'), + // new qCal_Date('01/01/2001'), + // new qCal_Date('01/01/2002'), + // new qCal_Date('01/01/2003'), + // new qCal_Date('01/01/2004'), + // )); + // + // } + + public function testLooperFactory() { + + $yearly = qCal_DateTime_Recur::factory('yearly', time()); + $this->assertIsA($yearly, 'qCal_DateTime_Recur_Yearly'); + $monthly = qCal_DateTime_Recur::factory('MonTHLY', time()); + $this->assertIsA($monthly, 'qCal_DateTime_Recur_Monthly'); + $weekly = qCal_DateTime_Recur::factory('WEEKLY', time()); + $this->assertIsA($weekly, 'qCal_DateTime_Recur_Weekly'); + $daily = qCal_DateTime_Recur::factory('Daily', time()); + $this->assertIsA($daily, 'qCal_DateTime_Recur_Daily'); + $hourly = qCal_DateTime_Recur::factory('hourly', time()); + $this->assertIsA($hourly, 'qCal_DateTime_Recur_Hourly'); + $minutely = qCal_DateTime_Recur::factory('minutely', time()); + $this->assertIsA($minutely, 'qCal_DateTime_Recur_Minutely'); + $secondly = qCal_DateTime_Recur::factory('SeCoNdLy', time()); + $this->assertIsA($secondly, 'qCal_DateTime_Recur_Secondly'); + + } + + public function xxxtestBuildRule() { + + $recur = new qCal_DateTime_Recur_Yearly; + $recur->interval(2) // every other year + ->byMonth(array(1,2,3)) // every other year in january, february and march + ->byMonthDay(10) // every 10th of the month + ->byDay(array('1SU', '2TU', 'MO')) + ->byHour(array(8,9)) // every first sunday in january, february, and march of every other year at 8am and 9am + ->byMinute(30); // every last sunday in january, february, and march of every other year at 8:30am and 9:30am + $start = '08/24/1995'; + $end = '08/24/2009'; + + $dates = $recur->getRecurrences($start, $end); + //pr($dates); // should return an array of qCal_Dates that represent every instance in the timespan + foreach ($dates as $date) { + // pr($date->format('r')); + } + + } + + /** + * Let's start with a really simple rule and go from there... + */ + // public function testBuildSimpleRule() { + // + // $rule = new qCal_Date_Recur('daily'); + // $rule->interval(1); + // // should return every day in august + // $dates = $rule->getRecurrences('08/01/2009', '08/31/2009'); + // // pr($dates[30]->format('m/d/Y')); // this is wrong... should not include 9/1/2009 and it does + // $this->assertEqual(count($dates), 31); + // + // } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Renderer.php b/lib/qCal/tests/UnitTestCase/Renderer.php new file mode 100644 index 0000000..42f7bcb --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Renderer.php @@ -0,0 +1,88 @@ +attach(new qCal_Component_Todo()); + $alarm = new qCal_Component_Alarm(array('action' => 'audio', 'trigger' => '15m')); + $todo_w_alarm = new qCal_Component_Todo(); + $todo_w_alarm->setDescription("Remember to eat a pile of bacon soda", array('altrep' => 'This is an alternate representation')); + $todo_w_alarm->setDue("2008-12-25 8:00"); + $todo_w_alarm->attach($alarm); + $calendar->attach($todo_w_alarm); + $ical = $calendar->render(); // can pass it a renderer, otherwise it uses ical format + // pre($ical); + + } + /** + * @todo Remove the binary attach stuff from here and put it in the test below. + */ + public function testLongLinesFolded() { + + $cal = new qCal; + $todo = new qCal_Component_Vtodo(array( + 'description' => 'This is a really long line that will of course need to be folded. I mean, we can\'t just have long lines laying around in an icalendar file. That would be like not ok. So, let\'s find out if this folded properly!', + 'summary' => 'This is a short summary, which I think is like a title', + 'dtstart' => '2008-04-23 1:00am', + )); + $cal->attach($todo); + $lines = explode("\r\n", $cal->render()); + $long = false; + foreach ($lines as $line) { + if (strlen($line) > 76) $long = true; + } + $this->assertFalse($long); + + } + /** + * Test that binary data can be encoded as text and then decoded to be put back together. + */ + public function testBinaryData() { + + $cal = new qCal; + $journal = new qCal_Component_Vjournal(array( + 'description' => 'This is a really long line that will of course need to be folded. I mean, we can\'t just have long lines laying around in an icalendar file. That would be like not ok. So, let\'s find out if this folded properly!', + 'summary' => 'This is a short summary, which I think is like a title', + 'dtstamp' => '2008-04-23 1:00am', + new qCal_Property_Attach(file_get_contents('./files/me.jpg'), array( + 'encoding' => 'base64', + 'fmtype' => 'image/basic', + 'value' => 'binary', + )), + )); + $cal->attach($journal); + $attach = $journal->getProperty('attach'); + $jpg = base64_decode($attach[0]->getValue()); + file_put_contents('./files/me2.jpg', $jpg); + $this->assertEqual(file_get_contents('./files/me2.jpg'), file_get_contents('./files/me.jpg')); + + } + /** + * Test that all of the right characters are escaped when rendered + * @todo Need to make sure that when parsing the escape characters are removed. + */ + public function testCharactersAreEscaped() { + + $journal = new qCal_Component_Vjournal(array( + 'summary' => 'The most interesting, but non-interesting journal entry ever.', + 'description' => 'This is a sentence that ends with a semi-colon, which I\'m not sure needs to be escaped; I will read the RFC a bit and find out what, exactly, needs to escaped. I know commas do though, and this entry has plenty of those.', + 'dtstart' => qCal_DateTime::factory('20090809T113500') + )); + $this->assertEqual($journal->render(), "BEGIN:VJOURNAL\r\nSUMMARY:The most interesting\, but non-interesting journal entry ever.\r\nDESCRIPTION:This is a sentence that ends with a semi-colon\, which I'm not \r\n sure needs to be escaped; I will read the RFC a bit and find out what\, exa\r\n ctly\, needs to escaped. I know commas do though\, and this entry has plent\r\n y of those.\r\nDTSTART:20090809T113500\r\nEND:VJOURNAL\r\n"); + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/SprintOne.php b/lib/qCal/tests/UnitTestCase/SprintOne.php new file mode 100644 index 0000000..472803d --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/SprintOne.php @@ -0,0 +1,106 @@ +assertEqual($date->getWeekDayName(), "Sunday"); + $this->assertEqual($date->getXthWeekdayOfMonth(-1), $date); // last sunday of the month + $this->assertEqual($date->getXthWeekdayOfMonth(4), $date); // fourth sunday of the month + $this->assertNotEqual($date->getXthWeekdayOfMonth(2), $date); // NOT the second Sunday of the month + $this->assertEqual($date->getXthWeekdayOfYear(8), $date); // eighth sunday of the year + $this->assertEqual($date->getXthWeekdayOfYear(-45), $date); + + } + + /** + * How many days from the beginning or end of the month or year it is + * (ie: February 15th is the 46th day of the year and 13 days from the end of the month) + */ + public function testHowManyDaysFromTheBeginningOrEndOfTheMonthOrYearItIs() { + + /** + * Year + */ + // days 'til end of year + $date = new qCal_Date(2010, 1, 15, "GMT"); // mom's birthday! + $this->assertEqual($date->getYearDay(), 14); // year day starts at zero + $this->assertEqual($date->getYearDay(true), 15); // pass true to the method and it will start from one + $this->assertEqual($date->getNumDaysUntilEndOfYear(), 350); + $date2 = new qCal_Date(2010, 12, 25); // jesus's birthday (not really, but w/e)! + $this->assertEqual($date2->getNumDaysUntilEndOfYear(), 6); + + /** + * Month + */ + $date3 = new qCal_Date(2010, 3, 20); // mom and dady's anniversary! + $this->assertEqual($date3->getDay(), 20); // this one is pretty obvious... + $this->assertEqual($date3->getNumDaysUntilEndOfMonth(), 11); + + } + + /** + * How many weeks from the beginning or end of the month or year it is + * (ie: January 3rd is in the first week of the year and 51 weeks from the end of the year) + * @todo This needs to be capable of setting the "week start day", which will have an impact on this method... + */ + public function testHowManyWeeksFromTheBeginningOrEndOfTheMonthOrYearItIs() { + + $date = new qCal_Date(2010, 1, 15); + // $date->setWeekStart("Sunday"); + $this->assertEqual($date->getWeekOfYear(), 2); + $date2 = new qCal_Date(2010, 4, 23); + $this->assertEqual($date2->getWeekOfYear(), 16); + + // how many weeks until the end of the year? + $date3 = new qCal_Date(2010, 12, 1); + $this->assertEqual($date3->getWeeksUntilEndOfYear(), 4); + + } + + /** + * How many months are there left in the year? + */ + public function testHowManyMonthsLeftInTheYear() { + + $date = new qCal_Date(2010, 10, 23); + $this->assertEqual($date->getNumMonthsUntilEndOfYear(), 2); + + } + + /** + * Timezone + */ + + /** + * Certain timezones have daylight savings rules. The timezone component needs to be aware + * of those rules and adjust the time accordingly. + */ + public function testTimezoneIsAwareOfDaylightSavings() { + + // coming soon! + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/SprintTwo.php b/lib/qCal/tests/UnitTestCase/SprintTwo.php new file mode 100644 index 0000000..9eba379 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/SprintTwo.php @@ -0,0 +1,252 @@ +assertEqual($date1->format("F jS, Y"), "January 10th, 2010"); + + $date3 = new qCal_Date(2010, 1, 35, true); // results in February 4th, 2010 because the rollover parameter was set to true + $this->assertEqual($date3->format("F jS, Y"), "February 4th, 2010"); + + $this->expectException(new qCal_DateTime_Exception_InvalidDate('Invalid date specified for qCal_Date: "1/35/2010"')); + $date2 = new qCal_Date(2010, 1, 35); // invalid, and will throw a qCal_DateTime_Exception_InvalidDate exception + + } + + public function testDateConvertToString() { + + $date = new qCal_Date(2010, 1, 10); + $this->assertEqual($date->__toString(), "01/10/2010"); // will output "01/10/2010" + + } + + public function testDateConvertToStringUsingFormatMethod() { + + $date = new qCal_Date(2010, 1, 10); + $date->setFormat("Y"); + //echo $date; // outputs "2010" + + $date->setFormat('l, F \the jS, Y'); + //echo $date; // outputs "Sunday, January the 10th, 2010" + + } + + public function testDateGetXthWeekdayOfMonthExamples() { + + $date = new qCal_Date(2010, 1, 10); // Sunday, January 10th, 2010 + + $first_sunday_in_january = $date->getXthWeekdayOfMonth(1); // will return a qCal_Date object for January 3rd, 2010 + $this->assertEqual($first_sunday_in_january->__toString(), "01/03/2010"); + + $first_tuesday_in_january = $date->getXthWeekdayOfMonth(1, "tuesday"); // will return a qCal_Date object for January 5th, 2010 + $this->assertEqual($first_tuesday_in_january->__toString(), "01/05/2010"); + + $second_to_last_wednesday_in_january = $date->getXthWeekdayOfMonth(-2, "wednesday"); // will return a qCal_Date object for January 20th, 2010 + $this->assertEqual($second_to_last_wednesday_in_january->__toString(), "01/20/2010"); + + $second_monday_in_february = $date->getXthWeekdayOfMonth(2, 1, 2); // will return qCal_Date object for February 8th, 2010 + $this->assertEqual($second_monday_in_february->__toString(), "02/08/2010"); + + $second_monday_in_february = $date->getXthWeekdayOfMonth(2, "monday", "february"); // the previous example could also be done like this + $this->assertEqual($second_monday_in_february->__toString(), "02/08/2010"); + + $last_sunday_in_august = $date->getXthWeekdayOfMonth(-1, "sunday", "august"); // will return qCal_Date object for August 29th, 2010 + $this->assertEqual($last_sunday_in_august->__toString(), "08/29/2010"); + + $last_sunday_in_december_2011 = $date->getXthWeekdayOfMonth(-1, "sunday", "december", 2011); // will return qCal_Date object for December 25th, 2011 + $this->assertEqual($last_sunday_in_december_2011->__toString(), "12/25/2011"); + + $first_sunday_in_january_2012 = $date->getXthWeekdayOfMonth(1, 0, 1, 2012); // will return qCal_Date object for January 1st, 2012 + $this->assertEqual($first_sunday_in_january_2012->__toString(), "01/01/2012"); + + } + + public function testDateGetXthWeekdayOfYearExamples() { + + $date = new qCal_Date(2010, 1, 10); // Sunday, January 10th, 2010 + + $first_sunday_of_year = $date->getXthWeekdayOfYear(1); // will return a qCal_Date object for January 3rd, 2010 + $this->assertEqual($first_sunday_of_year->__toString(), "01/03/2010"); + + $first_monday_of_the_year = $date->getXthWeekdayOfYear(1, "monday"); // will return a qCal_Date object for January 4th, 2010 + $this->assertEqual($first_monday_of_the_year->__toString(), "01/04/2010"); + + $last_monday_of_the_year = $date->getXthWeekdayOfYear(-1, "monday"); // will return a qCal_Date object for December 27th, 2010 + $this->assertEqual($last_monday_of_the_year->__toString(), "12/27/2010"); + + $thirtieth_sunday_of_the_year = $date->getXthWeekdayOfYear(30, 0); // will return a qCal_Date object for July 25th, 2010 + $this->assertEqual($thirtieth_sunday_of_the_year->__toString(), "07/25/2010"); + + $thirtieth_sunday_of_the_year = $date->getXthWeekdayOfYear(30, "sunday"); // the previous example could also be done like this + $this->assertEqual($thirtieth_sunday_of_the_year->__toString(), "07/25/2010"); + + $tenth_monday_of_2012 = $date->getXthWeekdayOfYear(10, "monday", 2012); // will return a qCal_Date object for March 5th, 2012 + $this->assertEqual($tenth_monday_of_2012->__toString(), "03/05/2012"); + + } + + public function testGetFirstAndLastDayOfMonth() { + + $date = new qCal_Date(2010, 1, 10); + $first = $date->getFirstDayOfMonth(); // will return a qCal_Date object for January 1st, 2010 + $this->assertEqual($first->__toString(), "01/01/2010"); + + $date = new qCal_Date(2010, 1, 10); + $last = $date->getLastDayOfMonth(); // will return a qCal_Date object for January 31st, 2010 + $this->assertEqual($last->__toString(), "01/31/2010"); + + } + + /** + * Test qCal_Time examples + */ + + public function testTimeConstructor() { + + $time = new qCal_Time(10, 30, 0, "GMT"); // will result in an object for 10:30:00am GMT + $this->assertEqual($time->__toString(), "10:30:00"); + $this->assertEqual($time->getTimezone()->getName(), "GMT"); + + $time = new qCal_Time(23, 0, 0, new qCal_Timezone("Custom_Timezone", -3600)); // will result in an object for 11:00:00pm with a custom timezone + $this->assertEqual($time->__toString(), "23:00:00"); + $this->assertEqual($time->getTimezone()->getName(), "Custom_Timezone"); + + $time = new qCal_Time(5, 70, 0, null, true); // will result in 6:10:00 using the server's default timezone + $this->assertEqual($time->__toString(), "06:10:00"); + $this->assertEqual($time->getTimezone()->getName(), date_default_timezone_get()); + + } + + public function testTimeFactory() { + + $time1 = qCal_Time::factory("4:00"); // will result in 4:00am using the server's timezone + $this->assertEqual($time1->__toString(), "04:00:00"); + + $time2 = qCal_Time::factory("tomorrow", "GMT"); // will result in whatever tomorrow's date is in GMT + $time3 = qCal_Time::factory("now", "America/Los_Angeles"); // will result in the current time in America/Los_Angeles + + } + + public function testTimeToString() { + + $time = new qCal_Time(4, 0, 0); + $this->assertEqual($time->__toString(), "04:00:00"); // will output "04:00:00" + + $time = new qCal_Time(15, 30, 30); + $time->setFormat("g:ia"); + $this->assertEqual($time->__toString(), "3:30pm"); // outputs "3:30pm" + + $time->setFormat("H"); + $this->assertEqual($time->__toString(), "15"); // outputs "15" + + $time = new qCal_Time(6, 30, 0); + $string = $time->format("H:i"); + $this->assertEqual($time->__toString(), "06:30:00"); // still outputs "06:30:00" because we did not call setFormat() + $this->assertEqual($string, "06:30"); // outputs "06:30" + + } + + /** + * Test qCal_DateTime examples + */ + + public function testDateTimeFactory() { + + $datetime1 = qCal_DateTime::factory("January 21st, 2010 3pm", "GMT"); + $this->assertEqual($datetime1->__toString(), "2010-01-21T15:00:00+00:00"); + + // this is not very easy to test... + // $datetime2 = qCal_DateTime::factory("noon tomorrow"); // will result in whatever tomorrow's date is and at noon + + // nor is this + // $datetime3 = qCal_DateTime::factory("now"); // will result in today's date and time + + // nor is this + // $datetime4 = qCal_DateTime::factory(time()); // will result in today's date and time + + $datetime5 = qCal_DateTime::factory("October 1st, 2009 9am", "America/Los_Angeles"); // will result in October 1st, 2009 at 9am in America/Los_Angeles time + $this->assertEqual($datetime5->__toString(), "2009-10-01T09:00:00-08:00"); + + } + + public function testDateTimeConvertToString() { + + $datetime = new qCal_DateTime(2010, 1, 12, 4, 30, 0, "America/Los_Angeles"); + $this->assertEqual($datetime->__toString(), "2010-01-12T04:30:00-08:00"); // will output "2010-01-12T04:00:00-08:00" + + $datetime = new qCal_DateTime(2010, 12, 10, 15, 30, 0, "GMT"); + $datetime->setFormat('m/d/Y \a\t g:ia'); + $this->assertEqual($datetime->__toString(), "12/10/2010 at 3:30pm"); // outputs "12/10/2010 at 3:30pm" + + $datetime->setFormat("H"); + $this->assertEqual($datetime->__toString(), "15"); // outputs "15" + + $datetime = new qCal_DateTime(2010, 11, 10, 6, 30, 0, "GMT"); + $string = $datetime->format("H:i"); + $this->assertEqual($datetime->__toString(), "2010-11-10T06:30:00+00:00"); // still outputs "2010-11-10T06:30:00+00:00" because we did not call setFormat() + $this->assertEqual($string, "06:30"); // outputs "06:30" + + } + + public function testGetUtc() { + + $datetime = new qCal_DateTime(2009, 10, 31, 10, 30, 0, "America/Los_Angeles"); + $this->assertEqual($datetime->getUtc(), "20091031T183000Z"); + $this->assertEqual($datetime->getUtc(true), "2009-10-31T18:30:00Z"); + + } + + /** + * Test qCal_Timezone examples + */ + + public function testFactoryExamples() { + + $gmt = qCal_Timezone::factory("GMT"); // will result in Greenwich Mean Time + $this->assertEqual($gmt->getOffsetSeconds(), 0); + + $la = qCal_Timezone::factory("America/Los_Angeles"); // will result in timezone for America/Los_Angeles, which is eight hours behind UTC + $this->assertEqual($la->getOffsetSeconds(), -28800); + + $eastern = qCal_Timezone::factory("US/Eastern"); // will result in the timezone for Eastern Standard Time, which is five hours behind UTC + $this->assertEqual($eastern->getOffsetSeconds(), (3600 * -5)); + + $defaultTZ = qCal_Timezone::factory(); // will result in whatever timezone your server is currently set to + $dfOffset = date("Z"); + $this->assertEqual($gmt->getOffsetSeconds(), $dfOffset); + + } + + public function testTimezoneToStringOutput() { + + $tz = new qCal_Timezone("America/Los_Angeles", -28800, "PST"); + $tz->setFormat("T P"); + $this->assertEqual($tz->__toString(), "PST -08:00"); + + $tz->setFormat("Z"); + $this->assertEqual($tz->__toString(), "-28800"); + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Time.php b/lib/qCal/tests/UnitTestCase/Time.php new file mode 100644 index 0000000..10b33e0 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Time.php @@ -0,0 +1,204 @@ +format("H:i:s")); + // pr($time->getHour()); + // + // $time->setTimezone("America/Los_Angeles"); + // pr($time->format("H:i:s")); + // pr($time->getHour()); + + } + /** + * Test that when you instantiate a time, it takes the timezone into account + * when creating the timestamp. + */ + public function testTimezoneAffectsTimestamp() { + + $defaultTz = date_default_timezone_get(); + // the timestamp for this should be able to be passed to date() + // when the server's timezone is set to the same timezone + // and return 10:30 + $time = new qCal_Time(10, 30, 0, "America/Los_Angeles"); + date_default_timezone_set("America/Los_Angeles"); + $this->assertEqual($time->getHour(), "10"); + + // gmdate() doesn't apply any offset to the timestamp, so I use it here so that + // we don't have to worry about its effect on the timestamp + // getTimestamp() should return a timestamp that is GMT 10:30 + 8 hours + // because then if you call date() with the server's timezone set to -8 hours, it will be correct + $this->assertEqual(gmdate("H:i", $time->getTimestamp()), "18:30"); + $this->assertEqual(date("H:i", $time->getTimestamp()), "10:30"); + + // date() subtracts 8 hours from the timestamp + // getTimestamp(false) returns the timestamp without any adjustment (so 10:30 GMT) + // so calling date() with getTimestamp(false) should result in the time minus 8 hours + // calling gmdate() with getTimestamp(false) should result in the time without any adjustment + $this->assertEqual(date("H:i", $time->getTimestamp(false)), "02:30"); + $this->assertEqual(gmdate("H:i", $time->getTimestamp(false)), "10:30"); + + date_default_timezone_set("GMT"); + $time = new qCal_Time(12, 45, 0, "GMT"); + $this->assertEqual(date("H:i", $time->getTimestamp(true)), "12:45"); + $this->assertEqual(date("H:i", $time->getTimestamp(false)), "12:45"); + + date_default_timezone_set($defaultTz); + + } + /** + * Test that timezone defaults to server timezone + */ + public function testTimezoneDefault() { + + $time = new qCal_Time(0, 0, 0); + $this->assertEqual($time->getTimezone()->getName(), date_default_timezone_get()); + + } + /** + * Setting the timezone adjusts the offset used when calculating the timestamp + */ + public function testSetTimezone() { + + $time = new qCal_Time(0, 0, 0, "GMT"); + $this->assertEqual($time->getHour(), 0); + $this->assertEqual($time->getTimezone()->getOffsetSeconds(), 0); + $this->assertEqual($time->getTimestamp(), 0); + $this->assertEqual($time->getTimestamp(true), 0); + + } + /** + * Because PHP stores the time as how many seconds since unix epoch, we cannot simply create a + * time component without a date attached to it. We MUST have a date attached to it. To make things + * simple, we store the time as how many seconds since start of unix epoch. That way it is like + * it is how many seconds since the start of the day, which is close to storing time without a date + */ + public function testTimestampIsHowManySecondsSinceSecondZeroOfToday() { + + $today = strtotime(date("Y/m/d")); + $now = strtotime(date("Y/m/d G:i:s")); + $nowhour = date("G", $now); + $nowminute = date("i", $now); + $nowsecond = date("s", $now); + $diff = $now - $today; + $time = new qCal_Time($nowhour, $nowminute, $nowsecond, qCal_Timezone::factory("GMT")); + $this->assertEqual($time->getTimestamp(), $diff); + + } + /** + * All of PHP's date function's time-related meta-characters should work with this class + * Any of the other meta-characters defined for date() do not work. + */ + public function testFormatDateMetacharacters() { + + $time = new qCal_Time(4, 20, 0, "GMT"); + $this->assertEqual($time->__toString(), "04:20:00"); + $this->assertEqual($time->format("g:ia"), "4:20am"); + + } + /** + * Test that setting the format causes __toString to use that format thereafter + */ + public function testSetFormat() { + + $time = new qCal_Time(21, 15, 0, "GMT"); + $time->setFormat("g:i:sa"); + $this->assertEqual($time->__toString(), "9:15:00pm"); + + } + /** + * Time rolls over similar to how qCal_Date rolls over, but it is off by default + */ + public function testTimeRolloverException() { + + $this->expectException(new qCal_DateTime_Exception_InvalidTime("Invalid time specified for qCal_Time: \"01:01:100\"")); + $time = new qCal_Time(1, 1, 100); // should rollover to 1:02:40, but doesn't because rollover is off by default + + } + /** + * Time rolls over similar to how qCal_Date rolls over + */ + public function testTimeRollover() { + + $time = new qCal_Time(1, 1, 100, qCal_Timezone::factory("GMT"), true); // should rollover to 1:02:40 + $this->assertEqual($time->getTimestamp(), 3760); + + } + /** + * Test Time Getters (hours, minutes, seconds, etc.) + */ + public function testTimeGetters() { + + $time = new qCal_Time(8, 10, 5, "GMT"); + $this->assertEqual($time->getHour(), 8); + $this->assertEqual($time->getMinute(), 10); + $this->assertEqual($time->getSecond(), 5); + + } + /** + * You can use any of the date() function's time-related metacharacters + */ + public function testTimeFormat() { + + $time = new qCal_Time(1, 0, 0, "GMT"); + $time->setFormat("G:i:sa"); + $this->assertEqual($time->__toString(), "1:00:00am"); + + } + /** + * Test that metacharacters can be escaped with a backslash + */ + public function testEscapeMetacharacters() { + + $time = new qCal_Time(0, 0, 0, "GMT"); + $time->setFormat("\G:\i:\s\a G:i:sa"); + $this->assertEqual($time->__toString(), "G:i:sa 0:00:00am"); + + } + /** + * Time can be generated from string by using the factory method + */ + public function testFactoryMethod() { + + // test with default timezone + $time = qCal_Time::factory("1:30pm"); // should default to America/Los_Angeles + $this->assertEqual($time->getHour(), 13); + $this->assertEqual($time->getMinute(), 30); + $this->assertEqual($time->getSecond(), 0); + $this->assertEqual($time->getTimezone()->getName(), date_default_timezone_get()); + + // test with specific timezone + $time = qCal_Time::factory("1:30pm", "MST"); // GMT - 7 hours + $this->assertEqual($time->getHour(), 13); // timezone doesn't change the time + $this->assertEqual($time->getMinute(), 30); + $this->assertEqual($time->getSecond(), 0); + $this->assertEqual($time->getTimezone()->getName(), "MST"); + + } + /** + * Swatch Internet Time (http://en.wikipedia.org/wiki/Swatch_Internet_Time) + * This is a method of time-keeping that eliminates leap-years, leap-seconds, timezones, daylight savings, + * non-decimal units, and all of the other inconsistencies and annoyances which you normally have to deal + * with when working with times. + */ + public function testSwatchInternetTime() { + + // @todo Because this method of time-keeping has not been widely adopted, + // I don't feel it is a real priority at the moment, but it's something I do intend to implement eventually + + } + /** + * @todo Look into the leap-second and what this class needs to do to support it + */ + public function testLeapSecond() { + + // coming soon! + + } + /** + * @todo Look into leap-seconds (right above 4.3.6 in the spec) + */ + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Timezone.php b/lib/qCal/tests/UnitTestCase/Timezone.php new file mode 100644 index 0000000..66c43c5 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Timezone.php @@ -0,0 +1,123 @@ +timezone = date_default_timezone_get(); + // + // } + // /** + // * Tear down test environment + // * Set the timezone back to what it was + // */ + // public function tearDown() { + // + // date_default_timezone_set($this->timezone); + // + // } + public function testTimezoneSetsServerTimezoneToGMT() { + + $timezone = qCal_Timezone::factory(array("foo" => "bar", "name" => "FooBar/Assmunch", "abbreviation" => "tits", "offsetSeconds" => "-28800")); + // this way, our timezone component works independently of the server timezone. + // if I can find a way to work with times without having php's functions adjust the output + // then I will. otherwise, I'll just have to set the timezone to GMT + // date_default_timezone_set("GMT"); + $date = gmdate("Y-m-d H:i:s", 0); + $date = qCal_Date::gmgetdate(0); + + } + /** + * The timezone defaults to server timezone + */ + public function testTimezoneDefaultsToServerTimezone() { + + $timezone = qCal_Timezone::factory(); + $this->assertEqual($timezone->getName(), date_default_timezone_get()); + + } + /** + * Test string output (can be formatted with PHP's date function's timezone-related meta-characters) + */ + public function testFormatString() { + + // format defaults to timezone name + $timezone = qCal_Timezone::factory("America/Los_Angeles"); + $this->assertEqual($timezone->__toString(), "America/Los_Angeles"); + + // test that format can be changed + $timezone->setFormat("P"); + $this->assertEqual($timezone->__toString(), "-08:00"); + + // test escapability + $timezone->setFormat('\PP'); + $this->assertEqual($timezone->__toString(), "P-08:00"); + + // test each of the metacharacters available + $timezone->setFormat('eIOPTZ'); + $this->assertEqual($timezone->__toString(), "America/Los_Angeles0-0800-08:00PST-28800"); + + } + /** + * Test that timezone's getters work + */ + public function testTimezoneOffsetGetters() { + + $timezone = qCal_Timezone::factory("America/Los_Angeles"); + $this->assertEqual($timezone->getOffset(), "-08:00"); + $this->assertEqual($timezone->getOffsetHours(), "-0800"); + $this->assertEqual($timezone->getOffsetSeconds(), "-28800"); + $this->assertEqual($timezone->getAbbreviation(), "PST"); + $this->assertEqual($timezone->isDaylightSavings(), false); + $this->assertEqual($timezone->getName(), "America/Los_Angeles"); + + } + /** + * GMT is an offset of zero. Test that it works as it should. + */ + public function testTimezoneSetToGMT() { + + $timezone = qCal_Timezone::factory("GMT"); + $this->assertEqual($timezone->getName(), "GMT"); + $this->assertEqual($timezone->getOffset(), 0); + + } + /** + * Test that you can create your own custom timezone + */ + public function testCustomTimezone() { + + $timezone = new qCal_Timezone("Custom", "5400", "CSTM", false); // hour and a half past GMT + $this->assertEqual($timezone->getOffsetSeconds(), "5400"); + + } + /** + * You should be able to register a custom timezone so that you can refer to it by name later on + */ + public function testUnregisteredCustomTimezoneThrowsException() { + + $this->expectException(new qCal_DateTime_Exception_InvalidTimezone("'Custom' is not a valid timezone.")); + $time = new qCal_Time(0, 0, 0, "Custom"); + + } + /** + * Now test that registering the timezone prevents the exception + */ + public function testCustomTimezoneRegister() { + + // now we register the timezone so that we can use it + $timezone = new qCal_Timezone("Custom", "5400", "CSTM", false); + qCal_Timezone::register($timezone); + // Create a new time with the custom timezone. The time should now be 12:00:00 in the custom timezone... + $time = new qCal_Time(0, 0, 0, "Custom"); + $this->assertEqual($time->getTimezone(), $timezone); + $this->assertEqual($time->getTimezone()->getOffsetSeconds(), "5400"); + // $this->assertEqual($time->getTimestamp(), "5400"); + $this->assertEqual($time->__toString(), "00:00:00"); + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Value.php b/lib/qCal/tests/UnitTestCase/Value.php new file mode 100644 index 0000000..63a0ef8 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Value.php @@ -0,0 +1,302 @@ +assertEqual($value->__toString(), base64_encode("TEST DATA")); + + } + /** + * Test that binary data is handled right + */ + public function testActualBinary() { + + $binary = file_get_contents('./files/me.jpg'); + $value = new qCal_Value_Binary($binary); + $this->assertEqual($value->__toString(), base64_encode($binary)); + $this->assertEqual($value->getValue(), $binary); + + } + /** + * Test that boolean data is handled right + */ + public function testBooleanToString() { + + $value = new qCal_Value_Boolean(true); + $this->assertEqual($value->__toString(), "TRUE"); + + } + /** + * Test that boolean data is handled right + */ + public function testRawBoolean() { + + $value = new qCal_Value_Boolean(false); + $this->assertEqual($value->getValue(), false); + + } + /** + * Test that cal-address data is handled right + */ + public function testCalAddressToString() { + + $value = new qCal_Value_CalAddress('http://www.example.com/webcal'); + $this->assertEqual($value->__toString(), "http://www.example.com/webcal"); + + } + /** + * Test that cal-address data is handled right + */ + public function testRawCalAddress() { + + $value = new qCal_Value_CalAddress('http://www.example.com/webcal'); + $this->assertEqual($value->getValue(), 'http://www.example.com/webcal'); + + } + /** + * Test that uri data is handled right + */ + public function testUriToString() { + + $value = new qCal_Value_Uri('http://www.example.com/webcal'); + $this->assertEqual($value->__toString(), "http://www.example.com/webcal"); + + } + /** + * Test that uri data is handled right + */ + public function testRawUri() { + + $value = new qCal_Value_Uri('http://www.example.com/webcal'); + $this->assertEqual($value->getValue(), 'http://www.example.com/webcal'); + + } + /** + * Test that period data is handled right + */ + public function testPeriodToString() { + + $value = new qCal_Value_Period('19970101T180000Z/19970102T070000Z'); + $this->assertEqual($value->__toString(), '19970101T180000Z/19970102T070000Z'); + // start date + duration + // 01-01-1997 at 12 am until plus two weeks five hours thirty minutes + // should be 01-15-1997 at 5:30 am but is 1997-01-14 21-30-00 + $value2 = new qCal_Value_Period('19970101T010000Z/PT5H'); + $this->assertEqual($value2->__toString(), '19970101T010000Z/19970101T060000Z'); + + } + /** + * Test that period data is handled right + */ + public function testRawPeriod() { + + $value = new qCal_Value_Period('19970101T180000Z/19970102T070000Z'); + $this->assertEqual($value->getValue(), new qCal_DateTime_Period('19970101T180000Z', '19970102T070000Z')); + + } + /** + * Test that date data is handled right + */ + public function testDateToString() { + + $value = new qCal_Value_Date('2009-04-23'); + $this->assertEqual($value->__toString(), "20090423"); + + } + /** + * Test that date data is handled right + */ + public function testRawDate() { + + $value = new qCal_Value_Date('2009-04-23'); + $this->assertEqual($value->getValue(), qCal_Date::factory('2009-04-23')); + + } + /** + * Test that date-time data is handled right + */ + public function testDateTimeToString() { + + $value = new qCal_Value_DateTime('2009-04-23 6:00'); + $this->assertEqual($value->__toString(), "20090423T060000"); + + } + /** + * Test that date-time data is handled right + */ + public function testRawDateTime() { + + $value = new qCal_Value_DateTime('2009-04-23 6:00'); + $this->assertEqual($value->getValue(), qCal_DateTime::factory('2009-04-23 6:00')); + + } + /** + * Test that duration data is handled right + */ + public function testDurationToString() { + + $value = new qCal_Value_Duration('P2WT2H45M'); + $this->assertEqual($value->__toString(), 'P2WT2H45M'); + + } + /** + * Test that duration passed in in an "unnormalized"? format gets corrected + */ + public function testDurationToStringNormalizes() { + + $value = new qCal_Value_Duration('P18D'); + $this->assertEqual($value->__toString(), 'P2W4D'); // 18 days == 2 weeks and 4 days + + $value = new qCal_Value_Duration('P180D18938S'); // 180 days + 18938 seconds + $this->assertEqual($value->__toString(), 'P25W5DT5H15M38S'); // converts to 25 weeks, 5 days, 5 hours, 15 minutes, and 38 seconds + // @todo: this doesn't handle negative values yet + + $value = new qCal_Value_Duration(-49200); + $this->assertEqual($value->__toString(), '-PT13H40M'); + + $value = new qCal_Value_Duration('-P180D18938S'); // 180 days + 18938 seconds + $this->assertEqual($value->__toString(), '-P25W5DT5H15M38S'); // converts to - 25 weeks, 5 days, 5 hours, 15 minutes, and 38 seconds + + } + /** + * If there is a zero-increment, it is removed + */ + public function testDurationRemovesZeroIncrement() { + + // @todo Actually I'm not sure about this, I'm not sure what the expected behavior is here + // for instance, if we have a duration of P15DT5H0M20S, should the 0M part be removed? + + } + /** + * Test that duration data is handled right + */ + public function testRawDuration() { + + $value = new qCal_Value_Duration('P1W3DT2H3M45S'); + $this->assertEqual($value->getValue(), new qCal_DateTime_Duration('P1W3DT2H3M45S')); // this is how many seconds are in the duration + + } + /** + * Test that duration passed in in an "unnormalized"? format gets corrected + */ + public function testDurationIsCaseInsensitive() { + + $duration = new qCal_Value_Duration('p2w6d30s'); + $this->assertEqual($duration->__toString(), 'P2W6DT30S'); + + } + /** + * Test that float data is handled right + */ + public function testFloatToString() { + + $value = new qCal_Value_Float(5.667); + $this->assertIdentical($value->__toString(), '5.667'); + + } + /** + * Test that float data is handled right + */ + public function testRawFloat() { + + $value = new qCal_Value_Float(5.667); + $this->assertIdentical($value->getValue(), 5.667); + + } + /** + * Test that integer data is handled right + */ + public function testIntegerToString() { + + $value = new qCal_Value_Integer(5667); + $this->assertIdentical($value->__toString(), '5667'); + + } + /** + * Test that integer data is handled right + */ + public function testRawInteger() { + + $value = new qCal_Value_Integer(5667); + $this->assertIdentical($value->getValue(), 5667); + + } + /** + * Test that integer data is handled right + */ + public function testTextToString() { + + $value = new qCal_Value_Text('text'); + $this->assertIdentical($value->__toString(), 'text'); + + } + /** + * Test that integer data is handled right + */ + public function testRawText() { + + $value = new qCal_Value_Text('text'); + $this->assertIdentical($value->getValue(), 'text'); + + } + /* + public function testPlayingAroundWithqCal() { + + $cal = new qCal(); + $journal = new qCal_Component_Journal(array( + 'uid' => '19970901T130000Z-123405@host.com', + 'dtstamp' => '19970901T1300Z', + 'dtstart' => new qCal_Property_Dtstart('19970317', array('value' => 'date')), + )); + $cal->attach($journal); + pre($cal->render()); + + } + */ + /** + * FROM RFC 2445 + */ + /** + * value type for a property will either be specified implicitly as the + * default value type + */ + public function testPropertySpecifiedImplicitlyAsDefault() { + + // I need to learn how to do this iwthout being so specific... with mocks or something... + $property = new qCal_Property_Dtstart('2008-12-31 5:00:00'); + $this->assertEqual($property->getType(), 'DATE-TIME'); + + } + /** + * or will be explicitly specified with the "VALUE" + * parameter. If the value type of a property is one of the alternate + * valid types, then it MUST be explicitly specified with the "VALUE" + * parameter. + */ + public function testPropertySpecifiedExplicitlyAsValue() { + + $property = new qCal_Property_Dtstart('2008-12-31 5:00:00'); + $property->setParam('value', 'date'); + $this->assertEqual($property->getType(), 'DATE'); + + } + /** + * Many values can have multiple values separated by commas + */ + public function testMultipleValues() { + + /*$value = new qCal_Value_Duration('P15DT5H0M20S'); + $value->addValue('P15D') + ->addValue('PT25M30S'); + $this->assertEqual($value->__toString(), 'P15DT5H0M20S,P15D,PT25M30S');*/ + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Value/Date.php b/lib/qCal/tests/UnitTestCase/Value/Date.php new file mode 100644 index 0000000..7df058a --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Value/Date.php @@ -0,0 +1,42 @@ +addValue('2008-04-24') + ->addValue('2008-04-25'); + $this->assertEqual('20080423T000000,20080424T000000,20080425T000000', $property->__toString()); + + $property2 = new qCal_Property_Exdate('2008-12-30', array('value' => 'date')); + $property2->addValue('2008-12-31'); + $this->assertEqual($property2->__toString(), "20081230,20081231"); + + //$value = new qCal_Value_Date('') + + } + /** + * The format for the value type is expressed as the [ISO + * 8601] complete representation, basic format for a calendar date. The + * textual format specifies a four-digit year, two-digit month, and + * two-digit day of the month. There are no separator characters between + * the year, month and day component text. + */ + public function testFormatsToISO8601() { + + $date = new qCal_Value_Date('Jan 15 2009'); + $this->assertEqual($date->__toString(), '20090115'); + + } + +} \ No newline at end of file diff --git a/lib/qCal/tests/UnitTestCase/Value/Multi.php b/lib/qCal/tests/UnitTestCase/Value/Multi.php new file mode 100644 index 0000000..d2ad955 --- /dev/null +++ b/lib/qCal/tests/UnitTestCase/Value/Multi.php @@ -0,0 +1,22 @@ +until('20120101T000000Z') // that occurs until 01/01/2012 + // ->interval(1) // happens every 1 years + // ->byMonth("2,4"); // in february and april + // + // } + /** + * If the property permits, multiple "recur" values are + * specified by a COMMA character (US-ASCII decimal 44) separated list + * of values. The value type is a structured value consisting of a list + * of one or more recurrence grammar parts. Each rule part is defined by + * a NAME=VALUE pair. The rule parts are separated from each other by + * the SEMICOLON character (US-ASCII decimal 59). The rule parts are not + * ordered in any particular sequence. Individual rule parts MUST only + * be specified once. + */ + public function testPropertyPermitsMultipleRecurValues() { + + + + } + /** + * The FREQ rule part identifies the type of recurrence rule. This rule + * part MUST be specified in the recurrence rule. Valid values include + * SECONDLY, to specify repeating events based on an interval of a + * second or more; MINUTELY, to specify repeating events based on an + * interval of a minute or more; HOURLY, to specify repeating events + * based on an interval of an hour or more; DAILY, to specify repeating + * events based on an interval of a day or more; WEEKLY, to specify + * repeating events based on an interval of a week or more; MONTHLY, to + * specify repeating events based on an interval of a month or more; and + * YEARLY, to specify repeating events based on an interval of a year or + * more. + */ + public function testFreqPartIdentifiesTypeOfRecurrenceRule() { + + //$recur = new qCal_Value_Recur(''); + + } + /** + * The INTERVAL rule part contains a positive integer representing how + * often the recurrence rule repeats. The default value is "1", meaning + * every second for a SECONDLY rule, or every minute for a MINUTELY + * rule, every hour for an HOURLY rule, every day for a DAILY rule, + * every week for a WEEKLY rule, every month for a MONTHLY rule and + * every year for a YEARLY rule. + */ + public function testIntervalSetsHowOftenRecurrenceRepeats() { + + + + } + public function testIntervalDefaultsToOne() { + + + + } + /** + * The UNTIL rule part defines a date-time value which bounds the + * recurrence rule in an inclusive manner. If the value specified by + * UNTIL is synchronized with the specified recurrence, this date or + * date-time becomes the last instance of the recurrence. If specified + * as a date-time value, then it MUST be specified in an UTC time + * format. If not present, and the COUNT rule part is also not present, + * the RRULE is considered to repeat forever. + */ + public function testUntilEndsRecurrence() { + + + + } + // if the UNTIL date falls on an occurance date of the recurrence rule, then that + // date should be included in the recurrence rule + public function testIfUntilValueLandsOnAnOccurenceDate() { + + + + } + // make sure end date is specified as UTC + public function testIfUntilIsDateTimeMustBeUtc() { + + + + } + // if UNTIL is not present, then there is no COUNT, it should repeat forever + public function testUntilNotPresent() { + + + + } + /** + * The COUNT rule part defines the number of occurrences at which to + * range-bound the recurrence. The "DTSTART" property value, if + * specified, counts as the first occurrence. + */ + public function testCountDefinesAmountOfOccurrences() { + + + + } + // if there is a dtstart, it should count as the first occurance + public function testDtstartCountsAsFirstIfPresent() { + + + + } + /** + * The BYSECOND rule part specifies a COMMA character (US-ASCII decimal + * 44) separated list of seconds within a minute. Valid values are 0 to + * 59. The BYMINUTE rule part specifies a COMMA character (US-ASCII + * decimal 44) separated list of minutes within an hour. Valid values + * are 0 to 59. The BYHOUR rule part specifies a COMMA character (US- + * ASCII decimal 44) separated list of hours of the day. Valid values + * are 0 to 23. + */ + public function testByIntervalDefinedAsCommaDelimitedList() { + + // test that only these values can be specified for ByInterval parts + + } + /** + * The BYDAY rule part specifies a COMMA character (US-ASCII decimal 44) + * separated list of days of the week; MO indicates Monday; TU indicates + * Tuesday; WE indicates Wednesday; TH indicates Thursday; FR indicates + * Friday; SA indicates Saturday; SU indicates Sunday. + */ + public function testByDaySpecifiesDays() { + + + + } + /** + * Each BYDAY value can also be preceded by a positive (+n) or negative + * (-n) integer. If present, this indicates the nth occurrence of the + * specific day within the MONTHLY or YEARLY RRULE. For example, within + * a MONTHLY rule, +1MO (or simply 1MO) represents the first Monday + * within the month, whereas -1MO represents the last Monday of the + * month. If an integer modifier is not present, it means all days of + * this type within the specified frequency. For example, within a + * MONTHLY rule, MO represents all Mondays within the month. + */ + public function testByDayAllowPosNegModifiers() { + + + + } + /** + * The BYMONTHDAY rule part specifies a COMMA character (ASCII decimal + * 44) separated list of days of the month. Valid values are 1 to 31 or + * -31 to -1. For example, -10 represents the tenth to the last day of + * the month. + */ + public function testByMonthDaySpecifiesListOfDaysOfMonth() { + + + + } + // test that positive and negative modifiers may be used + public function testByMonthDayPosNegModifiers() { + + + + } + /** + * The BYYEARDAY rule part specifies a COMMA character (US-ASCII decimal + * 44) separated list of days of the year. Valid values are 1 to 366 or + * -366 to -1. For example, -1 represents the last day of the year + * (December 31st) and -306 represents the 306th to the last day of the + * year (March 1st). + */ + public function testByYearDaySpecifiesListOfDaysOfYear() { + + + + } + // test that positive and negative modifiers may be used + public function testByYearDayPosNegModifiers() { + + + + } + /** + * The BYWEEKNO rule part specifies a COMMA character (US-ASCII decimal + * 44) separated list of ordinals specifying weeks of the year. Valid + * values are 1 to 53 or -53 to -1. This corresponds to weeks according + * to week numbering as defined in [ISO 8601]. A week is defined as a + * seven day period, starting on the day of the week defined to be the + * week start (see WKST). Week number one of the calendar year is the + * first week which contains at least four (4) days in that calendar + * year. This rule part is only valid for YEARLY rules. For example, 3 + * represents the third week of the year. + * + * Note: Assuming a Monday week start, week 53 can only occur when + * Thursday is January 1 or if it is a leap year and Wednesday is + * January 1. + */ + public function testByWeekNoSpecifiesListOfWeeksOfYear() { + + + + } + // test that positive and negative modifiers may be used + public function testByWeekNoPosNegModifiers() { + + + + } + // test that this part is only valid for yearly rules + public function testByWeekNoOnlyValidForYearlyRules() { + + + + } + // test that week 53 can only occure when thurs is january 1 or if it is a leap year and wednesday is jan 1 + public function testByWeekNoEdgeCases() { + + + + } + /** + * The BYMONTH rule part specifies a COMMA character (US-ASCII decimal + * 44) separated list of months of the year. Valid values are 1 to 12. + */ + public function testByMonthSpecifiesListOfMonthsOfYear() { + + + + } + /** + * The WKST rule part specifies the day on which the workweek starts. + * Valid values are MO, TU, WE, TH, FR, SA and SU. This is significant + * when a WEEKLY RRULE has an interval greater than 1, and a BYDAY rule + * part is specified. This is also significant when in a YEARLY RRULE + * when a BYWEEKNO rule part is specified. The default value is MO. + */ + public function testWkstSpecifiesWhenWorkWeekStarts() { + + + + } + // WKST defaults to monday + public function testWkstDefaultsToMo() { + + + + } + // There are some cases in the docblock directly above, test them + public function testWkstEdgeCases() { + + + + } + /** + * The BYSETPOS rule part specifies a COMMA character (US-ASCII decimal + * 44) separated list of values which corresponds to the nth occurrence + * within the set of events specified by the rule. Valid values are 1 to + * 366 or -366 to -1. It MUST only be used in conjunction with another + * BYxxx rule part. For example "the last work day of the month" could + * be represented as: + * + * RRULE:FREQ=MONTHLY;BYDAY=MO,TU,WE,TH,FR;BYSETPOS=-1 + */ + public function testBySetPosSpecifiesListOfNthOccurrence() { + + + + } + public function testBySetPosMustOnlyBeUsedInConjunctionWithAnotherByXXXRule() { + + + + } + /** + * Each BYSETPOS value can include a positive (+n) or negative (-n) + * integer. If present, this indicates the nth occurrence of the + * specific occurrence within the set of events specified by the rule. + * + * If BYxxx rule part values are found which are beyond the available + * scope (ie, BYMONTHDAY=30 in February), they are simply ignored. + */ + public function testBySetPosAllowsPosNegModifiers() { + + + + } + public function testBySetPosIgnoresInvalidValues() { + + + + } + /** + * Information, not contained in the rule, necessary to determine the + * various recurrence instance start time and dates are derived from the + * Start Time (DTSTART) entry attribute. For example, + * "FREQ=YEARLY;BYMONTH=1" doesn't specify a specific day within the + * month or a time. This information would be the same as what is + * specified for DTSTART. + */ + public function testDtstartDerivedFromDtstartProperty() { + + + + } + /** + * BYxxx rule parts modify the recurrence in some manner. BYxxx rule + * parts for a period of time which is the same or greater than the + * frequency generally reduce or limit the number of occurrences of the + * recurrence generated. For example, "FREQ=DAILY;BYMONTH=1" reduces the + * number of recurrence instances from all days (if BYMONTH tag is not + * present) to all days in January. BYxxx rule parts for a period of + * time less than the frequency generally increase or expand the number + * of occurrences of the recurrence. For example, + * "FREQ=YEARLY;BYMONTH=1,2" increases the number of days within the + * yearly recurrence set from 1 (if BYMONTH tag is not present) to 2. + */ + public function testByXXXModifiers() { + + + + } + /** + * If multiple BYxxx rule parts are specified, then after evaluating the + * specified FREQ and INTERVAL rule parts, the BYxxx rule parts are + * applied to the current set of evaluated occurrences in the following + * order: BYMONTH, BYWEEKNO, BYYEARDAY, BYMONTHDAY, BYDAY, BYHOUR, + * BYMINUTE, BYSECOND and BYSETPOS; then COUNT and UNTIL are evaluated. + * + * Here is an example of evaluating multiple BYxxx rule parts. + * + * DTSTART;TZID=US-Eastern:19970105T083000 + * RRULE:FREQ=YEARLY;INTERVAL=2;BYMONTH=1;BYDAY=SU;BYHOUR=8,9; + * BYMINUTE=30 + */ + public function testMultipleByXXXRuleParts() { + + + + } + /** + * First, the "INTERVAL=2" would be applied to "FREQ=YEARLY" to arrive + * at "every other year". Then, "BYMONTH=1" would be applied to arrive + * at "every January, every other year". Then, "BYDAY=SU" would be + * applied to arrive at "every Sunday in January, every other year". + * Then, "BYHOUR=8,9" would be applied to arrive at "every Sunday in + * January at 8 AM and 9 AM, every other year". Then, "BYMINUTE=30" + * would be applied to arrive at "every Sunday in January at 8:30 AM and + * 9:30 AM, every other year". Then, lacking information from RRULE, the + * second is derived from DTSTART, to end up in "every Sunday in January + * at 8:30:00 AM and 9:30:00 AM, every other year". Similarly, if the + * BYMINUTE, BYHOUR, BYDAY, BYMONTHDAY or BYMONTH rule part were + * missing, the appropriate minute, hour, day or month would have been + * retrieved from the "DTSTART" property. + */ + public function testAllTheCrapAbove() { + + + + } + /** + * No additional content value encoding (i.e., BACKSLASH character + * encoding) is defined for this value type. + * + * Example: The following is a rule which specifies 10 meetings which + * occur every other day: + * + * FREQ=DAILY;COUNT=10;INTERVAL=2 + * + * There are other examples specified in the "RRULE" specification. + */ + +} \ No newline at end of file diff --git a/lib/qCal/tests/config.php b/lib/qCal/tests/config.php new file mode 100644 index 0000000..e7e7924 --- /dev/null +++ b/lib/qCal/tests/config.php @@ -0,0 +1,6 @@ +"; + echo "Size: " . $length . "\n"; + echo "Value: \n"; + var_dump($var); + echo ""; + echo ob_get_clean(); + +} + +function pre($var) { + + pr($var); + exit; + +} +/** + * Define the autoload function so that classes get loaded automatically in testing + * I may eventually try to work out some type of autoload solution in the library but im not sure + * of the implications as of yet. + +function __autoload($className) { + $paths = explode(PATH_SEPARATOR, get_include_path()); + foreach ($paths as $path) { + $fileName = str_replace("_", DIRECTORY_SEPARATOR, $className); + if($exists = !class_exists($className) && file_exists($class = $path.DIRECTORY_SEPARATOR.$fileName.'.php')) { + require_once $class; + } elseif(!$exists) { + //eval('class '.$className.' extends Exception {}'); + //throw new $className('[__autoload] this file doesn\'t exist: '.$class); + } + } +} + */ \ No newline at end of file diff --git a/lib/qCal/tests/files/mac.ics b/lib/qCal/tests/files/mac.ics new file mode 100644 index 0000000..2310a8a --- /dev/null +++ b/lib/qCal/tests/files/mac.ics @@ -0,0 +1,21 @@ +BEGIN:VCALENDAR +CALSCALE:GREGORIAN +X-WR-TIMEZONE;VALUE=TEXT:US/Pacific +METHOD:PUBLISH +PRODID:-//Apple Computer\, Inc//iCal 1.0//EN +X-WR-CALNAME;VALUE=TEXT:Example +VERSION:2.0 +BEGIN:VEVENT +SEQUENCE:5 +DTSTART;VALUE=DATE;TZID=US/Pacific:20021028 +DTSTAMP:20021028T011706Z +SUMMARY:Coffee with: Jason +UID:EC9439B1-FF65-11D6-9973-003065F99D04 +DTEND;TZID=US/Pacific:20021028T150000 +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-P1D +ACTION:DISPLAY +DESCRIPTION:Event reminder +END:VALARM +END:VEVENT +END:VCALENDAR \ No newline at end of file diff --git a/lib/qCal/tests/files/me.jpg b/lib/qCal/tests/files/me.jpg new file mode 100644 index 0000000..6deaf84 Binary files /dev/null and b/lib/qCal/tests/files/me.jpg differ diff --git a/lib/qCal/tests/files/mycalendar.ics b/lib/qCal/tests/files/mycalendar.ics new file mode 100644 index 0000000..96e289d --- /dev/null +++ b/lib/qCal/tests/files/mycalendar.ics @@ -0,0 +1,439 @@ +BEGIN:VCALENDAR +PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN +VERSION:2.0 +METHOD:PUBLISH +X-CALSTART:20081029T220000Z +X-CALEND:20130102T000000 +X-WR-RELCALID:{00000018-3728-6F53-15C5-9F21E6AA7D3E} +X-WR-CALNAME:All calendars combined +BEGIN:VTIMEZONE +TZID:Pacific Time (US & Canada) +BEGIN:STANDARD +DTSTART:16011104T020000 +RRULE:FREQ=YEARLY;BYDAY=1SU;BYMONTH=11 +TZOFFSETFROM:-0700 +TZOFFSETTO:-0800 +END:STANDARD +BEGIN:DAYLIGHT +DTSTART:16010311T020000 +RRULE:FREQ=YEARLY;BYDAY=2SU;BYMONTH=3 +TZOFFSETFROM:-0800 +TZOFFSETTO:-0700 +END:DAYLIGHT +END:VTIMEZONE +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081105T060000Z +DTSTAMP:20081216T050300Z +DTSTART:20081105T050000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1225878078 +SUMMARY:Meeting with Joe Hobson / Navigation North and Credentify +TRANSP:OPAQUE +UID:555b4a5b-0a3e-431a-a84c-510e96c9311f +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081126T230000Z +DTSTAMP:20081216T050300Z +DTSTART:20081126T220000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227650367 +SUMMARY:Talent Technology Resume Mirror Demo +TRANSP:OPAQUE +UID:13525a9b-a4f7-447b-8c54-a3426a555722 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081107T190000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081107T180000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=50;BYDAY=FR +SEQUENCE:1229132291 +SUMMARY:Weekly Invoice +TRANSP:OPAQUE +UID:d0bbbd7d-ed73-4a62-9c74-9fda7cf7ae05 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081112T220000Z +DTSTAMP:20081216T050300Z +DTSTART:20081112T210000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1226522203 +SUMMARY:Meet Michael at MC2 Design +TRANSP:OPAQUE +UID:bf1b73b7-2ab5-4dde-899e-31d6ba47e873 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;VALUE=DATE:20090102 +DTSTAMP:20081216T050300Z +DTSTART;VALUE=DATE:20090101 +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1228154526 +SUMMARY:Update MC2 wordpress blogs +TRANSP:TRANSPARENT +UID:6ee87c25-4769-4cac-8729-99221614bf5f +X-MICROSOFT-CDO-BUSYSTATUS:FREE +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;VALUE=DATE:20081202 +DTSTAMP:20081216T050300Z +DTSTART;VALUE=DATE:20081201 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=MONTHLY;COUNT=50;BYMONTHDAY=1 +SEQUENCE:1228154526 +SUMMARY:Update MC2 wordpress blogs +TRANSP:TRANSPARENT +UID:5abc0556-de5f-4f3b-af35-47f92e2c11ae +X-MICROSOFT-CDO-BUSYSTATUS:FREE +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081029T230000Z +DTSTAMP:20081216T050300Z +DTSTART:20081029T220000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1224598635 +SUMMARY:2611 esplenade "denny forland" by tortilla flatts +TRANSP:OPAQUE +UID:c6cff7ba-3cc0-4bfd-9ecb-cfd845e5589a +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;VALUE=DATE:20081101 +DTSTAMP:20081216T050300Z +DTSTART;VALUE=DATE:20081031 +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1225402278 +SUMMARY:Halloween!!! +TRANSP:TRANSPARENT +UID:7d5111cb-c0a5-42a2-8e16-24d3ff7105c8 +X-MICROSOFT-CDO-BUSYSTATUS:FREE +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081106T213000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081106T203000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=50;BYDAY=TH +SEQUENCE:1229054894 +SUMMARY:Change the water in the aquarium +TRANSP:OPAQUE +UID:c5b6d630-c5a1-4f42-ba43-428c3914b273 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081105T180000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081105T170000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=50;BYDAY=WE +SEQUENCE:1228955898 +SUMMARY:Laundry Night +TRANSP:OPAQUE +UID:3b6f1d6e-a5da-42fc-b2ad-32cd73d4a50b +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081104T200000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081104T190000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=50;BYDAY=TU +SEQUENCE:1228876676 +SUMMARY:Call mom and let her know how things are going +TRANSP:OPAQUE +UID:3e3214e5-dbf7-4045-bb96-50b35b8a452f +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081103T200000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081103T190000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=50;BYDAY=MO +SEQUENCE:1229394636 +SUMMARY:Work on qCal / other side projects +TRANSP:OPAQUE +UID:589bad4b-e6b4-4205-8b40-420f0b0f499f +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081105T200000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081105T190000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=50;BYDAY=WE +SEQUENCE:1228963102 +SUMMARY:Work out with JB +TRANSP:OPAQUE +UID:a3137403-77a5-43a9-b38d-ad73dadf30c3 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081108T140000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081108T130000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=4;BYDAY=SA +SEQUENCE:1227991030 +SUMMARY:Work out with JB +TRANSP:OPAQUE +UID:6be61724-15d7-4640-82f7-43d630f2f5a6 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081104T200000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081104T190000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=50;BYDAY=TU +SEQUENCE:1228876676 +SUMMARY:Clean up the house +TRANSP:OPAQUE +UID:40798dc3-c1b0-42a1-8937-787af9575bfd +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081105T170000Z +DTSTAMP:20081216T050300Z +DTSTART:20081105T160000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1225899378 +SUMMARY:Meeting with Tyler Smalley +TRANSP:OPAQUE +UID:48042175-e37f-44db-97ae-fc6a794cb873 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;VALUE=DATE:20081115 +DTSTAMP:20081216T050300Z +DTSTART;VALUE=DATE:20081114 +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1226682383 +SUMMARY:Turn in date +TRANSP:TRANSPARENT +UID:88a54a54-dca5-41c7-8729-218483e5f255 +X-MICROSOFT-CDO-BUSYSTATUS:FREE +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;VALUE=DATE:20081114 +DTSTAMP:20081216T050300Z +DTSTART;VALUE=DATE:20081113 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=50;BYDAY=TH +SEQUENCE:1229015263 +SUMMARY:Work on a post on your blog +TRANSP:TRANSPARENT +UID:aa7efe00-3b2e-41bd-bbdb-d930f240d0f0 +X-MICROSOFT-CDO-BUSYSTATUS:FREE +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081119T170000Z +DTSTAMP:20081216T050300Z +DTSTART:20081119T160000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227108985 +SUMMARY:SWAP Orientation at +TRANSP:OPAQUE +UID:3e34c9ca-16d6-4a4f-93bc-5a7cb48f1cd2 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081122T163000Z +DTSTAMP:20081216T050300Z +DTSTART:20081122T153000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227139386 +SUMMARY:SWAP work at Park & Ride +TRANSP:OPAQUE +UID:351d56f3-8e9e-49fb-85ca-fb97c9dfc5c2 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081123T163000Z +DTSTAMP:20081216T050300Z +DTSTART:20081123T153000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227139413 +SUMMARY:SWAP work at Park & Ride +TRANSP:OPAQUE +UID:e5f51f97-5abe-4350-8fb1-37d959195696 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081206T163000Z +DTSTAMP:20081216T050300Z +DTSTART:20081206T153000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227139452 +SUMMARY:SWAP work at Park & Ride +TRANSP:OPAQUE +UID:f1f73092-ff49-4f70-89b2-31fac625ae78 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081207T163000Z +DTSTAMP:20081216T050300Z +DTSTART:20081207T153000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227139464 +SUMMARY:SWAP work at Park & Ride +TRANSP:OPAQUE +UID:1a29efd4-2ef0-490d-b9ed-527062fe6bc2 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081213T163000Z +DTSTAMP:20081216T050300Z +DTSTART:20081213T153000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227139484 +SUMMARY:SWAP work at Park & Ride +TRANSP:OPAQUE +UID:8646144b-fa84-4967-ada7-957cad22cdbf +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081214T163000Z +DTSTAMP:20081216T050300Z +DTSTART:20081214T153000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227139493 +SUMMARY:SWAP work at Park & Ride +TRANSP:OPAQUE +UID:f52dfd94-7d33-4112-85b5-9cba4e1df884 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081220T163000Z +DTSTAMP:20081216T050300Z +DTSTART:20081220T153000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227139507 +SUMMARY:SWAP work at Park & Ride +TRANSP:OPAQUE +UID:42bd990b-ac6d-48f9-a134-8c8a1e120c86 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081227T140000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081227T130000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=43;BYDAY=SA +SEQUENCE:1227139600 +SUMMARY:Work out with JB +TRANSP:OPAQUE +UID:0b941d05-b48f-4531-a6e7-8b0b42401939 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081125T030000Z +DTSTAMP:20081216T050300Z +DTSTART:20081125T020000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1227577033 +SUMMARY:Go to Ryan's and begin isitlegit project at +TRANSP:OPAQUE +UID:17fd7852-93f6-40b8-aee6-e97725060560 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND;TZID="Pacific Time (US & Canada)":20081127T190000 +DTSTAMP:20081216T050300Z +DTSTART;TZID="Pacific Time (US & Canada)":20081127T180000 +LAST-MODIFIED:20081216T050352Z +RRULE:FREQ=WEEKLY;COUNT=50;BYDAY=TH +SEQUENCE:1229045894 +SUMMARY:Weekly meeting with Ryan for our project +TRANSP:OPAQUE +UID:5bc50530-4704-4b3d-8fad-5aaf23b9241d +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081204T180000Z +DTSTAMP:20081216T050300Z +DTSTART:20081204T170000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1228354516 +SUMMARY:37signals live at +TRANSP:OPAQUE +UID:92735a88-8821-428c-9652-6d1348d7bd49 +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081216T050352Z +DTEND:20081217T030000Z +DTSTAMP:20081216T050300Z +DTSTART:20081217T020000Z +LAST-MODIFIED:20081216T050352Z +SEQUENCE:1228878374 +SUMMARY:Dinnger at the Olive Garden with Grandma +TRANSP:OPAQUE +UID:c884d286-d5f1-487c-a8ee-da9ce0e5fc9a +X-MICROSOFT-CDO-BUSYSTATUS:BUSY +END:VEVENT +END:VCALENDAR diff --git a/lib/qCal/tests/files/simple.ics b/lib/qCal/tests/files/simple.ics new file mode 100644 index 0000000..e557cc6 --- /dev/null +++ b/lib/qCal/tests/files/simple.ics @@ -0,0 +1,94 @@ +BEGIN:VCALENDAR +PRODID:-//Microsoft Corporation//Outlook 12.0 MIMEDIR//EN +VERSION:2.0 +METHOD:PUBLISH +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081105T104258Z +DESCRIPTION:A discussion with Tyler about how everything is over coffee + and donuts. I imagine we'll probably talk about our personal projects. +DTEND:20081105T160000Z +DTSTAMP:20081105T104200Z +DTSTART:20081105T150000Z +LAST-MODIFIED:20081105T104258Z +SEQUENCE:0 +SUMMARY:Meeting with Tyler at Starbucks +TRANSP:OPAQUE +UID:3632597 +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081105T104258Z +DESCRIPTION:"Call Zach and talk to him about his interview" +DTEND:20081105T230000Z +DTSTAMP:20081105T104200Z +DTSTART:20081105T220000Z +LAST-MODIFIED:20081105T104258Z +SEQUENCE:0 +SUMMARY:Call Zach +TRANSP:OPAQUE +UID:3632752 +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-P1D +ACTION:DISPLAY +DESCRIPTION:Event reminder +END:VALARM +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081105T104258Z +DESCRIPTION:Need to be sure to get quarters for laundry day +DTEND:20081106T015000Z +DTSTAMP:20081105T104200Z +DTSTART:20081106T005000Z +LAST-MODIFIED:20081105T104258Z +SEQUENCE:0 +SUMMARY:Get quarters for laundry +TRANSP:OPAQUE +UID:3632770 +BEGIN:VALARM +TRIGGER;VALUE=DURATION:-P1D +ACTION:DISPLAY +DESCRIPTION:Event reminder +END:VALARM +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081105T104258Z +DESCRIPTION:Go to Kmart / Walmart and buy a pair of cheap shoes for work +DTEND:20081108T015000Z +DTSTAMP:20081105T104200Z +DTSTART:20081108T005000Z +LAST-MODIFIED:20081105T104258Z +SEQUENCE:0 +SUMMARY:Buy new shoes +TRANSP:OPAQUE +UID:3632769 +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081105T104258Z +DESCRIPTION:PG&E Bill is due, get money from roommates and pay it. +DTEND:20081116T003000Z +DTSTAMP:20081105T104200Z +DTSTART:20081115T233000Z +LAST-MODIFIED:20081105T104258Z +SEQUENCE:0 +SUMMARY:Pay PG&E Bill +TRANSP:OPAQUE +UID:3533118 +END:VEVENT +BEGIN:VEVENT +CLASS:PUBLIC +CREATED:20081105T104258Z +DESCRIPTION:Pay rent (late on the 5th) +DTEND:20081201T223000Z +DTSTAMP:20081105T104200Z +DTSTART:20081201T213000Z +LAST-MODIFIED:20081105T104258Z +SEQUENCE:0 +SUMMARY:Pay Rent +TRANSP:OPAQUE +UID:3615354 +END:VEVENT +END:VCALENDAR diff --git a/lib/qCal/tests/index.php b/lib/qCal/tests/index.php new file mode 100644 index 0000000..9f96564 --- /dev/null +++ b/lib/qCal/tests/index.php @@ -0,0 +1,62 @@ +addTestCase(new UnitTestCase_Parser); +$test->addTestCase(new UnitTestCase_Component); +$test->addTestCase(new UnitTestCase_Component_Alarm); +$test->addTestCase(new UnitTestCase_Component_Calendar); +$test->addTestCase(new UnitTestCase_Component_Timezone); +$test->addTestCase(new UnitTestCase_Component_Event); +$test->addTestCase(new UnitTestCase_Property); +$test->addTestCase(new UnitTestCase_Value); +$test->addTestCase(new UnitTestCase_Value_Date); +$test->addTestCase(new UnitTestCase_Value_Recur); +$test->addTestCase(new UnitTestCase_Value_Multi); +$test->addTestCase(new UnitTestCase_Renderer); +$test->addTestCase(new UnitTestCase_DateTime); +$test->addTestCase(new UnitTestCase_Date); +$test->addTestCase(new UnitTestCase_Timezone); +$test->addTestCase(new UnitTestCase_Time); +$test->addTestCase(new UnitTestCase_Recur); +// $test->addTestCase(new UnitTestCase_Database); + +/** + * Sprint One: 12/15/2009 - 12/29/2009 + */ +$test->addTestCase(new UnitTestCase_SprintOne); +/** + * Sprint Two: 12/30/2009 - 1/14/2009 + */ +$test->addTestCase(new UnitTestCase_SprintTwo); + +$test->run(new HtmlReporter()); \ No newline at end of file diff --git a/lib/qCal/tests/testclasses/qCal/Property/XLvFoo.php b/lib/qCal/tests/testclasses/qCal/Property/XLvFoo.php new file mode 100644 index 0000000..63c90be --- /dev/null +++ b/lib/qCal/tests/testclasses/qCal/Property/XLvFoo.php @@ -0,0 +1,9 @@ +setCurrentSubId($_GET['subId']); + + $controller = new Controller(); + $controller->updateSub($_GET['subId']); + + $database->activate( $_GET['subId'], $fbUserId ); + + $msg = urlencode('Subscription was reactivated successfully!'); + header("Location: index.php?action=showSubscriptionList&success=1&successMsg=" . $msg); + +} catch (Exception $e) { + //send error back to user + + $errorMsg = urlencode($e->getMessage()); + header("Location: index.php?action=showSubscriptionList&error=1&errorMsg=

    Could not reactivate this subscription because there occurred an error when trying to update its events.

    " . $errorMsg); +} + +?> \ No newline at end of file diff --git a/pages/doDeactivatePage.php b/pages/doDeactivatePage.php new file mode 100644 index 0000000..180a007 --- /dev/null +++ b/pages/doDeactivatePage.php @@ -0,0 +1,20 @@ +setCurrentSubId($_GET['subId']); + + $database->deactivate( $_GET['subId'], $fbUserId ); + + header("Location: index.php?action=showSubscriptionList"); + +} catch (Exception $e) { + //send error back to user + + $errorMsg = urlencode($e->getMessage()); + header("Location: index.php?action=showSubscriptionList&error=1&errorMsg=" . $errorMsg); +} + +?> \ No newline at end of file diff --git a/pages/doEditSubscriptionPage.php b/pages/doEditSubscriptionPage.php new file mode 100644 index 0000000..810fab0 --- /dev/null +++ b/pages/doEditSubscriptionPage.php @@ -0,0 +1,16 @@ +updateSubscriptionData($_POST, $fbUserId); + + $msg = urlencode('Subscription was changed successfully!'); + header("Location: " . 'index.php?action=showSubscriptionList&success=1&successMsg=' . $msg); + +} catch (Exception $e) { + + $errorMsg = urlencode('

    Could not change subscription because there was an error.

    ' . $e->getMessage()); + header("Location: " . 'index.php?action=showSubscribeToiCalendar&editSub=1&error=1&errorMsg=' . $errorMsg . $fields); +} + +?> diff --git a/pages/doSubscribePage.php b/pages/doSubscribePage.php new file mode 100644 index 0000000..adc4aa3 --- /dev/null +++ b/pages/doSubscribePage.php @@ -0,0 +1,63 @@ +checkUrl($_POST['iCalendarUrl']); + + $subId = $database->insertSubscription($_POST['subName'], $fbUserId, $_POST['iCalendarUrl'], $_POST['pageId'], $_POST['imageProperty']); + + $controller->updateSub($subId); + + //send user to subscription list + $msg = urlencode('Subscription was added successfully!'); + header("Location: " . 'index.php?action=showSubscriptionList&success=1&successMsg=' . $msg); + +} catch (Exception $e) { + //send error back to user + + if ($config['debugMode']) + $logger->error("doSubscribePage: ", $e); + + $subId = $logger->getCurrentSubId(); + if( $database->hasSuccessfulImport($subId) ) { + //subscription is added to db but there went something wrong during publishing to fb + + $errorMsg = urlencode('

    Subscription added successfully but there was an error when trying to post an event to Facebook.

    ' . $e->getMessage()); + header("Location: " . 'index.php?action=showSubscriptionList&error=1&errorMsg=' . $errorMsg); + } else { + //could not add subscription + + $fields = ''; + foreach ($_POST as $key => $value) + $fields .= '&'.$key.'='.$value; + + $errorMsg = urlencode('

    Could not subscribe this calendar because there was an error.

    ' . $e->getMessage()); + header("Location: " . 'index.php?action=showSubscribeToiCalendar&error=1&errorMsg=' . $errorMsg . $fields); + } +} + + +?> \ No newline at end of file diff --git a/pages/doUnsubscribePage.php b/pages/doUnsubscribePage.php new file mode 100644 index 0000000..65a7460 --- /dev/null +++ b/pages/doUnsubscribePage.php @@ -0,0 +1,85 @@ +setCurrentSubId($subId); + + $STH = $database->selectUserIdAndAccessToken($subId); + $row = $STH->fetch(); + $subUserId = $row->fbUserId; + $token = $row->fbAccessToken; + + if ($subUserId != $fbUserId) + exit; + + if( !isset($_GET['doNotDeleteEvents']) ) { + + $STH = $database->selectAllEvents($subId); + + $notDeleted = array(); + + $thereIsAnotherRow = ( $row = $STH->fetch() ); + while($thereIsAnotherRow) { + $logger->setCurrentOurEventId($row->ourEventId); + + $tokenArray = array( + 'access_token' => $token + ); + + try { + $success = $facebook->api('/' . $row->fbEventId, "DELETE", $tokenArray); + + if ($success) { + $database->setEventDeleted($row->ourEventId); + $logger->info("Event deleted on facebook. fbEventId: " . $row->fbEventId); + } else { + throw Exception("fb exception"); + } + } catch (Exception $e) { + $logger->warning("Event could not be deleted on Facebook.", $e); + + array_push($notDeleted, $row->fbEventId); + } + + $logger->unsetCurrentOurEventId(); + + $thereIsAnotherRow = $row = $STH->fetch(); + if ($thereIsAnotherRow) + sleep($config['waitForNextEventPublish']); + } + + } else { + // IF not doNotDeleteEvents + // i.e. if no delete events from facebook but only form db + $database->deleteEvents($subId); + } + + + if( empty($notDeleted) ) { + $database->deleteSubscription($subId); + $msg = urlencode('Deleted subscription successfully.'); + header("Location: " . 'index.php?action=showSubscriptionList&success=1&successMsg=' . $msg); + } else { + + $errorMsg = "

    The following events could not be deleted.

    "; + header("Location: " . 'index.php?action=showSubscriptionList&error=1&showUnsubscribeAnywayButton=1&subId=' + .$subId.'&errorMsg='.urlencode($errorMsg) ); + } + +} catch (Exception $e) { + //send error back to user + + $errorMsg = urlencode($e->getMessage()); + header("Location: " . 'index.php?action=showSubscriptionList&error=1&errorMsg=' . $errorMsg); +} + +?> diff --git a/pages/doUpdatePage.php b/pages/doUpdatePage.php new file mode 100644 index 0000000..2bb03be --- /dev/null +++ b/pages/doUpdatePage.php @@ -0,0 +1,23 @@ +updateSub($_GET['subId']); + + $msg = urlencode('Subscription updated successfully.'); + header("Location: " . 'index.php?action=showSubscriptionList&success=1&successMsg=' . $msg); + +} catch (Exception $e) { + + if ($config['debugMode']) { + $logger->warning("Could not update subscription manually.", $e); + } + + $errorMsg = urlencode($e->getMessage()); + header("Location: " . 'index.php?action=showSubscriptionList&error=1&errorMsg=' . $errorMsg); +} + +?> diff --git a/pages/donateInclude.php b/pages/donateInclude.php new file mode 100644 index 0000000..45a1216 --- /dev/null +++ b/pages/donateInclude.php @@ -0,0 +1,9 @@ +
    +
    + + + + +
    +
    \ No newline at end of file diff --git a/pages/footerInclude.php b/pages/footerInclude.php new file mode 100644 index 0000000..f3b0030 --- /dev/null +++ b/pages/footerInclude.php @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/pages/headerInclude.php b/pages/headerInclude.php new file mode 100644 index 0000000..96acc10 --- /dev/null +++ b/pages/headerInclude.php @@ -0,0 +1,23 @@ + + + + Calendar to Facebook + + + + + + + + + + +
    \ No newline at end of file diff --git a/pages/loginPage.php b/pages/loginPage.php new file mode 100644 index 0000000..2b524d7 --- /dev/null +++ b/pages/loginPage.php @@ -0,0 +1,20 @@ +getLoginUrl(array( + 'canvas' => 1, + 'fbconnect' => 0, + 'scope' => 'offline_access,create_event,manage_pages,user_groups' + //'redirect_uri' => "" +)); + +header("Location: " . $loginUrl); + +?> diff --git a/pages/showDocs.php b/pages/showDocs.php new file mode 100644 index 0000000..f5bd17c --- /dev/null +++ b/pages/showDocs.php @@ -0,0 +1,76 @@ + + + +

    About

    +

    With this app you can subscribe to an iCalendar file which then gets regularly checked for updates. When new events in the calendar become available, it will create a facebook event in your name. This works also for Facebook pages and groups where you are an administrator.

    + +

    Example: Google Calendar

    +

    Most calendars export to the iCalendar format, usually a file with the suffix .ics. For example, to get your Google Calendar URL: in Google Calendar go to Settings -> Calendars -> choose a calendar -> public ICAL.

    + +

    Images

    +

    If some of the events in your iCalendar file have a special X-field that cointains an URL which points to an image file, you can enter the name of that field in the 'Picture' field in the advanced options of this App. For example if your ics file looks like the following +

    +BEGIN:VEVENT
    +DTSTART;VALUE=DATE:20060704
    +DTEND;VALUE=DATE:20060705
    +SUMMARY:Independence Day
    +X-GOOGLE-CALENDAR-CONTENT-URL:http://www.google.com/logos/july4th06.gif
    +END:VEVENT
    +

    +then you would write X-GOOGLE-CALENDAR-CONTENT-URL in the Picture field in the advanced options of a new subscription or click edit for an existing one. Or you can write ATTACH if you have attached an image by URL.

    + +

    If you're using Google Calendar you can do the following: +

      +
    1. in the iCalendar-to-Event app, in Advanced options (or Edit for an existing subscription), write 'ATTACH' (without the quotations marks) in the Picture field
    2. +
    3. in Google Calendar, click on the Labs icon in the upper right corner (the green potion)
    4. +
    5. enable the Event attachments feature.
    6. +
    7. now, when you create a new event you can add an attachement. choose an image
    8. +
    +

    + +

    Subscription was deactivated

    +

    If the app always encounters an error when updating a subscription for an extended period of time, that subscription will be deactivated. It will say 'deactivated' in the iCalendar-to-Event app subscriptions list. You can try to reactivate it there which will only work if the error has been fixed. This is done as to not bother my server with checking lots of ics-files that don't work and aren't used anymore.

    + + +

    Miscellaneous/FAQ

    +
      +
    • Don't add a calendar with lots of event, remove it again, add it again etc. Facebook imposes certain limits and they don't like adding/removing lots of event too fast. When they decide you posted too much too quick the App will simply fail to create new events for you and usually there is a cryptic message in your Log.
    • +
    • Facebook doesn't support adding events that have a starttime in the past. So currently I've set the App to post only events from now until 3 months into the future.
    • +
    • If you change events in the ical file the facebook events will get updated but not the other way around. Also if you delete an event in your calendar it will remain on facebook.
    • +
    • New events are usually created every four hours or so.
    • +
    • If the events in your calendar are encoded in UTC (i.e. have a Z at the end) your calendar should have a calendar-timezone. Either VTIMEZONE or X-WR-TIMEZONE work fine. Otherwise the app won't know in what time you want the events displayed (and no, facebook unfortunately doesn't support timezones).
    • +
    • If you see post of events on your wall it is Facebook's own 'Events' App that is posting to your wall and not my app. While I currently don't know of any way to disable that behaviour for your personal page, the following works for pages: +
        +
      1. Go to your page and click 'Edit page'
      2. +
      3. Select 'Apps' in the list on the left
      4. +
      5. At the Events app click 'Edit settings' -> 'Additional Permissions' and deselect 'Publish content to my wall'
      6. +
      +
    • +
    • Internet Explorer might have some problems with this app. Get a decent browser like Firefox or Chrome.
    • +
    • Privacy Policy
    • +
    + +
    +

    Future development

    +

    Planned Features

    +

    Here is a list of planned features and options that are not yet supported:

    +
      +
    • support recurring events in ics files (there's a fundraiser for that)
    • +
    • associate a picture with a subscription which then will be added to every event created
    • +
    • your default RSVP: not attending
    • +
    • invite fans
    • +
    + + +

    Support

    +

    I'm working on this app in my spare time and running it on my private server. Please feel free to donate some money. Thank you!

    + + diff --git a/pages/showErrorLogPage.php b/pages/showErrorLogPage.php new file mode 100644 index 0000000..b61caac --- /dev/null +++ b/pages/showErrorLogPage.php @@ -0,0 +1,53 @@ +getSubscription($subId, $fbUserId); + +$calUrl = $sub->calUrl; + +include 'headerInclude.php'; + +function printLog($log) { + + echo ''; + echo' '; + echo' '; + echo' '; + echo' '; + echo ' '; + echo ''; + while ($row = $log->fetch()) { + echo ''; + echo ' '; + echo ' '; + echo ' '; + echo ''; + } + echo ' '; + echo '
    Log IdTimeMessage
    ' . $row->logId . '' . date("r", $row->timestamp) . '' . $row->message . '
    '; +} +?> + +

    Error Log for Subscription subName ?>

    + +

    The calendar URL is .

    +

    Subscription ID:

    + +

    Errors

    +selectErrorLog($subId)); +?> + + +

    Infos

    +selectInfoLog($subId)); + +include 'footerInclude.php'; +?> diff --git a/pages/showPolicy.php b/pages/showPolicy.php new file mode 100644 index 0000000..23d9d18 --- /dev/null +++ b/pages/showPolicy.php @@ -0,0 +1,12 @@ + + +

    Privacy Policy

    + +

    This application stores potentially all information entered by the user, including the contents of the subscribed iCalendar feeds. This is required in order to post the events to Facebook without duplicates which is the sole use of the data. No user data will be displayed, shared or transferred to third parties except for the obvious and intended use to post the events and their data to Facebook.

    + + \ No newline at end of file diff --git a/pages/showPricing.php b/pages/showPricing.php new file mode 100644 index 0000000..805a4e5 --- /dev/null +++ b/pages/showPricing.php @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BasicAdvanced
    Calendars to subscribe1infinite
    Import events fromnext three monthsset own timeframe
    Recurring eventsnoyes
    Pricefree10$/month
    + diff --git a/pages/subscribeToiCalendarPage.php b/pages/subscribeToiCalendarPage.php new file mode 100644 index 0000000..f20ba40 --- /dev/null +++ b/pages/subscribeToiCalendarPage.php @@ -0,0 +1,169 @@ +getSubscription($subId, $fbUserId); + + $subName = $sub->subName; + $imageProperty = $sub->imageProperty; +} else { + $editSub = false; + + if( isset($_GET['subName'])) + $subName = $_GET['subName']; + else + $subName = ''; + + if( isset($_GET['imageProperty'])) + $imageProperty = $_GET['imageProperty']; + else + $imageProperty = ''; + + if( isset($_GET['iCalendarUrl'])) + $iCalendarUrl = $_GET['iCalendarUrl']; + else + $iCalendarUrl = ''; + + if( isset($_GET['pageId'])) + $pageId = $_GET['pageId']; + else + $pageId = 0; +} + +if(!$editSub) { + //get the pages of the user + $token = array( + 'access_token' => $database->getAccessToken($fbUserId) + ); + try { + $userPages = $facebook->api('/me/accounts', 'GET', $token); + + //get groups + try { + $userGroups = $facebook->api('/me/groups', 'GET', $token); + + $userPagesLoaded = true; + + } catch (FacebookApiException $e) { + $logger->error("Could not get list of user's groups for display in GUI.", $e); + $userPagesLoaded = false; + } + } catch (FacebookApiException $e) { + $logger->error("Could not get list of user's pages for display in GUI.", $e); + $userPagesLoaded = false; + } +} + +include 'headerInclude.php'; + +if ( isset($_GET['error']) ) { + echo '
    '; + echo $_GET['errorMsg']; + echo'
    '; +} + +?> + +'; + echo ' '; + } else { + echo '
    '; + } +?> +
    +
    Name of subscription (anything meaningful):
    + +
    + + +
    +
    URL/Web address of the iCalendar file:
    + +
    + +
    +
    Create the events on:
    + + Could not get the list of your pages from facebook. You can still subscribe to get the events created on your personal profile page.
    '; + } + ?> +
    + + +