-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpwic_extension.py
730 lines (668 loc) · 40.1 KB
/
pwic_extension.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
# Pwic.wiki server running on Python and SQLite
# Copyright (C) 2020-2025 Alexandre Bréard
#
# https://pwic.wiki
# https://github.com/gitbra/pwic
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
from typing import Any, Dict, List, Optional, Tuple
import sqlite3
from datetime import tzinfo
from multidict import MultiDict
from aiohttp import web
from prettytable import PrettyTable
from urllib.request import Request
from pwic_lib import PwicLib
class PwicExtension():
''' Extensions for Pwic.wiki
The behavior of Pwic.wiki is changeable in this file through a logic of events
positioned at critical positions in the code base. It is easier and safer to
implement some changes here but it remains technically sensitive.
Each method is always active and generally returns from 0 to 2 results.
The first one usually indicates if something happened. The second one provides
the new result. With no result, the parameters of the method are changeable
if they are passed as a reference, else raise an exception.
'''
# ============
# User exits
# ============
@staticmethod
def on_api_document_convert(sql: sqlite3.Cursor, # Cursor to query the database
project: str, # Name of the project
user: str, # Name of the user
page: Optional[str], # Name of the page
doc_id: int, # Identifier of the document
url: Optional[str], # Remote web page
markdown: str, # Converted Markdown
) -> str:
''' Event when a file (doc_id>0) or remote web page (doc_id=0) is converted to Markdown.
The result is the new converted text.
'''
return markdown
@staticmethod
def on_api_document_create_end(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
document: Dict[str, Any], # Document as defined in the database and extra fields
) -> None:
''' Event after a file is loaded on the server. The database is committed already.
You can trigger an asynchronous task to do what you want with the new/updated file.
It is up to you to update safely the table of the documents and do the appropriate audit.
By using the field "documents.exturl", the file must not exist locally anymore.
'''
@staticmethod
def on_api_document_create_start(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
document: Dict[str, Any], # Submitted document (changeable)
) -> bool:
''' Event when a new document is submitted and before many internal checks are executed.
The result indicates if the creation of the document is possible.
'''
return True
@staticmethod
def on_api_document_delete(sql: sqlite3.Cursor, # Cursor to query the database
request: Optional[web.Request], # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: Optional[str], # Name of the page
doc_id: Optional[int], # Identifier of the document
filename: str, # Name of the file
) -> bool:
''' Event when the file must be deleted.
For the local files, the result indicates if the deletion of the document is possible and Pwic.wiki will perform the deletion.
For the external files, you must delete the file with your custom logic, so the result indicates if the operation is successful.
The page and id may be None when a mandatory allowed technical maintenance occurs on the repository.
'''
# local_path = os.path.join(PwicConst.DOCUMENTS_PATH % project, filename)
return True
@staticmethod
def on_api_document_list(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
page: str, # Name of the page
documents: List[Dict[str, Any]], # List of the documents (changeable)
) -> None:
''' Event when the list of the documents of a page is requested.
Modify the parameter 'documents' without reallocating it.
'''
@staticmethod
def on_api_document_rename(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: str, # Name of the page
doc_id: int, # Identifier of the document
old_filename: str, # Current file name
new_filename: str, # Target file name
) -> bool:
''' Event when a local file is renamed.
The result indicates if the renaming of the document is possible.
'''
# local_path = os.path.join(PwicConst.DOCUMENTS_PATH % project, filename)
return True
@staticmethod
def on_api_page_create(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: str, # Name of the page
kb: bool, # Is knowledge base article
tags: str, # Tags
milestone: str, # Milestone
) -> bool:
''' Event when a new page is created.
The result indicates if the creation of the page is possible.
'''
return True
@staticmethod
def on_api_page_delete(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: str, # Name of the page
revision: int, # Number of the revision
) -> bool:
''' Event when a given revision of a page is about to be deleted.
The result indicates if the deletion of the revision is possible.
'''
return True
@staticmethod
def on_api_page_edit(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: str, # Name of the page
title: str, # Title of the page
markdown: str, # Content of the page
tags: str, # Tags
comment: str, # Reason for the commit
milestone: str, # Milestone
draft: bool, # Flag for draft
final: bool, # Flag for final
header: bool, # Flag for header
protection: bool, # Flag for protection
) -> bool:
''' Event when a new revision is submitted.
The result indicates if the update of the page is possible.
'''
return True
@staticmethod
def on_api_page_export(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: str, # Name of the page
revision: int, # Number of the revision
extension: str, # Extension of the file format
name: str # Target file name
) -> Tuple[bool, Any, Dict]:
''' Event when a single page is exported.
The first result indicates if the own implementation overrides the standard download.
The second result gives the new content to be downloaded. The value None denotes a forbidden download.
The third result configures the HTTP response to ease the download with a correct format.
An exception can be raised to cancel the download.
'''
return False, None, {}
@staticmethod
def on_api_page_move(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Source project
user: str, # Name of the user
page: str, # Source page
dst_project: str, # Destination project
dst_page: str, # Destination page
) -> bool:
''' Event when a given page is renamed and/or moved to another project.
The result indicates if both the rename and the move of the page is possible.
No other extension is called during the operation.
'''
return True
@staticmethod
def on_api_page_requested(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
action: str, # Performed action
project: str, # Name of the project
page: str, # Name of the page
revision: int, # Number of the revision
) -> None:
''' Event when a page is accessed.
An exception can be raised to guide the navigation.
'''
@staticmethod
def on_api_page_validate(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: str, # Name of the page
revision: int, # Number of the revision
) -> bool:
''' Event when a given revision of a page is validated.
The result indicates if the validation of the page is possible.
'''
return True
@staticmethod
def on_api_project_env_set(sql: sqlite3.Cursor, # Cursor to query the database
request: Optional[web.Request], # HTTP request when online
project: str, # Name of the project (empty = global scope)
user: str, # Name of the user
key: str, # Name of the option modified for the project
value: str, # New value of the option
) -> str:
''' Event when an option is about to be changed.
The non-null result gives the new value of the variable.
Setting the result to an empty string will delete the option.
'''
return value.replace('\r', '').strip()
@staticmethod
def on_api_project_info_get(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: str, # Name of a precise page
data: Dict[str, Dict[str, List[Dict[str, Any]]]], # Output data (changeable)
) -> None:
''' Event when the project information are queried through the API.
Modify the parameter 'data' to change the returned content.
'''
@staticmethod
def on_api_user_create(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
admin: str, # Name of the administrator
user: str, # Sanitized name of the new user account
) -> bool:
''' Event when an administrator requests the creation of a new user account (if needed) and also its assignment to the project.
The result indicates if the full operation is permitted.
This check is important because there is no native way to remove a misspelled user account.
'''
return True
@staticmethod
def on_api_user_password_change(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
user: str, # Name of the user whose password is modified
new_password: str, # New desired password
) -> bool:
''' Event when a user changes his password.
The result indicates if the modification of the password is allowed.
'''
return True
@staticmethod
def on_api_user_roles_set(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
admin: str, # Name of the administrator
user: str, # Name of the user whose rights are modified
role: str, # Affected role: admin, manager, editor, validator, reader, disabled, delete
state: Optional[bool], # New value of the role
) -> bool:
''' Event when an administrator deletes or modifies a role of a user account.
The result indicates if the modification of the role is allowed.
'''
return True
@staticmethod
def on_admin_split_project(sql_src: sqlite3.Cursor, # Cursor for the source database
sql_dst: sqlite3.Cursor, # Cursor for the target database
projects: List[str], # List of the impacted projects
) -> None:
''' Event when a database is split.
You can execute additional operations for your custom tables.
'''
@staticmethod
def on_admin_stats(sql: sqlite3.Cursor, # Cursor to query the database
tab: PrettyTable, # Result view
) -> None:
''' Event when an administrator runs the command "show-stats".
'''
# tab.add_row(['Custom stat', 'demo-project', '2024-01', 123])
@staticmethod
def on_audit(sql: sqlite3.Cursor, # Cursor to query the database
request: Optional[web.Request], # HTTP request, None if called from the console
event: Dict[str, Any], # Details of the event
) -> None:
''' Event after an auditable operation is just executed:
archive-audit change-password clear-cache create-backup create-document create-project create-revision
create-user delete-document delete-page delete-project delete-revision delete-user execute-sql
export-project fetch-url grant-admin grant-editor grant-manager grant-reader grant-validator
init-db login logout rename-document repair-documents reset-password reset-totp
set-* shutdown-server split-project start-server ungrant-admin ungrant-editor ungrant-manager
ungrant-reader ungrant-validator unlock-db unset-* update-document update-revision validate-revision
You cannot change the content of the event that is saved already.
You should not write yourself to the table 'audit'.
The database is not committed yet.
You cannot raise any exception.
'''
@staticmethod
def on_audit_skip(sql: sqlite3.Cursor, # Cursor to query the database
request: Optional[web.Request], # HTTP request, None if called from the console
event: Dict[str, Any], # Details of the event (passed by value)
) -> bool:
''' Event to block some audit events. You have no possibility to recover the rejected events.
You can't edit the event in the parameter. The result indicates if the audit event is skipped.
'''
return False
@staticmethod
def on_cache(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: str, # Name of the page
revision: int, # Revision of the page
) -> bool:
''' Event when a page is calling the cache.
The result indicates if the cache can be used, so if the page should not be regenerated.
'''
return True
@staticmethod
def on_document_get(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
filename: str, # Name of the file
mime: str, # Mime type of the file
filesize: int, # Size of the file
) -> bool:
''' Event when a document is requested.
The result indicates if the download of the document is allowed.
'''
return True
@staticmethod
def on_download_pre(url: str, # Target URL
request: Request, # Changeable request
) -> None:
''' Event when the server downloads a file on behalf of the user.
You can populate the session cookies to bypass an authentication wall for example.
'''
# request.add_header('Cookie', '...')
@staticmethod
def on_html(sql: sqlite3.Cursor, # Cursor to query the database
project: str, # Name of the project
page: Optional[str], # Name of the page
revision: int, # Revision of the page
html: str, # Current converted Markdown to HTML code
) -> str:
''' Event when a page is converted to HTML and cached, or previewed during its edition.
The result is the converted HTML code.
Warning: the conversion to HTML is used in the export to OpenDocument (odt). Changing
the HTML inappropriately may result in a technical failure of this feature.
'''
return html
@staticmethod
def on_html_description(sql: sqlite3.Cursor, # Cursor to query the database
project: str, # Name of the project
user: Optional[str], # Name of the user
page: str, # Name of the page
revision: int, # Revision of the page
) -> str:
''' Event to determine the description of the page in the HTML field "meta description".
The impact on SEO is limited but this may be required by some search engines.
The result is the calculated description.
'''
# Read the first paragraph to provide the description of the page
sql.execute(''' SELECT markdown
FROM pages
WHERE project = ?
AND page = ?
AND revision = ?''',
(project, page, revision))
lines = sql.fetchone()['markdown'][:500].split('\n')
for e in lines:
if (e[:1] not in ['', '#', '<', '>', '[', '!', '&']) and (e[:3] != '```') and (len(e) > 64):
return PwicLib.no_md(e).replace(' ', ' ').replace(' .', '.').strip()
return ''
@staticmethod
def on_html_keywords(sql: sqlite3.Cursor, # Cursor to query the database
project: str, # Name of the project
user: Optional[str], # Name of the user
page: str, # Name of the page
revision: int, # Revision of the page
) -> str:
''' Event to determine the keywords of the page in the HTML field "meta keywords".
The impact on SEO is limited but this may be required by some search engines.
The result is the calculated keywords.
'''
# Read the existing tags
sql.execute(''' SELECT tags --, markdown
FROM pages
WHERE project = ?
AND page = ?
AND revision = ?''',
(project, page, revision))
row = sql.fetchone()
if row['tags'] != '':
return row['tags'].replace(' ', ', ')
# Determine the most occurring words from the Mardown
# words = PwicLib.no_html(PwicLib.no_md(row['markdown'])).lower()
# for w in ['\n', '.', ',', ':', "'", '’']:
# words = words.replace(w, ' ')
# words = [w for w in words.split(' ') if (len(w) > 6) and ('/' not in w)]
# words = [(w, words.count(w)) for w in set(words)]
# words = [w for w in words if w[1] > 1]
# if len(words) > 0:
# words.sort(key=lambda w: w[1], reverse=True)
# words = [w[0] for w in words[:10]]
# return ', '.join(words)
# Default result
return ''
@staticmethod
def on_html_robots(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
template: str, # Name of the template
data: Dict[str, Any], # Data elements of the page
robots: Dict[str, Optional[bool]], # Status of the robots (assign None to remove the value)
) -> bool:
''' Event to calculate the meta HTML header 'robots' when a page is rendered.
The result indicates if the parameter 'robots' (that contains the result) has been changed.
'''
# Offline mode for the special pages
offline = template in ['login', 'logout', 'page-404', 'page-history', 'page-special', 'search', 'user']
if (template == 'page') and not data['latest']:
seo_hide_revs = PwicLib.option(sql, project, 'seo_hide_revs') is not None
validated_only = PwicLib.option(sql, project, 'validated_only') is not None
if seo_hide_revs and not validated_only:
offline = True
if offline:
robots['archive'] = False
robots['cache'] = False
if template in ['page-history']:
robots['follow'] = False
robots['imageindex'] = False
robots['index'] = False
robots['snippet'] = False
return True
@staticmethod
def on_http_headers(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
headers: MultiDict, # Output HTTP headers
project: str, # Name of the project
template: Optional[str], # Layout of the page. None denotes a file download
) -> None:
''' Event when a page or a document is delivered, excluding the API and the static files.
To change the HTTP headers, modify the parameter 'headers' without reallocating it.
'''
if template == 'login':
headers['X-Frame-Options'] = 'deny'
@staticmethod
def on_ip_check(ip: str, # Remote IP address
authorized: bool, # Current status of the authorization
) -> bool:
''' Event when the IP address of the user is checked.
The result indicates if the IP address is authorized.
'''
return authorized
@staticmethod
def on_ip_header(request: Optional[web.Request], # HTTP request
) -> str:
''' Event when the remote IP address must be retrieved from the HTTP headers.
With internal proxies, you should not rely on the remote address of the TCP connection only.
The single result is the IP fetched according to your logic.
'''
if request is None:
return ''
# return str(request.headers.get('X-Forwarded-For', request.remote)) # Enable this line if you use a reverse proxy
return str(request.remote)
@staticmethod
def on_language_detected(request: web.Request, # HTTP request
language: str, # Current language
available_langs: List[str], # All the available languages
sso: bool, # True from the federated authentication
) -> str:
''' Event when a default language is suggested.
The result gives the new default language that must belong to the authorized languages.
'''
return language
@staticmethod
def on_login(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
user: str, # Name of the user
language: str, # Selected language
ip: str, # IP address
) -> bool:
''' Event when a user successfully connects with a password and 2FA TOTP.
The result indicates if the connection is possible.
Note: the custom check for OAuth is covered by "PwicExtension.on_oauth".
'''
return True
@staticmethod
def on_markdown_pre(sql: sqlite3.Cursor, # Cursor to query the database
project: str, # Name of the project
page: Optional[str], # Name of the page
revision: int, # Revision of the page
markdown: str, # Current Markdown
) -> str:
''' Event when a Markdown text is selected prior to conversion.
The result is the new Markdown text to be processed.
The control hash key is not affected.
'''
return markdown
@staticmethod
def on_oauth(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
emails: List[str], # Array of candidate email addresses (changeable)
) -> None:
''' Event when email addresses are fetched from the remote OAuth server.
Modify the parameter 'emails' without reallocating it.
'''
@staticmethod
def on_odata_xml_definition(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
filename: str, # Name of the static XML configuration file
content: str, # Content of the file
):
''' Event to change the XML definition of the OData service.
The result is the content of the new file.
'''
return content
@staticmethod
def on_odata_content_pre(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
user: str, # Name of the user
) -> bool:
''' Event when a user calls a content through the OData interface.
The result indicates if the operation is allowed.
'''
return True
@staticmethod
def on_odata_custom_content(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
user: str, # Name of the user
table: str, # Name of the custom table
) -> bool:
''' Event to process the data selection of one custom table.
The result indicates if some data were retrieved within the SQL cursor.
'''
return False
@staticmethod
def on_odata_content(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
user: str, # Name of the user
data: Dict[str, Any], # Content to be delivered
) -> None:
''' Event when an OData object is returned to the connected user.
Changing the structure of the OData object in parameter is not allowed by the OData design.
'''
@staticmethod
def on_project_export_documents(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
documents: List[Dict[str, Any]], # List of the documents to be exported (changeable)
) -> None:
''' Event when a list of documents is to be exported as an archive for a given project.
Modify the parameter 'documents' without reallocating it.
'''
@staticmethod
def on_related_pages(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
page: str, # Name of the page
relations: List[Tuple[str, str]], # Related pages
) -> None:
''' Event to determine the related pages of a page.
Modify the parameter 'relations' without reallocating it.
A related link is a tuple made of the URL and its description.
The URL should respect the formats "/project/page" or "http://your-site.tld/project/page".
'''
@staticmethod
def on_render_post(app: web.Application, # Access to the application (do not change)
sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
pwic: Dict[str, Any], # Rendered content (not changeable)
html: str, # Current output to HTML
) -> str:
''' Event after a page is rendered to HTML.
The result returns the new output.
Beware to be as efficient as possible in your custom logic.
Raise an exception web.HTTP* to cancel the rendering.
'''
return html
@staticmethod
def on_render_pre(app: web.Application, # Access to the application (do not change)
sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
pwic: Dict[str, Any], # Content to be rendered (changeable)
) -> None:
''' Event when a page is about to be rendered.
The variable 'pwic' contains all the calculated data and you can interact with it.
Raise an exception web.HTTP* to cancel the rendering.
'''
@staticmethod
def on_search_documents(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
user: str, # Name of the user
pwic: Dict[str, Any], # Work area
query: Dict[str, List[str]], # Search terms
) -> bool:
''' Event to delegate the search of the documents.
Populate pwic['documents'] to return the found documents.
The result indicates if you implemented a custom logic.
'''
return False
@staticmethod
def on_search_pages(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
user: str, # Name of the user
pwic: Dict[str, Any], # Work area
query: Dict[str, List[str]], # Search terms
) -> bool:
''' Event to delegate the search of the pages.
Populate pwic['pages'] to return the found pages, without reallocating it.
The result indicates if you implemented a custom logic.
'''
return False
@staticmethod
def on_search_terms(sql: sqlite3.Cursor, # Cursor to query the database
request: web.Request, # HTTP request
project: str, # Name of the project
user: str, # Name of the user
query: Optional[Dict[str, List[str]]], # Not-null parsed search terms (changeable)
with_rev: bool, # Search in the old revisions too?
) -> None:
''' Event when a search is launched by a user.
The variable 'query' is the changeable result.
'''
@staticmethod
def on_server_ready(app: web.Application, # Full access to the application (changeable)
sql: sqlite3.Cursor, # Cursor to query the database
) -> bool:
''' Event when the server is ready to start.
The result indicates if the server can start.
'''
return True
@staticmethod
def on_timezone() -> Optional[tzinfo]:
''' Event when the current date is determined.
The result is the timezone to be used for the determination.
The local date is used by default.
'''
# from pytz import utc # UTC+0
# return utc
return None # Local time
# ===============
# Custom routes
# ===============
@staticmethod
def load_custom_routes(server, # PwicServer
) -> List[web.RouteDef]:
# return [web.static('/.well-known/acme-challenge/', '/path/to/acme/challenge/'),
# web.get('/special/sample', PwicExtension.on_page_special_sample),
# web.get('/robots.txt', PwicExtension.on_page_static_robots)]
return []
# @staticmethod
# async def on_page_special_sample(request: web.Request) -> web.Response:
# return web.Response(text='Hello world!', content_type=PwicLib.mime('html'))
# @staticmethod
# async def on_page_static_robots(request: web.Request) -> web.Response:
# return web.FileResponse('./static/robots.txt') # sof/34121814