Updated the HEAD build version to 20090911
[moodle.git] / blog / locallib.php
CommitLineData
cae83708 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18
19/**
20 * Classes for Blogs.
21 *
22 * @package moodlecore
23 * @subpackage blog
24 * @copyright 2009 Nicolas Connault
25 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
26 */
27
28
29/**
30 * Blog_entry class. Represents an entry in a user's blog. Contains all methods for managing this entry.
31 * This class does not contain any HTML-generating code. See blog_listing sub-classes for such code.
32 * This class follows the Object Relational Mapping technique, its member variables being mapped to
33 * the fields of the posts table.
34 *
35 * @package moodlecore
36 * @subpackage blog
37 * @copyright 2009 Nicolas Connault
38 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
39 */
40class blog_entry {
41 // Public Database fields
42 public $id;
43 public $userid;
44 public $subject;
45 public $summary;
46 public $rating = 0;
47 public $attachment;
48 public $publishstate;
49
50 // Locked Database fields (Don't touch these)
51 public $courseid = 0;
52 public $groupid = 0;
53 public $module = 'blog';
54 public $moduleid = 0;
55 public $coursemoduleid = 0;
56 public $content;
57 public $format = 1;
58 public $uniquehash = '';
59 public $lastmodified;
60 public $created;
61 public $usermodified;
62
63 // Other class variables
64 public $form;
65 public $tags = array();
66
67 // Methods
68 /**
69 * Constructor. If given an id, will fetch the corresponding record from the DB.
70 *
71 * @param mixed $id_or_params A blog entry id if INT, or data for a new entry if array
72 */
73 public function __construct($id_or_params=null, $form=null) {
74 global $DB;
75
76 if (!empty($id_or_params) && !is_array($id_or_params) && !is_object($id_or_params)) {
77 $object = $DB->get_record('post', array('id' => $id_or_params));
78 foreach ($object as $var => $val) {
79 $this->$var = $val;
80 }
81 } else if (!empty($id_or_params) && (is_array($id_or_params) || is_object($id_or_params))) {
82 foreach ($id_or_params as $var => $val) {
83 $this->$var = $val;
84 }
85 }
86
87 $this->form = $form;
88 }
89
90 /**
91 * Prints or returns the HTML for this blog entry.
92 *
93 * @param bool $return
94 * @return string
95 */
96 public function print_html($return=false) {
97
98 global $USER, $CFG, $COURSE, $DB, $OUTPUT;
99
100 // Comments
101 $user = $DB->get_record('user', array('id'=>$this->userid));
102
103 $template['body'] = format_text($this->summary, $this->format);
104 $template['title'] = '<a id="b'. s($this->id) .'" />';
105 //enclose the title in nolink tags so that moodle formatting doesn't autolink the text
106 $template['title'] .= '<span class="nolink">'. format_string($this->subject) .'</span>';
107 $template['userid'] = $user->id;
108 $template['author'] = fullname($user);
109 $template['created'] = userdate($this->created);
110
111 if($this->created != $this->lastmodified){
112 $template['lastmod'] = userdate($this->lastmodified);
113 }
114
115 $template['publishstate'] = $this->publishstate;
116
117 $stredit = get_string('edit');
118 $strdelete = get_string('delete');
119
120 //check to see if the entry is unassociated with group/course level access
121 $unassociatedentry = false;
122 if (!empty($CFG->useblogassociations) && ($this->publishstate == 'group' || $this->publishstate == 'course')) {
123 if (!$DB->record_exists('blog_association', array('blogid' => $this->id))) {
124 $unassociatedentry = true;
125 }
126 }
127
128 /// Start printing of the blog
129 $table = new html_table();
130 $table->cellspacing = 0;
131 $table->add_classes('forumpost blog_entry blog'. ($unassociatedentry ? 'draft' : $template['publishstate']));
132 $table->width = '100%';
133
134 $picturecell = new html_table_cell();
135 $picturecell->add_classes('picture left');
136 $picturecell->text = $OUTPUT->user_picture(moodle_user_picture::make($user, SITEID));
137
138 $table->head[] = $picturecell;
139
140 $topiccell = new html_table_cell();
141 $topiccell->add_classes('topic starter');
142 $topiccell->text = $OUTPUT->container($template['title'], 'subject');
143 $topiccell->text .= $OUTPUT->container_start('author');
144
145 $fullname = fullname($user, has_capability('moodle/site:viewfullnames', get_context_instance(CONTEXT_COURSE, $COURSE->id)));
146 $by = new object();
147 $by->name = $OUTPUT->link(html_link::make(new moodle_url($CFG->wwwroot.'/user/view.php', array('id' => $user->id, 'course' => $COURSE->id)), $fullname));
148 $by->date = $template['created'];
149
150 $topiccell->text .= get_string('bynameondate', 'forum', $by);
151 $topiccell->text .= $OUTPUT->container_end();
152 $topiccell->header = false;
153 $table->head[] = $topiccell;
154
155 /// Actual content
156 $mainrow = new html_table_row();
157
158 $leftsidecell = new html_table_cell();
159 $leftsidecell->add_classes('left side');
160 $mainrow->cells[] = $leftsidecell;
161
162 $contentcell = new html_table_cell();
163 $contentcell->add_class('content');
164
165 if ($this->attachment) {
166 $attachedimages = $OUTPUT->container($this->print_attachments(), 'attachments');
167 } else {
168 $attachedimages = '';
169 }
170
171 //retrieve associations in case they're needed early
172 $blog_associations = $DB->get_records('blog_association', array('blogid' => $this->id));
173 //determine text for publish state
174 switch ($template['publishstate']) {
175 case 'draft':
176 $blogtype = get_string('publishtonoone', 'blog');
177 break;
178 case 'site':
179 $blogtype = get_string('publishtosite', 'blog');
180 break;
181 case 'public':
182 $blogtype = get_string('publishtoworld', 'blog');
183 break;
184 default:
185 $blogtype = '';
186 break;
187
188 }
189
190 $contentcell->text .= $OUTPUT->container($blogtype, 'audience');
191
192 $contentcell->text .= $template['body'];
193 $contentcell->text .= $attachedimages;
194
195 // Uniquehash is used as a link to an external blog
196 if (!empty($this->uniquehash) && blog_is_valid_url($this->uniquehash)) {
197 $contentcell->text .= $OUTPUT->container_start('externalblog');
198 $contentcell->text .= $OUTPUT->link(html_link::make($this->uniquehash, get_string('linktooriginalentry', 'blog')));
199 $contentcell->text .= $OUTPUT->container_end();
200 }
201
202 // Links to tags
203
204 if (!empty($CFG->usetags) && ($blogtags = tag_get_tags_csv('post', $this->id)) ) {
205 $contentcell->text .= $OUTPUT->container_start('tags');
206
207 if ($blogtags) {
208 $contentcell->text .= get_string('tags', 'tag') .': '. $blogtags;
209 }
210 $contentcell->text .= $OUTPUT->container_end();
211 }
212
213 //add associations
214 if (!empty($CFG->useblogassociations) && $blog_associations) {
215 $contentcell->text .= $OUTPUT->container_start('tags');
216 $assoc_str = '';
217
218 foreach ($blog_associations as $assoc_rec) { //first find and show the associated course
219 $context_rec = $DB->get_record('context', array('id' => $assoc_rec->contextid));
220 if ($context_rec->contextlevel == CONTEXT_COURSE) {
221 $associcon = new moodle_action_icon();
222 $associcon->link->url = new moodle_url($CFG->wwwroot.'/course/view.php', array('id' => $context_rec->instanceid));
223 $associcon->image->src = $OUTPUT->old_icon_url('i/course');
224 $associcon->linktext = $DB->get_field('course', 'shortname', array('id' => $context_rec->instanceid));
225 $assoc_str .= $OUTPUT->action_icon($associcon);
226 }
227 }
228
229 foreach ($blog_associations as $assoc_rec) { //now show each mod association
230 $context_rec = $DB->get_record('context', array('id' => $assoc_rec->contextid));
231
232 if ($context_rec->contextlevel == CONTEXT_MODULE) {
233 $modinfo = $DB->get_record('course_modules', array('id' => $context_rec->instanceid));
234 $modname = $DB->get_field('modules', 'name', array('id' => $modinfo->module));
235
236 $associcon = new moodle_action_icon();
237 $associcon->link->url = new moodle_url($CFG->wwwroot.'/mod/'.$modname.'/view.php', array('id' => $modinfo->id));
238 $associcon->image->src = $OUTPUT->mod_icon_url('icon', $modname);
239 $associcon->linktext = $DB->get_field($modname, 'name', array('id' => $modinfo->instance));
240 $assoc_str .= $OUTPUT->action_icon($associcon);
241
242 $assoc_str .= ', ';
243 $assoc_str .= $OUTPUT->action_icon($associcon);
244 }
245 }
246 $contentcell->text .= get_string('associations', 'blog') . ': '. $assoc_str;
247
248 $contentcell->text .= $OUTPUT->container_end();
249 }
250
251 if ($unassociatedentry) {
252 $contentcell->text .= $OUTPUT->container(get_string('associationunviewable', 'blog'), 'noticebox');
253 }
254
255 /// Commands
256
257 $contentcell->text .= $OUTPUT->container_start('commands');
258
259 if (blog_user_can_edit_entry($this)) {
9366362a 260 $contentcell->text .= $OUTPUT->link(html_link::make(new moodle_url($CFG->wwwroot.'/blog/edit.php', array('action' => 'edit', 'entryid' => $this->id)), $stredit)) . ' | ';
cae83708 261 if (!$DB->record_exists_sql('SELECT a.timedue, a.preventlate, a.emailteachers, a.var2, asub.grade
262 FROM {assignment} a, {assignment_submissions} as asub WHERE
263 a.id = asub.assignment AND userid = '.$USER->id.' AND a.assignmenttype = \'blog\'
264 AND asub.data1 = \''.$this->id.'\'')) {
265
9366362a 266 $contentcell->text .= $OUTPUT->link(html_link::make(new moodle_url($CFG->wwwroot.'/blog/edit.php', array('action' => 'delete', 'entryid' => $this->id)), $strdelete)) . ' | ';
cae83708 267 }
268 }
269
270 $contentcell->text .= $OUTPUT->link(html_link::make(new moodle_url($CFG->wwwroot.'/blog/index.php', array('entryid' => $this->id)), get_string('permalink', 'blog')));
271
272 $contentcell->text .= $OUTPUT->container_end();
273
274 if (isset($template['lastmod']) ){
275 $contentcell->text .= '<div style="font-size: 55%;">';
276 $contentcell->text .= ' [ '.get_string('modified').': '.$template['lastmod'].' ]';
277 $contentcell->text .= '</div>';
278 }
279
280 $mainrow->cells[] = $contentcell;
281 $table->data = array($mainrow);
282
283 if ($return) {
284 return $OUTPUT->table($table);
285 } else {
286 echo $OUTPUT->table($table);
287 }
288 }
289
290 /**
291 * Inserts this entry in the database. Access control checks must be done by calling code.
292 *
293 * @param mform $form Used for attachments
294 * @return void
295 */
296 public function process_attachment($form) {
297 $this->form = $form;
298 }
299
300 /**
301 * Inserts this entry in the database. Access control checks must be done by calling code.
302 * TODO Set the publishstate correctly
303 * @param mform $form Used for attachments
304 * @return void
305 */
306 public function add() {
307 global $CFG, $USER, $DB;
308
309 unset($this->id);
310 $this->module = 'blog';
311 $this->userid = (empty($this->userid)) ? $USER->id : $this->userid;
312 $this->lastmodified = time();
313 $this->created = time();
314
315 // Insert the new blog entry.
316 $this->id = $DB->insert_record('post', $this);
317
318 // Add blog attachment
319 if (!empty($this->form) && $this->form->get_new_filename('attachment')) {
320 if ($this->form->save_stored_file('attachment', SYSCONTEXTID, 'blog', $this->id, '/', $this->form->get_new_filename('attachment'), $USER->id)) {
321 $DB->set_field("post", "attachment", 1, array("id"=>$this->id));
322 }
323 }
324
325 // Update tags.
326 $this->add_tags_info();
327
328 if (!empty($CFG->useblogassociations)) {
329 $this->add_associations();
330 add_to_log(SITEID, 'blog', 'add', 'index.php?userid='.$this->userid.'&entryid='.$this->id, $this->subject);
331
332 }
333
334 tag_set('post', $this->id, $this->tags);
335 }
336
337 /**
338 * Updates this entry in the database. Access control checks must be done by calling code.
339 *
340 * @param mform $form Used for attachments
341 * @return void
342 */
343 public function edit($params=array(), $form=null) {
344 global $CFG, $USER, $DB;
345
346 $this->form = $form;
347 foreach ($params as $var => $val) {
348 $this->$var = $val;
349 }
350
351 //check to see if it's part of a submitted blog assignment
352 if ($blogassignment = $DB->get_record_sql("SELECT a.timedue, a.preventlate, a.emailteachers, a.var2, asi.grade, asi.id
353 FROM {assignment} a, {assignment_submissions} as asi WHERE
354 a.id = asi.assignment AND userid = ? AND a.assignmenttype = 'blog'
355 AND asi.data1 = ?", array($USER->id, $this->id))) {
356 //email teachers if necessary
357 if ($blogassignment->emailteachers) {
358 email_teachers($DB->get_record('assignment_submissions', array('id'=>$blogassignment['id'])));
359 }
360
361 } else {
362 //only update the attachment and associations if it is not a submitted assignment
363 if (!empty($CFG->useblogassociations)) {
364 $this->add_associations();
365 }
366 }
367
368 $this->lastmodified = time();
369
370 if (!empty($this->form) && $this->form->get_new_filename('attachment')) {
371 $this->delete_attachments();
372 if ($this->form->save_stored_file('attachment', SYSCONTEXTID, 'blog', $this->id, '/', false, $USER->id)) {
373 $this->attachment = 1;
374 } else {
375 $this->attachment = 1;
376 }
377 }
378
379 // Update record
380 $DB->update_record('post', $this);
381 tag_set('post', $this->id, $this->tags);
382
383 add_to_log(SITEID, 'blog', 'update', 'index.php?userid='.$USER->id.'&entryid='.$this->id, $this->subject);
384 }
385
386 /**
387 * Deletes this entry from the database. Access control checks must be done by calling code.
388 *
389 * @return void
390 */
391 public function delete() {
392 global $DB, $USER;
393
394 $returnurl = '';
395
396 //check to see if it's part of a submitted blog assignment
397 if ($blogassignment = $DB->get_record_sql("SELECT a.timedue, a.preventlate, a.emailteachers, asub.grade
398 FROM {assignment} a, {assignment_submissions} as asub WHERE
399 a.id = asub.assignment AND userid = ? AND a.assignmenttype = 'blog'
400 AND asub.data1 = ?", array($USER->id, $this->id))) {
401 print_error('cantdeleteblogassignment', 'blog', $returnurl);
402 }
403
404 $this->delete_attachments();
405
406 $DB->delete_records('post', array('id' => $this->id));
407 tag_set('post', $this->id, array());
408
409 add_to_log(SITEID, 'blog', 'delete', 'index.php?userid='. $this->userid, 'deleted blog entry with entry id# '. $this->id);
410 }
411
412 /**
413 * function to add all context associations to an entry
414 * @param int entry - data object processed to include all 'entry' fields and extra data from the edit_form object
415 */
416 public function add_associations() {
417 global $DB, $USER;
418
419 $allowaddcourseassoc = true;
420
421 $this->remove_associations();
422
423 if (!empty($this->courseassoc)) {
424 $this->add_association($this->courseassoc);
425 $allowaddcourseassoc = false;
426 }
427
428 if (!empty($this->modassoc)) {
429 foreach ($this->modassoc as $modid) {
430 $this->add_association($modid, $allowaddcourseassoc);
431 $allowaddcourseassoc = false; //let the course be added the first time
432 }
433 }
434 }
435
436 /**
437 * add a single association for a blog entry
438 * @param int contextid - id of context to associate with the blog entry
439 */
440 public function add_association($contextid) {
441 global $DB;
442
443 $assoc_object = new StdClass;
444 $assoc_object->contextid = $contextid;
445 $assoc_object->blogid = $this->id;
446 $DB->insert_record('blog_association', $assoc_object);
447 }
448
449 /**
450 * remove all associations for a blog entry
451 * @return voic
452 */
453 public function remove_associations() {
454 global $DB;
455 $DB->delete_records('blog_association', array('blogid' => $this->id));
456 }
457
458 /**
459 * Deletes all the user files in the attachments area for an entry
460 *
461 * @return void
462 */
463 public function delete_attachments() {
464 $fs = get_file_storage();
465 $fs->delete_area_files(SYSCONTEXTID, 'blog', $this->id);
466 }
467
468 /**
469 * if return=html, then return a html string.
470 * if return=text, then return a text-only string.
471 * otherwise, print HTML for non-images, and return image HTML
472 *
473 * @param bool $return Whether to return or print the generated code
474 * @return void
475 */
476 public function print_attachments($return=false) {
477 global $CFG;
478
479 require_once($CFG->libdir.'/filelib.php');
480
481 $fs = get_file_storage();
482 $browser = get_file_browser();
483
484 $files = $fs->get_area_files(SYSCONTEXTID, 'blog', $this->id);
485
486 $imagereturn = "";
487 $output = "";
488
489 $strattachment = get_string("attachment", "forum");
490
491 foreach ($files as $file) {
492 if ($file->is_directory()) {
493 continue;
494 }
495
496 $filename = $file->get_filename();
497 $ffurl = file_encode_url($CFG->wwwroot.'/pluginfile.php', '/'.SYSCONTEXTID.'/blog/'.$this->id.'/'.$filename);
498 $type = $file->get_mimetype();
499 $icon = mimeinfo_from_type("icon", $type);
500 $type = mimeinfo_from_type("type", $type);
501
502 $image = "<img src=\"$CFG->pixpath/f/$icon\" class=\"icon\" alt=\"\" />";
503
504 if ($return == "html") {
505 $output .= "<a href=\"$ffurl\">$image</a> ";
506 $output .= "<a href=\"$ffurl\">$filename</a><br />";
507
508 } else if ($return == "text") {
509 $output .= "$strattachment $filename:\n$ffurl\n";
510
511 } else {
512 if (in_array($type, array('image/gif', 'image/jpeg', 'image/png'))) { // Image attachments don't get printed as links
513 $imagereturn .= "<br /><img src=\"$ffurl\" alt=\"\" />";
514 } else {
515 $imagereturn .= "<a href=\"$ffurl\">$image</a> ";
516 $imagereturn .= filter_text("<a href=\"$ffurl\">$filename</a><br />");
517 }
518 }
519 }
520
521 if ($return) {
522 return $output;
523 }
524
525 return $imagereturn;
526
527 }
528
529 /**
530 * function to attach tags into an entry
531 * @return void
532 */
533 public function add_tags_info() {
534
535 $tags = array();
536
537 if ($otags = optional_param('otags', '', PARAM_INT)) {
538 foreach ($otags as $tagid) {
539 // TODO : make this use the tag name in the form
540 if ($tag = tag_get('id', $tagid)) {
541 $tags[] = $tag->name;
542 }
543 }
544 }
545
546 tag_set('post', $this->id, $tags);
547 }
548
549 /**
550 * User can edit a blog entry if this is their own blog entry and they have
551 * the capability moodle/blog:create, or if they have the capability
552 * moodle/blog:manageentries.
553 * This also applies to deleting of entries.
554 *
555 * @param int $userid Optional. If not given, $USER is used
556 * @return boolean
557 */
558 public function can_user_edit($userid=null) {
559 global $CFG, $USER;
560
561 if (empty($userid)) {
562 $userid = $USER->id;
563 }
564
565 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
566
567 if (has_capability('moodle/blog:manageentries', $sitecontext)) {
568 return true; // can edit any blog entry
569 }
570
571 if ($this->userid == $userid && has_capability('moodle/blog:create', $sitecontext)) {
572 return true; // can edit own when having blog:create capability
573 }
574
575 return false;
576 }
577
578 /**
579 * Checks to see if a user can view the blogs of another user.
580 * Only blog level is checked here, the capabilities are enforced
581 * in blog/index.php
582 *
583 * @param int $targetuserid ID of the user we are checking
584 *
585 * @return bool
586 */
587 public function can_user_view($targetuserid) {
588 global $CFG, $USER, $DB;
589
590 if (empty($CFG->bloglevel)) {
591 return false; // blog system disabled
592 }
593
594 if (!empty($USER->id) and $USER->id == $targetuserid) {
595 return true; // can view own entries in any case
596 }
597
598 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
599 if (has_capability('moodle/blog:manageentries', $sitecontext)) {
600 return true; // can manage all entries
601 }
602
603 // coming for 1 entry, make sure it's not a draft
604 if ($this->publishstate == 'draft') {
605 return false; // can not view draft of others
606 }
607
608 // coming for 1 entry, make sure user is logged in, if not a public blog
609 if ($this->publishstate != 'public' && !isloggedin()) {
610 return false;
611 }
612
613 switch ($CFG->bloglevel) {
614 case BLOG_GLOBAL_LEVEL:
615 return true;
616 break;
617
618 case BLOG_SITE_LEVEL:
619 if (!empty($USER->id)) { // not logged in viewers forbidden
620 return true;
621 }
622 return false;
623 break;
624
625 case BLOG_USER_LEVEL:
626 default:
627 $personalcontext = get_context_instance(CONTEXT_USER, $targetuserid);
628 return has_capability('moodle/user:readuserblogs', $personalcontext);
629 break;
630 }
631 }
632
633 /**
634 * Use this function to retrieve a list of publish states available for
635 * the currently logged in user.
636 *
637 * @return array This function returns an array ideal for sending to moodles'
638 * choose_from_menu function.
639 */
640
641 public static function get_applicable_publish_states() {
642 global $CFG;
643 $options = array();
644
645 // everyone gets draft access
646 if ($CFG->bloglevel >= BLOG_USER_LEVEL) {
647 $options['draft'] = get_string('publishtonoone', 'blog');
648 }
649
650 if ($CFG->bloglevel > BLOG_USER_LEVEL) {
651 $options['site'] = get_string('publishtosite', 'blog');
652 }
653
654 if ($CFG->bloglevel >= BLOG_GLOBAL_LEVEL) {
655 $options['public'] = get_string('publishtoworld', 'blog');
656 }
657
658 return $options;
659 }
660}
661
662/**
663 * Abstract Blog_Listing class: used to gather blog entries and output them as listings. One of the subclasses must be used.
664 *
665 * @package moodlecore
666 * @subpackage blog
667 * @copyright 2009 Nicolas Connault
668 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
669 */
670class blog_listing {
671 /**
672 * Array of blog_entry objects.
673 * @var array $entries
674 */
675 public $entries = array();
676
677 /**
678 * An array of blog_filter_* objects
679 * @var array $filters
680 */
681 public $filters = array();
682
683 /**
684 * Constructor
685 *
686 * @param array $filters An associative array of filtername => filterid
687 */
688 public function __construct($filters=array()) {
689 // Unset filters overridden by more specific filters
690 foreach ($filters as $type => $id) {
691 if (!empty($type) && !empty($id)) {
692 $this->filters[$type] = blog_filter::get_instance($id, $type);
693 }
694 }
695
696 foreach ($this->filters as $type => $filter) {
697 foreach ($filter->overrides as $override) {
698 if (array_key_exists($override, $this->filters)) {
699 unset($this->filters[$override]);
700 }
701 }
702 }
703 }
704
705 /**
706 * Fetches the array of blog entries.
707 *
708 * @return array
709 */
710 public function get_entries($start=0, $limit=10) {
711 global $DB;
712
713 if (empty($this->entries)) {
714 if ($sql_array = $this->get_entry_fetch_sql()) {
715 $this->entries = $DB->get_records_sql($sql_array['sql'] . " LIMIT $start, $limit", $sql_array['params']);
716 } else {
717 return false;
718 }
719 }
720
721 return $this->entries;
722 }
723
724 public function get_entry_fetch_sql($count=false, $sort='lastmodified DESC', $userid = false) {
725 global $DB, $USER, $CFG;
726
727 if(!$userid) {
728 $userid = $USER->id;
729 }
730
731 // The query used to locate blog entries is complicated. It will be built from the following components:
732 $requiredfields = "p.*, u.firstname, u.lastname, u.email"; // the SELECT clause
733 $tables = array('p' => 'post', 'u' => 'user'); // components of the FROM clause (table_id => table_name)
734 $conditions = array('u.deleted = 0', 'p.userid = u.id', 'p.module = \'blog\''); // components of the WHERE clause (conjunction)
735
736 // build up a clause for permission constraints
737
738 $params = array();
739
740 // fix for MDL-9165, use with readuserblogs capability in a user context can read that user's private blogs
741 // admins can see all blogs regardless of publish states, as described on the help page
742 if (has_capability('moodle/user:readuserblogs', get_context_instance(CONTEXT_SYSTEM))) {
743 // don't add permission constraints
744
745 } else if(!empty($this->filters['user']) && has_capability('moodle/user:readuserblogs',
746 get_context_instance(CONTEXT_USER, (empty($this->filters['user']->id) ? 0 : $this->filters['user']->id)))) {
747 // don't add permission constraints
748
749 } else {
750 if (isloggedin() && !has_capability('moodle/legacy:guest', get_context_instance(CONTEXT_SYSTEM, SITEID), $userid, false)) {
751 $assocexists = $DB->record_exists('blog_association', array()); //dont check association records if there aren't any
752
753 //begin permission sql clause
754 $permissionsql = '(p.userid = ? ';
755 $params[] = $userid;
756
757 if ($CFG->bloglevel >= BLOG_SITE_LEVEL) { // add permission to view site-level entries
758 $permissionsql .= " OR p.publishstate = 'site' ";
759 }
760
761 if ($CFG->bloglevel >= BLOG_GLOBAL_LEVEL) { // add permission to view global entries
762 $permissionsql .= " OR p.publishstate = 'public' ";
763 }
764
765 $permissionsql .= ') '; //close permissions sql clause
766 } else { // default is access to public entries
767 $permissionsql = "p.publishstate = 'public'";
768 }
769 $conditions[] = $permissionsql; //add permission constraints
770 }
771
772 foreach ($this->filters as $type => $blog_filter) {
773 $conditions = array_merge($conditions, $blog_filter->conditions);
774 $params = array_merge($params, $blog_filter->params);
775 $tables = array_merge($tables, $blog_filter->tables);
776 }
777
778 $tablessql = ''; // build up the FROM clause
779 foreach ($tables as $tablename => $table) {
780 $tablessql .= ($tablessql ? ', ' : '').'{'.$table.'} '.$tablename;
781 }
782
783 $sql = ($count) ? 'SELECT COUNT(*)' : 'SELECT ' . $requiredfields;
784 $sql .= " FROM $tablessql WHERE " . implode(' AND ', $conditions);
785 $sql .= ($count) ? '' : " GROUP BY p.id ORDER BY $sort";
786
787 return array('sql' => $sql, 'params' => $params);
788 }
789
790 /**
791 * Outputs all the blog entries aggregated by this blog listing.
792 *
793 * @return void
794 */
795 public function print_entries() {
796 global $CFG, $USER, $DB, $OUTPUT;
797 $sitecontext = get_context_instance(CONTEXT_SYSTEM);
798
799 $page = optional_param('blogpage', 0, PARAM_INT);
800 $limit = optional_param('limit', get_user_preferences('blogpagesize', 10), PARAM_INT);
801 $start = $page * $limit;
802
803 $morelink = '<br />&nbsp;&nbsp;';
804
805 if ($sql_array = $this->get_entry_fetch_sql(true)) {
806 $totalentries = $DB->count_records_sql($sql_array['sql'], $sql_array['params']);
807 } else {
808 $totalentries = 0;
809 }
810
811 $entries = $this->get_entries($start, $limit);
812 $pagingbar = moodle_paging_bar::make($totalentries, $page, $limit, $this->get_baseurl());
813 $pagingbar->pagevar = 'blogpage';
814
815 echo $OUTPUT->paging_bar($pagingbar);
816
817 /* TODO RSS link
818 if ($CFG->enablerssfeeds) {
819 $this->blog_rss_print_link($filtertype, $filterselect, $tag);
820 }
821 */
822
823 if (has_capability('moodle/blog:create', $sitecontext)) {
824 //the user's blog is enabled and they are viewing their own blog
825 $userid = optional_param('userid', null, PARAM_INT);
826
827 if (empty($userid) || (!empty($userid) && $userid == $USER->id)) {
828 $add_url = new moodle_url("$CFG->wwwroot/blog/edit.php");
829 $url_params = array('action' => 'add',
830 'userid' => $userid,
831 'courseid' => optional_param('courseid', null, PARAM_INT),
832 'groupid' => optional_param('groupid', null, PARAM_INT),
833 'modid' => optional_param('modid', null, PARAM_INT),
834 'tagid' => optional_param('tagid', null, PARAM_INT),
835 'tag' => optional_param('tag', null, PARAM_INT),
836 'search' => optional_param('search', null, PARAM_INT));
837
838 foreach ($url_params as $var => $val) {
839 if (empty($val)) {
840 unset($url_params[$var]);
841 }
842 }
843 $add_url->params($url_params);
844
845 $addlink = '<div class="addbloglink">';
846 $addlink .= '<a href="'.$add_url->out().'">'. get_string('addnewentry', 'blog').'</a>';
847 $addlink .= '</div>';
848 echo $addlink;
849 }
850 }
851
852 if ($entries) {
853 $count = 0;
854
855 foreach ($entries as $entry) {
856 $blog_entry = new blog_entry($entry);
857 $blog_entry->print_html();
858 $count++;
859 }
860
861 echo $OUTPUT->paging_bar($pagingbar);
862
863 if (!$count) {
864 print '<br /><div style="text-align:center">'. get_string('noentriesyet', 'blog') .'</div><br />';
865 }
866
867 print $morelink.'<br />'."\n";
868 return;
869 }
870 }
871
872 /// Find the base url from $_GET variables, for print_paging_bar
873 public function get_baseurl() {
874 $getcopy = $_GET;
875
876 unset($getcopy['blogpage']);
877
878 if (!empty($getcopy)) {
879 $first = false;
880 $querystring = '';
881
882 foreach ($getcopy as $var => $val) {
883 if (!$first) {
884 $first = true;
885 $querystring .= "?$var=$val";
886 } else {
887 $querystring .= '&amp;'.$var.'='.$val;
888 $hasparam = true;
889 }
890 }
891 } else {
892 $querystring = '?';
893 }
894
895 return strip_querystring(qualified_me()) . $querystring;
896
897 }
898}
899
900/**
901 * Abstract class for blog_filter objects.
902 * A set of core filters are implemented here. To write new filters, you need to subclass
903 * blog_filter and give it the name of the type you want (for example, blog_filter_entry).
904 * The blog_filter abstract class will automatically use it when the filter is added to the
905 * URL. The first parameter of the constructor is the ID of your filter, but it can be a string
906 * or have any other meaning you wish it to have. The second parameter is called $type and is
907 * used as a sub-type for filters that have a very similar implementation (see blog_filter_context for an example)
908 */
909abstract class blog_filter {
910 /**
911 * An array of strings representing the available filter types for each blog_filter.
912 * @var array $available_types
913 */
914 public $available_types = array();
915
916 /**
917 * The type of filter (for example, types of blog_filter_context are site, course and module)
918 * @var string $type
919 */
920 public $type;
921
922 /**
923 * The unique ID for a filter's associated record
924 * @var int $id
925 */
926 public $id;
927
928 /**
929 * An array of table aliases that are used in the WHERE conditions
930 * @var array $tables
931 */
932 public $tables = array();
933
934 /**
935 * An array of WHERE conditions
936 * @var array $conditions
937 */
938 public $conditions = array();
939
940 /**
941 * An array of SQL params
942 * @var array $params
943 */
944 public $params = array();
945
946 /**
947 * An array of filter types which this particular filter type overrides: their conditions will not be evaluated
948 */
949 public $overrides = array();
950
951 public function __construct($id, $type=null) {
952 $this->id = $id;
953 $this->type = $type;
954 }
955
956 /**
957 * TODO This is poor design. A parent class should not know anything about its children.
958 * The default case helps to resolve this design issue
959 */
960 public static function get_instance($id, $type) {
961
962 switch ($type) {
963 case 'site':
964 case 'course':
965 case 'module':
966 return new blog_filter_context($id, $type);
967 break;
968
969 case 'group':
970 case 'user':
971 return new blog_filter_user($id, $type);
972 break;
973
974 case 'tag':
975 return new blog_filter_tag($id);
976 break;
977
978 default:
979 $class_name = "blog_filter_$type";
980 if (class_exists($class_name)) {
981 return new $class_name($id, $type);
982 }
983 }
984 }
985}
986
987/**
988 * This filter defines the context level of the blog entries being searched: site, course, module
989 */
990class blog_filter_context extends blog_filter {
991 /**
992 * Constructor
993 *
994 * @param string $type
995 * @param int $id
996 */
997 public function __construct($id=null, $type='site') {
998 global $SITE, $CFG, $DB;
999
1000 if (empty($id)) {
1001 $this->type = 'site';
1002 } else {
1003 $this->id = $id;
1004 $this->type = $type;
1005 }
1006
1007 $this->available_types = array('site' => get_string('site'), 'course' => get_string('course'), 'module' => get_string('module'));
1008
1009 switch ($this->type) {
1010 case 'course': // Careful of site course!
1011 // Ignore course filter if blog associations are not enabled
1012 if ($this->id != $SITE->id && !empty($CFG->useblogassociations)) {
1013 $this->overrides = array('site');
1014 $context = get_context_instance(CONTEXT_COURSE, $this->id);
1015 $this->tables['ba'] = 'blog_association';
1016 $this->conditions[] = 'p.id = ba.blogid';
1017 $this->conditions[] = 'ba.contextid = '.$context->id;
1018 break;
1019 } else {
1020 // We are dealing with the site course, do not break from the current case
1021 }
1022
1023 case 'site':
1024 // No special constraints
1025 break;
1026 case 'module':
1027 if (!empty($CFG->useblogassociations)) {
1028 $this->overrides = array('course', 'site');
1029
1030 $context = get_context_instance(CONTEXT_MODULE, $this->id);
1031 $this->tables['ba'] = 'blog_association';
1032 $this->tables['p'] = 'post';
1033 $this->conditions = array('p.id = ba.blogid', 'ba.contextid = ?');
1034 $this->params = array($context->id);
1035 }
1036 break;
1037 }
1038 }
1039}
1040
1041/**
1042 * This filter defines the user level of the blog entries being searched: a userid or a groupid.
1043 * It can be combined with a context filter in order to refine the search.
1044 */
1045class blog_filter_user extends blog_filter {
1046 public $tables = array('u' => 'user');
1047
1048 /**
1049 * Constructor
1050 *
1051 * @param string $type
1052 * @param int $id
1053 */
1054 public function __construct($id=null, $type='user') {
1055 $this->available_types = array('user' => get_string('user'), 'group' => get_string('group'));
1056
1057 if (empty($id)) {
1058 $this->id = $USER->id;
1059 $this->type = 'user';
1060 } else {
1061 $this->id = $id;
1062 $this->type = $type;
1063 }
1064
1065 if ($this->type == 'user') {
1066 $this->conditions = array('u.id = ?');
1067 $this->params = array($this->id);
1068 $this->overrides = array('group');
1069
1070 } elseif ($this->type == 'group') {
1071 $this->overrides = array('course', 'site');
1072
1073 $this->tables['gm'] = 'groups_members';
1074 $this->conditions[] = 'p.userid = gm.userid';
1075 $this->conditions[] = 'gm.groupid = ?';
1076 $this->params[] = $this->id;
1077
1078 if (!empty($CFG->useblogassociations)) { // only show blog entries associated with this course
1079 $course_context = get_context_instance(CONTEXT_COURSE, $DB->get_field('groups', 'courseid', array('id' => $this->id)));
1080 $this->tables['ba'] = 'blog_association';
1081 $this->conditions[] = 'gm.groupid = ?';
1082 $this->conditions[] = 'ba.contextid = ?';
1083 $this->conditions[] = 'ba.blogid = p.id';
1084 $this->params[] = $this->id;
1085 $this->params[] = $course_context->id;
1086 }
1087 }
1088 }
1089}
1090
1091/**
1092 * This filter defines a tag by which blog entries should be searched.
1093 */
1094class blog_filter_tag extends blog_filter {
1095 public $tables = array('t' => 'tag', 'ti' => 'tag_instance', 'p' => 'post');
1096
1097 /**
1098 * Constructor
1099 *
1100 * @return void
1101 */
1102 public function __construct($id) {
1103 global $DB;
1104 $this->id = $id;
1105
1106 $this->conditions = array('ti.tagid = t.id',
1107 "ti.itemtype = 'post'",
1108 'ti.itemid = p.id',
1109 't.id = ?');
1110 $this->params = array($this->id);
1111 }
1112}
1113
1114/**
1115 * This filter defines a specific blog entry id.
1116 */
1117class blog_filter_entry extends blog_filter {
1118 public $conditions = array('p.id = ?');
1119 public $overrides = array('site', 'course', 'module', 'group', 'user', 'tag');
1120
1121 public function __construct($id) {
1122 $this->id = $id;
1123 $this->params[] = $this->id;
1124 }
1125}
1126
1127/**
1128 * Filter used to perform full-text search on an entry's subject, summary and content
1129 */
1130class blog_filter_search extends blog_filter {
1131
1132 public function __construct($search_term) {
1133 global $DB;
1134 $ilike = $DB->sql_ilike();
1135 $this->conditions = array("(p.summary $ilike '%$search_term%' OR
1136 p.content $ilike '%$search_term%' OR
1137 p.subject $ilike '%$search_term%')");
1138 }
1139}