Skip to content
Snippets Groups Projects
class.queue.php 92.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • <?php
    /*********************************************************************
        class.queue.php
    
        Custom (ticket) queues for osTicket
    
        Jared Hancock <jared@osticket.com>
        Peter Rotich <peter@osticket.com>
    
        Copyright (c)  2006-2015 osTicket
    
        http://www.osticket.com
    
        Released under the GNU General Public License WITHOUT ANY WARRANTY.
        See LICENSE.TXT for details.
    
        vim: expandtab sw=4 ts=4 sts=4:
    **********************************************************************/
    
    
    class CustomQueue extends VerySimpleModel {
    
        static $meta = array(
    
            'table' => QUEUE_TABLE,
            'pk' => array('id'),
            'ordering' => array('sort'),
    
            'select_related' => array('parent', 'default_sort'),
    
            'joins' => array(
    
                'children' => array(
                    'reverse' => 'CustomQueue.parent',
                    'constrain' => ['children__id__gt' => 0],
                ),
    
                'columns' => array(
    
                    'reverse' => 'QueueColumnGlue.queue',
    
                    'constrain' => array('staff_id' =>'QueueColumnGlue.staff_id'),
    
                    'broker' => 'QueueColumnListBroker',
    
                'sorts' => array(
                    'reverse' => 'QueueSortGlue.queue',
                    'broker' => 'QueueSortListBroker',
                ),
    
                'default_sort' => array(
                    'constraint' => array('sort_id' => 'QueueSort.id'),
                    'null' => true,
                ),
    
    Peter Rotich's avatar
    Peter Rotich committed
                'exports' => array(
                    'reverse' => 'QueueExport.queue',
                ),
    
                'parent' => array(
                    'constraint' => array(
                        'parent_id' => 'CustomQueue.id',
                    ),
                    'null' => true,
                ),
                'staff' => array(
                    'constraint' => array(
                        'staff_id' => 'Staff.staff_id',
                    )
    
        const FLAG_PUBLIC =           0x0001; // Shows up in e'eryone's saved searches
        const FLAG_QUEUE =            0x0002; // Shows up in queue navigation
        const FLAG_DISABLED =         0x0004; // NOT enabled
    
        const FLAG_INHERIT_CRITERIA = 0x0008; // Include criteria from parent
    
        const FLAG_INHERIT_COLUMNS =  0x0010; // Inherit column layout from parent
        const FLAG_INHERIT_SORTING =  0x0020; // Inherit advanced sorting from parent
        const FLAG_INHERIT_DEF_SORT = 0x0040; // Inherit default selected sort
    
    Peter Rotich's avatar
    Peter Rotich committed
        const FLAG_INHERIT_EXPORT  =  0x0080; // Inherit export fields from parent
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        const FLAG_INHERIT_EVERYTHING = 0x158; // Maskf or all INHERIT flags
    
        var $_conditions;
    
        static function queues() {
    
            return parent::objects()->filter(array(
                'flags__hasbit' => static::FLAG_QUEUE
            ));
        }
    
    
        function __onload() {
            // Ensure valid state
    
            if ($this->hasFlag(self::FLAG_INHERIT_COLUMNS) && !$this->parent_id)
    
                $this->clearFlag(self::FLAG_INHERIT_COLUMNS);
    
    Peter Rotich's avatar
    Peter Rotich committed
    
           if ($this->hasFlag(self::FLAG_INHERIT_EXPORT) && !$this->parent_id)
                $this->clearFlag(self::FLAG_INHERIT_EXPORT);
    
        function getId() {
            return $this->id;
        }
    
        function getName() {
            return $this->title;
        }
    
        function getHref() {
            // TODO: Get base page from getRoot();
            $root = $this->getRoot();
            return 'tickets.php?queue='.$this->getId();
        }
    
        function getRoot() {
            switch ($this->root) {
            case 'T':
            default:
                return 'Ticket';
            }
        }
    
        function getPath() {
            return $this->path ?: $this->buildPath();
        }
    
    
        function criteriaRequired() {
            return true;
        }
    
    
        function getCriteria($include_parent=false) {
            if (!isset($this->criteria)) {
                $this->criteria = is_string($this->config)
                    ? JsonDataParser::decode($this->config)
                    : $this->config;
    
                // XXX: Drop this block in v1.12
    
                // Auto-upgrade v1.10 saved-search criteria to new format
                // But support new style with `conditions` support
    
                $old = @$this->config[0] === '{';
    
                if ($old && is_array($this->criteria)
                    && !isset($this->criteria['conditions'])
                ) {
    
                    // TODO: Upgrade old ORM path names
    
                    // Parse criteria out of JSON if any.
                    $this->criteria = self::isolateCriteria($this->criteria,
                            $this->getRoot());
    
                }
            }
            $criteria = $this->criteria ?: array();
    
            // Support new style with `conditions` support
            if (isset($criteria['criteria']))
                $criteria = $criteria['criteria'];
    
            if ($include_parent && $this->parent_id && $this->parent) {
                $criteria = array_merge($this->parent->getCriteria(true),
                    $criteria);
            }
            return $criteria;
        }
    
        function describeCriteria($criteria=false){
            $all = $this->getSupportedMatches($this->getRoot());
            $items = array();
            $criteria = $criteria ?: $this->getCriteria(true);
            foreach ($criteria as $C) {
                list($path, $method, $value) = $C;
    
                if ($path === ':keywords') {
                    $items[] = Format::htmlchars("\"{$value}\"");
                    continue;
                }
    
                if (!isset($all[$path]))
                    continue;
    
                list($label, $field) = $all[$path];
                $items[] = $field->describeSearch($method, $value, $label);
    
            }
            return implode("\nAND ", $items);
        }
    
        /**
         * Fetch an AdvancedSearchForm instance for use in displaying or
         * configuring this search in the user interface.
         *
         * Parameters:
         * $search - <array> Request parameters ($_POST) used to update the
         *      search beyond the current configuration of the search criteria
    
         * $searchables - search fields - default to current if not provided
    
        function getForm($source=null, $searchable=null) {
            $fields = array();
            $validator = false;
            if (!isset($searchable)) {
                $searchable = $this->getCurrentSearchFields($source);
                $validator = true;
                $fields = array(
                    ':keywords' => new TextboxField(array(
                        'id' => 3001,
                        'configuration' => array(
                            'size' => 40,
                            'length' => 400,
                            'autofocus' => true,
                            'classes' => 'full-width headline',
                            'placeholder' => __('Keywords — Optional'),
                        ),
                    )),
                );
    
            foreach ($searchable as $path=>$field)
                $fields = array_merge($fields, static::getSearchField($field, $path));
    
    
            $form = new AdvancedSearchForm($fields, $source);
    
    
            // Field selection validator
            if ($this->criteriaRequired()) {
                $form->addValidator(function($form) {
                        if (!$form->getNumFieldsSelected())
                            $form->addError(__('No fields selected for searching'));
                    });
            }
    
    
            // Load state from current configuraiton
            if (!$source) {
                foreach ($this->getCriteria() as $I) {
                    list($path, $method, $value) = $I;
                    if ($path == ':keywords' && $method === null) {
                        if ($F = $form->getField($path))
                            $F->value = $value;
                        continue;
                    }
    
                    if (!($F = $form->getField("{$path}+search")))
                        continue;
                    $F->value = true;
    
                    if (!($F = $form->getField("{$path}+method")))
                        continue;
                    $F->value = $method;
    
                    if ($value && ($F = $form->getField("{$path}+{$method}")))
                        $F->value = $value;
                }
            }
            return $form;
        }
    
        /**
         * Fetch a bucket of fields for a custom search. The fields should be
         * added to a form before display. One searchable field may encompass 10
         * or more actual fields because fields are expanded to support multiple
         * search methods along with the fields for each search method. This
         * method returns all the FormField instances for all the searchable
         * model fields currently in use.
         *
         * Parameters:
         * $source - <array> data from a request. $source['fields'] is expected
         *      to contain a list extra fields by ORM path, of newly added
         *      fields not yet saved in this object's getCriteria().
         */
    
        function getCurrentSearchFields($source=array(), $criteria=array()) {
    
            static $basic = array(
                'Ticket' => array(
    
                    'status__state',
                    'dept_id',
                    'assignee',
                    'topic_id',
                    'created',
                    'est_duedate',
                )
            );
    
            $all = $this->getSupportedMatches();
            $core = array();
    
            // Include basic fields for new searches
            if (!isset($this->id))
                foreach ($basic[$this->getRoot()] as $path)
                    if (isset($all[$path]))
                        $core[$path] = $all[$path];
    
            // Add others from current configuration
    
            foreach ($criteria ?: $this->getCriteria() as $C) {
    
                list($path) = $C;
                if (isset($all[$path]))
                    $core[$path] = $all[$path];
            }
    
            if (isset($source['fields']))
                foreach ($source['fields'] as $path)
                    if (isset($all[$path]))
                        $core[$path] = $all[$path];
    
            return $core;
        }
    
    
        /**
        * Fetch all supported ORM fields filterable by this search object.
        */
        function getSupportedFilters() {
            return static::getFilterableFields($this->getRoot());
        }
    
    
        /**
         * Get get supplemental matches for public queues.
         *
         */
    
        function getSupplementalMatches() {
            return array();
        }
    
        function getSupplementalCriteria() {
            return array();
        }
    
    
        /**
         * Fetch all supported ORM fields searchable by this search object. The
         * returned list represents searchable fields, keyed by the ORM path.
         * Use ::getCurrentSearchFields() or ::getSearchField() to retrieve for
         * use in the user interface.
         */
        function getSupportedMatches() {
            return static::getSearchableFields($this->getRoot());
        }
    
        /**
         * Trace ORM fields from a base object and retrieve a complete list of
         * fields which can be used in an ORM query based on the base object.
         * The base object must implement Searchable interface and extend from
         * VerySimpleModel. Then all joins from the object are also inspected,
         * and any which implement the Searchable interface are traversed and
         * automatically added to the list. The resulting list is cached based
         * on the $base class, so multiple calls for the same $base return
         * quickly.
         *
         * Parameters:
         * $base - Class, name of a class implementing Searchable
         * $recurse - int, number of levels to recurse, default is 2
         * $cache - bool, cache results for future class for the same base
         * $customData - bool, include all custom data fields for all general
         *      forms
         */
        static function getSearchableFields($base, $recurse=2,
            $customData=true, $exclude=array()
        ) {
            static $cache = array(), $otherFields;
    
            // Early exit if already cached
            $fields = &$cache[$base];
            if ($fields)
                return $fields;
    
    
            if (!in_array('Searchable', class_implements($base)))
                return array();
    
    
            $fields = $fields ?: array();
            foreach ($base::getSearchableFields() as $path=>$F) {
                if (is_array($F)) {
                    list($label, $field) = $F;
                }
                else {
    
                    $label = $F->getLocal('label');
    
                    $field = $F;
                }
                $fields[$path] = array($label, $field);
            }
    
            if ($customData && $base::supportsCustomData()) {
                if (!isset($otherFields)) {
                    $otherFields = array();
                    $dfs = DynamicFormField::objects()
                        ->filter(array('form__type' => 'G'))
                        ->select_related('form');
                    foreach ($dfs as $field) {
                        $otherFields[$field->getId()] = array($field->form,
                            $field->getImpl());
                    }
                }
                foreach ($otherFields as $id=>$F) {
                    list($form, $field) = $F;
                    $label = sprintf("%s / %s",
    
                        $form->getTitle(), $field->getLocal('label'));
    
                    $fields["entries__answers!{$id}__value"] = array(
                        $label, $field);
                }
            }
    
            if ($recurse) {
                $exclude[$base] = 1;
                foreach ($base::getMeta('joins') as $path=>$j) {
                    $fc = $j['fkey'][0];
                    if (isset($exclude[$fc]) || $j['list'])
                        continue;
                    foreach (static::getSearchableFields($fc, $recurse-1,
                        true, $exclude)
                    as $path2=>$F) {
                        list($label, $field) = $F;
                        $fields["{$path}__{$path2}"] = array(
                            sprintf("%s / %s", $fc, $label),
                            $field);
                    }
                }
            }
    
    
            // Sort the field listing by the (localized) label name
            if (function_exists('collator_create')) {
                $coll = Collator::create(Internationalization::getCurrentLanguage());
                $keys = array_map(function($a) use ($coll) {
    
                    return $coll->getSortKey($a[0]); #nolint
    
                }, $fields);
            }
            else {
                // Fall back to 8-bit string sorting
                $keys = array_map(function($a) { return $a[0]; }, $fields);
            }
            array_multisort($keys, $fields);
    
    
      /**
         * Fetch all searchable fileds, for the base object  which support quick filters.
         */
        function getFilterableFields($object) {
            $filters = array();
            foreach (static::getSearchableFields($object) as $p => $f) {
                list($label, $field) = $f;
                if ($field && $field->supportsQuickFilter())
                    $filters[$p] = $f;
            }
    
            return $filters;
        }
    
    
        /**
         * Fetch the FormField instances used when for configuring a searchable
         * field in the user interface. This is the glue between a field
         * representing a searchable model field and the configuration of that
         * search in the user interface.
         *
         * Parameters:
         * $F - <array<string, FormField>> the label and the FormField instance
         *      representing the configurable search
         * $name - <string> ORM path for the search
         */
        static function getSearchField($F, $name) {
            list($label, $field) = $F;
    
            $pieces = array();
            $pieces["{$name}+search"] = new BooleanField(array(
                'id' => sprintf('%u', crc32($name)) >> 1,
                'configuration' => array(
                    'desc' => $label ?: $field->getLocal('label'),
                    'classes' => 'inline',
                ),
            ));
            $methods = $field->getSearchMethods();
    
    aydreeihn's avatar
    aydreeihn committed
    
            //remove future options for datetime fields that can't be in the future
            if (in_array($field->getLabel(), DateTimeField::getPastPresentLabels()))
              unset($methods['ndays'], $methods['future'], $methods['distfut']);
    
    
            $pieces["{$name}+method"] = new ChoiceField(array(
                'choices' => $methods,
                'default' => key($methods),
                'visibility' => new VisibilityConstraint(new Q(array(
                    "{$name}+search__eq" => true,
                )), VisibilityConstraint::HIDDEN),
            ));
            $offs = 0;
            foreach ($field->getSearchMethodWidgets() as $m=>$w) {
                if (!$w)
                    continue;
                list($class, $args) = $w;
                $args['required'] = true;
                $args['__searchval__'] = true;
                $args['visibility'] = new VisibilityConstraint(new Q(array(
                        "{$name}+method__eq" => $m,
                    )), VisibilityConstraint::HIDDEN);
                $pieces["{$name}+{$m}"] = new $class($args);
            }
            return $pieces;
        }
    
        function getField($path) {
            $searchable = $this->getSupportedMatches();
            return $searchable[$path];
        }
    
        // Remove this and adjust advanced-search-criteria template to use the
        // getCriteria() list and getField()
        function getSearchFields($form=false) {
            $form = $form ?: $this->getForm();
            $searchable = $this->getCurrentSearchFields();
            $info = array();
            foreach ($form->getFields() as $f) {
                if (substr($f->get('name'), -7) == '+search') {
                    $name = substr($f->get('name'), 0, -7);
                    $value = null;
                    // Determine the search method and fetch the original field
                    if (($M = $form->getField("{$name}+method"))
                        && ($method = $M->getClean())
                        && (list(,$field) = $searchable[$name])
                    ) {
                        // Request the field to generate a search Q for the
                        // search method and given value
                        if ($value = $form->getField("{$name}+{$method}"))
                            $value = $value->getClean();
                    }
                    $info[$name] = array(
                        'field' => $field,
                        'method' => $method,
                        'value' => $value,
                        'active' =>  $f->getClean(),
                    );
                }
            }
            return $info;
        }
    
        /**
         * Take the criteria from the SavedSearch fields setup and isolate the
         * field name being search, the method used for searhing, and the method-
         * specific data entered in the UI.
         */
    
        static function isolateCriteria($criteria, $base='Ticket') {
    
    
            if (!is_array($criteria))
    
    
            $items = array();
            $searchable = static::getSearchableFields($base);
    
            foreach ($criteria as $k=>$v) {
                if (substr($k, -7) === '+method') {
                    list($name,) = explode('+', $k, 2);
                    if (!isset($searchable[$name]))
                        continue;
    
                    // Require checkbox to be checked too
                    if (!$criteria["{$name}+search"])
                        continue;
    
                    // Lookup the field to search this condition
                    list($label, $field) = $searchable[$name];
    
                    // Get the search method
                    $method = is_array($v) ? key($v) : $v;
    
                    // Not all search methods require a value
                    $value = $criteria["{$name}+{$method}"];
    
                    $items[] = array($name, $method, $value);
                }
            }
    
            if (isset($criteria[':keywords'])
                && ($kw = $criteria[':keywords'])
            ) {
                $items[] = array(':keywords', null, $kw);
    
        function getConditions() {
            if (!isset($this->_conditions)) {
                $this->getCriteria();
                $conds = array();
                if (is_array($this->criteria)
                    && isset($this->criteria['conditions'])
                ) {
                    $conds = $this->criteria['conditions'];
                }
                foreach ($conds as $C)
                    if ($T = QueueColumnCondition::fromJson($C))
                        $this->_conditions[] = $T;
            }
            return $this->_conditions;
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function getExportableFields() {
            $cdata = $fields = array();
            foreach (TicketForm::getInstance()->getFields() as $f) {
                // Ignore core fields
                if (in_array($f->get('name'), array('priority')))
                    continue;
                // Ignore non-data fields
                elseif (!$f->hasData() || $f->isPresentationOnly())
                    continue;
    
                $name = $f->get('name') ?: 'field_'.$f->get('id');
                $key = 'cdata.'.$name;
                $cdata[$key] = $f->getLocal('label');
            }
    
            // Standard export fields if none is provided.
            $fields = array(
                    'number' =>         __('Ticket Number'),
                    'created' =>        __('Date Created'),
                    'cdata.subject' =>  __('Subject'),
                    'user.name' =>      __('From'),
                    'user.default_email.address' => __('From Email'),
                    'cdata.:priority.priority_desc' => __('Priority'),
                    'dept::getLocalName' => __('Department'),
                    'topic::getName' => __('Help Topic'),
                    'source' =>         __('Source'),
                    'status::getName' =>__('Current Status'),
                    'lastupdate' =>     __('Last Updated'),
    
                    'est_duedate' =>    __('SLA Due Date'),
                    'duedate' =>        __('Due Date'),
    
                    'closed' =>         __('Closed Date'),
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'isoverdue' =>      __('Overdue'),
                    'isanswered' =>     __('Answered'),
                    'staff::getName' => __('Agent Assigned'),
                    'team::getName' =>  __('Team Assigned'),
                    'thread_count' =>   __('Thread Count'),
    
                    'reopen_count' =>   __('Reopen Count'),
    
    Peter Rotich's avatar
    Peter Rotich committed
                    'attachment_count' => __('Attachment Count'),
                    ) + $cdata;
    
            return $fields;
        }
    
    
        function getExportFields($inherit=true) {
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            $fields = array();
    
            if ($inherit
                && $this->parent_id
    
    Peter Rotich's avatar
    Peter Rotich committed
                && $this->hasFlag(self::FLAG_INHERIT_EXPORT)
                && $this->parent
            ) {
                $fields = $this->parent->getExportFields();
            }
            elseif (count($this->exports)) {
    
                foreach ($this->exports as $f)
                    $fields[$f->path] = $f->getHeading();
    
    Peter Rotich's avatar
    Peter Rotich committed
            }
            elseif ($this->isAQueue())
    
                $fields = $this->getExportableFields();
    
            if (!count($fields))
                $fields = $this->getExportableFields();
    
            return $fields;
    
        function getStandardColumns() {
            return $this->getColumns();
        }
    
    
        function getColumns($use_template=false) {
    
            if ($this->columns_id
                && ($q = CustomQueue::lookup($this->columns_id))
            ) {
                // Use columns from cited queue
                return $q->getColumns();
            }
            elseif ($this->parent_id
    
                && $this->hasFlag(self::FLAG_INHERIT_COLUMNS)
                && $this->parent
            ) {
    
                $columns = $this->parent->getColumns();
                foreach ($columns as $c)
                    $c->setQueue($this);
                return $columns;
    
            elseif (count($this->columns)) {
                return $this->columns;
    
            // Use the columns of the "Open" queue as a default template
            if ($use_template && ($template = CustomQueue::lookup(1)))
                return $template->getColumns();
    
    
            // Last resort — use standard columns
    
            foreach (array(
                QueueColumn::placeholder(array(
    
    Peter Rotich's avatar
    Peter Rotich committed
                    "id" => 1,
    
                    "heading" => "Number",
                    "primary" => 'number',
                    "width" => 85,
    
    Peter Rotich's avatar
    Peter Rotich committed
                    "bits" => QueueColumn::FLAG_SORTABLE,
    
                    "filter" => "link:ticketP",
                    "annotations" => '[{"c":"TicketSourceDecoration","p":"b"}]',
                    "conditions" => '[{"crit":["isanswered","set",null],"prop":{"font-weight":"bold"}}]',
                )),
    
                QueueColumn::placeholder(array(
    
                    "heading" => "Created",
                    "primary" => 'created',
    
                    "filter" => 'date:full',
                    "truncate" =>'wrap',
                    "width" => 120,
    
    Peter Rotich's avatar
    Peter Rotich committed
                    "bits" => QueueColumn::FLAG_SORTABLE,
    
                QueueColumn::placeholder(array(
    
                    "heading" => "Subject",
                    "primary" => 'cdata__subject',
                    "width" => 250,
    
    Peter Rotich's avatar
    Peter Rotich committed
                    "bits" => QueueColumn::FLAG_SORTABLE,
    
                    "filter" => "link:ticket",
                    "annotations" => '[{"c":"TicketThreadCount","p":">"},{"c":"ThreadAttachmentCount","p":"a"},{"c":"OverdueFlagDecoration","p":"<"}]',
    
    Peter Rotich's avatar
    Peter Rotich committed
                    "conditions" => '[{"crit":["isanswered","set",null],"prop":{"font-weight":"bold"}}]',
    
                    "truncate" => 'ellipsis',
                )),
    
                QueueColumn::placeholder(array(
    
                    "heading" => "From",
                    "primary" => 'user__name',
                    "width" => 150,
    
                    "bits" => QueueColumn::FLAG_SORTABLE,
    
                QueueColumn::placeholder(array(
    
                    "heading" => "Priority",
                    "primary" => 'cdata__priority',
                    "width" => 120,
    
                    "bits" => QueueColumn::FLAG_SORTABLE,
    
                QueueColumn::placeholder(array(
    
                    "heading" => "Assignee",
                    "primary" => 'assignee',
                    "width" => 100,
    
                    "bits" => QueueColumn::FLAG_SORTABLE,
    
            ) as $col)
                $this->addColumn($col);
    
            return $this->getColumns();
    
        }
    
        function addColumn(QueueColumn $col) {
            $this->columns->add($col);
            $col->queue = $this;
        }
    
    
        function getSortOptions() {
            if ($this->inheritSorting() && $this->parent) {
                return $this->parent->getSortOptions();
            }
            return $this->sorts;
        }
    
    
        function getDefaultSortId() {
            if ($this->isDefaultSortInherited() && $this->parent
                && ($sort_id = $this->parent->getDefaultSortId())
            ) {
                return $sort_id;
            }
            return $this->sort_id;
        }
    
        function getDefaultSort() {
            if ($this->isDefaultSortInherited() && $this->parent
                && ($sort = $this->parent->getDefaultSort())
            ) {
                return $sort;
            }
            return $this->default_sort;
        }
    
    
        function getStatus() {
    
            return $this->hasFlag(self::FLAG_DISABLED)
                ? __('Disabled') : __('Active');
    
        function getChildren() {
            return $this->children;
        }
    
        function getPublicChildren() {
            return $this->children->findAll(array(
    
                'flags__hasbit' => self::FLAG_QUEUE
    
            ));
        }
    
        function getMyChildren() {
            global $thisstaff;
            if (!$thisstaff instanceof Staff)
                return array();
    
            return $this->children->findAll(array(
                'staff_id' => $thisstaff->getId(),
                Q::not(array(
                    'flags__hasbit' => self::FLAG_PUBLIC
                ))
            ));
        }
    
    
        function export($options=array()) {
    
    Peter Rotich's avatar
    Peter Rotich committed
    
            if (!($query=$this->getBasicQuery()))
                return false;
    
    
            if (!($fields=$this->getExportFields()))
    
    Peter Rotich's avatar
    Peter Rotich committed
                return false;
    
    
    
            $filename = sprintf('%s Tickets-%s.csv',
                    $this->getName(),
                    strftime('%Y%m%d'));
            // See if we have cached export preference
            if (isset($_SESSION['Export:Q'.$this->getId()])) {
                $opts = $_SESSION['Export:Q'.$this->getId()];
                if (isset($opts['fields']))
                    $fields = array_intersect_key($fields,
                            array_flip($opts['fields']));
                if (isset($opts['filename'])
                        && ($parts = pathinfo($opts['filename']))) {
                    $filename = $opts['filename'];
                    if (strcasecmp($parts['extension'], 'csv') !=0)
                        $filename ="$filename.csv";
                }
    
                if (isset($opts['delimiter']))
                    $options['delimiter'] = $opts['delimiter'];
    
            }
    
    
            return Export::saveTickets($query, $fields, $filename, 'csv',
                    $options);
    
        /**
         * Add critiera to a query based on the constraints configured for this
         * queue. The criteria of the parent queue is also automatically added
         * if the queue is configured to inherit the criteria.
         */
        function getBasicQuery() {
    
            if ($this->parent && $this->inheritCriteria()) {
                $query = $this->parent->getBasicQuery();
            }
            else {
                $root = $this->getRoot();
                $query = $root::objects();
            }
    
            return $this->mangleQuerySet($query);
    
        }
    
        /**
         * Retrieve a QuerySet instance based on the type of object (root) of
         * this Q, which is automatically configured with the data and criteria
         * of the queue and its columns.
         *
         * Returns:
         * <QuerySet> instance
         */
    
        function getQuery($form=false, $quick_filter=null) {
    
            // Start with basic criteria
    
            $query = $this->getBasicQuery($form);
    
    
            // Apply quick filter
            if (isset($quick_filter)
                && ($qf = $this->getQuickFilterField($quick_filter))
            ) {
    
                $filter = @self::getOrmPath($this->getQuickFilter(), $query);
    
                $query = $qf->applyQuickFilter($query, $quick_filter,
    
            }
    
            // Apply column, annotations and conditions additions
    
            foreach ($this->getColumns() as $C) {
    
                $query = $C->mangleQuery($query, $this->getRoot());
    
        function getQuickFilter() {
            if ($this->filter == '::' && $this->parent) {
                return $this->parent->getQuickFilter();
            }
            return $this->filter;
        }
    
    
        function getQuickFilterField($value=null) {
    
            if ($this->filter == '::') {
                if ($this->parent) {
                    return $this->parent->getQuickFilterField($value);
                }
            }
            elseif ($this->filter
    
                && ($fields = self::getSearchableFields($this->getRoot()))
    
                && (list(,$f) = @$fields[$this->filter])
    
                && $f->supportsQuickFilter()
            ) {
                $f->value = $value;
                return $f;
            }
        }
    
    
        /**
         * Get a description of a field in a search. Expects an entry from the
         * array retrieved in ::getSearchFields()
         */
        function describeField($info, $name=false) {
    
            $name = $name ?: $info['field']->get('label');
    
            return $info['field']->describeSearch($info['method'], $info['value'], $name);
        }
    
        function mangleQuerySet(QuerySet $qs, $form=false) {
            $qs = clone $qs;
            $searchable = $this->getSupportedMatches();
    
            // Figure out fields to search on
            foreach ($this->getCriteria() as $I) {
                list($name, $method, $value) = $I;
    
                // Consider keyword searching
                if ($name === ':keywords') {
                    global $ost;
                    $qs = $ost->searcher->find($value, $qs, false);
                }
                else {
                    // XXX: Move getOrmPath to be more of a utility
                    // Ensure the special join is created to support custom data joins
                    $name = @static::getOrmPath($name, $qs);
    
                    if (preg_match('/__answers!\d+__/', $name)) {
    
                        $qs->annotate(array($name => SqlAggregate::MAX($name)));
    
                    }
    
                    // Fetch a criteria Q for the query
                    if (list(,$field) = $searchable[$name])
                        if ($q = $field->getSearchQ($method, $value, $name))
                            $qs = $qs->filter($q);
                }
            }
    
    
            return $qs;
        }
    
        function applyDefaultSort($qs) {
            // Apply default sort
            if ($sorter = $this->getDefaultSort()) {
                $qs = $sorter->applySort($qs, false, $this->getRoot());
            }
    
            return $qs;
        }
    
        function checkAccess(Staff $agent) {
    
            return $this->isPublic() || $this->checkOwnership($agent);
        }
    
        function checkOwnership(Staff $agent) {
    
            return ($agent->getId() == $this->staff_id &&
                    !$this->isAQueue());
        }
    
        function isOwner(Staff $agent) {
            return $agent && $this->isPrivate() && $this->checkOwnership($agent);
    
    aydreeihn's avatar
    aydreeihn committed
        function isSaved() {
            return true;
        }
    
    
        function ignoreVisibilityConstraints(Staff $agent) {
    
    aydreeihn's avatar
    aydreeihn committed
            // For searches (not queues), some staff can have a permission to
    
    aydreeihn's avatar
    aydreeihn committed
            return ($this->isASearch()
    
                    && $this->isOwner($agent)
                    && $agent->canSearchEverything());
    
        }
    
        function inheritCriteria() {
            return $this->flags & self::FLAG_INHERIT_CRITERIA;
        }
    
        function inheritColumns() {
            return $this->hasFlag(self::FLAG_INHERIT_COLUMNS);
        }
    
    
        function useStandardColumns() {
            return !count($this->columns);
        }
    
    
    Peter Rotich's avatar
    Peter Rotich committed
        function inheritExport() {
            return ($this->hasFlag(self::FLAG_INHERIT_EXPORT) ||
                    !count($this->exports));
        }
    
    
        function inheritSorting() {
            return $this->hasFlag(self::FLAG_INHERIT_SORTING);
        }
    
    
        function isDefaultSortInherited() {
            return $this->hasFlag(self::FLAG_INHERIT_DEF_SORT);
        }
    
    
        function buildPath() {
            if (!$this->id)
                return;
    
    
            $path = $this->parent ? $this->parent->buildPath() : '';
            return rtrim($path, "/") . "/{$this->id}/";
    
        }
    
        function getFullName() {
            $base = $this->getName();
            if ($this->parent)
                $base = sprintf("%s / %s", $this->parent->getFullName(), $base);
            return $base;
        }
    
    
        function isASubQueue() {
            return $this->parent ? $this->parent->isASubQueue() :
                $this->isAQueue();
        }
    
    
        function isAQueue() {
            return $this->hasFlag(self::FLAG_QUEUE);
        }