1
2
3
4 """
5 The core that allows content to be submitted to the site. Modules and
6 scripts may programmatically submit nodes using the usual form API pattern.
7
8 @package user
9 @see <a href='http://drupy.net'>Drupy Homepage</a>
10 @see <a href='http://drupal.org'>Drupal Homepage</a>
11 @note Drupy is a port of the Drupal project.
12 @note This file was ported from Drupal's modules/node/node.module
13 @author Brendon Crawford
14 @copyright 2008 Brendon Crawford
15 @contact message144 at users dot sourceforge dot net
16 @created 2008-01-10
17 @version 0.1
18 @note License:
19
20 This program is free software; you can redistribute it and/or
21 modify it under the terms of the GNU General Public License
22 as published by the Free Software Foundation; either version 2
23 of the License, or (at your option) any later version.
24
25 This program is distributed in the hope that it will be useful,
26 but WITHOUT ANY WARRANTY; without even the implied warranty of
27 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 GNU General Public License for more details.
29
30 You should have received a copy of the GNU General Public License
31 along with this program; if not, write to:
32
33 The Free Software Foundation, Inc.,
34 51 Franklin Street, Fifth Floor,
35 Boston, MA 02110-1301,
36 USA
37 """
38
39 __version__ = "$Revision: 1 $"
40
41 from lib.drupy import DrupyPHP as php
42 from lib.drupy import DrupyImport
43 from includes import common as lib_common
44 from includes import path as lib_path
45 from includes import database as lib_database
46 from includes import bootstrap as lib_bootstrap
47 from includes import plugin as lib_plugin
48
49
50
51
52
53
54
55
56
57 NODE_NEW_LIMIT = php.time_() - 30 * 24 * 60 * 60
58
59
60
61
62 NODE_BUILD_NORMAL = 0
63
64
65
66
67 NODE_BUILD_PREVIEW = 1
68
69
70
71
72 NODE_BUILD_SEARCH_INDEX = 2
73
74
75
76
77 NODE_BUILD_SEARCH_RESULT = 3
78
79
80
81
82 NODE_BUILD_RSS = 4
83
84
85
86
87 NODE_BUILD_PRINT = 5
88
90 """
91 Implementation of hook_help().
92 """
93
94
95
96 if (path != 'admin/content/node-settings/rebuild' and \
97 path != 'batch' and php.strpos(path, '#') == False
98 and lib_plugin.plugins['user'].access(\
99 'access administration pages') and access_needs_rebuild()):
100 if (path == 'admin/content/node-settings'):
101 message = lib_common.t(\
102 'The content access permissions need to be rebuilt.')
103 else:
104 message = lib_common.t('The content access permissions need to ' + \
105 'be rebuilt. Please visit ' + \
106 '<a href="@node_access_rebuild">this page</a>.', \
107 {'@node_access_rebuild' : \
108 lib_common.url('admin/content/node-settings/rebuild')})
109 lib_bootstrap.drupal_set_message(message, 'error')
110 if path == 'admin/help#node':
111 output = '<p>' + t('The node module manages content on your ' + \
112 'site, and stores all posts (regardless of type) as a ' + \
113 '"node". In addition to basic publishing settings, including ' + \
114 'whether the post has been published, promoted to the site ' + \
115 'front page, or should remain present (or sticky) at the top ' + \
116 'of lists, the node module also records basic information about ' + \
117 'the author of a post. Optional revision control over edits is ' + \
118 'available. For additional functionality, the node module is ' + \
119 'often extended by other modules.') + '</p>'
120 output += '<p>' + t('Though each post on your site is a node, each ' + \
121 'post is also of a particular ' + \
122 '<a href="@content-type">content type</a>. ' + \
123 '<a href="@content-type">Content types</a> are used to define the ' + \
124 'characteristics of a post, including the title and description ' + \
125 'of the fields displayed on its add and edit pages. Each ' + \
126 'content type may have different default settings for ' + \
127 '<em>Publishing options</em> and other workflow controls. ' + \
128 'By default, the two content types in a standard Drupal ' + \
129 'installation are <em>Page</em> and <em>Story</em>. Use the ' + \
130 '<a href="@content-type">content types page</a> to add new or ' + \
131 'edit existing content types. Additional content types also ' + \
132 'become available as you enable additional core, contributed and ' + \
133 'custom modules.', {'@content-type' : \
134 lib_common.url('admin/build/types')}) + '</p>'
135 output += '<p>' + lib_common.t('The administrative ' + \
136 '<a href="@content">content page</a> allows you to review and ' + \
137 'manage your site content. The ' + \
138 '<a href="@post-settings">post settings page</a> sets certain ' + \
139 'options for the display of posts. The node module makes a ' + \
140 'number of permissions available for each content type, which ' + \
141 'may be set by role on the ' + \
142 '<a href="@permissions">permissions page</a>.', \
143 {'@content' : lib_common.url('admin/content/node'), \
144 '@post-settings' : lib_common.url('admin/content/node-settings'), \
145 '@permissions' : lib_common.url('admin/user/permissions')}) + '</p>'
146 output += '<p>' + t('For more information, see the online ' + \
147 'handbook entry for <a href="@node">Node module</a>.', \
148 {'@node' : 'http://drupal.org/handbook/modules/node/'}) + '</p>'
149 return output
150 elif path == 'admin/content/node':
151
152 return ' '
153 elif path == 'admin/build/types':
154 return '<p>' + t('Below is a list of all the content types on your ' + \
155 'site. All posts that exist on your site are instances of one ' + \
156 'of these content types.') + '</p>'
157 elif path == 'admin/build/types/add':
158 return '<p>' + t('To create a new content type, enter the ' + \
159 'human-readable name, the machine-readable name, and all other ' + \
160 'relevant fields that are on this page. Once created, users ' + \
161 'of your site will be able to create posts that are ' + \
162 'instances of this content type.') + '</p>'
163 elif path == 'node/%/revisions':
164 return '<p>' + lib_common.t('The revisions let you track differences ' + \
165 'between multiple versions of a post.') + '</p>'
166 elif path == 'node/%/edit':
167 node = load(arg[1])
168 type_ = get_types('type', node.type_)
169 return (('<p>' + filter_xss_admin(type.help) + '</p>') if \
170 (not php.empty(type_.help)) else '')
171 if (arg[0] == 'node' and arg[1] == 'add' and arg[2]):
172 type_ = get_types('type', php.str_replace('-', '_', arg[2]))
173 return (('<p>' + filter_xss_admin(type_.help) + '</p>') if \
174 (not empty(type_.help)) else '')
175
176
177
178
180 """
181 Implementation of hook_theme().
182 """
183 return {
184 'node' : {
185 'arguments' : {'node' : None, 'teaser' : False, 'page' : False},
186 'template' : 'node'
187 },
188 'node_list' : {
189 'arguments' : {'items' : None, 'title' : None}
190 },
191 'node_search_admin' : {
192 'arguments' : {'form' : None}
193 },
194 'node_filter_form' : {
195 'arguments' : {'form' : None},
196 'file' : 'node.admin.inc'
197 },
198 'node_filters' : {
199 'arguments' : {'form' : None},
200 'file' : 'node.admin.inc'
201 },
202 'node_admin_nodes' : {
203 'arguments' : {'form' : None},
204 'file' : 'node.admin.inc'
205 },
206 'node_add_list' : {
207 'arguments' : {'content' : None},
208 'file' : 'node.pages.inc'
209 },
210 'node_form' : {
211 'arguments' : {'form' : None},
212 'file' : 'node.pages.inc'
213 },
214 'node_preview' : {
215 'arguments' : {'node' : None},
216 'file' : 'node.pages.inc'
217 },
218 'node_log_message' : {
219 'arguments' : {'log' : None}
220 },
221 'node_submitted' : {
222 'arguments' : {'node' : None}
223 },
224 }
225
226
227
228
230 """
231 Implementation of hook_cron().
232 """
233 lib_database.query('DELETE FROM {history} WHERE timestamp < %d', \
234 NODE_NEW_LIMIT)
235
236
238 """
239 Gather a listing of links to nodes.
240
241 @param result
242 A DB result object from a query to fetch node objects. If your query
243 joins the <code>node_comment_statistics</code> table so that the
244 <code>comment_count</code> field is available, a title attribute will
245 be added to show the number of comments.
246 @param title
247 A heading for the resulting list.
248
249 @return
250 An HTML list suitable as content for a block, or False if no result can
251 fetch from DB result object.
252 """
253 items = []
254 num_rows = False
255 while True:
256 node = db_fetch_object(result)
257 if not node:
258 break
259 items.append(l((({'attributes' : {'title' : format_plural(\
260 node.comment_count, '1 comment', '@count comments')}}) if \
261 (node.title, 'node/' + node.nid, \
262 not php.empty(node.comment_count)) else {})))
263 num_rows = True
264 return (lib_theme.theme('node_list', items, title) if num_rows else False)
265
266
267
269 """
270 Format a listing of links to nodes.
271
272 @ingroup themeable
273 """
274 return lib_theme.theme('item_list', items, title)
275
276
278 """
279 Update the 'last viewed' timestamp of the specified node for current user.
280 """
281 if (lib_appglobals.user.uid):
282 if (last_viewed(nid)):
283 lib_database.query('UPDATE {history} SET timestamp = %d ' + \
284 'WHERE uid = %d AND nid = %d', php.time_(), \
285 lib_appglobals.user.uid, nid)
286 else:
287 lib_database.query('INSERT INTO {history} (uid, nid, timestamp) ' + \
288 'VALUES (%d, %d, %d)', lib_appglobals.user.uid, nid, php.time_())
289
290
291
293 """
294 Retrieves the timestamp at which the current user last viewed the
295 specified node.
296 """
297 php.static(last_view, 'history', {})
298 if (not php.isset(last_view.history, 'nid')):
299 last_view.history[nid] = lib_database.fetch_object(lib_database.query(\
300 "SELECT timestamp FROM {history} " + \
301 "WHERE uid = %d AND nid = %d", lib_appglobals.user.uid, nid))
302 return (last_view.history[nid].timestamp if \
303 php.isset(last_view.history[nid], 'timestamp') else 0)
304
305
306
307 -def mark(nid, timestamp):
328
329
331 """
332 See if the user used JS to submit a teaser.
333 """
334 php.Reference.check(form)
335 php.Reference.check(form_state)
336 if (php.isset(form['#post'], 'teaser_js')):
337
338 if (php.trim(form_state['values']['teaser_js'])):
339
340 body = php.trim(form_state['values']['teaser_js']) + \
341 "\r\n<not --break-.\r\n" + php.trim(form_state['values']['body'])
342 else:
343
344 body = '<not --break-.' + form_state['values']['body']
345
346 form_set_value(form['body'], body, form_state)
347
348
349 form['body']['#value'] = body
350 return form
351
352
353
355 """
356 Ensure value of "teaser_include" checkbox is consistent with other form data.
357
358 This handles two situations in which an unchecked checkbox is rejected:
359
360 1. The user defines a teaser (summary) but it is empty
361 2. The user does not define a teaser (summary) (in this case an
362 unchecked checkbox would cause the body to be empty, or missing
363 the auto-generated teaser).
364
365 If JavaScript is active then it is used to force the checkbox to be
366 checked when hidden, and so the second case will not arise.
367
368 In either case a warning message is output.
369 """
370 php.Reference.check(form)
371 php.Reference.check(form_state)
372 message = ''
373
374 if (php.isset(form['#post']['body']) and \
375 php.isset(form_state['values'], 'teaser_include') and \
376 not form_state['values']['teaser_include']):
377
378 if (php.strpos(form_state['values']['body'], '<!--break-.') == 0):
379
380 message = lib_common.t(\
381 'You specified that the summary should not be shown when this ' + \
382 'post is displayed in full view. This setting is ignored ' + \
383 'when the summary is empty.')
384 elif (php.strpos(form_state['values']['body'], '<!--break-.') == False):
385
386 message = lib_common.t('You specified that the summary should ' + \
387 'not be shown when this post is displayed in full view. ' + \
388 'This setting has been ignored since you have not defined a ' + \
389 'summary for the post. (To define a summary, insert the ' + \
390 'delimiter "<not --break-->" (without the quotes) in ' + \
391 'the Body of the post to indicate the end of the summary and ' + \
392 'the start of the main content.)')
393 if (not php.empty(message)):
394 drupal_set_message(message, 'warning')
395
396 form_set_value(form['teaser_include'], 1, form_state)
397
398
399 form['teaser_include']['#value'] = 1
400 return form
401
402
403
404 -def teaser(body, format = None, size = None):
405 """
406 Generate a teaser for a node body.
407
408 If the end of the teaser is not indicated using the <!--break-. delimiter
409 then we generate the teaser automatically, trying to end it at a sensible
410 place such as the end of a paragraph, a line break, or the end of a
411 sentence (in that order of preference).
412
413 @param body
414 The content for which a teaser will be generated.
415 @param format
416 The format of the content. If the content contains PHP code, we do not
417 split it up to prevent parse errors. If the line break filter is present
418 then we treat newlines embedded in body as line breaks.
419 @param size
420 The desired character length of the teaser. If omitted, the default
421 value will be used. Ignored if the special delimiter is present
422 in body.
423 @return
424 The generated teaser.
425 """
426 if (size is None):
427 size = lib_bootstrap.variable_get('teaser_length', 600)
428
429 delimiter = php.strpos(body, '<!--break-.')
430
431
432 if (size == 0 and delimiter == False):
433 return body
434
435 if (delimiter is not False):
436 return php.substr(body, 0, delimiter)
437
438
439
440 if (format is not None):
441 filters = filter_list_format(format)
442 if (php.isset(filters['php/0']) and php.strpos(body, '<?') is not False):
443 return body
444
445 if (drupal_strlen(body) <= size):
446 return body
447
448
449
450 teaser = truncate_utf8(body, size)
451
452
453 max_rpos = php.strlen(teaser)
454
455
456
457 min_rpos = max_rpos
458
459
460 reversed = php.strrev(teaser)
461
462 break_points = array()
463
464 break_points.append( {'</p>' : 0} )
465
466 line_breaks = {'<br />' : 6, '<br>' : 4}
467
468
469 if (php.isset(filters['filter/1'])):
470 line_breaks["\n"] = 1
471 break_points.append( line_breaks )
472
473
474
475 break_points.append( {'. ' : 1, '! ' : 1, '? ' : 1} )
476
477 for points in break_points:
478
479 for point,offset in points.items():
480
481 rpos = php.strpos(reversed, php.strrev(point))
482 if (rpos is not False):
483 min_rpos = php.min(rpos + offset, min_rpos)
484
485 if (min_rpos != max_rpos):
486
487 return (teaser if (min_rpos == 0) else \
488 php.substr(teaser, 0, 0 - min_rpos))
489
490 return teaser
491
492
493
494
495 -def get_types(op = 'types', node = None, reset = False):
496 """
497 Builds a list of available node types, and returns all of part of this list
498 in the specified format.
499
500 @param op
501 The format in which to return the list. When this is set to 'type',
502 'module', or 'name', only the specified node type is
503 returned. When set to
504 'types' or 'names', all node types are returned.
505 @param node
506 A node object, array, or string that indicates the node type to return.
507 Leave at default value (None) to return a list of all node types.
508 @param reset
509 Whether or not to reset this function's internal cache (defaults to
510 False).
511
512 @return
513 Either an array of all available node types, or a single node type, in a
514 variable format. Returns False if the node type is not found.
515 """
516 php.static(get_types, '_node_types', {})
517 php.static(get_types, '_get_types', {})
518 if (reset or php.empty(get_types._node_types)):
519 get_types._node_types, get_types._node_names = _types_build()
520 if (node):
521 if (php.is_array(node)):
522 type_ = node['type']
523 elif (php.is_object(node)):
524 type_ = node.type_
525 elif (php.is_string(node)):
526 type_ = node
527 if (not php.isset(get_types._node_types, 'type_')):
528 return False
529 if op == 'types':
530 return _node_types
531 elif op =='type':
532 return (_node_types[type] if \
533 php.isset(get_types._node_types, type_) else False)
534 elif op == 'module':
535 return (get_types._node_types[type_].plugin if \
536 php.isset(get_types._node_types[type_], 'plugin') else False)
537 elif op == 'names':
538 return get_types._node_names
539 elif op =='name':
540 return (get_types._node_names[type_] if \
541 php.isset(_node_names, type_) else False)
542
543
544
546 """
547 Resets the database cache of node types, and saves all new or non-modified
548 module-defined node types to the database.
549 """
550 _types_build()
551 node_types = get_types('types', None, True)
552 for type_,info in node_types.items():
553 if (not empty(info.is_new)):
554 type_save(info)
555 if (not empty(info.disabled)):
556 type_delete(info.type_)
557 _types_build()
558
559
560
562 """
563 Saves a node type to the database.
564
565 @param info
566 The node type to save, as an object.
567
568 @return
569 Status flag indicating outcome of the operation.
570 """
571 is_existing = False
572 existing_type = (info.old_type if \
573 not php.empty(info.old_type) else info.type)
574 is_existing = lib_database.result(lib_database.query(\
575 "SELECT COUNT(*) FROM {node_type} WHERE type = '%s'", existing_type))
576 if (not php.isset(info, 'help')):
577 info.help = ''
578 if (not isset(info, 'min_word_count')):
579 info.min_word_count = 0
580 if (not isset(info, 'body_label')):
581 info.body_label = ''
582 if (is_existing):
583 lib_database.query(\
584 "UPDATE {node_type} SET type = '%s', name = '%s', " + \
585 "module = '%s', has_title = %d, title_label = '%s', " + \
586 "has_body = %d, body_label = '%s', description = '%s', " + \
587 "help = '%s', min_word_count = %d, custom = %d, modified = %d, " + \
588 "locked = %d WHERE type = '%s'", \
589 info.type, info.name, info.module, info.has_title, \
590 info.title_label, info.has_body, info.body_label, \
591 info.description, info.help, info.min_word_count, \
592 info.custom, info.modified, info.locked, existing_type)
593 lib_plugin.invoke_all('node_type', 'update', info)
594 return SAVED_UPDATED
595 else:
596 lib_database.query("INSERT INTO {node_type} (type, name, module, " + \
597 "has_title, title_label, has_body, body_label, description, " + \
598 "help, min_word_count, custom, modified, locked, orig_type) " + \
599 "VALUES ('%s', '%s', '%s', %d, '%s', %d, '%s', '%s', '%s', " + \
600 "%d, %d, %d, %d, '%s')", + \
601 info.type, info.name, info.module, info.has_title, \
602 info.title_label, info.has_body, info.body_label, \
603 info.description, info.help, info.min_word_count, \
604 info.custom, info.modified, info.locked, info.orig_type)
605 lib_plugin.invoke_all('node_type', 'insert', info)
606 return SAVED_NEW
607
608
610 """
611 Deletes a node type from the database.
612
613 @param type
614 The machine-readable name of the node type to be deleted.
615 """
616 info = get_types('type', type_)
617 lib_database.query("DELETE FROM {node_type} WHERE type = '%s'", type_)
618 lib_plugin.invoke_all('node_type', 'delete', info)
619
620
621
623 """
624 Updates all nodes of one type to be of another type.
625
626 @param old_type
627 The current node type of the nodes.
628 @param type
629 The new node type of the nodes.
630
631 @return
632 The number of nodes whose node type field was modified.
633 """
634 lib_database.query("UPDATE {node} SET type = '%s' WHERE type = '%s'", \
635 type_, old_type)
636 return lib_database.affected_rows()
637
638
639
641 """
642 Builds and returns the list of available node types.
643
644 The list of types is built by querying hook_node_info() in all modules, and
645 by comparing this information with the node types in the {node_type} table.
646 """
647 _node_types = []
648 _node_names = []
649 info_array = lib_plugin.invoke_all('node_info')
650 for type_,info in info_array.items():
651 info['type'] = type_
652 _node_types[type_] = php.object_(_type_set_defaults(info))
653 _node_names[type_] = info['name']
654 type_result = lib_database.query(lib_database.rewrite_sql('\
655 SELECT nt.type, nt.* FROM {node_type} nt ORDER BY nt.type ASC', \
656 'nt', 'type'))
657 while True:
658 type_object = lib_database.fetch_object(type_result)
659 if type_object:
660 break
661
662
663
664
665
666 if (type_object.plugin != 'node' and \
667 php.empty(info_array[type_object.type_])):
668 type_object.disabled = True
669 if (not php.isset(_node_types, type_object.type_) or type_object.modified):
670 _node_types[type_object.type_] = type_object
671 _node_names[type_object.type_] = type_object.name
672 if (type_object.type_ != type_object.orig_type):
673 del(_node_types[type_object.orig_type])
674 del(_node_names[type_object.orig_type])
675 php.asort(_node_names)
676 return (_node_types, _node_names)
677
678
679
681 """
682 Set default values for a node type defined through hook_node_info().
683 """
684 if (not php.isset(info['has_title'])):
685 info['has_title'] = True
686 if (info['has_title'] and not php.isset(info, 'title_label')):
687 info['title_label'] = t('Title')
688 if (not isset(info, 'has_body')):
689 info['has_body'] = True
690 if (info['has_body'] and not php.isset(info, 'body_label')):
691 info['body_label'] = lib_common.t('Body')
692 if (not php.isset(info, 'help')):
693 info['help'] = ''
694 if (not php.isset(info, 'min_word_count')):
695 info['min_word_count'] = 0
696 if (not php.isset(info, 'custom')):
697 info['custom'] = False
698 if (not php.isset(info, 'modified')):
699 info['modified'] = False
700 if (not php.isset(info, 'locked')):
701 info['locked'] = True
702 info['orig_type'] = info['type']
703 info['is_new'] = True
704 return info
705
706
707
708 -def hook(node, hook_):
709 """
710 Determine whether a node hook exists.
711
712 @param &node
713 Either a node object, node array, or a string containing the node type.
714 @param hook
715 A string containing the name of the hook.
716 @return
717 True iff the hook exists in the node type of node.
718 """
719 plugin = get_types('module', node)
720 if (module == 'node'):
721
722 lib_plugin = 'node_content'
723 return lib_plugin.hook(plugin, hook_)
724
725
726
727 -def invoke(node, hook_, a2 = None, a3 = None, a4 = None):
728 """
729 Invoke a node hook.
730
731 @param &node
732 Either a node object, node array, or a string containing the node type.
733 @param hook
734 A string containing the name of the hook.
735 @param a2, a3, a4
736 Arguments to pass on to the hook, after the node argument.
737 @return
738 The returned value of the invoked hook.
739 """
740 if (hook(node, hook_)):
741 plugin = get_types('plugin', node)
742 if (plugin == 'node'):
743
744 plugin = 'content';
745 function = plugin + '_' + hook
746 return php.call_user_func(function, node, a2, a3, a4)
747
748
749
772
773
774 """
775 Load a node object from the database.
776
777 @param param
778 Either the nid of the node or an array of conditions to match against in
779 the database query
780 @param revision
781 Which numbered revision to load. Defaults to the current version.
782 @param reset
783 Whether to reset the internal node_load cache.
784
785 @return
786 A fully-populated node object.
787 """
788 -def load(param = [], revision = None, reset = None):
789 php.static(load, 'nodes', [])
790 if (reset):
791 load.nodes = []
792 cachable = (revision == None)
793 arguments = []
794 if (php.is_numeric(param)):
795 if (cachable):
796
797 if (php.isset(load.nodes[param])):
798 return (php.clone(nodes[param]) if \
799 php.is_object(load.nodes[param]) else nodes[param])
800 cond = 'n.nid = %d'
801 arguments.append( param )
802 elif (php.is_array(param)):
803
804 for key,value in param.items():
805 cond.append( 'n.' + lib_database.escape_table(key) + " = '%s'" )
806 arguments.append( value )
807 cond = implode(' AND ', cond)
808 else:
809 return False
810
811 fields = drupal_schema_fields_sql('node', 'n')
812 fields = php.array_merge(fields, \
813 drupal_schema_fields_sql('node_revisions', 'r'))
814 fields = php.array_merge(fields, ('u.name', 'u.picture', 'u.data'))
815
816
817
818 fields = php.array_diff(fields, ('n.vid', 'n.title', 'r.nid'))
819 fields = php.implode(', ', fields)
820
821 fields = php.str_replace('r.timestamp', \
822 'r.timestamp AS revision_timestamp', fields)
823
824 fields = php.str_replace('r.uid', 'r.uid AS revision_uid', fields)
825
826
827 if (revision):
828 php.array_unshift(arguments, revision)
829 node = lib_database.fetch_object(lib_database.query(\
830 'SELECT ' + fields + ' FROM {node} n ' + \
831 'INNER JOIN {users} u ON u.uid = n.uid ' + \
832 'INNER JOIN {node_revisions} r ON r.nid = n.nid AND r.vid = %d ' + \
833 'WHERE ' + cond, arguments))
834 else:
835 node = lib_database.fetch_object(lib_database.query(\
836 'SELECT ' + fields + ' FROM {node} n ' + \
837 'INNER JOIN {users} u ON u.uid = n.uid ' + \
838 'INNER JOIN {node_revisions} r ON r.vid = n.vid WHERE ' + \
839 cond, arguments))
840 if (node and node.nid):
841
842
843 extra = invoke(node, 'load')
844 if extra:
845 for key,value in extra.items():
846 setattr(node, key, value)
847 extra = invoke_nodeapi(node, 'load')
848 if extra:
849 for key,value in extra.items():
850 setattr(node, key, value)
851 if (cachable):
852 nodes[node.nid] = (php.clone(node) if php.is_object(node) else node)
853 return node
854
855
856
858 """
859 Perform validation checks on the given node.
860 """
861
862 node = php.object_(node)
863 type_ = get_types('type', node)
864
865
866
867 if (not php.empty(type_.min_word_count) and php.isset(node, 'body') and \
868 php.count(php.explode(' ', node.body)) < type_.min_word_count):
869 lib_form.set_error('body', lib_common.t(\
870 'The body of your @type is too short. You need at least ' +
871 '%words words.', \
872 {'%words' : type.min_word_count, '@type' : type.name}))
873 if (php.isset(node, 'nid') and (last_changed(node.nid) > node.changed)):
874 lib_form.set_error('changed', t(\
875 'This content has been modified by another user, ' + \
876 'changes cannot be saved.'))
877 if (lib_plugin.plugins['user'].access('administer nodes')):
878
879 if (not php.empty(node.name)):
880 account = user_load({'name' : node.name})
881 if not account:
882
883
884
885 lib_form.set_error('name',\
886 lib_common.t('The username %name does not exist.', \
887 {'%name' : node.name}))
888
889
890 if (not php.empty(node.date) and php.strtotime(node.date) <= 0):
891 lib_form.set_error('date', lib_common.t(\
892 'You have to specify a valid date.'))
893
894 invoke(node, 'validate', form)
895 invoke_nodeapi(node, 'validate', form)
896
897
898
899
932
933
934
936 """
937 Save a node object into the database.
938 """
939 php.Reference.check(node)
940
941 invoke_nodeapi(node, 'presave')
942 node.is_new = False
943
944 if (php.empty(node.nid)):
945
946 node.is_new = True
947
948
949
950
951
952 if (not php.isset(node, 'log')):
953 node.log = ''
954
955
956
957 if (not php.isset(node, 'teaser')):
958 node.teaser = ''
959 if (not php.isset(node, 'body')):
960 node.body = ''
961 elif (not php.empty(node.revision)):
962 node.old_vid = node.vid
963 else:
964
965
966 if (php.empty(node.log)):
967 del(node.log)
968
969 if (php.empty(node.created)):
970 node.created = php.time_()
971
972
973 node.changed = php.time_()
974 node.timestamp = php.time_()
975 node.format = (node.format if php.isset(node.format) else \
976 FILTER_FORMAT_DEFAULT)
977 update_node = True
978
979 if (node.is_new):
980 drupal_write_record('node', node)
981 _save_revision(node, lib_appglobals.user.uid)
982 op = 'insert'
983 else:
984 drupal_write_record('node', node, 'nid')
985 if (not empty(node.revision)):
986 _save_revision(node, lib_appglobals.user.uid)
987 else:
988 _save_revision(node, lib_appglobals.user.uid, 'vid')
989 update_node = False
990 op = 'update'
991 if (update_node):
992 lib_database.query(\
993 'UPDATE {node} SET vid = %d WHERE nid = %d', node.vid, node.nid)
994
995
996
997 invoke(node, op)
998 invoke_nodeapi(node, op)
999
1000 access_acquire_grants(node)
1001
1002 lib_cache.clear_all()
1003
1004
1005
1007 """
1008 Helper function to save a revision with the uid of the current user.
1009
1010 Node is taken by reference, becuse drupal_write_record() updates the
1011 node with the revision id, and we need to pass that back to the caller.
1012 """
1013 php.Reference.check(node)
1014 temp_uid = node.uid
1015 node.uid = uid
1016 if (update is not None):
1017 drupal_write_record('node_revisions', node, update)
1018 else:
1019 drupal_write_record('node_revisions', node)
1020 node.uid = temp_uid
1021
1022
1024 """
1025 Delete a node.
1026 """
1027 node = load(nid)
1028 if (access('delete', node)):
1029 lib_database.query('DELETE FROM {node} WHERE nid = %d', node.nid)
1030 lib_database.query('DELETE FROM {node_revisions} WHERE nid = %d', node.nid)
1031
1032 invoke(node, 'delete')
1033 invoke_nodeapi(node, 'delete')
1034
1035 lib_cache.clear_all()
1036
1037
1038
1039
1040 if (lib_plugin.exists('search')):
1041 lib_plugin.plugins['search'].wipe(node.nid, 'node')
1042 watchdog('content', '@type: deleted %title.', {'@type' : node.type, \
1043 '%title' : node.title})
1044 drupal_set_message(lib_common.t('@type %title has been deleted.', \
1045 {'@type' : node_get_types('name', node), '%title' : node.title}))
1046
1047
1048 -def view(node, teaser = False, page = False, links = True):
1049 """
1050 Generate a display of the given node.
1051
1052 @param node
1053 A node array or node object.
1054 @param teaser
1055 Whether to display the teaser only or the full form.
1056 @param page
1057 Whether the node is being displayed by itself as a page.
1058 @param links
1059 Whether or not to display node links. Links are omitted for node previews.
1060
1061 @return
1062 An HTML representation of the themed node.
1063 """
1064 node = php.object_(node)
1065 node = build_content(node, teaser, page)
1066 if (links is not None):
1067 node.links = lib_plugin.invoke_all('link', 'node', node, teaser)
1068 drupal_alter('link', node.links, node)
1069
1070
1071 content = drupal_render(node.content)
1072 if (teaser):
1073 node.teaser = content
1074 del(node.body)
1075 else:
1076 node.body = content
1077 del(node.teaser)
1078
1079 invoke_nodeapi(node, 'alter', teaser, page)
1080 return lib_theme.theme('node', node, teaser, page)
1081
1082
1083
1084 -def prepare(node, teaser = False):
1101
1102
1103
1104 -def build_content(node, teaser = False, page = False):
1105 """
1106 Builds a structured array representing the node's content.
1107
1108 @param node
1109 A node object.
1110 @param teaser
1111 Whether to display the teaser only, as on the main page.
1112 @param page
1113 Whether the node is being displayed by itself as a page.
1114
1115 @return
1116 An structured array containing the individual elements
1117 of the node's body.
1118 """
1119
1120 if (not php.isset(node.build_mode)):
1121 node.build_mode = NODE_BUILD_NORMAL
1122
1123 node.body = (php.str_replace('<!--break-.', '', node.body) if \
1124 php.isset(node.body) else '')
1125
1126
1127 if (hook(node, 'view')):
1128 node = invoke(node, 'view', teaser, page)
1129 else:
1130 node = prepare(node, teaser)
1131
1132 invoke_nodeapi(node, 'view', teaser, page)
1133 return node
1134
1135
1136
1137 -def show(node, cid, message = False):
1152
1153
1154
1155 """
1156 Theme a log message.
1157
1158 @ingroup themeable
1159 """
1161 return '<div class="log"><div class="title">' + \
1162 lib_common.t('Log') + ':</div>' + log + '</div>'
1163
1164
1165
1167 """
1168 Implementation of hook_perm().
1169 """
1170 perms = {
1171 'administer content types' : \
1172 lib_common.t('Manage content types and ' + \
1173 'content type administration settings.'),
1174 'administer nodes' : \
1175 lib_common.t('Manage all website content, and ' + \
1176 'bypass any content-related access control + %warning', \
1177 {'%warning' : lib_common.t('Warning: Give to trusted ' + \
1178 'roles only; this permission has security implications.')}),
1179 'access content' : lib_common.t('View published content.'),
1180 'view revisions' : lib_common.t('View content revisions.'),
1181 'revert revisions' : \
1182 lib_common.t('Replace content with an older revision.'),
1183 'delete revisions' : lib_common.t('Delete content revisions.')
1184 }
1185 for type_ in get_types():
1186 if (type_.plugin == 'node'):
1187 perms += ist_permissions(type_)
1188 return perms
1189
1190
1191
1193 """
1194 Gather the rankings from the the hook_ranking implementations.
1195 """
1196 rankings = {
1197 'total' : 0, 'join' : [], 'score' : [], 'args' : []
1198 }
1199 ranking = lib_plugin.invoke_all('ranking')
1200 if ranking:
1201 for rank,values in ranking.items():
1202 node_rank = lib_common.variable_get('node_rank_' + rank, 0)
1203 if node_rank:
1204
1205
1206 if (php.isset(values['join']) and \
1207 not php.isset(rankings['join'], values['join'])):
1208 rankings['join'][values['join']] = values['join']
1209
1210
1211 rankings['score'].append( \
1212 '%f * COALESCE((' + values['score'] + '), 0)' )
1213
1214
1215 rankings['total'] += node_rank
1216 rakings['arguments'].append( node_rank )
1217
1218 if (php.isset(values, 'arguments')):
1219 rankings['arguments'] = php.array_merge(rankings['arguments'], \
1220 values['arguments'])
1221 return rankings
1222
1223
1224
1226 """
1227 Implementation of hook_search().
1228 """
1229 if op == 'name':
1230 return lib_common.t('Content')
1231 elif op == 'reset':
1232 lib_database.query(\
1233 "UPDATE {search_dataset} SET reindex = %d WHERE type = 'node'", \
1234 php.time_())
1235 return
1236 elif op == 'status':
1237 total = lib_database.result(lib_database.query(\
1238 'SELECT COUNT(*) FROM {node} WHERE status = 1'))
1239 remaining = lib_database.result(lib_database.query(\
1240 "SELECT COUNT(*) FROM {node} n " + \
1241 "LEFT JOIN {search_dataset} d ON d.type = 'node' AND " + \
1242 "d.sid = n.nid WHERE n.status = 1 AND " + \
1243 "d.sid IS None OR d.reindex <> 0"))
1244 return {'remaining' : remaining, 'total' : total}
1245 elif op == 'admin':
1246 form = []
1247
1248 form['content_ranking'] = {
1249 '#type' : 'fieldset',
1250 '#title' : lib_common.t('Content ranking')
1251 }
1252 form['content_ranking']['#theme'] = 'node_search_admin'
1253 form['content_ranking']['info'] = {
1254 '#value' : '<em>' + lib_common.t(\
1255 'The following numbers control which properties the ' + \
1256 'content search should favor when ordering the results. ' + \
1257 'Higher numbers mean more influence, zero means the ' + \
1258 'property is ignored. Changing these numbers does not ' + \
1259 'require the search index to be rebuilt. Changes take ' + \
1260 'effect immediately.') + '</em>'
1261 }
1262
1263 options = drupal_map_assoc(range(0, 10))
1264 for var, values in lib_plugin.invoke_all('ranking').items():
1265 form['content_ranking']['factors']['node_rank_' + var] = {
1266 '#title' : values['title'],
1267 '#type' : 'select',
1268 '#options' : options,
1269 '#default_value' : variable_get('node_rank_'. var, 0)
1270 }
1271 return form
1272 elif op == 'search':
1273
1274 join1, where1 = lib_database._rewrite_sql()
1275 arguments1 = []
1276 conditions1 = 'n.status = 1'
1277 type_ = lib_plugin.plugins['search'].query_extract(keys, 'type')
1278 if type_:
1279 types = []
1280 for t_ in explode(',', type_):
1281 types.append( "n.type = '%s'" )
1282 arguments1.append( t_ )
1283 conditions1 += ' AND (' + implode(' OR ', types) + ')'
1284 keys = lib_plugin.plugins['search'].query_insert(keys, 'type')
1285 category = lib_plugin.plugins['search'].query_extract(keys, 'category')
1286 if category:
1287 categories = []
1288 for c in explode(',', category):
1289 categories.append( "tn.tid = %d" )
1290 arguments1.append( c )
1291 conditions1 += ' AND (' + php.implode(' OR ', categories) + ')'
1292 join1 += ' INNER JOIN {term_node} tn ON n.vid = tn.vid'
1293 keys = lib_plugin.plugins['search'].query_insert(keys, 'category')
1294 languages = lib_plugin.plugins['search'].query_extract(keys, 'language')
1295 if languages:
1296 categories = []
1297 for l in explode(',', languages):
1298 categories.append( "n.language = '%s'" )
1299 arguments1.append( l )
1300 conditions1 += ' AND (' + php.implode(' OR ', categories) + ')'
1301 keys = lib_plugin.plugins['search'].query_insert(keys, 'language')
1302
1303 rankings = _rankings()
1304
1305
1306 if (rankings['total'] == 0):
1307 total = 1
1308 arguments2 = []
1309 join2 = ''
1310 select2 = 'i.relevance AS score'
1311 else:
1312 total = rankings['total']
1313 arguments2 = rankings['arguments']
1314 join2 = php.implode(' ', rankings['join'])
1315 select2 = '(' + php.implode(' + ', rankings['score']) + ') AS score'
1316
1317 find = do_search(keys, 'node', \
1318 'INNER JOIN {node} n ON n.nid = i.sid ' + join1, \
1319 conditions1 + ('' if empty(where1) else ' AND ' + where1), \
1320 arguments1, select2, join2, arguments2)
1321
1322 results = []
1323 for item in find:
1324
1325 node = load(item.sid)
1326 node.build_mode = NODE_BUILD_SEARCH_RESULT
1327 node = build_content(node, False, False)
1328 node.body = drupal_render(node.content)
1329
1330 node.body += lib_plugin.invoke('comment', 'nodeapi', node, \
1331 'update index')
1332
1333 node.body += lib_plugin.invoke('taxonomy', \
1334 'nodeapi', node, 'update index')
1335 extra = invoke_nodeapi(node, 'search result')
1336 results.append({
1337 'link' : url('node/' + item.sid, {'absolute' : True}),
1338 'type' : check_plain(get_types('name', node)),
1339 'title' : node.title,
1340 'user' : lib_theme.theme('username', node),
1341 'date' : node.changed,
1342 'node' : node,
1343 'extra' : extra,
1344 'score' : ((item.score / total) if total else 0),
1345 'snippet' : lib_plugin.plugins['search'].excerpt(keys, node.body)
1346 })
1347 return results
1348
1349
1350
1352 """
1353 Implementation of hook_ranking().
1354 """
1355
1356 ranking = {
1357 'relevance' : {
1358 'title' : lib_common.t('Keyword relevance'),
1359
1360 'score' : 'i.relevance'
1361 },
1362 'sticky' : {
1363 'title' : lib_common.t('Content is sticky at top of lists'),
1364
1365 'score' : 'n.sticky'
1366 },
1367 'promote' : {
1368 'title' : lib_common.t('Content is promoted to the front page'),
1369
1370 'score' : 'n.promote'
1371 }
1372 }
1373
1374 node_cron_last = lib_bootstrap.variable_get('node_cron_last', 0)
1375 if node_cron_last:
1376 ranking['recent'] = {
1377 'title' : t('Recently posted'),
1378
1379 'score' : '(POW(2, GREATEST(n.created, n.changed) - %d) * 6.43e-8)',
1380 'arguments' : [node_cron_last]
1381 }
1382 return ranking
1383
1384
1386 """
1387 Implementation of hook_user().
1388 """
1389 php.Reference.check(edit)
1390 php.Reference.check(user)
1391 if (op == 'delete'):
1392 lib_database.query('UPDATE {node} SET uid = 0 WHERE uid = %d', \
1393 user.uid)
1394 lib_database.query('UPDATE {node_revisions} SET uid = 0 WHERE uid = %d', \
1395 user.uid)
1396
1397
1398
1400 """
1401 Theme the content ranking part of the search settings admin page.
1402
1403 @ingroup themeable
1404 """
1405 output = drupal_render(form['info'])
1406 header = (lib_common.t('Factor'), lib_common.t('Weight'))
1407 for key in element_children(form['factors']):
1408 row = []
1409 row.append( form['factors'][key]['#title'] )
1410 del(form['factors'][key]['#title'])
1411 row.append( drupal_render(form['factors'][key]) )
1412 rows.append( row )
1413 output += lib_theme.theme('table', header, rows)
1414 output += drupal_render(form)
1415 return output
1416
1417
1418
1428
1429
1430
1431 -def hook_link(type_, node = None, teaser = False):
1432 """
1433 Implementation of hook_link().
1434 """
1435 links = []
1436 if (type == 'node'):
1437 if (teaser == 1 and node.teaser and not php.empty(node.readmore)):
1438 links['node_read_more'] = {
1439 'title' : php.t('Read more'),
1440 'href' : "node/node.nid",
1441
1442
1443 'attributes' : {'title' : php.t('Read the rest of not title.', \
1444 {'not title' : node.title})}
1445 }
1446 return links
1447
1448
1449
1478
1479
1480
1481
1483 types = get_types()
1484 for type_ in types:
1485 if (hook(type_.type_, 'form') and access('create', type_.type_)):
1486 return True
1487 return False
1488
1489
1490
1492 """
1493 Implementation of hook_menu().
1494 """
1495 items['admin/content/node'] = {
1496 'title' : 'Content',
1497 'description' : "View, edit, and delete your site's content.",
1498 'page callback' : 'drupal_get_form',
1499 'page arguments' : ('node_admin_content',),
1500 'access arguments' : ('administer nodes',)
1501 }
1502 items['admin/content/node/overview'] = {
1503 'title' : 'List',
1504 'type' : MENU_DEFAULT_LOCAL_TASK,
1505 'weight' : -10
1506 }
1507 items['admin/content/node-settings'] = {
1508 'title' : 'Post settings',
1509 'description' : 'Control posting behavior, such as teaser ' + \
1510 'length, requiring previews before posting, and the number ' + \
1511 'of posts on the front page.',
1512 'page callback' : 'drupal_get_form',
1513 'page arguments' : ('node_configure',),
1514 'access arguments' : ('administer nodes',)
1515 }
1516 items['admin/content/node-settings/rebuild'] = {
1517 'title' : 'Rebuild permissions',
1518 'page arguments' : ('node_configure_rebuild_confirm',),
1519
1520
1521 'access arguments' : ('access administration pages',),
1522 'type' : MENU_CALLBACK
1523 }
1524 items['admin/build/types'] = {
1525 'title' : 'Content types',
1526 'description' : 'Manage posts by content type, including default ' + \
1527 'status, front page promotion, etc.',
1528 'page callback' : 'node_overview_types',
1529 'access arguments' : ('administer content types',)
1530 }
1531 items['admin/build/types/list'] = {
1532 'title' : 'List',
1533 'type' : MENU_DEFAULT_LOCAL_TASK,
1534 'weight' : -10
1535 }
1536 items['admin/build/types/add'] = {
1537 'title' : 'Add content type',
1538 'page callback' : 'drupal_get_form',
1539 'page arguments' : ('node_type_form',),
1540 'access arguments' : ('administer content types',),
1541 'type' : MENU_LOCAL_TASK
1542 }
1543 items['node'] = {
1544 'title' : 'Content',
1545 'page callback' : 'node_page_default',
1546 'access arguments' : ('access content',),
1547 'type' : MENU_CALLBACK
1548 }
1549 items['node/add'] = {
1550 'title' : 'Create content',
1551 'page callback' : 'node_add_page',
1552 'access callback' : '_node_add_access',
1553 'weight' : 1
1554 }
1555 items['rss.xml'] = {
1556 'title' : 'RSS feed',
1557 'page callback' : 'node_feed',
1558 'access arguments' : ('access content',),
1559 'type' : MENU_CALLBACK
1560 }
1561 for type_ in get_types('types', None, True):
1562 type_url_str = php.str_replace('_', '-', type_.type_)
1563 items['node/add/' + type_url_str] = {
1564 'title' : drupal_ucfirst(type_.name),
1565 'title callback' : 'check_plain',
1566 'page callback' : 'node_add',
1567 'page arguments' : (2,),
1568 'access callback' : 'node_access',
1569 'access arguments' : ('create', type.type),
1570 'description' : type.description
1571 }
1572 items['admin/build/node-type/' + type_url_str] = {
1573 'title' : type_.name,
1574 'page callback' : 'drupal_get_form',
1575 'page arguments' : ('node_type_form', type),
1576 'access arguments' : ('administer content types',),
1577 'type' : MENU_CALLBACK
1578 }
1579 items['admin/build/node-type/' + type_url_str + '/edit'] = {
1580 'title' : 'Edit',
1581 'type' : MENU_DEFAULT_LOCAL_TASK
1582 }
1583 items['admin/build/node-type/' + type_url_str + '/delete'] = {
1584 'title' : 'Delete',
1585 'page arguments' : ('node_type_delete_confirm', type),
1586 'access arguments' : ('administer content types',),
1587 'type' : MENU_CALLBACK
1588 }
1589 items['node/%node'] = {
1590 'title callback' : 'node_page_title',
1591 'title arguments' : (1,),
1592 'page callback' : 'node_page_view',
1593 'page arguments' : (1,),
1594 'access callback' : 'node_access',
1595 'access arguments' : ('view', 1),
1596 'type' : MENU_CALLBACK
1597 }
1598 items['node/%node/view'] = {
1599 'title' : 'View',
1600 'type' : MENU_DEFAULT_LOCAL_TASK,
1601 'weight' : -10
1602 }
1603 items['node/%node/edit'] = {
1604 'title' : 'Edit',
1605 'page callback' : 'node_page_edit',
1606 'page arguments' : (1,),
1607 'access callback' : 'node_access',
1608 'access arguments' : ('update', 1),
1609 'weight' : 1,
1610 'type' : MENU_LOCAL_TASK
1611 }
1612 items['node/%node/delete'] = {
1613 'title' : 'Delete',
1614 'page callback' : 'drupal_get_form',
1615 'page arguments' : ('node_delete_confirm', 1),
1616 'access callback' : 'node_access',
1617 'access arguments' : ('delete', 1),
1618 'weight' : 1,
1619 'type' : MENU_CALLBACK
1620 }
1621 items['node/%node/revisions'] = {
1622 'title' : 'Revisions',
1623 'page callback' : 'node_revision_overview',
1624 'page arguments' : (1,),
1625 'access callback' : '_node_revision_access',
1626 'access arguments' : (1,),
1627 'weight' : 2,
1628 'type' : MENU_LOCAL_TASK
1629 }
1630 items['node/%node/revisions/%/view'] = {
1631 'title' : 'Revisions',
1632 'load arguments' : (3,),
1633 'page callback' : 'node_show',
1634 'page arguments' : (1, None, True),
1635 'access callback' : '_node_revision_access',
1636 'access arguments' : (1,),
1637 'type' : MENU_CALLBACK
1638 }
1639 items['node/%node/revisions/%/revert'] = {
1640 'title' : 'Revert to earlier revision',
1641 'load arguments' : (3,),
1642 'page callback' : 'drupal_get_form',
1643 'page arguments' : ('node_revision_revert_confirm', 1),
1644 'access callback' : '_node_revision_access',
1645 'access arguments' : (1, 'update'),
1646 'type' : MENU_CALLBACK
1647 }
1648 items['node/%node/revisions/%/delete'] = {
1649 'title' : 'Delete earlier revision',
1650 'load arguments' : (3,),
1651 'page callback' : 'drupal_get_form',
1652 'page arguments' : ('node_revision_delete_confirm', 1),
1653 'access callback' : '_node_revision_access',
1654 'access arguments' : (1, 'delete'),
1655 'type' : MENU_CALLBACK
1656 }
1657 return items
1658
1659
1660
1661 -def page_title(node):
1662 """
1663 Title callback.
1664 """
1665 return node.title
1666
1667
1668
1669
1671 """
1672 Implementation of hook_init().
1673 """
1674 lib_common.drupal_add_css(\
1675 lib_common.drupal_get_path('plugin', 'node') + '/node.css')
1676
1677
1678
1679
1681 node = lib_database.fetch_object(lib_database.query(\
1682 'SELECT changed FROM {node} WHERE nid = %d', nid))
1683 return (node.changed)
1684
1685
1686
1688 """
1689 Return a list of all the existing revision numbers.
1690 """
1691 revisions = []
1692 result = lib_database.query(\
1693 'SELECT r.vid, r.title, r.log, r.uid, n.vid AS current_vid, ' + \
1694 'r.timestamp, u.name FROM {node_revisions} r ' + \
1695 'LEFT JOIN {node} n ON n.vid = r.vid ' + \
1696 'INNER JOIN {users} u ON u.uid = r.uid ' + \
1697 'WHERE r.nid = %d ORDER BY r.timestamp DESC', node.nid)
1698 while True:
1699 revision = lib_database.fetch_object(result)
1700 if not revision:
1701 break
1702 revisions[revision.vid] = revision
1703 return revisions
1704
1705
1706
1707
1709 """
1710 Implementation of hook_block().
1711 """
1712 if (op == 'list'):
1713 blocks['syndicate']['info'] = lib_common.t('Syndicate')
1714
1715 blocks['syndicate']['cache'] = BLOCK_NO_CACHE
1716 return blocks
1717 elif (op == 'view'):
1718 block['subject'] = lib_common.t('Syndicate')
1719 block['content'] = lib_theme.theme('feed_icon', \
1720 url('rss.xml'), lib_common.t('Syndicate'))
1721 return block
1722
1723
1724
1725 -def feed(nids = False, channel = []):
1726 """
1727 A generic function for generating RSS feeds from a set of nodes.
1728
1729 @param nids
1730 An array of node IDs (nid). Defaults to False so empty feeds can be
1731 generated with passing an empty array, if no items are to be added
1732 to the feed.
1733 @param channel
1734 An associative array containing title, link, description and other keys.
1735 The link should be an absolute URL.
1736 """
1737 if (nids == False):
1738 nids = []
1739 result = lib_database.query_range(lib_database.rewrite_sql(\
1740 'SELECT n.nid, n.created FROM {node} n ' + \
1741 'WHERE n.promote = 1 AND n.status = 1 ORDER BY n.created DESC'), 0, \
1742 lib_bootstrap.variable_get('feed_default_items', 10))
1743 while True:
1744 row = lib_database.fetch_object(result)
1745 nids.append( row.nid )
1746 item_length = lib_bootstrap.variable_get('feed_item_length', 'teaser')
1747 namespaces = {'xmlns:dc' : 'http://purl.org/dc/elements/1.1/'}
1748 items = ''
1749 for nid in nids:
1750
1751 item = load(nid)
1752 item.build_mode = NODE_BUILD_RSS
1753 item.link = url("node/nid", {'absolute' : True})
1754 if (item_length != 'title'):
1755 teaser = (True if (item_length == 'teaser') else False)
1756
1757 if (hook(item, 'view')):
1758 item = invoke(item, 'view', teaser, False)
1759 else:
1760 item = node_prepare(item, teaser)
1761
1762 invoke_nodeapi(item, 'view', teaser, False)
1763
1764 extra = invoke_nodeapi(item, 'rss item')
1765 extra = php.array_merge(extra, \
1766 ({'key' : 'pubDate', 'value' : php.gmdate('r', item.created)}, \
1767 {'key' : 'dc:creator', 'value' : item.name}, \
1768 {'key' : 'guid', 'value' : item.nid + ' at ' + base_url, \
1769 'attributes' : {'isPermaLink' : 'False'}}))
1770 for element in extra:
1771 if (php.isset(element, 'namespace')):
1772 namespaces = php.array_merge(namespaces, element['namespace'])
1773
1774 if item_length == 'fulltext':
1775 item_text = item.body
1776 elif item_length == 'teaser':
1777 item_text = item.teaser
1778 if (not empty(item.readmore)):
1779 item_text += '<p>' + l(lib_common.t('read more'), \
1780 'node/' + item.nid, {'absolute' : True, 'attributes' : \
1781 {'target' : '_blank'}}) + '</p>'
1782 elif item_length == 'title':
1783 item_text = ''
1784 items += format_rss_item(item.title, item.link, item_text, extra)
1785 channel_defaults = {
1786 'version' : '2.0',
1787 'title' : lib_bootstrap.variable_get('site_name', 'Drupal'),
1788 'link' : lib_appglobals.base_url,
1789 'description' : lib_bootstrap.variable_get('site_mission', ''),
1790 'language' : lib_appglobals.language.language
1791 }
1792 channel = php.array_merge(channel_defaults, channel)
1793 output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
1794 output += "<rss version=\"" + channel["version"] + "\" xml:base=\"" + \
1795 lib_appglobals.base_url + "\" " + drupal_attributes(namespaces) + \
1796 ">\n"
1797 output += format_rss_channel(channel['title'], channel['link'], \
1798 channel['description'], items, channel['language'])
1799 output += "</rss>\n"
1800 drupal_set_header('Content-Type: application/rss+xml; charset=utf-8')
1801 print output
1802
1803
1805 """
1806 Menu callback; Generate a listing of promoted nodes.
1807 """
1808 result = pager_query(db_rewrite_sql('SELECT n.nid, n.sticky, n.created FROM {node} n WHERE n.promote = 1 AND n.status = 1 ORDER BY n.sticky DESC, n.created DESC'), variable_get('default_nodes_main', 10))
1809 output = ''
1810 num_rows = False
1811 while True:
1812 node = lib_database.fetch_object(result)
1813 if not node:
1814 break
1815 output += view(load(node.nid), 1)
1816 num_rows = True
1817 if (num_rows):
1818 feed_url = url('rss.xml', {'absolute' : True})
1819 drupal_add_feed(feed_url, lib_bootstrap.variable_get('site_name', \
1820 'Drupal') + ' ' + t('RSS'))
1821 output += lib_theme.theme('pager', None, \
1822 lib_bootstrap.variable_get('default_nodes_main', 10))
1823 else:
1824 default_message = '<h1 class="title">' + lib_common.t(\
1825 'Welcome to your new Drupal website! ') + '</h1>'
1826 default_message += '<p>' + lib_common.t(\
1827 'Please follow these steps to set up and start using your website:') + \
1828 '</p>'
1829 default_message += '<ol>'
1830 default_message += '<li>' + lib_common.t(\
1831 '<strong>Configure your website</strong> Once logged in, visit ' + \
1832 'the <a href="@admin">administration section</a>, where you can ' + \
1833 '<a href="@config">customize and configure</a> all aspects of ' + \
1834 'your website.', {'@admin' : url('admin'), \
1835 '@config' : url('admin/settings')}) + '</li>'
1836 default_message += '<li>' + lib_common.t(\
1837 '<strong>Enable additional functionality</strong> ' + \
1838 'Next, visit the <a href="@modules">module list</a> and ' + \
1839 'enable features which suit your specific needs. '+ \
1840 'You can find additional modules in the <a href="@download_modules">' + \
1841 'Drupal modules download section</a>.', \
1842 {'@modules' : url('admin/build/modules'), \
1843 '@download_modules' : 'http://drupal.org/project/modules'}) + '</li>'
1844 default_message += '<li>' + lib_common.t(\
1845 '<strong>Customize your website design</strong> To change the ' + \
1846 '"look and feel" of your website, visit the <a href="@themes">' + \
1847 'themes section</a> + You may choose from one of the included ' + \
1848 'themes or download additional themes from the ' + \
1849 '<a href="@download_themes">Drupal themes download section</a>.', \
1850 {'@themes' : url('admin/build/themes'), \
1851 '@download_themes' : 'http://drupal.org/project/themes'}) + '</li>'
1852 default_message += '<li>' + lib_common.t(\
1853 '<strong>Start posting content</strong> Finally, you can ' + \
1854 '<a href="@content">create content</a> for your website. ' + \
1855 'This message will disappear once you have promoted a post ' + \
1856 'to the front page.', {'@content' : url('node/add')}) + '</li>'
1857 default_message += '</ol>'
1858 default_message += '<p>' + lib_common.t(\
1859 'For more information, please refer to the <a href="@help">' + \
1860 'help section</a>, or the <a href="@handbook">online Drupal ' + \
1861 'handbooks</a> + You may also post at the <a href="@forum">' + \
1862 'Drupal forum</a>, or view the wide range of ' + \
1863 '<a href="@support">other support options</a> available.', \
1864 {'@help' : url('admin/help'), '@handbook' : \
1865 'http://drupal.org/handbooks', '@forum' : 'http://drupal.org/forum', \
1866 '@support' : 'http://drupal.org/support'}) + '</p>'
1867 output = '<div id="first-time">' + default_message + '</div>'
1868 drupal_set_title('')
1869 return output
1870
1871
1872
1873 -def page_view(node, cid = None):
1874 """
1875 Menu callback; view a single node.
1876 """
1877 drupal_set_title(check_plain(node.title))
1878 return show(node, cid)
1879
1880
1881
1883 """
1884 Implementation of hook_update_index().
1885 """
1886 limit = int(lib_bootstrap.variable_get('search_cron_limit', 100))
1887
1888
1889 lib_bootstrap.variable_set('node_cron_comments_scale', \
1890 1.0 / php.max(1, lib_database.result(lib_database.query(\
1891 'SELECT MAX(comment_count) FROM {node_comment_statistics}'))))
1892 lib_bootstrap.variable_set('node_cron_views_scale', \
1893 1.0 / php.max(1, lib_database.result(lib_database.query(\
1894 'SELECT MAX(totalcount) FROM {node_counter}'))))
1895 result = lib_database.query_range(\
1896 "SELECT n.nid FROM {node} n LEFT JOIN {search_dataset} d " + \
1897 "ON d.type = 'node' AND d.sid = n.nid WHERE d.sid IS None OR " + \
1898 "d.reindex <> 0 ORDER BY d.reindex ASC, n.nid ASC", 0, limit)
1899 while True:
1900 node = db_fetch_object(result)
1901 if not node:
1902 break
1903 _index_node(node)
1904
1905
1906
1928
1929
2009
2010
2012 """
2013 Form API callback for the search form. Registered in node_form_alter().
2014 """
2015 php.Reference.check(form_state)
2016
2017 keys = form_state['values']['processed_keys']
2018
2019 if (php.isset(form_state['values'], 'type') and \
2020 php.is_array(form_state['values']['type'])):
2021
2022
2023 form_state['values']['type'] = php.array_filter(\
2024 form_state['values']['type'])
2025 if (php.count(form_state['values']['type'])):
2026 keys = search_query_insert(keys, 'type', \
2027 php.implode(',', php.array_keys(form_state['values']['type'])))
2028 if (php.isset(form_state['values'], 'category') and \
2029 php.is_array(form_state['values']['category'])):
2030 keys = search_query_insert(keys, 'category', \
2031 php.implode(',', form_state['values']['category']))
2032 if (php.isset(form_state['values']['language']) and \
2033 php.is_array(form_state['values']['language'])):
2034 keys = search_query_insert(keys, 'language', php.implode(',', \
2035 php.array_filter(form_state['values']['language'])))
2036 if (form_state['values']['or'] != ''):
2037 if (php.preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' + \
2038 form_state['values']['or'], matches)):
2039 keys += ' ' + php.implode(' OR ', matches[1])
2040 if (form_state['values']['negative'] != ''):
2041 if (php.preg_match_all('/ ("[^"]+"|[^" ]+)/i', ' ' + \
2042 form_state['values']['negative'], matches)):
2043 keys += ' -' + php.implode(' -', matches[1])
2044 if (form_state['values']['phrase'] != ''):
2045 keys += ' "' + php.str_replace('"', ' ', \
2046 form_state['values']['phrase']) + '"'
2047 if (not php.empty(keys)):
2048 form_set_value(form['basic']['inline']['processed_keys'], trim(keys),\
2049 form_state)
2050
2051
2052 -def access(op, node, account = None):
2053 """
2054 @defgroup node_access Node access rights
2055 @{
2056 The node access system determines who can do what to which nodes.
2057
2058 In determining access rights for a node, node_access() first checks
2059 whether the user has the "administer nodes" permission. Such users have
2060 unrestricted access to all nodes. Then the node module's hook_access()
2061 is called, and a True or False return value will grant or deny access.
2062 This allows, for example, the blog module to always grant access to the
2063 blog author, and for the book module to always deny editing access to
2064 PHP pages.
2065
2066 If node module does not intervene (returns None), then the
2067 node_access table is used to determine access. All node access
2068 modules are queried using hook_node_grants() to assemble a list of
2069 "grant IDs" for the user. This list is compared against the table.
2070 If any row contains the node ID in question (or 0, which stands for "all
2071 nodes"), one of the grant IDs returned, and a value of True for the
2072 operation in question, then access is granted. Note that this table is a
2073 list of grants; any matching row is sufficient to grant access to the
2074 node.
2075
2076 In node listings, the process above is followed except that
2077 hook_access() is not called on each node for performance reasons and for
2078 proper functioning of the pager system. When adding a node listing to your
2079 module, be sure to use db_rewrite_sql() to add
2080 the appropriate clauses to your query for access checks.
2081
2082 To see how to write a node access module of your own, see
2083 node_access_example.module.
2084
2085 Determine whether the current user may perform the given operation on the
2086 specified node.
2087
2088 @param op
2089 The operation to be performed on the node. Possible values are:
2090 - "view"
2091 - "update"
2092 - "delete"
2093 - "create"
2094 @param node
2095 The node object (or node array) on which the operation is to be performed,
2096 or node type (e.g. 'forum') for "create" operation.
2097 @param account
2098 Optional, a user object representing the user for whom the operation is to
2099 be performed. Determines access for a user other than the current user.
2100 @return
2101 True if the operation may be performed.
2102 """
2103 if (not node):
2104 return False
2105
2106 if (op != 'create'):
2107 node = php.object_(node)
2108
2109 if (empty(account)):
2110 account = lib_appglobals.user
2111
2112 if (op == 'update' and not filter_access(node.format)):
2113 return False
2114 if (lib_plugin.plugins['user'].access('administer nodes', account)):
2115 return True
2116 if (not lib_plugin.plugins['user'].access('access content', account)):
2117 return False
2118
2119
2120 plugin = get_types('plugin', node)
2121 if (module == 'node'):
2122 plugin = 'node_content';
2123 access = lib_plugin.invoke(plugin, 'access', op, node, account)
2124 if (not is_none(access)):
2125 return access
2126
2127
2128 if (op != 'create' and node.nid and node.status):
2129 grants = []
2130 for realm,gids in access_grants(op, account).items():
2131 for gid in gids:
2132 grants.append( "(gid = gid AND realm = 'realm')" )
2133 grants_sql = ''
2134 if (php.count(grants)):
2135 grants_sql = 'AND (' + php.implode(' OR ', grants) + ')'
2136 sql = "SELECT COUNT(*) FROM {node_access} WHERE (nid = 0 OR nid = %d) grants_sql AND grant_op >= 1"
2137 result = lib_database.query(sql, node.nid)
2138 return (lib_database.result(result))
2139
2140 if (op == 'view' and account.uid == node.uid and account.uid != 0):
2141 return True
2142 return False
2143
2144
2145
2147 """
2148 Generate an SQL join clause for use in fetching a node listing.
2149
2150 @param node_alias
2151 If the node table has been given an SQL alias other than the default
2152 "n", that must be passed here.
2153 @param node_access_alias
2154 If the node_access table has been given an SQL
2155 alias other than the default
2156 "na", that must be passed here.
2157 @return
2158 An SQL join clause.
2159 """
2160 if (lib_plugin.plugins['user'].access('administer nodes')):
2161 return ''
2162 return 'INNER JOIN {node_access} ' + \
2163 node_access_alias + ' ON ' + node_access_alias + '.nid = ' + \
2164 node_alias + '.nid'
2165
2166
2167
2169 """
2170 Generate an SQL where clause for use in fetching a node listing.
2171
2172 @param op
2173 The operation that must be allowed to return a node.
2174 @param node_access_alias
2175 If the node_access table has been given an SQL alias other than the default
2176 "na", that must be passed here.
2177 @param account
2178 The user object for the user performing the operation. If omitted, the
2179 current user is used.
2180 @return
2181 An SQL where clause.
2182 """
2183 if (lib_plugin.plugins['user'].access('administer nodes')):
2184 return
2185 grants = []
2186 for realm,gids in node_access_grants(op, account).items():
2187 for gid in gids:
2188 grants.append( "(node_access_alias.gid = gid AND " + \
2189 "node_access_alias.realm = 'realm')" )
2190 grants_sql = ''
2191 if (php.count(grants)):
2192 grants_sql = 'AND (' + php.implode(' OR ', grants) + ')'
2193 sql = "node_access_alias.grant_op >= 1 grants_sql"
2194 return sql
2195
2196
2197
2199 """
2200 Fetch an array of permission IDs granted to the given user ID.
2201
2202 The implementation here provides only the universal "all" grant. A node
2203 access module should implement hook_node_grants() to provide a grant
2204 list for the user.
2205
2206 @param op
2207 The operation that the user is trying to perform.
2208 @param account
2209 The user object for the user performing the operation. If omitted, the
2210 current user is used.
2211 @return
2212 An associative array in which the keys are realms, and the values are
2213 arrays of grants for those realms.
2214 """
2215 if (account is None):
2216 account = lib_appglobals.user
2217 return php.array_merge({'all' : (0,)},\
2218 lib_plugin.invoke_all('node_grants', account, op))
2219
2220
2221
2223 """
2224 Determine whether the user has a global viewing grant for all nodes.
2225 """
2226 php.static(access_view_all_nodes, 'access', None)
2227 if (access_view_all_nodes.access is None):
2228 grants = []
2229 for realm,gids in access_grants('view').items():
2230 for gid in gids:
2231 grants.append("(gid = gid AND realm = 'realm')" )
2232 grants_sql = ''
2233 if (php.count(grants)):
2234 grants_sql = 'AND (' + php.implode(' OR ', grants) + ')'
2235 sql = "SELECT COUNT(*) FROM {node_access} " + \
2236 "WHERE nid = 0 grants_sql AND grant_view >= 1"
2237 result = lib_database.query(sql)
2238 access = lib_database.result(result)
2239 return access
2240
2241
2242
2255
2256
2258 """
2259 This function will call module invoke to get a list of grants and then
2260 write them to the database. It is called at node save, and should be
2261 called by modules whenever something other than a node_save causes
2262 the permissions on a node to change.
2263
2264 This function is the only function that should write to the node_access
2265 table.
2266
2267 @param node
2268 The node to acquire grants for.
2269 """
2270 grants = lib_plugin.invoke_all('node_access_records', node)
2271 if (empty(grants)):
2272 grants.append( {'realm' : 'all', 'gid' : 0, 'grant_view' : 1, \
2273 'grant_update' : 0, 'grant_delete' : 0} )
2274 else:
2275
2276 grant_by_priority = []
2277 for g in grants:
2278 grant_by_priority[php.intval(g['priority'])].append(g)
2279 php.krsort(grant_by_priority)
2280 grants = php.array_shift(grant_by_priority)
2281 access_write_grants(node, grants)
2282
2283
2284
2286 """
2287 This function will write a list of grants to the database, deleting
2288 any pre-existing grants. If a realm is provided, it will only
2289 delete grants from that realm, but it will always delete a grant
2290 from the 'all' realm. Modules which utilize node_access can
2291 use this function when doing mass updates due to widespread permission
2292 changes.
2293
2294 @param node
2295 The node being written to. All that is necessary is that it contain
2296 a nid.
2297 @param grants
2298 A list of grants to write. Each grant is an array that must contain the
2299 following keys: realm, gid, grant_view, grant_update, grant_delete.
2300 The realm is specified by a particular module; the gid is as well, and
2301 is a module-defined id to define grant privileges. each grant_* field
2302 is a boolean value.
2303 @param realm
2304 If provided, only read/write grants for that realm.
2305 @param delete
2306 If False, do not delete records. This is only for optimization purposes,
2307 and assumes the caller has already performed a mass delete of some form.
2308 """
2309 if (delete):
2310 query = 'DELETE FROM {node_access} WHERE nid = %d'
2311 if (realm):
2312 query += " AND realm in ('%s', 'all')"
2313 lib_database.query(query, node.nid, realm)
2314
2315 if (php.count(lib_plugin.implements('node_grants'))):
2316 for grant in grants:
2317 if (realm and realm != grant['realm']):
2318 continue
2319
2320 if (grant['grant_view'] or grant['grant_update'] or\
2321 grant['grant_delete']):
2322 lib_database.query("INSERT INTO {node_access} (nid, " + \
2323 "realm, gid, grant_view, grant_update, grant_delete) " + \
2324 "VALUES (%d, '%s', %d, %d, %d, %d)", \
2325 node.nid, grant['realm'], grant['gid'], grant['grant_view'], \
2326 grant['grant_update'], grant['grant_delete'])
2327
2328
2329
2331 """
2332 Flag / unflag the node access grants for rebuilding, or read the current
2333 value of the flag.
2334
2335 When the flag is set, a message is displayed to users with 'access
2336 administration pages' permission, pointing to the 'rebuild' confirm form.
2337 This can be used as an alternative to direct node_access_rebuild calls,
2338 allowing administrators to decide when they want to perform the actual
2339 (possibly time consuming) rebuild.
2340 When unsure the current user is an adminisrator, node_access_rebuild
2341 should be used instead.
2342
2343 @param rebuild
2344 (Optional) The boolean value to be written.
2345 @return
2346 (If no value was provided for rebuild) The current value of the flag.
2347 """
2348 if (rebuild is None):
2349 return lib_bootstrap.variable_get('node_access_needs_rebuild', False)
2350 elif (rebuild):
2351 lib_bootstrap.variable_set('node_access_needs_rebuild', True)
2352 else:
2353 lib_bootstrap.variable_del('node_access_needs_rebuild')
2354
2355
2356
2358 """
2359 Rebuild the node access database. This is occasionally needed by modules
2360 that make system-wide changes to access levels.
2361
2362 When the rebuild is required by an admin-triggered action (e.g module
2363 settings form), calling node_access_needs_rebuild(True) instead of
2364 node_access_rebuild() lets the user perform his changes and actually
2365 rebuild only once he is done.
2366
2367 Note : As of Drupal 6, node access modules are not required to (and actually
2368 should not) call node_access_rebuild() in hook_enable/disable anymore.
2369
2370 @see node_access_needs_rebuild()
2371
2372 @param batch_mode
2373 Set to True to process in 'batch' mode, spawning processing over several
2374 HTTP requests (thus avoiding the risk of PHP timeout if the site has a
2375 large number of nodes).
2376 hook_update_N and any form submit handler are safe contexts to use the
2377 'batch mode'. Less decidable cases (such as calls from hook_user,
2378 hook_taxonomy, hook_node_type...) might consider using the non-batch mode.
2379 """
2380 lib_database.query("DELETE FROM {node_access}")
2381
2382 if (php.count(lib_plugin.implements('node_grants'))):
2383 if (batch_mode):
2384 batch = {
2385 'title' : lib_common.t('Rebuilding content access permissions'),
2386 'operations' : (
2387 ('_access_rebuild_batch_operation', tuple()),
2388 ),
2389 'finished' : '_access_rebuild_batch_finished'
2390 }
2391 batch_set(batch)
2392 else:
2393
2394
2395
2396
2397 result = lib_database.query("SELECT nid FROM {node}")
2398 while True:
2399 node = lib_datbase.fetch_object(result)
2400 if not node:
2401 break
2402 loaded_node = load(node.nid, None, True)
2403
2404
2405 if (not php.empty(loaded_node)):
2406 access_acquire_grants(loaded_node)
2407 else:
2408
2409 lib_database.query(\
2410 "INSERT INTO {node_access} VALUES (0, 0, 'all', 1, 0, 0)")
2411 if (batch is None):
2412 drupal_set_message(lib_common.t('Content permissions have been rebuilt.'))
2413 access_needs_rebuild(False)
2414 cache_clear_all()
2415
2416
2417
2419 """
2420 Batch operation for node_access_rebuild_batch.
2421
2422 This is a mutlistep operation : we go through all nodes by packs of 20.
2423 The batch processing engine interrupts processing and sends progress
2424 feedback after 1 second execution time.
2425 """
2426 php.Reference.check(context)
2427 if (php.empty(context['sandbox'])):
2428
2429 context['sandbox']['progress'] = 0
2430 context['sandbox']['current_node'] = 0
2431 context['sandbox']['max'] = lib_database.result(\
2432 lib_database.query('SELECT COUNT(DISTINCT nid) FROM {node}'))
2433
2434 limit = 20
2435 result = lib_database.query_range(\
2436 "SELECT nid FROM {node} WHERE nid > %d ORDER BY nid ASC", \
2437 context['sandbox']['current_node'], 0, limit)
2438 while True:
2439 row = lib_database.fetch_array(result)
2440 if not row:
2441 break
2442 loaded_node = load(row['nid'], None, True)
2443
2444
2445 if (not php.empty(loaded_node)):
2446 access_acquire_grants(loaded_node)
2447 context['sandbox']['progress'] += 1
2448 context['sandbox']['current_node'] = loaded_node.nid
2449
2450 if (context['sandbox']['progress'] != context['sandbox']['max']):
2451 context['finished'] = \
2452 context['sandbox']['progress'] / context['sandbox']['max']
2453
2454
2455
2467
2468
2469
2470 -def hook_content_access(op, node, account):
2471 """
2472 @} End of "defgroup node_access".
2473
2474
2475 @defgroup node_content Hook implementations for user-created content types.
2476 @{
2477
2478
2479 Implementation of hook_access().
2480
2481 Named so as not to conflict with node_access()
2482 """
2483 type_ = (node if php.is_string(node) else (node['type'] if \
2484 php.is_array(node) else node.type_))
2485 if (op == 'create'):
2486 return lib_plugin.plugins['user'].access(\
2487 'create ' + type_ + ' content', account)
2488 if (op == 'update'):
2489 if (lib_plugin.plugins['user'].access(\
2490 'edit any ' + type_ + ' content', account) or \
2491 (lib_plugin.plugins['user'].access('edit own ' + \
2492 type_ + ' content', account) and (account.uid == node.uid))):
2493 return True
2494 if (op == 'delete'):
2495 if (lib_plugin.plugins['user'].access('delete any ' + type_ + \
2496 ' content', account) or (lib_plugin.plugins['user'].access(\
2497 'delete own ' + type_ + ' content', account) and \
2498 (account.uid == node.uid))):
2499 return True
2500
2501
2503 """
2504 Implementation of hook_form().
2505 """
2506 type_ = get_types('type', node)
2507 form = []
2508 if (type_.has_title):
2509 form['title'] = {
2510 '#type' : 'textfield',
2511 '#title' : check_plain(type_.title_label),
2512 '#required' : True,
2513 '#default_value' : node.title,
2514 '#maxlength' : 255,
2515 '#weight' : -5
2516 }
2517 if (type_.has_body):
2518 form['body_field'] = body_field(node, type_.body_label, \
2519 type_.min_word_count)
2520 return form
2521
2522
2523
2534
2535
2536
2538 """
2539 Format the "Submitted by username on date/time" for each node
2540
2541 @ingroup themeable
2542 """
2543 return lib_common.t('Submitted by not username on @datetime', {
2544 'not username' : lib_theme.theme('username', node),
2545 '@datetime' : format_date(node.created),
2546 })
2547
2548
2549
2551 """
2552 Implementation of hook_hook_info().
2553 """
2554 return {
2555 'node' : {
2556 'nodeapi' : {
2557 'presave' : {
2558 'runs when' : lib_common.t('When either saving a new post ' + \
2559 'or updating an existing post')
2560 },
2561 'insert' : {
2562 'runs when' : lib_common.t('After saving a new post')
2563 },
2564 'update' : {
2565 'runs when' : lib_common.t('After saving an updated post')
2566 },
2567 'delete' : {
2568 'runs when' : lib_common.t('After deleting a post')
2569 },
2570 'view' : {
2571 'runs when' : lib_common.t('When content is viewed by an ' + \
2572 'authenticated user')
2573 },
2574 },
2575 },
2576 }
2577
2578
2580 """
2581 Implementation of hook_action_info().
2582 """
2583 return {
2584 'node_publish_action' : {
2585 'type' : 'node',
2586 'description' : lib_common.t('Publish post'),
2587 'configurable' : False,
2588 'behavior' : ('changes_node_property',),
2589 'hooks' : {
2590 'nodeapi' : ('presave',),
2591 'comment' : ('insert', 'update'),
2592 }
2593 },
2594 'node_unpublish_action' : {
2595 'type' : 'node',
2596 'description' : lib_common.t('Unpublish post'),
2597 'configurable' : False,
2598 'behavior' : ('changes_node_property',),
2599 'hooks' : {
2600 'nodeapi' : ('presave',),
2601 'comment' : ('delete', 'insert', 'update')
2602 }
2603 },
2604 'node_make_sticky_action' : {
2605 'type' : 'node',
2606 'description' : lib_common.t('Make post sticky'),
2607 'configurable' : False,
2608 'behavior' : ('changes_node_property',),
2609 'hooks' : {
2610 'nodeapi' : ('presave',),
2611 'comment' : ('insert', 'update'),
2612 },
2613 },
2614 'node_make_unsticky_action' : {
2615 'type' : 'node',
2616 'description' : lib_common.t('Make post unsticky'),
2617 'configurable' : False,
2618 'behavior' : ('changes_node_property',),
2619 'hooks' : {
2620 'nodeapi' : ('presave',),
2621 'comment' : ('delete', 'insert', 'update'),
2622 },
2623 },
2624 'node_promote_action' : {
2625 'type' : 'node',
2626 'description' : lib_common.t('Promote post to front page'),
2627 'configurable' : False,
2628 'behavior' : ('changes_node_property',),
2629 'hooks' : {
2630 'nodeapi' : ('presave',),
2631 'comment' : ('insert', 'update'),
2632 }
2633 },
2634 'node_unpromote_action' : {
2635 'type' : 'node',
2636 'description' : lib_common.t('Remove post from front page'),
2637 'configurable' : False,
2638 'behavior' : ('changes_node_property',),
2639 'hooks' : {
2640 'nodeapi' : ('presave',),
2641 'comment' : ('delete', 'insert', 'update'),
2642 },
2643 },
2644 'node_assign_owner_action' : {
2645 'type' : 'node',
2646 'description' : lib_common.t('Change the author of a post'),
2647 'configurable' : True,
2648 'behavior' : ('changes_node_property',),
2649 'hooks' : {
2650 'any' : True,
2651 'nodeapi' : ('presave',),
2652 'comment' : ('delete', 'insert', 'update'),
2653 },
2654 },
2655 'node_save_action' : {
2656 'type' : 'node',
2657 'description' : lib_common.t('Save post'),
2658 'configurable' : False,
2659 'hooks' : {
2660 'comment' : ('delete', 'insert', 'update'),
2661 },
2662 },
2663 'node_unpublish_by_keyword_action' : {
2664 'type' : 'node',
2665 'description' : lib_common.t('Unpublish post containing keyword(s)'),
2666 'configurable' : True,
2667 'hooks' : {
2668 'nodeapi' : ('presave', 'insert', 'update'),
2669 }
2670 }
2671 }
2672
2673
2674
2675
2677 """
2678 Implementation of a Drupal action.
2679 Sets the status of a node to 1, meaning published.
2680 """
2681 php.Reference.check(node)
2682 node.status = 1
2683 watchdog('action', 'Set @type %title to published.', \
2684 {'@type' : get_types('name', node), '%title' : node.title})
2685
2686
2687
2689 """
2690 Implementation of a Drupal action.
2691 Sets the status of a node to 0, meaning unpublished.
2692 """
2693 php.Reference.check(node)
2694 node.status = 0
2695 watchdog('action', 'Set @type %title to unpublished.', \
2696 {'@type' : get_types('name', node), '%title' : node.title})
2697
2698
2699
2701 """
2702 Implementation of a Drupal action.
2703 Sets the sticky-at-top-of-list property of a node to 1.
2704 """
2705 php.Reference.check(node)
2706 node.sticky = 1
2707 watchdog('action', 'Set @type %title to sticky.', \
2708 {'@type' : get_types('name', node), '%title' : node.title})
2709
2710
2711
2713 """
2714 Implementation of a Drupal action.
2715 Sets the sticky-at-top-of-list property of a node to 0.
2716 """
2717 php.Reference.check(node)
2718 node.sticky = 0
2719 watchdog('action', 'Set @type %title to unsticky.', \
2720 {'@type' : get_types('name', node), '%title' : node.title})
2721
2722
2723
2733
2734
2735
2745
2746
2747
2749 """
2750 Implementation of a Drupal action.
2751 Saves a node.
2752 """
2753 save(node)
2754 watchdog('action', 'Saved @type %title', \
2755 {'@type' : get_types('name', node), '%title' : node.title})
2756
2757
2758
2760 """
2761 Implementation of a configurable Drupal action.
2762 Assigns ownership of a node to a user.
2763 """
2764 php.Reference.check(node)
2765 node.uid = context['owner_uid']
2766 owner_name = lib_database.result(lib_database.query(\
2767 "SELECT name FROM {users} WHERE uid = %d", context['owner_uid']))
2768 watchdog('action', 'Changed owner of @type %title to uid %name.', \
2769 {'@type' : get_types('type', node), \
2770 '%title' : node.title, '%name' : owner_name})
2771
2772
2773
2811
2812
2813
2815 count = lib_database.result(lib_database.query(\
2816 "SELECT COUNT(*) FROM {users} WHERE name = '%s'", \
2817 form_state['values']['owner_name']))
2818 if (php.intval(count) != 1):
2819 form_set_error('owner_name', lib_common.t(\
2820 'Please enter a valid username.'))
2821
2822
2823
2825
2826 uid = lib_database.result(lib_database.query(\
2827 "SELECT uid from {users} WHERE name = '%s'", \
2828 form_state['values']['owner_name']))
2829 return {'owner_uid' : uid}
2830
2831
2832
2846
2847
2848
2849
2853
2854
2855
2857 """
2858 Implementation of a configurable Drupal action.
2859 Unpublish a node if it contains a certain string.
2860
2861 @param context
2862 An array providing more information about the context of the call to
2863 this action.
2864 @param comment
2865 A node object.
2866 """
2867 for keyword in context['keywords']:
2868 if (php.strstr(view(php.clone(node)), keyword) or \
2869 php.strstr(node.title, keyword)):
2870 node.status = 0
2871 watchdog('action', 'Set @type %title to unpublished.', \
2872 {'@type' : get_types('name', node), '%title' : node.title})
2873 break
2874
2875
2876
2878 """
2879 Helper function to generate standard node permission list for a given type.
2880
2881 @param type
2882 The machine-readable name of the node type.
2883 @return array
2884 An array of permission names and descriptions.
2885 """
2886 info = get_types('type', type_)
2887 type_ = check_plain(info.type_)
2888
2889 perms["create type content"] = \
2890 lib_common.t('Create new %type_name content.', \
2891 {'%type_name' : info.name})
2892 perms["delete any type content"] = \
2893 lib_common.t('Delete any %type_name content, regardless of its author.', \
2894 {'%type_name' : info.name})
2895 perms["delete own type content"] = \
2896 lib_common.t('Delete %type_name content created by the user.', \
2897 {'%type_name' : info.name})
2898 perms["edit own type content"] = \
2899 lib_common.t('Edit %type_name content created by the user.', \
2900 {'%type_name' : info.name})
2901 perms["edit any type content"] = \
2902 lib_common.t('Edit any %type_name content, regardless of its author.', \
2903 {'%type_name' : info.name})
2904 return perms
2905