Invision Power Services, Inc. * @copyright (c) Invision Power Services, Inc. * @license https://www.invisioncommunity.com/legal/standards/ * @package Invision Community * @subpackage Nexus * @since 23 Apr 2014 */ namespace IPS\nexus\modules\admin\support; /* To prevent PHP errors (extending class does not exist) revealing path */ if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) ) { header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' ); exit; } /** * Support Reports */ class _reports extends \IPS\Dispatcher\Controller { /** * Execute * * @return void */ public function execute() { \IPS\Dispatcher::i()->checkAcpPermission( 'reports_manage' ); parent::execute(); } /** * Dashboard * * @return void */ protected function manage() { /* Tabs */ $tabs = array( 'overview' => 'overview', 'replies' => 'latest_replies', 'feedback_ratings' => 'feedback_ratings', 'latest_feedback' => 'latest_feedback', ); $activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'staff_productivity'; $activeTabContents = ''; /* Staff Productivity */ if ( $activeTab === 'overview' ) { /* Filters */ $filters = array( 'last_24_hours' => \IPS\DateTime::create()->sub( new \DateInterval( 'P1D' ) )->getTimestamp(), 'last_7_days' => \IPS\DateTime::create()->sub( new \DateInterval( 'P7D' ) )->getTimestamp(), 'last_30_days' => \IPS\DateTime::create()->sub( new \DateInterval( 'P30D' ) )->getTimestamp(), ); /* Set default values */ $data = array(); foreach ( \IPS\nexus\Support\Request::staff() as $staffId => $name ) { $data[ $staffId ] = array( 'name' => $name, 'reply_count' => 0 ); if ( \IPS\Settings::i()->nexus_support_satisfaction ) { $data[ $staffId ]['rating_count'] = 0; $data[ $staffId ]['rating_average'] = 0; } } /* Get their replies */ $where = array( array( \IPS\Db::i()->in( 'reply_member', array_keys( $data ) ) ) ); if ( isset( \IPS\Request::i()->filter ) and array_key_exists( \IPS\Request::i()->filter, $filters ) ) { $where[] = array( 'reply_date>?', $filters[ \IPS\Request::i()->filter ] ); } foreach ( \IPS\Db::i()->select( 'COUNT(*) AS count, reply_member', 'nexus_support_replies', $where, NULL, NULL, 'reply_member' ) as $row ) { $data[ $row['reply_member'] ]['reply_count'] = $row['count']; } /* And their ratings */ if ( \IPS\Settings::i()->nexus_support_satisfaction ) { $where = array( \IPS\Db::i()->in( 'rating_staff', array_keys( $data ) ) ); if ( isset( \IPS\Request::i()->filter ) and array_key_exists( \IPS\Request::i()->filter, $filters ) ) { $where[] = array( 'rating_date>?', $filters[ \IPS\Request::i()->filter ] ); } foreach ( \IPS\Db::i()->select( 'COUNT(*) AS count, AVG(rating_rating) AS rating, rating_staff', 'nexus_support_ratings', $where, NULL, NULL, 'rating_staff' ) as $row ) { $data[ $row['rating_staff'] ]['rating_count'] = $row['count']; $data[ $row['rating_staff'] ]['rating_average'] = $row['rating']; } } /* Build the table */ $staffProductivityTable = new \IPS\Helpers\Table\Custom( $data, \IPS\Http\Url::internal( 'app=nexus&module=support&controller=reports&tab=overview' ) ); $staffProductivityTable->langPrefix = 'staff_'; $staffProductivityTable->widths = \IPS\Settings::i()->nexus_support_satisfaction ? array( 'name' => 50, 'reply_count' => 25, 'rating_average' => 25 ) : array( 'name' => 75, 'reply_count' => 25 ); if ( \IPS\Settings::i()->nexus_support_satisfaction ) { $staffProductivityTable->parsers['rating_average'] = function( $val, $row ) { return \IPS\Theme::i()->getTemplate('supportreports')->averageRatingCell( $val, $row['rating_count'] ); }; $staffProductivityTable->include = array( 'name', 'reply_count', 'rating_average' ); } $staffProductivityTable->sortBy = $staffProductivityTable->sortBy ?: 'reply_count'; $staffProductivityTable->quickSearch = 'name'; /* Specify the filters and search options */ $staffProductivityTable->filters = $filters; /* Buttons for each member */ $staffProductivityTable->rowButtons = function( $row, $id ) { return array( 'report' => array( 'icon' => 'search', 'title' => 'view_report', 'link' => \IPS\Http\Url::internal('app=nexus&module=support&controller=reports&do=staff')->setQueryString( 'id', $id ), ), ); }; /* Output */ $activeTabContents = (string) $staffProductivityTable; } /* Replies */ elseif ( $activeTab === 'replies' ) { $staffRepliesChart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=nexus&module=support&controller=reports&tab=replies' ), 'nexus_support_replies', 'reply_date', '', array( 'vAxis' => array( 'title' => \IPS\Member::loggedIn()->language()->addToStack('replies_made') ), 'height' => 700, 'backgroundColor' => '#ffffff', 'colors' => array( '#10967e', '#ea7963', '#de6470', '#6b9dde', '#b09be4', '#eec766', '#9fc973', '#e291bf', '#55c1a6', '#5fb9da' ), 'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ), 'lineWidth' => 1, 'areaOpacity' => 0.4 ), 'AreaChart', 'daily', array( 'start' => \IPS\DateTime::create()->sub( new \DateInterval( 'P1M' ) ), 'end' => 0 ) ); $staffRepliesChart->where[] = array( 'reply_type=?', \IPS\nexus\Support\Reply::REPLY_STAFF ); $staffRepliesChart->groupBy = 'reply_member'; foreach( \IPS\nexus\Support\Request::staff() as $id => $name ) { $staffRepliesChart->addSeries( $name, 'number', 'COUNT(*)', TRUE, $id ); } $activeTabContents = (string) $staffRepliesChart; } /* Replies */ elseif ( $activeTab === 'feedback_ratings' ) { $staffRatingsChart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( 'app=nexus&module=support&controller=reports&tab=feedback_ratings' ), 'nexus_support_ratings', 'rating_date', '', array( 'vAxis' => array( 'title' => \IPS\Member::loggedIn()->language()->addToStack('average_rating'), 'viewWindow' => array( 'min' => 0, 'max' => 5 ) ), 'height' => 700, 'backgroundColor' => '#ffffff', 'colors' => array( '#10967e', '#ea7963', '#de6470', '#6b9dde', '#b09be4', '#eec766', '#9fc973', '#e291bf', '#55c1a6', '#5fb9da' ), 'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ), 'lineWidth' => 1, 'areaOpacity' => 0.4 ), 'ColumnChart', 'monthly', array( 'start' => \IPS\DateTime::create()->sub( new \DateInterval( 'P1M' ) ), 'end' => \IPS\DateTime::create() ) ); $staffRatingsChart->plotZeros = FALSE; $staffRatingsChart->groupBy = 'rating_staff'; foreach( \IPS\nexus\Support\Request::staff() as $id => $name ) { $staffRatingsChart->addSeries( $name, 'number', 'AVG(rating_rating)', TRUE, $id ); } $activeTabContents = (string) $staffRatingsChart; } /* Latest Feedback */ elseif ( $activeTab === 'latest_feedback' ) { $table = new \IPS\Helpers\Table\Db( 'nexus_support_ratings', \IPS\Http\Url::internal('app=nexus&module=support&controller=reports&tab=latest_feedback') ); $table->joins = array( array( 'from' => 'nexus_support_replies', 'where' => 'reply_id=rating_reply' ), array( 'from' => 'nexus_support_requests', 'where' => 'r_id=reply_request' ) ); $table->sortBy = 'rating_date'; $table->sortDirection = 'desc'; $table->parsers = array( 'reply_post' => function( $val ) { return $val; } ); $table->tableTemplate = array( \IPS\Theme::i()->getTemplate('support'), 'latestFeedback' ); $activeTabContents = (string) $table; } /* Display */ if ( \IPS\Request::i()->isAjax() ) { \IPS\Output::i()->output = $activeTabContents; } else { \IPS\Output::i()->title = \IPS\Member::loggedIn()->language()->addToStack('performance_reports'); \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=nexus&module=support&controller=reports" ) ); } } /** * Staff Report * * @return void */ protected function staff() { /* Load */ $id = \IPS\Request::i()->id; if ( !array_key_exists( $id, \IPS\nexus\Support\Request::staff() ) ) { \IPS\Output::i()->error( 'node_error', '2X207/1', 404, '' ); } $staff = \IPS\Member::load( $id ); /* Tabs */ $tabs = array( 'productivity' => 'productivity', 'latest_replies' => 'latest_replies', 'feedback_ratings' => 'feedback_ratings', 'latest_feedback' => 'latest_feedback' ); $activeTab = ( isset( \IPS\Request::i()->tab ) and array_key_exists( \IPS\Request::i()->tab, $tabs ) ) ? \IPS\Request::i()->tab : 'productivity'; $activeTabContents = ''; /* Daily Productivity */ if ( $activeTab === 'productivity' ) { /* Date Range */ $where = array( array( 'reply_type=?', \IPS\nexus\Support\Reply::REPLY_STAFF ) ); $timeframe = isset( \IPS\Request::i()->timeframe ) ? \IPS\Request::i()->timeframe : 'last_30_days'; switch ( $timeframe ) { case 'last_24_hours': $daysInTimeframe = 1; break; case 'last_7_days': $daysInTimeframe = 7; break; case 'last_30_days': $daysInTimeframe = 30; break; } $where[] = array( 'reply_date>?', \IPS\DateTime::ts( time() )->sub( new \DateInterval( "P{$daysInTimeframe}D" ) )->getTimestamp() ); /* Init */ $numberOfStaffMembers = \IPS\Db::i()->select( 'COUNT(DISTINCT reply_member)', 'nexus_support_replies', $where )->first(); $now = \IPS\DateTime::ts( time() ); $myOffset = $now->getTimezone()->getOffset( $now ) / 3600; /* Group */ $span = isset( \IPS\Request::i()->span ) ? \IPS\Request::i()->span : 'day'; $group = $span === 'day' ? '%H' : '%w'; /* Type */ $type = isset( \IPS\Request::i()->type ) ? \IPS\Request::i()->type : 'total'; /* Get average replies by hour for this staff member */ $thisStaffMember = array(); $thisStaffMemberCount = array(); foreach( \IPS\Db::i()->select( "COUNT(*) AS count, reply_date AS unixtime, DATE_FORMAT( FROM_UNIXTIME( reply_date ), '{$group}' ) AS hour", 'nexus_support_replies', array_merge( $where, array( array( 'reply_member=?', $staff->member_id ) ) ), NULL, NULL, array( 'hour', 'reply_date' ) ) as $row ) { $_group = $span === 'day' ? \IPS\DateTime::ts( $row['unixtime'] )->format('G') : \IPS\DateTime::ts( $row['unixtime'] )->format('w'); if ( !isset( $thisStaffMemberCount[ $_group ] ) ) { $thisStaffMemberCount[ $_group ] = 0; } $thisStaffMemberCount[ $_group ] = $thisStaffMemberCount[ $_group ] + $row['count']; } foreach( $thisStaffMemberCount AS $_group => $count ) { $thisStaffMember[ $_group ] = round( ( $type === 'average' ) ? ( $count / $daysInTimeframe ) : $count, 1 ); } /* Get average replies by hour for all staff members */ if ( $numberOfStaffMembers > 1 ) { $allStaffMembers = array(); $allStaffMembersCount = array(); foreach( \IPS\Db::i()->select( "COUNT(*) AS count, reply_date AS unixtime, DATE_FORMAT( FROM_UNIXTIME( reply_date ), '{$group}' ) AS hour", 'nexus_support_replies', $where, NULL, NULL, array( 'hour', 'reply_date' ) ) as $row ) { $_group = $span === 'day' ? \IPS\DateTime::ts( $row['unixtime'] )->format('G') : \IPS\DateTime::ts( $row['unixtime'] )->format('w'); if ( !isset( $allStaffMembersCount[ $_group ] ) ) { $allStaffMembersCount[ $_group ] = 0; } $allStaffMembersCount[ $_group ] = $allStaffMembersCount[ $_group ] + $row['count']; } foreach( $allStaffMembersCount AS $_group => $count ) { $allStaffMembers[ $_group ] = round( ( ( $type === 'average' ) ? ( $count / $daysInTimeframe ) : $count ) / $numberOfStaffMembers, 1 ); } } /* Build Chart */ $chart = new \IPS\Helpers\Chart; $chart->addHeader( \IPS\Member::loggedIn()->language()->addToStack('hour'), 'string' ); $chart->addHeader( $staff->name, 'number' ); if ( $numberOfStaffMembers > 1 ) { $chart->addHeader( \IPS\Member::loggedIn()->language()->addToStack('all_staff_average'), 'number' ); } if ( $span === 'day' ) { foreach ( range( 0 - $myOffset, 23 - $myOffset ) as $hour ) { $timestamp = mktime( 0, 0, 0 ) + ( $hour * 3600 ); if ( $numberOfStaffMembers > 1 ) { $chart->addRow( array( \IPS\DateTime::ts( $timestamp )->localeTime( FALSE, FALSE ), isset( $thisStaffMember[ $hour + $myOffset ] ) ? $thisStaffMember[ $hour + $myOffset ] : 0, isset( $allStaffMembers[ $hour + $myOffset ] ) ? $allStaffMembers[ $hour + $myOffset ] : 0 ) ); } else { $chart->addRow( array( \IPS\DateTime::ts( $timestamp )->localeTime( FALSE, FALSE ), isset( $thisStaffMember[ $hour + $myOffset ] ) ? $thisStaffMember[ $hour + $myOffset ] : 0 ) ); } } } else { foreach ( range( 0, 6 ) as $day ) { if ( $numberOfStaffMembers > 1 ) { $chart->addRow( array( \IPS\Member::loggedIn()->language()->addToStack("weekday_{$day}"), isset( $thisStaffMember[ $day ] ) ? $thisStaffMember[ $day ] : 0, isset( $allStaffMembers[ $day ] ) ? $allStaffMembers[ $day ] : 0 ) ); } else { $chart->addRow( array( \IPS\Member::loggedIn()->language()->addToStack("weekday_{$day}"), isset( $thisStaffMember[ $day ] ) ? $thisStaffMember[ $day ] : 0 ) ); } } } /* Display */ $activeTabContents = \IPS\Theme::i()->getTemplate('supportreports')->timeChart( $chart->render( 'SteppedAreaChart', array( 'legend' => array( 'position' => 'none' ), 'areaOpacity' => 0.4, 'lineWidth' => 1, 'backgroundColor' => '#ffffff', 'colors' => array( '#10967e' ), 'hAxis' => array( 'gridlines' => array( 'color' => '#f5f5f5' ) ), ) ), \IPS\Http\Url::internal( "app=nexus&module=support&controller=reports&do=staff&id={$staff->member_id}&tab=productivity" )->setQueryString( array( 'span' => $span, 'timeframe' => $timeframe, 'type' => $type ) ) ); } /* Latest Replies */ elseif ( $activeTab === 'latest_replies' ) { \IPS\Output::i()->cssFiles = array_merge( \IPS\Output::i()->cssFiles, \IPS\Theme::i()->css( 'support.css', 'nexus', 'admin' ) ); $table = new \IPS\Helpers\Table\Content( 'IPS\nexus\Support\Reply', \IPS\Http\Url::internal( "app=nexus&module=support&controller=reports&do=staff&id={$staff->member_id}&tab=latest_replies" ), array( array( 'reply_type=? AND reply_member=?', \IPS\nexus\Support\Reply::REPLY_STAFF, $staff->member_id ) ) ); $table->tableTemplate = array( \IPS\Theme::i()->getTemplate( 'tables', 'core', 'front' ), 'table' ); $table->rowsTemplate = array( \IPS\Theme::i()->getTemplate( 'supportreports' ), 'supportReplyRows' ); $table->sortBy = 'reply_date'; $activeTabContents = (string) $table; } /* Feedback */ elseif ( $activeTab === 'feedback_ratings' ) { $staffRatingsChart = new \IPS\Helpers\Chart\Database( \IPS\Http\Url::internal( "app=nexus&module=support&controller=reports&do=staff&id={$staff->member_id}&tab=feedback_ratings" ), 'nexus_support_ratings', 'rating_date', '', array( 'vAxis' => array( 'title' => \IPS\Member::loggedIn()->language()->addToStack('average_rating'), 'viewWindow' => array( 'min' => 0, 'max' => 5 ) ), 'legend' => 'none' ), 'LineChart', 'monthly' ); $staffRatingsChart->plotZeros = FALSE; $staffRatingsChart->addSeries( $staff->name, 'number', 'AVG(rating_rating)', FALSE ); $activeTabContents = (string) $staffRatingsChart; } /* Latest Feedback */ elseif ( $activeTab === 'latest_feedback' ) { $table = new \IPS\Helpers\Table\Db( 'nexus_support_ratings', \IPS\Http\Url::internal("app=nexus&module=support&controller=reports&do=staff&id={$staff->member_id}&tab=latest_feedback"), array( 'rating_staff=?', $staff->member_id ) ); $table->joins = array( array( 'from' => 'nexus_support_replies', 'where' => 'reply_id=rating_reply' ), array( 'from' => 'nexus_support_requests', 'where' => 'r_id=reply_request' ) ); $table->sortBy = 'rating_date'; $table->sortDirection = 'desc'; $table->parsers = array( 'reply_post' => function( $val ) { return $val; } ); $table->tableTemplate = array( \IPS\Theme::i()->getTemplate('support'), 'latestFeedback' ); $activeTabContents = (string) $table; } /* Display */ if ( \IPS\Request::i()->isAjax() ) { \IPS\Output::i()->output = $activeTabContents; } else { \IPS\Output::i()->title = $staff->name; \IPS\Output::i()->output = \IPS\Theme::i()->getTemplate( 'global', 'core' )->tabs( $tabs, $activeTab, $activeTabContents, \IPS\Http\Url::internal( "app=nexus&module=support&controller=reports&do=staff&id={$staff->member_id}" ) ); } } }