Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.
 
 
 
 
 
 

363 linhas
11 KiB

  1. # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
  2. # MIT License. See license.txt
  3. from __future__ import unicode_literals
  4. import frappe, os
  5. from frappe.website.utils import can_cache, delete_page_cache, extract_title
  6. from frappe.model.document import get_controller
  7. from six import text_type
  8. def resolve_route(path):
  9. """Returns the page route object based on searching in pages and generators.
  10. The `www` folder is also a part of generator **Web Page**.
  11. The only exceptions are `/about` and `/contact` these will be searched in Web Pages
  12. first before checking the standard pages."""
  13. if path not in ("about", "contact"):
  14. context = get_page_info_from_template(path)
  15. if context:
  16. return context
  17. return get_page_context_from_doctype(path)
  18. else:
  19. context = get_page_context_from_doctype(path)
  20. if context:
  21. return context
  22. return get_page_info_from_template(path)
  23. def get_page_context(path):
  24. page_context = None
  25. if can_cache():
  26. page_context_cache = frappe.cache().hget("page_context", path) or {}
  27. page_context = page_context_cache.get(frappe.local.lang, None)
  28. if not page_context:
  29. page_context = make_page_context(path)
  30. if can_cache(page_context.no_cache):
  31. page_context_cache[frappe.local.lang] = page_context
  32. frappe.cache().hset("page_context", path, page_context_cache)
  33. return page_context
  34. def make_page_context(path):
  35. context = resolve_route(path)
  36. if not context:
  37. raise frappe.DoesNotExistError
  38. context.doctype = context.ref_doctype
  39. if context.page_title:
  40. context.title = context.page_title
  41. context.pathname = frappe.local.path
  42. return context
  43. def get_page_info_from_template(path):
  44. '''Return page_info from path'''
  45. for app in frappe.get_installed_apps(frappe_last=True):
  46. app_path = frappe.get_app_path(app)
  47. folders = get_start_folders()
  48. for start in folders:
  49. search_path = os.path.join(app_path, start, path)
  50. options = (search_path, search_path + '.html', search_path + '.md',
  51. search_path + '/index.html', search_path + '/index.md')
  52. for o in options:
  53. option = frappe.as_unicode(o)
  54. if os.path.exists(option) and not os.path.isdir(option):
  55. return get_page_info(option, app, start, app_path=app_path)
  56. return None
  57. def get_page_context_from_doctype(path):
  58. page_info = get_page_info_from_doctypes(path)
  59. if page_info:
  60. return frappe.get_doc(page_info.get("doctype"),
  61. page_info.get("name")).get_page_info()
  62. def clear_sitemap():
  63. delete_page_cache("*")
  64. def get_all_page_context_from_doctypes():
  65. '''Get all doctype generated routes (for sitemap.xml)'''
  66. routes = frappe.cache().get_value("website_generator_routes")
  67. if not routes:
  68. routes = get_page_info_from_doctypes()
  69. frappe.cache().set_value("website_generator_routes", routes)
  70. return routes
  71. def get_page_info_from_doctypes(path=None):
  72. routes = {}
  73. for doctype in get_doctypes_with_web_view():
  74. condition = ""
  75. values = []
  76. controller = get_controller(doctype)
  77. meta = frappe.get_meta(doctype)
  78. condition_field = meta.is_published_field or controller.website.condition_field
  79. if condition_field:
  80. condition ="where {0}=1".format(condition_field)
  81. if path:
  82. condition += ' {0} `route`=%s limit 1'.format('and' if 'where' in condition else 'where')
  83. values.append(path)
  84. try:
  85. for r in frappe.db.sql("""select route, name, modified from `tab{0}`
  86. {1}""".format(doctype, condition), values=values, as_dict=True):
  87. routes[r.route] = {"doctype": doctype, "name": r.name, "modified": r.modified}
  88. # just want one path, return it!
  89. if path:
  90. return routes[r.route]
  91. except Exception as e:
  92. if e.args[0]!=1054: raise e
  93. return routes
  94. def get_pages(app=None):
  95. '''Get all pages. Called for docs / sitemap'''
  96. def _build(app):
  97. pages = {}
  98. if app:
  99. apps = [app]
  100. else:
  101. apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps()
  102. for app in apps:
  103. app_path = frappe.get_app_path(app)
  104. for start in get_start_folders():
  105. pages.update(get_pages_from_path(start, app, app_path))
  106. return pages
  107. return frappe.cache().get_value('website_pages', lambda: _build(app))
  108. def get_pages_from_path(start, app, app_path):
  109. pages = {}
  110. start_path = os.path.join(app_path, start)
  111. if os.path.exists(start_path):
  112. for basepath, folders, files in os.walk(start_path):
  113. # add missing __init__.py
  114. if not '__init__.py' in files:
  115. open(os.path.join(basepath, '__init__.py'), 'a').close()
  116. for fname in files:
  117. fname = frappe.utils.cstr(fname)
  118. if not '.' in fname:
  119. continue
  120. page_name, extn = fname.rsplit(".", 1)
  121. if extn in ('js', 'css') and os.path.exists(os.path.join(basepath, fname + '.html')):
  122. # js, css is linked to html, skip
  123. continue
  124. if extn in ("html", "xml", "js", "css", "md"):
  125. page_info = get_page_info(os.path.join(basepath, fname),
  126. app, start, basepath, app_path, fname)
  127. pages[page_info.route] = page_info
  128. # print frappe.as_json(pages[-1])
  129. return pages
  130. def get_page_info(path, app, start, basepath=None, app_path=None, fname=None):
  131. '''Load page info'''
  132. if fname is None:
  133. fname = os.path.basename(path)
  134. if app_path is None:
  135. app_path = frappe.get_app_path(app)
  136. if basepath is None:
  137. basepath = os.path.dirname(path)
  138. page_name, extn = fname.rsplit(".", 1)
  139. # add website route
  140. page_info = frappe._dict()
  141. page_info.basename = page_name if extn in ('html', 'md') else fname
  142. page_info.basepath = basepath
  143. page_info.page_or_generator = "Page"
  144. page_info.template = os.path.relpath(os.path.join(basepath, fname), app_path)
  145. if page_info.basename == 'index':
  146. page_info.basename = ""
  147. # get route from template name
  148. page_info.route = page_info.template.replace(start, '').strip('/')
  149. if os.path.basename(page_info.route) in ('index.html', 'index.md'):
  150. page_info.route = os.path.dirname(page_info.route)
  151. # remove the extension
  152. if page_info.route.endswith('.md') or page_info.route.endswith('.html'):
  153. page_info.route = page_info.route.rsplit('.', 1)[0]
  154. page_info.name = page_info.page_name = page_info.route
  155. # controller
  156. page_info.controller_path = os.path.join(basepath, page_name.replace("-", "_") + ".py")
  157. if os.path.exists(page_info.controller_path):
  158. controller = app + "." + os.path.relpath(page_info.controller_path,
  159. app_path).replace(os.path.sep, ".")[:-3]
  160. page_info.controller = controller
  161. # get the source
  162. setup_source(page_info)
  163. # extract properties from HTML comments
  164. load_properties(page_info)
  165. # if not page_info.title:
  166. # print('no-title-for', page_info.route)
  167. return page_info
  168. def setup_source(page_info):
  169. '''Get the HTML source of the template'''
  170. from markdown2 import markdown
  171. jenv = frappe.get_jenv()
  172. source = jenv.loader.get_source(jenv, page_info.template)[0]
  173. html = ''
  174. if page_info.template.endswith('.md'):
  175. source = markdown(source)
  176. # if only content
  177. if page_info.template.endswith('.html') or page_info.template.endswith('.md'):
  178. if ('</body>' not in source) and ('{% block' not in source):
  179. page_info.only_content = True
  180. html = '{% extends "templates/web.html" %}'
  181. html += '\n{% block page_content %}\n' + source + '\n{% endblock %}'
  182. else:
  183. html = source
  184. # load css/js files
  185. js, css = '', ''
  186. js_path = os.path.join(page_info.basepath, (page_info.basename or 'index') + '.js')
  187. if os.path.exists(js_path):
  188. if not '{% block script %}' in html:
  189. js = text_type(open(js_path, 'r').read(), 'utf-8')
  190. html += '\n{% block script %}<script>' + js + '\n</script>\n{% endblock %}'
  191. css_path = os.path.join(page_info.basepath, (page_info.basename or 'index') + '.css')
  192. if os.path.exists(css_path):
  193. if not '{% block style %}' in html:
  194. css = text_type(open(css_path, 'r').read(), 'utf-8')
  195. html += '\n{% block style %}\n<style>\n' + css + '\n</style>\n{% endblock %}'
  196. page_info.source = html
  197. # show table of contents
  198. setup_index(page_info)
  199. def setup_index(page_info):
  200. '''Build page sequence from index.txt'''
  201. if page_info.basename=='':
  202. # load index.txt if loading all pages
  203. index_txt_path = os.path.join(page_info.basepath, 'index.txt')
  204. if os.path.exists(index_txt_path):
  205. page_info.index = open(index_txt_path, 'r').read().splitlines()
  206. def load_properties(page_info):
  207. '''Load properties like no_cache, title from raw'''
  208. if not page_info.title:
  209. page_info.title = extract_title(page_info.source, page_info.route)
  210. # if page_info.title and not '{% block title %}' in page_info.source:
  211. # if not page_info.only_content:
  212. # page_info.source += '\n{% block title %}{{ title }}{% endblock %}'
  213. if "<!-- no-breadcrumbs -->" in page_info.source:
  214. page_info.no_breadcrumbs = 1
  215. if "<!-- show-sidebar -->" in page_info.source:
  216. page_info.show_sidebar = 1
  217. if "<!-- add-breadcrumbs -->" in page_info.source:
  218. page_info.add_breadcrumbs = 1
  219. if "<!-- no-header -->" in page_info.source:
  220. page_info.no_header = 1
  221. # else:
  222. # # every page needs a header
  223. # # add missing header if there is no <h1> tag
  224. # if (not '{% block header %}' in page_info.source) and (not '<h1' in page_info.source):
  225. # if page_info.only_content:
  226. # page_info.source = '<h1>{{ title }}</h1>\n' + page_info.source
  227. # else:
  228. # page_info.source += '\n{% block header %}<h1>{{ title }}</h1>{% endblock %}'
  229. if "<!-- no-cache -->" in page_info.source:
  230. page_info.no_cache = 1
  231. if "<!-- no-sitemap -->" in page_info.source:
  232. page_info.no_cache = 1
  233. def get_doctypes_with_web_view():
  234. '''Return doctypes with Has Web View or set via hooks'''
  235. def _get():
  236. installed_apps = frappe.get_installed_apps()
  237. doctypes = frappe.get_hooks("website_generators")
  238. doctypes += [d.name for d in frappe.get_all('DocType', 'name, module',
  239. dict(has_web_view=1)) if frappe.local.module_app[frappe.scrub(d.module)] in installed_apps]
  240. return doctypes
  241. return frappe.cache().get_value('doctypes_with_web_view', _get)
  242. def sync_global_search():
  243. '''Sync page content in global search'''
  244. from frappe.website.render import render_page
  245. from frappe.utils.global_search import sync_global_search
  246. from bs4 import BeautifulSoup
  247. if frappe.flags.update_global_search:
  248. sync_global_search()
  249. frappe.flags.update_global_search = []
  250. frappe.session.user = 'Guest'
  251. frappe.local.no_cache = True
  252. frappe.db.sql('delete from __global_search where doctype="Static Web Page"')
  253. for app in frappe.get_installed_apps(frappe_last=True):
  254. app_path = frappe.get_app_path(app)
  255. folders = get_start_folders()
  256. for start in folders:
  257. for basepath, folders, files in os.walk(os.path.join(app_path, start)):
  258. for f in files:
  259. if f.endswith('.html') or f.endswith('.md'):
  260. path = os.path.join(basepath, f.rsplit('.', 1)[0])
  261. try:
  262. content = render_page(path)
  263. soup = BeautifulSoup(content, 'html.parser')
  264. text = ''
  265. route = os.path.relpath(path, os.path.join(app_path, start))
  266. for div in soup.findAll("div", {'class':'page-content'}):
  267. text += div.text
  268. frappe.flags.update_global_search.append(
  269. dict(doctype='Static Web Page',
  270. name=route,
  271. content=text_type(text),
  272. published=1,
  273. title=text_type(soup.title.string),
  274. route=route))
  275. except Exception:
  276. pass
  277. sync_global_search()
  278. def get_start_folders():
  279. return frappe.local.flags.web_pages_folders or ('www', 'templates/pages')