/** * Generate SQL to enumerate the records of an entity, returning the resulting * rows and related pagination data. * * @param array $params Parameters that will be used to generate the resulting SQL. * These parameters are typically provided by the entity's model, * usually in a method called list_params(). * @param boolean $set_vars Automatically set template variables: (data, totalrows, curpage, perpage) * @return array An array of: (data, total rows, current page, records per page) */ function enumerate($params, $set_vars = false) { // move the list parameters into the local namespace // (from,exprs,gexprs,select,where,group_by,having,order,limit) extract($params, EXTR_OVERWRITE); assert_type($exprs, 'array'); assert_type($gexprs, 'array'); foreach ($exprs as $k => $v) { $select .= ",{$v} \"{$k}\""; } foreach ($gexprs as $k => $v) { $select .= ",{$v} \"{$k}\""; } // if $params[select] was empty, then we have a leading comma... if (substr($select, 0, 1) == ',') { $select = substr($select, 1); } // Build WHERE/HAVING clauses from list params and // criteria sent from the browser list($w_sql, $w_args, $h_sql, $h_args) = $this->filter($exprs, $gexprs); // WHERE if (is_array($where)) { foreach ($where as $v) { $w_sql .= " AND ({$v})"; } } else { if (!empty($where)) { $w_sql .= " AND ({$where})"; } } // backwards compatibility if (!empty($where_args)) { $w_args = array_merge($w_args, $where_args); } // HAVING if (is_array($having)) { foreach ($having as $v) { $h_sql .= " AND ({$v})"; } } else { if (!empty($having)) { $h_sql .= " AND ({$having})"; } } // Merge all SQL args if necessary ($h_args could be empty) $args = empty($h_args) ? $w_args : array_merge($w_args, $h_args); // ORDER/LIMIT $sort_sql = $this->sort($order, $exprs); $page_sql = $this->paginate($limit); // Get data rows if ($this->db->type == 'mysql') { $select = "SQL_CALC_FOUND_ROWS {$select}"; } $sql = $this->db->build_sql($select, $from, $w_sql, $group_by, $h_sql, $sort_sql, $page_sql); $data = $this->db->get_all($sql, $args); // Count all matching rows if ($this->db->type == 'mysql') { $ttlrows = $this->db->get_value("SELECT FOUND_ROWS()"); } else { $ttlrows = $this->db->get_value($this->db->build_sql("COUNT(*)", $from, $w_sql, $group_by, $h_sql), $args); } if ($set_vars) { $this->page->template->set('data', $data); $this->page->template->set('totalrows', $ttlrows); $this->page->template->set('curpage', $this->page->param('p_p', 1)); $this->page->template->set('perpage', $this->page->param('p_pp', $limit)); } // data, total rows, current page, per page return array($data, $ttlrows, $this->page->param('p_p', 1), $this->page->param('p_pp', $limit)); }
/** * Generate a multi-column form. * * @param array $params General form parameters * @param array $data Values to prefill form elements with * @param array $errors Errors to attach to elements containing invalid data * * Form Parameters: * - form_name () :: form name * - form_id () :: form id * - class (form) :: CSS class to use for outer <div> * - action () * - method (POST) * - enctype () * - data_id () :: PK of data element being edited * - submit :: label on submit button; can be a 2-element array: ('create','update') * - submit_msg :: change button message to this when clicked * - submit_pos :: "left" or "right" (default is "right") * - submit_html :: if set, use this for the submit button code instead of generating it * - options array * - noopen :: don't include opening div/form tags * - noclose :: don't include closing div/form tags or a Submit button * - nosubmit :: don't include a Submit button * - nopk :: don't include the name="id" hidden element * - toplabel :: put labels above elements instead of beside them * - numcols :: number of columns to use for form elements (1) * - hide_misc_errors :: don't show errors that aren't bound to an element in the form definition * - spinner :: image to show beside submit button when clicked * - layout array * - <column name> :: array(colspan<int>, label_width<int|"auto">, width<int>) * - elements array * - <name> => array * - prompt :: label to display beside form element * - type :: element type (see the _show_element() method for types) * - value :: value of element * - error :: error message associated with element * - help :: context-specific help message (popup) * - help_image :: icon image to use for help popups * - after :: content (HTML) to be added after the element * - before :: content (HTML) to be added before the element * - attribs :: array of additional html attributes * - type-specific parameters (size, maxlength, rows, cols, etc) */ function build_form($params, $data = array(), $errors = array()) { $guid = ++$this->guid; $class = $this->_getparam($params, 'class', 'form'); $method = $this->_getparam($params, 'method', 'post'); $submit = $this->_getparam($params, 'submit', __('Save Changes')); $options = $this->_getparam($params, 'options', array()); $data_id = $this->_getparam($params, 'data_id', 0); $layout = $this->_getparam($params, 'layout', array()); $elements = $this->_getparam($params, 'elements', array()); $form_id = $this->_getparam($params, 'form_id', 'form' . $guid); $form_name = $this->_getparam($params, 'form_name', 'form' . $guid); $action = $this->_getparam($params, 'action', ''); // this is used to pass the data ID back to all form element generators; it // will be unset once build_form() is done so it's not accidentally used // for form element calls that originate outside of build_form() $this->data_id = $data_id; if (!$options['noopen']) { // <form> $form_attribs = array('id' => $form_id); if (isset($params['enctype'])) { $form_attribs['enctype'] = $params['enctype']; } $out .= $this->open_form($form_name, $action, $method, $errors, $form_attribs); // <div> $out .= '<div'; if ($class) { $out .= ' class="' . $class . '"'; } $out .= ">\n"; } $numcols = $this->_getparam($options, 'numcols', 1); // if numcols is one, then we might have to massage the elements array into // the proper multi-column-friendly format $first_key = key($elements); $first_val = current($elements); if (isset($first_val['type']) && !is_array($first_val['type'])) { $elements = array('0' => $elements); } // if we're editing a record (as opposed to creating one), then load in // the PK of the data element if ($data_id && !$options['nopk']) { $elements[$first_key]['id'] = array('type' => 'hidden', 'value' => $data_id); } // check for a happy layout definition assert_type($layout, 'array'); foreach ($elements as $col => $subform) { assert_type($layout[$col], 'array'); if (!isset($layout[$col]['colspan'])) { $layout[$col]['colspan'] = 1; } if (!isset($layout[$col]['label_width'])) { $layout[$col]['label_width'] = 'auto'; } } // set element layout $elem_layout = $options['toplabel'] ? 'toplabel' : 'leftlabel'; // Prefill form values and errors foreach ($elements as $column => $elems) { foreach ($elems as $name => $elem) { if (isset($elem['value']) && $elem['type'] == 'checkbox') { if ($data[$name] == $elem['value']) { $elements[$column][$name]['checked'] = true; } } else { if (!isset($elem['value']) && isset($data[$name])) { // don't prefill a date field if the value is '0000-00-00' if (!($elements[$column][$name]['type'] == 'date' && $data[$name] == '0000-00-00')) { $elements[$column][$name]['value'] = $data[$name]; } } } unset($errors[$name]); } } // If there are any errors not bound to a form element, display them at // the top if (count($errors) && !$options['hide_misc_errors']) { $out .= $this->error_box($errors); } // form elements (hidden) foreach ($elements as $column => $subform) { foreach ($subform as $name => $elem) { if ($elem['type'] != 'hidden') { continue; } $out .= $this->hidden($name, $elem['value']); } } $c_loc = 0; $clearnext = true; foreach ($elements as $col_id => $subform) { // the <br> fixes some float issues in IE if ($clearnext && $c_loc > 0) { $out .= '<br class="clearfix" />'; } $c_loc += $layout[$col_id]['colspan']; if (isset($layout[$col_id]['width'])) { $width = $layout[$col_id]['width']; if (is_numeric($width)) { $width .= 'px'; } } else { $width = floor($layout[$col_id]['colspan'] / $numcols * 100) . '%'; } $cls = $c_loc % $numcols == 0 ? ' dynsize' : ''; if ($layout[$col_id]['colspan']) { $cls .= " {$layout[$col_id]['class']}"; } $out .= '<div class="subform' . $cls . '" style="width:' . $width . ';'; if ($clearnext) { $out .= 'clear:left;'; $clearnext = false; } else { if ($c_loc % $numcols == 0) { // IE6 workaround: use clear:both if $numcols==1 $out .= $numcols == 1 ? 'clear:both' : 'clear:right;'; $clearnext = true; } } $out .= '">'; // calculate optimal width of the label divs if (!isset($layout[$col_id]['label_width'])) { $layout[$col_id]['label_width'] = 'auto'; } $lblwidth = $layout[$col_id]['label_width']; if ($elem_layout == 'leftlabel' && $lblwidth == 'auto') { if ($layout[$col_id]['label_width'] == 'auto') { // find the longest label to determine width of the label divs $llength = 0; foreach ($subform as $name => $elem) { if ($elem['type'] == 'label') { continue; } if (!isset($elem['prompt'])) { continue; } $s = mb_strlen(strip_tags($elem['prompt'])); if ($s > $llength) { $llength = $s; } } $lblwidth = $llength . 'em'; } } if (is_numeric(substr($lblwidth, -1, 1))) { $lblwidth .= 'px'; } foreach ($subform as $name => $elem) { if ($elem['type'] == 'hidden') { continue; } $subvars = array('help' => '', 'label' => '', 'element' => '', 'error' => '', 'lblwidth' => $lblwidth); $attribs = $this->_getparam($elem, 'attribs', array()); // set the "error" class if validation failed if (isset($elem['error'])) { $attribs['class'] = 'error'; } // set the tabindex relative to the column we're in $attribs['tabindex'] = $c_loc; // tooltip if (isset($elem['help'])) { $img = isset($elem['help_image']) ? $elem['help_image'] : ''; $subvars['help'] = $this->tooltip($elem['help'], $img); if ($elem_layout == 'toplabel') { $subvars['help'] .= ' '; } } else { if ($elem_layout == 'leftlabel') { $subvars['help'] = ' '; } } // label, element, error if (empty($elem['prompt'])) { $elem['prompt'] = ' '; } $subvars['label'] = '<label for="' . $name . '">' . $elem['prompt'] . '</label>'; $subvars['element'] = $this->_show_element($name, $elem, $attribs); if (!empty($elem['error'])) { $subvars['error'] = '<br /><p class="error">' . $elem['error'] . '</p>'; } if ($elem['type'] == 'label') { $el = $this->element_layouts['labelonly']; } else { if ($elem['nolabel'] == true) { $el = $this->element_layouts['elemonly']; } else { $el = $this->element_layouts[$elem_layout]; } } // sub in vars and output this element $out .= str_replace(array('{{HELP}}', '{{LABEL}}', '{{ELEMENT}}', '{{ERROR}}', '{{LBLWIDTH}}'), $subvars, $el); } $out .= "</div>\n"; } if (!$options['noclose'] && !$options['nosubmit']) { $c = array_shift(explode(' ', $class)); if (empty($params['submit_pos']) || $params['submit_pos'] == 'right') { // TODO: This is ugly, fix it. $js = <<<EOT var elem_width = 0; try { var parent = \$('#{$form_id} div.{$c}').position(); } catch(err) { return; } \$('#{$form_id} div.dynsize').find('div.form_element').each(function(){ \tvar pos = \$(this).position().left + \$(this).width() - parent.left; \tif(pos > elem_width) elem_width = pos; }); \$('#{$form_id} div.form_submit').css('width', elem_width+'px'); EOT; $this->depends->html->js_run('', $js); } $out .= '<div class="form_submit">'; // If submit_html was used, output the contents directly. Otherwise // build a submit button ourselves. if (isset($params['submit_html'])) { $out .= $params['submit_html']; } else { if (!empty($submit)) { $opts = array(); if (isset($options['spinner'])) { $opts['onClick'] .= "\$(this).prev('img.spinner').css('display','inline');"; $out .= $this->depends->html->image($options['spinner'], array('class' => 'spinner', 'align' => 'top', 'style' => 'display:none')); } if (isset($params['submit_msg'])) { $opts['onClick'] .= "this.value='{$params['submit_msg']}';this.disabled=true;this.form.submit();return false;"; } if (is_array($submit)) { if ($data_id) { $out .= $this->submit('submit_btn' . $guid, $submit[1], $opts); } else { $out .= $this->submit('submit_btn' . $guid, $submit[0], $opts); } } else { $out .= $this->submit('submit_btn' . $guid, $submit, $opts); } } } $out .= "</div>\n"; } if (!$options['noclose']) { $out .= "</div>\n"; $out .= $this->close_form(); } $this->data_id = 0; return $out; }
/** * Return multiple records by PK, optionally ignoring empty/false records. * * @param array $ids * @param boolean $ignore_empty Ignore empty records. * @return array */ function load_all($ids = null, $ignore_empty = true) { if (is_null($ids)) { $ids = $this->find()->get($this->pk); } assert_type($ids, 'array'); $data = array(); foreach ($ids as $id) { $rec = $this->load($id); if ($rec || !$ignore_empty) { $data[] = $rec; } } return $data; }
function _get_ids() { if (!empty($this->id_cache)) { return $this->id_cache; } $ids = $this->get('id'); assert_type($ids, 'array'); $this->id_cache = $ids; return $ids; }
/** * Used by form handlers to redirect back to the calling form after a * failed validation or other errors. * * @param string $url URL to redirect to (leave blank to use referrer) * @param array $data Form data array * @param array $errors Form errors array * @param array $name The unique names used for the form data and errors. * These are used when passing the data back to the * template. Use this if you're handling multiple * forms on one page. The first element is the name * of the form data array, the second is the name * of the form errors array. */ function return_to_form($url, $data, $errors = array(), $name = array('data', 'errors')) { if ($this->ajax) { $vars = array($name[1] => $errors); $this->ajax_render('', $vars); } else { assert_type($_SESSION['form_data'], 'array'); assert_type($_SESSION['form_errors'], 'array'); $_SESSION['form_data'][$name[0]] = $data; $_SESSION['form_errors'][$name[1]] = $errors; if ($url) { $this->redirect($url); } else { $this->redirect_to_referrer(); } } }