{-# LANGUAGE DeriveDataTypeable, GeneralizedNewtypeDeriving, MultiParamTypeClasses, FlexibleInstances, TypeSynonymInstances, FlexibleContexts, TypeFamilies, RankNTypes, RecordWildCards, ScopedTypeVariables, UndecidableInstances, OverloadedStrings, TemplateHaskell #-}
module Clckwrks.Monad
( Clck
, ClckPlugins
, ClckPluginsSt(cpsAcid)
, initialClckPluginsSt
, ClckT(..)
, ClckForm
, ClckFormT
, ClckFormError(..)
, ChildType(..)
, ClckwrksConfig(..)
, TLSSettings(..)
, AttributeType(..)
, Theme(..)
, ThemeStyle(..)
, ThemeStyleId(..)
, ThemeName
, getThemeStyles
, themeTemplate
, calcBaseURI
, calcTLSBaseURI
, evalClckT
, execClckT
, runClckT
, mapClckT
, withRouteClckT
, ClckState(..)
, Content(..)
, addAdminMenu
, appendRequestInit
, getNavBarLinks
, addPreProc
, addNavBarCallback
, getPreProcessors
, getEnableAnalytics
, googleAnalytics
, getUnique
, setUnique
, setRedirectCookie
, getRedirectCookie
, query
, update
, nestURL
, withAbs
, withAbs'
, segments
, transform
, module HSP.XML
, module HSP.XMLGenerator
)
where
import Clckwrks.Admin.URL (AdminURL(..))
import Clckwrks.Acid (Acid(..), CoreState, GetAcidState(..), GetUACCT(..))
import Clckwrks.ProfileData.Acid (ProfileDataState, ProfileDataError(..), GetRoles(..), HasRole(..))
import Clckwrks.ProfileData.Types (Role(..))
import Clckwrks.NavBar.Acid (NavBarState)
import Clckwrks.NavBar.Types (NavBarLinks(..))
import Clckwrks.Types (NamedLink(..), Prefix, Trust(Trusted))
import Clckwrks.Unauthorized (unauthorizedPage)
import Clckwrks.URL (ClckURL(..))
import Control.Applicative (Alternative, Applicative, (<$>), (<|>), many, optional)
import Control.Monad (MonadPlus, foldM)
import Control.Monad.State (MonadState, StateT, evalStateT, execStateT, get, mapStateT, modify, put, runStateT)
import Control.Monad.Reader (MonadReader, ReaderT, mapReaderT)
import Control.Monad.Trans (MonadIO(liftIO), MonadTrans(lift))
import Control.Concurrent.STM (TVar, readTVar, writeTVar, atomically)
import Data.Acid (AcidState, EventState, EventResult, QueryEvent, UpdateEvent)
import Data.Acid.Advanced (query', update')
import Data.Attoparsec.Text.Lazy (Parser, parseOnly, char, asciiCI, try, takeWhile, takeWhile1)
import qualified Data.HashMap.Lazy as HashMap
import qualified Data.List as List
import qualified Data.Map as Map
import Data.Monoid ((<>), mappend, mconcat)
import qualified Data.Serialize as S
import Data.Traversable (sequenceA)
import qualified Data.Vector as Vector
import Data.ByteString.Lazy as LB (ByteString)
import Data.ByteString.Lazy.UTF8 as LB (toString)
import Data.Data (Data, Typeable)
import Data.Map (Map)
import Data.Maybe (fromJust)
import Data.SafeCopy (SafeCopy(..), Contained, deriveSafeCopy, base, contain)
import Data.Set (Set)
import qualified Data.Set as Set
import Data.Sequence (Seq)
import qualified Data.Text as T
import qualified Data.Text as Text
import qualified Data.Text.Lazy as TL
import Data.Text.Lazy.Builder (Builder, fromText)
import qualified Data.Text.Lazy.Builder as B
import Data.Time.Clock (UTCTime)
import Data.Time.Format (formatTime)
import Data.UserId (UserId(..))
import Happstack.Server ( CookieLife(Session), Happstack, ServerMonad(..), FilterMonad(..)
, WebMonad(..), Input, Request(..), Response, HasRqData(..)
, ServerPart, ServerPartT, UnWebT, addCookie, expireCookie, escape
, internalServerError, lookCookieValue, mapServerPartT, mkCookie
, toResponse
)
import Happstack.Server.HSP.HTML ()
import Happstack.Server.XMLGenT ()
import Happstack.Server.Internal.Monads (FilterFun)
import HSP.Google.Analytics (UACCT, universalAnalytics)
import HSP.XML
import HSP.XMLGenerator
import HSP.JMacro (IntegerSupply(..))
import Language.Javascript.JMacro
import Prelude hiding (takeWhile)
import Data.Time.Locale.Compat (defaultTimeLocale)
import Text.Blaze.Html (Html)
import Text.Blaze.Html.Renderer.Text (renderHtml)
import Text.Reform (CommonFormError, Form, FormError(..))
import Web.Routes (URL, MonadRoute(askRouteFn), RouteT(RouteT, unRouteT), mapRouteT, showURL, withRouteT)
import Web.Plugins.Core (Plugins, getConfig, getPluginsSt, modifyPluginsSt, getTheme)
import qualified Web.Routes as R
import Web.Routes.Happstack (seeOtherURL)
import Web.Routes.XMLGenT ()
type ThemeName = T.Text
newtype ThemeStyleId = ThemeStyleId { unThemeStyleId :: Int }
deriving (Eq, Ord, Read, Show, Data, Typeable)
instance SafeCopy ThemeStyleId where
getCopy = contain $ ThemeStyleId <$> S.get
putCopy (ThemeStyleId i) = contain $ S.put i
errorTypeName _ = "ThemeStyleId"
data ThemeStyle = ThemeStyle
{ themeStyleName :: T.Text
, themeStyleDescription :: T.Text
, themeStylePreview :: Maybe FilePath
, themeStyleTemplate :: forall headers body.
( EmbedAsChild (ClckT ClckURL (ServerPartT IO)) headers
, EmbedAsChild (ClckT ClckURL (ServerPartT IO)) body) =>
T.Text
-> headers
-> body
-> XMLGenT (ClckT ClckURL (ServerPartT IO)) XML
}
data Theme = Theme
{ themeName :: ThemeName
, themeStyles :: [ThemeStyle]
, themeDataDir :: IO FilePath
}
themeTemplate :: ( EmbedAsChild (ClckT ClckURL (ServerPartT IO)) headers
, EmbedAsChild (ClckT ClckURL (ServerPartT IO)) body
) =>
ClckPlugins
-> ThemeStyleId
-> T.Text
-> headers
-> body
-> ClckT ClckURL (ServerPartT IO) Response
themeTemplate plugins tsid ttl hdrs bdy =
do mTheme <- getTheme plugins
case mTheme of
Nothing -> escape $ internalServerError $ toResponse $ ("No theme package is loaded." :: T.Text)
(Just theme) ->
case lookupThemeStyle tsid (themeStyles theme) of
Nothing -> escape $ internalServerError $ toResponse $ ("The current theme does not seem to contain any theme styles." :: T.Text)
(Just themeStyle) ->
fmap toResponse $ unXMLGenT $ ((themeStyleTemplate themeStyle) ttl hdrs bdy)
lookupThemeStyle :: ThemeStyleId -> [a] -> Maybe a
lookupThemeStyle _ [] = Nothing
lookupThemeStyle (ThemeStyleId 0) (t:_) = Just t
lookupThemeStyle (ThemeStyleId n) (t':ts) = lookupThemeStyle' (n - 1) ts
where
lookupThemeStyle' _ [] = Just t'
lookupThemeStyle' 0 (t:ts) = Just t
lookupThemeStyle' n (_:ts) = lookupThemeStyle' (n - 1) ts
getThemeStyles :: (MonadIO m) => ClckPlugins -> m [(ThemeStyleId, ThemeStyle)]
getThemeStyles plugins =
do mTheme <- getTheme plugins
case mTheme of
Nothing -> return []
(Just theme) -> return $ zip (map ThemeStyleId [0..]) (themeStyles theme)
data TLSSettings = TLSSettings
{ clckTLSPort :: Int
, clckTLSCert :: FilePath
, clckTLSKey :: FilePath
, clckTLSCA :: Maybe FilePath
}
data ClckwrksConfig = ClckwrksConfig
{ clckHostname :: String
, clckPort :: Int
, clckTLS :: Maybe TLSSettings
, clckHidePort :: Bool
, clckJQueryPath :: FilePath
, clckJQueryUIPath :: FilePath
, clckJSTreePath :: FilePath
, clckJSON2Path :: FilePath
, clckTopDir :: Maybe FilePath
, clckEnableAnalytics :: Bool
, clckInitHook :: T.Text -> ClckState -> ClckwrksConfig -> IO (ClckState, ClckwrksConfig)
}
calcBaseURI :: ClckwrksConfig -> T.Text
calcBaseURI c =
Text.pack $ "http://" ++ (clckHostname c) ++ if ((clckPort c /= 80) && (clckHidePort c == False)) then (':' : show (clckPort c)) else ""
calcTLSBaseURI :: ClckwrksConfig -> Maybe T.Text
calcTLSBaseURI c =
case clckTLS c of
Nothing -> Nothing
(Just tlsSettings) ->
Just $ Text.pack $ "https://" ++ (clckHostname c) ++ if ((clckPort c /= 443) && (clckHidePort c == False)) then (':' : show (clckTLSPort tlsSettings)) else ""
data ClckState = ClckState
{ acidState :: Acid
, uniqueId :: TVar Integer
, adminMenus :: [(T.Text, [(Set Role, T.Text, T.Text)])]
, enableAnalytics :: Bool
, plugins :: ClckPlugins
, requestInit :: ServerPart ()
}
newtype ClckT url m a = ClckT { unClckT :: RouteT url (StateT ClckState m) a }
deriving (Functor, Applicative, Alternative, Monad, MonadIO, MonadPlus, ServerMonad, HasRqData, FilterMonad r, WebMonad r, MonadState ClckState)
instance (Happstack m) => Happstack (ClckT url m)
instance MonadTrans (ClckT url) where
lift = ClckT . lift . lift
evalClckT :: (Monad m) =>
(url -> [(Text.Text, Maybe Text.Text)] -> Text.Text)
-> ClckState
-> ClckT url m a
-> m a
evalClckT showFn clckState m = evalStateT (unRouteT (unClckT m) showFn) clckState
execClckT :: (Monad m) =>
(url -> [(Text.Text, Maybe Text.Text)] -> Text.Text)
-> ClckState
-> ClckT url m a
-> m ClckState
execClckT showFn clckState m =
execStateT (unRouteT (unClckT m) showFn) clckState
runClckT :: (Monad m) =>
(url -> [(Text.Text, Maybe Text.Text)] -> Text.Text)
-> ClckState
-> ClckT url m a
-> m (a, ClckState)
runClckT showFn clckState m =
runStateT (unRouteT (unClckT m) showFn) clckState
mapClckT :: (m (a, ClckState) -> n (b, ClckState))
-> ClckT url m a
-> ClckT url n b
mapClckT f (ClckT r) = ClckT $ mapRouteT (mapStateT f) r
data ClckFormError
= ClckCFE (CommonFormError [Input])
| PDE ProfileDataError
| EmptyUsername
deriving (Show)
instance FormError ClckFormError where
type ErrorInputType ClckFormError = [Input]
commonFormError = ClckCFE
type ClckFormT error m = Form m [Input] error [XMLGenT m XML] ()
type ClckForm url = Form (ClckT url (ServerPartT IO)) [Input] ClckFormError [XMLGenT (ClckT url (ServerPartT IO)) XML] ()
data ClckPluginsSt = ClckPluginsSt
{ cpsPreProcessors :: forall m. (Functor m, MonadIO m, Happstack m) => [TL.Text -> ClckT ClckURL m TL.Text]
, cpsNavBarLinks :: [ClckT ClckURL IO (String, [NamedLink])]
, cpsAcid :: Acid
}
initialClckPluginsSt :: Acid -> ClckPluginsSt
initialClckPluginsSt acid = ClckPluginsSt
{ cpsPreProcessors = []
, cpsNavBarLinks = []
, cpsAcid = acid
}
type ClckPlugins = Plugins Theme (ClckT ClckURL (ServerPartT IO) Response) (ClckT ClckURL IO ()) ClckwrksConfig ClckPluginsSt
setUnique :: (Functor m, MonadIO m) => Integer -> ClckT url m ()
setUnique i =
do u <- uniqueId <$> get
liftIO $ atomically $ writeTVar u i
getUnique :: (Functor m, MonadIO m) => ClckT url m Integer
getUnique =
do u <- uniqueId <$> get
liftIO $ atomically $ do i <- readTVar u
writeTVar u (succ i)
return i
getEnableAnalytics :: (Functor m, MonadState ClckState m) => m Bool
getEnableAnalytics = enableAnalytics <$> get
addAdminMenu :: (Monad m) => (T.Text, [(Set Role, T.Text, T.Text)]) -> ClckT url m ()
addAdminMenu (category, entries) =
modify $ \cs ->
let oldMenus = adminMenus cs
newMenus = Map.toAscList $ Map.insertWith List.union category entries $ Map.fromList oldMenus
in cs { adminMenus = newMenus }
appendRequestInit :: (Monad m) => ServerPart () -> ClckT url m ()
appendRequestInit action =
modify $ \cs -> cs { requestInit = (requestInit cs) >> action }
withRouteClckT :: ((url' -> [(T.Text, Maybe T.Text)] -> T.Text) -> url -> [(T.Text, Maybe T.Text)] -> T.Text)
-> ClckT url m a
-> ClckT url' m a
withRouteClckT f (ClckT routeT) = (ClckT $ withRouteT f routeT)
type Clck url = ClckT url (ServerPartT IO)
instance (Functor m, MonadIO m) => IntegerSupply (ClckT url m) where
nextInteger = getUnique
nestURL :: (url1 -> url2) -> ClckT url1 m a -> ClckT url2 m a
nestURL f (ClckT r) = ClckT $ R.nestURL f r
withAbs :: (Happstack m) => ClckT url m a -> ClckT url m a
withAbs m =
do secure <- rqSecure <$> askRq
clckState <- get
cc <- getConfig (plugins clckState)
let base = if secure then (fromJust $ calcTLSBaseURI cc) else calcBaseURI cc
withAbs' base m
withAbs' :: T.Text
-> ClckT url m a
-> ClckT url m a
withAbs' prefix (ClckT (RouteT r)) =
ClckT $ RouteT $ \showFn ->
r (\url params -> prefix <> showFn url params)
instance (Monad m) => MonadRoute (ClckT url m) where
type URL (ClckT url m) = url
askRouteFn = ClckT $ askRouteFn
query :: forall event m. (QueryEvent event, GetAcidState m (EventState event), Functor m, MonadIO m, MonadState ClckState m) => event -> m (EventResult event)
query event =
do as <- getAcidState
query' (as :: AcidState (EventState event)) event
update :: forall event m. (UpdateEvent event, GetAcidState m (EventState event), Functor m, MonadIO m, MonadState ClckState m) => event -> m (EventResult event)
update event =
do as <- getAcidState
update' (as :: AcidState (EventState event)) event
instance (GetAcidState m st) => GetAcidState (XMLGenT m) st where
getAcidState = XMLGenT getAcidState
instance (Functor m, Monad m) => GetAcidState (ClckT url m) CoreState where
getAcidState = (acidCore . acidState) <$> get
instance (Functor m, Monad m) => GetAcidState (ClckT url m) NavBarState where
getAcidState = (acidNavBar . acidState) <$> get
instance (Functor m, Monad m) => GetAcidState (ClckT url m) ProfileDataState where
getAcidState = (acidProfileData . acidState) <$> get
instance (Functor m, Monad m) => XMLGen (ClckT url m) where
type XMLType (ClckT url m) = XML
type StringType (ClckT url m) = TL.Text
newtype ChildType (ClckT url m) = ClckChild { unClckChild :: XML }
newtype AttributeType (ClckT url m) = ClckAttr { unClckAttr :: Attribute }
genElement n attrs children =
do attribs <- map unClckAttr <$> asAttr attrs
childer <- flattenCDATA . map (unClckChild) <$> asChild children
XMLGenT $ return (Element
(toName n)
attribs
childer
)
xmlToChild = ClckChild
pcdataToChild = xmlToChild . pcdata
flattenCDATA :: [XML] -> [XML]
flattenCDATA cxml =
case flP cxml [] of
[] -> []
[CDATA _ ""] -> []
xs -> xs
where
flP :: [XML] -> [XML] -> [XML]
flP [] bs = reverse bs
flP [x] bs = reverse (x:bs)
flP (x:y:xs) bs = case (x,y) of
(CDATA e1 s1, CDATA e2 s2) | e1 == e2 -> flP (CDATA e1 (s1<>s2) : xs) bs
_ -> flP (y:xs) (x:bs)
instance (Functor m, Monad m) => EmbedAsAttr (ClckT url m) Attribute where
asAttr = return . (:[]) . ClckAttr
instance (Functor m, Monad m, IsName n TL.Text) => EmbedAsAttr (ClckT url m) (Attr n String) where
asAttr (n := str) = asAttr $ MkAttr (toName n, pAttrVal $ TL.pack str)
instance (Functor m, Monad m, IsName n TL.Text) => EmbedAsAttr (ClckT url m) (Attr n Char) where
asAttr (n := c) = asAttr (n := TL.singleton c)
instance (Functor m, Monad m, IsName n TL.Text) => EmbedAsAttr (ClckT url m) (Attr n Bool) where
asAttr (n := True) = asAttr $ MkAttr (toName n, pAttrVal "true")
asAttr (n := False) = asAttr $ MkAttr (toName n, pAttrVal "false")
instance (Functor m, Monad m, IsName n TL.Text) => EmbedAsAttr (ClckT url m) (Attr n Int) where
asAttr (n := i) = asAttr $ MkAttr (toName n, pAttrVal $ TL.pack (show i))
instance (Functor m, Monad m, IsName n TL.Text) => EmbedAsAttr (ClckT url m) (Attr n Integer) where
asAttr (n := i) = asAttr $ MkAttr (toName n, pAttrVal $ TL.pack (show i))
instance (IsName n TL.Text) => EmbedAsAttr (Clck ClckURL) (Attr n ClckURL) where
asAttr (n := u) =
do url <- showURL u
asAttr $ MkAttr (toName n, pAttrVal $ TL.fromStrict url)
instance (IsName n TL.Text) => EmbedAsAttr (Clck AdminURL) (Attr n AdminURL) where
asAttr (n := u) =
do url <- showURL u
asAttr $ MkAttr (toName n, pAttrVal (TL.fromStrict url))
instance (Functor m, Monad m, IsName n TL.Text) => (EmbedAsAttr (ClckT url m) (Attr n TL.Text)) where
asAttr (n := a) = asAttr $ MkAttr (toName n, pAttrVal $ a)
instance (Functor m, Monad m, IsName n TL.Text) => (EmbedAsAttr (ClckT url m) (Attr n T.Text)) where
asAttr (n := a) = asAttr $ MkAttr (toName n, pAttrVal $ TL.fromStrict a)
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) Char where
asChild = XMLGenT . return . (:[]) . ClckChild . pcdata . TL.singleton
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) String where
asChild = XMLGenT . return . (:[]) . ClckChild . pcdata . TL.pack
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) Int where
asChild = XMLGenT . return . (:[]) . ClckChild . pcdata . TL.pack . show
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) Integer where
asChild = XMLGenT . return . (:[]) . ClckChild . pcdata . TL.pack . show
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) Double where
asChild = XMLGenT . return . (:[]) . ClckChild . pcdata . TL.pack . show
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) Float where
asChild = XMLGenT . return . (:[]) . ClckChild . pcdata . TL.pack . show
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) TL.Text where
asChild = asChild . TL.unpack
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) T.Text where
asChild = asChild . T.unpack
instance (EmbedAsChild (ClckT url1 m) a, url1 ~ url2) => EmbedAsChild (ClckT url1 m) (ClckT url2 m a) where
asChild c =
do a <- XMLGenT c
asChild a
instance (Functor m, MonadIO m, EmbedAsChild (ClckT url m) a) => EmbedAsChild (ClckT url m) (IO a) where
asChild c =
do a <- XMLGenT (liftIO c)
asChild a
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) XML where
asChild = XMLGenT . return . (:[]) . ClckChild
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) Html where
asChild = XMLGenT . return . (:[]) . ClckChild . cdata . renderHtml
instance (Functor m, MonadIO m, Happstack m) => EmbedAsChild (ClckT url m) ClckFormError where
asChild formError = asChild (show formError)
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) () where
asChild () = return []
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) UTCTime where
asChild = asChild . formatTime defaultTimeLocale "%a, %F @ %r"
instance (Functor m, Monad m, EmbedAsChild (ClckT url m) a) => EmbedAsChild (ClckT url m) (Maybe a) where
asChild Nothing = asChild ()
asChild (Just a) = asChild a
instance (Functor m, Monad m) => AppendChild (ClckT url m) XML where
appAll xml children = do
chs <- children
case xml of
CDATA _ _ -> return xml
Element n as cs -> return $ Element n as (cs ++ (map unClckChild chs))
instance (Functor m, Monad m) => SetAttr (ClckT url m) XML where
setAll xml hats = do
attrs <- hats
case xml of
CDATA _ _ -> return xml
Element n as cs -> return $ Element n (foldr (:) as (map unClckAttr attrs)) cs
instance (Functor m, Monad m) => XMLGenerator (ClckT url m)
data Content
= TrustedHtml T.Text
| PlainText T.Text
deriving (Eq, Ord, Read, Show, Data, Typeable)
instance (Functor m, Monad m) => EmbedAsChild (ClckT url m) Content where
asChild (TrustedHtml html) = asChild $ cdata (TL.fromStrict html)
asChild (PlainText txt) = asChild $ pcdata (TL.fromStrict txt)
addPreProc :: (MonadIO m) =>
Plugins theme n hook config ClckPluginsSt
-> (forall mm. (Functor mm, MonadIO mm, Happstack mm) => TL.Text -> ClckT ClckURL mm TL.Text)
-> m ()
addPreProc plugins p =
modifyPluginsSt plugins $ \cps -> cps { cpsPreProcessors = p : (cpsPreProcessors cps) }
data Segment cmd
= TextBlock T.Text
| Cmd cmd
deriving Show
instance Functor Segment where
fmap f (TextBlock t) = TextBlock t
fmap f (Cmd c) = Cmd (f c)
transform :: (Monad m) =>
(cmd -> m Builder)
-> [Segment cmd]
-> m Builder
transform f segments =
do bs <- mapM (transformSegment f) segments
return (mconcat bs)
transformSegment :: (Monad m) =>
(cmd -> m Builder)
-> Segment cmd
-> m Builder
transformSegment f (TextBlock t) = return (B.fromText t)
transformSegment f (Cmd cmd) = f cmd
segments :: T.Text
-> Parser a
-> Parser [Segment a]
segments name p =
many (cmd name p <|> plainText)
cmd :: T.Text -> Parser cmd -> Parser (Segment cmd)
cmd n p =
do char '{'
((try $ do asciiCI n
char '|'
r <- p
char '}'
return (Cmd r))
<|>
(do t <- takeWhile1 (/= '{')
return $ TextBlock (T.cons '{' t)))
plainText :: Parser (Segment cmd)
plainText =
do t <- takeWhile1 (/= '{')
return $ TextBlock t
setRedirectCookie :: (Happstack m) =>
String -> m ()
setRedirectCookie url =
addCookie Session (mkCookie "clckwrks-authenticate-redirect" url)
getRedirectCookie :: (Happstack m) =>
m (Maybe String)
getRedirectCookie =
do expireCookie "clckwrks-authenticate-redirect"
optional $ lookCookieValue "clckwrks-authenticate-redirect"
addNavBarCallback :: (MonadIO m) =>
Plugins theme n hook config ClckPluginsSt
-> ClckT ClckURL IO (String, [NamedLink])
-> m ()
addNavBarCallback plugins ml =
modifyPluginsSt plugins $ \cps -> cps { cpsNavBarLinks = (cpsNavBarLinks cps) ++ [ml] }
getNavBarLinks :: (MonadIO m) =>
Plugins theme n hook config ClckPluginsSt
-> ClckT ClckURL m NavBarLinks
getNavBarLinks plugins =
mapClckT liftIO $
do genNavBarLinks <- (cpsNavBarLinks <$> getPluginsSt plugins)
NavBarLinks <$> sequenceA genNavBarLinks
getPreProcessors :: (MonadIO m) =>
Plugins theme n hook config ClckPluginsSt
-> (forall mm. (Functor mm, MonadIO mm, Happstack mm) => ClckT url m [TL.Text -> ClckT ClckURL mm TL.Text])
getPreProcessors plugins =
mapClckT liftIO $
(cpsPreProcessors <$> getPluginsSt plugins)
googleAnalytics :: XMLGenT (Clck url) XML
googleAnalytics =
do enabled <- getEnableAnalytics
case enabled of
False -> return $ cdata ""
True ->
do muacct <- query GetUACCT
case muacct of
Nothing -> return $ cdata ""
(Just uacct) ->
universalAnalytics uacct