8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
8 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
9 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS |
10 # FOR A PARTICULAR PURPOSE. |
10 # FOR A PARTICULAR PURPOSE. |
11 # |
11 # |
12 |
12 |
13 import re |
|
14 |
|
15 from pyquery import PyQuery |
|
16 from pyramid.events import subscriber |
13 from pyramid.events import subscriber |
17 from pyramid.threadlocal import get_current_registry |
|
18 from zope.interface import implementer |
14 from zope.interface import implementer |
19 from zope.lifecycleevent import ObjectCreatedEvent |
|
20 from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectModifiedEvent |
15 from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectModifiedEvent |
21 from zope.schema.fieldproperty import FieldProperty |
16 from zope.schema.fieldproperty import FieldProperty |
22 |
17 |
23 from pyams_content.component.association.interfaces import IAssociationContainer |
18 from pyams_content.component.extfile.interfaces import IExtFileContainerTarget |
24 from pyams_content.component.extfile.interfaces import IBaseExtFile, IExtFileContainerTarget |
|
25 from pyams_content.component.illustration.interfaces import IIllustrationTarget |
19 from pyams_content.component.illustration.interfaces import IIllustrationTarget |
26 from pyams_content.component.links import ExternalLink, InternalLink, MailtoLink |
20 from pyams_content.component.links.interfaces import ILinkContainerTarget |
27 from pyams_content.component.links.interfaces import IExternalLink, IInternalLink, \ |
|
28 ILinkContainerTarget, IMailtoLink |
|
29 from pyams_content.component.paragraph import BaseParagraph, BaseParagraphContentChecker, \ |
21 from pyams_content.component.paragraph import BaseParagraph, BaseParagraphContentChecker, \ |
30 BaseParagraphFactory |
22 BaseParagraphFactory |
|
23 from pyams_content.component.paragraph.association import check_associations |
31 from pyams_content.component.paragraph.interfaces import IParagraphFactory |
24 from pyams_content.component.paragraph.interfaces import IParagraphFactory |
32 from pyams_content.component.paragraph.interfaces.html import HTML_PARAGRAPH_NAME, \ |
25 from pyams_content.component.paragraph.interfaces.html import HTML_PARAGRAPH_NAME, \ |
33 HTML_PARAGRAPH_RENDERERS, HTML_PARAGRAPH_TYPE, IHTMLParagraph, IRawParagraph, \ |
26 HTML_PARAGRAPH_RENDERERS, HTML_PARAGRAPH_TYPE, IHTMLParagraph, IRawParagraph, \ |
34 RAW_PARAGRAPH_NAME, RAW_PARAGRAPH_RENDERERS, RAW_PARAGRAPH_TYPE |
27 RAW_PARAGRAPH_NAME, RAW_PARAGRAPH_RENDERERS, RAW_PARAGRAPH_TYPE |
35 from pyams_content.features.checker.interfaces import IContentChecker, MISSING_LANG_VALUE, \ |
28 from pyams_content.features.checker.interfaces import IContentChecker, MISSING_LANG_VALUE, \ |
36 MISSING_VALUE |
29 MISSING_VALUE |
37 from pyams_content.features.renderer import RenderersVocabulary |
30 from pyams_content.features.renderer import RenderersVocabulary |
38 from pyams_i18n.interfaces import II18n, II18nManager, INegotiator |
31 from pyams_i18n.interfaces import II18n, II18nManager, INegotiator |
39 from pyams_sequence.interfaces import ISequentialIntIds |
|
40 from pyams_utils.adapter import adapter_config |
32 from pyams_utils.adapter import adapter_config |
41 from pyams_utils.factory import factory_config |
33 from pyams_utils.factory import factory_config |
42 from pyams_utils.registry import get_utility, utility_config |
34 from pyams_utils.registry import get_utility, utility_config |
43 from pyams_utils.request import check_request |
|
44 from pyams_utils.traversing import get_parent |
35 from pyams_utils.traversing import get_parent |
45 from pyams_utils.url import absolute_url |
|
46 from pyams_utils.vocabulary import vocabulary_config |
36 from pyams_utils.vocabulary import vocabulary_config |
47 |
37 |
48 |
38 |
49 __docformat__ = 'restructuredtext' |
39 __docformat__ = 'restructuredtext' |
50 |
40 |
127 |
117 |
128 name = HTML_PARAGRAPH_NAME |
118 name = HTML_PARAGRAPH_NAME |
129 content_type = HTMLParagraph |
119 content_type = HTMLParagraph |
130 |
120 |
131 |
121 |
132 FULL_EMAIL = re.compile('(.*) \<(.*)\>') |
|
133 |
|
134 |
|
135 def check_associations(context, body, lang, notify=True): |
|
136 """Check for link associations from HTML content""" |
|
137 registry = get_current_registry() |
|
138 associations = IAssociationContainer(context) |
|
139 html = PyQuery('<html>{0}</html>'.format(body)) |
|
140 for link in html('a[href]'): |
|
141 link_info = None |
|
142 has_link = False |
|
143 href = link.attrib['href'] |
|
144 if href.startswith('oid://'): |
|
145 sequence = get_utility(ISequentialIntIds) |
|
146 oid = sequence.get_full_oid(href.split('//', 1)[1]) |
|
147 for association in associations.values(): |
|
148 internal_info = IInternalLink(association, None) |
|
149 if (internal_info is not None) and (internal_info.reference == oid): |
|
150 has_link = True |
|
151 break |
|
152 if not has_link: |
|
153 link_info = InternalLink() |
|
154 link_info.visible = False |
|
155 link_info.reference = oid |
|
156 link_info.title = {lang: link.attrib.get('title') or link.text} |
|
157 elif href.startswith('mailto:'): |
|
158 name = None |
|
159 email = href[7:] |
|
160 if '<' in email: |
|
161 groups = FULL_EMAIL.findall(email) |
|
162 if groups: |
|
163 name, email = groups[0] |
|
164 for association in associations.values(): |
|
165 mailto_info = IMailtoLink(association, None) |
|
166 if (mailto_info is not None) and (mailto_info.address == email): |
|
167 has_link = True |
|
168 break |
|
169 if not has_link: |
|
170 link_info = MailtoLink() |
|
171 link_info.visible = False |
|
172 link_info.address = email |
|
173 link_info.address_name = name or email |
|
174 link_info.title = {lang: link.attrib.get('title') or link.text} |
|
175 elif href.startswith('http://') or href.startswith('https://'): |
|
176 for association in associations.values(): |
|
177 external_info = IExternalLink(association, None) |
|
178 if (external_info is not None) and (external_info.url == href): |
|
179 has_link = True |
|
180 break |
|
181 else: |
|
182 extfile_info = IBaseExtFile(association, None) |
|
183 if extfile_info is not None: |
|
184 request = check_request() |
|
185 extfile_url = absolute_url( |
|
186 II18n(extfile_info).query_attribute('data', request=request), |
|
187 request=request) |
|
188 if extfile_url.endswith(href): |
|
189 has_link = True |
|
190 break |
|
191 if not has_link: |
|
192 link_info = ExternalLink() |
|
193 link_info.visible = False |
|
194 link_info.url = href |
|
195 link_info.title = {lang: link.attrib.get('title') or link.text} |
|
196 if link_info is not None: |
|
197 registry.notify(ObjectCreatedEvent(link_info)) |
|
198 associations.append(link_info, notify=notify) |
|
199 |
|
200 |
|
201 @subscriber(IObjectAddedEvent, context_selector=IHTMLParagraph) |
122 @subscriber(IObjectAddedEvent, context_selector=IHTMLParagraph) |
202 def handle_added_html_paragraph(event): |
123 def handle_added_html_paragraph(event): |
203 """Check for new associations from added paragraph""" |
124 """Check for new associations from added paragraph""" |
204 paragraph = event.object |
125 paragraph = event.object |
205 for lang, body in (paragraph.body or {}).items(): |
126 for lang, body in (paragraph.body or {}).items(): |