options as $key => $value) { $this->$key = $value; } $this->type = $type; $this->qt = $qt; $this->options = &$qt->options; } static function typeFor($variable) { $wrapper = entity_metadata_wrapper('node', $variable); $is_categorical = count($wrapper->field_variable_categories->value()) > 1; $type = $wrapper->field_value_type->value(); if ($is_categorical) { return 'CategoryTerm'; } if ($type == 'integer' || $type == 'decimal') { return 'RangeTerm'; } return 'TodoTerm'; } function getType() { return $this->type; } function getName() { return $this->qt->id; } function getFieldName() { return $this->getVariable()->title; } function isInverted() { return $this->not; } function setInverted($inverted) { $this->not = $inverted; return $this->options['not'] = $inverted; } function isRepeatable() { $variable = $this->getVariable(); return isset($variable->field_repeatable[LANGUAGE_NONE][0]['value']) && $variable->field_repeatable[LANGUAGE_NONE][0]['value'] === '1'; } function getOptions() { return $this->options; } function toString() { return $this->getFieldName(); } /** * array( * study_id => connector->facetTerm(), * study_id => connector->facetTerm(), * ... * ) */ function stats() { // One column for the category name and one per study $facets = array(); $connectors = $this->getConnectors(); foreach ($connectors as $connector) { try { $facets[$connector->study_id] = $connector->facetTerm($this); } catch (Exception $e) { $connector->displayError($e); } } return $facets; } protected function getVariable() { return node_load($this->qt->variable_id); } protected function getConnectors() { $connectors = array(); $dataset_id = NULL; if (!empty($this->qt->query_id)) { $query = mica_dataset_query_load($this->qt->query_id); $dataset_id = $query->dataset_id; } elseif (!empty($this->qt->dataset_id)) { $dataset_id = $this->qt->dataset_id; } if (isset($dataset_id)) { $connectors = mica_dataset_connector_query_multiple($dataset_id, TRUE); } return $connectors; } function hasConnectors() { return count($this->getConnectors()) > 0; } protected function getStudyName($study_id) { $study = node_load($study_id); $study_wrapper = entity_metadata_wrapper('node', $study); $acronym = $study_wrapper->field_acroym->value(); return !empty($acronym) ? $study_wrapper->field_acroym->value() : $study_wrapper->title->value(); } } /** * Recognizes the following options structure: * { * categories: [ '1', '2', '3' ], * operator : 'OR' * } * where * categories is a list of categories names (can be empty or null) * operator is either 'AND' or 'OR' (can be empty or null) */ class CategoryTerm extends AbstractTerm { function __construct(MicaDatasetQueryTerm $qt) { parent::__construct('CategoryTerm', $qt); } function categories() { return isset($this->options['categories']) ? $this->options['categories'] : array(); } function setCategories($categories = array()) { $this->options['categories'] = $categories; } function operator() { return isset($this->options['operator']) ? $this->options['operator'] : 'OR'; } function view() { $variable = $this->getVariable(); $headers = array('Value'); $stats = $this->stats(); foreach ($stats as $study_id => $stat) { $headers[] = $this->getStudyName($study_id); } $rows = array(); $wrapper = entity_metadata_wrapper('node', $variable); foreach ($wrapper->field_variable_categories->value() as $category) { $category_name = $category["name"]; $row = array($category_name); foreach ($stats as $study_id => $stat) { $row[] = isset($stat[$category_name]) ? $stat[$category_name] : 0; } $rows[] = $row; } $row = array( array( 'data' => t('All'), 'class' => array('active') ) ); foreach ($stats as $study_id => $stat) { $row[] = array( 'data' => isset($stat['_all']) ? $stat['_all'] : 0, 'class' => array('active') ); } $rows[] = $row; return theme('table', array('header' => $headers, 'rows' => $rows, 'empty' => t('No studies available'))); } function form($form, &$form_state) { $form['inverted'] = array( '#title' => t('Operator'), '#type' => 'select', '#options' => array('in' => 'in', 'notin' => 'not in'), '#default_value' => $this->isInverted() ? 'notin' : 'in', ); // One column for the category name and one per study $headers = array('Value'); $connectors = $this->getConnectors(); foreach ($connectors as $connector) { $study = node_load($connector->study_id); $headers[] = $study->title; } $defaultValues = array(); $options = array(); $variable = $this->getVariable(); $facets = $this->stats(); $wrapper = entity_metadata_wrapper('node', $variable); foreach ($wrapper->field_variable_categories->value() as $category) { $category_name = $category["name"]; $defaultValues[$category_name] = 0; $row = array($category_name); foreach ($connectors as $connector) { if (isset($facets[$connector->study_id][$category_name])) { $row[] = $facets[$connector->study_id][$category_name]; } else { $row[] = 0; } } $options['c_' . $category_name] = $row; } $form['valuecontainer']['fieldset'] = array( '#type' => 'fieldset', '#title' => !empty($connectors) ? 'Values and Statistics' : 'Values', ); $form['valuecontainer']['fieldset']['categories'] = array( '#type' => 'tableselect', '#header' => $headers, '#options' => $options, '#default_value' => $this->categoriesToForm($defaultValues), ); return $form; } function validate($form, &$form_state) { } function submit($form, &$form_state) { $selection = $form_state['values']['categories']; $inverted = $form_state['values']['inverted']; $this->setInverted($inverted == 'notin'); $this->setCategories($this->formToCategories($selection)); } function toString() { $str = parent::toString(); if (count($this->categories()) > 0) { switch ($this->operator()) { case 'AND': $str .= $this->isInverted() ? ' is not ' : ' is '; break; default: $str .= $this->isInverted() ? ' not in ' : ' in '; break; } $str .= '(' . implode(', ', $this->categories()) . ')'; } return $str; } /** * Returns an array category => 0/1 */ private function categoriesToForm($rows) { foreach ($this->categories() as $c) { $rows['c_' . $c] = 1; } return $rows; } /** * Undoes the categoriesToForm() function * @param unknown_type $formCategories */ private function formToCategories($formCategories) { // array_filter will remove all non-selected categories // array_keys will return only the array keys $selected = array_keys(array_filter($formCategories)); $categories = array(); foreach ($selected as $c) { $categories[] = substr($c, 2); } return $categories; } } /** * { * ranges : [ {from:10, to: 40}, {value:42} ] * } */ class RangeTerm extends AbstractTerm { function __construct(MicaDatasetQueryTerm $qt) { parent::__construct('RangeTerm', $qt); } public function ranges() { return isset($this->options['ranges']) ? $this->options['ranges'] : array(); } function view() { $headers = array('Study', 'Min', 'Max', 'Mean', 'Std. Dev', 'Count'); $rows = array(); $stats = $this->stats(); foreach ($stats as $study_id => $stat) { $rows[] = array( $this->getStudyName($study_id), $this->f($stat['min']), $this->f($stat['max']), $this->f($stat['mean']), $this->f($stat['std_deviation']), $stat['count'] ); } return theme('table', array('header' => $headers, 'rows' => $rows, 'empty' => t('No studies available'))); } function f($number) { // TODO: figure out how to specify significant digits instead of # of decimals return is_double($number) ? number_format($number, 3) : $number; } function form($form, &$form_state) { $variable = $this->getVariable(); $type = $variable->field_value_type[LANGUAGE_NONE][0]['value']; $operator = isset($form_state['values']['operator']) ? $form_state['values']['operator'] : NULL; $form['inverted'] = array( '#title' => t('Operator'), '#type' => 'select', '#options' => array('in' => 'in', 'notin' => 'not in'), '#default_value' => $this->isInverted() ? 'notin' : 'in', ); $form['valuecontainer'] = array( '#type' => 'item', '#prefix' => '