Invision Power Services, Inc. * @copyright (c) 2001 - SVN_YYYY Invision Power Services, Inc. * @license http://www.invisionpower.com/legal/standards/ * @package IPS Social Suite * @subpackage Nexus * @since 8 Apr 2014 * @version SVN_VERSION_NUMBER */ namespace IPS\nexus\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 Request Model */ class _Request extends \IPS\Content\Item implements \IPS\Content\ReadMarkers { /** * @brief Multiton Store */ protected static $multitons; /** * @brief Application */ public static $application = 'nexus'; /** * @brief Module */ public static $module = 'support'; /** * @brief Database Table */ public static $databaseTable = 'nexus_support_requests'; /** * @brief Database Prefix */ public static $databasePrefix = 'r_'; /** * @brief Database Column Map */ public static $databaseColumnMap = array( 'title' => 'title', 'author' => 'member', 'date' => 'started', 'num_comments' => 'replies', 'last_comment' => 'last_reply', 'last_comment_by' => 'last_reply_by' ); /** * @brief Title */ public static $title = 'support_request'; /** * @brief Form language prefix */ public static $formLangPrefix = 'support_'; /** * @brief Comment Class */ public static $commentClass = 'IPS\nexus\Support\Reply'; /** * @brief [Content\Item] First "comment" is part of the item? */ public static $firstCommentRequired = TRUE; /** * @brief Node Class */ public static $containerNodeClass = 'IPS\nexus\Support\Department'; /* !Generic */ /** * Should posting this increment the poster's post count? * * @param \IPS\Node\Model|NULL $container Container * @return void */ public static function incrementPostCount( \IPS\Node\Model $container = NULL ) { return FALSE; } /** * My Filters * * @param array|null $_filters Filters (NULL to get from cookie) * @return array */ public static function myFilters( $_filters=null ) { $myFilters = array(); $myDepartmentIds = NULL; $myStatusIds = NULL; if ( $_filters === null and isset( \IPS\Request::i()->cookie['support_filters'] ) ) { $_filters = json_decode( \IPS\Request::i()->cookie['support_filters'], TRUE ); } if ( $_filters !== null ) { $myDepartmentIds = array_filter( $_filters['departments'] ); $myStatusIds = array_filter( $_filters['statuses'] ); } if ( !$myStatusIds ) { $myStatusIds = iterator_to_array( \IPS\Db::i()->select( 'status_id', 'nexus_support_statuses', array( 'status_open=1' ) ) ); } if ( !$myDepartmentIds ) { $myDepartmentIds = iterator_to_array( \IPS\Db::i()->select( 'dpt_id', 'nexus_support_departments', "dpt_staff='*' OR " . \IPS\Db::i()->findInSet( 'dpt_staff', \IPS\nexus\Support\Department::staffDepartmentPerms() ) ) ); } $myFilters = \IPS\Db::i()->in( 'r_department', $myDepartmentIds ) . ' AND ' . \IPS\Db::i()->in( 'r_status', $myStatusIds ); return array( 'whereClause' => $myFilters, 'departments' => $myDepartmentIds, 'statuses' => $myStatusIds ); } /** * Change Filters * * @param array $currentFilters The current filters * @param int $id The ID to set/unset * @return array */ public static function changeFilters( $currentFilters, $id ) { $index = array_search( $id, $currentFilters ); if ( $index === FALSE ) { $currentFilters[] = $id; } else { unset( $currentFilters[ $index ] ); } return $currentFilters; } /** * Support Desk Staff * * @return array */ public static function staff() { $return = array(); foreach ( \IPS\Db::i()->select( '*', 'core_admin_permission_rows' ) as $row ) { if ( $row['row_id_type'] === 'group' ) { foreach ( \IPS\Db::i()->select( '*', 'core_members', array( 'member_group_id=? OR ' . \IPS\Db::i()->findInSet( 'mgroup_others', array( $row['row_id'] ) ), $row['row_id'] ) ) as $member ) { $return[ $member['member_id'] ] = $member['name']; } } else { $return[ $row['row_id'] ] = \IPS\Member::load( $row['row_id'] )->name; } } asort( $return ); return $return; } /** * Table * * * @return \IPS\Helpers\Table\Db */ public static function table( \IPS\Http\Url $url, $where ) { $table = new \IPS\Helpers\Table\Db( 'nexus_support_requests', $url, $where ); $table->joins = array( array( 'select' => 'sev_position', 'from' => 'nexus_support_severities', 'where' => 'sev_id=r_severity' ), array( 'select' => 'dpt_position, dpt_staff', 'from' => 'nexus_support_departments', 'where' => 'dpt_id=r_department' ), array( 'select' => 'status_position', 'from' => 'nexus_support_statuses', 'where' => 'status_id=r_status' ), array( 'select' => 'request_id', 'from' => 'nexus_support_tracker', 'where' => array( 'request_id=r_id AND member_id=?', \IPS\Member::loggedIn()->member_id ) ) ); $table->include = array( 'r_id', 'r_title', 'sev_position', 'dpt_position', 'r_member', 'r_replies', 'r_started', 'r_staff', 'r_last_new_reply', 'r_last_reply_by', 'r_last_reply', 'r_last_staff_reply', 'status_position' ); $table->keyField = 'r_id'; $table->extra = array(); $highlightCutoff = NULL; if ( \IPS\Settings::i()->support_old_number ) { $table->include[] = '_highlight'; $highlightCutoff = \IPS\DateTime::create()->sub( new \DateInterval( 'PT' . \IPS\Settings::i()->support_old_number . 'M' ) )->getTimestamp(); } $table->sortBy = $table->sortBy ? "sev_position asc, {$table->sortBy}" : 'sev_position asc, r_last_new_reply'; $table->sortDirection = $table->sortDirection ?: 'asc'; $table->parsers = array( 'r_title' => function( $val, $row ) use ( $table ) { $filter = NULL; $sort = NULL; if ( !isset( \IPS\Request::i()->member ) and !isset( \IPS\Request::i()->quicksearch ) and !isset( \IPS\Request::i()->advanced_search_submitted ) ) { $filter = $table->filter; $sort = $table->sortBy . ' ' . $table->sortDirection; } return \IPS\Theme::i()->getTemplate('support')->link( \IPS\nexus\Support\Request::load( $row['r_id'] ), $row['request_id'], $filter, $sort ); }, 'sev_position'=> function( $val, $row ) { return \IPS\Theme::i()->getTemplate('support')->severity( \IPS\nexus\Support\Severity::load( $row['r_severity'] ) ); }, 'dpt_position'=> function( $val, $row ) { return \IPS\Theme::i()->getTemplate('support')->department( \IPS\nexus\Support\Department::load( $row['r_department'] ) ); }, 'r_member' => function( $val, $row ) { if ( $val ) { return \IPS\Theme::i()->getTemplate('global')->userLink( \IPS\Member::load( $val ) ); } else { return htmlspecialchars( $row['r_email'], \IPS\HTMLENTITIES, 'UTF-8', FALSE ); } }, 'r_last_reply_by' => function( $val, $row ) { return \IPS\Theme::i()->getTemplate('global')->userLink( \IPS\Member::load( $val ) ); }, 'r_replies' => function( $val ) { return --$val; }, 'r_started' => function( $val ) { /* Use formatInterval to show an elapsed time here rather than a simple date */ return \IPS\DateTime::formatInterval( \IPS\DateTime::ts( $val )->diff( new \IPS\DateTime ), 2 ); }, 'r_staff' => function ( $val ) { if ( $val ) { return \IPS\Member::load( $val )->name; } }, 'r_last_new_reply' => function( $val ) { return \IPS\DateTime::ts( $val )->relative(); }, 'r_last_reply' => function( $val ) { return \IPS\DateTime::ts( $val )->relative(); }, 'r_last_staff_reply' => function( $val ) { return $val ? \IPS\DateTime::ts( $val )->relative() : \IPS\Member::loggedIn()->language()->addToStack('never'); }, 'status_position' => function( $val, $row ) { return \IPS\Theme::i()->getTemplate('support')->status( \IPS\nexus\Support\Status::load( $row['r_status'] ) ); }, '_highlight' => function( $val, $row ) use ( $table, $highlightCutoff ) { if ( $row[ \IPS\Settings::i()->support_old_stat ] < $highlightCutoff ) { $table->extra[] = $row['r_id']; } } ); return $table; } /* !Getters/Setters */ /** * Set Default Values * * @return void */ public function setDefaultValues() { $emailKey = ''; for ( $i = 0; $i < 3; $i++ ) { do { $num = mt_rand( 48, 122 ); } while ( in_array( $num, array( 58, 59, 60, 61, 62, 63, 64, 91, 92, 93, 94, 95, 96 ) ) ); $emailKey .= chr( $num ); } $this->email_key = $emailKey; $this->last_new_reply = time(); $this->staff = NULL; $this->purchase = NULL; } /** * Get department * * @return \IPS\nexus\Support\Department */ public function get_department() { return Department::load( $this->_data['department'] ); } /** * Set department * * @param \IPS\nexus\Support\Department * @return void */ public function set_department( Department $department ) { $this->_data['department'] = $department->id; } /** * Get purchase * * @return \IPS\nexus\Purchase */ public function get_purchase() { try { return $this->_data['purchase'] ? \IPS\nexus\Purchase::load( $this->_data['purchase'] ) : NULL; } catch ( \OutOfRangeException $e ) { return NULL; } } /** * Set purchase * * @param \IPS\nexus\Purchase * @return void */ public function set_purchase( \IPS\nexus\Purchase $purchase = NULL ) { $this->_data['purchase'] = $purchase ? $purchase->id : 0; } /** * Get status * * @return \IPS\nexus\Support\Status */ public function get_status() { return Status::load( $this->_data['status'] ); } /** * Set purchase * * @param \IPS\nexus\Support\Status * @return void */ public function set_status( Status $status ) { $this->_data['status'] = $status->id; } /** * Get severity * * @return \IPS\nexus\Support\Severity */ public function get_severity() { return Severity::load( $this->_data['severity'] ); } /** * Set severity * * @param \IPS\nexus\Support\Severity * @return void */ public function set_severity( Severity $severity ) { $this->_data['severity'] = $severity->id; } /** * Set last reply by * * @param int $lastReplyBy * @return void */ public function set_last_reply_by( $lastReplyBy ) { if ( array_key_exists( $lastReplyBy, static::staff() ) ) { $this->_data['last_staff_reply'] = time(); \IPS\Db::i()->update( 'nexus_support_views', array( 'view_reply' => time() ), array( 'view_rid=? AND view_member=?', $this->id, $lastReplyBy ) ); } elseif ( !isset( $this->_data['last_reply_by'] ) or $lastReplyBy != $this->_data['last_reply_by'] ) { $this->_data['last_new_reply'] = time(); } $this->_data['last_reply_by'] = $lastReplyBy; } /** * Get staff * * @return \IPS\Member|NULL */ public function get_staff() { return $this->_data['staff'] ? \IPS\Member::load( $this->_data['staff'] ) : NULL; } /** * Set staff * * @param \IPS\Member|NULL $member The staff member to assign to * @return void */ public function set_staff( \IPS\Member $member = NULL ) { $this->_data['staff'] = $member ? $member->member_id : 0; } /** * Get notify * * @return array */ public function get_notify() { return isset( $this->_data['notify'] ) ? json_decode( $this->_data['notify'], TRUE ) : array(); } /** * Set notify * * @param array $notify Notify data * @return void */ public function set_notify( array $notify ) { $this->_data['notify'] = json_encode( $notify ); } /** * Get custom fields * * @return array */ public function get_cfields() { return $this->_data['cfields'] ? json_decode( $this->_data['cfields'], TRUE ) : array(); } /** * Set custom fields * * @param array $values Values * @return void */ public function set_cfields( array $values ) { $this->_data['cfields'] = json_encode( $values ); } /** * Get PPI invoice * * @return \IPS\nexus\Invoice|NULL */ public function get_ppi_invoice() { return $this->_data['ppi_invoice'] ? \IPS\nexus\Invoice::load( $this->_data['ppi_invoice'] ) : NULL; } /** * Set PPI invoice * * @param \IPS\nexus\Invoice|NULL * @return void */ public function set_ppi_invoice( \IPS\nexus\Invoice $invoice = NULL ) { $this->_data['ppi_invoice'] = $invoice ? $invoice->id : NULL; } /* !Forms */ /** * Get elements for add/edit form * * @param \IPS\Content\Item|NULL $item The current item if editing or NULL if creating * @param \IPS\Node\Model|NULL $container Container (e.g. forum), if appropriate * @return array */ public static function formElements( $item=NULL, \IPS\Node\Model $container=NULL ) { /* Account */ if ( isset( \IPS\Request::i()->account ) and ( \IPS\Request::i()->account == \IPS\nexus\Customer::loggedIn()->member_id or array_key_exists( \IPS\Request::i()->account, iterator_to_array( \IPS\nexus\Customer::loggedIn()->parentContacts( array( 'support=1' ) ) ) ) ) ) { $account = \IPS\nexus\Customer::load( \IPS\Request::i()->account ); $purchaseWhere = array( array( 'ps_member=?', $account->member_id ) ); } else { $account = \IPS\nexus\Customer::loggedIn(); $purchaseWhere = array(); foreach ( $account->parentContacts() as $contact ) { foreach ( $contact->purchaseIds() as $id ) { $purchaseWhere[] = "ps_id={$id}"; } } if ( count( $purchaseWhere ) ) { $purchaseWhere = array( array( "ps_member={$account->member_id} OR ( " . implode( ' OR ', $purchaseWhere ) . ' )' ) ); } else { $purchaseWhere = array( array( 'ps_member=?', $account->member_id ) ); } } /* Basic elements */ $return = parent::formElements( $item, $container ); $content = $return['content']; unset( $return['content'] ); unset( $return['container'] ); /* Init */ $availableDepartments = array(); $doNotShowPurchasesForDepartments = array(); foreach ( Department::roots( NULL, NULL, 'dpt_open=1' ) as $department ) { if ( !\IPS\nexus\Customer::loggedIn()->member_id or !count( \IPS\Db::i()->select( '*', 'nexus_purchases', array_merge( $purchaseWhere, array( array( 'ps_app=? AND ps_type=?', 'nexus', 'package' ) ), array( \IPS\Db::i()->in( 'ps_item_id', explode( ',', $department->packages ) ) ) ) ) ) ) { if ( $department->require_package ) { continue; } else { $doNotShowPurchasesForDepartments[] = $department->id; } } $availableDepartments[ $department->id ] = $department; } if ( !count( $availableDepartments ) ) { \IPS\Output::i()->error( \IPS\nexus\Customer::loggedIn()->member_id ? 'no_module_permission' : 'no_module_permission_guest', '1X248/1', 403, '' ); } $departmentToggles = array(); /* What custom fields need to be triggered by departments? */ $customFields = \IPS\nexus\Support\CustomField::roots( NULL, NULL, "sf_departments='*' OR " . \IPS\Db::i()->findInSet( 'sf_departments', array_keys( $availableDepartments ) ) ); foreach ( $customFields as $field ) { if ( $field->departments and $field->departments !== '*' ) { foreach ( explode( ',', $field->departments ) as $departmentId ) { if ( !isset( $departmentToggles[ $departmentId ] ) ) { $departmentToggles[ $departmentId ] = array(); } $departmentToggles[ $departmentId ][] = "nexus_cfield_{$field->id}"; } } } /* What purchase fields need to be triggered by departments? */ $departments = array(); $purchaseFields = array(); $ppiDepartments = array(); foreach ( $availableDepartments as $department ) { $departments[ $department->id ] = $department->_title; if ( !isset( $departmentToggles[ $department->id ] ) ) { $departmentToggles[ $department->id ] = array(); } $departmentToggles[ $department->id ][] = "department_message_{$department->id}"; if ( $department->ppiCost() and !\IPS\Db::i()->select( 'COUNT(*)', 'nexus_purchases', array( 'ps_app=? AND ps_type=? AND ps_item_id=? AND ps_member=?', 'nexus', 'ppi', $department->id, \IPS\Member::loggedIn()->member_id ) )->first() ) { $content->required = FALSE; if ( $content->error === 'form_required' ) { $content->error = NULL; } $ppiDepartments[] = $department->id; } else { $departmentToggles[ $department->id ][] = static::$formLangPrefix . 'content_editor'; if ( $department->packages and !in_array( $department->id, $doNotShowPurchasesForDepartments ) ) { $allowedPackageIds = explode( ',', $department->packages ); $allowedPackages = $department->serializedPackageIds(); $departmentToggles[ $department->id ][] = 'support_purchase_' . $allowedPackages; if ( !isset( $purchaseFields[ $allowedPackages ] ) ) { $field = new \IPS\Helpers\Form\Node( 'support_purchase_' . $allowedPackages, isset( \IPS\Request::i()->purchase ) ? \IPS\Request::i()->purchase : NULL, $department->require_package ? NULL : FALSE, array( 'class' => 'IPS\nexus\Purchase', 'where' => $purchaseWhere, 'permissionCheck' => function( $node ) use ( $allowedPackageIds ) { return $node->active and $node->app === 'nexus' and $node->type === 'package' and in_array( $node->item_id, $allowedPackageIds ); } ), $department->require_package ? function( $val ) use ( $allowedPackages, $departmentToggles ) { if ( !$val and in_array( 'support_purchase_' . $allowedPackages, $departmentToggles[ \IPS\Request::i()->support_department ] ) ) { throw new \DomainException('support_purchase_required'); } } : NULL, NULL, NULL, 'support_purchase_' . $allowedPackages ); $field->label = \IPS\Member::loggedIn()->language()->addToStack('support_purchase'); $purchaseFields[ $allowedPackages ] = $field; } } } } /* Do severities need to be triggered by departments? */ $defaultSeverityId = Severity::load( TRUE, 'sev_default' )->id; $severityFields = array(); foreach ( $availableDepartments as $department ) { if ( count( $department->availableSeverities() ) > 1 ) { $serialized = "severity_{$department->serializedSeverityIds()}"; if ( !isset( $severityFields[ $serialized ] ) ) { $options = array(); foreach ( $department->availableSeverities() as $severity ) { $options[ $severity->id ] = "nexus_severity_{$severity->id}"; } $field = new \IPS\Helpers\Form\Radio( 'support_severity_' . $serialized, in_array( $defaultSeverityId, array_keys( $options ) ) ? $defaultSeverityId : NULL, FALSE, array( 'options' => $options ), NULL, NULL, NULL, 'support_severity_' . $serialized ); $field->label = \IPS\Member::loggedIn()->language()->addToStack('support_severity'); $severityFields[ $serialized ] = $field; } if ( !isset( $departmentToggles[ $department->id ] ) ) { $departmentToggles[ $department->id ] = array(); } $departmentToggles[ $department->id ][] = 'support_severity_' . $serialized; } } /* Add department */ $return['department'] = new \IPS\Helpers\Form\Select( 'support_department', isset( \IPS\Request::i()->department ) ? \IPS\Request::i()->department : NULL, TRUE, array( 'options' => $departments, 'toggles' => $departmentToggles ) ); foreach ( $availableDepartments as $department ) { if ( \IPS\Member::loggedIn()->language()->checkKeyExists("nexus_department_{$department->id}_desc") ) { $return[ "department_message_{$department->id}" ] = \IPS\Member::loggedIn()->language()->addToStack("nexus_department_{$department->id}_desc"); } } /* Severity */ $return = array_merge( $return, $severityFields ); /* Purchase */ $return = array_merge( $return, $purchaseFields ); /* Custom Fields */ foreach ( $customFields as $field ) { $validation = NULL; if ( $field->required ) { $validation = function( $val ) use ( $field, $departmentToggles ) { if ( $val === '' and ( !isset( \IPS\Request::i()->support_department ) or in_array( "nexus_cfield_{$field->id}", $departmentToggles[ \IPS\Request::i()->support_department ] ) ) ) { throw new \DomainException('form_required'); } }; } $return[] = $field->buildHelper( NULL, $validation ); } /* Return */ $return['content'] = $content; return $return; } /** * Process create/edit form * * @param array $values Values from form * @return void */ public function processForm( $values ) { parent::processForm( $values ); /* Pay-Per-Incident? */ $department = Department::load( $values['support_department'] ); if ( $ppiCost = $department->ppiCost() ) { try { $purchase = \IPS\nexus\Purchase::constructFromData( \IPS\Db::i()->select( '*', 'nexus_purchases', array( 'ps_app=? AND ps_type=? AND ps_item_id=?', 'nexus', 'ppi', $department->id ) )->first() ); $this->ppi_invoice = $purchase->original_invoice; $purchase->delete(); } catch ( \UnderflowException $e ) { $invoice = new \IPS\nexus\Invoice; $invoice->member = \IPS\nexus\Customer::loggedIn(); $invoice->return_uri = "app=nexus&module=support&controller=home&do=create&department={$department->id}&title=" . urlencode( $values['support_title'] ); $item = new \IPS\nexus\extensions\nexus\Item\SupportCharge( \IPS\Member::loggedIn()->language()->get( 'nexus_department_' . $department->_id ), $ppiCost ); $item->id = $department->id; if ( $department->ppi_tax ) { try { $item->tax = \IPS\nexus\Tax::load( $department->ppi_tax ); } catch ( \OutOfRangeException $e ) {} } $invoice->addItem( $item ); $invoice->save(); \IPS\Output::i()->redirect( $invoice->checkoutUrl() ); } } $this->department = $department; /* Purchase */ if ( $department->packages and isset( $values[ 'support_purchase_' . $department->serializedPackageIds() ] ) ) { $purchase = $values[ 'support_purchase_' . $department->serializedPackageIds() ]; if ( $purchase ) { $this->purchase = $values[ 'support_purchase_' . $department->serializedPackageIds() ]; } } /* Account */ if ( isset( \IPS\Request::i()->account ) and ( \IPS\Request::i()->account == \IPS\nexus\Customer::loggedIn()->member_id or array_key_exists( \IPS\Request::i()->account, iterator_to_array( \IPS\nexus\Customer::loggedIn()->parentContacts( array( 'support=1' ) ) ) ) ) ) { $this->member = \IPS\nexus\Customer::load( \IPS\Request::i()->account )->member_id; } elseif ( $this->purchase ) { $this->member = $this->purchase->member->member_id; } /* Status */ if ( !isset( $values['support_status'] ) ) { $this->status = Status::load( TRUE, 'status_default_member' ); } /* Selected Severity */ $defaultSeverity = Severity::load( TRUE, 'sev_default' ); if ( isset( $values[ 'support_severity_' . $department->serializedSeverityIds() ] ) ) { $this->severity = Severity::load( $values[ 'support_severity_' . $department->serializedSeverityIds() ] ); } else { $availableSeverities = $department->availableSeverities(); if ( count( $availableSeverities ) === 1 ) { $this->severity = array_pop( $availableSeverities ); } else { $this->severity = $defaultSeverity; } } /* Purchase sets its own severity? */ if ( $this->severity->id === $defaultSeverity->id and $this->purchase ) { if ( $overrideSeverity = $this->purchase->supportSeverity() ) { $this->severity = $overrideSeverity; } elseif ( $parent = $this->purchase->parent() and $overrideSeverity = $parent->supportSeverity() ) { $this->severity = $overrideSeverity; } else { foreach ( $this->purchase->children() as $childPurchase ) { if ( $overrideSeverity = $childPurchase->supportSeverity() ) { $this->severity = $overrideSeverity; break; } } } } /* Custom Fields */ $customFieldObjects = CustomField::roots(); $cfields = array(); foreach ( $values as $k => $v ) { if ( mb_substr( $k, 0, 13 ) === 'nexus_cfield_' ) { $k = mb_substr( $k, 13 ); $class = $customFieldObjects[ $k ]->buildHelper(); $cfields[ $k ] = $class::stringValue( $v ); } } $this->cfields = $cfields; } /** * Process created object AFTER the object has been created * * @param \IPS\Content\Comment|NULL $comment The first comment * @param array $values Values from form * @return void */ public function processAfterCreate( $comment, $values ) { if ( $this->member ) { $data = array( 'id' => $this->id, 'title' => $this->title ); if ( $comment->type === Reply::REPLY_STAFF ) { $data['type'] = 'staff'; } elseif ( $comment->type === Reply::REPLY_EMAIL ) { $data['type'] = 'email'; } \IPS\nexus\Customer::load( $this->member )->log( 'support', $data ); } parent::processAfterCreate( $comment, $values ); $this->sendNotifications(); } /** * Set buttons * * @param \IPS\Helpers\Form $form The form * @param string $key 'note' or 'reply' * @return void */ protected function _staffFormButtons( \IPS\Helpers\Form $form, $key ) { if ( isset( \IPS\Request::i()->sort ) ) { $form->hiddenValues['sort'] = \IPS\Request::i()->sort; $form->hiddenValues['filter'] = \IPS\Request::i()->filter; $form->addButton( $key . '_and_return_to_list', 'submit', NULL, 'ipsButton ipsButton_alternate', array( 'name' => 'goto', 'value' => 'list' ) ); if ( $next = $this->nextPrevious( \IPS\Request::i()->sort, \IPS\Request::i()->filter ) ) { $form->addButton( $key . '_and_go_to_next', 'submit', NULL, 'ipsButton ipsButton_alternate', array( 'name' => 'goto', 'value' => 'next' ) ); } } else { $form->addButton( $key . '_and_go_to_list', 'submit', NULL, 'ipsButton ipsButton_alternate', array( 'name' => 'goto', 'value' => 'list' ) ); } } /** * Redirect after submission * * @param bool $pending If the submitted comment is pending * @return void */ protected function _staffFormRedirect( $pending=FALSE ) { if ( !$pending and isset( \IPS\Request::i()->goto ) ) { switch ( \IPS\Request::i()->goto ) { case 'next': if ( $next = $this->nextPrevious( \IPS\Request::i()->sort, \IPS\Request::i()->filter ) ) { \IPS\Output::i()->redirect( $next->acpUrl()->setQueryString( array( 'sort' => \IPS\Request::i()->sort, 'filter' => \IPS\Request::i()->filter ) ) ); } break; case 'list': $listUrl = \IPS\Http\Url::internal("app=nexus&module=support&controller=requests"); if ( isset( \IPS\Request::i()->sort ) ) { preg_match( '/^(([a-z_]+(, )?)+)( ((a|de)sc))?$/i', trim( \IPS\Request::i()->sort ), $matches ); if ( isset( $matches[1] ) ) { $listUrl = $listUrl->setQueryString( 'sortby', $matches[1] ); } if ( isset( $matches[5] ) ) { $listUrl = $listUrl->setQueryString( 'sortdirection', $matches[5] ); } } if ( isset( \IPS\Request::i()->filter ) ) { $listUrl = $listUrl->setQueryString( 'filter', \IPS\Request::i()->filter ); } \IPS\Output::i()->redirect( $listUrl ); break; } } $lastPageUrl = $this->acpUrl(); $order = isset( \IPS\Request::i()->order ) ? \IPS\Request::i()->order : ( isset( \IPS\Request::i()->cookie['support_order'] ) ? \IPS\Request::i()->cookie['support_order'] : 'desc' ); if ( $order === 'asc' ) { $lastPageUrl = $lastPageUrl->setQueryString( 'page', $this->commentPageCount() ); } if ( $pending ) { $lastPageUrl = $lastPageUrl->setQueryString( 'pending', 1 ); } \IPS\Output::i()->redirect( $lastPageUrl ); } /** * Add Note Form * * @return \IPS\Helpers\Form */ public function noteForm() { $form = new \IPS\Helpers\Form( 'note', 'add_note' ); $form->class = 'ipsForm_vertical'; $form->add( new \IPS\Helpers\Form\Editor( 'note', NULL, TRUE, array( 'app' => 'nexus', 'key' => 'Support', 'minimize' => 'support__note_placeholder', 'autoSaveKey' => md5( "req{$this->id}-note" ) ) ) ); $form->add( new \IPS\Helpers\Form\Select( 'department', $this->department->id, FALSE, array( 'options' => \IPS\nexus\Support\Department::rootsAsArray() ), NULL, NULL, NULL, 'staffNoteDepartment' ) ); $form->add( new \IPS\Helpers\Form\Select( 'status', $this->status->id, FALSE, array( 'options' => \IPS\nexus\Support\Status::rootsAsArray() ), NULL, NULL, NULL, 'staffNoteStatus' ) ); $form->add( new \IPS\Helpers\Form\Select( 'assign_to', $this->staff ? $this->staff->member_id : 0, FALSE, array( 'options' => array_merge( array( 0 => 'unassigned' ), static::staff() ) ), NULL, NULL, NULL, 'staffNoteAssign' ) ); /* Buttons */ $this->_staffFormButtons( $form, 'note' ); /* Handle submissions */ if ( $values = $form->values() ) { $message = new Reply; $message->request = $this->id; $message->member = \IPS\Member::loggedIn()->member_id; $message->type = Reply::REPLY_HIDDEN; $message->post = $values['note']; $message->hidden = TRUE; $message->date = time(); $message->ip_address = \IPS\Request::i()->ipAddress(); $message->save(); \IPS\File::claimAttachments( 'reply-nexus/support-' . $this->id, $this->id, $message->id ); /* Update the request */ $this->department = Department::load( $values['department'] ); $this->status = Status::load( $values['status'] ); $this->staff = $values['assign_to'] ? \IPS\Member::load( $values['assign_to'] ) : NULL; $this->save(); $message->sendNotifications(); /* Redirect */ $this->_staffFormRedirect(); } return $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'support', 'nexus' ) ), 'noteForm' ), $this ); } /** * Staff Reply Form * * @return \IPS\Helpers\Form */ public function staffReplyForm() { /* Init */ $form = new \IPS\Helpers\Form( 'reply', 'reply' ); $form->attributes = array( 'data-controller' => 'nexus.admin.support.staffreply' ); $lastReply = $this->comments( 1, 0, 'date', 'desc' ); $lastReplyId = $lastReply->id; $form->hiddenValues['latestReply'] = $lastReplyId; /* Stock Actions */ $stockActions = array( 0 => '' ); foreach ( \IPS\nexus\Support\StockAction::roots( NULL, NULL, "action_show_in='*' OR " . \IPS\Db::i()->findInSet( 'action_show_in', array( $this->department->id ) ) ) as $action ) { $stockActions[ $action->id ] = $action->_title; } /* To/Cc/Bcc */ $defaultRecipients = $this->getDefaultRecipients(); $form->add( new \IPS\Helpers\Form\Email( 'to', $defaultRecipients['to'], FALSE, array( 'disabled' => TRUE ) ) ); $form->add( new \IPS\Helpers\Form\Text( 'cc', $defaultRecipients['cc'], FALSE, array( 'autocomplete' => array( 'unique' => TRUE, 'forceLower' => TRUE ) ) ) ); $form->add( new \IPS\Helpers\Form\Text( 'bcc', $defaultRecipients['bcc'], FALSE, array( 'autocomplete' => array( 'unique' => TRUE, 'forceLower' => TRUE ) ) ) ); /* Attributes */ if ( count( \IPS\nexus\Support\StockAction::roots() ) ) { $form->add( new \IPS\Helpers\Form\Select( 'stock_action', NULL, FALSE, array( 'options' => $stockActions ), NULL, NULL, NULL, 'stock_action' ) ); } $form->add( new \IPS\Helpers\Form\Editor( 'message', NULL, TRUE, array( 'app' => 'nexus', 'key' => 'Support', 'minimize' => 'support__comment_placeholder', 'autoSaveKey' => md5( "req{$this->id}-reply" ) ) ) ); $form->add( new \IPS\Helpers\Form\Select( 'department', $this->department->id, FALSE, array( 'options' => \IPS\nexus\Support\Department::rootsAsArray() ), NULL, NULL, NULL, 'staffReplyDepartment' ) ); $form->add( new \IPS\Helpers\Form\Select( 'status', \IPS\nexus\Support\Status::load( TRUE, 'status_default_staff' )->id, FALSE, array( 'options' => \IPS\nexus\Support\Status::rootsAsArray() ), NULL, NULL, NULL, 'staffReplyStatus' ) ); $form->add( new \IPS\Helpers\Form\Select( 'assign_to', $this->staff_lock ? ( $this->staff ? $this->staff->member_id : 0 ) : 0, FALSE, array( 'options' => array( 0 => 'unassigned' ) + static::staff() ), NULL, NULL, NULL, 'staffReplyAssign' ) ); /* Buttons */ $this->_staffFormButtons( $form, 'reply' ); /* Handle Submissions */ if ( $values = $form->values() ) { /* Update notify */ $notify = $this->notify; foreach ( $values['cc'] as $cc ) { foreach ( $notify as $n ) { if ( $n['value'] === $cc ) { continue 2; } } $notify[] = array( 'type' => 'e', 'value' => $cc ); } foreach ( $values['bcc'] as $cc ) { foreach ( $notify as $k => $n ) { if ( $n['value'] === $cc ) { if ( $n['bcc'] ) { unset( $notify[ $k ]['bcc'] ); } continue 2; } } $notify[] = array( 'type' => 'e', 'value' => $cc, 'bcc' => 1 ); } $this->notify = $notify; /* Are we blocking this? */ $pending = FALSE; if ( \IPS\Request::i()->latestReply != $lastReplyId ) { $pending = TRUE; } /* Create the message */ $message = new Reply; $message->request = $this->id; $message->member = \IPS\Member::loggedIn()->member_id; $message->type = $pending ? Reply::REPLY_PENDING : Reply::REPLY_STAFF; $message->post = $values['message']; $message->hidden = $pending; $message->date = time(); $message->cc = implode( ',', $values['cc'] ); $message->bcc = implode( ',', $values['bcc'] ); $message->ip_address = \IPS\Request::i()->ipAddress(); $message->save(); \IPS\File::claimAttachments( 'reply-nexus/support-' . $this->id, $this->id, $message->id ); $message->postCreate(); /* Update the request */ $newDepartment = Department::load( $values['department'] ); if ( $this->department != $newDepartment ) { $this->log( 'department', $this->department, $newDepartment ); $this->department = $newDepartment; } $this->status = Status::load( $values['status'] ); $newStaff = $values['assign_to'] ? \IPS\Member::load( $values['assign_to'] ) : NULL; if ( $this->staff != $newStaff ) { $this->log( 'staff', $this->staff, $newStaff ); $this->staff = $newStaff; } $this->save(); /* Send notifications */ if ( !$pending ) { $message->sendCustomerNotifications( $values['to'] ?: $defaultRecipients['to'], $values['cc'], $values['bcc'] ); $message->sendNotifications(); } /* Redirect */ $this->_staffFormRedirect( $pending ); } /* Display */ return $form->customTemplate( array( call_user_func_array( array( \IPS\Theme::i(), 'getTemplate' ), array( 'support', 'nexus' ) ), 'staffReplyForm' ), $this ); } /** * Validate email * * @param string $email Email address * @return void * @throws \DomainException */ public static function _validateEmail( $email ) { if ( $email and filter_var( $email, FILTER_VALIDATE_EMAIL ) === FALSE ) { throw new \InvalidArgumentException('form_email_bad'); } } /* !Permissions */ /** * Get items with permisison check * * @param array $where Where clause * @param string $order MySQL ORDER BY clause (NULL to order by date) * @param int|array $limit Limit clause * @param string|NULL $permissionKey A key which has a value in the permission map (either of the container or of this class) matching a column ID in core_permission_index or NULL to ignore permissions * @param bool|NULL $includeHiddenItems Include hidden files? Boolean or NULL to detect if currently logged member has permission * @param int $queryFlags Select bitwise flags * @param \IPS\Member $member The member (NULL to use currently logged in member) * @param bool $joinContainer If true, will join container data (set to TRUE if your $where clause depends on this data) * @param bool $joinComments If true, will join comment data (set to TRUE if your $where clause depends on this data) * @param bool $joinReviews If true, will join review data (set to TRUE if your $where clause depends on this data) * @param bool $countOnly If true will return the count * @param array|null $joins Additional arbitrary joins for the query * @param mixed $skipPermission If you are getting records from a specific container, pass the container to reduce the number of permission checks necessary or pass TRUE to skip conatiner-based permission. You must still specify this in the $where clause * @param bool $joinTags If true, will join the tags table * @param bool $joinAuthor If true, will join the members table for the author * @param bool $joinLastCommenter If true, will join the members table for the last commenter * @return \IPS\Patterns\ActiveRecordIterator|int */ public static function getItemsWithPermission( $where=array(), $order=NULL, $limit=10, $permissionKey='read', $includeHiddenItems=NULL, $queryFlags=0, \IPS\Member $member=NULL, $joinContainer=FALSE, $joinComments=FALSE, $joinReviews=FALSE, $countOnly=FALSE, $joins=NULL, $skipPermission=FALSE, $joinTags=TRUE, $joinAuthor=TRUE, $joinLastCommenter=TRUE ) { /* Get customer object */ if ( !$member ) { $member = \IPS\nexus\Customer::loggedIn(); } elseif ( !( $member instanceof \IPS\nexus\Customer ) ) { $member = \IPS\nexus\Customer::load( $member->member_id ); } $extraClause = array( 'r_member=?', $member->member_id ); /* Work out the clause for parent alternative contacts */ $alternativeContactWhere = array(); foreach ( $member->parentContacts() as $contact ) { if ( $contact->support ) { $alternativeContactWhere[] = '( r_member=' . $contact->main_id->member_id . ' )'; } else { $alternativeContactWhere[] = '( r_member=' . $contact->main_id->member_id . ' AND ' . \IPS\Db::i()->in( 'r_purchase', $contact->purchaseIds() ) . ' )'; } } if ( count( $alternativeContactWhere ) ) { $extraClause[0] = '( ' . $extraClause[0] . ' OR ( ' . implode( ' OR ', $alternativeContactWhere ) . ' ) )'; } /* Work out the clause for admins */ if ( ( !\IPS\Dispatcher::hasInstance() or \IPS\Dispatcher::i()->controllerLocation === 'admin' ) and $member->isAdmin() ) { $extraClause[0] = '( ' . $extraClause[0] . " OR dpt_staff='*' OR " . \IPS\Db::i()->findInSet( 'dpt_staff', Department::staffDepartmentPerms( $member ) ) . ' )'; $joins[] = array( 'from' => 'nexus_support_departments', 'where' => 'dpt_id=r_department' ); } /* Do it */ $where[] = $extraClause; return parent::getItemsWithPermission( $where, $order, $limit, $permissionKey, $includeHiddenItems, $queryFlags, $member, $joinContainer, $joinComments, $joinReviews, $countOnly, $joins, $skipPermission, $joinTags, $joinAuthor, $joinLastCommenter ); } /** * Can view? * * @param \IPS\Member|NULL $member The member to check for or NULL for the currently logged in member * @return bool */ public function canView( $member=NULL ) { /* Get customer object */ if ( !$member ) { $member = \IPS\nexus\Customer::loggedIn(); } elseif ( !( $member instanceof \IPS\nexus\Customer ) ) { $member = \IPS\nexus\Customer::load( $member->member_id ); } /* Owner */ if ( $member->member_id == $this->author()->member_id ) { return TRUE; } /* Staff */ elseif ( ( !\IPS\Dispatcher::hasInstance() or \IPS\Dispatcher::i()->controllerLocation === 'admin' ) and $member->isAdmin() and ( $this->department->staff === '*' or count( array_intersect( explode( ',', $this->department->staff ), Department::staffDepartmentPerms( $member ) ) ) ) ) { return TRUE; } /* Altcontact */ elseif ( in_array( $this->author()->member_id, array_keys( iterator_to_array( $member->parentContacts( $this->purchase ? array( 'support=1 OR ' . \IPS\Db::i()->findInSet( 'purchases', array( $this->purchase->id ) ) ) : array( 'support=1' ) ) ) ) ) ) { return TRUE; } return FALSE; } /** * Can comment? * * @param \IPS\Member\NULL $member The member (NULL for currently logged in member) * @return bool */ public function canComment( $member=NULL ) { return parent::canComment( $member ) and !$this->status->is_locked; } /* !Notifications */ /** * Get default to/cc/bcc * * @return array */ public function getDefaultRecipients() { $to = $this->member ? $this->author()->email : $this->email; $cc = array(); $bcc = array(); foreach ( $this->notify as $notify ) { try { $email = ( $notify['type'] === 'm' ) ? \IPS\Member::load( $notify['value'] )->email : $notify['value']; } catch ( \OutOfRangeException $e ) { continue; } if ( $email != $to ) { if ( isset( $notify['bcc'] ) and $notify['bcc'] ) { $bcc[] = $email; } else { $cc[] = $email; } } } return array( 'to' => $to, 'cc' => $cc, 'bcc' => $bcc ); } /** * Send notifications * * @return void */ public function sendNotifications() { $staffIds = array_keys( static::staff() ); foreach ( \IPS\Db::i()->select( 'staff_id', 'nexus_support_notify', array( array( 'type=?', 'n' ), array( "(departments='*' OR " . \IPS\Db::i()->findInSet( 'departments', array( $this->department->id ) ) . ')' ) ) ) as $staffId ) { if ( in_array( $staffId, $staffIds ) ) { $member = \IPS\Member::load( $staffId ); if ( $this->department->staff === '*' or count( array_intersect( \IPS\nexus\Support\Department::staffDepartmentPerms( $member ), explode( ',', $this->department->staff ) ) ) ) { $email = \IPS\Email::buildFromTemplate( 'nexus', 'staffNotifyNew', array( $this ) ); $email->from = ( $this->department->email ) ? $this->department->email : \IPS\Settings::i()->email_out; switch ( \IPS\Settings::i()->nexus_sout_from ) { case 'staff': $email->fromName = $this->supportAuthor()->name(); break; case 'dpt': $email->fromName = $email->language->get( 'nexus_department_' . $this->department->_id ); break; default: $email->fromName = \IPS\Settings::i()->nexus_sout_from; break; } $email->unsubscribe = array( 'text' => 'manage_notification_settings', 'link' => \IPS\Http\Url::internal( 'app=nexus&module=support&controller=requests&do=notifications', 'admin' ) ); $email->send( $member ); } } else { \IPS\Db::i()->delete( 'nexus_support_notify', array( 'staff_id=?', $staffId ) ); } } } /* !Other */ /** * Get replies, and the log, for staff view * * @param string $orderDirection "asc" or "desc" * @return \IPS\Patterns\UnionIterator */ public function repliesAndLog( $orderDirection='asc' ) { $orderDirection = ( $orderDirection === 'desc' ) ? $orderDirection : 'asc'; $replies = $this->comments( NULL, NULL, 'date', $orderDirection ); $where = array( array( 'rlog_request=?', $this->id ) ); $first = NULL; foreach ( $replies as $last ) { if ( $first === NULL ) { $first = $last; } } if ( $first ) { if ( $orderDirection === 'asc' ) { $where[] = array( 'rlog_date>?', $first->date ); if ( $firstOnNextPage = $this->comments( 1, NULL, 'date', 'asc', NULL, NULL, \IPS\DateTime::ts( $last->date ) ) ) { $where[] = array( 'rlog_datedate ); } } else { $where[] = array( 'rlog_date>?', $last->date ); if ( $firstOnNextPage = $this->comments( 1, NULL, 'date', 'asc', NULL, NULL, NULL, array( 'reply_datedate ) ) ) { $where[] = array( 'rlog_datedate ); } } } $iterator = new \IPS\Patterns\UnionIterator( $orderDirection ); $iterator->attachIterator( new \ArrayIterator( $replies ), 'date' ); $iterator->attachIterator( new \IPS\Patterns\ActiveRecordIterator( \IPS\Db::i()->select( '*', 'nexus_support_request_log', $where, 'rlog_date ' . $orderDirection ), 'IPS\nexus\Support\Log' ), 'date' ); return $iterator; } /** * @brief Author */ protected $_author; /** * Get author * * @return \IPS\nexus\Support\Author */ public function supportAuthor() { if ( $this->_author === NULL ) { if ( $this->member ) { try { $this->_author = new Author\Member( \IPS\nexus\Customer::load( $this->member ) ); } catch ( \OutOfRangeException $e ) { $this->_author = new Author\Member( new \IPS\nexus\Customer ); } } else { $this->_author = new Author\Email( $this->email ); } } return $this->_author; } /** * @brief Staff views */ protected $_staffViews; /** * Staff views * * @return array */ public function staffViews() { if ( $this->_staffViews === NULL ) { $this->_staffViews = iterator_to_array( \IPS\Db::i()->select( '*', 'nexus_support_views', array( 'view_rid=?', $this->id ), 'view_last DESC' )->setKeyField( 'view_member' ) ); } return $this->_staffViews; } /** * Set staff view * * @param \IPS\Member $staff The staff member viewing * @return void */ public function setStaffView( \IPS\Member $staff ) { $this->staffViews(); if ( !isset( $this->_staffViews[ $staff->member_id ] ) ) { $view = array( 'view_rid' => $this->id, 'view_member' => $staff->member_id, 'view_first' => time(), 'view_last' => time(), 'view_reply' => 0 ); \IPS\Db::i()->insert( 'nexus_support_views', $view, TRUE ); } else { $view = $this->_staffViews[ $staff->member_id ]; $view['view_last'] = time(); \IPS\Db::i()->update( 'nexus_support_views', array( 'view_last' => time() ), array( 'view_rid=? AND view_member=?', $this->id, $staff->member_id ) ); unset( $this->_staffViews[ $staff->member_id ] ); } array_unshift( $this->_staffViews, $view ); } /** * Next/Previous Cache */ protected $nextPrevious = array(); /** * Get Next/Previous Request (for viewing in the ACP) * * @param string $sort ORDER BY clause * @param string $filter The filter to use * @param bool $previous If true, will return previous (otherwise will return next) * @return \IPS\nexus\Support\Request|NULL */ public function nextPrevious( $sort, $filter=NULL, $previous=FALSE ) { if ( !isset( $this->nextPrevious[ md5( $sort . $filter . $previous ) ] ) ) { /* Init */ $select = array( 'nexus_support_requests.*, nexus_support_departments.dpt_staff' ); $joins = array( 'nexus_support_departments' => 'dpt_id=r_department' ); $where = array( array( 'r_id<>?', $this->id ) ); /* Validate the sort */ if ( !preg_match( '/^(([a-z_]+(, )?)+)( ((a|de)sc))?$/i', trim( $sort ), $matches ) ) { return NULL; } $lastColumn = NULL; $ascDesc= isset( $matches[5] ) ? mb_strtolower( trim( $matches[5] ) ) : 'desc'; foreach ( explode( ',', $matches[1] ) as $column ) { $column = trim( $column ); if ( !in_array( $column, array( 'r_title', 'sev_position', 'dpt_position', 'r_member', 'r_replies', 'r_started', 'r_last_new_reply', 'r_last_reply', 'r_last_staff_reply', 'status_position' ) ) ) { return NULL; } if ( $column === 'sev_position' ) { $select[] = 'nexus_support_severities.sev_position'; $joins['nexus_support_severities'] = 'sev_id=r_severity'; } if ( $column === 'dpt_position' ) { $select[] = 'nexus_support_departments.dpt_position'; } if ( $column === 'status_position' ) { $select[] = 'nexus_support_statuses.status_position'; $joins['nexus_support_statuses'] = 'status_id=r_status'; } if ( mb_substr( $column, 0, 2 ) === 'r_' ) { $lastColumn = mb_substr( $column, 2 ); } } if ( !$lastColumn ) { return NULL; } /* Build the where */ $where[] = array( "( dpt_staff='*' OR " . \IPS\Db::i()->findInSet( 'dpt_staff', \IPS\nexus\Support\Department::staffDepartmentPerms() ) . ')' ); switch ( $filter ) { case 'my_departments': $myFilters = static::myFilters(); $where[] = array( $myFilters['whereClause'] ); break; case 'tracked': $where[] = 'request_id IS NOT NULL'; break; case 'my_last_replies': $where[] = \IPS\Db::i()->in( 'r_id', iterator_to_array( \IPS\Db::i()->select( 'DISTINCT(reply_request)', 'nexus_support_replies', array( 'reply_member=?', \IPS\Member::loggedIn()->member_id ), 'reply_date DESC', 100 ) ) ); $select = array( 'nexus_support_tracker.request_id' ); $joins['nexus_support_tracker'] = array( 'request_id=r_id AND member_id=?', \IPS\Member::loggedIn()->member_id ); break; } /* And stick the next/previous where on */ if ( $previous ) { $symbol = ( $ascDesc === 'asc' ? '<' : '>' ); } else { $symbol = ( $ascDesc === 'asc' ? '>' : '<' ); } $where[] = array( "r_{$lastColumn}{$symbol}?", $this->$lastColumn ); /* Build the query */ $select = \IPS\Db::i()->select( implode( ', ', $select ), 'nexus_support_requests', $where, $sort, 1 ); foreach ( $joins as $table => $clause ) { $select->join( $table, $clause ); } /* Return */ try { $this->nextPrevious[ md5( $sort . $filter . $previous ) ] = static::constructFromData( $select->first() ); } catch ( \UnderflowException $e ) { $this->nextPrevious[ md5( $sort . $filter . $previous ) ] = NULL; } } return $this->nextPrevious[ md5( $sort . $filter . $previous ) ]; } /** * Log that something happened * * @param string $action What changed * @param mixed $old Old value * @param mixed $new New value * @return void */ public function log( $action, $old, $new ) { $log = new \IPS\nexus\Support\Log; $log->request = $this; $log->member = \IPS\Member::loggedIn(); $log->action = $action; $log->old = $old; $log->new = $new; $log->date = \IPS\DateTime::create(); $log->save(); } /* !URLs */ /** * @brief Cached URLs */ protected $_url = array(); /** * Get URL * * @param string|NULL $action Action * @return \IPS\Http\Url */ public function url( $action=NULL ) { $_key = md5( $action ); if( !isset( $this->_url[ $_key ] ) ) { $this->_url[ $_key ] = \IPS\Http\Url::internal( "app=nexus&module=support&controller=view&id={$this->id}", 'front', 'support_view' ); if ( $action ) { $this->_url[ $_key ] = $this->_url[ $_key ]->setQueryString( 'do', $action ); } } return $this->_url[ $_key ]; } /** * Get ACP URL * * @param string|NULL $action Action * @return \IPS\Http\Url */ public function acpUrl( $action=NULL ) { $url = \IPS\Http\Url::internal( "app=nexus&module=support&controller=request&id={$this->id}", 'admin' ); if( \IPS\Dispatcher::hasInstance() and \IPS\Dispatcher::i()->controllerLocation === 'admin' and isset( \IPS\Request::i()->sort ) ) { $url = $url->setQueryString( 'sort', \IPS\Request::i()->sort ); } return $url; } /* !Delete */ /** * Delete * * @return void */ public function delete() { \IPS\Db::i()->delete( 'nexus_support_tracker', array( 'request_id=?', $this->id ) ); \IPS\Db::i()->delete( 'nexus_support_views', array( 'view_rid=?', $this->id ) ); parent::delete(); } }