XF 2.1 Capturing "Reject with Reason" for resources in approval queue

CStrategies

Member
I am building an add-on to enable the same "Reject with Reason" approval controls found when a user registrations shows up in the moderator approval queue, but do it for resources posted with the XenForo Resource Manager which by default only gives the option to "approve" or "delete".

The issue that I'm running into is that the reason typed into the field in the approval queue is either not being saved or not being passed to my custom method, because I am getting the error XF\PrintableException: Job XF:ApprovalQueueProcess: Please enter a value for the required field 'reason'. even when I enter something in the field before saving. I understand this is partly because I've set that variable as required in the entity, but I don't understand why the same logic used in XF/ApprovalQueue/User won't work to capture and pass the reason in my XFRM/ApprovalQueue/ResourceItem extension.

Primary steps taken so far:
1. Created a table and entity to store user_id resource_id rejection_date and reason
2. Extended XFRM/ApprovalQueue/ResourceItem.php to include an actionReject method
3. Modified the approval_item_resource and approval_queue_macros to display the new approval controls when a resource appears in the approval queue

Added this to the approval_item_resource template:
Code:
<xf:set var="$actionsHtml">
    <xf:radio name="queue[{$unapprovedItem.content_type}][{$unapprovedItem.content_id}]">
        <xf:option value="" checked="checked" label="{{ phrase('do_nothing') }}" data-xf-click="approval-control" />
        <xf:option value="approve" label="{{ phrase('approve') }}" data-xf-click="approval-control" />
        <xf:option value="reject" label="{{ phrase('reject_with_reason:') }}"
            data-xf-click="approval-control">
            <xf:textbox name="reason[{$unapprovedItem.content_type}][{$unapprovedItem.content_id}]"
                
                placeholder="{{ phrase('optional') }}" />
            <xf:html>
                <div class="formRow-explain">{{ phrase('this_will_be_shown_to_user_if_provided') }}</div>
            </xf:html>
        </xf:option>
    </xf:radio>
</xf:set>

Added this to the approval_queue_macros template:
Code:
<div class="message-cell message-cell--extra">
    <xf:if is="$contentType == 'resource'">
        {$actionsHtml|raw}
    <xf:else />
        <xf:if is="$unapprovedItem AND $handler">
            <xf:macro template="approval_queue_macros" name="action_radio"
                arg-unapprovedItem="{$unapprovedItem}"
                arg-handler="{$handler}" />
        <xf:else />
            {$actionsHtml|raw}
        </xf:if>
    </xf:if>
</div>

And added this method to my class extension of the XFRM/ApprovalQueue/ResourceItem:
Code:
public function actionReject(\XFRM\Entity\ResourceItem $resource)
    {
            
        $this->quickUpdate($resource, 'resource_state', 'deleted');
        
     // Increment the 'resources_rejected' count for the associated user
    $user = $resource->User;
    $user->resources_rejected += 1;
    $user->save();
    
    // Fetch the associated discussion thread
    $threadId = $resource->discussion_thread_id;
    if ($threadId) {
        $thread = \XF::em()->find('XF:Thread', $threadId);

     // Check if the thread was successfully fetched
        if ($thread) {
            // Soft-delete the thread
            $thread->discussion_state = 'deleted';
            $thread->save();
        }
    }
    
    // Fetch the reason for rejection
    $reason = $this->getInput('reason','No Reason Provided');

    // Get the Entity Manager
    $em = \XF::em();

    // Create a new entry in the 'resource_rejection' table
    $rejection = $em->create('MyDevName\MyCustomAddon:ResourceRejection');

    // Populate the fields
    $rejection->user_id = $resource->User->user_id;
    $rejection->resource_id = $resource->resource_id;
    $rejection->rejection_date = \XF::$time; // Current timestamp
    $rejection->reason = $reason;

    // Save the new rejection record
    $rejection->save();
}

As far as debugging, I've confirmed that the template modifications are active, that the proper arguments are being passed between templates, and that my actionReject method is being called and the resource soft-deleted. It seems what is not happening is the capture of the reason field and the updates to user_id resource_id rejection_date and reason in my entity.

Any assistance would be greatly appreciated.
 
Update: It looks like the issue was in part caused by the getInput method of the Abstract Handler, which in the case of User Registrations is looking for an array that includes the reason as well as the input from the checkbox that says "notify user of rejection". In the case of resources, I didn't include the checkbox and so the method was looking for an array but in my version there was only a string. I added a new getInput method to my extension of the XFRM/ApprovalQueue/ResourceItem:

Code:
protected function getInput($key, $id)
{
    if ($key === 'reason') {
        // Filter the reason input as a string
        return $this->inputFilterer->filter($key . '.' . $this->contentType . '.' . $id, 'str');
    } else {
        // For other inputs, continue to filter as an array
        if (!isset($this->filterCache[$key]))
        {
            $this->filterCache[$key] = $this->inputFilterer->filter($key, 'array');
        }

        return !empty($this->filterCache[$key][$this->contentType][$id]) ? $this->filterCache[$key][$this->contentType][$id] : '';
    }
}

And included an option for no reason provided in my actionReject method:

Code:
$reason = $this->getInput('reason', $resource->resource_id);
    if ($reason === '') {
    $reason = 'No Reason Provided';
    }

Hope this helps someone else.
 
You should be fine using the original \XF\ApprovalQueue\AbstractHandler::getInput method:

PHP:
$this->getInput('reason', $resource->resource_id

Per your template changes, reason (and other approval queue inputs) is an array with the form:
PHP:
'resource' => [
    1 => 'the reason given for resource 1',
    2 => 'the reason given for resource 2',
    // ...
]
 
You should be fine using the original \XF\ApprovalQueue\AbstractHandler::getInput method:

PHP:
$this->getInput('reason', $resource->resource_id

Per your template changes, reason (and other approval queue inputs) is an array with the form:
PHP:
'resource' => [
    1 => 'the reason given for resource 1',
    2 => 'the reason given for resource 2',
    // ...
]
When using the original method, reason is still reported as 'null' by my debug line:
Code:
\XF::logError('Fetching reason for resource ID: ' . $resource->resource_id);
    $reason = $this->getInput('reason', $resource->resource_id);
    \XF::logError('Fetched reason: ' . $reason);

Even though the reason is in the array:

Code:
public function setInput(array $input)
    {
        \XF::logError('Setting input data: ' . print_r($input, true)); 

        $this->inputFilterer = new InputFiltererArray(
            \XF::app()->inputFilterer(), $input
        );
    }

ErrorException: Setting input data: Array( [queue] => Array ( [resource_update] => Array ( [268] => ) [resource_version] => Array ( [302] => ) [post] => Array ( [706] => ) [thread] => Array ( [337] => [343] => ) [resource] => Array ( [249] => reject ) ) [reason] => Array ( [resource] => Array ( [249] => rejection reason for test 024 ) )
 
Update: Apparently the issue was in $this->contentType which is part of the original getInput method:

Code:
protected function getInput($key, $id)
    {   
        if (!isset($this->filterCache[$key]))
        {
            $this->filterCache[$key] = $this->inputFilterer->filter($key, 'array');
        }
        return !empty($this->filterCache[$key][$this->contentType][$id]) ? $this->filterCache[$key][$this->contentType][$id] : '';
    }

I needed to set the contentType to resource before calling getInput:

Code:
// set the content type
    $this->contentType = 'resource';
    
// Fetch the reason for rejection
    $reason = $this->getInput('reason', $resource->resource_id);
    if ($reason === '') {
    $reason = 'No Reason Provided';
    \XF::logError('No Reason Provided');
    }

This solved the issue.
 
Top Bottom