{-# LANGUAGE
Arrows
, OverloadedStrings
, TypeOperators
, UnicodeSyntax
#-}
module Rakka.Resource.PageEntity
( fallbackPageEntity
)
where
import Control.Applicative
import Control.Arrow
import Control.Arrow.ArrowIO
import Control.Arrow.ArrowIf
import Control.Arrow.ArrowList
import Control.Arrow.Unicode
import qualified Codec.Binary.UTF8.String as UTF8
import Control.Monad.Trans
import qualified Data.ByteString.Lazy as L hiding (ByteString)
import qualified Data.CaseInsensitive as CI
import Data.Char
import qualified Data.Map as M
import Data.Maybe
import Data.Monoid.Unicode
import qualified Data.Text as T
import Data.Time
import qualified Data.Time.W3C as W3C
import Network.HTTP.Lucu
import Network.URI hiding (path)
import Prelude.Unicode
import Rakka.Environment
import Rakka.Page
import Rakka.Resource
import Rakka.Storage
import Rakka.SystemConfig
import Rakka.Utils
import Rakka.Wiki.Engine
import System.FilePath.Posix
import Text.HyperEstraier hiding (getText)
import Text.XML.HXT.Arrow.Namespace
import Text.XML.HXT.Arrow.WriteDocument
import Text.XML.HXT.Arrow.XmlArrow
import Text.XML.HXT.Arrow.XmlState
import Text.XML.HXT.DOM.TypeDefs
import Text.XML.HXT.XPath
fallbackPageEntity ∷ Environment → [String] → IO (Maybe ResourceDef)
fallbackPageEntity env path
| T.null name = return Nothing
| isLower $ T.head name = return Nothing -- 先頭の文字が小文字であってはならない
| otherwise
= pure $ Just ResourceDef {
resUsesNativeThread = False
, resIsGreedy = True
, resGet = Just $ handleGet env name
, resHead = Nothing
, resPost = Nothing
, resPut = Just $ handlePut env name
, resDelete = Just $ handleDelete env name
}
where
name ∷ PageName
name = T.pack ∘ dropExtension ∘ UTF8.decodeString $ joinPath path
handleGet :: Environment -> PageName -> Resource ()
handleGet env name
= do BaseURI baseURI <- getSysConf (envSysConf env)
runIdempotentA baseURI $ proc ()
-> do pageM <- getPageA (envStorage env) -< (name, Nothing)
case pageM of
Nothing
-> do items <- getDirContentsA (envStorage env) -< (name, Nothing)
case items of
[] -> handlePageNotFound env -< name
_ -> handleGetPageListing env -< (name, items)
Just page
-> if isEntity page then
handleGetEntity env -< page
else
handleRedirect env -< page
{-
HTTP/1.1 302 Found
Location: http://example.org/Destination.html#Redirect:Source
-}
handleRedirect ∷ (ArrowXml (⇝), ArrowIO (⇝)) ⇒ Environment → Page ⇝ Resource ()
handleRedirect env
= proc redir
→ returnA ⤙ do mType ← getEntityType
case mType of
MIMEType "text" "xml" _
→ do setContentType mType
[resultStr] ← liftIO $
runX ( setErrorMsgHandler False fail
⋙
constA redir
⋙
xmlizePage
⋙
writeDocumentToString
[ withIndent yes
, withXmlPi yes
]
)
output $ UTF8.encodeString resultStr
_ → do BaseURI baseURI ← getSysConf (envSysConf env)
let uri = mkPageFragmentURI
baseURI
(redirDest redir)
("Redirect:" ⊕ redirName redir)
redirect Found uri
handleGetEntity :: (ArrowXml a, ArrowChoice a, ArrowIO a) => Environment -> a Page (Resource ())
handleGetEntity env
= proc page
-> do tree <- xmlizePage -< page
returnA -< outputXmlPage tree [ (MIMEType "application" "xhtml+xml" [], entityToXHTML env)
, (MIMEType "application" "rss+xml" [], entityToRSS env)
]
entityToXHTML ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
⇒ Environment
→ XmlTree ⇝ XmlTree
entityToXHTML env
= proc page
→ do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
StyleSheet styleSheet ← getSysConfA (envSysConf env) ⤙ ()
GlobalLock isGLocked ← getSysConfA (envSysConf env) ⤙ ()
name ← (getXPathTreesInDoc "/page/@name/text()" ⋙ getText) ⤙ page
isLocked ← (getXPathTreesInDoc "/page/@isLocked/text()" ⋙ getText ⋙ parseYesOrNo) ⤙ page
let cssHref = [uriToString id (mkObjectURI baseURI styleSheet) ""]
scriptSrc = [uriToString id (baseURI { uriPath = uriPath baseURI > "js" }) ""]
pageTitle ← listA (readSubPage env) ⤙ (T.pack name, Just page, "PageTitle")
leftSideBar ← listA (readSubPage env) ⤙ (T.pack name, Just page, "SideBar/Left")
rightSideBar ← listA (readSubPage env) ⤙ (T.pack name, Just page, "SideBar/Right")
pageBody ← listA (makeMainXHTML (envStorage env) (envSysConf env) (envInterpTable env)) ⤙ page
( eelem "/"
+= ( eelem "html"
+= sattr "xmlns" "http://www.w3.org/1999/xhtml"
+= ( getXPathTreesInDoc "/page/@lang"
`guards`
qattr (mkQName "xml" "lang" "")
( getXPathTreesInDoc "/page/@lang/text()" )
)
+= ( eelem "head"
+= ( eelem "title"
+= txt (T.unpack siteName)
+= txt " - "
+= getXPathTreesInDoc "/page/@name/text()"
)
+= ( constL cssHref
⋙
eelem "link"
+= sattr "rel" "stylesheet"
+= sattr "type" "text/css"
+= attr "href" (arr id ⋙ mkText)
)
+= mkFeedList env
+= ( constL scriptSrc
⋙
eelem "script"
+= sattr "type" "text/javascript"
+= attr "src" (arr id ⋙ mkText)
)
+= ( eelem "script"
+= sattr "type" "text/javascript"
+= txt ("Rakka.baseURI=\"" ⊕ uriToString id baseURI "" ⊕ "\";")
+= txt ("Rakka.isLocked=" ⊕ trueOrFalse isLocked ⊕ ";" )
+= txt ("Rakka.isGlobalLocked=" ⊕ trueOrFalse isGLocked ⊕ ";" )
)
+= mkGlobalJSList env
)
+= ( eelem "body"
+= ( eelem "div"
+= sattr "class" "header"
)
+= ( eelem "div"
+= sattr "class" "center"
+= ( eelem "div"
+= sattr "class" "title"
+= constL pageTitle
)
+= ( eelem "div"
+= sattr "class" "body"
+= constL pageBody
)
)
+= ( eelem "div"
+= sattr "class" "footer"
)
+= ( eelem "div"
+= sattr "class" "left sideBar"
+= ( eelem "div"
+= sattr "class" "content"
+= constL leftSideBar
)
)
+= ( eelem "div"
+= sattr "class" "right sideBar"
+= ( eelem "div"
+= sattr "class" "content"
+= constL rightSideBar
)
)
)
⋙
uniqueNamespacesFromDeclAndQNames
) ) ⤛ page
entityToRSS ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
⇒ Environment
→ XmlTree ⇝ XmlTree
entityToRSS env
= proc page
→ do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
name ← (getXPathTreesInDoc "/page/@name/text()" ⋙ getText) ⤙ page
summary ← maybeA (getXPathTreesInDoc "/page/summary/text()" ⋙ getText) ⤙ page
pages ← makePageLinkList (envStorage env) (envSysConf env) (envInterpTable env) ⤙ page
( eelem "/"
+= ( eelem "rdf:RDF"
+= sattr "xmlns" "http://purl.org/rss/1.0/"
+= sattr "xmlns:rdf" "http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+= sattr "xmlns:dc" "http://purl.org/dc/elements/1.1/"
+= ( eelem "channel"
+= sattr "rdf:about" (uriToString id (mkFeedURI baseURI (T.pack name)) "")
+= ( eelem "title"
+= txt (T.unpack siteName)
+= txt " - "
+= getXPathTreesInDoc "/page/@name/text()"
)
+= ( eelem "link"
+= txt (uriToString id baseURI "")
)
+= ( eelem "description"
+= txt (case summary of
Nothing → "RSS Feed for " ⊕ T.unpack siteName
Just s → s)
)
+= ( eelem "items"
+= ( eelem "rdf:Seq"
+= ( constL pages
⋙
eelem "rdf:li"
+= attr "resource" (arr (mkPageURIStr baseURI) ⋙ mkText) ) ) ) )
+= ( constL pages
⋙
arr (\n → (n, Nothing))
⋙
getPageA (envStorage env)
⋙
arr fromJust
⋙
eelem "item"
+= attr "rdf:about" (arr (mkPageURIStr baseURI ∘ entityName) ⋙ mkText)
+= ( eelem "title"
+= (arr (T.unpack ∘ entityName) ⋙ mkText)
)
+= ( eelem "link"
+= (arr (mkPageURIStr baseURI ∘ entityName) ⋙ mkText)
)
+= ( arrL (\p → case entitySummary p of
Nothing → []
Just s → [s])
⋙
eelem "description"
+= mkText
)
+= ( eelem "dc:date"
+= ( arrIO (utcToLocalZonedTime . entityLastMod)
⋙
arr W3C.format
⋙
mkText
)
)
)
⋙
uniqueNamespacesFromDeclAndQNames
) ) ⤛ page
where
mkPageURIStr :: URI → PageName → String
mkPageURIStr baseURI name
= uriToString id (mkPageURI baseURI name) ""
readSubPage ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
⇒ Environment
→ (PageName, Maybe XmlTree, PageName) ⇝ XmlTree
readSubPage env
= proc (mainPageName, mainPage, subPageName) →
do langM ← case mainPage of
Nothing
→ returnA ⤙ Nothing
Just p
→ maybeA (getXPathTreesInDoc "/page/@lang/text()" ⋙ getText) ⤙ p
subPage ← getPageA (envStorage env) ⋙ arr fromJust ⤙ (subPageName, Nothing)
localSubPage ← case langM of
Nothing
→ returnA ⤙ subPage
Just l
→ localize (envStorage env) ⤙ (CI.mk $ T.pack l, subPage)
subPageXml ← xmlizePage ⤙ localSubPage
subXHTML ← makeSubXHTML (envStorage env) (envSysConf env) (envInterpTable env)
⤙ (Just mainPageName, mainPage, subPageXml)
returnA ⤙ subXHTML
where
localize ∷ (ArrowChoice (⇝), ArrowIO (⇝)) ⇒ Storage → (LanguageTag, Page) ⇝ Page
localize sto
= proc (lang, origPage)
→ do let otherLang = entityOtherLang origPage
localName = M.lookup lang otherLang
case localName of
Nothing
→ returnA ⤙ origPage
Just ln
→ do localPage ← getPageA sto ⤙ (ln, Nothing)
returnA ⤙ case localPage of
Nothing → origPage
Just p → p
{-
-}
handleGetPageListing ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
⇒ Environment
→ (PageName, [PageName]) ⇝ Resource ()
handleGetPageListing env
= proc (dir, items)
→ do tree ← ( eelem "/"
+= ( eelem "pageListing"
+= attr "path" (arr (T.unpack ∘ fst) ⋙ mkText)
+= ( arrL snd
⋙
( eelem "page"
+= attr "name" (arr (T.unpack ∘ id) ⋙ mkText)
)
)
)
) ⤙ (dir, items)
returnA ⤙ outputXmlPage' tree (pageListingToXHTML env) -- FIXME: rss 對應
pageListingToXHTML ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
⇒ Environment
→ XmlTree ⇝ XmlTree
pageListingToXHTML env
= proc pageListing
→ do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
StyleSheet styleSheet ← getSysConfA (envSysConf env) ⤙ ()
GlobalLock isGLocked ← getSysConfA (envSysConf env) ⤙ ()
name ← (getXPathTreesInDoc "/pageListing/@path/text()" ⋙ getText) ⤙ pageListing
let cssHref = [uriToString id (mkObjectURI baseURI styleSheet) ""]
scriptSrc = [uriToString id (baseURI { uriPath = uriPath baseURI > "js" }) ""]
pageTitle ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "PageTitle")
leftSideBar ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "SideBar/Left")
rightSideBar ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "SideBar/Right")
( eelem "/"
+= ( eelem "html"
+= sattr "xmlns" "http://www.w3.org/1999/xhtml"
+= ( eelem "head"
+= ( eelem "title"
+= txt (T.unpack siteName)
+= txt " - "
+= getXPathTreesInDoc "/pageListing/@path/text()"
)
+= ( constL cssHref
⋙
eelem "link"
+= sattr "rel" "stylesheet"
+= sattr "type" "text/css"
+= attr "href" (arr id ⋙ mkText)
)
+= mkFeedList env
+= ( constL scriptSrc
⋙
eelem "script"
+= sattr "type" "text/javascript"
+= attr "src" (arr id ⋙ mkText)
)
+= ( eelem "script"
+= sattr "type" "text/javascript"
+= txt ("Rakka.baseURI = \"" ++ uriToString id baseURI "" ++ "\";")
+= txt ("Rakka.isGlobalLocked=" ++ trueOrFalse isGLocked ++ ";")
)
+= mkGlobalJSList env
)
+= ( eelem "body"
+= ( eelem "div"
+= sattr "class" "header"
)
+= ( eelem "div"
+= sattr "class" "center"
+= ( eelem "div"
+= sattr "class" "title"
+= constL pageTitle
)
+= ( eelem "div"
+= sattr "class" "body"
+= ( eelem "ul"
+= ( getXPathTreesInDoc "/pageListing/page/@name/text()"
⋙
eelem "li"
+= ( eelem "a"
+= attr "href" ( getText
⋙
arr (\ x → uriToString id (mkPageURI baseURI (T.pack x)) "")
⋙
mkText
)
+= this
) ) ) ) )
+= ( eelem "div"
+= sattr "class" "footer"
)
+= ( eelem "div"
+= sattr "class" "left sideBar"
+= ( eelem "div"
+= sattr "class" "content"
+= constL leftSideBar
)
)
+= ( eelem "div"
+= sattr "class" "right sideBar"
+= ( eelem "div"
+= sattr "class" "content"
+= constL rightSideBar
)
)
)
⋙
uniqueNamespacesFromDeclAndQNames
) ) ⤛ pageListing
{-
-}
handlePageNotFound ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
⇒ Environment
→ PageName ⇝ Resource ()
handlePageNotFound env
= proc name
→ do tree ← ( eelem "/"
+= ( eelem "pageNotFound"
+= attr "name" (arr T.unpack ⋙ mkText)
)
) ⤙ name
returnA ⤙ do setStatus NotFound
outputXmlPage' tree (notFoundToXHTML env)
notFoundToXHTML ∷ (ArrowXml (⇝), ArrowChoice (⇝), ArrowIO (⇝))
⇒ Environment
→ XmlTree ⇝ XmlTree
notFoundToXHTML env
= proc pageNotFound
→ do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
StyleSheet styleSheet ← getSysConfA (envSysConf env) ⤙ ()
GlobalLock isGLocked ← getSysConfA (envSysConf env) ⤙ ()
name ← (getXPathTreesInDoc "/pageNotFound/@name/text()" ⋙ getText) ⤙ pageNotFound
let cssHref = [uriToString id (mkObjectURI baseURI styleSheet) ""]
scriptSrc = [uriToString id (baseURI { uriPath = uriPath baseURI > "js" }) ""]
pageTitle ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "PageTitle" )
leftSideBar ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "SideBar/Left" )
rightSideBar ← listA (readSubPage env) ⤙ (T.pack name, Nothing, "SideBar/Right")
( eelem "/"
+= ( eelem "html"
+= sattr "xmlns" "http://www.w3.org/1999/xhtml"
+= ( eelem "head"
+= ( eelem "title"
+= txt (T.unpack siteName)
+= txt " - "
+= getXPathTreesInDoc "/pageNotFound/@name/text()"
)
+= ( constL cssHref
⋙
eelem "link"
+= sattr "rel" "stylesheet"
+= sattr "type" "text/css"
+= attr "href" (arr id ⋙ mkText)
)
+= mkFeedList env
+= ( constL scriptSrc
⋙
eelem "script"
+= sattr "type" "text/javascript"
+= attr "src" (arr id ⋙ mkText)
)
+= ( eelem "script"
+= sattr "type" "text/javascript"
+= txt ("Rakka.baseURI = \"" ++ uriToString id baseURI "" ++ "\";")
+= txt ("Rakka.isGlobalLocked=" ++ trueOrFalse isGLocked ++ ";")
)
+= mkGlobalJSList env
)
+= ( eelem "body"
+= ( eelem "div"
+= sattr "class" "header"
)
+= ( eelem "div"
+= sattr "class" "center"
+= ( eelem "div"
+= sattr "class" "title"
+= constL pageTitle
)
+= ( eelem "div"
+= sattr "class" "body"
+= txt "404 Not Found (FIXME)" -- FIXME
)
)
+= ( eelem "div"
+= sattr "class" "footer"
)
+= ( eelem "div"
+= sattr "class" "left sideBar"
+= ( eelem "div"
+= sattr "class" "content"
+= constL leftSideBar
)
)
+= ( eelem "div"
+= sattr "class" "right sideBar"
+= ( eelem "div"
+= sattr "class" "content"
+= constL rightSideBar
)
)
)
⋙
uniqueNamespacesFromDeclAndQNames
) ) ⤛ pageNotFound
handlePut ∷ Environment → PageName → Resource ()
handlePut env name
= do userID ← getUserID env
runXmlA "rakka-page-1.0.rng" $ proc tree
→ do page ← parseXmlizedPage ⤙ (name, tree)
status ← putPageA (envStorage env) ⤙ (userID, page)
returnA ⤙ setStatus status
handleDelete ∷ Environment → PageName → Resource ()
handleDelete env name
= do userID ← getUserID env
status ← deletePage (envStorage env) userID name
setStatus status
mkFeedList ∷ (ArrowIO (⇝), ArrowXml (⇝)) ⇒ Environment → β ⇝ XmlTree
mkFeedList env
= proc _
→ do SiteName siteName ← getSysConfA (envSysConf env) ⤙ ()
BaseURI baseURI ← getSysConfA (envSysConf env) ⤙ ()
feed ← unlistA ⋘ arrIO0 (findFeeds $ envStorage env) ⤙ ()
( eelem "link"
+= sattr "rel" "alternate"
+= sattr "type" "application/rss+xml"
+= attr "title" (txt (T.unpack siteName) <+> txt " - " <+> (arr T.unpack ⋙ mkText))
+= attr "href" (arr (mkFeedURIStr baseURI) ⋙ mkText) ) ⤛ feed
findFeeds :: Storage -> IO [PageName]
findFeeds sto
= do cond <- newCondition
setPhrase cond "[UVSET]"
addAttrCond cond "rakka:isFeed STREQ yes"
setOrder cond "@uri STRA"
result <- searchPages sto cond
return (map hpPageName $ srPages result)
mkGlobalJSList :: (ArrowIO a, ArrowXml a, ArrowChoice a) => Environment -> a b XmlTree
mkGlobalJSList env
= proc _ -> do BaseURI baseURI <- getSysConfA (envSysConf env) -< ()
scriptName <- unlistA <<< arrIO0 (findJavaScripts $ envStorage env) -< ()
pageM <- getPageA (envStorage env) -< (scriptName, Nothing)
case pageM of
Nothing -> none -< ()
Just page
| isEntity page
-> ( if entityIsBinary page then
( eelem "script"
+= sattr "type" "text/javascript"
+= attr "src" (arr (mkObjectURIStr baseURI . pageName) >>> mkText) )
else
( eelem "script"
+= sattr "type" "text/javascript"
+= (arr (UTF8.decode . L.unpack . entityContent) >>> mkText) )
) -<< page
| otherwise
-> none -< ()
findJavaScripts ∷ Storage → IO [PageName]
findJavaScripts sto
= do cond ← newCondition
setPhrase cond "[UVSET]"
addAttrCond cond "@title STRBW Global/"
addAttrCond cond "@type STRBW text/javascript"
setOrder cond "@uri STRA"
result ← searchPages sto cond
return (map hpPageName $ srPages result)
mkFeedURIStr ∷ URI → PageName → String
mkFeedURIStr = flip flip "" ∘ (uriToString id ∘) ∘ mkFeedURI
mkObjectURIStr ∷ URI → PageName → String
mkObjectURIStr = flip flip "" ∘ (uriToString id ∘) ∘ mkObjectURI