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