7 module Rakka.Resource.PageEntity
11 import Control.Applicative
13 import Control.Arrow.ArrowIO
14 import Control.Arrow.ArrowIf
15 import Control.Arrow.ArrowList
16 import Control.Arrow.Unicode
17 import qualified Codec.Binary.UTF8.String as UTF8
18 import Control.Monad.Trans
19 import qualified Data.ByteString.Lazy as L hiding (ByteString)
20 import qualified Data.CaseInsensitive as CI
22 import qualified Data.Map as M
24 import Data.Monoid.Unicode
25 import qualified Data.Text as T
27 import Network.HTTP.Lucu
28 import Network.URI hiding (path)
29 import Prelude.Unicode
30 import Rakka.Environment
34 import Rakka.SystemConfig
36 import Rakka.W3CDateTime
37 import Rakka.Wiki.Engine
38 import System.FilePath.Posix
39 import Text.HyperEstraier hiding (getText)
40 import Text.XML.HXT.Arrow.Namespace
41 import Text.XML.HXT.Arrow.WriteDocument
42 import Text.XML.HXT.Arrow.XmlArrow
43 import Text.XML.HXT.Arrow.XmlState
44 import Text.XML.HXT.DOM.TypeDefs
45 import Text.XML.HXT.XPath
47 fallbackPageEntity ∷ Environment → [String] → IO (Maybe ResourceDef)
48 fallbackPageEntity env path
49 | T.null name = return Nothing
50 | isLower $ T.head name = return Nothing -- 先頭の文字が小文字であってはならない
52 = pure $ Just ResourceDef {
53 resUsesNativeThread = False
55 , resGet = Just $ handleGet env name
58 , resPut = Just $ handlePut env name
59 , resDelete = Just $ handleDelete env name
63 name = T.pack ∘ dropExtension ∘ UTF8.decodeString $ joinPath path
65 handleGet :: Environment -> PageName -> Resource ()
67 = do BaseURI baseURI <- getSysConf (envSysConf env)
68 runIdempotentA baseURI $ proc ()
69 -> do pageM <- getPageA (envStorage env) -< (name, Nothing)
72 -> do items <- getDirContentsA (envStorage env) -< (name, Nothing)
74 [] -> handlePageNotFound env -< name
75 _ -> handleGetPageListing env -< (name, items)
77 -> if isEntity page then
78 handleGetEntity env -< page
80 handleRedirect env -< page
84 Location: http://example.org/Destination.html#Redirect:Source
86 handleRedirect ∷ (ArrowXml (⇝), ArrowIO (⇝)) ⇒ Environment → Page ⇝ Resource ()
89 → returnA ⤙ do mType ← getEntityType
91 MIMEType "text" "xml" _
92 → do setContentType mType
93 [resultStr] ← liftIO $
94 runX ( setErrorMsgHandler False fail
100 writeDocumentToString
105 output $ UTF8.encodeString resultStr
106 _ → do BaseURI baseURI ← getSysConf (envSysConf env)
107 let uri = mkPageFragmentURI
110 ("Redirect:" ⊕ redirName redir)
113 handleGetEntity :: (ArrowXml a, ArrowChoice a, ArrowIO a) => Environment -> a Page (Resource ())
116 -> do tree <- xmlizePage -< page
117 returnA -< outputXmlPage tree [ (MIMEType "application" "xhtml+xml" [], entityToXHTML env)
118 , (MIMEType "application" "rss+xml" [], entityToRSS env)
122 entityToXHTML ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
127 → do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
128 BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
129 StyleSheet styleSheet ← getSysConfA (envSysConf env) ⤙ ()
130 GlobalLock isGLocked ← getSysConfA (envSysConf env) ⤙ ()
132 name ← (getXPathTreesInDoc "/page/@name/text()" ⋙ getText) ⤙ page
133 isLocked ← (getXPathTreesInDoc "/page/@isLocked/text()" ⋙ getText ⋙ parseYesOrNo) ⤙ page
135 let cssHref = [uriToString id (mkObjectURI baseURI styleSheet) ""]
136 scriptSrc = [uriToString id (baseURI { uriPath = uriPath baseURI </> "js" }) ""]
138 pageTitle ← listA (readSubPage env) ⤙ (T.pack name, Just page, "PageTitle")
139 leftSideBar ← listA (readSubPage env) ⤙ (T.pack name, Just page, "SideBar/Left")
140 rightSideBar ← listA (readSubPage env) ⤙ (T.pack name, Just page, "SideBar/Right")
141 pageBody ← listA (makeMainXHTML (envStorage env) (envSysConf env) (envInterpTable env)) ⤙ page
145 += sattr "xmlns" "http://www.w3.org/1999/xhtml"
146 += ( getXPathTreesInDoc "/page/@lang"
148 qattr (mkQName "xml" "lang" "")
149 ( getXPathTreesInDoc "/page/@lang/text()" )
153 += txt (T.unpack siteName)
155 += getXPathTreesInDoc "/page/@name/text()"
160 += sattr "rel" "stylesheet"
161 += sattr "type" "text/css"
162 += attr "href" (arr id ⋙ mkText)
165 += ( constL scriptSrc
168 += sattr "type" "text/javascript"
169 += attr "src" (arr id ⋙ mkText)
172 += sattr "type" "text/javascript"
173 += txt ("Rakka.baseURI=\"" ⊕ uriToString id baseURI "" ⊕ "\";")
174 += txt ("Rakka.isLocked=" ⊕ trueOrFalse isLocked ⊕ ";" )
175 += txt ("Rakka.isGlobalLocked=" ⊕ trueOrFalse isGLocked ⊕ ";" )
177 += mkGlobalJSList env
181 += sattr "class" "header"
184 += sattr "class" "center"
186 += sattr "class" "title"
190 += sattr "class" "body"
195 += sattr "class" "footer"
198 += sattr "class" "left sideBar"
200 += sattr "class" "content"
201 += constL leftSideBar
205 += sattr "class" "right sideBar"
207 += sattr "class" "content"
208 += constL rightSideBar
213 uniqueNamespacesFromDeclAndQNames
216 entityToRSS ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
221 → do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
222 BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
224 name ← (getXPathTreesInDoc "/page/@name/text()" ⋙ getText) ⤙ page
225 summary ← maybeA (getXPathTreesInDoc "/page/summary/text()" ⋙ getText) ⤙ page
226 pages ← makePageLinkList (envStorage env) (envSysConf env) (envInterpTable env) ⤙ page
230 += sattr "xmlns" "http://purl.org/rss/1.0/"
231 += sattr "xmlns:rdf" "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
232 += sattr "xmlns:dc" "http://purl.org/dc/elements/1.1/"
233 += sattr "xmlns:trackback" "http://madskills.com/public/xml/rss/module/trackback/"
235 += sattr "rdf:about" (uriToString id (mkFeedURI baseURI (T.pack name)) "")
237 += txt (T.unpack siteName)
239 += getXPathTreesInDoc "/page/@name/text()"
242 += txt (uriToString id baseURI "")
244 += ( eelem "description"
245 += txt (case summary of
246 Nothing → "RSS Feed for " ⊕ T.unpack siteName
254 += attr "resource" (arr (mkPageURIStr baseURI) ⋙ mkText) ) ) ) )
257 arr (\n → (n, Nothing))
259 getPageA (envStorage env)
264 += attr "rdf:about" (arr (mkPageURIStr baseURI ∘ entityName) ⋙ mkText)
266 += (arr (T.unpack ∘ entityName) ⋙ mkText)
269 += (arr (mkPageURIStr baseURI ∘ entityName) ⋙ mkText)
271 += ( arrL (\p → case entitySummary p of
279 += ( arrIO (utcToLocalZonedTime . entityLastMod)
281 arr formatW3CDateTime
286 += ( eelem "trackback:ping"
287 += attr "rdf:resource" (arr (mkTrackbackURIStr baseURI . entityName) ⋙ mkText)
291 uniqueNamespacesFromDeclAndQNames
294 mkPageURIStr :: URI → PageName → String
295 mkPageURIStr baseURI name
296 = uriToString id (mkPageURI baseURI name) ""
298 mkTrackbackURIStr :: URI → PageName → String
299 mkTrackbackURIStr baseURI name
300 = uriToString id (mkAuxiliaryURI baseURI ["trackback"] name) ""
302 readSubPage ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
304 → (PageName, Maybe XmlTree, PageName) ⇝ XmlTree
306 = proc (mainPageName, mainPage, subPageName) →
307 do langM ← case mainPage of
311 → maybeA (getXPathTreesInDoc "/page/@lang/text()" ⋙ getText) ⤙ p
312 subPage ← getPageA (envStorage env) ⋙ arr fromJust ⤙ (subPageName, Nothing)
313 localSubPage ← case langM of
317 → localize (envStorage env) ⤙ (CI.mk $ T.pack l, subPage)
318 subPageXml ← xmlizePage ⤙ localSubPage
319 subXHTML ← makeSubXHTML (envStorage env) (envSysConf env) (envInterpTable env)
320 ⤙ (Just mainPageName, mainPage, subPageXml)
323 localize ∷ (ArrowChoice (⇝), ArrowIO (⇝)) ⇒ Storage → (LanguageTag, Page) ⇝ Page
325 = proc (lang, origPage)
326 → do let otherLang = entityOtherLang origPage
327 localName = M.lookup lang otherLang
332 → do localPage ← getPageA sto ⤙ (ln, Nothing)
333 returnA ⤙ case localPage of
339 <pageListing path="Foo">
340 <page name="Foo/Bar" />
341 <page name="Foo/Baz" />
344 handleGetPageListing ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
346 → (PageName, [PageName]) ⇝ Resource ()
347 handleGetPageListing env
349 → do tree ← ( eelem "/"
350 += ( eelem "pageListing"
351 += attr "path" (arr (T.unpack ∘ fst) ⋙ mkText)
355 += attr "name" (arr (T.unpack ∘ id) ⋙ mkText)
360 returnA ⤙ outputXmlPage' tree (pageListingToXHTML env) -- FIXME: rss 對應
362 pageListingToXHTML ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
365 pageListingToXHTML env
367 → do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
368 BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
369 StyleSheet styleSheet ← getSysConfA (envSysConf env) ⤙ ()
370 GlobalLock isGLocked ← getSysConfA (envSysConf env) ⤙ ()
372 name ← (getXPathTreesInDoc "/pageListing/@path/text()" ⋙ getText) ⤙ pageListing
374 let cssHref = [uriToString id (mkObjectURI baseURI styleSheet) ""]
375 scriptSrc = [uriToString id (baseURI { uriPath = uriPath baseURI </> "js" }) ""]
377 pageTitle ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "PageTitle")
378 leftSideBar ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "SideBar/Left")
379 rightSideBar ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "SideBar/Right")
383 += sattr "xmlns" "http://www.w3.org/1999/xhtml"
386 += txt (T.unpack siteName)
388 += getXPathTreesInDoc "/pageListing/@path/text()"
393 += sattr "rel" "stylesheet"
394 += sattr "type" "text/css"
395 += attr "href" (arr id ⋙ mkText)
398 += ( constL scriptSrc
401 += sattr "type" "text/javascript"
402 += attr "src" (arr id ⋙ mkText)
405 += sattr "type" "text/javascript"
406 += txt ("Rakka.baseURI = \"" ++ uriToString id baseURI "" ++ "\";")
407 += txt ("Rakka.isGlobalLocked=" ++ trueOrFalse isGLocked ++ ";")
409 += mkGlobalJSList env
413 += sattr "class" "header"
416 += sattr "class" "center"
418 += sattr "class" "title"
422 += sattr "class" "body"
424 += ( getXPathTreesInDoc "/pageListing/page/@name/text()"
428 += attr "href" ( getText
430 arr (\ x → uriToString id (mkPageURI baseURI (T.pack x)) "")
437 += sattr "class" "footer"
440 += sattr "class" "left sideBar"
442 += sattr "class" "content"
443 += constL leftSideBar
447 += sattr "class" "right sideBar"
449 += sattr "class" "content"
450 += constL rightSideBar
455 uniqueNamespacesFromDeclAndQNames
459 <pageNotFound name="Foo/Bar" />
461 handlePageNotFound ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
463 → PageName ⇝ Resource ()
464 handlePageNotFound env
466 → do tree ← ( eelem "/"
467 += ( eelem "pageNotFound"
468 += attr "name" (arr T.unpack ⋙ mkText)
471 returnA ⤙ do setStatus NotFound
472 outputXmlPage' tree (notFoundToXHTML env)
474 notFoundToXHTML ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
479 → do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
480 BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
481 StyleSheet styleSheet ← getSysConfA (envSysConf env) ⤙ ()
482 GlobalLock isGLocked ← getSysConfA (envSysConf env) ⤙ ()
484 name ← (getXPathTreesInDoc "/pageNotFound/@name/text()" ⋙ getText) ⤙ pageNotFound
486 let cssHref = [uriToString id (mkObjectURI baseURI styleSheet) ""]
487 scriptSrc = [uriToString id (baseURI { uriPath = uriPath baseURI </> "js" }) ""]
489 pageTitle ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "PageTitle" )
490 leftSideBar ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "SideBar/Left" )
491 rightSideBar ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "SideBar/Right")
495 += sattr "xmlns" "http://www.w3.org/1999/xhtml"
498 += txt (T.unpack siteName)
500 += getXPathTreesInDoc "/pageNotFound/@name/text()"
505 += sattr "rel" "stylesheet"
506 += sattr "type" "text/css"
507 += attr "href" (arr id ⋙ mkText)
510 += ( constL scriptSrc
513 += sattr "type" "text/javascript"
514 += attr "src" (arr id ⋙ mkText)
517 += sattr "type" "text/javascript"
518 += txt ("Rakka.baseURI = \"" ++ uriToString id baseURI "" ++ "\";")
519 += txt ("Rakka.isGlobalLocked=" ++ trueOrFalse isGLocked ++ ";")
521 += mkGlobalJSList env
525 += sattr "class" "header"
528 += sattr "class" "center"
530 += sattr "class" "title"
534 += sattr "class" "body"
535 += txt "404 Not Found (FIXME)" -- FIXME
539 += sattr "class" "footer"
542 += sattr "class" "left sideBar"
544 += sattr "class" "content"
545 += constL leftSideBar
549 += sattr "class" "right sideBar"
551 += sattr "class" "content"
552 += constL rightSideBar
557 uniqueNamespacesFromDeclAndQNames
560 handlePut ∷ Environment → PageName → Resource ()
562 = do userID ← getUserID env
563 runXmlA "rakka-page-1.0.rng" $ proc tree
564 → do page ← parseXmlizedPage ⤙ (name, tree)
565 status ← putPageA (envStorage env) ⤙ (userID, page)
566 returnA ⤙ setStatus status
568 handleDelete ∷ Environment → PageName → Resource ()
569 handleDelete env name
570 = do userID ← getUserID env
571 status ← deletePage (envStorage env) userID name
574 mkFeedList ∷ (ArrowIO (⇝), ArrowXml (⇝)) ⇒ Environment → β ⇝ XmlTree
577 → do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
578 BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
579 feed ← unlistA ⋘ arrIO0 (findFeeds $ envStorage env) ⤙ ()
581 += sattr "rel" "alternate"
582 += sattr "type" "application/rss+xml"
583 += attr "title" (txt (T.unpack siteName) <+> txt " - " <+> (arr T.unpack ⋙ mkText))
584 += attr "href" (arr (mkFeedURIStr baseURI) ⋙ mkText) ) ⤛ feed
586 findFeeds :: Storage -> IO [PageName]
588 = do cond <- newCondition
589 setPhrase cond "[UVSET]"
590 addAttrCond cond "rakka:isFeed STREQ yes"
591 setOrder cond "@uri STRA"
592 result <- searchPages sto cond
593 return (map hpPageName $ srPages result)
596 mkGlobalJSList :: (ArrowIO a, ArrowXml a, ArrowChoice a) => Environment -> a b XmlTree
598 = proc _ -> do BaseURI baseURI <- getSysConfA (envSysConf env) -< ()
600 scriptName <- unlistA <<< arrIO0 (findJavaScripts $ envStorage env) -< ()
601 pageM <- getPageA (envStorage env) -< (scriptName, Nothing)
604 Nothing -> none -< ()
607 -> ( if entityIsBinary page then
609 += sattr "type" "text/javascript"
610 += attr "src" (arr (mkObjectURIStr baseURI . pageName) >>> mkText) )
613 += sattr "type" "text/javascript"
614 += (arr (UTF8.decode . L.unpack . entityContent) >>> mkText) )
619 findJavaScripts ∷ Storage → IO [PageName]
621 = do cond ← newCondition
622 setPhrase cond "[UVSET]"
623 addAttrCond cond "@title STRBW Global/"
624 addAttrCond cond "@type STRBW text/javascript"
625 setOrder cond "@uri STRA"
626 result ← searchPages sto cond
627 return (map hpPageName $ srPages result)
629 mkFeedURIStr ∷ URI → PageName → String
630 mkFeedURIStr = flip flip "" ∘ (uriToString id ∘) ∘ mkFeedURI
632 mkObjectURIStr ∷ URI → PageName → String
633 mkObjectURIStr = flip flip "" ∘ (uriToString id ∘) ∘ mkObjectURI