consider all of a note's links when building an access list
[planner-el.git] / planner-authz.el
blob95ce27db04359b5e10b491a78df7d81260208635
1 ;;; planner-authz.el --- restrict portions of published pages
3 ;; Copyright (C) 2004, 2005 Andrew J. Korty <ajk@iu.edu>
4 ;; Parts copyright (C) 2004, 2005 Free Software Foundation, Inc.
6 ;; Emacs Lisp Archive Entry
7 ;; Filename: planner-authz.el
8 ;; Keywords: hypermedia
9 ;; Author: Andrew J. Korty <ajk@iu.edu>
10 ;; Maintainer: Andrew J. Korty <ajk@iu.edu>
11 ;; Description: Control access to portions of published planner pages
12 ;; URL:
13 ;; Compatibility: Emacs21
15 ;; This file is not part of GNU Emacs.
17 ;; This is free software; you can redistribute it and/or modify it under
18 ;; the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2, or (at your option) any later
20 ;; version.
22 ;; This is distributed in the hope that it will be useful, but WITHOUT
23 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
24 ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
25 ;; for more details.
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with GNU Emacs; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
32 ;;; Commentary:
34 ;; This library lets you publish your planner pages while controlling
35 ;; access to certain portions of them to users you specify. When you
36 ;; load this library, you gain access to two additional markup
37 ;; directives to use in your planner pages. The <authz> tag lets you
38 ;; restrict access to arbitrary content as follows:
40 ;; Here is a sentence everyone should see. This sentence also
41 ;; contains no sensitive data whatsoever. <authz users="ajk">This
42 ;; sentence, however, talks about my predilection for that French
43 ;; vanilla instant coffee that comes in the little tin, and I'm
44 ;; embarrassed for anyone else to know about that.</authz> And
45 ;; here's some more perfectly innocuous content.
47 ;; You can use <authz> tags to mark up entire paragraphs, tasks,
48 ;; notes, and anything else. The tags are replaced with Mason code by
49 ;; default, but you could add support for some other templating system
50 ;; by configuring planner-authz-mason-markup-strings and
51 ;; planner-authz-after-publish-hook.
53 ;; The #authz directive restricts access to an entire page. It will
54 ;; generate a 403 error when someone not listed tries to access it.
55 ;; Any notes or tasks on a #authz-protected page are also wrapped in
56 ;; authorization controls on linked pages.
58 ;; * Diary Markup
60 ;; If your pages have a section with diary entries maintained by
61 ;; planner-appt.el (or by any other means), you can control access to
62 ;; these entries. First, customize sectionalize-markup-tagname to map
63 ;; your diary section to a tag called "diary", for example:
65 ;; (add-to-list 'sectionalize-markup-tagname '("* Schedule" . "diary"))
67 ;; Then make sure the diary entries you want restricted contain a
68 ;; corresponding plan page name in parentheses, for example:
70 ;; 10:00 10:30 Meeting with boss (WorkStuff)
72 ;; * Startup
74 ;; Add the following to your .emacs file to cause
75 ;; M-x muse-project-publish to automatically use planner-authz
76 ;; features.
78 ;; (require 'planner-authz)
80 ;; * Customization
82 ;; All user-serviceable options can be customized with
83 ;; M-x customize-group RET planner-authz RET.
85 ;; * Defaults
87 ;; The following customization options let you set default access
88 ;; lists for pages that don't have explicit settings:
90 ;; planner-authz-project-default
92 ;; Default access list for project pages (not day pages). If a
93 ;; given project page doesn't contain a #authz tag, it will receive
94 ;; the access list defined here. If this variable is nil, all users
95 ;; will be allowed to view the page. No corresponding variable is
96 ;; provided for day pages because it doesn't seem like you'd ever
97 ;; want to control access based on what day it was. (But I will
98 ;; accept patches. :) Notes and tasks referencing pages without
99 ;; #authz tags will also be restricted to the users listed here.
101 ;; planner-authz-day-note-default
103 ;; Default access list for notes on day pages not associated with
104 ;; any project. There is way to set a default for notes on project
105 ;; pages for the reason above; they would only be associated with
106 ;; date pages anyway.
108 ;; planner-authz-day-task-default
110 ;; Same as above but for tasks.
112 ;;; Todo
114 ;; - Make more specific tags override less specific ones, rather than
115 ;; more restrictive overriding less restrictive
117 ;;; Code
119 (require 'planner-publish)
121 ;; Customization options
123 (defgroup planner-authz nil
124 "A planner.el extension for restricting portions of your
125 published pages to specified users."
126 :group 'planner
127 :prefix "planner-authz")
129 (defcustom planner-authz-after-publish-hook
130 '(planner-authz-generate-mason-component)
131 "Functions called after all pages have been published."
132 :group 'planner-authz
133 :type 'hook)
135 (defcustom planner-authz-appt-alt nil
136 "If non-nil, show `planner-appt' appointments to users not
137 authorized to see them, but replace the text of the appointment with
138 the contents of this variable. If nil, don't show any part of an
139 appointment to an unauthorized user.
141 For example, if this variable is set to \"Private appointment\" and
142 some hypothetical user is not authorized for the SecretStuff page, an
143 appointment that was entered as
145 #A1 _ @10:00 12:00 Secret meeting (SecretStuff)
147 would appear to our unauthorized user as
149 #A1 _ @10:00 12:00 Private appointment"
150 :group 'planner-authz
151 :type '(choice (string :tag "Replacement text")
152 (const :tag "Disable" nil)))
154 (defcustom planner-authz-appt-regexp
155 (if (require 'planner-appt nil t)
156 (concat "\\(?:[@!][ \t]*\\)?\\(?:" planner-appt-time-regexp
157 "\\|&nbsp;\\)\\(?:[ \t|]+\\(?:" planner-appt-time-regexp
158 "\\|&nbsp;\\)\\)?[ \t|]+"))
159 "Regexp that matches a `planner-appt' start and end time specification."
160 :group 'planner-authz
161 :type 'string)
163 (defcustom planner-authz-day-note-default nil
164 "Default list of users for restricting non-project notes on day pages."
165 :group 'planner-authz
166 :type '(repeat string))
168 (defcustom planner-authz-day-task-default nil
169 "Default list of users for restricting non-project tasks on day pages."
170 :group 'planner-authz
171 :type '(repeat string))
173 (defcustom planner-authz-link-regexp
174 (concat "(\\(" muse-explicit-link-regexp
175 (if (boundp 'muse-wiki-wikiword-regexp)
176 (concat "\\|" muse-wiki-wikiword-regexp))
177 "\\|" muse-implicit-link-regexp "\\))$")
178 "Regexp that matches the plan page link at the end of a line in a
179 task or diary entry."
180 :group 'planner-authz
181 :type '(string))
183 (defcustom planner-authz-mason-component-contents
184 "<%once>
185 sub authz {
186 my $r_user = $r ? $r->connection->user
187 : $ENV{REMOTE_USER} or return 0;
188 foreach (@_) { return 1 if $r_user eq $_ }
189 return 0;
191 </%once>
192 <%method content>
193 <%args>
194 $alt => undef
195 @users
196 </%args>
197 % if (authz @users) {
198 <% $m->content %>\\
199 % } elsif ($alt) {
200 <% $alt %>\\
202 </%method>
203 <%method page>
204 <%args>@users</%args>
205 <%perl>
206 unless (authz @users) {
207 $m->clear_buffer;
208 $m->abort(404);
210 </%perl>
211 </%method>
213 "Mason code to be stored in a component.
214 The component's name is determined from
215 `planner-authz-mason-component-name'."
216 :group 'planner-authz
217 :type 'string)
219 (defcustom planner-authz-mason-component-name "authz.mas"
220 "Name of Mason component that restricts content."
221 :group 'planner-authz
222 :type 'string)
224 (defcustom planner-authz-multi-func 'planner-authz-multi-union
225 "*Function used to combine access lists for multiple planner pages.
227 When `planner-multi' is in effect and a task or note is linked to
228 multiple plan pages, `planner-authz' uses this function to decide how
229 to build the access list for the task or note from the access lists of
230 the linked pages.
232 It is passed a list of sublists, each sublist being the access list (a
233 list of usernames) for one of the linked pages. It should return a
234 combined single list of usernames.
236 Two such functions are provided: `planner-authz-multi-intersection'
237 returns only those user names that are common to all the access lists
238 for all the linked pages, and `planner-authz-multi-union' returns a
239 list of all the unique user names in any of those access lists."
240 :group 'planner-authz
241 :type '(radio (function-item :tag "Intersection"
242 planner-authz-multi-intersection)
243 (function-item :tag "Union" planner-authz-multi-union)
244 (function :tag "Other")))
247 (defcustom planner-authz-project-default nil
248 "Default list of users for restricting project pages if #authz is nil."
249 :group 'planner-authz
250 :type '(repeat string))
252 (defcustom planner-authz-sections-regexp "^\\([*]\\)+\\s-+\\(.+\\)"
253 "Regexp that matches headings for sections authorization markup."
254 :group 'planner-authz
255 :type '(string))
257 (defcustom planner-authz-sections-rule-list nil
258 "List of sections and their access rule.
260 Each rule is a sublist of the form:
262 (SECTION-NAME PREDICTION USER-LIST)
264 For sections matching SECTION-NAME, if the PREDICTION is t or a
265 function return t, that section will be accessable for users in
266 USER-LIST only.
268 The following example will make the \"Timeclock\" section and
269 \"Accomplishments\" section on day pages only accessable by user1 and
270 user2, while on plan pages obey the \"parent\" rule.
272 ((\"Timeclock\" planner-authz-day-p
273 (\"user1\" \"user2\"))
274 (\"Accomplishments\" planner-authz-day-p
275 (\"user1\" \"user2\")))"
276 :group 'planner-authz
277 :type '(repeat (regexp (choice boolean function))
278 (repeat string)))
280 (defcustom planner-authz-markup-functions
281 '((table . planner-authz-mason-markup-table))
282 "An alist of style types to custom functions for that kind of text."
283 :group 'planner-authz
284 :type '(alist :key-type symbol :value-type function))
286 (defcustom planner-authz-markup-tags
287 '(("authz" t t planner-authz-tag)
288 ("diary" t t planner-authz-diary-tag)
289 ("note" t t planner-authz-note-tag)
290 ("task" t t planner-authz-task-tag))
291 "A list of tag specifications for authorization markup."
292 :group 'planner-authz
293 :type '(repeat (list (string :tag "Markup tag")
294 (boolean :tag "Expect closing tag" :value t)
295 (boolean :tag "Parse attributes" :value nil)
296 function)))
298 (defcustom planner-authz-mason-markup-strings
299 '((planner-authz-begin . "<&| authz.mas:content, 'users', [qw(%s)] &>")
300 (planner-authz-begin-alt
301 . "<&| authz.mas:content, 'users', [qw(%s)], 'alt', '%s' &>")
302 (planner-authz-end . "</&>")
303 (planner-authz-page . "<& authz.mas:page, 'users', [qw(%s)] &>"))
304 "Strings used for additing authorization controls.
306 If a markup rule is not found here, `planner-html-markup-strings' is
307 searched."
308 :type '(alist :key-type symbol :value-type string)
309 :group 'planner-authz)
311 ;; Non-customizable variables
313 (defvar planner-authz-pages nil
314 "Alist of planner pages and users authorized to view them.
315 The list of users is separated by spaces. This variable is
316 internal to planner-authz; do not set it manually.")
317 (defvar planner-authz-pages-to-republish nil
318 "Queue of planner pages to republish when finished with current round.
319 Used to markup planner day pages that wouldn't ordinarily get
320 republished because they haven't explicitly changed. This
321 variable is internal to planner-authz; do not set it manually.")
322 (defvar planner-authz-disable-republishing nil
323 "If non-nil, `planner-authz' will not republish pages linked to the page currently being published.
324 Normally, linked pages are republished in case the access list for the
325 current page has changed. This variable is set to t while
326 `planner-authz' is republishing pages to avoid indefinite recursion.")
328 ;;; Functions
330 (defun planner-authz-after-markup ()
331 "Remove the page currently being marked up from the queue of pages
332 to republish and enforce default access controls for project pages."
333 (let ((page (planner-page-name)))
334 (when page
335 (delete page planner-authz-pages-to-republish)
336 (let ((users (planner-authz-users)))
337 (when users
338 (goto-char (point-min))
339 (planner-insert-markup (muse-markup-text 'planner-authz-page users))
340 (insert "\n"))))))
342 (defun planner-authz-after-project-publish (project)
343 "Republish pages that reference restricted pages and call the
344 generate Mason code."
345 (when (string= planner-project (car project))
346 (let ((planner-authz-disable-republishing t)
347 file)
348 (while (setq file (pop planner-authz-pages-to-republish))
349 (muse-project-publish-file file planner-project t)))
350 (run-hook-with-args 'planner-authz-after-publish-hook project)))
352 (defun planner-authz-before-markup ()
353 "Process #authz directives when publishing only a single page. Mark
354 planner page sections according to
355 `planner-authz-sections-rule-list'."
356 (planner-authz-markup-all-sections))
358 (defun planner-authz-day-p (&optional page)
359 "Return non-nil if the current page or PAGE is a day page."
360 (save-match-data
361 (string-match planner-date-regexp (or page (planner-page-name)))))
363 (defun planner-authz-default (page)
364 "Return the default space-separated string of users that would apply
365 to PAGE. Nil is always returned for day pages."
366 (and planner-authz-project-default
367 (not (planner-authz-day-p page)) ; not on day pages
368 (mapconcat 'identity planner-authz-project-default " ")))
370 (defun planner-authz-file-alist (users)
371 "Generate a list of planner files that USERS have access to."
372 (let ((pages (planner-file-alist))
373 result)
374 (while pages
375 (let (not-found-p)
376 (with-temp-buffer
377 (insert-file-contents-literally (cdar pages))
378 (when (re-search-forward "^#authz\\s-+\\(.+\\)\n+" nil t)
379 (let ((users-iter users)
380 (authz (split-string (match-string 1))))
381 (while (and users-iter (not not-found-p))
382 (unless (member (car users-iter) authz)
383 (setq not-found-p t))
384 (setq users-iter (cdr users-iter)))))
385 (unless not-found-p
386 (setq result (append (list (car pages)) result))))
387 (setq pages (cdr pages))))
388 result))
390 (defun planner-authz-generate-mason-component (project)
391 "Generate the Mason component restricting content.
392 The component's name is taken from
393 `planner-authz-mason-component-name' and initialized with the
394 contents of `planner-authz-mason-component-contents'. The
395 component restricts access to users specified by <authz> and
396 #authz tags."
397 (with-temp-buffer
398 (insert planner-authz-mason-component-contents)
399 (let ((backup-inhibited t)
400 (styles (cddr project)))
401 (while styles
402 (let ((path (muse-style-element :path (car styles))))
403 (and path
404 (string-match "mason" (muse-style-element :base (car styles)))
405 (write-file
406 (concat (file-name-directory path)
407 planner-authz-mason-component-name))))
408 (setq styles (cdr styles))))))
410 (defun planner-authz-markup-section-predict (rule)
411 "Check if the prediction is satisfied."
412 (let ((predict (elt rule 1)))
413 (if (functionp predict)
414 (funcall predict)
415 predict)))
417 (defun planner-authz-markup-section ()
418 "Restrict section according to `planner-authz-sections-rule-list'."
419 (let ((begin (planner-line-beginning-position))
420 (rule-list planner-authz-sections-rule-list)
421 section-name
422 section-level
423 next-section-regexp)
424 (goto-char begin)
425 (save-match-data
426 (re-search-forward planner-authz-sections-regexp nil t)
427 (setq section-level (length (match-string 1)))
428 (setq section-name (match-string 2)))
429 (let ((rule (catch 'done
430 (while rule-list
431 (if (string-match (caar rule-list) section-name)
432 (throw 'done (car rule-list))
433 (setq rule-list (cdr rule-list))))
434 nil)))
435 (if (and rule
436 (planner-authz-markup-section-predict rule))
437 (progn
438 (goto-char begin)
439 (muse-publish-surround-text
440 (format "<authz users=\"%s\">\n"
441 (mapconcat 'identity (elt rule 2) " "))
442 "\n</authz>\n"
443 (lambda ()
444 (save-match-data
445 (let ((found nil))
446 (re-search-forward planner-authz-sections-regexp nil t)
447 (while (and (not found)
448 (re-search-forward planner-authz-sections-regexp
449 nil t))
450 (if (<= (length (match-string 1))
451 section-level)
452 (setq found t)))
453 (if found
454 (goto-char (planner-line-beginning-position))
455 (goto-char (point-max))))))))))))
457 (defun planner-authz-markup-all-sections ()
458 "Run `planner-authz-markup-section' on the entire buffer."
459 (goto-char (point-min))
460 (while (re-search-forward planner-authz-sections-regexp nil t)
461 (planner-authz-markup-section)))
463 (defun planner-authz-mason-markup-table ()
464 "Protect \"<&|\" Mason constructs from Muse table markup."
465 (let* ((beg (planner-line-beginning-position))
466 (style (muse-style-element :base (muse-style)))
467 (base (if style
468 (muse-style-element :base style)))
469 (func (if base
470 (muse-find-markup-element
471 :functions 'table (muse-style-element :base base)))))
472 (when (functionp func)
473 (save-excursion
474 (save-match-data
475 (goto-char beg)
476 (while (search-forward "<&|" (line-end-position) t)
477 (replace-match "<&:" t t))))
478 (funcall func)
479 (let ((end (point)))
480 (goto-char beg)
481 (while (search-forward "<&:" end t)
482 (replace-match "<&|" t t))))))
484 (defun planner-authz-index-as-string (&optional as-list exclude-private)
485 "Generate an index of all Muse pages with authorization controls.
486 In the published index, only those links to pages which the remote
487 user is authorized to access will be shown.
488 If AS-LIST is non-nil, insert a dash and spaces before each item.
489 If EXCLUDE-PRIVATE is non-nil, exclude files that have private permissions.
490 If EXCLUDE-CURRENT is non-nil, exclude the current file from the output."
491 (with-temp-buffer
492 (insert (planner-index-as-string as-list exclude-private))
493 (goto-char (point-min))
494 (while (and (re-search-forward
495 (if as-list
496 (concat "^[" muse-regexp-blank "]+\\(-["
497 muse-regexp-blank "]*\\)")
498 (concat "^\\([" muse-regexp-blank "]*\\)"))
499 nil t)
500 (save-match-data (looking-at muse-explicit-link-regexp)))
501 (when as-list
502 (let ((func (muse-markup-function 'list)))
503 (if (functionp func)
504 (save-excursion (funcall func))))
505 (re-search-forward "<li" nil t)
506 (goto-char (match-beginning 0)))
507 (let* ((link (buffer-substring (point) (line-end-position)))
508 (pages (mapcar 'planner-link-base
509 (if (featurep 'planner-multi)
510 (planner-multi-split link)
511 link)))
512 (users (if pages (planner-authz-multi-users pages))))
513 (when users
514 (planner-insert-markup (muse-markup-text
515 'planner-authz-begin users))
516 (if as-list
517 (re-search-forward "</li>" nil t)
518 (end-of-line))
519 (planner-insert-markup (muse-markup-text 'planner-authz-end)))))
520 (buffer-substring (point-min) (point-max))))
522 (defun planner-authz-republish-pages-maybe (linked-pages)
523 "Remember LINKED-PAGES to be republished later.
524 The pages will be republished if and only if the current page is
525 restricted."
526 (and (not planner-authz-disable-republishing)
527 (planner-authz-users)
528 (while linked-pages
529 (add-to-list 'planner-authz-pages-to-republish
530 (planner-page-file (car linked-pages)))
531 (setq linked-pages (cdr linked-pages)))))
533 (defun planner-authz-tag (beg end attrs)
534 "Publish <authz> tags. The region from BEG to END is protected.
535 ATTRS should be an alist of tag attributes including \"users\" and
536 optionally \"alt\" for alternative text to be displayed to
537 unauthorized users."
538 (save-excursion
539 (let ((alt (or (cdr (assoc "alt" attrs)) ""))
540 (users (or (cdr (assoc "users" attrs)) "")))
541 (goto-char beg)
542 (planner-insert-markup
543 (if (zerop (length alt))
544 (muse-markup-text 'planner-authz-begin users)
545 (muse-markup-text 'planner-authz-begin-alt users alt)))
546 (goto-char end)
547 (planner-insert-markup (muse-markup-text 'planner-authz-end)))))
549 (defun planner-authz-diary-tag (beg end attrs)
550 "Restrict entries in a diary section."
551 (save-excursion
552 (save-restriction
553 (narrow-to-region beg end)
554 (planner-publish-nested-section-tag beg end)
555 (goto-char beg)
556 (while (and (zerop (forward-line))
557 (= (point) (planner-line-beginning-position)))
558 (unless (looking-at "^\\(?:[ \t]*\\|No entries\\|</div>\\)$")
559 (let ((line-begin (point))
560 (line-end (line-end-position)))
561 (re-search-forward planner-authz-link-regexp line-end t)
562 (let* ((link (match-string 1))
563 (linked-pages (if link
564 (mapcar 'planner-link-base
565 (if (featurep 'planner-multi)
566 (planner-multi-split link)
567 link))))
568 (linked-users
569 (if linked-pages
570 (planner-authz-multi-users linked-pages)
571 (and planner-authz-day-task-default
572 (mapconcat 'identity planner-authz-day-task-default
573 " ")))))
574 (when linked-users
575 (if (and planner-authz-appt-alt planner-authz-appt-regexp
576 (progn
577 (goto-char line-begin)
578 (re-search-forward
579 planner-authz-appt-regexp line-end t)))
580 (progn
581 (search-forward " - " (+ 2 (point)) t)
582 (planner-insert-markup
583 (muse-markup-text 'planner-authz-begin-alt linked-users
584 planner-authz-appt-alt)))
585 (planner-insert-markup
586 (muse-markup-text 'planner-authz-begin linked-users)))
587 (end-of-line)
588 (planner-insert-markup
589 (muse-markup-text 'planner-authz-end))))))))))
591 (defun planner-authz-note-tag (beg end attrs)
592 "Restrict notes linked to a restricted page. If this page is
593 restricted and the note is linked to another page, remember to
594 republish that page later and restrict the note as it appears there.
595 Call `planner-publish-note-tag' as a side effect."
596 (save-excursion
597 (save-restriction
598 (narrow-to-region beg end)
599 (planner-publish-note-tag beg end attrs)
600 (let* ((categories (cdr (assoc "categories" attrs)))
601 (links (if (or (not categories) (zerop (length categories)))
602 (cdr (assoc "link" attrs))
603 categories))
604 (linked-pages (if (and links (not (zerop (length links))))
605 (mapcar 'planner-link-base
606 (if (featurep 'planner-multi)
607 (planner-multi-split links)
608 links))))
609 (linked-users
610 (if linked-pages
611 (planner-authz-multi-users linked-pages)
612 (and planner-authz-day-note-default
613 (planner-authz-day-p)
614 (mapconcat 'identity
615 planner-authz-day-note-default " ")))))
617 ;; If this note is linked to another page, republish that page
618 ;; later to restrict the note as it appears there, providing that
619 ;; page has an authz restriction
621 (if linked-pages
622 (planner-authz-republish-pages-maybe linked-pages))
624 ;; If the linked page has an authz restriction, restrict this note
626 (when linked-users
627 (goto-char (point-min))
628 (planner-insert-markup
629 (muse-markup-text 'planner-authz-begin linked-users))
630 (insert "\n")
631 (goto-char (point-max))
632 (planner-insert-markup (muse-markup-text 'planner-authz-end))
633 (insert "\n"))))))
635 (defun planner-authz-task-tag (beg end attrs)
636 "Restrict tasks linked to restricted pages. If this page is
637 restricted and the task is linked to another page, remember to
638 republish that page later and restrict the task as it appears there.
639 Call `planner-publish-task-tag' as a side effect."
640 (save-excursion
641 (save-restriction
642 (narrow-to-region beg end)
643 (planner-publish-task-tag beg end attrs)
644 (let* ((link (cdr (assoc "link" attrs)))
645 (linked-pages (if link
646 (mapcar 'planner-link-base
647 (if (featurep 'planner-multi)
648 (planner-multi-split link)
649 link))))
650 (linked-users
651 (if linked-pages
652 (planner-authz-multi-users linked-pages)
653 (and planner-authz-day-task-default
654 (planner-authz-day-p)
655 (mapconcat 'identity
656 planner-authz-day-task-default " ")))))
658 ;; If this task is linked to another page, republish that page
659 ;; later to restrict the task as it appears there, providing that
660 ;; page has an authz restriction
662 (if linked-pages
663 (planner-authz-republish-pages-maybe linked-pages))
665 ;; If the linked page has an authz restriction, restrict this task
667 (when linked-users
668 (goto-char (point-min))
669 (planner-insert-markup
670 (muse-markup-text 'planner-authz-begin linked-users))
671 (goto-char (point-max))
672 (planner-insert-markup (muse-markup-text 'planner-authz-end)))))))
674 (defun planner-authz-users (&optional page)
675 "Return a list of acceptable users for PAGE.
676 The list of users is returned as space-separated string, based on
677 a #authz directive appearing in the page. If PAGE contains no
678 #authz directive and is a project page (it doesn't match
679 `planner-date-regexp'), return `planner-authz-project-default' as
680 a space-separated string.
682 If PAGE is nil, return a list of users associated with the
683 current page."
684 (unless page (setq page (planner-page-name)))
685 (let ((match (cdr (assoc page planner-authz-pages))))
686 (unless match
687 (let ((file (cdr (assoc page (planner-file-alist)))))
688 (setq match
689 (or (and file
690 (with-temp-buffer
691 (insert-file-contents-literally file)
692 (if (re-search-forward "^#authz\\s-+\\(.+\\)\n+"
693 nil t)
694 (match-string 1))))
695 (planner-authz-default page))))
696 (push `(,page . ,match) planner-authz-pages))
697 match))
699 (defun planner-authz-multi-intersection (list)
700 "Merge a list of `planner-authz' access lists, returning a list of only those user names that are common to all the passed access lists."
701 (let ((count (length list))
702 alist intersection sublist)
704 ;; in alist, associate each name with its frequency of appearance
705 (while list
706 (setq sublist (car list))
707 (while sublist
708 (let ((entry (assoc (car sublist) alist)))
709 (if entry
710 (setcdr entry (1+ (cdr entry)))
711 (setq alist (cons `(,(car sublist) . 1) alist))))
712 (setq sublist (cdr sublist)))
713 (setq list (cdr list)))
715 ;; those names with `count' frequencies were in every sublist
716 (while alist
717 (if (= (cdar alist) count)
718 (setq intersection (cons (caar alist) intersection)))
719 (setq alist (cdr alist)))
720 intersection))
722 (defun planner-authz-multi-union (list)
723 "Merge a list of `planner-authz' access lists, returning a list of all the unique user names in any of those access lists."
724 (let (union)
725 (while list
726 (setq sublist (car list))
727 (while sublist
728 (add-to-list 'union (car sublist))
729 (setq sublist (cdr sublist)))
730 (setq list (cdr list)))
731 union))
733 (defun planner-authz-multi-users (pages)
734 "Return a merged access list for PAGES.
735 The list of users is returned as space-separated string, based on a
736 #authz directive appearing in the PAGES. If one of PAGES contains no
737 #authz directive and is a project page (it doesn't match
738 `planner-date-regexp'), it will contribute
739 `planner-authz-project-default' to the merge."
740 (let ((users
741 (funcall planner-authz-multi-func
742 (mapcar (lambda (page)
743 (if (not (planner-authz-day-p page))
744 (let ((users (planner-authz-users page)))
745 (if users
746 (split-string users)))))
747 pages))))
748 (if users
749 (mapconcat 'identity users " "))))
751 (add-hook 'muse-after-project-publish-hook
752 'planner-authz-after-project-publish)
754 (let ((styles (list "html" "xhtml")))
755 (while styles
756 (let ((style (concat "planner-authz-mason-" (car styles))))
757 (unless (assoc style muse-publishing-styles)
758 (muse-derive-style
759 style (concat "planner-" (car styles))
760 :before 'planner-authz-before-markup
761 :after 'planner-authz-after-markup
762 :functions 'planner-authz-markup-functions
763 :strings 'planner-authz-mason-markup-strings
764 :tags (append planner-authz-markup-tags
765 planner-publish-markup-tags))))
766 (setq styles (cdr styles))))
768 (provide 'planner-authz)
770 ;;; planner-authz.el ends here