/** * End a transaction * * End the current transaction. If there's no active transaction, * raise an error and return false. * * The transaction is really ended when the last started transaction * is closed. For instance, if you call startTransaction() twice and * endTransaction() once, the data will be never committed. * * The error flag indicates if the transaction must be rolled back * ($commit = true) of committed ($commit = false). * * @param bool $commit Commit (if true) or rollback (if false) * @return bool true on success or false on errors */ public function endTransaction($commit) { if ($this->_transaction_ref == 0) { TIP::error('ending a never started transaction'); return false; } --$this->_transaction_ref; if ($this->_transaction_ref > 0) { return true; } $action = $commit ? TIP_TRANSACTION_COMMIT : TIP_TRANSACTION_ROLLBACK; return $this->transaction($action); }
/** * Execute a template file * * Parses and executes a template. * * @param TIP_Template &$template The template to run * @param TIP_Module &$caller The caller module * @return bool true on success or false on errors */ public function run(&$template, &$caller) { $path =& $template->getProperty('path'); // Check for cached result if ($this->caching) { $cache = implode(DIRECTORY_SEPARATOR, array_merge($this->cache_root, $path)); if (is_readable($cache)) { return readfile($cache) !== false; } } // Check for compiled file if ($this->compiling) { $compiled = implode(DIRECTORY_SEPARATOR, array_merge(array('.'), $this->compiled_root, $path)) . '.php'; if (is_readable($compiled)) { return (include $compiled) !== false; } } // No cache or compiled file found: parse and run this template $file = $template->__toString(); isset($this->extension) && ($file .= $this->extension); isset($template->_buffer) || ($template->_buffer = file_get_contents($file)); if ($template->_buffer === false) { TIP::error("unable to read file ({$file})"); return false; } ob_start(); $result = $this->runBuffer($template, $caller); if (is_string($result)) { ob_end_clean(); TIP::error($result); return false; } if ($result == false && isset($cache)) { // Caching requested $dir = dirname($cache); if (is_dir($dir) || mkdir($dir, 0777, true)) { file_put_contents($cache, ob_get_contents(), LOCK_EX); } else { TIP::warning("Unable to create the cache path ({$dir})"); } } elseif (isset($compiled)) { // Compiling requested $result = $this->compileBuffer($template); if (is_string($result)) { // Compilation successfull $dir = dirname($compiled); if (is_dir($dir) || mkdir($dir, 0777, true)) { file_put_contents($compiled, $result, LOCK_EX); } else { TIP::warning("Unable to create the compiled path ({$dir})"); } } } ob_end_flush(); return true; }
/** * Perform the browse query throught a pager * * $params is a string in the form "quanto,query_adds". * * The quanto is the number of rows per page: leave it undefined to disable * the pager. In query_adds you can specify additional SQL commands to * append to the query, such as ORDER clauses. * * This function checks if there is a row more than what specified in the * quanto: this provides a simple way to know whether the 'NEXT' button * must be rendered or not. */ protected function tagPager($params) { if (is_null($this->_pager_conditions)) { TIP::error('no active browse action'); return null; } @(list($quanto, $query_template) = explode(',', $params, 2)); $quanto = (int) $quanto; $pager = $quanto > 0; if (empty($this->_pager_conditions)) { } elseif (is_array($this->_pager_conditions)) { $conditions = array(); foreach ($this->_pager_conditions as $id => $value) { $conditions[] = $this->getData()->addFilter('', $id, $value); } $filter = 'WHERE (' . implode(' AND ', $conditions) . ')'; } elseif (empty($this->search_field)) { $filter = $this->_pager_conditions; } else { is_string($this->search_field) && ($this->search_field = explode(',', $this->search_field)); $this->_search_tokens = explode(' ', $this->_pager_conditions); $pattern = '%' . implode('%', $this->_search_tokens) . '%'; $conditions = array(); foreach ($this->search_field as $id) { $conditions[] = $this->getData()->addFilter('', $id, $pattern, 'LIKE'); } $filter = 'WHERE (' . implode(' OR ', $conditions) . ')'; } if (isset($filter)) { $filter .= ' ' . $query_template; } else { $filter = $query_template; } $filter .= $this->getData()->order($this->default_order); if ($pager) { $offset = TIP::getGet('pg_offset', 'int'); $offset > 0 || ($offset = 0); $filter .= $this->getData()->limit($quanto + 1, $offset); } else { $offset = 0; } if (is_null($view = $this->startDataView($filter))) { TIP::notifyError('select'); $this->_search_tokens = null; return null; } ob_start(); if (!$view->isValid()) { $this->tryRun(array($main_id, $this->pager_empty_template)); } else { $main_id = TIP_Application::getGlobal('id'); $partial = $pager && $view->nRows() == $quanto + 1; if ($partial) { // Remove the trailing row from the view $rows =& $view->getProperty('rows'); array_splice($rows, $quanto); } if ($pager) { if ($offset > 0) { $this->keys['PREV'] = TIP::modifyActionUri(null, null, null, array('pg_offset' => $offset - $quanto > 0 ? $offset - $quanto : 0)); } if ($partial) { $this->keys['NEXT'] = TIP::modifyActionUri(null, null, null, array('pg_offset' => $offset + $quanto)); } $pager = $partial || $offset > 0; } // Pager rendering BEFORE the rows $pager && $this->tryRun(array($main_id, $this->pager_pre_template)); // Rows rendering $empty = true; $path = array($this->id, $this->pager_template); foreach ($view as $row) { $this->run($path); $empty = false; } // Empty result set $empty && $this->tryRun(array($main_id, $this->pager_empty_template)); // Pager rendering AFTER the rows $pager && $this->tryRun(array($main_id, $this->pager_post_template)); } $this->endView(); $this->_search_tokens = null; return ob_get_clean(); }
private function _addRule($id, $type, $format = '') { // Add the format as context to getLocale (in case the localized message // will embed any format field) if (is_array($format)) { $context = $format; } elseif (!empty($format)) { $context[0] = $format; } else { $context = null; } $message = $this->getLocale('rule.' . $type, $context); $result = $this->_form->addRule($id, $message, $type, $format, $this->validation); if (PEAR::isError($result)) { TIP::error($result->getMessage()); } }
public function query() { $pieces = func_get_args(); $query = implode(' ', $pieces); $result = mysql_query($query, $this->_connection); if ($result === false) { TIP::error(mysql_error($this->_connection) . " ({$query})"); } return $result; }
/** * Echo a row descriptor * * Outputs the full path (as generated by the row renderer) of the * row with $params id. */ protected function tagDescriptor($params) { if (empty($params)) { TIP::error('no row specified'); return null; } elseif (is_null($renderer = $this->_getRenderer())) { return null; } $rows =& $renderer->toArray(); if (!array_key_exists($params, $rows)) { TIP::error("row not found ({$params})"); return null; } return $rows[$params]; }
/** * Check if a value is in a list * * $params is a string in the form "needle,value1,value2,...". * * Outputs true if needle is present in the comma separated list of values. * Useful to check if a value is contained (that is, if it is selected) in * a "set" or "enum" field. */ protected function tagInList($params) { $pos = strpos($params, ','); if ($pos === false) { TIP::error("invalid inList parameter ({$params})"); return null; } $needle = substr($params, 0, $pos); $list = explode(',', substr($params, $pos + 1)); return in_array($needle, $list) ? 'true' : 'false'; }
/** * Get the child module * * Checks for the child module existence and caches the request. * If $class is not specified, no attempts are made to get the * child module: only the cache is returned or an error is raised. * * This method provides also a way to validate the data engine, * that **must** be shared between this module and the child one * to allow //transation protected// commits. * * @param string|null $class The class to use * @return TIP_Content|null|false The requested child module, * null if not needed or * false on errors * @internal */ private function &_getChildModule($class = null) { // The true value is used as "uncached" value static $child = true; // Check for cached result if ($child !== true) { return $child; } // Check for request without $class (no autodiscovering) if (is_null($class)) { TIP::error('No previous child request performed'); $error = false; return $error; } // Check if the child module is required $child = TIP_Type::getInstance($this->id . '-' . $class, false); if (is_null($child)) { // No child module needed return $child; } // Get and check the child module $child_data = $child->getProperty('data'); if (!$child_data instanceof TIP_Data) { TIP::error("the child module has no data (child = {$class})"); $child = false; } elseif ($child_data->getProperty('engine') != $this->data->getProperty('engine')) { TIP::error("master and child data must share the same data engine (child = {$class})"); $child = false; } return $child; }
private function &_getRows(&$data, $fields) { $path = $data->getProperty('path'); if (!array_key_exists($path, $this->_rows)) { if (strncmp($path, 'http://', 7) == 0) { $uri = $path; if (function_exists('curl_init')) { // CURL extension available: this should be the // first attempt because the dumb 'open_basedir' // directive can f**k up file_get_contents() $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $uri); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $xml_data = curl_exec($curl); curl_close($curl); } else { if (in_array('http', stream_get_wrappers())) { // http wrapper present $xml_data = file_get_contents($uri); } else { // No viable way to use the http protocol $xml_data = false; } } } else { $uri = TIP::buildDataPath($data->getProperty('path')); $xml_data = file_get_contents($uri); } $xml_tree = false; if (is_string($xml_data)) { // Work-around to let SimpleXML be happy with the f*****g // default namespace $xml_data = str_replace(' xmlns=', ' fakens=', $xml_data); $xml_tree = @simplexml_load_string($xml_data); } if ($xml_tree) { // Takes only the first element matching "base_xpath" $xml = reset($xml_tree->xpath($this->base_xpath)); $this->_data =& $data; if (empty($fields)) { $this->_fields = array_keys($this->fields_xpath); } else { $this->_fields = $fields; } $nodes = $xml->xpath($this->row_xpath); $rows = $this->_nodesToRows($nodes); unset($nodes, $this->_fields, $this->_data); } else { $rows = array(); TIP::error("failed to load XML file ({$uri})"); } $this->_rows[$path] = $rows; } return $this->_rows[$path]; }
private function _validate(&$row) { $this->getFields(false); // Keep only the keys that are fields $row = array_intersect_key($row, $this->_fields); if (isset($this->fieldset)) { // Apply the fieldset filter $row = array_intersect_key($row, array_flip($this->fieldset)); } // Check for empty set if (empty($row)) { TIP::error('no valid field found'); return false; } // Cast the set to the proper type $this->_castRow($row); return true; }
/** * Get a localized text * * Given an $id and a $prefix, retrieves the localized text (in the locale * specified by the 'locale' option of the main TIP_Application) binded to * "$prefix.$id". This means the data object of TIP_Locale must have a * series of rows with prefix.id values as the primary key. * * The choice of splitting the key in $prefix and $id allows to perform * a sort of cached read, that is, to avoid multiple queries, a single * request for a specified prefix gets all the id of this prefix. * If you are sure the other id are not used (such as for the TIP_Notify * module) you can disable the cache by passig false to $cached. * * The $context associative array contains a series of key=>value pairs * that can be substituted in the localized text. The get() method will * search in the localized text for any key enclosed by '|' and will put * the corresponding value. For instance, if there is a 'size'=>200 in * the $context array, the text 'Max allowed size is |size|...' will * expand to 'Max allowed size is 200...'. * * @param string $id The identifier * @param string $prefix The prefix * @param array $context A context associative array * @param bool $cached Whether to perform or not a cached read * @return string|null The localized text or null if not found */ public function get($id, $prefix, $context, $cached) { $row_id = $prefix . '.' . $id; if (array_key_exists($row_id, $this->_cache)) { // Localized text found in the TIP_Locale cache $row =& $this->_cache[$row_id]; } elseif ($cached) { $filter = $this->data->filter('id', $prefix . '.%', 'LIKE'); if (is_null($view =& $this->startDataView($filter))) { TIP::error("no way to get localized text ({$row_id})"); return null; } $this->_cache += $view->getProperty('rows'); $this->endView(); if (array_key_exists($row_id, $this->_cache)) { $row =& $this->_cache[$row_id]; } else { // $row_id not found $this->_cache[$row_id] = null; return null; } } else { $row =& $this->data->getRow($row_id); if (is_null($this->_cache[$row_id] = $row)) { // $row_id not found return null; } } $text = $row[$this->locale]; if (is_null($context) || strpos($text, '|') === false) { return $text; } // There are some embedded keys to expand ... $token = explode('|', $text); foreach ($token as $n => $value) { // Odd tokens are keys if ($n & 1) { $token[$n] = array_key_exists($value, $context) ? $context[$value] : ''; } } return implode($token); }
/** * 'on_process' callback for refresh actions * * Posticipates the expiration date using the current date as * reference. * * @param array &$row The row to update * @return bool true on success or false on errors */ public function _onRefresh(&$old_row) { if (!isset($this->expiration_field)) { // Undefined expiration field: returns true silently return true; } $expiration = strtotime($this->expiration); if ($expiration === false) { TIP::error("invalid expiration value ({$this->expiration})"); return false; } $row[$this->expiration_field] = TIP::formatDate('datetime_sql', $expiration); return $this->_onEdit($row, $old_row); }