Merged from mwolson@gnu.org--2006 (patch 34)
[planner-el.git] / planner-authz.el
blob6582ad5e62ac3bbaadae3b09a010bca50ce5d602
1 ;;; planner-authz.el --- restrict portions of published pages
3 ;; Copyright (C) 2004, 2005, 2006 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 part of Planner. It is not part of GNU Emacs.
17 ;; Planner is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by
19 ;; the Free Software Foundation; either version 2, or (at your option)
20 ;; any later version.
22 ;; Planner is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
24 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
25 ;; General Public License for more details.
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with Planner; 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-regexps
281 '((2300 "\\(<li>\\)\\(<&| [^<]*>\\)\\(.*\\)\\(</&>\\)\\(</li>\\)" 3
282 planner-authz-fix-list-item))
283 "List of markup rules for publishing PLANNER with `planner-authz' restrictions.
284 For more on the structure of this list, see `muse-publish-markup-regexps'."
285 :group 'planner-authz
286 :type '(repeat (choice
287 (list :tag "Markup rule"
288 integer
289 (choice regexp symbol)
290 integer
291 (choice string function symbol))
292 function)))
294 (defcustom planner-authz-markup-functions
295 '((table . planner-authz-mason-markup-table))
296 "An alist of style types to custom functions for that kind of text."
297 :group 'planner-authz
298 :type '(alist :key-type symbol :value-type function))
300 (defcustom planner-authz-markup-tags
301 '(("authz" t t planner-authz-tag)
302 ("diary" t t planner-authz-diary-tag)
303 ("note" t t planner-authz-note-tag)
304 ("task" t t planner-authz-task-tag))
305 "A list of tag specifications for authorization markup."
306 :group 'planner-authz
307 :type '(repeat (list (string :tag "Markup tag")
308 (boolean :tag "Expect closing tag" :value t)
309 (boolean :tag "Parse attributes" :value nil)
310 function)))
312 (defcustom planner-authz-mason-markup-strings
313 '((planner-authz-begin . "<&| authz.mas:content, 'users', [qw(%s)] &>")
314 (planner-authz-begin-alt
315 . "<&| authz.mas:content, 'users', [qw(%s)], 'alt', '%s' &>")
316 (planner-authz-end . "</&>")
317 (planner-authz-page . "<& authz.mas:page, 'users', [qw(%s)] &>"))
318 "Strings used for additing authorization controls.
320 If a markup rule is not found here, `planner-html-markup-strings' is
321 searched."
322 :type '(alist :key-type symbol :value-type string)
323 :group 'planner-authz)
325 ;; Non-customizable variables
327 (defvar planner-authz-pages nil
328 "Alist of planner pages and users authorized to view them.
329 The list of users is separated by spaces. This variable is
330 internal to planner-authz; do not set it manually.")
331 (defvar planner-authz-publishing-alist nil
332 "Alist used by `planner-authz' to track published pages and their dependencies.
333 This alist stores pages that have been published during the current
334 publishing process, as (PAGENAME . t), and pages whose tasks and notes
335 depend on those pages for access control, as (PAGENAME . nil). At the
336 end of publishing, `planner-authz' uses this alist to determine which
337 dependencies need to be republished, even if they themselves haven't
338 changed.")
339 (defvar planner-authz-disable-dependency-publishing nil
340 "If non-nil, `planner-authz' will not republish unchanged pages whose tasks or notes depend on the page currently being published.
341 Normally, linked pages are republished in case the access list for the
342 current page has changed. This variable is set to t while
343 `planner-authz' is republishing dependent pages to avoid indefinite
344 recursion.")
346 ;;; Functions
348 (defun planner-authz-after-markup ()
349 "Remove the page currently being marked up from the queue of pages
350 to republish and enforce default access controls for project pages."
351 (let ((page (planner-page-name)))
352 (when page
354 (let ((cell (assoc page planner-authz-publishing-alist)))
355 (if cell
357 ;; if already t, the list is stale; whack it
358 (if (cdr cell)
359 (setq planner-authz-publishing-alist '(page . t)))
361 (push '(page . t) planner-authz-publishing-alist)))
363 (let ((users (planner-authz-users)))
364 (when users
365 (goto-char (point-min))
366 (planner-insert-markup (muse-markup-text 'planner-authz-page users))
367 (insert "\n"))))))
369 (defun planner-authz-after-project-publish (project)
370 "Republish pages that reference restricted pages and call the
371 generate Mason code."
372 (when (string= planner-project (car project))
373 (while planner-authz-publishing-alist
374 (if (not (cdar planner-authz-publishing-alist))
375 (let ((planner-authz-disable-dependency-publishing t))
376 (muse-project-publish-file (caar planner-authz-publishing-alist)
377 planner-project t)))
378 (setq planner-authz-publishing-alist
379 (cdr planner-authz-publishing-alist)))
380 (run-hook-with-args 'planner-authz-after-publish-hook project)))
382 (defun planner-authz-before-markup ()
383 "Process #authz directives when publishing only a single page. Mark
384 planner page sections according to
385 `planner-authz-sections-rule-list'."
386 (planner-authz-markup-all-sections))
388 (defun planner-authz-day-p (&optional page)
389 "Return non-nil if the current page or PAGE is a day page."
390 (save-match-data
391 (string-match planner-date-regexp (or page (planner-page-name)))))
393 (defun planner-authz-default (page)
394 "Return the default space-separated string of users that would apply
395 to PAGE. Nil is always returned for day pages."
396 (and planner-authz-project-default
397 (not (planner-authz-day-p page)) ; not on day pages
398 (mapconcat 'identity planner-authz-project-default " ")))
400 (defun planner-authz-file-alist (users)
401 "Generate a list of planner files that USERS have access to."
402 (let ((pages (planner-file-alist))
403 result)
404 (while pages
405 (let (not-found-p)
406 (with-temp-buffer
407 (insert-file-contents-literally (cdar pages))
408 (when (re-search-forward "^#authz\\s-+\\(.+\\)\n+" nil t)
409 (let ((users-iter users)
410 (authz (split-string (match-string 1))))
411 (while (and users-iter (not not-found-p))
412 (unless (member (car users-iter) authz)
413 (setq not-found-p t))
414 (setq users-iter (cdr users-iter)))))
415 (unless not-found-p
416 (setq result (append (list (car pages)) result))))
417 (setq pages (cdr pages))))
418 result))
420 (defun planner-authz-fix-list-item ()
421 "Rearrange list items restricted by `planner-authz' to avoid empty list items on the published page."
422 (replace-match "\\2\\1\\3\\5\\4")
423 (muse-publish-mark-read-only (match-beginning 0) (match-end 2))
424 (muse-publish-mark-read-only (match-beginning 4) (match-end 0)))
426 (defun planner-authz-generate-mason-component (project)
427 "Generate the Mason component restricting content.
428 The component's name is taken from
429 `planner-authz-mason-component-name' and initialized with the
430 contents of `planner-authz-mason-component-contents'. The
431 component restricts access to users specified by <authz> and
432 #authz tags."
433 (with-temp-buffer
434 (insert planner-authz-mason-component-contents)
435 (let ((backup-inhibited t)
436 (styles (cddr project)))
437 (while styles
438 (let ((path (muse-style-element :path (car styles))))
439 (and path
440 (string-match "mason" (muse-style-element :base (car styles)))
441 (write-file
442 (concat (file-name-directory path)
443 planner-authz-mason-component-name))))
444 (setq styles (cdr styles))))))
446 (defun planner-authz-markup-section-predict (rule)
447 "Check if the prediction is satisfied."
448 (let ((predict (elt rule 1)))
449 (if (functionp predict)
450 (funcall predict)
451 predict)))
453 (defun planner-authz-markup-section ()
454 "Restrict section according to `planner-authz-sections-rule-list'."
455 (let ((begin (planner-line-beginning-position))
456 (rule-list planner-authz-sections-rule-list)
457 section-name
458 section-level
459 next-section-regexp)
460 (goto-char begin)
461 (save-match-data
462 (re-search-forward planner-authz-sections-regexp nil t)
463 (setq section-level (length (match-string 1)))
464 (setq section-name (match-string 2)))
465 (let ((rule (catch 'done
466 (while rule-list
467 (if (string-match (caar rule-list) section-name)
468 (throw 'done (car rule-list))
469 (setq rule-list (cdr rule-list))))
470 nil)))
471 (if (and rule
472 (planner-authz-markup-section-predict rule))
473 (progn
474 (goto-char begin)
475 (muse-publish-surround-text
476 (format "<authz users=\"%s\">\n"
477 (mapconcat 'identity (elt rule 2) " "))
478 "\n</authz>\n"
479 (lambda ()
480 (save-match-data
481 (let ((found nil))
482 (re-search-forward planner-authz-sections-regexp nil t)
483 (while (and (not found)
484 (re-search-forward planner-authz-sections-regexp
485 nil t))
486 (if (<= (length (match-string 1))
487 section-level)
488 (setq found t)))
489 (if found
490 (goto-char (planner-line-beginning-position))
491 (goto-char (point-max))))))))))))
493 (defun planner-authz-markup-all-sections ()
494 "Run `planner-authz-markup-section' on the entire buffer."
495 (goto-char (point-min))
496 (while (re-search-forward planner-authz-sections-regexp nil t)
497 (planner-authz-markup-section)))
499 (defun planner-authz-mason-markup-table ()
500 "Protect \"<&|\" Mason constructs from Muse table markup."
501 (let* ((beg (planner-line-beginning-position))
502 (style (muse-style-element :base (muse-style)))
503 (base (if style
504 (muse-style-element :base style)))
505 (func (if base
506 (muse-find-markup-element
507 :functions 'table (muse-style-element :base base)))))
508 (when (functionp func)
509 (save-excursion
510 (save-match-data
511 (goto-char beg)
512 (while (search-forward "<&|" (line-end-position) t)
513 (replace-match "<&:" t t))))
514 (funcall func)
515 (let ((end (point)))
516 (goto-char beg)
517 (while (search-forward "<&:" end t)
518 (replace-match "<&|" t t))))))
521 (defun planner-authz-index-as-string (&optional as-list exclude-private)
522 "Generate an index of all Muse pages with authorization controls.
523 In the published index, only those links to pages which the remote
524 user is authorized to access will be shown.
525 If AS-LIST is non-nil, insert a dash and spaces before each item.
526 If EXCLUDE-PRIVATE is non-nil, exclude files that have private permissions.
527 If EXCLUDE-CURRENT is non-nil, exclude the current file from the output."
528 (with-temp-buffer
529 (insert (planner-index-as-string as-list exclude-private))
530 (when muse-publishing-p
531 (goto-char (point-min))
532 (while (and (re-search-forward
533 (if as-list
534 (concat "^[" muse-regexp-blank "]+-["
535 muse-regexp-blank "]*")
536 (concat "^[" muse-regexp-blank "]*"))
537 nil t)
538 (looking-at muse-explicit-link-regexp))
539 (let* ((link (buffer-substring (point) (line-end-position)))
540 (page (planner-link-base link))
541 (users (if page (planner-authz-users page))))
542 (if users
543 (progn
544 (insert (format "<authz users=\"%s\">" users))
545 (end-of-line)
546 (insert "</authz>"))
547 (end-of-line)))))
548 (buffer-substring (point-min) (point-max))))
550 (defun planner-authz-republish-dependencies-maybe (linked-pages)
551 "Remember LINKED-PAGES to be republished later.
552 The pages will be republished if and only if the current page is
553 restricted."
554 (and (not planner-authz-disable-dependency-publishing)
555 (planner-authz-users)
556 (while linked-pages
557 (unless (assoc (car linked-pages) planner-authz-publishing-alist)
558 (push '(car linked-pages) planner-authz-publishing-alist))
559 (setq linked-pages (cdr linked-pages)))))
561 (defun planner-authz-tag (beg end attrs)
562 "Publish <authz> tags. The region from BEG to END is protected.
563 ATTRS should be an alist of tag attributes including \"users\" and
564 optionally \"alt\" for alternative text to be displayed to
565 unauthorized users."
566 (save-excursion
567 (let ((alt (or (cdr (assoc "alt" attrs)) ""))
568 (users (or (cdr (assoc "users" attrs)) "")))
569 (goto-char beg)
570 (planner-insert-markup
571 (if (zerop (length alt))
572 (muse-markup-text 'planner-authz-begin users)
573 (muse-markup-text 'planner-authz-begin-alt users alt)))
574 (goto-char end)
575 (planner-insert-markup (muse-markup-text 'planner-authz-end)))))
577 (defun planner-authz-diary-tag (beg end attrs)
578 "Restrict entries in a diary section."
579 (save-excursion
580 (save-restriction
581 (narrow-to-region beg end)
582 (planner-publish-nested-section-tag beg end)
583 (goto-char beg)
584 (while (and (zerop (forward-line))
585 (= (point) (planner-line-beginning-position)))
586 (unless (looking-at "^\\(?:[ \t]*\\|No entries\\|</div>\\)$")
587 (let ((line-begin (point))
588 (line-end (line-end-position)))
589 (re-search-forward planner-authz-link-regexp line-end t)
590 (let* ((link (match-string 1))
591 (linked-pages (if link
592 (mapcar 'planner-link-base
593 (if (featurep 'planner-multi)
594 (planner-multi-split link)
595 link))))
596 (linked-users
597 (if linked-pages
598 (planner-authz-multi-users linked-pages)
599 (and planner-authz-day-task-default
600 (mapconcat 'identity planner-authz-day-task-default
601 " ")))))
602 (when linked-users
603 (if (and planner-authz-appt-alt planner-authz-appt-regexp
604 (progn
605 (goto-char line-begin)
606 (re-search-forward
607 planner-authz-appt-regexp line-end t)))
608 (progn
609 (search-forward " - " (+ 2 (point)) t)
610 (planner-insert-markup
611 (muse-markup-text 'planner-authz-begin-alt linked-users
612 planner-authz-appt-alt)))
613 (planner-insert-markup
614 (muse-markup-text 'planner-authz-begin linked-users)))
615 (end-of-line)
616 (planner-insert-markup
617 (muse-markup-text 'planner-authz-end))))))))))
619 (defun planner-authz-note-tag (beg end attrs)
620 "Restrict notes linked to a restricted page. If this page is
621 restricted and the note is linked to another page, remember to
622 republish that page later and restrict the note as it appears there.
623 Call `planner-publish-note-tag' as a side effect."
624 (save-excursion
625 (save-restriction
626 (narrow-to-region beg end)
627 (planner-publish-note-tag beg end attrs)
628 (let* ((categories (cdr (assoc "categories" attrs)))
629 (links (if (or (not categories) (zerop (length categories)))
630 (cdr (assoc "link" attrs))
631 categories))
632 (linked-pages (if (and links (not (zerop (length links))))
633 (mapcar 'planner-link-base
634 (if (featurep 'planner-multi)
635 (planner-multi-split links)
636 links))))
637 (linked-users
638 (if linked-pages
639 (planner-authz-multi-users linked-pages)
640 (and planner-authz-day-note-default
641 (planner-authz-day-p)
642 (mapconcat 'identity
643 planner-authz-day-note-default " ")))))
645 ;; If this note is linked to another page, republish that page
646 ;; later to restrict the note as it appears there, providing that
647 ;; page has an authz restriction
649 (if linked-pages
650 (planner-authz-republish-dependencies-maybe linked-pages))
652 ;; If the linked page has an authz restriction, restrict this note
654 (when linked-users
655 (goto-char (point-min))
656 (planner-insert-markup
657 (muse-markup-text 'planner-authz-begin linked-users))
658 (insert "\n")
659 (goto-char (point-max))
660 (planner-insert-markup (muse-markup-text 'planner-authz-end))
661 (insert "\n"))))))
663 (defun planner-authz-task-tag (beg end attrs)
664 "Restrict tasks linked to restricted pages. If this page is
665 restricted and the task is linked to another page, remember to
666 republish that page later and restrict the task as it appears there.
667 Call `planner-publish-task-tag' as a side effect."
668 (save-excursion
669 (save-restriction
670 (narrow-to-region beg end)
671 (planner-publish-task-tag beg end attrs)
672 (let* ((link (cdr (assoc "link" attrs)))
673 (linked-pages (if link
674 (mapcar 'planner-link-base
675 (if (featurep 'planner-multi)
676 (planner-multi-split link)
677 link))))
678 (linked-users
679 (if linked-pages
680 (planner-authz-multi-users linked-pages)
681 (and planner-authz-day-task-default
682 (planner-authz-day-p)
683 (mapconcat 'identity
684 planner-authz-day-task-default " ")))))
686 ;; If this task is linked to another page, republish that page
687 ;; later to restrict the task as it appears there, providing that
688 ;; page has an authz restriction
690 (if linked-pages
691 (planner-authz-republish-dependencies-maybe linked-pages))
693 ;; If the linked page has an authz restriction, restrict this task
695 (when linked-users
696 (goto-char (point-min))
697 (planner-insert-markup
698 (muse-markup-text 'planner-authz-begin linked-users))
699 (goto-char (point-max))
700 (planner-insert-markup (muse-markup-text 'planner-authz-end)))))))
702 (defun planner-authz-users (&optional page)
703 "Return a list of acceptable users for PAGE.
704 The list of users is returned as space-separated string, based on
705 a #authz directive appearing in the page. If PAGE contains no
706 #authz directive and is a project page (it doesn't match
707 `planner-date-regexp'), return `planner-authz-project-default' as
708 a space-separated string.
710 If PAGE is nil, return a list of users associated with the
711 current page."
712 (unless page (setq page (planner-page-name)))
713 (let ((match (cdr (assoc page planner-authz-pages))))
714 (unless match
715 (let ((file (cdr (assoc page (planner-file-alist)))))
716 (setq match
717 (or (and file
718 (with-temp-buffer
719 (insert-file-contents-literally file)
720 (if (re-search-forward "^#authz\\s-+\\(.+\\)\n+"
721 nil t)
722 (match-string 1))))
723 (planner-authz-default page))))
724 (push `(,page . ,match) planner-authz-pages))
725 match))
727 (defun planner-authz-multi-intersection (list)
728 "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."
729 (let ((count (length list))
730 alist intersection sublist)
732 ;; in alist, associate each name with its frequency of appearance
733 (while list
734 (setq sublist (car list))
735 (while sublist
736 (let ((entry (assoc (car sublist) alist)))
737 (if entry
738 (setcdr entry (1+ (cdr entry)))
739 (setq alist (cons `(,(car sublist) . 1) alist))))
740 (setq sublist (cdr sublist)))
741 (setq list (cdr list)))
743 ;; those names with `count' frequencies were in every sublist
744 (while alist
745 (if (= (cdar alist) count)
746 (setq intersection (cons (caar alist) intersection)))
747 (setq alist (cdr alist)))
748 intersection))
750 (defun planner-authz-multi-union (list)
751 "Merge a list of `planner-authz' access lists, returning a list of all the unique user names in any of those access lists."
752 (let (sublist union)
753 (while list
754 (setq sublist (car list))
755 (while sublist
756 (add-to-list 'union (car sublist))
757 (setq sublist (cdr sublist)))
758 (setq list (cdr list)))
759 union))
761 (defun planner-authz-multi-users (pages)
762 "Return a merged access list for PAGES.
763 The list of users is returned as space-separated string, based on a
764 #authz directive appearing in the PAGES. If one of PAGES contains no
765 #authz directive and is a project page (it doesn't match
766 `planner-date-regexp'), it will contribute
767 `planner-authz-project-default' to the merge."
768 (let ((users
769 (funcall planner-authz-multi-func
770 (mapcar (lambda (page)
771 (if (not (planner-authz-day-p page))
772 (let ((users (planner-authz-users page)))
773 (if users
774 (split-string users)))))
775 pages))))
776 (if users
777 (mapconcat 'identity users " "))))
779 (add-hook 'muse-after-project-publish-hook
780 'planner-authz-after-project-publish)
782 (let ((styles (list "html" "xhtml")))
783 (while styles
784 (let ((style (concat "planner-authz-mason-" (car styles))))
785 (unless (assoc style muse-publishing-styles)
786 (muse-derive-style
787 style (concat "planner-" (car styles))
788 :before 'planner-authz-before-markup
789 :after 'planner-authz-after-markup
790 :functions 'planner-authz-markup-functions
791 :regexps 'planner-authz-markup-regexps
792 :strings 'planner-authz-mason-markup-strings
793 :tags (append planner-authz-markup-tags
794 planner-publish-markup-tags))))
795 (setq styles (cdr styles))))
797 (provide 'planner-authz)
799 ;;; planner-authz.el ends here