Index: branches/lqt-updates/extensions/LiquidThreads/new-schema.sql |
— | — | @@ -141,6 +141,11 @@ |
142 | 142 | lpv_parent_post bigint(10) unsigned null, |
143 | 143 | |
144 | 144 | -- Signature |
| 145 | + lpv_signature TINYBLOB NOT NULL, |
| 146 | + |
| 147 | + -- Attributed date/time |
| 148 | + lpv_post_time varbinary(14) not null, |
| 149 | + |
145 | 150 | lpv_signature TINYBLOB NOT NULL |
146 | 151 | ) /*$wgDBTableOptions*/; |
147 | 152 | |
Index: branches/lqt-updates/extensions/LiquidThreads/LiquidThreads.php |
— | — | @@ -246,6 +246,7 @@ |
247 | 247 | // // Path to the LQT directory |
248 | 248 | // $wgLiquidThreadsExtensionPath = "{$wgScriptPath}/extensions/LiquidThreads"; |
249 | 249 | |
| 250 | +// Data model |
250 | 251 | $wgAutoloadClasses['LiquidThreadsObject'] = "$dir/classes/model/Object.php"; |
251 | 252 | $wgAutoloadClasses['LiquidThreadsVersionedObject'] = "$dir/classes/model/VersionedObject.php"; |
252 | 253 | $wgAutoloadClasses['LiquidThreadsPost'] = "$dir/classes/model/Post.php"; |
— | — | @@ -254,6 +255,12 @@ |
255 | 256 | $wgAutoloadClasses['LiquidThreadsTopicVersion'] = "$dir/classes/model/TopicVersion.php"; |
256 | 257 | $wgAutoloadClasses['LiquidThreadsChannel'] = "$dir/classes/model/Channel.php"; |
257 | 258 | |
| 259 | +// Formatters |
| 260 | +$wgAutoloadClasses['LiquidThreadsFormatter'] = "$dir/classes/view/Formatter.php"; |
| 261 | +$wgAutoloadClasses['LiquidThreadsFormatterContext'] = "$dir/classes/view/Formatter.php"; |
| 262 | +$wgAutoloadClasses['LiquidThreadsPostFormatter'] = "$dir/classes/view/PostFormatter.php"; |
| 263 | +$wgAutoloadClasses['LiquidThreadsPostFormatterContext'] = "$dir/classes/view/PostFormatter.php"; |
| 264 | + |
258 | 265 | /** CONFIGURATION SECTION */ |
259 | 266 | |
260 | 267 | $wgDefaultUserOptions['lqt-watch-threads'] = true; |
Index: branches/lqt-updates/extensions/LiquidThreads/classes/model/PostVersion.php |
— | — | @@ -14,6 +14,9 @@ |
15 | 15 | /** ID of the post that this version applies to **/ |
16 | 16 | protected $postID; |
17 | 17 | |
| 18 | + /** The Post that this version applies to **/ |
| 19 | + protected $post; |
| 20 | + |
18 | 21 | /** User object for the person who created this *VERSION* **/ |
19 | 22 | protected $versionUser; |
20 | 23 | |
— | — | @@ -41,6 +44,9 @@ |
42 | 45 | /** Actual text **/ |
43 | 46 | protected $text = null; |
44 | 47 | |
| 48 | + /** Actual text, parsed to HTML **/ |
| 49 | + protected $contentHTML = null; |
| 50 | + |
45 | 51 | /** Whether or not the text has been modified directly in $text |
46 | 52 | * (and therefore needs to be saved to ES). **/ |
47 | 53 | protected $textDirty = false; |
— | — | @@ -54,6 +60,9 @@ |
55 | 61 | /** The signature attached to this post **/ |
56 | 62 | protected $signature = null; |
57 | 63 | |
| 64 | + /** Attributed timestamp for this post **/ |
| 65 | + protected $postTime = null; |
| 66 | + |
58 | 67 | /* Ancestry information, for not-saved errors */ |
59 | 68 | |
60 | 69 | /** Where this object was instantiated. Used for not-saved errors **/ |
— | — | @@ -121,9 +130,10 @@ |
122 | 131 | /** |
123 | 132 | * Factory method to retrieve a PostVersion from a Post and point in time. |
124 | 133 | * If the point in time is blank, it retrieves the most recent one. |
| 134 | + * Throws an exception on failure. |
125 | 135 | * @param $post LiquidThreadsPost: The Post to retrieve a version for. |
126 | 136 | * @param $timestamp String: A timestamp for the point in time (optional) |
127 | | - * @return LiquidThreadsPostVersion: The version for that post at that point in time, or null. |
| 137 | + * @return LiquidThreadsPostVersion: The version for that post at that point in time. |
128 | 138 | */ |
129 | 139 | public static function newPointInTime( $post, $timestamp = null ) { |
130 | 140 | if ( ! $post instanceof LiquidThreadsPost ) { |
— | — | @@ -139,12 +149,15 @@ |
140 | 150 | $dbr->addQuotes( $dbr->timestamp( $timestamp ) ); |
141 | 151 | } |
142 | 152 | |
143 | | - $row = $dbr->selectRow( 'lqt_post_version', '*', $conds, __METHOD__ ); |
| 153 | + $row = $dbr->selectRow( 'lqt_post_version', '*', $conds, __METHOD__, |
| 154 | + array( 'ORDER BY' => 'lpv_timestamp DESC' ) ); |
144 | 155 | |
145 | 156 | if ( $row ) { |
146 | | - return self::newFromRow( $row ); |
| 157 | + $obj = self::newFromRow( $row ); |
| 158 | + $obj->setPost( $post ); |
| 159 | + return $obj; |
147 | 160 | } else { |
148 | | - return null; |
| 161 | + throw new MWException( "No version available at this point in time" ); |
149 | 162 | } |
150 | 163 | } |
151 | 164 | |
— | — | @@ -247,6 +260,7 @@ |
248 | 261 | $this->comment = $row->lpv_comment; |
249 | 262 | $this->timestamp = wfTimestamp( TS_MW, $row->lpv_timestamp ); |
250 | 263 | $this->postID = $row->lpv_post; |
| 264 | + $this->postTime = $row->lpv_post_time; |
251 | 265 | |
252 | 266 | // Real version data loading |
253 | 267 | $user = null; |
— | — | @@ -325,6 +339,7 @@ |
326 | 340 | $this->textRow = null; |
327 | 341 | $this->textDirty = true; |
328 | 342 | $this->topicID = $topic->getID(); |
| 343 | + $this->signature = ''; |
329 | 344 | |
330 | 345 | if ( $parent ) { |
331 | 346 | $this->parentID = $parent->getID(); |
— | — | @@ -342,6 +357,13 @@ |
343 | 358 | } |
344 | 359 | |
345 | 360 | /** |
| 361 | + * @return true if this is the current version of the post, false otherwise. |
| 362 | + */ |
| 363 | + public function isCurrent() { |
| 364 | + $post = $this->getPost(); |
| 365 | + } |
| 366 | + |
| 367 | + /** |
346 | 368 | * Set the text of this version. |
347 | 369 | * @param $newtext String: The new text for this version. |
348 | 370 | */ |
— | — | @@ -416,6 +438,14 @@ |
417 | 439 | } |
418 | 440 | |
419 | 441 | /** |
| 442 | + * Sets the timestamp attributed to the post in this version. |
| 443 | + * @param $timestamp The new timestamp. |
| 444 | + */ |
| 445 | + public function setPostTime( $timestamp ) { |
| 446 | + $this->postTime = $timestamp; |
| 447 | + } |
| 448 | + |
| 449 | + /** |
420 | 450 | * Saves this Version to the database. |
421 | 451 | * @param $comment String: (optional) The edit comment for this version. |
422 | 452 | */ |
— | — | @@ -436,6 +466,8 @@ |
437 | 467 | 'lpv_timestamp' => $dbw->timestamp( wfTimestampNow() ), |
438 | 468 | 'lpv_comment' => $this->comment, |
439 | 469 | 'lpv_topic' => $this->topicID, |
| 470 | + 'lpv_signature' => $this->signature, |
| 471 | + 'lpv_post_time' => $this->postTime, |
440 | 472 | ); |
441 | 473 | |
442 | 474 | $this->timestamp = $row['lpv_timestamp']; |
— | — | @@ -556,6 +588,39 @@ |
557 | 589 | } |
558 | 590 | |
559 | 591 | /** |
| 592 | + * Gets the parsed HTML for this Post Version's text. |
| 593 | + */ |
| 594 | + public function getContentHTML() { |
| 595 | + if ( !is_null($this->contentHTML) ) { |
| 596 | + return $this->contentHTML; |
| 597 | + } |
| 598 | + |
| 599 | + if ( !$this->isMutable() ) { |
| 600 | + global $wgMemc; |
| 601 | + |
| 602 | + $memcKey = wfMemcKey( 'lqt', 'post-version', $this->getID(), 'html' ); |
| 603 | + |
| 604 | + $html = $wgMemc->get( $memcKey ); |
| 605 | + if ( $html ) { |
| 606 | + $this->contentHTML = $html; |
| 607 | + return $html; |
| 608 | + } |
| 609 | + } |
| 610 | + |
| 611 | + global $wgParser, $wgOut; |
| 612 | + |
| 613 | + $html = $wgOut->parse( $this->getText() ); |
| 614 | + $this->contentHTML = $html; |
| 615 | + |
| 616 | + if ( ! $this->isMutable() ) { |
| 617 | + // 7 day cache |
| 618 | + $wgMemc->set( $memcKey, $html, 86400 * 7 ); |
| 619 | + } |
| 620 | + |
| 621 | + return $html; |
| 622 | + } |
| 623 | + |
| 624 | + /** |
560 | 625 | * Retrieves the ID of the parent topic associated with this Post Version. |
561 | 626 | */ |
562 | 627 | public function getTopicID() { |
— | — | @@ -577,6 +642,33 @@ |
578 | 643 | } |
579 | 644 | |
580 | 645 | /** |
| 646 | + * Retrieves the timestamp attributed to the post in this version. |
| 647 | + */ |
| 648 | + public function getPostTime() { |
| 649 | + return $this->postTime; |
| 650 | + } |
| 651 | + |
| 652 | + /** |
| 653 | + * Gets the ID of the post. |
| 654 | + * @return The Post ID that this version is for. |
| 655 | + */ |
| 656 | + public function getPostID() { |
| 657 | + return $this->postID; |
| 658 | + } |
| 659 | + |
| 660 | + /** |
| 661 | + * Gets the post that this version is for. |
| 662 | + * @return The LiquidThreadsPost that this version applies to. |
| 663 | + */ |
| 664 | + public function getPost() { |
| 665 | + if ( is_null($this->post) ) { |
| 666 | + $this->post = LiquidThreadsPost::newFromID( $this->getPostID() ); |
| 667 | + } |
| 668 | + |
| 669 | + return $this->post; |
| 670 | + } |
| 671 | + |
| 672 | + /** |
581 | 673 | * Lets you set the post ID, once. |
582 | 674 | * Only valid use is from LiquidThreadsPost::save(), for a new LiquidThreadsPost |
583 | 675 | * @param $id Integer: The post ID that this version applies to. |
— | — | @@ -593,4 +685,16 @@ |
594 | 686 | array( 'lpv_id' => $this->getID() ), |
595 | 687 | __METHOD__ ); |
596 | 688 | } |
| 689 | + |
| 690 | + /** |
| 691 | + * Lets you provide the Post object to save loading |
| 692 | + * Validity checking *is* done. |
| 693 | + */ |
| 694 | + public function setPost( $post ) { |
| 695 | + if ( $post->getID() != $this->getPostID() ) { |
| 696 | + throw new MWException( "Invalid argument to ".__METHOD__ ); |
| 697 | + } |
| 698 | + |
| 699 | + $this->post = $post; |
| 700 | + } |
597 | 701 | } |
Index: branches/lqt-updates/extensions/LiquidThreads/classes/model/Post.php |
— | — | @@ -25,6 +25,9 @@ |
26 | 26 | /** The LiquidThreadsPost that this is found underneath. **/ |
27 | 27 | protected $parent; |
28 | 28 | |
| 29 | + /** Replies **/ |
| 30 | + protected $replies = null; |
| 31 | + |
29 | 32 | /* FACTORY METHODS */ |
30 | 33 | |
31 | 34 | /** |
— | — | @@ -427,5 +430,24 @@ |
428 | 431 | public function setSignature( $sig ) { |
429 | 432 | $this->getPendingVersion()->setSignature( $sig ); |
430 | 433 | } |
| 434 | + |
| 435 | + /** |
| 436 | + * Gets the timestamp attributed to this post. |
| 437 | + */ |
| 438 | + public function getPostTime() { |
| 439 | + return wfTimestamp( TS_MW, $this->getCurrentVersion()->getPostTime() ); |
| 440 | + } |
| 441 | + |
| 442 | + /** |
| 443 | + * Gets replies to this post. |
| 444 | + */ |
| 445 | + public function getReplies() { |
| 446 | + if ( is_null( $this->replies ) ) { |
| 447 | + $conditions = array( 'lqp_parent_post' => $this->getID() ); |
| 448 | + $this->replies = self::loadFromConditions( $conditions, true ); |
| 449 | + } |
| 450 | + |
| 451 | + return $this->replies; |
| 452 | + } |
431 | 453 | } |
432 | 454 | |
Index: branches/lqt-updates/extensions/LiquidThreads/classes/view/PostFormatter.php |
— | — | @@ -0,0 +1,455 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +class LiquidThreadsPostFormatter extends LiquidThreadsFormatter { |
| 5 | + /** |
| 6 | + * Returns the correct class for the context object |
| 7 | + */ |
| 8 | + public function getContextClass() { |
| 9 | + return 'LiquidThreadsPostFormatterContext'; |
| 10 | + } |
| 11 | + |
| 12 | + /** |
| 13 | + * Returns the class that can be formatted by this object. |
| 14 | + */ |
| 15 | + public function getObjectClass() { |
| 16 | + return 'LiquidThreadsPost'; |
| 17 | + } |
| 18 | + |
| 19 | + /** |
| 20 | + * Convert a LiquidThreadsPost to HTML. |
| 21 | + * @param $object The LiquidThreadsPost to convert. |
| 22 | + * @param $context LiquidThreadsPostFormatterContext object. |
| 23 | + */ |
| 24 | + public function getHTML( $object, $context = null ) |
| 25 | + { |
| 26 | + // Error checking |
| 27 | + $this->checkArguments( $object, $context ); |
| 28 | + |
| 29 | + if ( ! $context->get('language') ) { |
| 30 | + global $wgLang; |
| 31 | + $context->set( 'language', $wgLang ); |
| 32 | + } |
| 33 | + |
| 34 | + if ( ! $context->get('user') ) { |
| 35 | + global $wgUser; |
| 36 | + $context->set('user', $wgUser ); |
| 37 | + } |
| 38 | + |
| 39 | + if ( ! $context->get('nesting-level') ) { |
| 40 | + $context->set('nesting-level', 0); |
| 41 | + } |
| 42 | + |
| 43 | + $timestamp = $context->get('timestamp'); |
| 44 | + if ( !is_null($timestamp) ) { |
| 45 | + $version = PostVersion::newPointInTime( $post, $timestamp ); |
| 46 | + } else { |
| 47 | + $version = $object->getCurrentVersion(); |
| 48 | + } |
| 49 | + |
| 50 | + if ( $context->get('single') ) { |
| 51 | + return $this->formatSingleComment( $object, $version, $context ); |
| 52 | + } else { |
| 53 | + return $this->formatCommentTree( $object, $version, $context ); |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * Get the HTML for a full tree of comments. |
| 59 | + * @param $object The LiquidThreadsPost to show. |
| 60 | + * @param $version The LiquidThreadsPostVersion at which to show $object |
| 61 | + * @param $context The LiquidThreadsPostFormatterContext to use for context. |
| 62 | + * @return String: HTML result. |
| 63 | + */ |
| 64 | + protected function formatCommentTree( $object, $version, $context ) { |
| 65 | + $context->increment('nesting-level'); |
| 66 | + |
| 67 | + $wrapperClasses = array('lqt-post-tree-wrapper'); |
| 68 | + |
| 69 | + // FIXME shows children at all times, should only show appropriate children for the time. |
| 70 | + $children = $object->getReplies(); |
| 71 | + |
| 72 | + if ( count($children) ) { |
| 73 | + $wrapperClasses[] = 'lqt-post-with-replies'; |
| 74 | + } else { |
| 75 | + $wrapperClasses[] = 'lqt-post-without-replies'; |
| 76 | + } |
| 77 | + |
| 78 | + $html = Xml::openElement( 'div', |
| 79 | + array( |
| 80 | + 'class' => implode(' ', $wrapperClasses ), |
| 81 | + 'id' => "lqt-post-id-".$object->getID(), |
| 82 | + ) ); |
| 83 | + $html .= Xml::element( 'a', array('name' => $this->getAnchor($object) ) ); |
| 84 | + |
| 85 | + $html .= $this->formatSingleComment( $object, $version, $context ); |
| 86 | + |
| 87 | + if ( count($children) ) { |
| 88 | + $html .= Xml::openElement( 'div', array( 'class' => 'lqt-replies' ) ); |
| 89 | + foreach( $children as $child ) { |
| 90 | + $html .= $this->formatSingleComment( $child ); |
| 91 | + } |
| 92 | + $html .= Xml::closeElement( 'div' ); |
| 93 | + } |
| 94 | + |
| 95 | + $context->decrement('nesting-level'); |
| 96 | + |
| 97 | + return $html; |
| 98 | + } |
| 99 | + |
| 100 | + /** |
| 101 | + * Get the HTML for a *single comment*. |
| 102 | + * This is basically the guts of this formatter, without tree handling etc. |
| 103 | + * @param $object The LiquidThreadsPost to show. |
| 104 | + * @param $version The LiquidThreadsPostVersion at which to show $object |
| 105 | + * @param $context The LiquidThreadsPostFormatterContext to use for context. |
| 106 | + * @return String: HTML result. |
| 107 | + */ |
| 108 | + protected function formatSingleComment( $object, $version, $context ) { |
| 109 | + |
| 110 | + $divClass = $this->getDivClass( $object, $context ); |
| 111 | + |
| 112 | + $html = ''; |
| 113 | + |
| 114 | + $showAnything = wfRunHooks( 'LiquidThreadsShowPostBody', |
| 115 | + array( $object, $context, &$html ) ); |
| 116 | + |
| 117 | + if ( $showAnything && $this->runCallback( $object, $context ) ) { |
| 118 | + $html .= Xml::openElement( 'div', array( 'class' => $divClass ) ); |
| 119 | + |
| 120 | + $text = $version->getContentHTML(); |
| 121 | + |
| 122 | + $html .= $text; |
| 123 | + |
| 124 | + $html .= Xml::closeElement( 'div' ); |
| 125 | + |
| 126 | + $html .= $this->getToolbar( $object, $version, $context ); |
| 127 | + $html .= $this->getPostSignature( $object, $version, $context ); |
| 128 | + $html .= Xml::closeElement( 'div' ); |
| 129 | + } |
| 130 | + |
| 131 | + $html = Xml::tags( 'div', array( 'class' => 'lqt-post-wrapper' ), $html ); |
| 132 | + |
| 133 | + return $html; |
| 134 | + } |
| 135 | + |
| 136 | + /** |
| 137 | + * Get the class for the div surrounding the comment *text*. |
| 138 | + * @param $object The LiquidThreadsPost object |
| 139 | + * @param $context The LiquidThreadsPostFormatterContext object. |
| 140 | + * @return String: Space-separated list of classes. |
| 141 | + */ |
| 142 | + protected function getDivClass( $object, $context ) { |
| 143 | + $classes = array('lqt-post-content'); |
| 144 | + |
| 145 | + $nesting_level = $context->get('nesting-level'); |
| 146 | + if ( !is_null( $nesting_level ) ) { |
| 147 | + $classes[] = 'lqt-post-nest-' . $nesting_level; |
| 148 | + $alternatingType = ( $nesting_level % 2 ) ? 'odd' : 'even'; |
| 149 | + $classes[] = "lqt-post-$alternatingType"; |
| 150 | + } |
| 151 | + |
| 152 | + return implode( ' ', $classes ); |
| 153 | + } |
| 154 | + |
| 155 | + /** |
| 156 | + * Generates a toolbar for a post. |
| 157 | + * @param $post The LiquidThreadsPost being shown. |
| 158 | + * @param $version The LiquidThreadsPostVersion to show the dropdown for. |
| 159 | + * @param $context A LiquidThreadsPostFormatterContext object. |
| 160 | + * @return HTML |
| 161 | + */ |
| 162 | + protected function getToolbar( $post, $version, $context ) { |
| 163 | + $html = ''; |
| 164 | + |
| 165 | + $headerParts = array(); |
| 166 | + |
| 167 | + foreach ( $this->getMajorCommands( $post, $version, $context ) as $key => $cmd ) { |
| 168 | + $content = $this->formatCommand( $cmd, false /* No icon divs */ ); |
| 169 | + $headerParts[] = Xml::tags( 'li', |
| 170 | + array( 'class' => "lqt-command lqt-command-$key" ), |
| 171 | + $content ); |
| 172 | + } |
| 173 | + |
| 174 | + // Drop-down menu |
| 175 | + $commands = $this->getMinorCommands( $post, $version, $context ); |
| 176 | + $menuHTML = Xml::tags( 'ul', array( 'class' => 'lqt-thread-toolbar-command-list' ), |
| 177 | + $this->formatCommands( $commands ) ); |
| 178 | + |
| 179 | + $triggerText = Xml::tags( 'a', array( 'class' => 'lqt-thread-actions-icon', |
| 180 | + 'href' => '#' ), |
| 181 | + wfMsgHTML( 'lqt-menu-trigger' ) ); |
| 182 | + $dropDownTrigger = Xml::tags( 'div', |
| 183 | + array( 'class' => 'lqt-thread-actions-trigger ' . |
| 184 | + 'lqt-command-icon', 'style' => 'display: none;' ), |
| 185 | + $triggerText ); |
| 186 | + |
| 187 | + if ( count( $commands ) ) { |
| 188 | + $headerParts[] = Xml::tags( 'li', |
| 189 | + array( 'class' => 'lqt-thread-toolbar-menu' ), |
| 190 | + $dropDownTrigger ); |
| 191 | + } |
| 192 | + |
| 193 | + $html .= implode( ' ', $headerParts ); |
| 194 | + |
| 195 | + $html = Xml::tags( 'ul', array( 'class' => 'lqt-thread-toolbar-commands' ), $html ); |
| 196 | + |
| 197 | + $html = Xml::tags( 'div', array( 'class' => 'lqt-thread-toolbar' ), $html ) . |
| 198 | + $menuHTML; |
| 199 | + |
| 200 | + return $html; |
| 201 | + } |
| 202 | + |
| 203 | + /** |
| 204 | + * Formats a list of toolbar commands. |
| 205 | + * @param $commands Associative array of commands. |
| 206 | + * @return HTML |
| 207 | + * @see LiquidThreadsPostFormatter::formatCommand |
| 208 | + */ |
| 209 | + function formatCommands( $commands ) { |
| 210 | + $result = array(); |
| 211 | + foreach ( $commands as $key => $command ) { |
| 212 | + $thisCommand = $this->formatCommand( $command ); |
| 213 | + |
| 214 | + $thisCommand = Xml::tags( |
| 215 | + 'li', |
| 216 | + array( 'class' => 'lqt-command lqt-command-' . $key ), |
| 217 | + $thisCommand |
| 218 | + ); |
| 219 | + |
| 220 | + $result[] = $thisCommand; |
| 221 | + } |
| 222 | + return join( ' ', $result ); |
| 223 | + } |
| 224 | + |
| 225 | + /** |
| 226 | + * Formats a toolbar command |
| 227 | + * @param $command Associative array describing this command |
| 228 | + * Valid keys: |
| 229 | + * label: The text to show for this command. |
| 230 | + * href: The URL to link to. |
| 231 | + * enabled: Whether or not this command is enabled. |
| 232 | + * tooltip: If specified, the tooltip to show for this command. |
| 233 | + * icon: If specified, an icon is shown. |
| 234 | + * showlabel: Whether or not to show the label. Default: on. |
| 235 | + * @param $icon_divs Boolean: If false, do not insert <divs> to style with an icon. |
| 236 | + * @return HTML: Command formatted in a <div> |
| 237 | + */ |
| 238 | + function formatCommand( $command, $icon_divs = true ) { |
| 239 | + $label = $command['label']; |
| 240 | + $href = $command['href']; |
| 241 | + $enabled = $command['enabled']; |
| 242 | + $tooltip = isset( $command['tooltip'] ) ? $command['tooltip'] : ''; |
| 243 | + |
| 244 | + if ( isset( $command['icon'] ) ) { |
| 245 | + $icon = Xml::tags( 'div', array( 'title' => $label, |
| 246 | + 'class' => 'lqt-command-icon' ), ' ' ); |
| 247 | + if ( $icon_divs ) { |
| 248 | + if ( !empty( $command['showlabel'] ) ) { |
| 249 | + $label = $icon . ' ' . $label; |
| 250 | + } else { |
| 251 | + $label = $icon; |
| 252 | + } |
| 253 | + } else { |
| 254 | + if ( empty( $command['showlabel'] ) ) { |
| 255 | + $label = ''; |
| 256 | + } |
| 257 | + } |
| 258 | + } |
| 259 | + |
| 260 | + if ( $enabled ) { |
| 261 | + $thisCommand = Xml::tags( 'a', array( 'href' => $href, 'title' => $tooltip ), |
| 262 | + $label ); |
| 263 | + } else { |
| 264 | + $thisCommand = Xml::tags( 'span', array( 'class' => 'lqt_command_disabled', |
| 265 | + 'title' => $tooltip ), $label ); |
| 266 | + } |
| 267 | + |
| 268 | + return $thisCommand; |
| 269 | + } |
| 270 | + |
| 271 | + /** |
| 272 | + * Gets the commands to show in the dropdown of a post. |
| 273 | + * @param $post The LiquidThreadsPost to show a dropdown for. |
| 274 | + * @param $version The LiquidThreadsPostVersion to show the dropdown for. |
| 275 | + * @param $context A LiquidThreadsPostFormatterContext object. |
| 276 | + * @return An associative array of arguments suitable for |
| 277 | + * LiquidThreadsPostFormatter::formatCommand |
| 278 | + * @see LiquidThreadsPostFormatter::formatCommand |
| 279 | + */ |
| 280 | + function getMinorCommands( $post, $version, $context ) { |
| 281 | + $commands = array(); |
| 282 | + |
| 283 | + // TODO make this link operate properly |
| 284 | + $history_url = SpecialPage::getTitleFor( 'PostHistory', $post->getID() ); |
| 285 | + $commands['history'] = array( |
| 286 | + 'label' => wfMsgExt( 'history_short', 'parseinline' ), |
| 287 | + 'href' => $history_url, |
| 288 | + 'enabled' => true, |
| 289 | + ); |
| 290 | + |
| 291 | + // TODO permissions checking |
| 292 | + $edit_url = SpecialPage::getTitleFor( 'EditPost', $post->getID() ); |
| 293 | + $commands['edit'] = array( |
| 294 | + 'label' => wfMsgExt( 'edit', 'parseinline' ), |
| 295 | + 'href' => $edit_url, |
| 296 | + 'enabled' => true |
| 297 | + ); |
| 298 | + |
| 299 | + if ( $context->get('user')->isAllowed( 'lqt-split' ) ) { |
| 300 | + $splitUrl = SpecialPage::getTitleFor( 'SplitThread', $post->getID() )->getFullURL(); |
| 301 | + $commands['split'] = array( |
| 302 | + 'label' => wfMsgExt( 'lqt-thread-split', 'parseinline' ), |
| 303 | + 'href' => $splitUrl, |
| 304 | + 'enabled' => true |
| 305 | + ); |
| 306 | + } |
| 307 | + |
| 308 | + // TODO implement merging |
| 309 | +// if ( $context->get('user')->isAllowed( 'lqt-merge' ) ) { |
| 310 | +// $mergeParams = $_GET; |
| 311 | +// $mergeParams['lqt_merge_from'] = $thread->id(); |
| 312 | +// |
| 313 | +// unset( $mergeParams['title'] ); |
| 314 | +// |
| 315 | +// $mergeUrl = $this->title->getLocalURL( wfArrayToCGI( $mergeParams ) ); |
| 316 | +// $label = wfMsgExt( 'lqt-thread-merge', 'parseinline' ); |
| 317 | +// |
| 318 | +// $commands['merge'] = array( |
| 319 | +// 'label' => $label, |
| 320 | +// 'href' => $mergeUrl, |
| 321 | +// 'enabled' => true |
| 322 | +// ); |
| 323 | +// } |
| 324 | + |
| 325 | + $commands['link'] = array( |
| 326 | + 'label' => wfMsgExt( 'lqt_permalink', 'parseinline' ), |
| 327 | + 'href' => SpecialPage::getTitleFor( 'Post', $post->getID() ), |
| 328 | + 'enabled' => true, |
| 329 | + 'showlabel' => true, |
| 330 | + 'tooltip' => wfMsgExt( 'lqt_permalink', 'parseinline' ) |
| 331 | + ); |
| 332 | + |
| 333 | + wfRunHooks( 'LiquidThreadsPostMinorCommands', |
| 334 | + array( $post, $version, $context, &$commands ) ); |
| 335 | + |
| 336 | + return $commands; |
| 337 | + } |
| 338 | + |
| 339 | + /** |
| 340 | + * Gets the main commands in the main (non-dropdown) part of the toolbar. |
| 341 | + * @param $post The LiquidThreadsPost to show a toolbar for. |
| 342 | + * @param $version The LiquidThreadsPostVersion to show the toolbar for. |
| 343 | + * @param $context A LiquidThreadsPostFormatterContext object. |
| 344 | + * @return An associative array of arguments suitable for |
| 345 | + * LiquidThreadsPostFormatter::formatCommand |
| 346 | + * @see LiquidThreadsPostFormatter::formatCommand |
| 347 | + */ |
| 348 | + function getMajorCommands( $post, $version, $context ) { |
| 349 | + $commands = array(); |
| 350 | + |
| 351 | +// if ( $this->user->isAllowed( 'lqt-merge' ) && |
| 352 | +// $this->request->getCheck( 'lqt_merge_from' ) ) { |
| 353 | +// $srcThread = Threads::withId( $this->request->getVal( 'lqt_merge_from' ) ); |
| 354 | +// $par = $srcThread->title()->getPrefixedText(); |
| 355 | +// $mergeTitle = SpecialPage::getTitleFor( 'MergeThread', $par ); |
| 356 | +// $mergeUrl = $mergeTitle->getLocalURL( 'dest=' . $thread->id() ); |
| 357 | +// $label = wfMsgExt( 'lqt-thread-merge-to', 'parseinline' ); |
| 358 | +// |
| 359 | +// $commands['merge-to'] = array( |
| 360 | +// 'label' => $label, 'href' => $mergeUrl, |
| 361 | +// 'enabled' => true, |
| 362 | +// 'tooltip' => $label |
| 363 | +// ); |
| 364 | +// } |
| 365 | + |
| 366 | + // TODO permissions checking, proper URL |
| 367 | + $commands['reply'] = array( |
| 368 | + 'label' => wfMsgExt( 'lqt_reply', 'parseinline' ), |
| 369 | + 'href' => SpecialPage::getTitleFor('Reply', $post->getID() )->getFullURL(), |
| 370 | + 'enabled' => true, |
| 371 | + 'showlabel' => 1, |
| 372 | + 'tooltip' => wfMsg( 'lqt_reply' ), |
| 373 | + 'icon' => 'reply.png', |
| 374 | + ); |
| 375 | + |
| 376 | + // Parent post link |
| 377 | + if ( $version->getParentID() ) { |
| 378 | + $parentID = $version->getParentID(); |
| 379 | + |
| 380 | + $commands['parent'] = array( |
| 381 | + 'label' => wfMsgExt( 'lqt-parent', 'parseinline' ), |
| 382 | + 'href' => '#' . $this->getAnchor($parentID), |
| 383 | + 'enabled' => true, |
| 384 | + 'showlabel' => 1, |
| 385 | + ); |
| 386 | + } |
| 387 | + |
| 388 | + wfRunHooks( 'LiquidThreadsPostMajorCommands', |
| 389 | + array( $post, $version, $context, &$commands ) ); |
| 390 | + |
| 391 | + return $commands; |
| 392 | + } |
| 393 | + |
| 394 | + function getPostSignature( $post, $version, $context ) { |
| 395 | + $lang = $context->get('language'); |
| 396 | + |
| 397 | + $signature = $version->getSignature(); |
| 398 | + $signature = self::parseSignature( $signature ); |
| 399 | + |
| 400 | + $signature = Xml::tags( 'span', |
| 401 | + array( 'class' => 'lqt-thread-user-signature' ), |
| 402 | + $signature ); |
| 403 | + |
| 404 | + $timestamp = $lang->timeanddate( $version->getPostTime(), true ); |
| 405 | + $signature .= Xml::element( 'span', |
| 406 | + array( 'class' => 'lqt-thread-toolbar-timestamp' ), |
| 407 | + $timestamp ); |
| 408 | + |
| 409 | + wfRunHooks( 'LiquidThreadsPostSignature', array( $post, $version, &$signature ) ); |
| 410 | + |
| 411 | + $signature = Xml::tags( 'div', array( 'class' => 'lqt-thread-signature' ), |
| 412 | + $signature ); |
| 413 | + |
| 414 | + return $signature; |
| 415 | + } |
| 416 | + |
| 417 | + static function parseSignature( $sig ) { |
| 418 | + global $wgParser, $wgOut; |
| 419 | + |
| 420 | + static $parseCache = array(); |
| 421 | + $sigKey = md5( $sig ); |
| 422 | + |
| 423 | + if ( isset( $parseCache[$sigKey] ) ) { |
| 424 | + return $parseCache[$sigKey]; |
| 425 | + } |
| 426 | + |
| 427 | + $sig = $wgOut->parseInline( $sig ); |
| 428 | + |
| 429 | + $parseCache[$sigKey] = $sig; |
| 430 | + |
| 431 | + return $sig; |
| 432 | + } |
| 433 | +} |
| 434 | + |
| 435 | +/** |
| 436 | + * This class provides context for formatting a LiquidThreadsPost object. |
| 437 | + * Valid fields are: |
| 438 | + * timestamp: The point in time at which to show this LiquidThreadsPost. |
| 439 | + * user: The User object for which to show this LiquidThreadsPost. |
| 440 | + * single: If this is set, we will only show that comment and nothing else. |
| 441 | + * language: Override the Language object that the thread is shown in. |
| 442 | + * nesting-level: If set, specifies the nesting level. Outermost post should be 0. |
| 443 | + * post-callbacks: If set, an associative array of callbacks used to replace display of |
| 444 | + * individual posts. Key is the post ID, value is a callback. Used for |
| 445 | + * edit forms and the like. |
| 446 | + */ |
| 447 | +class LiquidThreadsPostFormatterContext extends LiquidThreadsFormatterContext { |
| 448 | + protected $validFields = array( |
| 449 | + 'timestamp', |
| 450 | + 'user', |
| 451 | + 'single', |
| 452 | + 'language', |
| 453 | + 'nesting-level', |
| 454 | + 'post-callbacks', |
| 455 | + ); |
| 456 | +} |
Index: branches/lqt-updates/extensions/LiquidThreads/classes/view/Formatter.php |
— | — | @@ -0,0 +1,160 @@ |
| 2 | +<?php |
| 3 | + |
| 4 | +/** |
| 5 | + * This is the abstract base class for a LiquidThreads "Formatter". |
| 6 | + * A formatter takes a LiquidThreads object and converts it to HTML for display. |
| 7 | + */ |
| 8 | +abstract class LiquidThreadsFormatter { |
| 9 | + |
| 10 | + /** |
| 11 | + * Convert an object to HTML. |
| 12 | + * @param $object Object: The object to convert. |
| 13 | + * @param $context LiquidThreadsFormatterContext object appropriate to this class. |
| 14 | + * See subclass documentation for valid fields. |
| 15 | + * @return String: HTML representing this object, for display to users. |
| 16 | + */ |
| 17 | + abstract public function getHTML( $object, $context = null ); |
| 18 | + |
| 19 | + /** |
| 20 | + * Checks arguments to getHTML. Throws an exception if there is a problem. |
| 21 | + * @param $object The object being formatted. |
| 22 | + * @param $context The context object. |
| 23 | + */ |
| 24 | + protected function checkArguments( &$object, &$context ) { |
| 25 | + if ( $context == null ) { |
| 26 | + $contextClass = $this->getContextClass(); |
| 27 | + $context = new $contextClass; |
| 28 | + } elseif ( ! is_a( $context, $this->getContextClass() ) ) { |
| 29 | + throw new MWException( "Invalid context argument" ); |
| 30 | + } |
| 31 | + |
| 32 | + if ( ! is_a( $object, $this->getObjectClass() ) ) { |
| 33 | + throw new MWException( "Invalid object for this formatter" ); |
| 34 | + } |
| 35 | + } |
| 36 | + |
| 37 | + /** |
| 38 | + * Runs the callback hooks in the $context object. |
| 39 | + * @param $object The LiquidThreadsObject to run hooks for. |
| 40 | + * @param $contest The LiquidThreadsFormatterContext object. |
| 41 | + * @return Similar to a MediaWiki hook: |
| 42 | + * true if no callback is run and the method should continue as usual. |
| 43 | + * false if a callback was run and no further processing is necessary. |
| 44 | + */ |
| 45 | + protected function runCallback( $object, $context ) { |
| 46 | + $callbacks = $context->get('post-callbacks'); |
| 47 | + |
| 48 | + if ( !is_array($callbacks) ) { |
| 49 | + return true; |
| 50 | + } |
| 51 | + |
| 52 | + if ( isset( $callbacks[$object->getID()] ) ) { |
| 53 | + $callback = $callbacks[$object->getID()]; |
| 54 | + call_user_func_array( $callback, array( $object, $context ) ); |
| 55 | + return false; |
| 56 | + } else { |
| 57 | + return true; |
| 58 | + } |
| 59 | + } |
| 60 | + |
| 61 | + /** |
| 62 | + * Returns the correct class for the context object |
| 63 | + */ |
| 64 | + abstract public function getContextClass(); |
| 65 | + |
| 66 | + /** |
| 67 | + * Returns the class that can be formatted by this object. |
| 68 | + */ |
| 69 | + abstract public function getObjectClass(); |
| 70 | + |
| 71 | + /** |
| 72 | + * Returns whether or not this class can format the given object. |
| 73 | + */ |
| 74 | + public function canFormat( $object ) { |
| 75 | + return is_a( $object, $this->getObjectClass() ); |
| 76 | + } |
| 77 | + |
| 78 | + /** |
| 79 | + * Generates an anchor name for a LiquidThreads object |
| 80 | + * @param $object Mixed: A LiquidThreads object, or an integer. |
| 81 | + * @param $type Mixed: If specified, allows $object to be an integer. |
| 82 | + * Valid values: |
| 83 | + * post: $object is a post ID |
| 84 | + * topic: $object is a topic ID |
| 85 | + * @return String The anchor name, without the # |
| 86 | + */ |
| 87 | + public function getAnchor( $object, $type = null ) { |
| 88 | + if ( $object instanceof LiquidThreadsPost ) { |
| 89 | + return 'comment_'.$object->getID(); |
| 90 | + } elseif ( $type == 'post' ) { |
| 91 | + return 'comment_'.$object; |
| 92 | + } elseif ( $object instanceof LiquidThreadsTopic ) { |
| 93 | + return 'topic_'.$object->getID(); |
| 94 | + } elseif ( $type == 'topic' ) { |
| 95 | + return "topic_$object"; |
| 96 | + } else { |
| 97 | + throw new MWException( "Unknown arguments to ".__METHOD__ ); |
| 98 | + } |
| 99 | + } |
| 100 | +} |
| 101 | + |
| 102 | +abstract class LiquidThreadsFormatterContext { |
| 103 | + protected $data; |
| 104 | + protected $requiredFields = array(); |
| 105 | + protected $validFields = array(); |
| 106 | + |
| 107 | + /** |
| 108 | + * Returns true if the field name is valid for this context class. |
| 109 | + * @param $field The name of the field. |
| 110 | + * @return Boolean: True if the field name is valid, false otherwise. |
| 111 | + */ |
| 112 | + protected function isValidField( $field ) { |
| 113 | + return in_array( $field, $this->validFields ); |
| 114 | + } |
| 115 | + |
| 116 | + /** |
| 117 | + * Returns the value of a field, or null if it is not set. |
| 118 | + * @param $field String: The field to retrieve. |
| 119 | + * @return Mixed: The value of the field. |
| 120 | + */ |
| 121 | + public function get( $field ) { |
| 122 | + if ( ! $this->isValidField( $field ) ) { |
| 123 | + throw new MWException( "Attempt to retrieve invalid item" ); |
| 124 | + } |
| 125 | + |
| 126 | + if ( isset( $this->data[$field] ) ) { |
| 127 | + return $this->data[$field]; |
| 128 | + } else { |
| 129 | + return null; |
| 130 | + } |
| 131 | + } |
| 132 | + |
| 133 | + /** |
| 134 | + * Sets the value of a field. |
| 135 | + * @param $field String: The field to set. |
| 136 | + * @param $value Mixed: The new value for the field. |
| 137 | + */ |
| 138 | + public function set( $field, $value ) { |
| 139 | + if ( ! $this->isValidField( $field ) ) { |
| 140 | + throw new MWException( "Attempt to set invalid item" ); |
| 141 | + } |
| 142 | + |
| 143 | + $this->data[$field] = $value; |
| 144 | + } |
| 145 | + |
| 146 | + /** |
| 147 | + * Increments a field. |
| 148 | + * @param $field String: The field to increment. |
| 149 | + */ |
| 150 | + public function increment( $field ) { |
| 151 | + $this->set( $field, $this->get($field) + 1); |
| 152 | + } |
| 153 | + |
| 154 | + /** |
| 155 | + * Decrements a field. |
| 156 | + * @param $field String: The field to decrement. |
| 157 | + */ |
| 158 | + public function decrement( $field ) { |
| 159 | + $this->set( $field, $this->get($field) - 1); |
| 160 | + } |
| 161 | +} |