Please upgrade here. These earlier versions are no longer being updated and have security issues.
HackerOne users: Testing against this community violates our program's Terms of Service and will result in your bounty being denied.

File Upload Count Per Discussion

Hi,

I'd like to be able to add the number of Files that have been uploaded to a Discussion (including those added to Comments) to the Meta div below a Discussion and to the the side menu if possible. Is this possible? Has anyone done this? I'm new to VanillaForums and getting used to the templating system.

If you need it, I'm running Vanilla 2.3.

Thank you in Advance!

Tagged:

Comments

  • vrijvlindervrijvlinder Papillon-Sauvage MVP

    The new Advanced editor that comes with vanilla works much better , no need for an uploader plugin.

  • R_JR_J Ex-Fanboy Munich Admin

    Good news is, that you can do anything with Vanilla. The bad news are, that you have to write some code on your own. Do you have knowledge in any programming language? If yes, I would say this shouldn't be a big thing to do. If not it would be a little bit harder.

    I could try to assist if you are willing to work and learn. My time is limited right now, so there might be times when you have to wait a little bit longer for a reply.

    If you want to start that would be your first task: look at the example plugin and try to understand what you see there. Create a copy of the example plugin and play around with it.

  • vrijvlindervrijvlinder Papillon-Sauvage MVP

    @R_J said:
    Good news is, that you can do anything with Vanilla. The bad news are, that you have to write some code on your own. Do you have knowledge in any programming language? If yes, I would say this shouldn't be a big thing to do. If not it would be a little bit harder.

    I could try to assist if you are willing to work and learn. My time is limited right now, so there might be times when you have to wait a little bit longer for a reply.

    If you want to start that would be your first task: look at the example plugin and try to understand what you see there. Create a copy of the example plugin and play around with it.

    Yes, that is nice, but he is asking about the Uploader plugin, my plugin. And I suggested he uses the new uploader that comes with vanilla which is superior to my plugin. So he should start with using that one instead of mine, which is limited and outdated...

  • R_JR_J Ex-Fanboy Munich Admin

    @vrijvlinder said:

    @R_J said:
    Good news is, that you can do anything with Vanilla. The bad news are, that you have to write some code on your own. Do you have knowledge in any programming language? If yes, I would say this shouldn't be a big thing to do. If not it would be a little bit harder.

    I could try to assist if you are willing to work and learn. My time is limited right now, so there might be times when you have to wait a little bit longer for a reply.

    If you want to start that would be your first task: look at the example plugin and try to understand what you see there. Create a copy of the example plugin and play around with it.

    Yes, that is nice

    Well, yes, thanks. I think so too, :blush:

    @vrijvlinder said:
    but he is asking about the Uploader plugin, my plugin.

    Obviously he asked a question referencing this plugin, no doubt about that!

    But if I just ignore for a second that there are other plugins for that task and I start focusing on an answer to the question how to show the number of attachments per discussion, I would think of

    • using the AfterSave events of discussion and comment model to
    • check if there is an attachment there and
    • use discussions Attributes column to keep track of the number and
    • would recalculate the number of attachments every time a comment is deleted

    Problems I see are moved comments...

    Now, coming back to the fact that the question was asked for a plugin which he shouldn't use, which is a good point: as long as the upload plugin makes use of the Media table, there should be no change needed in the above mentioned approach.

    But since there might be a mixed usage of different upload plugins (simply based on historical reasons), there should be an additional focus on testing such scenarios. Thanks for bringing that point to my awareness!

    @vrijvlinder said:
    And I suggested he uses the new uploader that comes with vanilla which is superior to my plugin. So he should start with using that one instead of mine, which is limited and outdated...

    I totally agree with that! And giving such a good advice is nice from you, too! =)


    Ah, we should be super-polite far more often! I really enjoy that =)

  • Thanks for responding!!

    I came up with the following two queries. I'd like to combine/optimize them but this works for now.

     $discussion_sql = "Select count(DISTINCT gm.MediaId) as DiscussionMedia
     FROM GDN_Media gm
     WHERE gm.ForeignTable = 'discussion' and gm.ForeignID = {$Discussion->DiscussionID}";
    
    
     $comment_sql = "Select count(DISTINCT gm.MediaId) as CommentMedia
    FROM GDN_Media gm
    WHERE gm.ForeignTable = 'comment' AND gm.ForeignID IN (
    Select c.CommentID
    FROM GDN_Comment c
    WHERE c.DiscussionID = {$Discussion->DiscussionID})";
    

    Then I just tally the results.

    @vrijvlinder - I didn't like the WYSIWYG editor that comes with the Advanced Editor, but if it does the job that I need it to do, I can work around that.

    @R_J - I thought about something along the lines of the event hooks. But 1) I couldn't find detailed documentation as to what events were available and 2) my event hook file wouldn't fire.

  • R_JR_J Ex-Fanboy Munich Admin

    That looks great! From what you are saying I would assume you use a custom theme and you are talking about the themehooks file?

    Would you mind posting it here together with its name and the folders name it resides in?

    Events

    In order to see which events are available, the best source is the source code. Look at applications/vanilla/models/class.commentmodel.php and class.discussionmodel.php in the same folder. Search for "FireEvent" (I prefer to search for "FireEve" since that sounds so heroic).
    You will soon find out that with a little bit of imagination you could guess the names of the events you are looking for (AfterSaveDiscussion and AfterSaveComment)

    Searching in plugins for this event names can give you some examples on how they could be used. If you need more details, just ask

    SQL

    You have a ready made sql, which in theory is fine, but isn't as portable as it should be if you think about reusing your code (which you always should). Using the query builder is quite simple:

    "Select count(DISTINCT gm.MediaId) as DiscussionMedia
     FROM GDN_Media gm
     WHERE gm.ForeignTable = 'discussion' and gm.ForeignID = {$Discussion->DiscussionID}";
    

    would be (though this is untested)

    $discussionMediaCount = Gdn::sql()
        ->distinct()
        ->from('Media')
        ->where('ForeignTable', 'discussion')
        ->where('ForeignID', $args['DiscussionID'])
        ->getCount();
    

    or you could try it like that (inspired by the flagging plugin but untested, too)

    $discussionMediaCount = Gdn::sql()
        ->select('count(distinct(MediaId)) as DiscussionMediaCount')
        ->from('Media')
        ->where('ForeignTable', 'discussion')
        ->where('ForeignID', $args['DiscussionID'])
        ->get()
        ->firstRow(DATASET_TYPE_ARRAY)
        ->DiscussionMediaCount;
    

    Efficiency

    I think doing this on every safe couldn't hurt but if there is a flag in the discussion or comment table that the current post has a media attached I would only run the query if there is a media. But this could cause problems if a user is editing his post afterwards and deleting the media...

    Storage of that information

    Do you know about the Attributes column of Discussion table and how it could be used? That is the best place to store that information

    Displaying the information

    Get the plugin eventi! It will help you finding the right event to use for displaying something. Just try it.

  • Attached is my hooks file, file extension changed since .php was not allowed.
    This is located in the following directory:
    ~/www/forum/themes/BridgingBarriers/class.bridgingbarriershooks.php

  • vrijvlindervrijvlinder Papillon-Sauvage MVP

    You can attach files of any kind inside a zip folder.

    Or copy the code and post it here by pasting it selecting it and choosing code from the editor drop down menu.

  • R_JR_J Ex-Fanboy Munich Admin

    You can post code here by enclosing it in three tildes like that
    ~~~
    your code here...
    ~~~

    There are several "issues" with the code:
    1. The file name should be class.themenamethemehooks.php, but that is of minor importance. You would find some themes naming that file simply "class.themehooks.php". I would advice using "class.bridgingbarriersthemehooks.php"
    2. The class name must end in "ThemeHooks", so you would have to use "BridgingBarriersThemeHooks" as class name
    3. Your class extends "Gdn_Plugin" while it should implement "Gdn_IPlugin"

    This comes down to changing this:
    class BridgingBarriersHooks extends Gdn_Plugin {
    to that:
    class BridgingBarriersThemeHooks implements Gdn_IPlugin {

    Gdn_IPlugin is an "interface". If you create a class and implement an interface (that's what the line above does), you are bound to some "rules" that the interface dictates. In this case, your class necessarily needs to have a method setup. But it only needs to exist, it doesn't have to have functionality.

    So add the following method to this class:

        public function setup() {
        }
    

    Changing your file like that gives you a working themehooks file. Add this to test it

        public function pluginController_themeHooksTest_create($sender, $args) {
            decho($args, 'args', true);
        }
    

    Visiting yourforum.com/themehookstest/hello/world should show you some text now.

  • Thank you!! I'll give that a shot

  • That works great! Thank you very much for your help!

    FYI, I was basing my code off of this page - http://docs.vanillaforums.com/developer/theming/hooks/

  • R_JR_J Ex-Fanboy Munich Admin

    Thanks for the info! I try to get that fixed

  • R_JR_J Ex-Fanboy Munich Admin

    Well, turns out that I have to take back what I have said. Seems like extending Gdn_Plugin is a better idea than implementing Gdn_IPlugin.

    Although I guess for what you are doing there would be no difference you should stick to the example. Instead of using the suggested
    class BridgingBarriersThemeHooks implements Gdn_IPlugin {
    please use
    class BridgingBarriersThemeHooks extends Gdn_Plugin {

    The reason why your first try failed was simply because you've named the class BridgingBarriersHooks when it needed to be named ...ThemeHooks.

    If you are extending Gdn_Plugin, you do not need that empty setup() method, by the way.

    If you are interested in hearing why you should prefer Gdn_Plugin, I could elaborate on that (as far as my limited programming knowledge allows).

  • @R_J said:

    Displaying the information

    Get the plugin eventi! It will help you finding the right event to use for displaying something. Just try it.

    Can you expand on this please? Right now I've modified the discussions/helper_functions.php by adding this snippet of code in:

    <span class="MItem MCount FileCount">
    <?php printf(pluralTranslate($total_media,
                    '%s file html', '%s files html', t('%s file'), t('%s files')),
                    bigPlural($total_media, '%s file'));?>
    </span>
    

    where $total_media is the sum of the results from my queries. So I've updated my Hooks file, and I'm able to run the new query, but I am trying to figure out how to return the result back to the view.

  • R_JR_J Ex-Fanboy Munich Admin

    @chrismartinez99 said:
    Can you expand on this please? Right now I've modified the discussions/helper_functions.php by adding this snippet of code in:

    Revert that. The next version upgrade would destroy this.

    By the way: a themehooks file is nothing else but a theme specific plugin, so I will just say "plugin" from now on, but everything I say will be valid for the themhooks file, too.
    And this also means that you can read more about that here: http://docs.vanillaforums.com/developer/plugins/

    I guess the event you could best would be DiscussionMeta. Just add those two methods to your themehooks file:

    public function base_discussionMeta_handler($sender, $args) {
        echo '<span class="MItem TestEachController">Controller: ';
        echo $sender->ControllerName;
        echo '</span>';
    }
    
    public function discussionsController_discussionMeta_handler($sender, $args) {
        echo '<span class="MItem TestDiscussionsController">Surprise</span>';
    }
    

    Then look at the discussions meta at yourforum.com/discussions, yourforum.com/discussion/1/blabla, yourforum.com/categories/discussions

    That would be the way to show data.

  • R_JR_J Ex-Fanboy Munich Admin

    But which data to show? I've suggested you add the information to the Attributes column of a discussion. But wait, "How to access the discussion itself?" you might ask.

    In the code snippets above you might have seen that there is a parameter $args which we haven't used. $args is an array containing all the values that have been passed before the fireEvent() call to the $EventArguments array. In the above mentioned helper_functions.php is a line $Sender->EventArguments['Discussion'] = &$Discussion; and that's why you can access the discussion like that:

    public function base_discussionMeta_handler($sender, $args) {
        echo '<span class="MItem DiscussionName">';
        echo $args['Discussion']->Name;
        echo '</span>';
    }
    

    The discussion is passed as an object which contains the information you can find in the database. What you can find in a column in the database is a property of this discussion object.
    You can check that by changing the Name in the above snippet by any other column name.

    The Attributes column is a special row. In the discussion object, it is an array which is saved as a serialized string in the database. So you would have to do something like that

    public function base_discussionMeta_handler($sender, $args) {
        if (!array_key_exists('FileCount', $args['Discussion']->Arguments) {
            return;
        }
    
        echo '<span class="MItem MCount FileCount">';
        echo $args['Discussion']->Attributes['FileCount'];
        echo '</span>';
    }
    

    With the if condition we determine if we can stop further processing right now or if there is anything to show.

    What is missing right now is the way to save that information to the Attributes column. You will need to use the events in the DiscussionModel and CommentModel for this...

  • R_JR_J Ex-Fanboy Munich Admin

    Inspect the class.commentmodel.php and search for AfterSaveComment.

    Hint: when using the events in a model, you most probably have no way to echo something to the screen, not even for debugging.

    The goal is to
    a) identify the discussion that a comment belongs to
    b) count the medias
    c) save that count to the discussion

    Look at what is passed as EventArguments. I haven't tested that but I would guess that you could find the DiscussionID in CommentData, based on this code:

    ...
    $Fields = $this->Validation->SchemaValidationFields();
    ...
    $CommentData = $CommentID ? array_merge($Fields, ['CommentID' => $CommentID]) : $Fields;
    ...
    $this->EventArguments['CommentData'] = $CommentData;
    ...
    

    So let's start with a):

    public function commentModel_afterSaveComment_handler($sender, $args) {
        $discussionID = $args['CommentData']['DiscussionID'];
    }
    

    Debugging is quite difficult, so by now we just assume I got it correct. If that doesn't work, you might have to use the EventArgument FormPostValues...

    We start by doing the "save a value" part first, before we look at the "get the correct value", since we have to make small steps because we cannot debug.

    There is a helper method in the class.model.php which helps by saving values to special columns like the Attributes column is. Open class.model.php and search for saveToSerializedColumn.

    Based on that let's try this:

    public function commentModel_afterSaveComment_handler($sender, $args) {
        // Let's use some random value here - small steps, remember?
        $fileCount = 11;
    
        // Get the discussion ID
        $discussionID = $args['CommentData']['DiscussionID'];
    
        // We need to save to the Discussion table, don't do this with sql!
        // Instead use the models provided by the model.
        $discussionModel = new DiscussionModel();
        $discussionModel->saveToSerializedColumn(
            'Attributes',
            $discussionID,
            'FileCount',
            $fileCount
        );
    }
    

    Afterwards, every discussion where a comment is saved should have the information in the Discussion table, column Attributes that there is a FileCount of 11 for this discussion.

    Check that by saving a comment and afterwards look in the database, checking the discussion that comment belongs to.

    If this works, you can change that random value to some SQL magic.

    And if it works and you have managed to successfully do the steps from my previous post, the discussion meta of your test discussion should show a file count of 11!

  • R_JR_J Ex-Fanboy Munich Admin

    Saving the information after a discussion has been created needs a similar process. It is a little bit easier, though. Try it yourself! =)

  • Oh wow!! Thank you so much for all of that!! I have a lot of reading to do and a lot more to learn about this. Thank you again!

Sign In or Register to comment.